@oceanum/oceanumlab 4.7.1 → 4.8.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/DatameshWidget.d.ts +11 -0
- package/lib/DatameshWidget.js +35 -5
- package/lib/StoredNotebooks.d.ts +27 -5
- package/lib/StoredNotebooks.js +141 -24
- package/lib/auth/serverAuth.js +1 -0
- package/lib/auth/startSignIn.d.ts +11 -0
- package/lib/auth/startSignIn.js +16 -0
- package/lib/auth/tokens.d.ts +13 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +43 -7
- package/lib/share/client.d.ts +86 -0
- package/lib/share/client.js +206 -0
- package/lib/share/events.d.ts +7 -0
- package/lib/share/events.js +9 -0
- package/lib/share/notebook.d.ts +216 -0
- package/lib/share/notebook.js +355 -0
- package/lib/share/oceanumOnly.d.ts +72 -0
- package/lib/share/oceanumOnly.js +127 -0
- package/lib/share/plugin.d.ts +23 -0
- package/lib/share/plugin.js +981 -0
- package/lib/share/upload.d.ts +6 -0
- package/lib/share/upload.js +27 -0
- package/lib/share/widgets.d.ts +25 -0
- package/lib/share/widgets.js +154 -0
- package/package.json +5 -1
- package/schema/datamesh-connect.json +6 -0
- package/src/DatameshWidget.tsx +79 -2
- package/src/StoredNotebooks.tsx +274 -24
- package/src/__tests__/OceanumPanel.spec.tsx +159 -5
- package/src/__tests__/StoredNotebooks.spec.tsx +528 -0
- package/src/__tests__/oceanumOnly.spec.ts +317 -0
- package/src/__tests__/settingsSchema.spec.ts +11 -1
- package/src/__tests__/shareClient.spec.ts +328 -0
- package/src/__tests__/shareNotebook.spec.ts +624 -0
- package/src/__tests__/sharePlugin.spec.ts +664 -0
- package/src/auth/serverAuth.ts +1 -0
- package/src/auth/startSignIn.ts +23 -0
- package/src/auth/tokens.ts +13 -0
- package/src/index.ts +43 -7
- package/src/share/client.ts +291 -0
- package/src/share/events.ts +10 -0
- package/src/share/notebook.ts +525 -0
- package/src/share/oceanumOnly.ts +177 -0
- package/src/share/plugin.ts +1220 -0
- package/src/share/upload.ts +27 -0
- package/src/share/widgets.ts +217 -0
- package/style/index.css +212 -18
- package/lib/specStore.d.ts +0 -37
- package/lib/specStore.js +0 -57
- package/src/__tests__/specStore.spec.ts +0 -126
- package/src/specStore.ts +0 -82
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask the user for a notebook file on their computer; null if they cancel.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from the plugin because it is browser-only: the tests replace it.
|
|
5
|
+
*/
|
|
6
|
+
export function chooseNotebookFile() {
|
|
7
|
+
return new Promise(resolve => {
|
|
8
|
+
const input = document.createElement('input');
|
|
9
|
+
input.type = 'file';
|
|
10
|
+
input.accept = '.ipynb,application/x-ipynb+json';
|
|
11
|
+
input.hidden = true;
|
|
12
|
+
// Attached while the picker is open: some browsers (Safari) drop the `change`
|
|
13
|
+
// event of a detached input, or collect the input before it fires.
|
|
14
|
+
document.body.appendChild(input);
|
|
15
|
+
const settle = (file) => {
|
|
16
|
+
input.remove();
|
|
17
|
+
resolve(file);
|
|
18
|
+
};
|
|
19
|
+
input.addEventListener('change', () => { var _a, _b; return settle((_b = (_a = input.files) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : null); }, {
|
|
20
|
+
once: true
|
|
21
|
+
});
|
|
22
|
+
// Browsers fire `cancel` when the picker is dismissed; without it the promise
|
|
23
|
+
// would simply never settle, which is harmless.
|
|
24
|
+
input.addEventListener('cancel', () => settle(null), { once: true });
|
|
25
|
+
input.click();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Dialog } from '@jupyterlab/apputils';
|
|
2
|
+
import { Widget } from '@lumino/widgets';
|
|
3
|
+
import type { ShareChoice } from './client';
|
|
4
|
+
import { ISpecSummary } from './notebook';
|
|
5
|
+
/** Dialog body listing notebooks to open; its value is the selected record id. */
|
|
6
|
+
export declare class NotebookListBody extends Widget implements Dialog.IBodyWidget<string | null> {
|
|
7
|
+
constructor(mine: readonly ISpecSummary[], shared: readonly ISpecSummary[]);
|
|
8
|
+
getValue(): string | null;
|
|
9
|
+
private _addSection;
|
|
10
|
+
private _select;
|
|
11
|
+
private _selected;
|
|
12
|
+
}
|
|
13
|
+
export declare const EVERYONE_WARNING: string;
|
|
14
|
+
/** Dialog body for sharing; its value is the change to make, or `null` if incomplete. */
|
|
15
|
+
export declare class ShareBody extends Widget implements Dialog.IBodyWidget<ShareChoice | null> {
|
|
16
|
+
constructor(link: string);
|
|
17
|
+
getValue(): ShareChoice | null;
|
|
18
|
+
private _radio;
|
|
19
|
+
private static _count;
|
|
20
|
+
private readonly _person;
|
|
21
|
+
private readonly _everyone;
|
|
22
|
+
private readonly _stopEveryone;
|
|
23
|
+
private readonly _email;
|
|
24
|
+
private readonly _level;
|
|
25
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Clipboard } from '@jupyterlab/apputils';
|
|
2
|
+
import { Widget } from '@lumino/widgets';
|
|
3
|
+
import { parseShareEmails, parseTimestamp } from './notebook';
|
|
4
|
+
function element(tag, className, text) {
|
|
5
|
+
const node = document.createElement(tag);
|
|
6
|
+
node.className = className;
|
|
7
|
+
if (text !== undefined) {
|
|
8
|
+
// Names and emails come from other users: always set text, never HTML.
|
|
9
|
+
node.textContent = text;
|
|
10
|
+
}
|
|
11
|
+
return node;
|
|
12
|
+
}
|
|
13
|
+
/** Dialog body listing notebooks to open; its value is the selected record id. */
|
|
14
|
+
export class NotebookListBody extends Widget {
|
|
15
|
+
constructor(mine, shared) {
|
|
16
|
+
super();
|
|
17
|
+
this._selected = null;
|
|
18
|
+
this.addClass('jp-OceanumShare-body');
|
|
19
|
+
this._addSection('My notebooks', mine, false);
|
|
20
|
+
this._addSection('Shared with me', shared, true);
|
|
21
|
+
}
|
|
22
|
+
getValue() {
|
|
23
|
+
return this._selected;
|
|
24
|
+
}
|
|
25
|
+
_addSection(title, items, showCreator) {
|
|
26
|
+
this.node.appendChild(element('h3', 'jp-OceanumShare-heading', title));
|
|
27
|
+
if (items.length === 0) {
|
|
28
|
+
this.node.appendChild(element('p', 'jp-OceanumShare-empty', 'None'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const list = element('ul', 'jp-OceanumShare-list');
|
|
32
|
+
list.setAttribute('role', 'listbox');
|
|
33
|
+
list.setAttribute('aria-label', title);
|
|
34
|
+
for (const item of items) {
|
|
35
|
+
const option = element('li', 'jp-OceanumShare-item');
|
|
36
|
+
option.setAttribute('role', 'option');
|
|
37
|
+
option.setAttribute('aria-selected', 'false');
|
|
38
|
+
option.tabIndex = 0;
|
|
39
|
+
option.appendChild(element('span', 'jp-OceanumShare-name', item.name));
|
|
40
|
+
const modified = parseTimestamp(item.modified);
|
|
41
|
+
const details = [
|
|
42
|
+
modified ? `Modified ${modified.toLocaleDateString()}` : '',
|
|
43
|
+
showCreator && item.creator ? item.creator : ''
|
|
44
|
+
].filter(Boolean);
|
|
45
|
+
option.appendChild(element('span', 'jp-OceanumShare-meta', details.join(' · ')));
|
|
46
|
+
const select = () => this._select(item.id, option);
|
|
47
|
+
option.addEventListener('click', select);
|
|
48
|
+
option.addEventListener('focus', select);
|
|
49
|
+
list.appendChild(option);
|
|
50
|
+
}
|
|
51
|
+
this.node.appendChild(list);
|
|
52
|
+
}
|
|
53
|
+
_select(id, option) {
|
|
54
|
+
this._selected = id;
|
|
55
|
+
for (const node of this.node.querySelectorAll('.jp-OceanumShare-item')) {
|
|
56
|
+
const selected = node === option;
|
|
57
|
+
node.classList.toggle('jp-mod-selected', selected);
|
|
58
|
+
node.setAttribute('aria-selected', String(selected));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export const EVERYONE_WARNING = 'Anyone with the link can read this notebook, including people without an Oceanum ' +
|
|
63
|
+
'account. This cannot be limited to your organisation. Its name and your email ' +
|
|
64
|
+
'address also become visible to anyone who lists Oceanum notebooks.';
|
|
65
|
+
const PUBLIC_GRANT = {
|
|
66
|
+
type: 'public',
|
|
67
|
+
entity: '',
|
|
68
|
+
permission: 'read'
|
|
69
|
+
};
|
|
70
|
+
/** Dialog body for sharing; its value is the change to make, or `null` if incomplete. */
|
|
71
|
+
export class ShareBody extends Widget {
|
|
72
|
+
constructor(link) {
|
|
73
|
+
super();
|
|
74
|
+
this.addClass('jp-OceanumShare-body');
|
|
75
|
+
this.node.appendChild(element('h3', 'jp-OceanumShare-heading', 'Link'));
|
|
76
|
+
const row = element('div', 'jp-OceanumShare-linkRow');
|
|
77
|
+
const linkInput = element('input', 'jp-OceanumShare-link');
|
|
78
|
+
linkInput.readOnly = true;
|
|
79
|
+
linkInput.value = link;
|
|
80
|
+
linkInput.setAttribute('aria-label', 'Notebook link');
|
|
81
|
+
const copy = element('button', 'jp-mod-styled jp-OceanumShare-copy', 'Copy link');
|
|
82
|
+
copy.type = 'button';
|
|
83
|
+
copy.addEventListener('click', () => {
|
|
84
|
+
Clipboard.copyToSystem(link);
|
|
85
|
+
copy.textContent = 'Copied';
|
|
86
|
+
});
|
|
87
|
+
row.append(linkInput, copy);
|
|
88
|
+
this.node.appendChild(row);
|
|
89
|
+
this.node.appendChild(element('p', 'jp-OceanumShare-hint', 'The link opens the notebook for anyone it is shared with.'));
|
|
90
|
+
this.node.appendChild(element('h3', 'jp-OceanumShare-heading', 'Share with'));
|
|
91
|
+
const name = `jp-OceanumShare-who-${++ShareBody._count}`;
|
|
92
|
+
this._person = this._radio(name, 'A specific person', true);
|
|
93
|
+
const personRow = element('div', 'jp-OceanumShare-personRow');
|
|
94
|
+
this._email = element('textarea', 'jp-OceanumShare-email');
|
|
95
|
+
this._email.rows = 2;
|
|
96
|
+
this._email.placeholder = 'name@example.com, another@example.com';
|
|
97
|
+
this._email.setAttribute('aria-label', 'Email addresses');
|
|
98
|
+
this._level = element('select', 'jp-OceanumShare-level');
|
|
99
|
+
this._level.setAttribute('aria-label', 'Access');
|
|
100
|
+
for (const [value, label] of [
|
|
101
|
+
['read', 'can view'],
|
|
102
|
+
['write', 'can edit']
|
|
103
|
+
]) {
|
|
104
|
+
const option = element('option', '', label);
|
|
105
|
+
option.value = value;
|
|
106
|
+
this._level.appendChild(option);
|
|
107
|
+
}
|
|
108
|
+
personRow.append(this._email, this._level);
|
|
109
|
+
this.node.appendChild(personRow);
|
|
110
|
+
this.node.appendChild(element('p', 'jp-OceanumShare-hint',
|
|
111
|
+
// Not "new lines": JupyterLab's dialog swallows Enter before it reaches the
|
|
112
|
+
// textarea, so one cannot be typed here. Pasted ones still separate addresses.
|
|
113
|
+
'Separate several addresses with commas or spaces. Everyone you add gets the access chosen here.'));
|
|
114
|
+
this._everyone = this._radio(name, 'Everyone with the link', false);
|
|
115
|
+
this.node.appendChild(element('p', 'jp-OceanumShare-warning', EVERYONE_WARNING));
|
|
116
|
+
this._stopEveryone = this._radio(name, 'Stop sharing with everyone', false);
|
|
117
|
+
const update = () => {
|
|
118
|
+
this._email.disabled = !this._person.checked;
|
|
119
|
+
this._level.disabled = !this._person.checked;
|
|
120
|
+
};
|
|
121
|
+
for (const radio of [this._person, this._everyone, this._stopEveryone]) {
|
|
122
|
+
radio.addEventListener('change', update);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
getValue() {
|
|
126
|
+
if (this._everyone.checked) {
|
|
127
|
+
return { action: 'grant', grants: [PUBLIC_GRANT], invalid: [] };
|
|
128
|
+
}
|
|
129
|
+
if (this._stopEveryone.checked) {
|
|
130
|
+
return { action: 'revoke', grant: PUBLIC_GRANT };
|
|
131
|
+
}
|
|
132
|
+
const { emails, invalid } = parseShareEmails(this._email.value);
|
|
133
|
+
if (emails.length === 0 && invalid.length === 0) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const permission = this._level.value === 'write' ? 'write' : 'read';
|
|
137
|
+
return {
|
|
138
|
+
action: 'grant',
|
|
139
|
+
grants: emails.map(entity => ({ type: 'user', entity, permission })),
|
|
140
|
+
invalid
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
_radio(name, text, checked) {
|
|
144
|
+
const label = element('label', 'jp-OceanumShare-choice');
|
|
145
|
+
const input = element('input', 'jp-OceanumShare-radio');
|
|
146
|
+
input.type = 'radio';
|
|
147
|
+
input.name = name;
|
|
148
|
+
input.checked = checked;
|
|
149
|
+
label.append(input, document.createTextNode(` ${text}`));
|
|
150
|
+
this.node.appendChild(label);
|
|
151
|
+
return input;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
ShareBody._count = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oceanum/oceanumlab",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "A Jupyterlab extension to interact with the Oceanum.io platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -62,12 +62,16 @@
|
|
|
62
62
|
"@jupyterlab/cells": "^4.6.3",
|
|
63
63
|
"@jupyterlab/console": "^4.6.3",
|
|
64
64
|
"@jupyterlab/coreutils": "^6.6.3",
|
|
65
|
+
"@jupyterlab/docmanager": "^4.6.3",
|
|
66
|
+
"@jupyterlab/mainmenu": "^4.6.3",
|
|
67
|
+
"@jupyterlab/nbformat": "^4.6.3",
|
|
65
68
|
"@jupyterlab/notebook": "^4.6.3",
|
|
66
69
|
"@jupyterlab/services": "^7.6.3",
|
|
67
70
|
"@jupyterlab/settingregistry": "^4.6.3",
|
|
68
71
|
"@lumino/commands": "^2.3.3",
|
|
69
72
|
"@lumino/coreutils": "^2.2.2",
|
|
70
73
|
"@lumino/disposable": "^2.1.5",
|
|
74
|
+
"@lumino/messaging": "^2.0.3",
|
|
71
75
|
"@lumino/signaling": "^2.1.5",
|
|
72
76
|
"@lumino/widgets": "^2.8.0",
|
|
73
77
|
"@phosphor-icons/react": "^2.1.10",
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
"title": "Iterate",
|
|
30
30
|
"description": "After running, send each cell's output back to the AI so it can fix an error or take the next step. Only takes effect with Auto-run code on.",
|
|
31
31
|
"default": false
|
|
32
|
+
},
|
|
33
|
+
"showExamples": {
|
|
34
|
+
"type": "boolean",
|
|
35
|
+
"title": "Show examples",
|
|
36
|
+
"description": "List Oceanum's example notebooks in the Notebooks tab, where the site provides them. Also switched from the Examples heading.",
|
|
37
|
+
"default": true
|
|
32
38
|
}
|
|
33
39
|
},
|
|
34
40
|
"patternProperties": {
|
package/src/DatameshWidget.tsx
CHANGED
|
@@ -22,6 +22,7 @@ import { marked } from 'marked';
|
|
|
22
22
|
|
|
23
23
|
import { DatasourceItem } from './DatasourceItem';
|
|
24
24
|
import { IOceanumAuth } from './auth/tokens';
|
|
25
|
+
import { startSignIn } from './auth/startSignIn';
|
|
25
26
|
import { StoredNotebooks } from './StoredNotebooks';
|
|
26
27
|
import { ITab, Tabs } from './Tabs';
|
|
27
28
|
import {
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
aiBackendUrl,
|
|
30
31
|
aiCredentialSource,
|
|
31
32
|
canSignIn,
|
|
33
|
+
isSignedIn,
|
|
32
34
|
pastedToken,
|
|
33
35
|
resolveAiCredential,
|
|
34
36
|
signInToken,
|
|
@@ -496,6 +498,8 @@ interface IAiAccess {
|
|
|
496
498
|
pasted: string;
|
|
497
499
|
/** Whether the host can sign the user in to Oceanum.io on this site. */
|
|
498
500
|
canSignIn: boolean;
|
|
501
|
+
/** Whether the host says the user is signed in to Oceanum.io. */
|
|
502
|
+
signedIn: boolean;
|
|
499
503
|
/** The backend address: the deployment's Oceanum AI. */
|
|
500
504
|
backend: string;
|
|
501
505
|
}
|
|
@@ -509,6 +513,7 @@ function readAiAccess(
|
|
|
509
513
|
source: aiCredentialSource(datameshToken, commands),
|
|
510
514
|
pasted: pastedToken(datameshToken),
|
|
511
515
|
canSignIn: canSignIn(commands),
|
|
516
|
+
signedIn: isSignedIn(commands),
|
|
512
517
|
backend: aiBackendUrl(settings.aiBackendUrl())
|
|
513
518
|
};
|
|
514
519
|
}
|
|
@@ -550,6 +555,34 @@ function useAiAccess(
|
|
|
550
555
|
return access;
|
|
551
556
|
}
|
|
552
557
|
|
|
558
|
+
/**
|
|
559
|
+
* The `showExamples` setting, read again whenever the settings change. True where the
|
|
560
|
+
* host passes no such setting; null while the host's setting has not loaded.
|
|
561
|
+
*/
|
|
562
|
+
function useShowExamples(
|
|
563
|
+
settingsChanged: ISignal<unknown, void>,
|
|
564
|
+
read: (() => boolean | null) | undefined
|
|
565
|
+
): boolean | null {
|
|
566
|
+
const [show, setShow] = React.useState<boolean | null>(() =>
|
|
567
|
+
read ? read() : true
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
React.useEffect(() => {
|
|
571
|
+
if (!read) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const update = () => setShow(read());
|
|
575
|
+
// Anything that changed between the first render and now.
|
|
576
|
+
update();
|
|
577
|
+
settingsChanged.connect(update);
|
|
578
|
+
return () => {
|
|
579
|
+
settingsChanged.disconnect(update);
|
|
580
|
+
};
|
|
581
|
+
}, [settingsChanged, read]);
|
|
582
|
+
|
|
583
|
+
return show;
|
|
584
|
+
}
|
|
585
|
+
|
|
553
586
|
/**
|
|
554
587
|
* Shows a message prompting the user to sign in to Oceanum.io, or to configure
|
|
555
588
|
* their Datamesh token where the site has no sign-in. Only renders when there
|
|
@@ -961,6 +994,17 @@ export interface IDatameshWidgetProps {
|
|
|
961
994
|
auth?: IOceanumAuth | null;
|
|
962
995
|
/** Open a stored notebook by spec store id. */
|
|
963
996
|
openStoredNotebook?: (id: string) => void;
|
|
997
|
+
/** Open a copy of an example by spec store id, named from its title. */
|
|
998
|
+
openExample?: (id: string, title: string) => void;
|
|
999
|
+
/** Upload a notebook from the user's computer. */
|
|
1000
|
+
uploadNotebook?: () => void;
|
|
1001
|
+
/**
|
|
1002
|
+
* The `showExamples` setting: whether the Notebooks tab lists the examples; null
|
|
1003
|
+
* until the settings have loaded.
|
|
1004
|
+
*/
|
|
1005
|
+
showExamples?: () => boolean | null;
|
|
1006
|
+
/** Save the `showExamples` setting, from the switch on the Examples heading. */
|
|
1007
|
+
setShowExamples?: (show: boolean) => void;
|
|
964
1008
|
}
|
|
965
1009
|
|
|
966
1010
|
/**
|
|
@@ -977,6 +1021,11 @@ function PanelTabs({
|
|
|
977
1021
|
commands,
|
|
978
1022
|
settings,
|
|
979
1023
|
renderDatamesh,
|
|
1024
|
+
openStoredNotebook,
|
|
1025
|
+
openExample,
|
|
1026
|
+
uploadNotebook,
|
|
1027
|
+
showExamples,
|
|
1028
|
+
setShowExamples,
|
|
980
1029
|
selected,
|
|
981
1030
|
onSelect
|
|
982
1031
|
}: {
|
|
@@ -984,10 +1033,16 @@ function PanelTabs({
|
|
|
984
1033
|
commands: CommandRegistry;
|
|
985
1034
|
settings: IAiSettings;
|
|
986
1035
|
renderDatamesh: () => React.ReactElement;
|
|
1036
|
+
openStoredNotebook?: (id: string) => void;
|
|
1037
|
+
openExample?: (id: string, title: string) => void;
|
|
1038
|
+
uploadNotebook?: () => void;
|
|
1039
|
+
showExamples?: () => boolean | null;
|
|
1040
|
+
setShowExamples?: (show: boolean) => void;
|
|
987
1041
|
selected: string;
|
|
988
1042
|
onSelect: (id: string) => void;
|
|
989
1043
|
}): React.ReactElement {
|
|
990
1044
|
const access = useAiAccess(commands, settings);
|
|
1045
|
+
const examplesShown = useShowExamples(settings.settingsChanged, showExamples);
|
|
991
1046
|
const tabs: ITab[] = [
|
|
992
1047
|
{
|
|
993
1048
|
id: 'notebooks',
|
|
@@ -996,7 +1051,21 @@ function PanelTabs({
|
|
|
996
1051
|
// An auth with no environment is a provider that has nothing to sign in to (the
|
|
997
1052
|
// server has sign-in turned off). Asking the user to sign in would be a dead end.
|
|
998
1053
|
auth?.environment ? (
|
|
999
|
-
<StoredNotebooks
|
|
1054
|
+
<StoredNotebooks
|
|
1055
|
+
auth={auth}
|
|
1056
|
+
onOpen={openStoredNotebook && (item => openStoredNotebook(item.id))}
|
|
1057
|
+
onOpenExample={
|
|
1058
|
+
openExample && (item => openExample(item.id, item.title))
|
|
1059
|
+
}
|
|
1060
|
+
onUpload={uploadNotebook}
|
|
1061
|
+
showExamples={examplesShown}
|
|
1062
|
+
onShowExamplesChange={setShowExamples}
|
|
1063
|
+
onSignIn={() => {
|
|
1064
|
+
startSignIn(commands, auth).catch(error => {
|
|
1065
|
+
console.warn('Oceanum.io sign-in could not start.', error);
|
|
1066
|
+
});
|
|
1067
|
+
}}
|
|
1068
|
+
/>
|
|
1000
1069
|
) : (
|
|
1001
1070
|
<div className="oceanum-text-empty">
|
|
1002
1071
|
Oceanum.io sign-in is not configured for this host.
|
|
@@ -1005,7 +1074,10 @@ function PanelTabs({
|
|
|
1005
1074
|
},
|
|
1006
1075
|
{ id: 'datamesh', label: 'Datamesh', render: renderDatamesh }
|
|
1007
1076
|
];
|
|
1008
|
-
|
|
1077
|
+
// Signed in, not merely holding a credential: a Datamesh token pasted into the
|
|
1078
|
+
// settings would reach the backend, but the tab stays out of the way until someone
|
|
1079
|
+
// has signed in to Oceanum.io.
|
|
1080
|
+
if (access.signedIn) {
|
|
1009
1081
|
tabs.push({
|
|
1010
1082
|
id: 'ai',
|
|
1011
1083
|
label: 'Oceanum AI',
|
|
@@ -1128,6 +1200,11 @@ export class DatameshConnectWidget extends ReactWidget {
|
|
|
1128
1200
|
commands={this.props.commands}
|
|
1129
1201
|
settings={this.props}
|
|
1130
1202
|
renderDatamesh={() => this.renderDatamesh()}
|
|
1203
|
+
openStoredNotebook={this.props.openStoredNotebook}
|
|
1204
|
+
openExample={this.props.openExample}
|
|
1205
|
+
uploadNotebook={this.props.uploadNotebook}
|
|
1206
|
+
showExamples={this.props.showExamples}
|
|
1207
|
+
setShowExamples={this.props.setShowExamples}
|
|
1131
1208
|
selected={this.selectedTab}
|
|
1132
1209
|
onSelect={id => this.selectTab(id)}
|
|
1133
1210
|
/>
|