UNPKG

12.6 kBJavaScriptView Raw
1import { ObservableList } from '@jupyterlab/observables';
2import { SettingRegistry } from '@jupyterlab/settingregistry';
3import { findIndex, toArray } from '@lumino/algorithm';
4import { JSONExt } from '@lumino/coreutils';
5import { Dialog, showDialog } from '../dialog';
6/**
7 * Default toolbar item rank
8 *
9 * #### Notes
10 * This will place item just before the white spacer item in the notebook toolbar.
11 */
12const DEFAULT_TOOLBAR_ITEM_RANK = 50;
13/**
14 * Display warning when the toolbar definition have been modified.
15 *
16 * @param trans Translation bundle
17 */
18async function displayInformation(trans) {
19 const result = await showDialog({
20 title: trans.__('Information'),
21 body: trans.__('Toolbar customization has changed. You will need to reload JupyterLab to see the changes.'),
22 buttons: [
23 Dialog.cancelButton(),
24 Dialog.okButton({ label: trans.__('Reload') })
25 ]
26 });
27 if (result.button.accept) {
28 location.reload();
29 }
30}
31/**
32 * Set the toolbar definition by accumulating all settings definition.
33 *
34 * The list will be populated only with the enabled items.
35 *
36 * @param toolbarItems Observable list to populate
37 * @param registry Application settings registry
38 * @param factoryName Widget factory name that needs a toolbar
39 * @param pluginId Settings plugin id
40 * @param translator Translator object
41 * @param propertyId Property holding the toolbar definition in the settings; default 'toolbar'
42 * @returns List of toolbar items
43 */
44async function setToolbarItems(toolbarItems, registry, factoryName, pluginId, translator, propertyId = 'toolbar') {
45 var _a;
46 const trans = translator.load('jupyterlab');
47 let canonical;
48 let loaded = {};
49 /**
50 * Populate the plugin's schema defaults.
51 *
52 * We keep track of disabled entries in case the plugin is loaded
53 * after the toolbar initialization.
54 */
55 function populate(schema) {
56 var _a, _b;
57 loaded = {};
58 const pluginDefaults = Object.keys(registry.plugins)
59 .map(plugin => {
60 var _a, _b;
61 const items = (_b = ((_a = registry.plugins[plugin].schema['jupyter.lab.toolbars']) !== null && _a !== void 0 ? _a : {})[factoryName]) !== null && _b !== void 0 ? _b : [];
62 loaded[plugin] = items;
63 return items;
64 })
65 .concat([(_b = ((_a = schema['jupyter.lab.toolbars']) !== null && _a !== void 0 ? _a : {})[factoryName]) !== null && _b !== void 0 ? _b : []])
66 .reduceRight((acc, val) => SettingRegistry.reconcileToolbarItems(acc, val, true), []);
67 // Apply default value as last step to take into account overrides.json
68 // The standard default being [] as the plugin must use `jupyter.lab.toolbars.<factory>`
69 // to define their default value.
70 schema.properties[propertyId].default = SettingRegistry.reconcileToolbarItems(pluginDefaults, schema.properties[propertyId].default, true).sort((a, b) => {
71 var _a, _b;
72 return ((_a = a.rank) !== null && _a !== void 0 ? _a : DEFAULT_TOOLBAR_ITEM_RANK) -
73 ((_b = b.rank) !== null && _b !== void 0 ? _b : DEFAULT_TOOLBAR_ITEM_RANK);
74 });
75 }
76 // Transform the plugin object to return different schema than the default.
77 registry.transform(pluginId, {
78 compose: plugin => {
79 var _a, _b, _c, _d, _e;
80 // Only override the canonical schema the first time.
81 if (!canonical) {
82 canonical = JSONExt.deepCopy(plugin.schema);
83 populate(canonical);
84 }
85 const defaults = (_c = ((_b = ((_a = canonical.properties) !== null && _a !== void 0 ? _a : {})[propertyId]) !== null && _b !== void 0 ? _b : {}).default) !== null && _c !== void 0 ? _c : [];
86 // Initialize the settings
87 const user = plugin.data.user;
88 const composite = plugin.data.composite;
89 // Overrides the value with using the aggregated default for the toolbar property
90 user[propertyId] = (_d = plugin.data.user[propertyId]) !== null && _d !== void 0 ? _d : [];
91 composite[propertyId] = ((_e = SettingRegistry.reconcileToolbarItems(defaults, user[propertyId], false)) !== null && _e !== void 0 ? _e : []).sort((a, b) => {
92 var _a, _b;
93 return ((_a = a.rank) !== null && _a !== void 0 ? _a : DEFAULT_TOOLBAR_ITEM_RANK) -
94 ((_b = b.rank) !== null && _b !== void 0 ? _b : DEFAULT_TOOLBAR_ITEM_RANK);
95 });
96 plugin.data = { composite, user };
97 return plugin;
98 },
99 fetch: plugin => {
100 // Only override the canonical schema the first time.
101 if (!canonical) {
102 canonical = JSONExt.deepCopy(plugin.schema);
103 populate(canonical);
104 }
105 return {
106 data: plugin.data,
107 id: plugin.id,
108 raw: plugin.raw,
109 schema: canonical,
110 version: plugin.version
111 };
112 }
113 });
114 // Repopulate the canonical variable after the setting registry has
115 // preloaded all initial plugins.
116 canonical = null;
117 const settings = await registry.load(pluginId);
118 // React to customization by the user
119 settings.changed.connect(() => {
120 var _a;
121 const newItems = (_a = settings.composite[propertyId]) !== null && _a !== void 0 ? _a : [];
122 transferSettings(newItems);
123 });
124 // React to plugin changes
125 registry.pluginChanged.connect(async (sender, plugin) => {
126 var _a, _b, _c, _d;
127 // As the plugin storing the toolbar definition is transformed using
128 // the above definition, if it changes, this means that a request to
129 // reloaded was triggered. Hence the toolbar definitions from the other
130 // plugins has been automatically reset during the transform step.
131 if (plugin !== pluginId) {
132 // If a plugin changed its toolbar items
133 const oldItems = (_a = loaded[plugin]) !== null && _a !== void 0 ? _a : [];
134 const newItems = (_c = ((_b = registry.plugins[plugin].schema['jupyter.lab.toolbars']) !== null && _b !== void 0 ? _b : {})[factoryName]) !== null && _c !== void 0 ? _c : [];
135 if (!JSONExt.deepEqual(oldItems, newItems)) {
136 if (loaded[plugin]) {
137 // The plugin has changed, request the user to reload the UI
138 await displayInformation(trans);
139 }
140 else {
141 // The plugin was not yet loaded => update the toolbar items list
142 loaded[plugin] = JSONExt.deepCopy(newItems);
143 const newList = ((_d = SettingRegistry.reconcileToolbarItems(toArray(toolbarItems), newItems, false)) !== null && _d !== void 0 ? _d : []).sort((a, b) => {
144 var _a, _b;
145 return ((_a = a.rank) !== null && _a !== void 0 ? _a : DEFAULT_TOOLBAR_ITEM_RANK) -
146 ((_b = b.rank) !== null && _b !== void 0 ? _b : DEFAULT_TOOLBAR_ITEM_RANK);
147 });
148 transferSettings(newList);
149 }
150 }
151 }
152 });
153 const transferSettings = (newItems) => {
154 // This is not optimal but safer because a toolbar item with the same
155 // name cannot be inserted (it will be a no-op). But that could happen
156 // if the settings are changing the items order.
157 toolbarItems.clear();
158 toolbarItems.pushAll(newItems.filter(item => !item.disabled));
159 };
160 // Initialize the toolbar
161 transferSettings((_a = settings.composite[propertyId]) !== null && _a !== void 0 ? _a : []);
162}
163/**
164 * Create the toolbar factory for a given container widget based
165 * on a data description stored in settings
166 *
167 * @param toolbarRegistry Toolbar widgets registry
168 * @param settingsRegistry Settings registry
169 * @param factoryName Toolbar container factory name
170 * @param pluginId Settings plugin id
171 * @param translator Translator
172 * @param propertyId Toolbar definition key in the settings plugin
173 * @returns List of toolbar widgets factory
174 */
175export function createToolbarFactory(toolbarRegistry, settingsRegistry, factoryName, pluginId, translator, propertyId = 'toolbar') {
176 const items = new ObservableList({
177 itemCmp: (a, b) => JSONExt.deepEqual(a, b)
178 });
179 // Get toolbar definition from the settings
180 setToolbarItems(items, settingsRegistry, factoryName, pluginId, translator, propertyId).catch(reason => {
181 console.error(`Failed to load toolbar items for factory ${factoryName} from ${pluginId}`, reason);
182 });
183 return (widget) => {
184 const updateToolbar = (list, change) => {
185 switch (change.type) {
186 case 'move':
187 toolbar.move(change.oldIndex, change.newIndex);
188 break;
189 case 'add':
190 change.newValues.forEach(item => toolbar.push({
191 name: item.name,
192 widget: toolbarRegistry.createWidget(factoryName, widget, item)
193 }));
194 break;
195 case 'remove':
196 change.oldValues.forEach(() => toolbar.remove(change.oldIndex));
197 break;
198 case 'set':
199 change.newValues.forEach(item => toolbar.set(change.newIndex, {
200 name: item.name,
201 widget: toolbarRegistry.createWidget(factoryName, widget, item)
202 }));
203 break;
204 }
205 };
206 const toolbar = new ObservableList({
207 values: toArray(items).map(item => {
208 return {
209 name: item.name,
210 widget: toolbarRegistry.createWidget(factoryName, widget, item)
211 };
212 })
213 });
214 items.changed.connect(updateToolbar);
215 widget.disposed.connect(() => {
216 items.changed.disconnect(updateToolbar);
217 });
218 return toolbar;
219 };
220}
221/**
222 * Set the toolbar items of a widget from a factory
223 *
224 * @param widget Widget with the toolbar to set
225 * @param factory Toolbar items factory
226 */
227export function setToolbar(widget, factory) {
228 if (!widget.toolbar) {
229 console.log(`Widget ${widget.id} has no 'toolbar'.`);
230 return;
231 }
232 const items = factory(widget);
233 if (Array.isArray(items)) {
234 items.forEach(({ name, widget: item }) => {
235 widget.toolbar.addItem(name, item);
236 });
237 }
238 else {
239 const updateToolbar = (list, changes) => {
240 switch (changes.type) {
241 case 'add':
242 changes.newValues.forEach((item, index) => {
243 widget.toolbar.insertItem(changes.newIndex + index, item.name, item.widget);
244 });
245 break;
246 case 'move':
247 changes.oldValues.forEach(item => {
248 item.widget.parent = null;
249 });
250 changes.newValues.forEach((item, index) => {
251 widget.toolbar.insertItem(changes.newIndex + index, item.name, item.widget);
252 });
253 break;
254 case 'remove':
255 changes.oldValues.forEach(item => {
256 item.widget.parent = null;
257 });
258 break;
259 case 'set':
260 changes.oldValues.forEach(item => {
261 item.widget.parent = null;
262 });
263 changes.newValues.forEach((item, index) => {
264 const existingIndex = findIndex(widget.toolbar.names(), name => item.name === name);
265 if (existingIndex >= 0) {
266 toArray(widget.toolbar.children())[existingIndex].parent = null;
267 }
268 widget.toolbar.insertItem(changes.newIndex + index, item.name, item.widget);
269 });
270 break;
271 }
272 };
273 updateToolbar(items, {
274 newIndex: 0,
275 newValues: toArray(items),
276 oldIndex: 0,
277 oldValues: [],
278 type: 'add'
279 });
280 items.changed.connect(updateToolbar);
281 widget.disposed.connect(() => {
282 items.changed.disconnect(updateToolbar);
283 });
284 }
285}
286//# sourceMappingURL=factory.js.map
\No newline at end of file