Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/base/src/formbuilder/creationform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ export class CreationForm extends React.Component<ICreationFormProps, any> {
formChangedSignal={this.sourceFormChangedSignal}
formErrorSignal={this.props.formErrorSignal}
dialogOptions={this.props.dialogOptions}
sourceType={this.props.sourceType}
/>
</div>
)}
Expand Down
1 change: 1 addition & 0 deletions packages/base/src/formbuilder/editform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export class EditForm extends React.Component<IEditFormProps, any> {
this.syncObjectProperties(this.props.source, properties);
}}
formChangedSignal={this.sourceFormChangedSignal}
sourceType={source?.type || 'RasterSource'}
/>
</div>
)}
Expand Down
8 changes: 7 additions & 1 deletion packages/base/src/formbuilder/formselectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -45,6 +52,5 @@ export function getSourceTypeForm(sourceType: SourceType): typeof BaseForm {
break;
// ADD MORE FORM TYPES HERE
}

return SourceForm;
}
21 changes: 6 additions & 15 deletions packages/base/src/formbuilder/objectform/baseform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Signal } from '@lumino/signaling';
import { deepCopy } from '../../tools';
import { IDict } from '../../types';
import { Slider, SliderLabel } from '@jupyter/react-components';
import { FileSelectorWidget } from './fileselectorwidget';
import { SourceType } from '@jupytergis/schema';

export interface IBaseFormStates {
schema?: IDict;
Expand Down Expand Up @@ -73,6 +73,11 @@ export interface IBaseFormProps {
* and other form-related parameters.
*/
dialogOptions?: any;

/**
* Source type property
*/
sourceType: SourceType;
}

const WrappedFormComponent = (props: any): JSX.Element => {
Expand Down Expand Up @@ -202,20 +207,6 @@ export class BaseForm extends React.Component<IBaseFormProps, IBaseFormStates> {
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
}
};
}
});
}

Expand Down
15 changes: 11 additions & 4 deletions packages/base/src/formbuilder/objectform/fileselectorwidget.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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://')
Expand Down Expand Up @@ -85,12 +86,17 @@ export const FileSelectorWidget = (props: any) => {
};

const handleURLChange = (event: React.ChangeEvent<HTMLInputElement>) => {
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 (
<div>
<div>
Expand All @@ -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%' }}
/>
Expand Down
49 changes: 7 additions & 42 deletions packages/base/src/formbuilder/objectform/geojsonsource.ts
Original file line number Diff line number Diff line change
@@ -1,16 +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';

/**
* 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();
Expand All @@ -28,48 +29,14 @@ export class GeoJSONSourcePropertiesForm extends BaseForm {
}

super.processSchema(data, schema, uiSchema);
if (!schema.properties || !data) {
return;
}

// This is not user-editable
delete schema.properties.valid;
}

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) {
this._validatePath(e.formData.path);
}
}

protected onFormSubmit(e: ISubmitEvent<any>) {
if (this.state.extraErrors?.path?.__errors?.length >= 1) {
showErrorMessage(
'Invalid JSON file',
this.state.extraErrors.path.__errors[0]
);
return;
}
super.onFormSubmit(e);
}

/**
* Validate the path, to avoid invalid path or invalid GeoJSON.
*
* @param path - the path to validate.
*/
private async _validatePath(path: string) {
protected async _validatePath(path: string) {
const extraErrors: IDict = this.state.extraErrors;

let error = '';
Expand All @@ -78,7 +45,7 @@ export class GeoJSONSourcePropertiesForm extends BaseForm {
try {
const geoJSONData = await loadFile({
filepath: path,
type: 'GeoJSONSource',
type: this.props.sourceType,
model: this.props.model
});
valid = this._validate(geoJSONData);
Expand Down Expand Up @@ -112,6 +79,4 @@ export class GeoJSONSourcePropertiesForm extends BaseForm {
this.props.formErrorSignal.emit(!valid);
}
}

private _validate: ValidateFunction;
}
115 changes: 115 additions & 0 deletions packages/base/src/formbuilder/objectform/pathbasedsource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { IDict } 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 {
constructor(props: IBaseFormProps) {
super(props);

if (this.props.sourceType !== 'GeoJSONSource') {
this._validatePath(props.sourceData?.path ?? '');
}
}

protected processSchema(
data: IDict<any> | 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
}
};
}
// This is not user-editable
delete schema.properties.valid;
}

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<any>) {
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.
*/
protected 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.props.sourceType,
model: this.props.model
});
} catch (e) {
valid = false;
error = `"${path}" is not a valid ${this.props.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);
}
}
}
Loading
Loading