From 5cfe38e6ce41bc19ce62d54baaaebc2ea638b6ec Mon Sep 17 00:00:00 2001 From: Meriem-BenIsmail Date: Fri, 17 Jan 2025 18:06:43 +0100 Subject: [PATCH 1/5] path validation for shapefile and image. --- .../base/src/formbuilder/formselectors.ts | 8 ++ .../src/formbuilder/objectform/baseform.tsx | 15 --- .../objectform/fileselectorwidget.tsx | 15 ++- .../formbuilder/objectform/geojsonsource.ts | 17 ++- .../formbuilder/objectform/pathbasedsource.ts | 115 ++++++++++++++++++ packages/base/src/tools.ts | 62 +++++++++- 6 files changed, 210 insertions(+), 22 deletions(-) create mode 100644 packages/base/src/formbuilder/objectform/pathbasedsource.ts diff --git a/packages/base/src/formbuilder/formselectors.ts b/packages/base/src/formbuilder/formselectors.ts index a6d9ee378..570eb0424 100644 --- a/packages/base/src/formbuilder/formselectors.ts +++ b/packages/base/src/formbuilder/formselectors.ts @@ -7,6 +7,7 @@ import { TileSourcePropertiesForm } from './objectform/tilesourceform'; import { VectorLayerPropertiesForm } from './objectform/vectorlayerform'; import { WebGlLayerPropertiesForm } from './objectform/webGlLayerForm'; import { GeoTiffSourcePropertiesForm } from './objectform/geotiffsource'; +import { PathBasedSourcePropertiesForm } from './objectform/pathbasedsource'; export function getLayerTypeForm( layerType: LayerType @@ -36,6 +37,12 @@ export function getSourceTypeForm(sourceType: SourceType): typeof BaseForm { case 'GeoJSONSource': SourceForm = GeoJSONSourcePropertiesForm; break; + case 'ImageSource': + SourceForm = PathBasedSourcePropertiesForm; + break; + case 'ShapefileSource': + SourceForm = PathBasedSourcePropertiesForm; + break; case 'GeoTiffSource': SourceForm = GeoTiffSourcePropertiesForm; break; @@ -45,6 +52,7 @@ export function getSourceTypeForm(sourceType: SourceType): typeof BaseForm { break; // ADD MORE FORM TYPES HERE } + (SourceForm as any).sourceType = sourceType; return SourceForm; } diff --git a/packages/base/src/formbuilder/objectform/baseform.tsx b/packages/base/src/formbuilder/objectform/baseform.tsx index fbd24e623..1660e9aa5 100644 --- a/packages/base/src/formbuilder/objectform/baseform.tsx +++ b/packages/base/src/formbuilder/objectform/baseform.tsx @@ -8,7 +8,6 @@ import { Signal } from '@lumino/signaling'; import { deepCopy } from '../../tools'; import { IDict } from '../../types'; import { Slider, SliderLabel } from '@jupyter/react-components'; -import { FileSelectorWidget } from './fileselectorwidget'; export interface IBaseFormStates { schema?: IDict; @@ -202,20 +201,6 @@ export class BaseForm extends React.Component { this.removeFormEntry(k, data, schema, uiSchema); } } - - // Customize the widget for path field - if (schema.properties && schema.properties.path) { - const docManager = - this.props.formChangedSignal?.sender.props.formSchemaRegistry.getDocManager(); - - uiSchema.path = { - 'ui:widget': FileSelectorWidget, - 'ui:options': { - docManager, - formOptions: this.props - } - }; - } }); } diff --git a/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx b/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx index d152027ac..46c03a35f 100644 --- a/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx +++ b/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { FileDialog } from '@jupyterlab/filebrowser'; import { Dialog } from '@jupyterlab/apputils'; import { CreationFormDialog } from '../../dialogs/formdialog'; @@ -10,9 +10,10 @@ export const FileSelectorWidget = (props: any) => { const [serverFilePath, setServerFilePath] = useState(''); const [urlPath, setUrlPath] = useState(''); + const isTypingURL = useRef(false); // Tracks whether the user is manually typing a URL useEffect(() => { - if (props.value) { + if (!isTypingURL.current && props.value) { if ( props.value.startsWith('http://') || props.value.startsWith('https://') @@ -85,12 +86,17 @@ export const FileSelectorWidget = (props: any) => { }; const handleURLChange = (event: React.ChangeEvent) => { - const url = event.target.value; - setServerFilePath(''); + const url = event.target.value.trim(); + isTypingURL.current = true; setUrlPath(url); + setServerFilePath(''); props.onChange(url); }; + const handleURLBlur = () => { + isTypingURL.current = false; + }; + return (
@@ -114,6 +120,7 @@ export const FileSelectorWidget = (props: any) => { id="root_path" className="jp-mod-styled" onChange={handleURLChange} + onBlur={handleURLBlur} value={urlPath || ''} style={{ width: '100%' }} /> diff --git a/packages/base/src/formbuilder/objectform/geojsonsource.ts b/packages/base/src/formbuilder/objectform/geojsonsource.ts index 7526190e2..3fd186583 100644 --- a/packages/base/src/formbuilder/objectform/geojsonsource.ts +++ b/packages/base/src/formbuilder/objectform/geojsonsource.ts @@ -6,6 +6,7 @@ import * as geojson from '@jupytergis/schema/src/schema/geojson.json'; import { BaseForm, IBaseFormProps } from './baseform'; import { loadFile } from '../../tools'; +import { FileSelectorWidget } from './fileselectorwidget'; /** * The form to modify a GeoJSON source. @@ -34,6 +35,20 @@ export class GeoJSONSourcePropertiesForm extends BaseForm { // This is not user-editable delete schema.properties.valid; + + // Customize the widget for path field + if (schema.properties && schema.properties.path) { + const docManager = + this.props.formChangedSignal?.sender.props.formSchemaRegistry.getDocManager(); + + uiSchema.path = { + 'ui:widget': FileSelectorWidget, + 'ui:options': { + docManager, + formOptions: this.props + } + }; + } } protected onFormBlur(id: string, value: any) { @@ -48,7 +63,7 @@ export class GeoJSONSourcePropertiesForm extends BaseForm { // we need to use `onFormChange` instead of `onFormBlur` because it's no longer a text field protected onFormChange(e: IChangeEvent): void { super.onFormChange(e); - if (e.formData?.path) { + if (e.formData?.path !== undefined) { this._validatePath(e.formData.path); } } diff --git a/packages/base/src/formbuilder/objectform/pathbasedsource.ts b/packages/base/src/formbuilder/objectform/pathbasedsource.ts new file mode 100644 index 000000000..b3e406f9d --- /dev/null +++ b/packages/base/src/formbuilder/objectform/pathbasedsource.ts @@ -0,0 +1,115 @@ +import { IDict, SourceType } from '@jupytergis/schema'; +import { showErrorMessage } from '@jupyterlab/apputils'; +import { IChangeEvent, ISubmitEvent } from '@rjsf/core'; + +import { BaseForm, IBaseFormProps } from './baseform'; +import { loadFile } from '../../tools'; +import { FileSelectorWidget } from './fileselectorwidget'; + +/** + * The form to modify a PathBasedSource source. + */ +export class PathBasedSourcePropertiesForm extends BaseForm { + private _sourceType: SourceType; + + constructor(props: IBaseFormProps) { + super(props); + this._sourceType = ( + this.constructor as typeof BaseForm & { sourceType: SourceType } + ).sourceType; + this._validatePath(props.sourceData?.path ?? ''); + } + + protected processSchema( + data: IDict | undefined, + schema: IDict, + uiSchema: IDict + ) { + super.processSchema(data, schema, uiSchema); + if (!schema.properties || !data) { + return; + } + + // Customize the widget for path field + if (schema.properties && schema.properties.path) { + const docManager = + this.props.formChangedSignal?.sender.props.formSchemaRegistry.getDocManager(); + + uiSchema.path = { + 'ui:widget': FileSelectorWidget, + 'ui:options': { + docManager, + formOptions: this.props + } + }; + } + } + + protected onFormBlur(id: string, value: any) { + // Is there a better way to spot the path text entry? + if (!id.endsWith('_path')) { + return; + } + this._validatePath(value); + } + + // we need to use `onFormChange` instead of `onFormBlur` because it's no longer a text field + protected onFormChange(e: IChangeEvent): void { + super.onFormChange(e); + if (e.formData?.path !== undefined) { + this._validatePath(e.formData.path); + } + } + + protected onFormSubmit(e: ISubmitEvent) { + if (this.state.extraErrors?.path?.__errors?.length >= 1) { + showErrorMessage('Invalid file', this.state.extraErrors.path.__errors[0]); + return; + } + super.onFormSubmit(e); + } + + /** + * Validate the path, to avoid invalid path. + * + * @param path - the path to validate. + */ + private async _validatePath(path: string) { + const extraErrors: IDict = this.state.extraErrors; + + let error = ''; + let valid = true; + if (!path) { + valid = false; + error = 'Path is required'; + } else { + try { + await loadFile({ + filepath: path, + type: this._sourceType, + model: this.props.model + }); + } catch (e) { + valid = false; + error = `"${path}" is not a valid ${this._sourceType} file.`; + } + } + + if (!valid) { + extraErrors.path = { + __errors: [error] + }; + + this.setState(old => ({ ...old, extraErrors })); + } else { + this.setState(old => ({ + ...old, + extraErrors: { ...extraErrors, path: { __errors: [] } } + })); + } + + if (this.props.formErrorSignal) { + this.props.formErrorSignal.emit(!valid); + } + } +} diff --git a/packages/base/src/tools.ts b/packages/base/src/tools.ts index d2f847949..6f98f0c46 100644 --- a/packages/base/src/tools.ts +++ b/packages/base/src/tools.ts @@ -473,7 +473,24 @@ export const loadFile = async (fileInfo: { if (filepath.startsWith('http://') || filepath.startsWith('https://')) { switch (type) { case 'ImageSource': { - return filepath; // Return the URL directly + try { + const response = await fetch(filepath); + if (!response.ok) { + throw new Error(`Failed to fetch image from URL: ${filepath}`); + } + + const contentType = response.headers.get('Content-Type'); + if (!contentType || !contentType.startsWith('image/')) { + throw new Error(`Invalid image URL. Content-Type: ${contentType}`); + } + + // load the image to verify it's not corrupted + await validateImage(await response.blob()); + return filepath; + } catch (error) { + console.error('Error validating remote image:', error); + throw error; + } } case 'ShapefileSource': { @@ -546,7 +563,18 @@ export const loadFile = async (fileInfo: { case 'ImageSource': { if (typeof file.content === 'string') { const mimeType = getMimeType(filepath); - return `data:${mimeType};base64,${file.content}`; + if (!mimeType.startsWith('image/')) { + throw new Error(`Invalid image file. MIME type: ${mimeType}`); + } + + // Attempt to decode the base64 data + try { + await validateImage(await base64ToBlob(file.content, mimeType)); + return `data:${mimeType};base64,${file.content}`; + } catch (error) { + console.error('Error image content failed to decode.:', error); + throw error; + } } else { throw new Error('Invalid file format for image content.'); } @@ -562,6 +590,36 @@ export const loadFile = async (fileInfo: { } }; +/** + * Validates whether a given Blob represents a valid image. + * + * @param blob - The Blob to validate. + * @returns A promise that resolves if the Blob is a valid image, or rejects with an error otherwise. + */ +const validateImage = async (blob: Blob): Promise => { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(); // Valid image + img.onerror = () => reject(new Error('Invalid image content.')); + img.src = URL.createObjectURL(blob); + }); +}; + +/** + * Converts a base64-encoded string to a Blob. + * + * @param base64 - The base64-encoded string representing the file data. + * @param mimeType - The MIME type of the data. + * @returns A promise that resolves to a Blob representing the decoded data. + */ +export const base64ToBlob = async ( + base64: string, + mimeType: string +): Promise => { + const response = await fetch(`data:${mimeType};base64,${base64}`); + return await response.blob(); +}; + /** * A mapping of file extensions to their corresponding MIME types. */ From ad4e9a773588b0cea6fd02d2e5dd0b3c08544db8 Mon Sep 17 00:00:00 2001 From: Meriem-BenIsmail Date: Mon, 20 Jan 2025 11:18:01 +0100 Subject: [PATCH 2/5] make geojson inherit from pathbasedform --- .../formbuilder/objectform/geojsonsource.ts | 64 ++----------------- .../formbuilder/objectform/pathbasedsource.ts | 9 ++- 2 files changed, 13 insertions(+), 60 deletions(-) diff --git a/packages/base/src/formbuilder/objectform/geojsonsource.ts b/packages/base/src/formbuilder/objectform/geojsonsource.ts index 3fd186583..29f7c6600 100644 --- a/packages/base/src/formbuilder/objectform/geojsonsource.ts +++ b/packages/base/src/formbuilder/objectform/geojsonsource.ts @@ -1,17 +1,17 @@ import { IDict } from '@jupytergis/schema'; -import { showErrorMessage } from '@jupyterlab/apputils'; -import { IChangeEvent, ISubmitEvent } from '@rjsf/core'; import { Ajv, ValidateFunction } from 'ajv'; import * as geojson from '@jupytergis/schema/src/schema/geojson.json'; -import { BaseForm, IBaseFormProps } from './baseform'; +import { IBaseFormProps } from './baseform'; +import { PathBasedSourcePropertiesForm } from './pathbasedsource'; import { loadFile } from '../../tools'; -import { FileSelectorWidget } from './fileselectorwidget'; /** * The form to modify a GeoJSON source. */ -export class GeoJSONSourcePropertiesForm extends BaseForm { +export class GeoJSONSourcePropertiesForm extends PathBasedSourcePropertiesForm { + private _validate: ValidateFunction; + constructor(props: IBaseFormProps) { super(props); const ajv = new Ajv(); @@ -29,54 +29,6 @@ export class GeoJSONSourcePropertiesForm extends BaseForm { } super.processSchema(data, schema, uiSchema); - if (!schema.properties || !data) { - return; - } - - // This is not user-editable - delete schema.properties.valid; - - // Customize the widget for path field - if (schema.properties && schema.properties.path) { - const docManager = - this.props.formChangedSignal?.sender.props.formSchemaRegistry.getDocManager(); - - uiSchema.path = { - 'ui:widget': FileSelectorWidget, - 'ui:options': { - docManager, - formOptions: this.props - } - }; - } - } - - protected onFormBlur(id: string, value: any) { - // Is there a better way to spot the path text entry? - if (!id.endsWith('_path')) { - return; - } - - this._validatePath(value); - } - - // we need to use `onFormChange` instead of `onFormBlur` because it's no longer a text field - protected onFormChange(e: IChangeEvent): void { - super.onFormChange(e); - if (e.formData?.path !== undefined) { - this._validatePath(e.formData.path); - } - } - - protected onFormSubmit(e: ISubmitEvent) { - if (this.state.extraErrors?.path?.__errors?.length >= 1) { - showErrorMessage( - 'Invalid JSON file', - this.state.extraErrors.path.__errors[0] - ); - return; - } - super.onFormSubmit(e); } /** @@ -84,7 +36,7 @@ export class GeoJSONSourcePropertiesForm extends BaseForm { * * @param path - the path to validate. */ - private async _validatePath(path: string) { + protected async _validatePath(path: string) { const extraErrors: IDict = this.state.extraErrors; let error = ''; @@ -93,7 +45,7 @@ export class GeoJSONSourcePropertiesForm extends BaseForm { try { const geoJSONData = await loadFile({ filepath: path, - type: 'GeoJSONSource', + type: this._sourceType, model: this.props.model }); valid = this._validate(geoJSONData); @@ -127,6 +79,4 @@ export class GeoJSONSourcePropertiesForm extends BaseForm { this.props.formErrorSignal.emit(!valid); } } - - private _validate: ValidateFunction; } diff --git a/packages/base/src/formbuilder/objectform/pathbasedsource.ts b/packages/base/src/formbuilder/objectform/pathbasedsource.ts index b3e406f9d..ec59dbe5b 100644 --- a/packages/base/src/formbuilder/objectform/pathbasedsource.ts +++ b/packages/base/src/formbuilder/objectform/pathbasedsource.ts @@ -10,14 +10,15 @@ import { FileSelectorWidget } from './fileselectorwidget'; * The form to modify a PathBasedSource source. */ export class PathBasedSourcePropertiesForm extends BaseForm { - private _sourceType: SourceType; + protected _sourceType: SourceType; constructor(props: IBaseFormProps) { super(props); this._sourceType = ( this.constructor as typeof BaseForm & { sourceType: SourceType } ).sourceType; - this._validatePath(props.sourceData?.path ?? ''); + if (this._sourceType !== 'GeoJSONSource') + this._validatePath(props.sourceData?.path ?? ''); } protected processSchema( @@ -43,6 +44,8 @@ export class PathBasedSourcePropertiesForm extends BaseForm { } }; } + // This is not user-editable + delete schema.properties.valid; } protected onFormBlur(id: string, value: any) { @@ -74,7 +77,7 @@ export class PathBasedSourcePropertiesForm extends BaseForm { * * @param path - the path to validate. */ - private async _validatePath(path: string) { + protected async _validatePath(path: string) { const extraErrors: IDict = this.state.extraErrors; let error = ''; From 91f99d89138b9fc1db64ec04fc6cd20e12d547f7 Mon Sep 17 00:00:00 2001 From: Meriem-BenIsmail Date: Mon, 20 Jan 2025 11:20:05 +0100 Subject: [PATCH 3/5] lint --- packages/base/src/formbuilder/objectform/pathbasedsource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/base/src/formbuilder/objectform/pathbasedsource.ts b/packages/base/src/formbuilder/objectform/pathbasedsource.ts index ec59dbe5b..68d2daa00 100644 --- a/packages/base/src/formbuilder/objectform/pathbasedsource.ts +++ b/packages/base/src/formbuilder/objectform/pathbasedsource.ts @@ -18,7 +18,7 @@ export class PathBasedSourcePropertiesForm extends BaseForm { this.constructor as typeof BaseForm & { sourceType: SourceType } ).sourceType; if (this._sourceType !== 'GeoJSONSource') - this._validatePath(props.sourceData?.path ?? ''); + {this._validatePath(props.sourceData?.path ?? '');} } protected processSchema( From 4c1305ed6ac6d5f136d7e0b5d8a7f4c95ad6eedc Mon Sep 17 00:00:00 2001 From: Meriem-BenIsmail Date: Mon, 20 Jan 2025 11:24:06 +0100 Subject: [PATCH 4/5] lint check --- packages/base/src/formbuilder/objectform/pathbasedsource.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/base/src/formbuilder/objectform/pathbasedsource.ts b/packages/base/src/formbuilder/objectform/pathbasedsource.ts index 68d2daa00..08111ca62 100644 --- a/packages/base/src/formbuilder/objectform/pathbasedsource.ts +++ b/packages/base/src/formbuilder/objectform/pathbasedsource.ts @@ -17,8 +17,9 @@ export class PathBasedSourcePropertiesForm extends BaseForm { this._sourceType = ( this.constructor as typeof BaseForm & { sourceType: SourceType } ).sourceType; - if (this._sourceType !== 'GeoJSONSource') - {this._validatePath(props.sourceData?.path ?? '');} + if (this._sourceType !== 'GeoJSONSource') { + this._validatePath(props.sourceData?.path ?? ''); + } } protected processSchema( From b89e7d0834c173b1d0e91f02eff383d72ce90613 Mon Sep 17 00:00:00 2001 From: Meriem-BenIsmail Date: Mon, 20 Jan 2025 12:06:49 +0100 Subject: [PATCH 5/5] add sourceType property to BaseForm. --- packages/base/src/formbuilder/creationform.tsx | 1 + packages/base/src/formbuilder/editform.tsx | 1 + packages/base/src/formbuilder/formselectors.ts | 2 -- .../base/src/formbuilder/objectform/baseform.tsx | 6 ++++++ .../src/formbuilder/objectform/geojsonsource.ts | 2 +- .../src/formbuilder/objectform/pathbasedsource.ts | 14 +++++--------- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/base/src/formbuilder/creationform.tsx b/packages/base/src/formbuilder/creationform.tsx index 959a79b44..3cd696ed5 100644 --- a/packages/base/src/formbuilder/creationform.tsx +++ b/packages/base/src/formbuilder/creationform.tsx @@ -213,6 +213,7 @@ export class CreationForm extends React.Component { formChangedSignal={this.sourceFormChangedSignal} formErrorSignal={this.props.formErrorSignal} dialogOptions={this.props.dialogOptions} + sourceType={this.props.sourceType} />
)} diff --git a/packages/base/src/formbuilder/editform.tsx b/packages/base/src/formbuilder/editform.tsx index 6cc2c9947..ba3dd9598 100644 --- a/packages/base/src/formbuilder/editform.tsx +++ b/packages/base/src/formbuilder/editform.tsx @@ -120,6 +120,7 @@ export class EditForm extends React.Component { this.syncObjectProperties(this.props.source, properties); }} formChangedSignal={this.sourceFormChangedSignal} + sourceType={source?.type || 'RasterSource'} />
)} diff --git a/packages/base/src/formbuilder/formselectors.ts b/packages/base/src/formbuilder/formselectors.ts index 570eb0424..27dec5956 100644 --- a/packages/base/src/formbuilder/formselectors.ts +++ b/packages/base/src/formbuilder/formselectors.ts @@ -52,7 +52,5 @@ export function getSourceTypeForm(sourceType: SourceType): typeof BaseForm { break; // ADD MORE FORM TYPES HERE } - (SourceForm as any).sourceType = sourceType; - return SourceForm; } diff --git a/packages/base/src/formbuilder/objectform/baseform.tsx b/packages/base/src/formbuilder/objectform/baseform.tsx index 1660e9aa5..149737c7e 100644 --- a/packages/base/src/formbuilder/objectform/baseform.tsx +++ b/packages/base/src/formbuilder/objectform/baseform.tsx @@ -8,6 +8,7 @@ import { Signal } from '@lumino/signaling'; import { deepCopy } from '../../tools'; import { IDict } from '../../types'; import { Slider, SliderLabel } from '@jupyter/react-components'; +import { SourceType } from '@jupytergis/schema'; export interface IBaseFormStates { schema?: IDict; @@ -72,6 +73,11 @@ export interface IBaseFormProps { * and other form-related parameters. */ dialogOptions?: any; + + /** + * Source type property + */ + sourceType: SourceType; } const WrappedFormComponent = (props: any): JSX.Element => { diff --git a/packages/base/src/formbuilder/objectform/geojsonsource.ts b/packages/base/src/formbuilder/objectform/geojsonsource.ts index 29f7c6600..81b3c66a4 100644 --- a/packages/base/src/formbuilder/objectform/geojsonsource.ts +++ b/packages/base/src/formbuilder/objectform/geojsonsource.ts @@ -45,7 +45,7 @@ export class GeoJSONSourcePropertiesForm extends PathBasedSourcePropertiesForm { try { const geoJSONData = await loadFile({ filepath: path, - type: this._sourceType, + type: this.props.sourceType, model: this.props.model }); valid = this._validate(geoJSONData); diff --git a/packages/base/src/formbuilder/objectform/pathbasedsource.ts b/packages/base/src/formbuilder/objectform/pathbasedsource.ts index 08111ca62..6d22013b8 100644 --- a/packages/base/src/formbuilder/objectform/pathbasedsource.ts +++ b/packages/base/src/formbuilder/objectform/pathbasedsource.ts @@ -1,4 +1,4 @@ -import { IDict, SourceType } from '@jupytergis/schema'; +import { IDict } from '@jupytergis/schema'; import { showErrorMessage } from '@jupyterlab/apputils'; import { IChangeEvent, ISubmitEvent } from '@rjsf/core'; @@ -10,14 +10,10 @@ import { FileSelectorWidget } from './fileselectorwidget'; * The form to modify a PathBasedSource source. */ export class PathBasedSourcePropertiesForm extends BaseForm { - protected _sourceType: SourceType; - constructor(props: IBaseFormProps) { super(props); - this._sourceType = ( - this.constructor as typeof BaseForm & { sourceType: SourceType } - ).sourceType; - if (this._sourceType !== 'GeoJSONSource') { + + if (this.props.sourceType !== 'GeoJSONSource') { this._validatePath(props.sourceData?.path ?? ''); } } @@ -90,12 +86,12 @@ export class PathBasedSourcePropertiesForm extends BaseForm { try { await loadFile({ filepath: path, - type: this._sourceType, + type: this.props.sourceType, model: this.props.model }); } catch (e) { valid = false; - error = `"${path}" is not a valid ${this._sourceType} file.`; + error = `"${path}" is not a valid ${this.props.sourceType} file.`; } }