diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index a9198e989..c8870607e 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -10,7 +10,7 @@ import { SourceType } from '@jupytergis/schema'; import { JupyterFrontEnd } from '@jupyterlab/application'; -import { showErrorMessage } from '@jupyterlab/apputils'; +import { InputDialog, showErrorMessage } from '@jupyterlab/apputils'; import { ICompletionProviderManager } from '@jupyterlab/completer'; import { IStateDB } from '@jupyterlab/statedb'; import { ITranslator } from '@jupyterlab/translation'; @@ -252,6 +252,60 @@ export function addCommands( ...icons.get(CommandIDs.temporalController) }); + commands.addCommand(CommandIDs.saveAs, { + label: trans.__('Save As...'), + isEnabled: () => true, + execute: async () => { + if (!tracker.currentWidget) { + return; + } + + const model = tracker.currentWidget.model; + const oldFilename = model.filePath; + let newFilename = ( + await InputDialog.getText({ + title: 'Save as...', + label: 'New filename', + placeholder: oldFilename + }) + ).value; + + if (!newFilename) { + return; + } + + if (newFilename.toLowerCase().endsWith('.qgz')) { + throw Error('Not supported yet'); + } else if (!newFilename.toLowerCase().endsWith('.jgis')) { + newFilename += '.jGIS'; + } + + const content = model.toJSON(); + + // FIXME: This doesn't re-open the project file in the current view where the save button was clicked. + app.serviceManager.contents.save(newFilename, { + content: JSON.stringify(content), + format: 'text', + type: 'file', + mimetype: 'text/json' + }); + // FIXME: The widget will only save to this new filename once, as opposed to continuously saving changes to the file like we expect. + model.filePath = newFilename; + + // FIXME: Saves to the currently open directory, while the above save is to the JupyterLab root directory. + // FIXME: Get "unsaved_project" from a constant + if (oldFilename && !oldFilename.endsWith('unsaved_project')) { + app.serviceManager.contents.save(oldFilename, { + content: JSON.stringify(content), + format: 'text', + type: 'file', + mimetype: 'text/json' + }); + } + }, + ...icons.get(CommandIDs.saveAs), + }) + /** * SOURCES and LAYERS creation commands. */ diff --git a/packages/base/src/constants.ts b/packages/base/src/constants.ts index eb91c975d..4acbc909a 100644 --- a/packages/base/src/constants.ts +++ b/packages/base/src/constants.ts @@ -11,6 +11,7 @@ export namespace CommandIDs { export const symbology = 'jupytergis:symbology'; export const identify = 'jupytergis:identify'; export const temporalController = 'jupytergis:temporalController'; + export const saveAs = 'jupytergis:saveAs'; // Layers and sources creation commands export const openLayerBrowser = 'jupytergis:openLayerBrowser'; @@ -106,7 +107,8 @@ const iconObject = { [CommandIDs.newGeoTiffEntry]: { iconClass: 'fa fa-image' }, [CommandIDs.symbology]: { iconClass: 'fa fa-brush' }, [CommandIDs.identify]: { iconClass: 'fa fa-info' }, - [CommandIDs.temporalController]: { iconClass: 'fa fa-clock' } + [CommandIDs.temporalController]: { iconClass: 'fa fa-clock' }, + [CommandIDs.saveAs]: {iconClass: 'fa fa-save'} }; /** diff --git a/packages/base/src/formbuilder/objectform/bufferProcessForm.tsx b/packages/base/src/formbuilder/objectform/bufferProcessForm.tsx new file mode 100644 index 000000000..87eb5cbe2 --- /dev/null +++ b/packages/base/src/formbuilder/objectform/bufferProcessForm.tsx @@ -0,0 +1,92 @@ +import { BaseForm, IBaseFormProps, IBaseFormStates } from './baseform'; // Ensure BaseForm imports states +import { IDict, IJupyterGISModel } from '@jupytergis/schema'; +import { IChangeEvent } from '@rjsf/core'; +// import { loadFile } from '../../tools'; +import proj4 from 'proj4'; + +interface IBufferFormOptions extends IBaseFormProps { + schema: IDict; + sourceData: IDict; + title: string; + cancelButton: (() => void) | boolean; + syncData: (props: IDict) => void; + model: IJupyterGISModel; +} + +export class BufferForm extends BaseForm { + private model: IJupyterGISModel; + private unit = ''; + + constructor(options: IBufferFormOptions) { + super(options); + this.model = options.model; + + // Ensure initial state matches IBaseFormStates + this.state = { + schema: options.schema ?? {} // Ensure schema is never undefined + }; + + this.onFormChange = this.handleFormChange.bind(this); + + this.computeDistanceUnits(options.sourceData.inputLayer); + } + + private async computeDistanceUnits(layerId: string) { + const layer = this.model.getLayer(layerId); + if (!layer?.parameters?.source) { + return; + } + const source = this.model.getSource(layer.parameters.source); + if (!source) { + return; + } + + const projection = source.parameters?.projection; + console.log(projection); + + // TODO: how to get layer info from OpenLayers? + // const srs = layer.from_ol().srs; + const srs = 'EPSG:4326'; + + try { + // console.log(proj4, srs); + this.unit = (proj4(srs) as any).oProj.units; + debugger; + this.updateSchema(); + } catch (error) { + console.error('Error calculating units:', error); + } + } + + public handleFormChange(e: IChangeEvent) { + super.onFormChange(e); + + if (e.formData.inputLayer) { + this.computeDistanceUnits(e.formData.inputLayer); + } + } + + private updateSchema() { + this.setState( + (prevState: IBaseFormStates) => ({ + schema: { + ...prevState.schema, + properties: { + ...prevState.schema?.properties, + bufferDistance: { + ...prevState.schema?.properties?.bufferDistance, + description: + prevState.schema?.properties?.bufferDistance.description.replace( + 'projection units', + this.unit + ) + } + } + } + }), + () => { + this.forceUpdate(); + } + ); + } +} diff --git a/packages/base/src/toolbar/widget.tsx b/packages/base/src/toolbar/widget.tsx index ce19e8c53..552a0bf81 100644 --- a/packages/base/src/toolbar/widget.tsx +++ b/packages/base/src/toolbar/widget.tsx @@ -39,6 +39,14 @@ export class ToolbarWidget extends ReactiveToolbar { this.addClass('jGIS-toolbar-widget'); if (options.commands) { + this.addItem( + 'Save as...', + new CommandToolbarButton({ + id: CommandIDs.saveAs, + label: '', + commands: options.commands + }), + ); this.addItem( 'undo', new CommandToolbarButton({