@eclipse-lyra/extension-notebook 0.7.57 → 0.7.59

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.
@@ -1,1162 +0,0 @@
1
- import { html, css } from "lit";
2
- import { property, state, customElement } from "lit/decorators.js";
3
- import { ref, createRef } from "lit/directives/ref.js";
4
- import { repeat } from "lit/directives/repeat.js";
5
- import { styleMap } from "lit/directives/style-map.js";
6
- import { unsafeHTML } from "lit/directives/unsafe-html.js";
7
- import { marked } from "marked";
8
- import { LyraPart, unsubscribe, subscribe, TOPIC_CONTRIBUTEIONS_CHANGED, contributionRegistry, createLogger } from "@eclipse-lyra/core";
9
- import { T as TARGET_NOTEBOOK_KERNELS } from "./notebook-kernel-api--RBL5TaE.js";
10
- var __defProp = Object.defineProperty;
11
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
- var __decorateClass = (decorators, target, key, kind) => {
13
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
14
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
15
- if (decorator = decorators[i])
16
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
17
- if (kind && result) __defProp(target, key, result);
18
- return result;
19
- };
20
- const logger = createLogger("NotebookRuntime");
21
- let LyraNotebookEditor = class extends LyraPart {
22
- constructor() {
23
- super(...arguments);
24
- this.cellOutputs = /* @__PURE__ */ new Map();
25
- this.executingCells = /* @__PURE__ */ new Set();
26
- this.availableKernels = [];
27
- this.selectedKernelId = null;
28
- this.kernel = null;
29
- this.kernelConnected = false;
30
- this.kernelConnecting = false;
31
- this.kernelVersion = void 0;
32
- this.editingMarkdownCells = /* @__PURE__ */ new Set();
33
- this.executionCounter = 0;
34
- this.isRunningAll = false;
35
- this.highlightedCellIndex = -1;
36
- this.focusedCellIndex = -1;
37
- this.cancelRunAll = false;
38
- this.cellWidgetRefs = /* @__PURE__ */ new Map();
39
- this.cellHeights = /* @__PURE__ */ new Map();
40
- }
41
- doClose() {
42
- this.input = void 0;
43
- this.notebook = void 0;
44
- this.cellOutputs.clear();
45
- this.executingCells.clear();
46
- this.cellWidgetRefs.clear();
47
- this.cellHeights.clear();
48
- this.focusedCellIndex = -1;
49
- if (this.unsubscribeContributionsToken) {
50
- unsubscribe(this.unsubscribeContributionsToken);
51
- this.unsubscribeContributionsToken = void 0;
52
- }
53
- if (this.kernel?.close) {
54
- void Promise.resolve(this.kernel.close());
55
- }
56
- this.kernel = null;
57
- this.kernelConnected = false;
58
- this.kernelVersion = void 0;
59
- }
60
- async save() {
61
- if (!this.notebook || !this.input) return;
62
- try {
63
- this.saveEditorContents();
64
- this.notebook.cells.forEach((cell, index) => {
65
- if (cell.cell_type === "code") {
66
- const output = this.cellOutputs.get(index);
67
- cell.outputs = output ? this.convertOutputToJupyter(output, cell.execution_count) : [];
68
- }
69
- });
70
- const notebookJson = JSON.stringify(this.notebook, null, 2);
71
- const file = this.input.data;
72
- await file.saveContents(notebookJson);
73
- this.markDirty(false);
74
- } catch (error) {
75
- console.error("Failed to save notebook:", error);
76
- throw error;
77
- }
78
- }
79
- doBeforeUI() {
80
- this.loadNotebook();
81
- }
82
- async onKernelDropdownSelect(e) {
83
- const value = e.detail?.item?.value ?? "";
84
- const nextId = value || null;
85
- if (this.selectedKernelId === nextId) return;
86
- if (this.kernel?.close) void Promise.resolve(this.kernel.close());
87
- this.kernel = null;
88
- this.kernelConnected = false;
89
- this.kernelVersion = void 0;
90
- this.selectedKernelId = nextId;
91
- if (this.notebook) {
92
- if (!this.notebook.metadata) this.notebook.metadata = {};
93
- this.notebook.metadata.kernel = nextId ?? void 0;
94
- }
95
- this.cellOutputs.clear();
96
- this.executionCounter = 0;
97
- this.notebook?.cells?.forEach((cell) => {
98
- if (cell.cell_type === "code") {
99
- cell.execution_count = null;
100
- cell.outputs = [];
101
- }
102
- });
103
- this.resetCellState();
104
- await this.ensureKernelLoaded();
105
- this.requestUpdate();
106
- }
107
- renderToolbar() {
108
- const kernels = this.availableKernels;
109
- kernels.length > 0;
110
- const currentLabel = this.selectedKernelId ? kernels.find((c) => c.id === this.selectedKernelId)?.label ?? this.selectedKernelId : "Select kernel";
111
- const connectionTitle = this.kernelConnecting ? "Connecting..." : this.kernelConnected ? "Kernel connected" : "Kernel disconnected";
112
- const connectionText = this.kernelConnecting ? "Connecting..." : this.kernelConnected ? this.kernelVersion ?? "Connected" : "Not connected";
113
- const iconColor = this.kernelConnected ? "var(--wa-color-green-40)" : this.kernelConnecting ? "var(--wa-color-warning-500)" : "var(--wa-color-red-40)";
114
- const runAllButton = this.isRunningAll ? html`
115
- <wa-button size="small" appearance="plain" @click=${() => this.cancelAllCells()} title="Cancel running all cells">
116
- <wa-icon name="stop" label="Stop"></wa-icon>
117
- Cancel All
118
- </wa-button>
119
- ` : html`
120
- <wa-button size="small" appearance="plain" @click=${() => this.runAllCells()} title="Run all code cells sequentially">
121
- <wa-icon name="play" label="Run"></wa-icon>
122
- Run All
123
- </wa-button>
124
- `;
125
- return html`
126
- <wa-dropdown
127
- class="kernel-select"
128
- placement="bottom-start"
129
- distance="4"
130
- size="small"
131
- @wa-select=${(e) => void this.onKernelDropdownSelect(e)}
132
- >
133
- <wa-button
134
- slot="trigger"
135
- appearance="plain"
136
- size="small"
137
- with-caret
138
- title="Notebook kernel"
139
- >
140
- ${currentLabel}
141
- </wa-button>
142
- ${kernels.map(
143
- (c) => html`
144
- <wa-dropdown-item
145
- value=${c.id}
146
- type="checkbox"
147
- ?checked=${c.id === this.selectedKernelId}
148
- >
149
- ${c.label}
150
- </wa-dropdown-item>
151
- `
152
- )}
153
- </wa-dropdown>
154
- ${runAllButton}
155
- <wa-button
156
- size="small"
157
- appearance="plain"
158
- @click=${() => this.clearAllOutputs()}
159
- title="Clear all outputs and reset execution counter"
160
- >
161
- <wa-icon name="eraser" label="Clear"></wa-icon>
162
- Clear Outputs
163
- </wa-button>
164
- ${this.kernel?.restart ? html`
165
- <wa-button
166
- size="small"
167
- appearance="plain"
168
- @click=${() => void this.restartKernel()}
169
- title="Restart kernel"
170
- ?disabled=${!this.kernelConnected || this.kernelConnecting}
171
- >
172
- <wa-icon name="arrows-rotate" label="Restart"></wa-icon>
173
- Restart Kernel
174
- </wa-button>
175
- ` : ""}
176
- ${this.kernel?.openPackageManager ? html`
177
- <wa-button
178
- size="small"
179
- appearance="plain"
180
- @click=${() => this.openPackageManager()}
181
- title="Manage packages"
182
- >
183
- <wa-icon name="box" label="Packages"></wa-icon>
184
- Packages
185
- </wa-button>
186
- ` : ""}
187
- ${this.kernel ? this.kernel.connect ? html`
188
- <wa-button
189
- appearance="plain"
190
- size="small"
191
- style="display: flex; align-items: center; gap: 0.5rem;"
192
- ?disabled=${this.kernelConnecting}
193
- @click=${() => void this.connectKernel()}
194
- title=${connectionTitle}
195
- >
196
- <wa-icon name="circle" label="Kernel status" style=${styleMap({ color: iconColor })}></wa-icon>
197
- ${connectionText}
198
- </wa-button>
199
- ` : html`
200
- <span style="display: flex; align-items: center; gap: 0.5rem;" title=${connectionTitle}>
201
- <wa-icon name="circle" label="Kernel status" style=${styleMap({ color: iconColor })}></wa-icon>
202
- ${connectionText}
203
- </span>
204
- ` : ""}
205
- `;
206
- }
207
- async connectKernel() {
208
- if (this.kernelConnecting || !this.kernel?.connect) return;
209
- try {
210
- this.kernelConnecting = true;
211
- this.requestUpdate();
212
- await this.kernel.connect({
213
- requiredPackages: this.notebook?.metadata?.required_packages
214
- });
215
- this.kernelConnected = true;
216
- if (this.kernel.getVersion) this.kernelVersion = await this.kernel.getVersion();
217
- } catch (err) {
218
- logger.error("Failed to connect kernel", err);
219
- } finally {
220
- this.kernelConnecting = false;
221
- this.requestUpdate();
222
- }
223
- }
224
- async doInitUI() {
225
- this.unsubscribeContributionsToken = subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
226
- if (event?.target === TARGET_NOTEBOOK_KERNELS) {
227
- void this.refreshKernels();
228
- }
229
- });
230
- await this.refreshKernels();
231
- }
232
- resolveDefaultKernelId(contributions) {
233
- if (!contributions.length) return null;
234
- const fromMeta = this.notebook?.metadata?.kernel;
235
- if (fromMeta && contributions.some((c) => c.id === fromMeta)) return fromMeta;
236
- const python = contributions.find((c) => c.id === "python");
237
- if (python) return python.id;
238
- const js = contributions.find((c) => c.id === "javascript");
239
- if (js) return js.id;
240
- return contributions[0].id;
241
- }
242
- async refreshKernels() {
243
- const contributions = contributionRegistry.getContributions(TARGET_NOTEBOOK_KERNELS);
244
- this.availableKernels = contributions;
245
- if (!this.selectedKernelId && contributions.length) {
246
- this.selectedKernelId = this.resolveDefaultKernelId(contributions);
247
- if (this.notebook && this.selectedKernelId) {
248
- if (!this.notebook.metadata) this.notebook.metadata = {};
249
- this.notebook.metadata.kernel = this.selectedKernelId;
250
- }
251
- }
252
- if (this.selectedKernelId && !contributions.some((c) => c.id === this.selectedKernelId)) {
253
- this.selectedKernelId = contributions.length ? contributions[0].id : null;
254
- }
255
- this.requestUpdate();
256
- await this.ensureKernelLoaded();
257
- }
258
- async ensureKernelLoaded() {
259
- const id = this.selectedKernelId;
260
- if (!id || this.kernel?.id === id) return;
261
- if (this.kernel?.close) void Promise.resolve(this.kernel.close());
262
- this.kernel = null;
263
- this.kernelConnected = false;
264
- this.kernelVersion = void 0;
265
- const contribution = this.availableKernels.find((c) => c.id === id);
266
- if (!contribution) return;
267
- try {
268
- this.kernelConnecting = true;
269
- this.requestUpdate();
270
- const k = await contribution.loadKernel();
271
- if (this.selectedKernelId !== id) return;
272
- this.kernel = k;
273
- if (k.connect) {
274
- await k.connect({
275
- requiredPackages: this.notebook?.metadata?.required_packages
276
- });
277
- }
278
- this.kernelConnected = true;
279
- if (k.getVersion) {
280
- this.kernelVersion = await k.getVersion();
281
- }
282
- } catch (err) {
283
- logger.error("Failed to load kernel", id, err);
284
- } finally {
285
- this.kernelConnecting = false;
286
- this.requestUpdate();
287
- }
288
- }
289
- async loadNotebook() {
290
- const file = this.input.data;
291
- const contents = await file.getContents();
292
- try {
293
- this.notebook = JSON.parse(contents);
294
- } catch (e) {
295
- console.error("Failed to parse notebook:", e);
296
- this.notebook = {
297
- cells: [{
298
- cell_type: "markdown",
299
- source: ["# Error\nFailed to parse notebook file."]
300
- }]
301
- };
302
- }
303
- if (this.notebook?.metadata?.kernel) {
304
- this.selectedKernelId = this.notebook.metadata.kernel;
305
- }
306
- if (this.notebook?.cells) {
307
- const maxCount = this.notebook.cells.filter((cell) => cell.cell_type === "code").map((cell) => cell.execution_count ?? 0).reduce((max, count) => Math.max(max, count), 0);
308
- this.executionCounter = maxCount;
309
- this.notebook.cells.forEach((cell, index) => {
310
- if (cell.cell_type === "code" && cell.outputs && cell.outputs.length > 0) {
311
- const output = this.convertOutputFromJupyter(cell.outputs[0]);
312
- if (output) {
313
- this.cellOutputs.set(index, output);
314
- }
315
- }
316
- });
317
- }
318
- void this.refreshKernels();
319
- }
320
- getCellSource(cell) {
321
- return Array.isArray(cell.source) ? cell.source.join("") : cell.source;
322
- }
323
- convertOutputToJupyter(output, executionCount) {
324
- if (output.type === "execute_result") {
325
- const data = {};
326
- if (output.imageData) data["image/png"] = output.imageData;
327
- if (output.data) data["text/plain"] = output.data;
328
- return [{
329
- output_type: "execute_result",
330
- data,
331
- execution_count: executionCount,
332
- metadata: {}
333
- }];
334
- } else if (output.type === "error") {
335
- return [{
336
- output_type: "error",
337
- ename: "Error",
338
- evalue: output.data,
339
- traceback: [output.data]
340
- }];
341
- }
342
- return [];
343
- }
344
- convertOutputFromJupyter(jupyterOutput) {
345
- if (jupyterOutput.output_type === "execute_result" && jupyterOutput.data) {
346
- return {
347
- type: "execute_result",
348
- data: jupyterOutput.data["text/plain"] || "",
349
- imageData: jupyterOutput.data["image/png"] || void 0
350
- };
351
- } else if (jupyterOutput.output_type === "error") {
352
- return {
353
- type: "error",
354
- data: jupyterOutput.evalue || jupyterOutput.traceback?.join("\n") || "Unknown error"
355
- };
356
- }
357
- return null;
358
- }
359
- renderHeaderActions(index, additionalButton) {
360
- return html`
361
- <div class="cell-header-actions">
362
- ${additionalButton || ""}
363
- ${additionalButton ? html`<span class="divider"></span>` : ""}
364
- <wa-button size="small" appearance="plain" @click=${() => this.addCell(index, "code")} title="Add code cell before">
365
- <wa-icon name="plus"></wa-icon>
366
- <wa-icon name="code" label="Code"></wa-icon>
367
- </wa-button>
368
- <wa-button size="small" appearance="plain" @click=${() => this.addCell(index, "markdown")} title="Add markdown cell before">
369
- <wa-icon name="plus"></wa-icon>
370
- <wa-icon name="font" label="Markdown"></wa-icon>
371
- </wa-button>
372
- <span class="divider"></span>
373
- <wa-button size="small" appearance="plain" @click=${() => this.deleteCell(index)} title="Delete cell" ?disabled=${this.notebook.cells.length <= 1}>
374
- <wa-icon name="trash" label="Delete cell"></wa-icon>
375
- </wa-button>
376
- </div>
377
- `;
378
- }
379
- renderFooterActions(index) {
380
- return html`
381
- <div class="cell-footer">
382
- <wa-button size="small" appearance="plain" @click=${() => this.addCell(index + 1, "code")} title="Add code cell after">
383
- <wa-icon name="code" label="Code"></wa-icon>
384
- <wa-icon name="plus"></wa-icon>
385
- </wa-button>
386
- <wa-button size="small" appearance="plain" @click=${() => this.addCell(index + 1, "markdown")} title="Add markdown cell after">
387
- <wa-icon name="font" label="Markdown"></wa-icon>
388
- <wa-icon name="plus"></wa-icon>
389
- </wa-button>
390
- </div>
391
- `;
392
- }
393
- // Helper to convert string to Jupyter source format
394
- stringToSourceArray(content) {
395
- if (!content) return [""];
396
- const lines = content.split("\n").map((line) => line + "\n");
397
- if (lines.length > 0) {
398
- lines[lines.length - 1] = lines[lines.length - 1].replace(/\n$/, "");
399
- }
400
- return lines;
401
- }
402
- // Helper to create a new cell
403
- createCell(cellType) {
404
- const newCell = {
405
- cell_type: cellType,
406
- source: [""],
407
- metadata: {}
408
- };
409
- if (cellType === "code") {
410
- newCell.execution_count = null;
411
- newCell.outputs = [];
412
- }
413
- return newCell;
414
- }
415
- async executeCell(cellIndex) {
416
- const cell = this.notebook.cells[cellIndex];
417
- if (cell.cell_type !== "code") return;
418
- this.executingCells.add(cellIndex);
419
- this.requestUpdate();
420
- try {
421
- await this.ensureKernelLoaded();
422
- const k = this.kernel;
423
- if (!k) {
424
- if (this.executingCells.has(cellIndex)) {
425
- this.cellOutputs.set(cellIndex, { type: "error", data: "No kernel selected" });
426
- }
427
- return;
428
- }
429
- const widget = this.getCellWidgetRef(cellIndex).value;
430
- const code = widget ? widget.getContent() : this.getCellSource(cell);
431
- if (code === null || code === void 0) return;
432
- const result = await k.execute(code);
433
- if (result.error) {
434
- this.cellOutputs.set(cellIndex, { type: "error", data: result.error });
435
- } else {
436
- this.cellOutputs.set(cellIndex, {
437
- type: "execute_result",
438
- data: result.data,
439
- imageData: result.imageData
440
- });
441
- }
442
- this.executionCounter++;
443
- cell.execution_count = this.executionCounter;
444
- this.markDirty(true);
445
- } catch (error) {
446
- if (this.executingCells.has(cellIndex)) {
447
- this.cellOutputs.set(cellIndex, {
448
- type: "error",
449
- data: error instanceof Error ? error.message : String(error)
450
- });
451
- }
452
- } finally {
453
- this.executingCells.delete(cellIndex);
454
- this.requestUpdate();
455
- }
456
- }
457
- cancelExecution(cellIndex) {
458
- if (this.kernel?.interrupt) {
459
- this.kernel.interrupt();
460
- } else {
461
- this.cellOutputs.set(cellIndex, {
462
- type: "error",
463
- data: "Cancellation not supported for this kernel"
464
- });
465
- this.executingCells.delete(cellIndex);
466
- this.requestUpdate();
467
- }
468
- }
469
- clearAllOutputs() {
470
- this.cellOutputs.clear();
471
- this.executionCounter = 0;
472
- if (this.notebook?.cells) {
473
- this.notebook.cells.forEach((cell) => {
474
- if (cell.cell_type === "code") {
475
- cell.execution_count = null;
476
- cell.outputs = [];
477
- }
478
- });
479
- }
480
- this.markDirty(true);
481
- this.requestUpdate();
482
- }
483
- async restartKernel() {
484
- if (!this.kernel?.restart || this.kernelConnecting) return;
485
- try {
486
- this.kernelConnecting = true;
487
- this.requestUpdate();
488
- await this.kernel.restart();
489
- this.kernelConnected = true;
490
- if (this.kernel.getVersion) this.kernelVersion = await this.kernel.getVersion();
491
- } catch (error) {
492
- logger.error("Failed to restart kernel", error);
493
- } finally {
494
- this.kernelConnecting = false;
495
- this.requestUpdate();
496
- }
497
- }
498
- async runAllCells() {
499
- if (!this.notebook?.cells) return;
500
- this.isRunningAll = true;
501
- this.cancelRunAll = false;
502
- this.requestUpdate();
503
- try {
504
- for (let i = 0; i < this.notebook.cells.length; i++) {
505
- if (this.cancelRunAll) {
506
- break;
507
- }
508
- const cell = this.notebook.cells[i];
509
- if (cell.cell_type === "code") {
510
- await this.executeCell(i);
511
- }
512
- }
513
- } finally {
514
- this.isRunningAll = false;
515
- this.cancelRunAll = false;
516
- this.requestUpdate();
517
- }
518
- }
519
- cancelAllCells() {
520
- this.cancelRunAll = true;
521
- this.kernel?.interrupt?.();
522
- }
523
- toggleMarkdownEdit(index) {
524
- if (this.editingMarkdownCells.has(index)) {
525
- this.editingMarkdownCells.delete(index);
526
- } else {
527
- this.editingMarkdownCells.add(index);
528
- }
529
- this.requestUpdate();
530
- }
531
- saveMarkdownEdit(index, event) {
532
- const textarea = event.target;
533
- const newContent = textarea.value;
534
- if (this.notebook && this.notebook.cells[index]) {
535
- const cell = this.notebook.cells[index];
536
- const oldContent = this.getCellSource(cell);
537
- cell.source = this.stringToSourceArray(newContent);
538
- if (oldContent !== newContent) {
539
- this.markDirty(true);
540
- }
541
- }
542
- this.editingMarkdownCells.delete(index);
543
- this.requestUpdate();
544
- }
545
- renderMarkdownCell(cell, index) {
546
- const source = this.getCellSource(cell);
547
- const isEmpty = !source || source.trim() === "";
548
- const isEditing = this.editingMarkdownCells.has(index);
549
- if (isEditing) {
550
- const editButtons = html`
551
- <wa-button
552
- size="small"
553
- appearance="plain"
554
- @click=${(e) => {
555
- const textarea = e.target.closest(".markdown-cell")?.querySelector("textarea");
556
- if (textarea) {
557
- this.saveMarkdownEdit(index, { target: textarea });
558
- }
559
- }}
560
- title="Save changes">
561
- <wa-icon name="check" label="Save"></wa-icon>
562
- </wa-button>
563
- <wa-button
564
- size="small"
565
- appearance="plain"
566
- @click=${() => this.toggleMarkdownEdit(index)}
567
- title="Cancel editing">
568
- <wa-icon name="xmark" label="Cancel"></wa-icon>
569
- </wa-button>
570
- `;
571
- return html`
572
- <div class="cell-wrapper">
573
- <wa-animation
574
- name="bounce"
575
- duration="1000"
576
- iterations="1"
577
- ?play=${this.highlightedCellIndex === index}
578
- @wa-finish=${() => this.highlightedCellIndex = -1}>
579
- <div class="cell markdown-cell editing">
580
- <div class="cell-header">
581
- ${this.renderHeaderActions(index, editButtons)}
582
- <span class="cell-label">Markdown</span>
583
- </div>
584
- <textarea
585
- class="markdown-editor"
586
- .value=${source}
587
- @blur=${(e) => this.saveMarkdownEdit(index, e)}
588
- placeholder="Enter markdown content here... (# for headings, ** for bold, etc.)"></textarea>
589
- ${this.renderFooterActions(index)}
590
- </div>
591
- </wa-animation>
592
- </div>
593
- `;
594
- }
595
- const rendered = marked.parse(source);
596
- const editButton = html`
597
- <wa-button
598
- size="small"
599
- appearance="plain"
600
- @click=${() => this.toggleMarkdownEdit(index)}
601
- title="Edit markdown">
602
- <wa-icon name="pencil" label="Edit"></wa-icon>
603
- </wa-button>
604
- `;
605
- return html`
606
- <div class="cell-wrapper">
607
- <wa-animation
608
- name="bounce"
609
- duration="1000"
610
- iterations="1"
611
- ?play=${this.highlightedCellIndex === index}
612
- @wa-finish=${() => this.highlightedCellIndex = -1}>
613
- <div class="cell markdown-cell ${isEmpty ? "empty" : ""}" @dblclick=${() => this.toggleMarkdownEdit(index)}>
614
- <div class="cell-header">
615
- ${this.renderHeaderActions(index, editButton)}
616
- <span class="cell-label"></span>
617
- </div>
618
- <div class="cell-content">
619
- ${isEmpty ? html`
620
- <div class="markdown-placeholder">
621
- <wa-icon name="font" label="Markdown"></wa-icon>
622
- <span>Double-click or click the pencil icon to edit markdown</span>
623
- </div>
624
- ` : unsafeHTML(rendered)}
625
- </div>
626
- ${this.renderFooterActions(index)}
627
- </div>
628
- </wa-animation>
629
- </div>
630
- `;
631
- }
632
- renderCodeCell(cell, index) {
633
- const output = this.cellOutputs.get(index);
634
- const isExecuting = this.executingCells.has(index);
635
- const source = this.getCellSource(cell);
636
- const language = this.kernel?.language ?? "javascript";
637
- const notebookPath = this.input?.data?.getWorkspacePath?.() ?? "notebook";
638
- const cellUri = `${notebookPath}-cell-${index}`;
639
- const cellHeight = this.cellHeights.get(index) ?? Math.max(100, source.split("\n").length * 19 + 10);
640
- const runButton = isExecuting ? html`
641
- <wa-button
642
- size="small"
643
- appearance="plain"
644
- @click=${() => this.cancelExecution(index)}
645
- title="Stop execution">
646
- <wa-icon name="stop" label="Stop" style="color: var(--wa-color-danger-500);"></wa-icon>
647
- </wa-button>
648
- ` : html`
649
- <lyra-command
650
- cmd="notebook.runCell"
651
- icon="play"
652
- title="Run cell"
653
- size="small"
654
- appearance="plain"
655
- .params=${{ cellIndex: index }}>
656
- </lyra-command>
657
- `;
658
- return html`
659
- <div class="cell-wrapper">
660
- <wa-animation
661
- name="bounce"
662
- duration="1000"
663
- iterations="1"
664
- ?play=${this.highlightedCellIndex === index}
665
- @wa-finish=${() => this.highlightedCellIndex = -1}>
666
- <div class="cell code-cell ${isExecuting ? "executing" : ""}">
667
- <div class="cell-header">
668
- <span class="cell-label">
669
- ${isExecuting ? html`
670
- In [<wa-animation name="pulse" duration="1000" iterations="Infinity" ?play=${isExecuting}>
671
- <span class="executing-indicator">*</span>
672
- </wa-animation>]
673
- ` : html`
674
- In [${cell.execution_count ?? " "}]
675
- `}
676
- </span>
677
- ${this.renderHeaderActions(index, runButton)}
678
- </div>
679
- <div
680
- class="cell-input monaco-container"
681
- style=${styleMap({ height: `${cellHeight}px` })}
682
- @wheel=${(e) => this.onCellWheel(index, e)}
683
- >
684
- <lyra-monaco-widget
685
- .value=${source}
686
- .language=${language}
687
- .uri=${cellUri}
688
- ?autoLayout=${true}
689
- @content-change=${() => this.markDirty(true)}
690
- @editor-focus=${() => {
691
- this.focusedCellIndex = index;
692
- }}
693
- @editor-blur=${() => {
694
- if (this.focusedCellIndex === index) this.focusedCellIndex = -1;
695
- }}
696
- @content-height-changed=${(e) => this.onCellHeightChange(index, e.detail.height)}
697
- ${ref(this.getCellWidgetRef(index))}
698
- ></lyra-monaco-widget>
699
- </div>
700
- ${output ? html`
701
- <div class="cell-output ${output.type === "error" ? "output-error" : ""}">
702
- <div class="output-label">Out [${index + 1}]:</div>
703
- ${output.imageData ? html`
704
- <img src="data:image/png;base64,${output.imageData}" alt="Output image" class="output-image" />
705
- ` : ""}
706
- ${output.data ? html`<pre><code>${output.data}</code></pre>` : ""}
707
- </div>
708
- ` : ""}
709
- ${this.renderFooterActions(index)}
710
- </div>
711
- </wa-animation>
712
- </div>
713
- `;
714
- }
715
- renderCell(cell, index) {
716
- if (cell.cell_type === "markdown") {
717
- return this.renderMarkdownCell(cell, index);
718
- } else if (cell.cell_type === "code") {
719
- return this.renderCodeCell(cell, index);
720
- } else {
721
- const source = this.getCellSource(cell);
722
- return html`
723
- <div class="cell raw-cell">
724
- <pre><code>${source}</code></pre>
725
- </div>
726
- `;
727
- }
728
- }
729
- addCell(index, cellType = "code") {
730
- if (!this.notebook) return;
731
- this.saveEditorContents();
732
- this.shiftIndices(index, "up");
733
- this.notebook.cells.splice(index, 0, this.createCell(cellType));
734
- if (cellType === "markdown") {
735
- this.editingMarkdownCells.add(index);
736
- }
737
- this.resetCellState();
738
- this.highlightedCellIndex = index;
739
- this.updateComplete.then(() => {
740
- this.scrollToCell(index);
741
- });
742
- }
743
- scrollToCell(index) {
744
- const cellWrapper = this.shadowRoot?.querySelectorAll(".cell-wrapper")[index];
745
- if (!cellWrapper) return;
746
- const scroller = this.closest("wa-scroller");
747
- if (!scroller) {
748
- cellWrapper.scrollIntoView({
749
- behavior: "smooth",
750
- block: "center",
751
- inline: "nearest"
752
- });
753
- return;
754
- }
755
- const scrollerRect = scroller.getBoundingClientRect();
756
- const cellRect = cellWrapper.getBoundingClientRect();
757
- const scrollTop = scroller.scrollTop;
758
- const targetScroll = scrollTop + (cellRect.top - scrollerRect.top) - scrollerRect.height / 2 + cellRect.height / 2;
759
- scroller.scrollTo({
760
- top: targetScroll,
761
- behavior: "smooth"
762
- });
763
- }
764
- saveEditorContents() {
765
- this.notebook?.cells.forEach((cell, index) => {
766
- if (cell.cell_type !== "code") return;
767
- const widget = this.getCellWidgetRef(index).value;
768
- const value = widget?.getContent();
769
- if (value !== void 0 && value !== null) {
770
- cell.source = this.stringToSourceArray(value);
771
- }
772
- });
773
- }
774
- resetCellState() {
775
- this.markDirty(true);
776
- }
777
- deleteCell(index) {
778
- if (!this.notebook || this.notebook.cells.length <= 1) return;
779
- this.saveEditorContents();
780
- this.cellOutputs.delete(index);
781
- this.executingCells.delete(index);
782
- this.editingMarkdownCells.delete(index);
783
- this.notebook.cells.splice(index, 1);
784
- this.shiftIndices(index, "down");
785
- this.resetCellState();
786
- }
787
- shiftIndices(startIndex, direction) {
788
- const shift = direction === "up" ? 1 : -1;
789
- const filterFn = direction === "up" ? (idx) => idx >= startIndex : (idx) => idx > startIndex;
790
- const sortFn = direction === "up" ? (a, b) => b - a : (a, b) => a - b;
791
- const shiftMap = (map) => {
792
- const indices = Array.from(map.keys()).filter(filterFn).sort(sortFn);
793
- indices.forEach((oldIndex) => {
794
- const value = map.get(oldIndex);
795
- map.delete(oldIndex);
796
- map.set(oldIndex + shift, value);
797
- });
798
- };
799
- const shiftSet = (set) => {
800
- const indices = Array.from(set).filter(filterFn).sort(sortFn);
801
- indices.forEach((oldIndex) => {
802
- set.delete(oldIndex);
803
- set.add(oldIndex + shift);
804
- });
805
- };
806
- shiftMap(this.cellOutputs);
807
- shiftSet(this.executingCells);
808
- shiftSet(this.editingMarkdownCells);
809
- shiftMap(this.cellWidgetRefs);
810
- shiftMap(this.cellHeights);
811
- }
812
- getCellWidgetRef(index) {
813
- if (!this.cellWidgetRefs.has(index)) {
814
- this.cellWidgetRefs.set(index, createRef());
815
- }
816
- return this.cellWidgetRefs.get(index);
817
- }
818
- onCellHeightChange(index, height) {
819
- const padding = 10;
820
- const minHeight = 100;
821
- const newHeight = Math.max(minHeight, height + padding);
822
- const current = this.cellHeights.get(index);
823
- if (current === newHeight) return;
824
- this.cellHeights = new Map(this.cellHeights);
825
- this.cellHeights.set(index, newHeight);
826
- this.requestUpdate();
827
- this.updateComplete.then(() => this.getCellWidgetRef(index).value?.layout?.());
828
- }
829
- onCellWheel(index, e) {
830
- const widget = this.getCellWidgetRef(index).value;
831
- const editor = widget?.getEditor();
832
- if (!editor) return;
833
- const scrollTop = editor.getScrollTop();
834
- const scrollHeight = editor.getScrollHeight();
835
- const contentHeight = editor.getContentHeight();
836
- const canScroll = scrollHeight > contentHeight;
837
- const atBoundary = e.deltaY < 0 && scrollTop <= 0 || e.deltaY > 0 && scrollTop + contentHeight >= scrollHeight;
838
- if (!canScroll || atBoundary) {
839
- e.stopImmediatePropagation();
840
- }
841
- }
842
- openPackageManager() {
843
- if (!this.kernel?.openPackageManager) return;
844
- const packages = this.notebook?.metadata?.required_packages ?? [];
845
- this.kernel.openPackageManager({
846
- requiredPackages: packages,
847
- onPackageAdded: (packageName) => {
848
- if (!this.notebook) return;
849
- if (!this.notebook.metadata) this.notebook.metadata = {};
850
- if (!this.notebook.metadata.required_packages) this.notebook.metadata.required_packages = [];
851
- if (!this.notebook.metadata.required_packages.includes(packageName)) {
852
- this.notebook.metadata.required_packages.push(packageName);
853
- this.markDirty(true);
854
- }
855
- },
856
- onPackageRemoved: (packageName) => {
857
- if (!this.notebook?.metadata?.required_packages) return;
858
- const index = this.notebook.metadata.required_packages.indexOf(packageName);
859
- if (index > -1) {
860
- this.notebook.metadata.required_packages.splice(index, 1);
861
- this.markDirty(true);
862
- }
863
- }
864
- });
865
- }
866
- updated(changedProperties) {
867
- super.updated(changedProperties);
868
- if (changedProperties.has("kernelConnected") || changedProperties.has("kernelConnecting") || changedProperties.has("kernelVersion") || changedProperties.has("isRunningAll") || changedProperties.has("availableKernels") || changedProperties.has("selectedKernelId")) ;
869
- }
870
- renderContent() {
871
- if (!this.notebook) {
872
- return html`<div class="loading">Loading notebook...</div>`;
873
- }
874
- return html`
875
- <div class="noteboocells">
876
- ${repeat(
877
- this.notebook.cells,
878
- (_cell, index) => index,
879
- (cell, index) => this.renderCell(cell, index)
880
- )}
881
- </div>
882
- `;
883
- }
884
- };
885
- LyraNotebookEditor.styles = css`
886
- :host {
887
- display: block;
888
- width: 100%;
889
- }
890
-
891
- .kernel-select {
892
- max-width: 10rem;
893
- }
894
-
895
- .noteboocells {
896
- display: flex;
897
- flex-direction: column;
898
- gap: 3rem;
899
- max-width: 1200px;
900
- margin: 0 auto;
901
- padding: 2rem 1rem;
902
- width: 100%;
903
- box-sizing: border-box;
904
- }
905
-
906
- .cell-wrapper {
907
- position: relative;
908
- }
909
-
910
- .cell {
911
- border-radius: 4px;
912
- overflow: hidden;
913
- opacity: 0.9;
914
- position: relative;
915
- }
916
-
917
- .cell-header-actions {
918
- display: flex;
919
- gap: 0.25rem;
920
- align-items: center;
921
- opacity: 0.5;
922
- transition: opacity 0.2s;
923
- }
924
-
925
- .cell-header-actions .divider {
926
- width: 1px;
927
- height: 1rem;
928
- background: var(--wa-color-outline);
929
- margin: 0 0.25rem;
930
- opacity: 0.5;
931
- }
932
-
933
- .cell-header:hover .cell-header-actions {
934
- opacity: 1;
935
- }
936
-
937
- .cell-footer {
938
- display: flex;
939
- gap: 0.5rem;
940
- align-items: center;
941
- justify-content: flex-start;
942
- padding: 0.5rem;
943
- border-top: 1px solid var(--wa-color-outline);
944
- opacity: 0.5;
945
- transition: opacity 0.2s;
946
- }
947
-
948
- .cell-footer:hover {
949
- opacity: 1;
950
- }
951
-
952
- .markdown-cell {
953
- cursor: pointer;
954
- transition: opacity 0.2s;
955
- }
956
-
957
- .markdown-cell:hover:not(.editing) {
958
- opacity: 0.9;
959
- }
960
-
961
- .markdown-cell .cell-content {
962
- padding: 1rem;
963
- }
964
-
965
- .markdown-cell.editing {
966
- cursor: default;
967
- padding: 0;
968
- }
969
-
970
- .markdown-cell.editing .cell-actions {
971
- display: none;
972
- }
973
-
974
- .markdown-editor {
975
- width: 100%;
976
- min-height: 200px;
977
- padding: 1rem;
978
- font-family: monospace;
979
- font-size: 0.95rem;
980
- line-height: 1.6;
981
- border: none;
982
- outline: none;
983
- resize: vertical;
984
- background: transparent;
985
- color: inherit;
986
- }
987
-
988
- .code-cell {
989
- border-left: 3px solid var(--wa-color-primary-500);
990
- transition: all 0.3s ease;
991
- }
992
-
993
- .code-cell.executing {
994
- border-left: 4px solid var(--wa-color-primary-500);
995
- box-shadow: 0 0 0 2px var(--wa-color-primary-500, rgba(59, 130, 246, 0.3));
996
- animation: pulse-cell 2s ease-in-out infinite;
997
- }
998
-
999
- @keyframes pulse-cell {
1000
- 0%, 100% {
1001
- box-shadow: 0 0 0 2px var(--wa-color-primary-500, rgba(59, 130, 246, 0.3));
1002
- opacity: 1;
1003
- }
1004
- 50% {
1005
- box-shadow: 0 0 0 4px var(--wa-color-primary-500, rgba(59, 130, 246, 0.5));
1006
- opacity: 0.95;
1007
- }
1008
- }
1009
-
1010
- .cell-header {
1011
- display: flex;
1012
- align-items: center;
1013
- justify-content: flex-start;
1014
- gap: 0.5rem;
1015
- padding: 0.5rem 1rem;
1016
- flex-wrap: nowrap;
1017
- }
1018
-
1019
- .cell-label {
1020
- font-family: monospace;
1021
- font-weight: bold;
1022
- flex-shrink: 0;
1023
- }
1024
-
1025
- .executing-indicator {
1026
- display: inline-block;
1027
- color: var(--wa-color-primary-500);
1028
- font-weight: bold;
1029
- font-size: 1.2em;
1030
- }
1031
-
1032
- .cell-input {
1033
- margin: 0;
1034
- }
1035
-
1036
- .monaco-container {
1037
- min-height: 100px;
1038
- height: auto;
1039
- width: 100%;
1040
- }
1041
-
1042
- .cell-output {
1043
- padding: 1rem;
1044
- }
1045
-
1046
- .output-label {
1047
- font-family: monospace;
1048
- font-weight: bold;
1049
- margin-bottom: 0.5rem;
1050
- opacity: 0.7;
1051
- }
1052
-
1053
- .cell-output pre {
1054
- margin: 0;
1055
- overflow-x: auto;
1056
- }
1057
-
1058
- .cell-output code {
1059
- font-family: 'Courier New', monospace;
1060
- font-size: 0.9rem;
1061
- line-height: 1.5;
1062
- }
1063
-
1064
- .output-image {
1065
- max-width: 100%;
1066
- height: auto;
1067
- display: block;
1068
- margin: 0.5rem 0;
1069
- border-radius: 4px;
1070
- }
1071
-
1072
- .output-error {
1073
- border-left: 3px solid var(--wa-color-danger-500);
1074
- }
1075
-
1076
- .raw-cell {
1077
- padding: 1rem;
1078
- }
1079
-
1080
- .raw-cell pre {
1081
- margin: 0;
1082
- }
1083
-
1084
- .loading {
1085
- display: flex;
1086
- align-items: center;
1087
- justify-content: center;
1088
- height: 100%;
1089
- font-size: 1.2rem;
1090
- }
1091
-
1092
- .markdown-placeholder {
1093
- display: flex;
1094
- align-items: center;
1095
- justify-content: center;
1096
- gap: 0.75rem;
1097
- padding: 3rem 1rem;
1098
- opacity: 0.5;
1099
- font-style: italic;
1100
- transition: opacity 0.2s;
1101
- }
1102
-
1103
- .markdown-cell.empty:hover .markdown-placeholder {
1104
- opacity: 0.8;
1105
- }
1106
-
1107
- .markdown-placeholder wa-icon {
1108
- font-size: 1.5rem;
1109
- }
1110
- `;
1111
- __decorateClass([
1112
- property({ attribute: false })
1113
- ], LyraNotebookEditor.prototype, "input", 2);
1114
- __decorateClass([
1115
- state()
1116
- ], LyraNotebookEditor.prototype, "notebook", 2);
1117
- __decorateClass([
1118
- state()
1119
- ], LyraNotebookEditor.prototype, "cellOutputs", 2);
1120
- __decorateClass([
1121
- state()
1122
- ], LyraNotebookEditor.prototype, "executingCells", 2);
1123
- __decorateClass([
1124
- state()
1125
- ], LyraNotebookEditor.prototype, "availableKernels", 2);
1126
- __decorateClass([
1127
- state()
1128
- ], LyraNotebookEditor.prototype, "selectedKernelId", 2);
1129
- __decorateClass([
1130
- state()
1131
- ], LyraNotebookEditor.prototype, "kernel", 2);
1132
- __decorateClass([
1133
- state()
1134
- ], LyraNotebookEditor.prototype, "kernelConnected", 2);
1135
- __decorateClass([
1136
- state()
1137
- ], LyraNotebookEditor.prototype, "kernelConnecting", 2);
1138
- __decorateClass([
1139
- state()
1140
- ], LyraNotebookEditor.prototype, "kernelVersion", 2);
1141
- __decorateClass([
1142
- state()
1143
- ], LyraNotebookEditor.prototype, "editingMarkdownCells", 2);
1144
- __decorateClass([
1145
- state()
1146
- ], LyraNotebookEditor.prototype, "executionCounter", 2);
1147
- __decorateClass([
1148
- state()
1149
- ], LyraNotebookEditor.prototype, "isRunningAll", 2);
1150
- __decorateClass([
1151
- state()
1152
- ], LyraNotebookEditor.prototype, "highlightedCellIndex", 2);
1153
- __decorateClass([
1154
- state()
1155
- ], LyraNotebookEditor.prototype, "cellHeights", 2);
1156
- LyraNotebookEditor = __decorateClass([
1157
- customElement("lyra-notebook-editor")
1158
- ], LyraNotebookEditor);
1159
- export {
1160
- LyraNotebookEditor
1161
- };
1162
- //# sourceMappingURL=notebook-runtime-DQ4uFUbN.js.map