Skip to content

Commit 5b440ca

Browse files
Introduce Custom Settings for JupyterGIS (#619)
* Settings to configure proxy url * don't use async method maybe * Add settings to core * Update python/jupytergis_core/src/jgisplugin/plugins.ts Co-authored-by: martinRenou <martin.renou@gmail.com> * list the schema folder as part of the package file * get settings in tools.ts * lint * settingsregistry to model * use settings * lint * load settings at plugin level * investigate the culprit * lint * have more specific type * Add temporary fallback * handling for jupyterlite * Update packages/base/src/tools.ts Co-authored-by: martinRenou <martin.renou@gmail.com> * lint * lint --------- Co-authored-by: martinRenou <martin.renou@gmail.com>
1 parent a49be8e commit 5b440ca

File tree

9 files changed

+122
-21
lines changed

9 files changed

+122
-21
lines changed

packages/base/src/tools.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -410,12 +410,24 @@ export const getFromIndexedDB = async (key: string) => {
410410

411411
const fetchWithProxies = async <T>(
412412
url: string,
413+
model: IJupyterGISModel,
413414
parseResponse: (response: Response) => Promise<T>
414415
): Promise<T | null> => {
416+
let settings: any = null;
417+
418+
try {
419+
settings = await model.getSettings();
420+
} catch (e) {
421+
console.warn('Failed to get settings from model. Falling back.', e);
422+
}
423+
424+
const proxyUrl =
425+
settings && settings.proxyUrl ? settings.proxyUrl : 'https://corsproxy.io';
426+
415427
const proxyUrls = [
416428
url, // Direct fetch
417429
`/jupytergis_core/proxy?url=${encodeURIComponent(url)}`, // Internal proxy
418-
`https://corsproxy.io/?url=${encodeURIComponent(url)}` // External proxy
430+
`${proxyUrl}/?url=${encodeURIComponent(url)}` // External proxy
419431
];
420432

421433
for (const proxyUrl of proxyUrls) {
@@ -444,6 +456,7 @@ const fetchWithProxies = async <T>(
444456
*/
445457
export const loadGeoTiff = async (
446458
sourceInfo: { url?: string | undefined },
459+
model: IJupyterGISModel,
447460
file?: Contents.IModel | null
448461
) => {
449462
if (!sourceInfo?.url) {
@@ -468,7 +481,9 @@ export const loadGeoTiff = async (
468481
let fileBlob: Blob | null = null;
469482

470483
if (!file) {
471-
fileBlob = await fetchWithProxies(url, async response => response.blob());
484+
fileBlob = await fetchWithProxies(url, model, async response =>
485+
response.blob()
486+
);
472487
if (!fileBlob) {
473488
showErrorMessage('Network error', `Failed to fetch ${url}`);
474489
throw new Error(`Failed to fetch ${url}`);
@@ -535,10 +550,14 @@ export const loadFile = async (fileInfo: {
535550
return cached.file;
536551
}
537552

538-
const geojson = await fetchWithProxies(filepath, async response => {
539-
const arrayBuffer = await response.arrayBuffer();
540-
return shp(arrayBuffer);
541-
});
553+
const geojson = await fetchWithProxies(
554+
filepath,
555+
model,
556+
async response => {
557+
const arrayBuffer = await response.arrayBuffer();
558+
return shp(arrayBuffer);
559+
}
560+
);
542561

543562
if (geojson) {
544563
await saveToIndexedDB(filepath, geojson);
@@ -555,8 +574,10 @@ export const loadFile = async (fileInfo: {
555574
return cached.file;
556575
}
557576

558-
const geojson = await fetchWithProxies(filepath, async response =>
559-
response.json()
577+
const geojson = await fetchWithProxies(
578+
filepath,
579+
model,
580+
async response => response.json()
560581
);
561582

562583
if (geojson) {
@@ -627,7 +648,7 @@ export const loadFile = async (fileInfo: {
627648

628649
case 'GeoTiffSource': {
629650
if (typeof file.content === 'string') {
630-
const tiff = loadGeoTiff({ url: filepath }, file);
651+
const tiff = loadGeoTiff({ url: filepath }, model, file);
631652
return tiff;
632653
} else {
633654
throw new Error('Invalid file format for tiff content.');

packages/schema/src/interfaces.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {
183183
contentsManager: Contents.IManager | undefined;
184184
filePath: string;
185185

186+
getSettings(): IJupyterGISSettings;
186187
getContent(): IJGISContent;
187188
getLayers(): IJGISLayers;
188189
getLayer(id: string): IJGISLayer | undefined;
@@ -349,3 +350,7 @@ export interface IAnnotation {
349350
parent: string;
350351
open: boolean;
351352
}
353+
354+
export interface IJupyterGISSettings {
355+
proxyUrl: string;
356+
}

packages/schema/src/model.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,17 @@ import {
3030
IUserData,
3131
IViewPortState,
3232
JgisCoordinates,
33-
Pointer
33+
Pointer,
34+
IJupyterGISSettings
3435
} from './interfaces';
3536
import jgisSchema from './schema/project/jgis.json';
37+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
38+
39+
const SETTINGS_ID = '@jupytergis/jupytergis-core:jupytergis-settings';
3640

3741
export class JupyterGISModel implements IJupyterGISModel {
3842
constructor(options: JupyterGISModel.IOptions) {
39-
const { annotationModel, sharedModel } = options;
43+
const { annotationModel, sharedModel, settingRegistry } = options;
4044

4145
if (sharedModel) {
4246
this._sharedModel = sharedModel;
@@ -50,6 +54,26 @@ export class JupyterGISModel implements IJupyterGISModel {
5054
this
5155
);
5256
this.annotationModel = annotationModel;
57+
this.settingRegistry = settingRegistry;
58+
}
59+
60+
/**
61+
* Initialize custom settings for JupyterLab.
62+
*/
63+
async initSettings(): Promise<void> {
64+
if (this.settingRegistry) {
65+
const setting = await this.settingRegistry.load(SETTINGS_ID);
66+
this._settings = setting.composite as any;
67+
68+
setting.changed.connect(() => {
69+
this._settings = setting.composite as any;
70+
console.log('JupyterGIS Settings updated:', this._settings);
71+
});
72+
}
73+
}
74+
75+
getSettings(): IJupyterGISSettings {
76+
return this._settings;
5377
}
5478

5579
private _onSharedModelChanged = (sender: any, changes: any): void => {
@@ -724,7 +748,9 @@ export class JupyterGISModel implements IJupyterGISModel {
724748
readonly defaultKernelName: string = '';
725749
readonly defaultKernelLanguage: string = '';
726750
readonly annotationModel?: IAnnotationModel;
751+
readonly settingRegistry?: ISettingRegistry;
727752

753+
private _settings: IJupyterGISSettings;
728754
private _sharedModel: IJupyterGISDoc;
729755
private _filePath: string;
730756
private _contentsManager?: Contents.IManager;
@@ -770,6 +796,7 @@ export namespace JupyterGISModel {
770796
export interface IOptions
771797
extends DocumentRegistry.IModelOptions<IJupyterGISDoc> {
772798
annotationModel?: IAnnotationModel;
799+
settingRegistry?: ISettingRegistry;
773800
}
774801
}
775802

python/jupytergis_core/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"author": "JupyterGIS contributors",
1616
"files": [
1717
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
18-
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
18+
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
19+
"schema/**/*.{json,js,ts}"
1920
],
2021
"main": "lib/index.js",
2122
"types": "lib/index.d.ts",
@@ -91,6 +92,7 @@
9192
"access": "public"
9293
},
9394
"jupyterlab": {
95+
"schemaDir": "schema",
9496
"discovery": {
9597
"server": {
9698
"managers": [
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"title": "JupyterGIS Settings",
3+
"description": "Settings for the JupyterGIS extension.",
4+
"type": "object",
5+
"properties": {
6+
"proxyUrl": {
7+
"type": "string",
8+
"title": "Proxy URL",
9+
"description": "The proxy URL to use for external requests.",
10+
"default": "https://corsproxy.io"
11+
}
12+
}
13+
}

python/jupytergis_core/src/jgisplugin/modelfactory.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@jupytergis/schema';
66
import { DocumentRegistry } from '@jupyterlab/docregistry';
77
import { Contents } from '@jupyterlab/services';
8+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
89

910
/**
1011
* A Model factory to create new instances of JupyterGISModel.
@@ -14,6 +15,7 @@ export class JupyterGISModelFactory
1415
{
1516
constructor(options: JupyterGISModelFactory.IOptions) {
1617
this._annotationModel = options.annotationModel;
18+
this._settingRegistry = options.settingRegistry;
1719
}
1820
/**
1921
* Whether the model is collaborative or not.
@@ -84,17 +86,21 @@ export class JupyterGISModelFactory
8486
const model = new JupyterGISModel({
8587
sharedModel: options.sharedModel,
8688
languagePreference: options.languagePreference,
87-
annotationModel: this._annotationModel
89+
annotationModel: this._annotationModel,
90+
settingRegistry: this._settingRegistry
8891
});
92+
model.initSettings();
8993
return model;
9094
}
9195

9296
private _annotationModel: IAnnotationModel;
97+
private _settingRegistry: ISettingRegistry;
9398
private _disposed = false;
9499
}
95100

96101
export namespace JupyterGISModelFactory {
97102
export interface IOptions {
98103
annotationModel: IAnnotationModel;
104+
settingRegistry: ISettingRegistry;
99105
}
100106
}

python/jupytergis_core/src/jgisplugin/plugins.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { PageConfig } from '@jupyterlab/coreutils';
2727
import { IFileBrowserFactory } from '@jupyterlab/filebrowser';
2828
import { ILauncher } from '@jupyterlab/launcher';
2929
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
30+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
3031

3132
import { CommandIDs, logoIcon, logoMiniIcon } from '@jupytergis/base';
3233
import { JupyterGISDocumentWidgetFactory } from '../factory';
@@ -37,8 +38,9 @@ const FACTORY = 'JupyterGIS .jgis Viewer';
3738
const CONTENT_TYPE = 'jgis';
3839
const PALETTE_CATEGORY = 'JupyterGIS';
3940
const MODEL_NAME = 'jupytergis-jgismodel';
41+
const SETTINGS_ID = '@jupytergis/jupytergis-core:jupytergis-settings';
4042

41-
const activate = (
43+
const activate = async (
4244
app: JupyterFrontEnd,
4345
tracker: WidgetTracker<IJupyterGISWidget>,
4446
themeManager: IThemeManager,
@@ -49,14 +51,24 @@ const activate = (
4951
rendermime: IRenderMimeRegistry,
5052
consoleTracker: IConsoleTracker,
5153
annotationModel: IAnnotationModel,
54+
settingRegistry: ISettingRegistry,
5255
launcher: ILauncher | null,
5356
palette: ICommandPalette | null,
5457
drive: ICollaborativeDrive | null
55-
): void => {
58+
): Promise<void> => {
5659
if (PageConfig.getOption('jgis_expose_maps')) {
5760
window.jupytergisMaps = {};
5861
}
5962

63+
let settings: ISettingRegistry.ISettings | null = null;
64+
65+
try {
66+
settings = await settingRegistry.load(SETTINGS_ID);
67+
console.log(`Loaded settings for ${SETTINGS_ID}`, settings);
68+
} catch (error) {
69+
console.warn(`Failed to load settings for ${SETTINGS_ID}`, error);
70+
}
71+
6072
const widgetFactory = new JupyterGISDocumentWidgetFactory({
6173
name: FACTORY,
6274
modelName: MODEL_NAME,
@@ -86,7 +98,10 @@ const activate = (
8698
app.docRegistry.addWidgetFactory(mimeDocumentFactory);
8799

88100
// Creating and registering the model factory for our custom DocumentModel
89-
const modelFactory = new JupyterGISModelFactory({ annotationModel });
101+
const modelFactory = new JupyterGISModelFactory({
102+
annotationModel,
103+
settingRegistry
104+
});
90105
app.docRegistry.addModelFactory(modelFactory);
91106

92107
// register the filetype
@@ -233,7 +248,8 @@ const jGISPlugin: JupyterFrontEndPlugin<void> = {
233248
IEditorServices,
234249
IRenderMimeRegistry,
235250
IConsoleTracker,
236-
IAnnotationToken
251+
IAnnotationToken,
252+
ISettingRegistry
237253
],
238254
optional: [ILauncher, ICommandPalette, ICollaborativeDrive],
239255
autoStart: true,

python/jupytergis_qgis/src/modelfactory.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@jupytergis/schema';
66
import { DocumentRegistry } from '@jupyterlab/docregistry';
77
import { Contents } from '@jupyterlab/services';
8+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
89

910
/**
1011
* A Model factory to create new instances of JupyterGISModel.
@@ -86,18 +87,21 @@ export class JupyterGISModelFactoryBase
8687
const model = new JupyterGISModel({
8788
sharedModel: options.sharedModel,
8889
languagePreference: options.languagePreference,
89-
annotationModel: this._annotationModel
90+
annotationModel: this._annotationModel,
91+
settingRegistry: this._settingRegistry
9092
});
9193
return model;
9294
}
9395

9496
private _annotationModel: IAnnotationModel;
97+
private _settingRegistry: ISettingRegistry;
9598
private _disposed = false;
9699
}
97100

98101
export namespace JupyterGISModelFactoryBase {
99102
export interface IOptions {
100103
annotationModel: IAnnotationModel;
104+
settingRegistry: ISettingRegistry;
101105
}
102106
}
103107

python/jupytergis_qgis/src/plugins.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { ConsolePanel, IConsoleTracker } from '@jupyterlab/console';
2929
import { PathExt } from '@jupyterlab/coreutils';
3030
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
3131
import { Widget } from '@lumino/widgets';
32+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
3233

3334
import {
3435
JupyterGISDocumentWidget,
@@ -78,6 +79,7 @@ const activate = async (
7879
rendermime: IRenderMimeRegistry,
7980
consoleTracker: IConsoleTracker,
8081
annotationModel: IAnnotationModel,
82+
settingRegistry: ISettingRegistry,
8183
commandPalette: ICommandPalette | null
8284
): Promise<void> => {
8385
const fcCheck = await requestAPI<{ installed: boolean }>(
@@ -133,8 +135,12 @@ const activate = async (
133135
app.docRegistry.addWidgetFactory(QGZWidgetFactory);
134136

135137
// Creating and registering the model factory for our custom DocumentModel
136-
app.docRegistry.addModelFactory(new QGSModelFactory({ annotationModel }));
137-
app.docRegistry.addModelFactory(new QGZModelFactory({ annotationModel }));
138+
app.docRegistry.addModelFactory(
139+
new QGSModelFactory({ annotationModel, settingRegistry })
140+
);
141+
app.docRegistry.addModelFactory(
142+
new QGZModelFactory({ annotationModel, settingRegistry })
143+
);
138144
// register the filetype
139145
app.docRegistry.addFileType({
140146
name: 'QGS',
@@ -311,7 +317,8 @@ export const qgisplugin: JupyterFrontEndPlugin<void> = {
311317
IEditorServices,
312318
IRenderMimeRegistry,
313319
IConsoleTracker,
314-
IAnnotationToken
320+
IAnnotationToken,
321+
ISettingRegistry
315322
],
316323
optional: [ICommandPalette],
317324
autoStart: true,

0 commit comments

Comments
 (0)