1 |
|
2 |
|
3 |
|
4 | import {
|
5 | Dialog,
|
6 | ISessionContext,
|
7 | Printing,
|
8 | showDialog
|
9 | } from '@jupyterlab/apputils';
|
10 | import { isMarkdownCellModel } from '@jupyterlab/cells';
|
11 | import { PageConfig } from '@jupyterlab/coreutils';
|
12 | import { DocumentRegistry, DocumentWidget } from '@jupyterlab/docregistry';
|
13 | import { Kernel, KernelMessage, Session } from '@jupyterlab/services';
|
14 | import { ITranslator } from '@jupyterlab/translation';
|
15 | import { Token } from '@lumino/coreutils';
|
16 | import { INotebookModel } from './model';
|
17 | import { Notebook, StaticNotebook } from './widget';
|
18 | import { Message } from '@lumino/messaging';
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | const NOTEBOOK_PANEL_CLASS = 'jp-NotebookPanel';
|
24 |
|
25 | const NOTEBOOK_PANEL_TOOLBAR_CLASS = 'jp-NotebookPanel-toolbar';
|
26 |
|
27 | const NOTEBOOK_PANEL_NOTEBOOK_CLASS = 'jp-NotebookPanel-notebook';
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | export class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
|
37 | |
38 |
|
39 |
|
40 | constructor(options: DocumentWidget.IOptions<Notebook, INotebookModel>) {
|
41 | super(options);
|
42 |
|
43 |
|
44 | this.addClass(NOTEBOOK_PANEL_CLASS);
|
45 | this.toolbar.addClass(NOTEBOOK_PANEL_TOOLBAR_CLASS);
|
46 | this.content.addClass(NOTEBOOK_PANEL_NOTEBOOK_CLASS);
|
47 |
|
48 |
|
49 | this.content.model = this.context.model;
|
50 | this.context.sessionContext.kernelChanged.connect(
|
51 | this._onKernelChanged,
|
52 | this
|
53 | );
|
54 | this.context.sessionContext.statusChanged.connect(
|
55 | this._onSessionStatusChanged,
|
56 | this
|
57 | );
|
58 |
|
59 | this.context.saveState.connect(this._onSave, this);
|
60 | void this.revealed.then(() => {
|
61 | if (this.isDisposed) {
|
62 |
|
63 | return;
|
64 | }
|
65 |
|
66 |
|
67 | if (this.content.widgets.length === 1) {
|
68 | const cellModel = this.content.widgets[0].model;
|
69 | if (
|
70 | cellModel.type === 'code' &&
|
71 | cellModel.sharedModel.getSource() === ''
|
72 | ) {
|
73 | this.content.mode = 'edit';
|
74 | }
|
75 | }
|
76 | });
|
77 | }
|
78 |
|
79 | |
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | private _onSave(
|
86 | sender: DocumentRegistry.Context,
|
87 | state: DocumentRegistry.SaveState
|
88 | ): void {
|
89 | if (state === 'started' && this.model) {
|
90 |
|
91 | for (const cell of this.model.cells) {
|
92 | if (isMarkdownCellModel(cell)) {
|
93 | for (const key of cell.attachments.keys) {
|
94 | if (!cell.sharedModel.getSource().includes(key)) {
|
95 | cell.attachments.remove(key);
|
96 | }
|
97 | }
|
98 | }
|
99 | }
|
100 | }
|
101 | }
|
102 |
|
103 | |
104 |
|
105 |
|
106 | get sessionContext(): ISessionContext {
|
107 | return this.context.sessionContext;
|
108 | }
|
109 |
|
110 | |
111 |
|
112 |
|
113 | get model(): INotebookModel | null {
|
114 | return this.content.model;
|
115 | }
|
116 |
|
117 | |
118 |
|
119 |
|
120 |
|
121 |
|
122 | setConfig(config: NotebookPanel.IConfig): void {
|
123 | this.content.editorConfig = config.editorConfig;
|
124 | this.content.notebookConfig = config.notebookConfig;
|
125 |
|
126 | const kernelPreference = this.context.sessionContext.kernelPreference;
|
127 | this.context.sessionContext.kernelPreference = {
|
128 | ...kernelPreference,
|
129 | shutdownOnDispose: config.kernelShutdown,
|
130 | autoStartDefault: config.autoStartDefault
|
131 | };
|
132 | }
|
133 |
|
134 | |
135 |
|
136 |
|
137 | setFragment(fragment: string): void {
|
138 | void this.context.ready.then(() => {
|
139 | void this.content.setFragment(fragment);
|
140 | });
|
141 | }
|
142 |
|
143 | |
144 |
|
145 |
|
146 | dispose(): void {
|
147 | this.content.dispose();
|
148 | super.dispose();
|
149 | }
|
150 |
|
151 | |
152 |
|
153 |
|
154 | [Printing.symbol]() {
|
155 | return async (): Promise<void> => {
|
156 |
|
157 | if (this.context.model.dirty && !this.context.model.readOnly) {
|
158 | await this.context.save();
|
159 | }
|
160 |
|
161 | await Printing.printURL(
|
162 | PageConfig.getNBConvertURL({
|
163 | format: 'html',
|
164 | download: false,
|
165 | path: this.context.path
|
166 | })
|
167 | );
|
168 | };
|
169 | }
|
170 |
|
171 | |
172 |
|
173 |
|
174 | protected onBeforeHide(msg: Message): void {
|
175 | super.onBeforeHide(msg);
|
176 |
|
177 | this.content.isParentHidden = true;
|
178 | }
|
179 |
|
180 | |
181 |
|
182 |
|
183 | protected onBeforeShow(msg: Message): void {
|
184 |
|
185 |
|
186 |
|
187 | this.content.isParentHidden = false;
|
188 | super.onBeforeShow(msg);
|
189 | }
|
190 |
|
191 | |
192 |
|
193 |
|
194 | private _onKernelChanged(
|
195 | sender: any,
|
196 | args: Session.ISessionConnection.IKernelChangedArgs
|
197 | ): void {
|
198 | if (!this.model || !args.newValue) {
|
199 | return;
|
200 | }
|
201 | const { newValue } = args;
|
202 | void newValue.info.then(info => {
|
203 | if (
|
204 | this.model &&
|
205 | this.context.sessionContext.session?.kernel === newValue
|
206 | ) {
|
207 | this._updateLanguage(info.language_info);
|
208 | }
|
209 | });
|
210 | void this._updateSpec(newValue);
|
211 | }
|
212 |
|
213 | private _onSessionStatusChanged(
|
214 | sender: ISessionContext,
|
215 | status: Kernel.Status
|
216 | ) {
|
217 |
|
218 |
|
219 | if (status === 'autorestarting' && !this._autorestarting) {
|
220 |
|
221 |
|
222 | void showDialog({
|
223 | title: this._trans.__('Kernel Restarting'),
|
224 | body: this._trans.__(
|
225 | 'The kernel for %1 appears to have died. It will restart automatically.',
|
226 | this.sessionContext.session?.path
|
227 | ),
|
228 | buttons: [Dialog.okButton({ label: this._trans.__('Ok') })]
|
229 | });
|
230 | this._autorestarting = true;
|
231 | } else if (status === 'restarting') {
|
232 |
|
233 |
|
234 |
|
235 |
|
236 | } else {
|
237 | this._autorestarting = false;
|
238 | }
|
239 | }
|
240 |
|
241 | |
242 |
|
243 |
|
244 | private _updateLanguage(language: KernelMessage.ILanguageInfo): void {
|
245 | this.model!.setMetadata('language_info', language);
|
246 | }
|
247 |
|
248 | |
249 |
|
250 |
|
251 | private async _updateSpec(kernel: Kernel.IKernelConnection): Promise<void> {
|
252 | const spec = await kernel.spec;
|
253 | if (this.isDisposed) {
|
254 | return;
|
255 | }
|
256 | this.model!.setMetadata('kernelspec', {
|
257 | name: kernel.name,
|
258 | display_name: spec?.display_name,
|
259 | language: spec?.language
|
260 | });
|
261 | }
|
262 |
|
263 | |
264 |
|
265 |
|
266 |
|
267 | private _autorestarting = false;
|
268 | translator: ITranslator;
|
269 | }
|
270 |
|
271 |
|
272 |
|
273 |
|
274 | export namespace NotebookPanel {
|
275 | |
276 |
|
277 |
|
278 | export interface IConfig {
|
279 | |
280 |
|
281 |
|
282 | autoStartDefault: boolean;
|
283 | |
284 |
|
285 |
|
286 | editorConfig: StaticNotebook.IEditorConfig;
|
287 | |
288 |
|
289 |
|
290 | notebookConfig: StaticNotebook.INotebookConfig;
|
291 | |
292 |
|
293 |
|
294 | kernelShutdown: boolean;
|
295 | }
|
296 |
|
297 | |
298 |
|
299 |
|
300 | export interface IContentFactory extends Notebook.IContentFactory {
|
301 | |
302 |
|
303 |
|
304 | createNotebook(options: Notebook.IOptions): Notebook;
|
305 | }
|
306 |
|
307 | |
308 |
|
309 |
|
310 | export class ContentFactory
|
311 | extends Notebook.ContentFactory
|
312 | implements IContentFactory
|
313 | {
|
314 | |
315 |
|
316 |
|
317 | createNotebook(options: Notebook.IOptions): Notebook {
|
318 | return new Notebook(options);
|
319 | }
|
320 | }
|
321 |
|
322 | |
323 |
|
324 |
|
325 | export const IContentFactory = new Token<IContentFactory>(
|
326 | '@jupyterlab/notebook:IContentFactory',
|
327 | `A factory object that creates new notebooks.
|
328 | Use this if you want to create and host notebooks in your own UI elements.`
|
329 | );
|
330 | }
|