@jupyterlab/settingeditor 4.0.0-alpha.9 → 4.0.0-beta.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.
@@ -0,0 +1,218 @@
1
+ /* -----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+
6
+ import { ISettingRegistry, Settings } from '@jupyterlab/settingregistry';
7
+ import { ITranslator } from '@jupyterlab/translation';
8
+ import { IFormRendererRegistry } from '@jupyterlab/ui-components';
9
+ import { ISignal } from '@lumino/signaling';
10
+ import type { Field } from '@rjsf/utils';
11
+ import React, { useEffect, useState } from 'react';
12
+ import { PluginList } from './pluginlist';
13
+ import { SettingsFormEditor } from './SettingsFormEditor';
14
+
15
+ export interface ISettingsPanelProps {
16
+ /**
17
+ * List of Settings objects that provide schema and values
18
+ * of plugins.
19
+ */
20
+ settings: Settings[];
21
+
22
+ /**
23
+ * Form component registry that provides renderers
24
+ * for the form editor.
25
+ */
26
+ editorRegistry: IFormRendererRegistry;
27
+
28
+ /**
29
+ * Handler for when selection change is triggered by scrolling
30
+ * in the SettingsPanel.
31
+ */
32
+ onSelect: (id: string) => void;
33
+
34
+ /**
35
+ * Signal that fires when a selection is made in the plugin list.
36
+ */
37
+ handleSelectSignal: ISignal<PluginList, string>;
38
+
39
+ /**
40
+ * Translator object
41
+ */
42
+ translator: ITranslator;
43
+
44
+ /**
45
+ * Callback to update the plugin list to display plugins with
46
+ * invalid / unsaved settings in red.
47
+ */
48
+ hasError: (id: string, error: boolean) => void;
49
+
50
+ /**
51
+ * Sends the updated dirty state to the parent class.
52
+ */
53
+ updateDirtyState: (dirty: boolean) => void;
54
+
55
+ /**
56
+ * Signal that sends updated filter when search value changes.
57
+ */
58
+ updateFilterSignal: ISignal<
59
+ PluginList,
60
+ (plugin: ISettingRegistry.IPlugin) => string[] | null
61
+ >;
62
+
63
+ /**
64
+ * If the settings editor is created with an initial search query, an initial
65
+ * filter function is passed to the settings panel.
66
+ */
67
+ initialFilter: (item: ISettingRegistry.IPlugin) => string[] | null;
68
+ }
69
+
70
+ /**
71
+ * React component that displays a list of SettingsFormEditor
72
+ * components.
73
+ */
74
+ export const SettingsPanel: React.FC<ISettingsPanelProps> = ({
75
+ settings,
76
+ editorRegistry,
77
+ onSelect,
78
+ handleSelectSignal,
79
+ hasError,
80
+ updateDirtyState,
81
+ updateFilterSignal,
82
+ translator,
83
+ initialFilter
84
+ }: ISettingsPanelProps): JSX.Element => {
85
+ const [expandedPlugin, setExpandedPlugin] = useState<string | null>(null);
86
+ const [filterPlugin, setFilter] = useState<
87
+ (plugin: ISettingRegistry.IPlugin) => string[] | null
88
+ >(() => initialFilter);
89
+
90
+ // Refs used to keep track of "selected" plugin based on scroll location
91
+ const editorRefs: {
92
+ [pluginId: string]: React.RefObject<HTMLDivElement>;
93
+ } = {};
94
+ for (const setting of settings) {
95
+ editorRefs[setting.id] = React.useRef(null);
96
+ }
97
+ const wrapperRef: React.RefObject<HTMLDivElement> = React.useRef(null);
98
+ const editorDirtyStates: React.RefObject<{
99
+ [id: string]: boolean;
100
+ }> = React.useRef({});
101
+
102
+ useEffect(() => {
103
+ const onFilterUpdate = (
104
+ list: PluginList,
105
+ newFilter: (plugin: ISettingRegistry.IPlugin) => string[] | null
106
+ ) => {
107
+ setFilter(() => newFilter);
108
+ for (const pluginSettings of settings) {
109
+ const filtered = newFilter(pluginSettings.plugin);
110
+ if (filtered === null || filtered.length > 0) {
111
+ setExpandedPlugin(pluginSettings.id);
112
+ break;
113
+ }
114
+ }
115
+ };
116
+
117
+ // Set first visible plugin as expanded plugin on initial load.
118
+ for (const pluginSettings of settings) {
119
+ const filtered = filterPlugin(pluginSettings.plugin);
120
+ if (filtered === null || filtered.length > 0) {
121
+ setExpandedPlugin(pluginSettings.id);
122
+ break;
123
+ }
124
+ }
125
+
126
+ // When filter updates, only show plugins that match search.
127
+ updateFilterSignal.connect(onFilterUpdate);
128
+
129
+ const onSelectChange = (list: PluginList, pluginId: string) => {
130
+ setExpandedPlugin(expandedPlugin !== pluginId ? pluginId : null);
131
+ // Scroll to the plugin when a selection is made in the left panel.
132
+ editorRefs[pluginId]?.current?.scrollIntoView(true);
133
+ };
134
+ handleSelectSignal?.connect?.(onSelectChange);
135
+
136
+ return () => {
137
+ updateFilterSignal.disconnect(onFilterUpdate);
138
+ handleSelectSignal?.disconnect?.(onSelectChange);
139
+ };
140
+ }, []);
141
+
142
+ const updateDirtyStates = React.useCallback(
143
+ (id: string, dirty: boolean) => {
144
+ if (editorDirtyStates.current) {
145
+ editorDirtyStates.current[id] = dirty;
146
+ for (const editor in editorDirtyStates.current) {
147
+ if (editorDirtyStates.current[editor]) {
148
+ updateDirtyState(true);
149
+ return;
150
+ }
151
+ }
152
+ }
153
+ updateDirtyState(false);
154
+ },
155
+ [editorDirtyStates, updateDirtyState]
156
+ );
157
+
158
+ const renderers = React.useMemo(
159
+ () =>
160
+ Object.entries(editorRegistry.renderers).reduce<{
161
+ [plugin: string]: { [property: string]: Field };
162
+ }>((agg, [id, renderer]) => {
163
+ const splitPosition = id.lastIndexOf('.');
164
+ const pluginId = id.substring(0, splitPosition);
165
+ const propertyName = id.substring(splitPosition + 1);
166
+ if (!agg[pluginId]) {
167
+ agg[pluginId] = {};
168
+ }
169
+ if (!agg[pluginId][propertyName] && renderer.fieldRenderer) {
170
+ agg[pluginId][propertyName] = renderer.fieldRenderer;
171
+ }
172
+ return agg;
173
+ }, {}),
174
+ [editorRegistry]
175
+ );
176
+
177
+ return (
178
+ <div className="jp-SettingsPanel" ref={wrapperRef}>
179
+ {settings.map(pluginSettings => {
180
+ // Pass filtered results to SettingsFormEditor to only display filtered fields.
181
+ const filtered = filterPlugin(pluginSettings.plugin);
182
+ // If filtered results are an array, only show if the array is non-empty.
183
+ if (filtered !== null && filtered.length === 0) {
184
+ return undefined;
185
+ }
186
+ return (
187
+ <div
188
+ ref={editorRefs[pluginSettings.id]}
189
+ className="jp-SettingsForm"
190
+ key={`${pluginSettings.id}SettingsEditor`}
191
+ >
192
+ <SettingsFormEditor
193
+ isCollapsed={pluginSettings.id !== expandedPlugin}
194
+ onCollapseChange={(willCollapse: boolean) => {
195
+ if (!willCollapse) {
196
+ setExpandedPlugin(pluginSettings.id);
197
+ } else if (pluginSettings.id === expandedPlugin) {
198
+ setExpandedPlugin(null);
199
+ }
200
+ }}
201
+ filteredValues={filtered}
202
+ settings={pluginSettings}
203
+ renderers={renderers}
204
+ hasError={(error: boolean) => {
205
+ hasError(pluginSettings.id, error);
206
+ }}
207
+ updateDirtyState={(dirty: boolean) => {
208
+ updateDirtyStates(pluginSettings.id, dirty);
209
+ }}
210
+ onSelect={onSelect}
211
+ translator={translator}
212
+ />
213
+ </div>
214
+ );
215
+ })}
216
+ </div>
217
+ );
218
+ };
package/src/tokens.ts ADDED
@@ -0,0 +1,33 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { IWidgetTracker, MainAreaWidget } from '@jupyterlab/apputils';
5
+ import { Token } from '@lumino/coreutils';
6
+ import { JsonSettingEditor as JSONSettingEditor } from './jsonsettingeditor';
7
+ import { SettingsEditor } from './settingseditor';
8
+
9
+ /**
10
+ * The setting editor tracker token.
11
+ */
12
+ export const ISettingEditorTracker = new Token<ISettingEditorTracker>(
13
+ '@jupyterlab/settingeditor:ISettingEditorTracker'
14
+ );
15
+
16
+ /**
17
+ * The setting editor tracker token.
18
+ */
19
+ export const IJSONSettingEditorTracker = new Token<IJSONSettingEditorTracker>(
20
+ '@jupyterlab/settingeditor:IJSONSettingEditorTracker'
21
+ );
22
+
23
+ /**
24
+ * A class that tracks the setting editor.
25
+ */
26
+ export interface IJSONSettingEditorTracker
27
+ extends IWidgetTracker<MainAreaWidget<JSONSettingEditor>> {}
28
+
29
+ /**
30
+ * A class that tracks the setting editor.
31
+ */
32
+ export interface ISettingEditorTracker
33
+ extends IWidgetTracker<MainAreaWidget<SettingsEditor>> {}
package/style/base.css CHANGED
@@ -18,9 +18,9 @@
18
18
  min-width: 360px;
19
19
  min-height: 240px;
20
20
  background-color: var(--jp-layout-color0);
21
+ color: var(--jp-ui-font-color0);
21
22
  margin-top: -1px;
22
23
  outline: none;
23
- color: var(--jp-content-font-color1) !important;
24
24
 
25
25
  /* This is needed so that all font sizing of children done in ems is
26
26
  * relative to this base size */
@@ -90,19 +90,10 @@
90
90
  right: 0;
91
91
  }
92
92
 
93
- .jp-PluginList .jp-SettingsHeader {
94
- display: flex;
95
- flex-basis: 100%;
96
- }
97
-
98
- .jp-PluginList .jp-SettingsHeader button {
99
- color: var(--jp-private-notebook-selected-color);
100
- white-space: nowrap;
101
- }
102
-
103
93
  .jp-PluginList .jp-PluginList-header {
104
94
  border-bottom: var(--jp-border-width) solid var(--jp-border-color2);
105
95
  border-top: var(--jp-border-width) solid var(--jp-border-color2);
96
+ color: var(--jp-ui-font-color1);
106
97
  }
107
98
 
108
99
  .jp-PluginList .jp-PluginList-noResults,
@@ -116,7 +107,7 @@
116
107
  margin: 10px;
117
108
  border-bottom: var(--jp-border-width) solid var(--jp-border-color2);
118
109
  border-top: var(--jp-border-width) solid var(--jp-border-color2);
119
- color: var(--jp-content-font-color1);
110
+ color: var(--jp-ui-font-color1);
120
111
  }
121
112
 
122
113
  .jp-PluginList .jp-SelectedIndicator {
@@ -143,11 +134,6 @@
143
134
  color: var(--jp-brand-color1);
144
135
  }
145
136
 
146
- .jp-PluginList button span {
147
- color: var(--jp-content-font-color1);
148
- line-height: var(--jp-cell-collapser-min-height);
149
- }
150
-
151
137
  .jp-FormComponent li span {
152
138
  overflow: hidden;
153
139
  }
@@ -164,10 +150,6 @@
164
150
  background-color: var(--jp-layout-color0);
165
151
  }
166
152
 
167
- .jp-PluginList-Searcher {
168
- margin: 5px;
169
- }
170
-
171
153
  ul.jp-PluginList li.jp-mod-selected span.jp-PluginList-icon.jp-FileIcon {
172
154
  background-image: var(--jp-icon-file-selected);
173
155
  }
@@ -224,35 +206,8 @@ ul.jp-PluginList li.jp-mod-selected span.jp-PluginList-icon.jp-FileIcon {
224
206
  text-align: right;
225
207
  }
226
208
 
227
- .jp-SettingsPanel fieldset input,
228
- .jp-SettingsPanel fieldset select,
229
- .jp-SettingsPanel fieldset textarea {
230
- font-size: var(--jp-content-font-size2);
231
- border-color: var(--jp-input-border-color);
232
- border-style: solid;
233
- border-radius: 5px;
234
- border-width: 1px;
235
- padding: 6px 8px;
236
- background: none;
237
- color: var(--jp-content-font-color0);
238
- height: inherit;
239
- }
240
-
241
- .jp-SettingsPanel fieldset input[type='checkbox'] {
242
- position: relative;
243
- top: 2px;
244
- margin-left: 0;
245
- }
246
-
247
- /** copy of `input.jp-mod-styled:focus` style */
248
- .jp-SettingsPanel fieldset input:focus {
249
- border: var(--jp-border-width) solid var(--md-blue-500);
250
- box-shadow: inset 0 0 4px var(--md-blue-300);
251
- }
252
-
253
- .jp-SettingsPanel .checkbox label {
254
- cursor: pointer;
255
- font-size: var(--jp-content-font-size2);
209
+ .jp-SettingsRawEditor .cm-editor {
210
+ height: 100%;
256
211
  }
257
212
 
258
213
  .jp-SettingsPanel .checkbox p {
@@ -264,61 +219,12 @@ ul.jp-PluginList li.jp-mod-selected span.jp-PluginList-icon.jp-FileIcon {
264
219
  flex-direction: column-reverse;
265
220
  }
266
221
 
267
- .jp-SettingsPanel .checkbox .field-description {
268
- /* Disable default description field for checkbox:
269
- because other widgets do not have description fields,
270
- we add descriptions to each widget on the field level.
271
- */
272
- display: none;
273
- }
274
-
275
- .jp-SettingsPanel button[type='submit'] {
276
- display: none;
277
- }
278
-
279
222
  .jp-SettingsPanel .form-group {
280
223
  display: flex;
281
224
  padding: 4px 8px 4px var(--jp-private-settingeditor-modifier-indent);
282
225
  margin-top: 5px;
283
226
  }
284
227
 
285
- .jp-SettingsPanel .jp-objectFieldWrapper .form-group {
286
- padding: 2px 8px 2px var(--jp-private-settingeditor-modifier-indent);
287
- margin-top: 2px;
288
- }
289
-
290
- .jp-ArrayOperations {
291
- margin-left: 8px;
292
- }
293
-
294
- .jp-SettingsPanel .jp-FormGroup-content {
295
- display: flex;
296
- align-items: center;
297
- flex-wrap: wrap;
298
- }
299
-
300
- .jp-SettingsPanel .jp-FormGroup-contentItem {
301
- margin-left: 7px;
302
- }
303
-
304
- .jp-SettingsPanel .jp-FormGroup-description {
305
- flex-basis: 100%;
306
- padding: 4px 7px;
307
- }
308
-
309
- .jp-SettingsPanel #root__description {
310
- display: none;
311
- }
312
-
313
- .jp-SettingsPanel fieldset {
314
- border: none;
315
- padding: 0;
316
- }
317
-
318
- .jp-SettingsPanel fieldset:not(:first-child) {
319
- margin-left: 7px;
320
- }
321
-
322
228
  .jp-SettingsPanel .jp-SaveSettingsBanner {
323
229
  position: absolute;
324
230
  bottom: 0;
@@ -340,71 +246,6 @@ ul.jp-PluginList li.jp-mod-selected span.jp-PluginList-icon.jp-FileIcon {
340
246
  color: var(--jp-brand-color0);
341
247
  }
342
248
 
343
- .jp-SettingsPanel .form-group.small-field:hover {
344
- background: var(--jp-border-color3);
345
- }
346
-
347
- .jp-SettingsPanel button.jp-mod-styled {
348
- cursor: pointer;
349
- }
350
-
351
- .jp-SettingsPanel button.jp-mod-styled:disabled {
352
- cursor: not-allowed;
353
- opacity: 0.5;
354
- }
355
-
356
- .jp-SettingsPanel .array-item button {
357
- margin: 2px;
358
- }
359
-
360
- .jp-openJSONSettingsEditor {
361
- position: absolute;
362
- bottom: 0;
363
- left: 0;
364
- }
365
-
366
- .jp-openJSONSettingsEditor button {
367
- border: 1px solid var(--jp-border-color1);
368
- color: var(--jp-ui-font-color0);
369
- padding: 5px;
370
- margin: 5px;
371
- cursor: pointer;
372
- background-color: var(--jp-border-color2);
373
- display: flex;
374
- align-items: center;
375
- }
376
-
377
- .jp-openJSONSettingsEditor button > div {
378
- display: flex;
379
- }
380
-
381
- .jp-openJSONSettingsEditor svg#icon {
382
- height: 1.5em;
383
- }
384
-
385
- .jp-SettingsPanel .array-item {
386
- border: 1px solid var(--jp-border-color2);
387
- border-radius: 4px;
388
- margin: 4px;
389
- }
390
-
391
- .jp-SettingsPanel .field-array-of-string .array-item {
392
- /* Display `jp-ArrayOperations` buttons side-by-side with content except
393
- for small screens where flex-wrap will place them one below the other.
394
- */
395
- display: flex;
396
- align-items: center;
397
- flex-wrap: wrap;
398
- }
399
-
400
- .jp-SettingsPanel .jp-root > fieldset > legend {
401
- display: none;
402
- }
403
-
404
- .jp-SettingsPanel .jp-root > fieldset > p {
405
- display: none;
406
- }
407
-
408
249
  .jp-SettingsPanel .jp-SettingsHeader h2 {
409
250
  font-size: var(--jp-content-font-size3);
410
251
  color: var(--jp-ui-font-color0);
@@ -420,20 +261,6 @@ ul.jp-PluginList li.jp-mod-selected span.jp-PluginList-icon.jp-FileIcon {
420
261
  line-height: var(--jp-content-font-size3);
421
262
  }
422
263
 
423
- .jp-SettingsPanel legend {
424
- font-size: var(--jp-content-font-size2);
425
- color: var(--jp-ui-font-color0);
426
- flex-basis: 100%;
427
- padding: 4px 0;
428
- font-weight: var(--jp-content-header-font-weight);
429
- border-bottom: 1px solid var(--jp-border-color2);
430
- }
431
-
432
- .jp-SettingsPanel .field-description {
433
- padding: 4px 0;
434
- white-space: pre-wrap;
435
- }
436
-
437
264
  .jp-SettingsPanel .jp-SettingsTitle {
438
265
  display: flex;
439
266
  align-items: center;
@@ -477,6 +304,10 @@ ul.jp-PluginList li.jp-mod-selected span.jp-PluginList-icon.jp-FileIcon {
477
304
  white-space: nowrap;
478
305
  }
479
306
 
307
+ .jp-PluginList-entry:hover {
308
+ background: var(--jp-layout-color1);
309
+ }
310
+
480
311
  .jp-PluginList-entry li {
481
312
  margin-left: 27px;
482
313
  margin-top: 5px;
@@ -485,14 +316,16 @@ ul.jp-PluginList li.jp-mod-selected span.jp-PluginList-icon.jp-FileIcon {
485
316
  text-overflow: ellipsis;
486
317
  }
487
318
 
488
- .jp-pluginList-entry-label {
319
+ .jp-PluginList-entry-label {
489
320
  display: flex;
490
321
  }
491
322
 
492
- .jp-pluginList-entry-label span {
323
+ .jp-PluginList-entry-label-text {
493
324
  text-overflow: ellipsis;
494
325
  overflow-x: hidden;
495
326
  white-space: nowrap;
327
+ color: var(--jp-ui-font-color1);
328
+ line-height: var(--jp-cell-collapser-min-height);
496
329
  }
497
330
 
498
331
  .jp-SettingsPanel .jp-SettingsHeader-Name {
@@ -500,32 +333,6 @@ ul.jp-PluginList li.jp-mod-selected span.jp-PluginList-icon.jp-FileIcon {
500
333
  font-size: var(--jp-content-font-size3);
501
334
  }
502
335
 
503
- .jp-SettingsPanel .jp-modifiedIndicator {
504
- width: 5px;
505
- background-color: var(--jp-brand-color2);
506
- margin-top: 0;
507
- margin-left: calc(var(--jp-private-settingeditor-modifier-indent) * -1);
508
- flex-shrink: 0;
509
- }
510
-
511
- .jp-SettingsPanel .jp-FormGroup-fieldLabel {
512
- font-size: var(--jp-content-font-size1);
513
- font-weight: normal;
514
- min-width: 120px;
515
- }
516
-
517
- .jp-SettingsPanel .jp-modifiedIndicator.jp-errorIndicator {
518
- background-color: var(--jp-error-color0);
519
- }
520
-
521
- .jp-SettingsPanel .validationErrors {
522
- color: var(--jp-error-color0);
523
- }
524
-
525
- .jp-SettingsPanel .panel.errors {
526
- display: none;
527
- }
528
-
529
336
  .jp-SettingsPanel .jp-SettingsEditor {
530
337
  padding: 20px;
531
338
  }
@@ -1,13 +0,0 @@
1
- import { ISignal } from '@lumino/signaling';
2
- import { SplitPanel as SPanel } from '@lumino/widgets';
3
- /**
4
- * A deprecated split panel that will be removed when the phosphor split panel
5
- * supports a handle moved signal. See https://github.com/phosphorjs/phosphor/issues/297.
6
- */
7
- export declare class SplitPanel extends SPanel {
8
- /**
9
- * Emits when the split handle has moved.
10
- */
11
- readonly handleMoved: ISignal<any, void>;
12
- handleEvent(event: Event): void;
13
- }
package/lib/splitpanel.js DELETED
@@ -1,26 +0,0 @@
1
- /* -----------------------------------------------------------------------------
2
- | Copyright (c) Jupyter Development Team.
3
- | Distributed under the terms of the Modified BSD License.
4
- |----------------------------------------------------------------------------*/
5
- import { Signal } from '@lumino/signaling';
6
- import { SplitPanel as SPanel } from '@lumino/widgets';
7
- /**
8
- * A deprecated split panel that will be removed when the phosphor split panel
9
- * supports a handle moved signal. See https://github.com/phosphorjs/phosphor/issues/297.
10
- */
11
- export class SplitPanel extends SPanel {
12
- constructor() {
13
- super(...arguments);
14
- /**
15
- * Emits when the split handle has moved.
16
- */
17
- this.handleMoved = new Signal(this);
18
- }
19
- handleEvent(event) {
20
- super.handleEvent(event);
21
- if (event.type === 'mouseup') {
22
- this.handleMoved.emit(undefined);
23
- }
24
- }
25
- }
26
- //# sourceMappingURL=splitpanel.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"splitpanel.js","sourceRoot":"","sources":["../src/splitpanel.ts"],"names":[],"mappings":"AAAA;;;+EAG+E;AAE/E,OAAO,EAAW,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,UAAU,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;GAGG;AACH,MAAM,OAAO,UAAW,SAAQ,MAAM;IAAtC;;QACE;;WAEG;QACM,gBAAW,GAAuB,IAAI,MAAM,CAAY,IAAI,CAAC,CAAC;IASzE,CAAC;IAPC,WAAW,CAAC,KAAY;QACtB,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAEzB,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE;YAC3B,IAAI,CAAC,WAAiC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;SACzD;IACH,CAAC;CACF"}