@jupyterlab/extensionmanager 4.0.0-alpha.8 → 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.
- package/lib/dialog.js +2 -2
- package/lib/dialog.js.map +1 -1
- package/lib/index.d.ts +0 -2
- package/lib/index.js +0 -2
- package/lib/index.js.map +1 -1
- package/lib/model.d.ts +90 -193
- package/lib/model.js +210 -451
- package/lib/model.js.map +1 -1
- package/lib/widget.d.ts +18 -207
- package/lib/widget.js +201 -317
- package/lib/widget.js.map +1 -1
- package/package.json +16 -15
- package/src/dialog.tsx +39 -0
- package/src/index.ts +9 -0
- package/src/model.ts +691 -0
- package/src/widget.tsx +760 -0
- package/style/base.css +59 -103
- package/style/index.css +1 -1
- package/style/index.js +1 -1
- package/lib/build-helper.d.ts +0 -9
- package/lib/build-helper.js +0 -60
- package/lib/build-helper.js.map +0 -1
- package/lib/companions.d.ts +0 -88
- package/lib/companions.js +0 -98
- package/lib/companions.js.map +0 -1
- package/lib/listings.d.ts +0 -50
- package/lib/listings.js +0 -80
- package/lib/listings.js.map +0 -1
- package/lib/npm.d.ts +0 -223
- package/lib/npm.js +0 -76
- package/lib/npm.js.map +0 -1
package/lib/widget.js
CHANGED
|
@@ -1,69 +1,19 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
import {
|
|
4
|
-
import { nullTranslator } from '@jupyterlab/translation';
|
|
5
|
-
import { Button, caretDownIcon, caretRightIcon, Collapse, InputGroup, jupyterIcon, listingsInfoIcon, refreshIcon, ToolbarButtonComponent, VDomRenderer } from '@jupyterlab/ui-components';
|
|
3
|
+
import { Button, FilterBox, infoIcon, jupyterIcon, PanelWithToolbar, ReactWidget, refreshIcon, SidePanel, ToolbarButton, ToolbarButtonComponent } from '@jupyterlab/ui-components';
|
|
6
4
|
import * as React from 'react';
|
|
7
5
|
import ReactPaginate from 'react-paginate';
|
|
8
6
|
import { ListModel } from './model';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Icons with custom styling bound.
|
|
13
|
-
*/
|
|
14
|
-
const caretDownIconStyled = caretDownIcon.bindprops({
|
|
15
|
-
height: 'auto',
|
|
16
|
-
width: '20px'
|
|
17
|
-
});
|
|
18
|
-
const caretRightIconStyled = caretRightIcon.bindprops({
|
|
19
|
-
height: 'auto',
|
|
20
|
-
width: '20px'
|
|
21
|
-
});
|
|
22
|
-
const badgeSize = 32;
|
|
23
|
-
const badgeQuerySize = Math.floor(devicePixelRatio * badgeSize);
|
|
24
|
-
/**
|
|
25
|
-
* Search bar VDOM component.
|
|
26
|
-
*/
|
|
27
|
-
export class SearchBar extends React.Component {
|
|
28
|
-
constructor(props) {
|
|
29
|
-
super(props);
|
|
30
|
-
/**
|
|
31
|
-
* Handler for search input changes.
|
|
32
|
-
*/
|
|
33
|
-
this.handleChange = (e) => {
|
|
34
|
-
const target = e.target;
|
|
35
|
-
this.setState({
|
|
36
|
-
value: target.value
|
|
37
|
-
});
|
|
38
|
-
};
|
|
39
|
-
this.state = {
|
|
40
|
-
value: ''
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Render the list view using the virtual DOM.
|
|
45
|
-
*/
|
|
46
|
-
render() {
|
|
47
|
-
return (React.createElement("div", { className: "jp-extensionmanager-search-bar" },
|
|
48
|
-
React.createElement(InputGroup, { className: "jp-extensionmanager-search-wrapper", type: "text", placeholder: this.props.placeholder, onChange: this.handleChange, value: this.state.value, rightIcon: "ui-components:search", disabled: this.props.disabled })));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Create a build prompt as a react element.
|
|
53
|
-
*
|
|
54
|
-
* @param props Configuration of the build prompt.
|
|
55
|
-
*/
|
|
56
|
-
function BuildPrompt(props) {
|
|
57
|
-
const translator = props.translator || nullTranslator;
|
|
58
|
-
const trans = translator.load('jupyterlab');
|
|
59
|
-
return (React.createElement("div", { className: "jp-extensionmanager-buildprompt" },
|
|
60
|
-
React.createElement("div", { className: "jp-extensionmanager-buildmessage" }, trans.__('A build is needed to include the latest changes')),
|
|
61
|
-
React.createElement(Button, { onClick: props.performBuild, minimal: true, small: true }, trans.__('Rebuild')),
|
|
62
|
-
React.createElement(Button, { onClick: props.ignoreBuild, minimal: true, small: true }, trans.__('Ignore'))));
|
|
63
|
-
}
|
|
7
|
+
const BADGE_SIZE = 32;
|
|
8
|
+
const BADGE_QUERY_SIZE = Math.floor(devicePixelRatio * BADGE_SIZE);
|
|
64
9
|
function getExtensionGitHubUser(entry) {
|
|
65
|
-
if (entry.
|
|
66
|
-
|
|
10
|
+
if (entry.homepage_url &&
|
|
11
|
+
entry.homepage_url.startsWith('https://github.com/')) {
|
|
12
|
+
return entry.homepage_url.split('/')[3];
|
|
13
|
+
}
|
|
14
|
+
else if (entry.repository_url &&
|
|
15
|
+
entry.repository_url.startsWith('https://github.com/')) {
|
|
16
|
+
return entry.repository_url.split('/')[3];
|
|
67
17
|
}
|
|
68
18
|
return null;
|
|
69
19
|
}
|
|
@@ -71,288 +21,140 @@ function getExtensionGitHubUser(entry) {
|
|
|
71
21
|
* VDOM for visualizing an extension entry.
|
|
72
22
|
*/
|
|
73
23
|
function ListEntry(props) {
|
|
74
|
-
|
|
75
|
-
const { entry, listMode, viewType } = props;
|
|
76
|
-
const translator = props.translator || nullTranslator;
|
|
77
|
-
const trans = translator.load('jupyterlab');
|
|
24
|
+
const { canFetch, entry, supportInstallation, trans } = props;
|
|
78
25
|
const flagClasses = [];
|
|
79
26
|
if (entry.status && ['ok', 'warning', 'error'].indexOf(entry.status) !== -1) {
|
|
80
27
|
flagClasses.push(`jp-extensionmanager-entry-${entry.status}`);
|
|
81
28
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (entryIsJupyterOrg) {
|
|
85
|
-
title = trans.__('%1 (Developed by Project Jupyter)', entry.name);
|
|
86
|
-
}
|
|
87
|
-
const githubUser = getExtensionGitHubUser(entry);
|
|
88
|
-
if (listMode === 'block' &&
|
|
89
|
-
entry.blockedExtensionsEntry &&
|
|
90
|
-
viewType === 'searchResult') {
|
|
91
|
-
return React.createElement("li", null);
|
|
92
|
-
}
|
|
93
|
-
if (listMode === 'allow' &&
|
|
94
|
-
!entry.allowedExtensionsEntry &&
|
|
95
|
-
viewType === 'searchResult') {
|
|
96
|
-
return React.createElement("li", null);
|
|
97
|
-
}
|
|
98
|
-
if (listMode === 'block' && ((_a = entry.blockedExtensionsEntry) === null || _a === void 0 ? void 0 : _a.name)) {
|
|
29
|
+
const githubUser = canFetch ? getExtensionGitHubUser(entry) : null;
|
|
30
|
+
if (!entry.allowed) {
|
|
99
31
|
flagClasses.push(`jp-extensionmanager-entry-should-be-uninstalled`);
|
|
100
32
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
return (React.createElement("li", { className: `jp-extensionmanager-entry ${flagClasses.join(' ')}`, title: title, style: { display: 'flex' } },
|
|
105
|
-
React.createElement("div", { style: { marginRight: '8px' } },
|
|
106
|
-
githubUser && (React.createElement("img", { src: `https://github.com/${githubUser}.png?size=${badgeQuerySize}`, style: { width: '32px', height: '32px' } })),
|
|
107
|
-
!githubUser && (React.createElement("div", { style: { width: `${badgeSize}px`, height: `${badgeSize}px` } }))),
|
|
33
|
+
return (React.createElement("li", { className: `jp-extensionmanager-entry ${flagClasses.join(' ')}`, style: { display: 'flex' } },
|
|
34
|
+
React.createElement("div", { style: { marginRight: '8px' } }, githubUser ? (React.createElement("img", { src: `https://github.com/${githubUser}.png?size=${BADGE_QUERY_SIZE}`, style: { width: '32px', height: '32px' } })) : (React.createElement("div", { style: { width: `${BADGE_SIZE}px`, height: `${BADGE_SIZE}px` } }))),
|
|
108
35
|
React.createElement("div", { className: "jp-extensionmanager-entry-description" },
|
|
109
36
|
React.createElement("div", { className: "jp-extensionmanager-entry-title" },
|
|
110
|
-
React.createElement("div", { className: "jp-extensionmanager-entry-name" }, entry.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
entryIsJupyterOrg && (React.createElement(jupyterIcon.react, { className: "jp-extensionmanager-is-jupyter-org", top: "1px", height: "auto", width: "1em" }))),
|
|
37
|
+
React.createElement("div", { className: "jp-extensionmanager-entry-name" }, entry.homepage_url ? (React.createElement("a", { href: entry.homepage_url, target: "_blank", rel: "noopener noreferrer", title: trans.__('%1 extension home page', entry.name) }, entry.name)) : (React.createElement("div", null, entry.name))),
|
|
38
|
+
React.createElement("div", { className: "jp-extensionmanager-entry-version" },
|
|
39
|
+
React.createElement("div", { title: trans.__('Version: %1', entry.installed_version) }, entry.installed_version)),
|
|
40
|
+
entry.installed && !entry.allowed && (React.createElement(ToolbarButtonComponent, { icon: infoIcon, iconLabel: trans.__('%1 extension is not allowed anymore. Please uninstall it immediately or contact your administrator.', entry.name), onClick: () => window.open('https://jupyterlab.readthedocs.io/en/latest/user/extensions.html') })),
|
|
41
|
+
entry.approved && (React.createElement(jupyterIcon.react, { className: "jp-extensionmanager-is-approved", top: "1px", height: "auto", width: "1em", title: trans.__('This extension is approved by your security team.') }))),
|
|
116
42
|
React.createElement("div", { className: "jp-extensionmanager-entry-content" },
|
|
117
43
|
React.createElement("div", { className: "jp-extensionmanager-entry-description" }, entry.description),
|
|
118
|
-
React.createElement("div", { className: "jp-extensionmanager-entry-buttons" },
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
title,
|
|
124
|
-
body: (React.createElement("div", null, getPrebuiltUninstallInstruction(entry, trans))),
|
|
125
|
-
buttons: [
|
|
126
|
-
Dialog.okButton({
|
|
127
|
-
label: trans.__('OK'),
|
|
128
|
-
caption: trans.__('OK')
|
|
129
|
-
})
|
|
130
|
-
]
|
|
131
|
-
}).then(result => {
|
|
132
|
-
return result.button.accept;
|
|
133
|
-
}), minimal: true, small: true }, trans.__('About')))))))));
|
|
134
|
-
}
|
|
135
|
-
function getPrebuiltUninstallInstruction(entry, trans) {
|
|
136
|
-
var _a, _b;
|
|
137
|
-
if ((_a = entry.install) === null || _a === void 0 ? void 0 : _a.uninstallInstructions) {
|
|
138
|
-
return (React.createElement("div", null,
|
|
139
|
-
React.createElement("p", null, trans.__(`This is a prebuilt extension. To uninstall it, please
|
|
140
|
-
apply following instructions.`)),
|
|
141
|
-
React.createElement("p", null, trans.__((_b = entry.install) === null || _b === void 0 ? void 0 : _b.uninstallInstructions))));
|
|
142
|
-
}
|
|
143
|
-
return (React.createElement("div", null,
|
|
144
|
-
React.createElement("p", null, trans.__(`This is a prebuilt extension. To uninstall it, please
|
|
145
|
-
read the user guide on:`)),
|
|
146
|
-
React.createElement("p", null,
|
|
147
|
-
React.createElement("a", { href: "https://jupyterlab.readthedocs.io/en/latest/user/extensions.html", target: "_blank", rel: "noopener noreferrer" }, "https://jupyterlab.readthedocs.io/en/latest/user/extensions.html"))));
|
|
44
|
+
props.performAction && (React.createElement("div", { className: "jp-extensionmanager-entry-buttons" }, entry.installed ? (React.createElement(React.Fragment, null,
|
|
45
|
+
supportInstallation && (React.createElement(React.Fragment, null,
|
|
46
|
+
ListModel.entryHasUpdate(entry) && (React.createElement(Button, { onClick: () => props.performAction('install', entry), title: trans.__('Update "%1" to "%2"', entry.name, entry.latest_version), minimal: true, small: true }, trans.__('Update to %1', entry.latest_version))),
|
|
47
|
+
React.createElement(Button, { onClick: () => props.performAction('uninstall', entry), title: trans.__('Uninstall "%1"', entry.name), minimal: true, small: true }, trans.__('Uninstall')))),
|
|
48
|
+
entry.enabled ? (React.createElement(Button, { onClick: () => props.performAction('disable', entry), title: trans.__('Disable "%1"', entry.name), minimal: true, small: true }, trans.__('Disable'))) : (React.createElement(Button, { onClick: () => props.performAction('enable', entry), title: trans.__('Enable "%1"', entry.name), minimal: true, small: true }, trans.__('Enable'))))) : (supportInstallation && (React.createElement(Button, { onClick: () => props.performAction('install', entry), title: trans.__('Install "%1"', entry.name), minimal: true, small: true }, trans.__('Install'))))))))));
|
|
148
49
|
}
|
|
149
50
|
/**
|
|
150
51
|
* List view widget for extensions
|
|
151
52
|
*/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const trans =
|
|
155
|
-
const entryViews = [];
|
|
156
|
-
for (const entry of props.entries) {
|
|
157
|
-
entryViews.push(React.createElement(ListEntry, { entry: entry, listMode: props.listMode, viewType: props.viewType, key: entry.name, performAction: props.performAction, translator: translator }));
|
|
158
|
-
}
|
|
159
|
-
let pagination;
|
|
160
|
-
if (props.numPages > 1) {
|
|
161
|
-
pagination = (React.createElement("div", { className: "jp-extensionmanager-pagination" },
|
|
162
|
-
React.createElement(ReactPaginate, { previousLabel: '<', nextLabel: '>', breakLabel: React.createElement("a", { href: "" }, "..."), breakClassName: 'break-me', pageCount: props.numPages, marginPagesDisplayed: 2, pageRangeDisplayed: 5, onPageChange: (data) => props.onPage(data.selected), containerClassName: 'pagination', activeClassName: 'active' })));
|
|
163
|
-
}
|
|
164
|
-
const listview = (React.createElement("ul", { className: "jp-extensionmanager-listview" }, entryViews));
|
|
53
|
+
function ListView(props) {
|
|
54
|
+
var _a;
|
|
55
|
+
const { canFetch, performAction, supportInstallation, trans } = props;
|
|
165
56
|
return (React.createElement("div", { className: "jp-extensionmanager-listview-wrapper" },
|
|
166
|
-
|
|
167
|
-
pagination
|
|
57
|
+
props.entries.length > 0 ? (React.createElement("ul", { className: "jp-extensionmanager-listview" }, props.entries.map(entry => (React.createElement(ListEntry, { key: entry.name, canFetch: canFetch, entry: entry, performAction: performAction, supportInstallation: supportInstallation, trans: trans }))))) : (React.createElement("div", { key: "message", className: "jp-extensionmanager-listview-message" }, trans.__('No entries'))),
|
|
58
|
+
props.numPages > 1 && (React.createElement("div", { className: "jp-extensionmanager-pagination" },
|
|
59
|
+
React.createElement(ReactPaginate, { previousLabel: '<', nextLabel: '>', breakLabel: "...", breakClassName: 'break-me', initialPage: ((_a = props.initialPage) !== null && _a !== void 0 ? _a : 1) - 1, pageCount: props.numPages, marginPagesDisplayed: 2, pageRangeDisplayed: 3, onPageChange: (data) => props.onPage(data.selected + 1), containerClassName: 'pagination', activeClassName: 'active' })))));
|
|
168
60
|
}
|
|
169
61
|
function ErrorMessage(props) {
|
|
170
|
-
return
|
|
62
|
+
return React.createElement("div", { className: "jp-extensionmanager-error" }, props.children);
|
|
171
63
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
this.
|
|
179
|
-
|
|
180
|
-
};
|
|
64
|
+
class Header extends ReactWidget {
|
|
65
|
+
constructor(model, trans, searchInputRef) {
|
|
66
|
+
super();
|
|
67
|
+
this.model = model;
|
|
68
|
+
this.trans = trans;
|
|
69
|
+
this.searchInputRef = searchInputRef;
|
|
70
|
+
model.stateChanged.connect(this.update, this);
|
|
71
|
+
this.addClass('jp-extensionmanager-header');
|
|
181
72
|
}
|
|
182
|
-
/**
|
|
183
|
-
* Render the collapsible section using the virtual DOM.
|
|
184
|
-
*/
|
|
185
73
|
render() {
|
|
186
|
-
let icon = this.state.isOpen ? caretDownIconStyled : caretRightIconStyled;
|
|
187
|
-
let isOpen = this.state.isOpen;
|
|
188
|
-
let className = 'jp-extensionmanager-headerText';
|
|
189
|
-
if (this.props.disabled) {
|
|
190
|
-
icon = caretRightIconStyled;
|
|
191
|
-
isOpen = false;
|
|
192
|
-
className = 'jp-extensionmanager-headerTextDisabled';
|
|
193
|
-
}
|
|
194
74
|
return (React.createElement(React.Fragment, null,
|
|
195
|
-
React.createElement("div", { className: "jp-
|
|
196
|
-
React.createElement(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
React.createElement(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
handleCollapse() {
|
|
207
|
-
this.setState({
|
|
208
|
-
isOpen: !this.state.isOpen
|
|
209
|
-
}, () => {
|
|
210
|
-
if (this.props.onCollapse) {
|
|
211
|
-
this.props.onCollapse(this.state.isOpen);
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
216
|
-
if (nextProps.forceOpen) {
|
|
217
|
-
this.setState({
|
|
218
|
-
isOpen: true
|
|
219
|
-
});
|
|
220
|
-
}
|
|
75
|
+
React.createElement("div", { className: "jp-extensionmanager-title" },
|
|
76
|
+
React.createElement("span", null, this.trans.__('%1 Manager', this.model.name)),
|
|
77
|
+
this.model.installPath && (React.createElement(infoIcon.react, { className: "jp-extensionmanager-path", tag: "span", title: this.trans.__('Extension installation path: %1', this.model.installPath) }))),
|
|
78
|
+
React.createElement(FilterBox, { placeholder: this.trans.__('Search'), disabled: !this.model.isDisclaimed, updateFilter: (fn, query) => {
|
|
79
|
+
this.model.query = query !== null && query !== void 0 ? query : '';
|
|
80
|
+
}, useFuzzyFilter: false, inputRef: this.searchInputRef }),
|
|
81
|
+
React.createElement("div", { className: `jp-extensionmanager-pending ${this.model.hasPendingActions() ? 'jp-mod-hasPending' : ''}` }),
|
|
82
|
+
this.model.actionError && (React.createElement(ErrorMessage, null,
|
|
83
|
+
React.createElement("p", null, this.trans.__('Error when performing an action.')),
|
|
84
|
+
React.createElement("p", null, this.trans.__('Reason given:')),
|
|
85
|
+
React.createElement("pre", null, this.model.actionError)))));
|
|
221
86
|
}
|
|
222
87
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
this.
|
|
230
|
-
this._trans = this.translator.load('jupyterlab');
|
|
231
|
-
this._settings = settings;
|
|
232
|
-
this._forceOpen = false;
|
|
233
|
-
this.addClass('jp-extensionmanager-view');
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* The search input node.
|
|
237
|
-
*/
|
|
238
|
-
get inputNode() {
|
|
239
|
-
return this.node.querySelector('.jp-extensionmanager-search-wrapper input');
|
|
88
|
+
class Warning extends ReactWidget {
|
|
89
|
+
constructor(model, trans) {
|
|
90
|
+
super();
|
|
91
|
+
this.model = model;
|
|
92
|
+
this.trans = trans;
|
|
93
|
+
this.addClass('jp-extensionmanager-disclaimer');
|
|
94
|
+
model.stateChanged.connect(this.update, this);
|
|
240
95
|
}
|
|
241
|
-
/**
|
|
242
|
-
* Render the extension view using the virtual DOM.
|
|
243
|
-
*/
|
|
244
96
|
render() {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return [React.createElement("div", { key: "empty" })];
|
|
249
|
-
}
|
|
250
|
-
if (model.listMode === 'invalid') {
|
|
251
|
-
return [
|
|
252
|
-
React.createElement("div", { style: { padding: 8 }, key: "invalid" },
|
|
253
|
-
React.createElement("div", null, this._trans
|
|
254
|
-
.__(`The extension manager is disabled. Please contact your system
|
|
255
|
-
administrator to verify the listings configuration.`)),
|
|
256
|
-
React.createElement("div", null,
|
|
257
|
-
React.createElement("a", { href: "https://jupyterlab.readthedocs.io/en/latest/user/extensions.html", target: "_blank", rel: "noopener noreferrer" }, this._trans.__('Read more in the JupyterLab documentation.'))))
|
|
258
|
-
];
|
|
259
|
-
}
|
|
260
|
-
const pages = Math.ceil(model.totalEntries / model.pagination);
|
|
261
|
-
const elements = [
|
|
262
|
-
React.createElement(SearchBar, { key: "searchbar", placeholder: this._trans.__('SEARCH'), disabled: !ListModel.isDisclaimed(), settings: this._settings })
|
|
263
|
-
];
|
|
264
|
-
if (model.promptBuild) {
|
|
265
|
-
elements.push(React.createElement(BuildPrompt, { key: "promt", translator: this.translator, performBuild: () => {
|
|
266
|
-
model.performBuild();
|
|
267
|
-
}, ignoreBuild: () => {
|
|
268
|
-
model.ignoreBuildRecommendation();
|
|
269
|
-
} }));
|
|
270
|
-
}
|
|
271
|
-
// Indicator element for pending actions:
|
|
272
|
-
elements.push(React.createElement("div", { key: "pending", className: `jp-extensionmanager-pending ${model.hasPendingActions() ? 'jp-mod-hasPending' : ''}` }));
|
|
273
|
-
const content = [];
|
|
274
|
-
content.push(React.createElement(CollapsibleSection, { key: "warning-section", isOpen: !ListModel.isDisclaimed(), disabled: false, header: this._trans.__('Warning') },
|
|
275
|
-
React.createElement("div", { className: "jp-extensionmanager-disclaimer" },
|
|
276
|
-
React.createElement("div", null, this._trans
|
|
97
|
+
return (React.createElement(React.Fragment, null,
|
|
98
|
+
React.createElement("p", null,
|
|
99
|
+
this.trans
|
|
277
100
|
.__(`The JupyterLab development team is excited to have a robust
|
|
278
101
|
third-party extension community. However, we do not review
|
|
279
102
|
third-party extensions, and some extensions may introduce security
|
|
280
|
-
risks or contain malicious code that runs on your machine
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
React.createElement("p", null, this._trans.__('Details:')),
|
|
308
|
-
React.createElement("pre", null, model.serverRequirementsError)));
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
// List installed and discovery sections
|
|
312
|
-
const installedContent = [];
|
|
313
|
-
if (model.installedError !== null) {
|
|
314
|
-
installedContent.push(React.createElement(ErrorMessage, { key: "install-error" }, `Error querying installed extensions${model.installedError ? `: ${model.installedError}` : '.'}`));
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
const query = new RegExp((_b = (_a = model.query) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '');
|
|
318
|
-
installedContent.push(React.createElement(ListView, { key: "installed-items", listMode: model.listMode, viewType: 'installed', entries: model.installed.filter(pkg => !model.query || query.test(pkg.name)), numPages: 1, translator: this.translator, onPage: value => {
|
|
319
|
-
/* no-op */
|
|
320
|
-
}, performAction: this.onAction.bind(this) }));
|
|
321
|
-
}
|
|
322
|
-
content.push(React.createElement(CollapsibleSection, { key: "installed-section", isOpen: ListModel.isDisclaimed(), forceOpen: this._forceOpen, disabled: !ListModel.isDisclaimed(), header: this._trans.__('Installed'), headerElements: React.createElement(ToolbarButtonComponent, { key: "refresh-button", icon: refreshIcon, onClick: () => {
|
|
323
|
-
model.refreshInstalled();
|
|
324
|
-
}, tooltip: this._trans.__('Refresh extension list') }) }, installedContent));
|
|
325
|
-
const searchContent = [];
|
|
326
|
-
if (model.searchError !== null) {
|
|
327
|
-
searchContent.push(React.createElement(ErrorMessage, { key: "search-error" }, `Error searching for extensions${model.searchError ? `: ${model.searchError}` : '.'}`));
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
searchContent.push(React.createElement(ListView, { key: "search-items", listMode: model.listMode, viewType: 'searchResult',
|
|
331
|
-
// Filter out installed extensions:
|
|
332
|
-
entries: model.searchResult.filter(entry => model.installed.indexOf(entry) === -1), numPages: pages, onPage: value => {
|
|
333
|
-
this.onPage(value);
|
|
334
|
-
}, performAction: this.onAction.bind(this), translator: this.translator }));
|
|
335
|
-
}
|
|
336
|
-
content.push(React.createElement(CollapsibleSection, { key: "search-section", isOpen: ListModel.isDisclaimed(), forceOpen: this._forceOpen, disabled: !ListModel.isDisclaimed(), header: model.query
|
|
337
|
-
? this._trans.__('Search Results')
|
|
338
|
-
: this._trans.__('Discover'), onCollapse: (isOpen) => {
|
|
339
|
-
if (isOpen && model.query === null) {
|
|
340
|
-
model.query = '';
|
|
341
|
-
}
|
|
342
|
-
} }, searchContent));
|
|
343
|
-
}
|
|
344
|
-
elements.push(React.createElement("div", { key: "content", className: "jp-extensionmanager-content" }, content));
|
|
345
|
-
// Reset the force open for future usage.
|
|
346
|
-
this._forceOpen = false;
|
|
347
|
-
return elements;
|
|
103
|
+
risks or contain malicious code that runs on your machine. Moreover in order
|
|
104
|
+
to work, this panel needs to fetch data from web services. Do you agree to
|
|
105
|
+
activate this feature?`),
|
|
106
|
+
React.createElement("br", null),
|
|
107
|
+
React.createElement("a", { href: "https://jupyterlab.readthedocs.io/en/latest/privacy_policies.html", target: "_blank", rel: "noreferrer" }, this.trans.__('Please read the privacy policy.'))),
|
|
108
|
+
this.model.isDisclaimed ? (React.createElement(Button, { className: "jp-extensionmanager-disclaimer-disable", onClick: (e) => {
|
|
109
|
+
this.model.isDisclaimed = false;
|
|
110
|
+
}, title: this.trans.__('This will withdraw your consent.') }, this.trans.__('No'))) : (React.createElement("div", null,
|
|
111
|
+
React.createElement(Button, { className: "jp-extensionmanager-disclaimer-enable", onClick: () => {
|
|
112
|
+
this.model.isDisclaimed = true;
|
|
113
|
+
} }, this.trans.__('Yes')),
|
|
114
|
+
React.createElement(Button, { className: "jp-extensionmanager-disclaimer-disable", onClick: () => {
|
|
115
|
+
this.model.isEnabled = false;
|
|
116
|
+
}, title: this.trans.__('This will disable the extension manager panel; including the listing of installed extension.') }, this.trans.__('No, disable'))))));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
class InstalledList extends ReactWidget {
|
|
120
|
+
constructor(model, trans) {
|
|
121
|
+
super();
|
|
122
|
+
this.model = model;
|
|
123
|
+
this.trans = trans;
|
|
124
|
+
model.stateChanged.connect(this.update, this);
|
|
125
|
+
}
|
|
126
|
+
render() {
|
|
127
|
+
return (React.createElement(React.Fragment, null, this.model.installedError !== null ? (React.createElement(ErrorMessage, null, `Error querying installed extensions${this.model.installedError ? `: ${this.model.installedError}` : '.'}`)) : this.model.isLoadingInstalledExtensions ? (React.createElement("div", { className: "jp-extensionmanager-loader" }, this.trans.__('Updating extensions list…'))) : (React.createElement(ListView, { canFetch: this.model.isDisclaimed, entries: this.model.installed.filter(pkg => new RegExp(this.model.query.toLowerCase()).test(pkg.name)), numPages: 1, trans: this.trans, onPage: value => {
|
|
128
|
+
/* no-op */
|
|
129
|
+
}, performAction: this.model.isDisclaimed ? this.onAction.bind(this) : null, supportInstallation: this.model.canInstall && this.model.isDisclaimed }))));
|
|
348
130
|
}
|
|
349
131
|
/**
|
|
350
|
-
* Callback handler for the user
|
|
132
|
+
* Callback handler for when the user wants to perform an action on an extension.
|
|
351
133
|
*
|
|
352
|
-
* @param
|
|
134
|
+
* @param action The action to perform.
|
|
135
|
+
* @param entry The entry to perform the action on.
|
|
353
136
|
*/
|
|
354
|
-
|
|
355
|
-
|
|
137
|
+
onAction(action, entry) {
|
|
138
|
+
switch (action) {
|
|
139
|
+
case 'install':
|
|
140
|
+
return this.model.install(entry);
|
|
141
|
+
case 'uninstall':
|
|
142
|
+
return this.model.uninstall(entry);
|
|
143
|
+
case 'enable':
|
|
144
|
+
return this.model.enable(entry);
|
|
145
|
+
case 'disable':
|
|
146
|
+
return this.model.disable(entry);
|
|
147
|
+
default:
|
|
148
|
+
throw new Error(`Invalid action: ${action}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
class SearchResult extends ReactWidget {
|
|
153
|
+
constructor(model, trans) {
|
|
154
|
+
super();
|
|
155
|
+
this.model = model;
|
|
156
|
+
this.trans = trans;
|
|
157
|
+
model.stateChanged.connect(this.update, this);
|
|
356
158
|
}
|
|
357
159
|
/**
|
|
358
160
|
* Callback handler for the user changes the page of the search result pagination.
|
|
@@ -382,6 +184,74 @@ risks or contain malicious code that runs on your machine.`)),
|
|
|
382
184
|
throw new Error(`Invalid action: ${action}`);
|
|
383
185
|
}
|
|
384
186
|
}
|
|
187
|
+
render() {
|
|
188
|
+
return (React.createElement(React.Fragment, null, this.model.searchError !== null ? (React.createElement(ErrorMessage, null, `Error searching for extensions${this.model.searchError ? `: ${this.model.searchError}` : '.'}`)) : this.model.isSearching ? (React.createElement("div", { className: "jp-extensionmanager-loader" }, this.trans.__('Updating extensions list…'))) : (React.createElement(ListView, { canFetch: this.model.isDisclaimed, entries: this.model.searchResult, initialPage: this.model.page, numPages: this.model.lastPage, onPage: value => {
|
|
189
|
+
this.onPage(value);
|
|
190
|
+
}, performAction: this.model.isDisclaimed ? this.onAction.bind(this) : null, supportInstallation: this.model.canInstall && this.model.isDisclaimed, trans: this.trans }))));
|
|
191
|
+
}
|
|
192
|
+
update() {
|
|
193
|
+
this.title.label = this.model.query
|
|
194
|
+
? this.trans.__('Search Results')
|
|
195
|
+
: this.trans.__('Discover');
|
|
196
|
+
super.update();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export class ExtensionsPanel extends SidePanel {
|
|
200
|
+
constructor(options) {
|
|
201
|
+
const { model, translator } = options;
|
|
202
|
+
super({ translator });
|
|
203
|
+
this._wasInitialized = false;
|
|
204
|
+
this._wasDisclaimed = true;
|
|
205
|
+
this.model = model;
|
|
206
|
+
this._searchInputRef = React.createRef();
|
|
207
|
+
this.addClass('jp-extensionmanager-view');
|
|
208
|
+
this.trans = translator.load('jupyterlab');
|
|
209
|
+
this.header.addWidget(new Header(model, this.trans, this._searchInputRef));
|
|
210
|
+
const warning = new Warning(model, this.trans);
|
|
211
|
+
warning.title.label = this.trans.__('Warning');
|
|
212
|
+
this.addWidget(warning);
|
|
213
|
+
const installed = new PanelWithToolbar();
|
|
214
|
+
installed.addClass('jp-extensionmanager-installedlist');
|
|
215
|
+
installed.title.label = this.trans.__('Installed');
|
|
216
|
+
installed.toolbar.addItem('refresh', new ToolbarButton({
|
|
217
|
+
icon: refreshIcon,
|
|
218
|
+
onClick: () => {
|
|
219
|
+
model.refreshInstalled(true).catch(reason => {
|
|
220
|
+
console.error(`Failed to refresh the installed extensions list:\n${reason}`);
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
tooltip: this.trans.__('Refresh extensions list')
|
|
224
|
+
}));
|
|
225
|
+
installed.addWidget(new InstalledList(model, this.trans));
|
|
226
|
+
this.addWidget(installed);
|
|
227
|
+
if (this.model.canInstall) {
|
|
228
|
+
const searchResults = new SearchResult(model, this.trans);
|
|
229
|
+
searchResults.addClass('jp-extensionmanager-searchresults');
|
|
230
|
+
this.addWidget(searchResults);
|
|
231
|
+
}
|
|
232
|
+
this._wasDisclaimed = this.model.isDisclaimed;
|
|
233
|
+
if (this.model.isDisclaimed) {
|
|
234
|
+
this.content.collapse(0);
|
|
235
|
+
this.content.layout.setRelativeSizes([0, 1, 1]);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// If warning is not disclaimed expand only the warning panel
|
|
239
|
+
this.content.expand(0);
|
|
240
|
+
this.content.collapse(1);
|
|
241
|
+
this.content.collapse(2);
|
|
242
|
+
}
|
|
243
|
+
this.model.stateChanged.connect(this._onStateChanged, this);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Dispose of the widget and its descendant widgets.
|
|
247
|
+
*/
|
|
248
|
+
dispose() {
|
|
249
|
+
if (this.isDisposed) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
this.model.stateChanged.disconnect(this._onStateChanged, this);
|
|
253
|
+
super.dispose();
|
|
254
|
+
}
|
|
385
255
|
/**
|
|
386
256
|
* Handle the DOM events for the extension manager search bar.
|
|
387
257
|
*
|
|
@@ -394,9 +264,6 @@ risks or contain malicious code that runs on your machine.`)),
|
|
|
394
264
|
*/
|
|
395
265
|
handleEvent(event) {
|
|
396
266
|
switch (event.type) {
|
|
397
|
-
case 'input':
|
|
398
|
-
this.onSearch(this.inputNode.value);
|
|
399
|
-
break;
|
|
400
267
|
case 'focus':
|
|
401
268
|
case 'blur':
|
|
402
269
|
this._toggleFocused();
|
|
@@ -409,15 +276,23 @@ risks or contain malicious code that runs on your machine.`)),
|
|
|
409
276
|
* A message handler invoked on a `'before-attach'` message.
|
|
410
277
|
*/
|
|
411
278
|
onBeforeAttach(msg) {
|
|
412
|
-
this.node.addEventListener('input', this);
|
|
413
279
|
this.node.addEventListener('focus', this, true);
|
|
414
280
|
this.node.addEventListener('blur', this, true);
|
|
281
|
+
super.onBeforeAttach(msg);
|
|
282
|
+
}
|
|
283
|
+
onBeforeShow(msg) {
|
|
284
|
+
if (!this._wasInitialized) {
|
|
285
|
+
this._wasInitialized = true;
|
|
286
|
+
this.model.refreshInstalled().catch(reason => {
|
|
287
|
+
console.log(`Failed to refresh installed extension list:\n${reason}`);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
415
290
|
}
|
|
416
291
|
/**
|
|
417
292
|
* A message handler invoked on an `'after-detach'` message.
|
|
418
293
|
*/
|
|
419
294
|
onAfterDetach(msg) {
|
|
420
|
-
|
|
295
|
+
super.onAfterDetach(msg);
|
|
421
296
|
this.node.removeEventListener('focus', this, true);
|
|
422
297
|
this.node.removeEventListener('blur', this, true);
|
|
423
298
|
}
|
|
@@ -426,18 +301,27 @@ risks or contain malicious code that runs on your machine.`)),
|
|
|
426
301
|
*/
|
|
427
302
|
onActivateRequest(msg) {
|
|
428
303
|
if (this.isAttached) {
|
|
429
|
-
const input = this.
|
|
304
|
+
const input = this._searchInputRef.current;
|
|
430
305
|
if (input) {
|
|
431
306
|
input.focus();
|
|
432
307
|
input.select();
|
|
433
308
|
}
|
|
434
309
|
}
|
|
310
|
+
super.onActivateRequest(msg);
|
|
311
|
+
}
|
|
312
|
+
_onStateChanged() {
|
|
313
|
+
if (!this._wasDisclaimed && this.model.isDisclaimed) {
|
|
314
|
+
this.content.collapse(0);
|
|
315
|
+
this.content.expand(1);
|
|
316
|
+
this.content.expand(2);
|
|
317
|
+
}
|
|
318
|
+
this._wasDisclaimed = this.model.isDisclaimed;
|
|
435
319
|
}
|
|
436
320
|
/**
|
|
437
321
|
* Toggle the focused modifier based on the input node focus state.
|
|
438
322
|
*/
|
|
439
323
|
_toggleFocused() {
|
|
440
|
-
const focused = document.activeElement === this.
|
|
324
|
+
const focused = document.activeElement === this._searchInputRef.current;
|
|
441
325
|
this.toggleClass('lm-mod-focused', focused);
|
|
442
326
|
}
|
|
443
327
|
}
|