@jupyterlab/extensionmanager 4.0.0-alpha.2 → 4.0.0-alpha.21
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 -328
- package/lib/widget.js.map +1 -1
- package/package.json +15 -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 +68 -119
- 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,299 +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
|
-
entry.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
ListModel.isDisclaimed() && (React.createElement(Button, { onClick: () => props.performAction('install', entry), minimal: true, small: true }, trans.__('Install'))),
|
|
124
|
-
ListModel.entryHasUpdate(entry) &&
|
|
125
|
-
entry.pkg_type == 'source' &&
|
|
126
|
-
!entry.blockedExtensionsEntry &&
|
|
127
|
-
!(!entry.allowedExtensionsEntry && listMode === 'allow') &&
|
|
128
|
-
ListModel.isDisclaimed() && (React.createElement(Button, { onClick: () => props.performAction('install', entry), minimal: true, small: true }, trans.__('Update'))),
|
|
129
|
-
entry.installed && entry.pkg_type == 'source' && (React.createElement(Button, { onClick: () => props.performAction('uninstall', entry), minimal: true, small: true }, trans.__('Uninstall'))),
|
|
130
|
-
entry.enabled && entry.pkg_type == 'source' && (React.createElement(Button, { onClick: () => props.performAction('disable', entry), minimal: true, small: true }, trans.__('Disable'))),
|
|
131
|
-
entry.installed && entry.pkg_type == 'source' && !entry.enabled && (React.createElement(Button, { onClick: () => props.performAction('enable', entry), minimal: true, small: true }, trans.__('Enable'))),
|
|
132
|
-
entry.installed && entry.pkg_type == 'prebuilt' && (React.createElement("div", { className: "jp-extensionmanager-entry-buttons" },
|
|
133
|
-
React.createElement(Button, { onClick: () => showDialog({
|
|
134
|
-
title,
|
|
135
|
-
body: (React.createElement("div", null, getPrebuiltUninstallInstruction(entry, trans))),
|
|
136
|
-
buttons: [
|
|
137
|
-
Dialog.okButton({
|
|
138
|
-
label: trans.__('OK'),
|
|
139
|
-
caption: trans.__('OK')
|
|
140
|
-
})
|
|
141
|
-
]
|
|
142
|
-
}).then(result => {
|
|
143
|
-
return result.button.accept;
|
|
144
|
-
}), minimal: true, small: true }, trans.__('About')))))))));
|
|
145
|
-
}
|
|
146
|
-
function getPrebuiltUninstallInstruction(entry, trans) {
|
|
147
|
-
var _a, _b;
|
|
148
|
-
if ((_a = entry.install) === null || _a === void 0 ? void 0 : _a.uninstallInstructions) {
|
|
149
|
-
return (React.createElement("div", null,
|
|
150
|
-
React.createElement("p", null, trans.__(`This is a prebuilt extension. To uninstall it, please
|
|
151
|
-
apply following instructions.`)),
|
|
152
|
-
React.createElement("p", null, trans.__((_b = entry.install) === null || _b === void 0 ? void 0 : _b.uninstallInstructions))));
|
|
153
|
-
}
|
|
154
|
-
return (React.createElement("div", null,
|
|
155
|
-
React.createElement("p", null, trans.__(`This is a prebuilt extension. To uninstall it, please
|
|
156
|
-
read the user guide on:`)),
|
|
157
|
-
React.createElement("p", null,
|
|
158
|
-
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'))))))))));
|
|
159
49
|
}
|
|
160
50
|
/**
|
|
161
51
|
* List view widget for extensions
|
|
162
52
|
*/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const trans =
|
|
166
|
-
const entryViews = [];
|
|
167
|
-
for (const entry of props.entries) {
|
|
168
|
-
entryViews.push(React.createElement(ListEntry, { entry: entry, listMode: props.listMode, viewType: props.viewType, key: entry.name, performAction: props.performAction, translator: translator }));
|
|
169
|
-
}
|
|
170
|
-
let pagination;
|
|
171
|
-
if (props.numPages > 1) {
|
|
172
|
-
pagination = (React.createElement("div", { className: "jp-extensionmanager-pagination" },
|
|
173
|
-
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' })));
|
|
174
|
-
}
|
|
175
|
-
const listview = (React.createElement("ul", { className: "jp-extensionmanager-listview" }, entryViews));
|
|
53
|
+
function ListView(props) {
|
|
54
|
+
var _a;
|
|
55
|
+
const { canFetch, performAction, supportInstallation, trans } = props;
|
|
176
56
|
return (React.createElement("div", { className: "jp-extensionmanager-listview-wrapper" },
|
|
177
|
-
|
|
178
|
-
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' })))));
|
|
179
60
|
}
|
|
180
61
|
function ErrorMessage(props) {
|
|
181
|
-
return
|
|
62
|
+
return React.createElement("div", { className: "jp-extensionmanager-error" }, props.children);
|
|
182
63
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.
|
|
190
|
-
|
|
191
|
-
};
|
|
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');
|
|
192
72
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Render the collapsible section using the virtual DOM.
|
|
195
|
-
*/
|
|
196
73
|
render() {
|
|
197
|
-
let icon = this.state.isOpen ? caretDownIconStyled : caretRightIconStyled;
|
|
198
|
-
let isOpen = this.state.isOpen;
|
|
199
|
-
let className = 'jp-extensionmanager-headerText';
|
|
200
|
-
if (this.props.disabled) {
|
|
201
|
-
icon = caretRightIconStyled;
|
|
202
|
-
isOpen = false;
|
|
203
|
-
className = 'jp-extensionmanager-headerTextDisabled';
|
|
204
|
-
}
|
|
205
74
|
return (React.createElement(React.Fragment, null,
|
|
206
|
-
React.createElement("div", { className: "jp-
|
|
207
|
-
React.createElement(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
React.createElement(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
handleCollapse() {
|
|
218
|
-
this.setState({
|
|
219
|
-
isOpen: !this.state.isOpen
|
|
220
|
-
}, () => {
|
|
221
|
-
if (this.props.onCollapse) {
|
|
222
|
-
this.props.onCollapse(this.state.isOpen);
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
227
|
-
if (nextProps.forceOpen) {
|
|
228
|
-
this.setState({
|
|
229
|
-
isOpen: true
|
|
230
|
-
});
|
|
231
|
-
}
|
|
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)))));
|
|
232
86
|
}
|
|
233
87
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
this.
|
|
241
|
-
this._trans = this.translator.load('jupyterlab');
|
|
242
|
-
this._settings = settings;
|
|
243
|
-
this._forceOpen = false;
|
|
244
|
-
this.addClass('jp-extensionmanager-view');
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* The search input node.
|
|
248
|
-
*/
|
|
249
|
-
get inputNode() {
|
|
250
|
-
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);
|
|
251
95
|
}
|
|
252
|
-
/**
|
|
253
|
-
* Render the extension view using the virtual DOM.
|
|
254
|
-
*/
|
|
255
96
|
render() {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
if (model.listMode === 'invalid') {
|
|
261
|
-
return [
|
|
262
|
-
React.createElement("div", { style: { padding: 8 }, key: "invalid" },
|
|
263
|
-
React.createElement("div", null, this._trans
|
|
264
|
-
.__(`The extension manager is disabled. Please contact your system
|
|
265
|
-
administrator to verify the listings configuration.`)),
|
|
266
|
-
React.createElement("div", null,
|
|
267
|
-
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.'))))
|
|
268
|
-
];
|
|
269
|
-
}
|
|
270
|
-
const pages = Math.ceil(model.totalEntries / model.pagination);
|
|
271
|
-
const elements = [
|
|
272
|
-
React.createElement(SearchBar, { key: "searchbar", placeholder: this._trans.__('SEARCH'), disabled: !ListModel.isDisclaimed(), settings: this._settings })
|
|
273
|
-
];
|
|
274
|
-
if (model.promptBuild) {
|
|
275
|
-
elements.push(React.createElement(BuildPrompt, { key: "promt", translator: this.translator, performBuild: () => {
|
|
276
|
-
model.performBuild();
|
|
277
|
-
}, ignoreBuild: () => {
|
|
278
|
-
model.ignoreBuildRecommendation();
|
|
279
|
-
} }));
|
|
280
|
-
}
|
|
281
|
-
// Indicator element for pending actions:
|
|
282
|
-
elements.push(React.createElement("div", { key: "pending", className: `jp-extensionmanager-pending ${model.hasPendingActions() ? 'jp-mod-hasPending' : ''}` }));
|
|
283
|
-
const content = [];
|
|
284
|
-
content.push(React.createElement(CollapsibleSection, { key: "warning-section", isOpen: !ListModel.isDisclaimed(), disabled: false, header: this._trans.__('Warning') },
|
|
285
|
-
React.createElement("div", { className: "jp-extensionmanager-disclaimer" },
|
|
286
|
-
React.createElement("div", null, this._trans
|
|
97
|
+
return (React.createElement(React.Fragment, null,
|
|
98
|
+
React.createElement("p", null,
|
|
99
|
+
this.trans
|
|
287
100
|
.__(`The JupyterLab development team is excited to have a robust
|
|
288
101
|
third-party extension community. However, we do not review
|
|
289
102
|
third-party extensions, and some extensions may introduce security
|
|
290
|
-
risks or contain malicious code that runs on your machine
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
React.createElement("p", null, this._trans.__('Details:')),
|
|
318
|
-
React.createElement("pre", null, model.serverRequirementsError)));
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
// List installed and discovery sections
|
|
322
|
-
const installedContent = [];
|
|
323
|
-
if (model.installedError !== null) {
|
|
324
|
-
installedContent.push(React.createElement(ErrorMessage, { key: "install-error" }, `Error querying installed extensions${model.installedError ? `: ${model.installedError}` : '.'}`));
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
installedContent.push(React.createElement(ListView, { key: "installed-items", listMode: model.listMode, viewType: 'installed', entries: model.searchResult.length > 0
|
|
328
|
-
? model.installed.filter(entry => model.searchResult.indexOf(entry) > -1)
|
|
329
|
-
: model.installed, numPages: 1, translator: this.translator, onPage: value => {
|
|
330
|
-
/* no-op */
|
|
331
|
-
}, performAction: this.onAction.bind(this) }));
|
|
332
|
-
}
|
|
333
|
-
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: () => {
|
|
334
|
-
model.refreshInstalled();
|
|
335
|
-
}, tooltip: this._trans.__('Refresh extension list') }) }, installedContent));
|
|
336
|
-
const searchContent = [];
|
|
337
|
-
if (model.searchError !== null) {
|
|
338
|
-
searchContent.push(React.createElement(ErrorMessage, { key: "search-error" }, `Error searching for extensions${model.searchError ? `: ${model.searchError}` : '.'}`));
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
searchContent.push(React.createElement(ListView, { key: "search-items", listMode: model.listMode, viewType: 'searchResult',
|
|
342
|
-
// Filter out installed extensions:
|
|
343
|
-
entries: model.searchResult.filter(entry => model.installed.indexOf(entry) === -1), numPages: pages, onPage: value => {
|
|
344
|
-
this.onPage(value);
|
|
345
|
-
}, performAction: this.onAction.bind(this), translator: this.translator }));
|
|
346
|
-
}
|
|
347
|
-
content.push(React.createElement(CollapsibleSection, { key: "search-section", isOpen: ListModel.isDisclaimed(), forceOpen: this._forceOpen, disabled: !ListModel.isDisclaimed(), header: model.query
|
|
348
|
-
? this._trans.__('Search Results')
|
|
349
|
-
: this._trans.__('Discover'), onCollapse: (isOpen) => {
|
|
350
|
-
if (isOpen && model.query === null) {
|
|
351
|
-
model.query = '';
|
|
352
|
-
}
|
|
353
|
-
} }, searchContent));
|
|
354
|
-
}
|
|
355
|
-
elements.push(React.createElement("div", { key: "content", className: "jp-extensionmanager-content" }, content));
|
|
356
|
-
// Reset the force open for future usage.
|
|
357
|
-
this._forceOpen = false;
|
|
358
|
-
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 }))));
|
|
359
130
|
}
|
|
360
131
|
/**
|
|
361
|
-
* Callback handler for the user
|
|
132
|
+
* Callback handler for when the user wants to perform an action on an extension.
|
|
362
133
|
*
|
|
363
|
-
* @param
|
|
134
|
+
* @param action The action to perform.
|
|
135
|
+
* @param entry The entry to perform the action on.
|
|
364
136
|
*/
|
|
365
|
-
|
|
366
|
-
|
|
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);
|
|
367
158
|
}
|
|
368
159
|
/**
|
|
369
160
|
* Callback handler for the user changes the page of the search result pagination.
|
|
@@ -393,6 +184,74 @@ risks or contain malicious code that runs on your machine.`)),
|
|
|
393
184
|
throw new Error(`Invalid action: ${action}`);
|
|
394
185
|
}
|
|
395
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
|
+
}
|
|
396
255
|
/**
|
|
397
256
|
* Handle the DOM events for the extension manager search bar.
|
|
398
257
|
*
|
|
@@ -405,9 +264,6 @@ risks or contain malicious code that runs on your machine.`)),
|
|
|
405
264
|
*/
|
|
406
265
|
handleEvent(event) {
|
|
407
266
|
switch (event.type) {
|
|
408
|
-
case 'input':
|
|
409
|
-
this.onSearch(this.inputNode.value);
|
|
410
|
-
break;
|
|
411
267
|
case 'focus':
|
|
412
268
|
case 'blur':
|
|
413
269
|
this._toggleFocused();
|
|
@@ -420,15 +276,23 @@ risks or contain malicious code that runs on your machine.`)),
|
|
|
420
276
|
* A message handler invoked on a `'before-attach'` message.
|
|
421
277
|
*/
|
|
422
278
|
onBeforeAttach(msg) {
|
|
423
|
-
this.node.addEventListener('input', this);
|
|
424
279
|
this.node.addEventListener('focus', this, true);
|
|
425
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
|
+
}
|
|
426
290
|
}
|
|
427
291
|
/**
|
|
428
292
|
* A message handler invoked on an `'after-detach'` message.
|
|
429
293
|
*/
|
|
430
294
|
onAfterDetach(msg) {
|
|
431
|
-
|
|
295
|
+
super.onAfterDetach(msg);
|
|
432
296
|
this.node.removeEventListener('focus', this, true);
|
|
433
297
|
this.node.removeEventListener('blur', this, true);
|
|
434
298
|
}
|
|
@@ -437,18 +301,27 @@ risks or contain malicious code that runs on your machine.`)),
|
|
|
437
301
|
*/
|
|
438
302
|
onActivateRequest(msg) {
|
|
439
303
|
if (this.isAttached) {
|
|
440
|
-
const input = this.
|
|
304
|
+
const input = this._searchInputRef.current;
|
|
441
305
|
if (input) {
|
|
442
306
|
input.focus();
|
|
443
307
|
input.select();
|
|
444
308
|
}
|
|
445
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;
|
|
446
319
|
}
|
|
447
320
|
/**
|
|
448
321
|
* Toggle the focused modifier based on the input node focus state.
|
|
449
322
|
*/
|
|
450
323
|
_toggleFocused() {
|
|
451
|
-
const focused = document.activeElement === this.
|
|
324
|
+
const focused = document.activeElement === this._searchInputRef.current;
|
|
452
325
|
this.toggleClass('lm-mod-focused', focused);
|
|
453
326
|
}
|
|
454
327
|
}
|