@theia/notebook 1.53.0-next.5 → 1.53.0-next.55

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.
Files changed (88) hide show
  1. package/README.md +30 -30
  2. package/lib/browser/contributions/notebook-actions-contribution.d.ts +1 -0
  3. package/lib/browser/contributions/notebook-actions-contribution.d.ts.map +1 -1
  4. package/lib/browser/contributions/notebook-actions-contribution.js +31 -5
  5. package/lib/browser/contributions/notebook-actions-contribution.js.map +1 -1
  6. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts +2 -0
  7. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts.map +1 -1
  8. package/lib/browser/contributions/notebook-cell-actions-contribution.js +41 -7
  9. package/lib/browser/contributions/notebook-cell-actions-contribution.js.map +1 -1
  10. package/lib/browser/contributions/notebook-status-bar-contribution.d.ts +14 -0
  11. package/lib/browser/contributions/notebook-status-bar-contribution.d.ts.map +1 -0
  12. package/lib/browser/contributions/notebook-status-bar-contribution.js +75 -0
  13. package/lib/browser/contributions/notebook-status-bar-contribution.js.map +1 -0
  14. package/lib/browser/notebook-editor-widget.d.ts.map +1 -1
  15. package/lib/browser/notebook-editor-widget.js +3 -5
  16. package/lib/browser/notebook-editor-widget.js.map +1 -1
  17. package/lib/browser/notebook-frontend-module.d.ts.map +1 -1
  18. package/lib/browser/notebook-frontend-module.js +3 -0
  19. package/lib/browser/notebook-frontend-module.js.map +1 -1
  20. package/lib/browser/notebook-open-handler.d.ts +3 -2
  21. package/lib/browser/notebook-open-handler.d.ts.map +1 -1
  22. package/lib/browser/notebook-open-handler.js +12 -5
  23. package/lib/browser/notebook-open-handler.js.map +1 -1
  24. package/lib/browser/view/notebook-cell-editor.d.ts +1 -0
  25. package/lib/browser/view/notebook-cell-editor.d.ts.map +1 -1
  26. package/lib/browser/view/notebook-cell-editor.js +30 -16
  27. package/lib/browser/view/notebook-cell-editor.js.map +1 -1
  28. package/lib/browser/view/notebook-cell-list-view.d.ts +6 -4
  29. package/lib/browser/view/notebook-cell-list-view.d.ts.map +1 -1
  30. package/lib/browser/view/notebook-cell-list-view.js +20 -13
  31. package/lib/browser/view/notebook-cell-list-view.js.map +1 -1
  32. package/lib/browser/view-model/notebook-cell-model.d.ts +3 -0
  33. package/lib/browser/view-model/notebook-cell-model.d.ts.map +1 -1
  34. package/lib/browser/view-model/notebook-cell-model.js +5 -0
  35. package/lib/browser/view-model/notebook-cell-model.js.map +1 -1
  36. package/package.json +8 -8
  37. package/src/browser/contributions/cell-operations.ts +44 -44
  38. package/src/browser/contributions/notebook-actions-contribution.ts +379 -350
  39. package/src/browser/contributions/notebook-cell-actions-contribution.ts +525 -487
  40. package/src/browser/contributions/notebook-color-contribution.ts +268 -268
  41. package/src/browser/contributions/notebook-context-keys.ts +113 -113
  42. package/src/browser/contributions/notebook-label-provider-contribution.ts +85 -85
  43. package/src/browser/contributions/notebook-outline-contribution.ts +114 -114
  44. package/src/browser/contributions/notebook-output-action-contribution.ts +82 -82
  45. package/src/browser/contributions/notebook-preferences.ts +92 -92
  46. package/src/browser/contributions/notebook-status-bar-contribution.ts +77 -0
  47. package/src/browser/contributions/notebook-undo-redo-handler.ts +41 -41
  48. package/src/browser/index.ts +27 -27
  49. package/src/browser/notebook-cell-resource-resolver.ts +130 -130
  50. package/src/browser/notebook-editor-widget-factory.ts +82 -82
  51. package/src/browser/notebook-editor-widget.tsx +330 -331
  52. package/src/browser/notebook-frontend-module.ts +119 -115
  53. package/src/browser/notebook-open-handler.ts +120 -114
  54. package/src/browser/notebook-output-utils.ts +119 -119
  55. package/src/browser/notebook-renderer-registry.ts +85 -85
  56. package/src/browser/notebook-type-registry.ts +54 -54
  57. package/src/browser/notebook-types.ts +186 -186
  58. package/src/browser/renderers/cell-output-webview.ts +33 -33
  59. package/src/browser/service/notebook-clipboard-service.ts +43 -43
  60. package/src/browser/service/notebook-context-manager.ts +162 -162
  61. package/src/browser/service/notebook-editor-widget-service.ts +101 -101
  62. package/src/browser/service/notebook-execution-service.ts +139 -139
  63. package/src/browser/service/notebook-execution-state-service.ts +311 -311
  64. package/src/browser/service/notebook-kernel-history-service.ts +124 -124
  65. package/src/browser/service/notebook-kernel-quick-pick-service.ts +479 -479
  66. package/src/browser/service/notebook-kernel-service.ts +357 -357
  67. package/src/browser/service/notebook-model-resolver-service.ts +160 -160
  68. package/src/browser/service/notebook-monaco-text-model-service.ts +48 -48
  69. package/src/browser/service/notebook-options.ts +155 -155
  70. package/src/browser/service/notebook-renderer-messaging-service.ts +121 -121
  71. package/src/browser/service/notebook-service.ts +215 -215
  72. package/src/browser/style/index.css +483 -471
  73. package/src/browser/view/notebook-cell-editor.tsx +263 -247
  74. package/src/browser/view/notebook-cell-list-view.tsx +279 -262
  75. package/src/browser/view/notebook-cell-toolbar-factory.tsx +102 -102
  76. package/src/browser/view/notebook-cell-toolbar.tsx +74 -74
  77. package/src/browser/view/notebook-code-cell-view.tsx +350 -350
  78. package/src/browser/view/notebook-find-widget.tsx +335 -335
  79. package/src/browser/view/notebook-main-toolbar.tsx +235 -235
  80. package/src/browser/view/notebook-markdown-cell-view.tsx +208 -208
  81. package/src/browser/view/notebook-viewport-service.ts +61 -61
  82. package/src/browser/view-model/notebook-cell-model.ts +473 -466
  83. package/src/browser/view-model/notebook-cell-output-model.ts +100 -100
  84. package/src/browser/view-model/notebook-model.ts +550 -550
  85. package/src/common/index.ts +18 -18
  86. package/src/common/notebook-common.ts +337 -337
  87. package/src/common/notebook-protocol.ts +35 -35
  88. package/src/common/notebook-range.ts +30 -30
@@ -1,262 +1,279 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2023 TypeFox and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
- import * as React from '@theia/core/shared/react';
17
- import { CellEditType, CellKind, NotebookCellsChangeType } from '../../common';
18
- import { NotebookCellModel } from '../view-model/notebook-cell-model';
19
- import { NotebookModel } from '../view-model/notebook-model';
20
- import { NotebookCellToolbarFactory } from './notebook-cell-toolbar-factory';
21
- import { animationFrame, codicon, onDomEvent } from '@theia/core/lib/browser';
22
- import { CommandRegistry, DisposableCollection, nls } from '@theia/core';
23
- import { NotebookCommands } from '../contributions/notebook-actions-contribution';
24
- import { NotebookCellActionContribution } from '../contributions/notebook-cell-actions-contribution';
25
- import { NotebookContextManager } from '../service/notebook-context-manager';
26
-
27
- export interface CellRenderer {
28
- render(notebookData: NotebookModel, cell: NotebookCellModel, index: number): React.ReactNode
29
- renderDragImage(cell: NotebookCellModel): HTMLElement
30
- }
31
-
32
- interface CellListProps {
33
- renderers: Map<CellKind, CellRenderer>;
34
- notebookModel: NotebookModel;
35
- notebookContext: NotebookContextManager;
36
- toolbarRenderer: NotebookCellToolbarFactory;
37
- commandRegistry: CommandRegistry
38
- }
39
-
40
- interface NotebookCellListState {
41
- selectedCell?: NotebookCellModel;
42
- scrollIntoView: boolean;
43
- dragOverIndicator: { cell: NotebookCellModel, position: 'top' | 'bottom' } | undefined;
44
- }
45
-
46
- export class NotebookCellListView extends React.Component<CellListProps, NotebookCellListState> {
47
-
48
- protected toDispose = new DisposableCollection();
49
-
50
- protected static dragGhost: HTMLElement | undefined;
51
- protected cellListRef: React.RefObject<HTMLUListElement> = React.createRef();
52
-
53
- constructor(props: CellListProps) {
54
- super(props);
55
- this.state = { selectedCell: props.notebookModel.selectedCell, dragOverIndicator: undefined, scrollIntoView: true };
56
- this.toDispose.push(props.notebookModel.onDidAddOrRemoveCell(e => {
57
- if (e.newCellIds && e.newCellIds.length > 0) {
58
- this.setState({
59
- ...this.state,
60
- selectedCell: this.props.notebookModel.cells.find(model => model.handle === e.newCellIds![e.newCellIds!.length - 1]),
61
- scrollIntoView: true
62
- });
63
- } else {
64
- this.setState({
65
- ...this.state,
66
- selectedCell: this.props.notebookModel.cells.find(cell => cell === this.state.selectedCell),
67
- scrollIntoView: false
68
- });
69
- }
70
- }));
71
-
72
- this.toDispose.push(props.notebookModel.onDidChangeContent(events => {
73
- if (events.some(e => e.kind === NotebookCellsChangeType.Move)) {
74
- // When a cell has been moved, we need to rerender the whole component
75
- this.forceUpdate();
76
- }
77
- }));
78
-
79
- this.toDispose.push(props.notebookModel.onDidChangeSelectedCell(e => {
80
- this.setState({
81
- ...this.state,
82
- selectedCell: e.cell,
83
- scrollIntoView: e.scrollIntoView
84
- });
85
- }));
86
-
87
- this.toDispose.push(onDomEvent(document, 'focusin', () => {
88
- animationFrame().then(() => {
89
- if (!this.cellListRef.current) {
90
- return;
91
- }
92
- let hasCellFocus = false;
93
- let hasFocus = false;
94
- if (this.cellListRef.current.contains(document.activeElement)) {
95
- if (this.props.notebookModel.selectedCell) {
96
- hasCellFocus = true;
97
- }
98
- hasFocus = true;
99
- }
100
- this.props.notebookContext.changeCellFocus(hasCellFocus);
101
- this.props.notebookContext.changeCellListFocus(hasFocus);
102
- });
103
- }));
104
- }
105
-
106
- override componentWillUnmount(): void {
107
- this.toDispose.dispose();
108
- }
109
-
110
- override render(): React.ReactNode {
111
- return <ul className='theia-notebook-cell-list' ref={this.cellListRef}>
112
- {this.props.notebookModel.cells
113
- .map((cell, index) =>
114
- <React.Fragment key={'cell-' + cell.handle}>
115
- <NotebookCellDivider
116
- isVisible={() => this.isEnabled()}
117
- onAddNewCell={(kind: CellKind) => this.onAddNewCell(kind, index)}
118
- onDrop={e => this.onDrop(e, index)}
119
- onDragOver={e => this.onDragOver(e, cell, 'top')} />
120
- {this.shouldRenderDragOverIndicator(cell, 'top') && <CellDropIndicator />}
121
- <li className={'theia-notebook-cell' + (this.state.selectedCell === cell ? ' focused' : '') + (this.isEnabled() ? ' draggable' : '')}
122
- onClick={e => {
123
- this.setState({ ...this.state, selectedCell: cell });
124
- this.props.notebookModel.setSelectedCell(cell, false);
125
- }}
126
- onDragStart={e => this.onDragStart(e, index, cell)}
127
- onDragEnd={e => {
128
- NotebookCellListView.dragGhost?.remove();
129
- this.setState({ ...this.state, dragOverIndicator: undefined });
130
- }}
131
- onDragOver={e => this.onDragOver(e, cell)}
132
- onDrop={e => this.onDrop(e, index)}
133
- draggable={true}
134
- tabIndex={-1}
135
- ref={ref => cell === this.state.selectedCell && this.state.scrollIntoView && ref?.scrollIntoView({ block: 'nearest' })}>
136
- <div className={'theia-notebook-cell-marker' + (this.state.selectedCell === cell ? ' theia-notebook-cell-marker-selected' : '')}></div>
137
- <div className='theia-notebook-cell-content'>
138
- {this.renderCellContent(cell, index)}
139
- </div>
140
- {this.state.selectedCell === cell &&
141
- this.props.toolbarRenderer.renderCellToolbar(NotebookCellActionContribution.ACTION_MENU, cell, {
142
- contextMenuArgs: () => [cell], commandArgs: () => [this.props.notebookModel]
143
- })
144
- }
145
- </li>
146
- {this.shouldRenderDragOverIndicator(cell, 'bottom') && <CellDropIndicator />}
147
- </React.Fragment>
148
- )
149
- }
150
- <NotebookCellDivider
151
- isVisible={() => this.isEnabled()}
152
- onAddNewCell={(kind: CellKind) => this.onAddNewCell(kind, this.props.notebookModel.cells.length)}
153
- onDrop={e => this.onDrop(e, this.props.notebookModel.cells.length - 1)}
154
- onDragOver={e => this.onDragOver(e, this.props.notebookModel.cells[this.props.notebookModel.cells.length - 1], 'bottom')} />
155
- </ul>;
156
- }
157
-
158
- renderCellContent(cell: NotebookCellModel, index: number): React.ReactNode {
159
- const renderer = this.props.renderers.get(cell.cellKind);
160
- if (!renderer) {
161
- throw new Error(`No renderer found for cell type ${cell.cellKind}`);
162
- }
163
- return renderer.render(this.props.notebookModel, cell, index);
164
- }
165
-
166
- protected onDragStart(event: React.DragEvent<HTMLLIElement>, index: number, cell: NotebookCellModel): void {
167
- event.stopPropagation();
168
- if (!this.isEnabled()) {
169
- event.preventDefault();
170
- return;
171
- }
172
-
173
- NotebookCellListView.dragGhost = document.createElement('div');
174
- NotebookCellListView.dragGhost.classList.add('theia-notebook-drag-ghost-image');
175
- NotebookCellListView.dragGhost.appendChild(this.props.renderers.get(cell.cellKind)?.renderDragImage(cell) ?? document.createElement('div'));
176
- document.body.appendChild(NotebookCellListView.dragGhost);
177
- event.dataTransfer.setDragImage(NotebookCellListView.dragGhost, -10, 0);
178
-
179
- event.dataTransfer.setData('text/theia-notebook-cell-index', index.toString());
180
- event.dataTransfer.setData('text/plain', this.props.notebookModel.cells[index].source);
181
- }
182
-
183
- protected onDragOver(event: React.DragEvent<HTMLLIElement>, cell: NotebookCellModel, position?: 'top' | 'bottom'): void {
184
- if (!this.isEnabled()) {
185
- return;
186
- }
187
- event.preventDefault();
188
- event.stopPropagation();
189
- // show indicator
190
- this.setState({ ...this.state, dragOverIndicator: { cell, position: position ?? event.nativeEvent.offsetY < event.currentTarget.clientHeight / 2 ? 'top' : 'bottom' } });
191
- }
192
-
193
- protected isEnabled(): boolean {
194
- return !Boolean(this.props.notebookModel.readOnly);
195
- }
196
-
197
- protected onDrop(event: React.DragEvent<HTMLLIElement>, dropElementIndex: number): void {
198
- if (!this.isEnabled()) {
199
- this.setState({ dragOverIndicator: undefined });
200
- return;
201
- }
202
- const index = parseInt(event.dataTransfer.getData('text/theia-notebook-cell-index'));
203
- const isTargetBelow = index < dropElementIndex;
204
- let newIdx = this.state.dragOverIndicator?.position === 'top' ? dropElementIndex : dropElementIndex + 1;
205
- newIdx = isTargetBelow ? newIdx - 1 : newIdx;
206
- if (index !== undefined && index !== dropElementIndex) {
207
- this.props.notebookModel.applyEdits([{
208
- editType: CellEditType.Move,
209
- length: 1,
210
- index,
211
- newIdx
212
- }], true);
213
- }
214
- this.setState({ ...this.state, dragOverIndicator: undefined });
215
- }
216
-
217
- protected onAddNewCell(kind: CellKind, index: number): void {
218
- if (this.isEnabled()) {
219
- this.props.commandRegistry.executeCommand(NotebookCommands.ADD_NEW_CELL_COMMAND.id,
220
- this.props.notebookModel,
221
- kind,
222
- index
223
- );
224
- }
225
- }
226
-
227
- protected shouldRenderDragOverIndicator(cell: NotebookCellModel, position: 'top' | 'bottom'): boolean {
228
- return this.isEnabled() &&
229
- this.state.dragOverIndicator !== undefined &&
230
- this.state.dragOverIndicator.cell === cell &&
231
- this.state.dragOverIndicator.position === position;
232
- }
233
-
234
- }
235
-
236
- export interface NotebookCellDividerProps {
237
- isVisible: () => boolean;
238
- onAddNewCell: (type: CellKind) => void;
239
- onDrop: (event: React.DragEvent<HTMLLIElement>) => void;
240
- onDragOver: (event: React.DragEvent<HTMLLIElement>) => void;
241
- }
242
-
243
- export function NotebookCellDivider({ isVisible, onAddNewCell, onDrop, onDragOver }: NotebookCellDividerProps): React.JSX.Element {
244
- const [hover, setHover] = React.useState(false);
245
-
246
- return <li className='theia-notebook-cell-divider' onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} onDrop={onDrop} onDragOver={onDragOver}>
247
- {hover && isVisible() && <div className='theia-notebook-add-cell-buttons'>
248
- <button className='theia-notebook-add-cell-button' onClick={() => onAddNewCell(CellKind.Code)} title={nls.localizeByDefault('Add Code Cell')}>
249
- <div className={codicon('add') + ' theia-notebook-add-cell-button-icon'} />
250
- {nls.localizeByDefault('Code')}
251
- </button>
252
- <button className='theia-notebook-add-cell-button' onClick={() => onAddNewCell(CellKind.Markup)} title={nls.localizeByDefault('Add Markdown Cell')}>
253
- <div className={codicon('add') + ' theia-notebook-add-cell-button-icon'} />
254
- {nls.localizeByDefault('Markdown')}
255
- </button>
256
- </div>}
257
- </li>;
258
- }
259
-
260
- function CellDropIndicator(): React.JSX.Element {
261
- return <div className='theia-notebook-cell-drop-indicator' />;
262
- }
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+ import * as React from '@theia/core/shared/react';
17
+ import { CellEditType, CellKind, NotebookCellsChangeType } from '../../common';
18
+ import { NotebookCellModel } from '../view-model/notebook-cell-model';
19
+ import { NotebookModel } from '../view-model/notebook-model';
20
+ import { NotebookCellToolbarFactory } from './notebook-cell-toolbar-factory';
21
+ import { animationFrame, onDomEvent } from '@theia/core/lib/browser';
22
+ import { CommandRegistry, DisposableCollection, MenuModelRegistry, MenuNode, nls } from '@theia/core';
23
+ import { NotebookCommands, NotebookMenus } from '../contributions/notebook-actions-contribution';
24
+ import { NotebookCellActionContribution } from '../contributions/notebook-cell-actions-contribution';
25
+ import { NotebookContextManager } from '../service/notebook-context-manager';
26
+
27
+ export interface CellRenderer {
28
+ render(notebookData: NotebookModel, cell: NotebookCellModel, index: number): React.ReactNode
29
+ renderDragImage(cell: NotebookCellModel): HTMLElement
30
+ }
31
+
32
+ interface CellListProps {
33
+ renderers: Map<CellKind, CellRenderer>;
34
+ notebookModel: NotebookModel;
35
+ notebookContext: NotebookContextManager;
36
+ toolbarRenderer: NotebookCellToolbarFactory;
37
+ commandRegistry: CommandRegistry;
38
+ menuRegistry: MenuModelRegistry;
39
+ }
40
+
41
+ interface NotebookCellListState {
42
+ selectedCell?: NotebookCellModel;
43
+ scrollIntoView: boolean;
44
+ dragOverIndicator: { cell: NotebookCellModel, position: 'top' | 'bottom' } | undefined;
45
+ }
46
+
47
+ export class NotebookCellListView extends React.Component<CellListProps, NotebookCellListState> {
48
+
49
+ protected toDispose = new DisposableCollection();
50
+
51
+ protected static dragGhost: HTMLElement | undefined;
52
+ protected cellListRef: React.RefObject<HTMLUListElement> = React.createRef();
53
+
54
+ constructor(props: CellListProps) {
55
+ super(props);
56
+ this.state = { selectedCell: props.notebookModel.selectedCell, dragOverIndicator: undefined, scrollIntoView: true };
57
+ this.toDispose.push(props.notebookModel.onDidAddOrRemoveCell(e => {
58
+ if (e.newCellIds && e.newCellIds.length > 0) {
59
+ this.setState({
60
+ ...this.state,
61
+ selectedCell: this.props.notebookModel.cells.find(model => model.handle === e.newCellIds![e.newCellIds!.length - 1]),
62
+ scrollIntoView: true
63
+ });
64
+ } else {
65
+ this.setState({
66
+ ...this.state,
67
+ selectedCell: this.props.notebookModel.cells.find(cell => cell === this.state.selectedCell),
68
+ scrollIntoView: false
69
+ });
70
+ }
71
+ }));
72
+
73
+ this.toDispose.push(props.notebookModel.onDidChangeContent(events => {
74
+ if (events.some(e => e.kind === NotebookCellsChangeType.Move)) {
75
+ // When a cell has been moved, we need to rerender the whole component
76
+ this.forceUpdate();
77
+ }
78
+ }));
79
+
80
+ this.toDispose.push(props.notebookModel.onDidChangeSelectedCell(e => {
81
+ this.setState({
82
+ ...this.state,
83
+ selectedCell: e.cell,
84
+ scrollIntoView: e.scrollIntoView
85
+ });
86
+ }));
87
+
88
+ this.toDispose.push(onDomEvent(document, 'focusin', () => {
89
+ animationFrame().then(() => {
90
+ if (!this.cellListRef.current) {
91
+ return;
92
+ }
93
+ let hasCellFocus = false;
94
+ let hasFocus = false;
95
+ if (this.cellListRef.current.contains(document.activeElement)) {
96
+ if (this.props.notebookModel.selectedCell) {
97
+ hasCellFocus = true;
98
+ }
99
+ hasFocus = true;
100
+ }
101
+ this.props.notebookContext.changeCellFocus(hasCellFocus);
102
+ this.props.notebookContext.changeCellListFocus(hasFocus);
103
+ });
104
+ }));
105
+ }
106
+
107
+ override componentWillUnmount(): void {
108
+ this.toDispose.dispose();
109
+ }
110
+
111
+ override render(): React.ReactNode {
112
+ return <ul className='theia-notebook-cell-list' ref={this.cellListRef}>
113
+ {this.props.notebookModel.cells
114
+ .map((cell, index) =>
115
+ <React.Fragment key={'cell-' + cell.handle}>
116
+ <NotebookCellDivider
117
+ menuRegistry={this.props.menuRegistry}
118
+ isVisible={() => this.isEnabled()}
119
+ onAddNewCell={(commandId: string) => this.onAddNewCell(commandId, index)}
120
+ onDrop={e => this.onDrop(e, index)}
121
+ onDragOver={e => this.onDragOver(e, cell, 'top')} />
122
+ {this.shouldRenderDragOverIndicator(cell, 'top') && <CellDropIndicator />}
123
+ <li className={'theia-notebook-cell' + (this.state.selectedCell === cell ? ' focused' : '') + (this.isEnabled() ? ' draggable' : '')}
124
+ onClick={e => {
125
+ this.setState({ ...this.state, selectedCell: cell });
126
+ this.props.notebookModel.setSelectedCell(cell, false);
127
+ }}
128
+ onDragStart={e => this.onDragStart(e, index, cell)}
129
+ onDragEnd={e => {
130
+ NotebookCellListView.dragGhost?.remove();
131
+ this.setState({ ...this.state, dragOverIndicator: undefined });
132
+ }}
133
+ onDragOver={e => this.onDragOver(e, cell)}
134
+ onDrop={e => this.onDrop(e, index)}
135
+ draggable={true}
136
+ tabIndex={-1}
137
+ ref={ref => {
138
+ if (ref && cell === this.state.selectedCell && this.state.scrollIntoView) {
139
+ ref.scrollIntoView({ block: 'nearest' });
140
+ if (cell.cellKind === CellKind.Markup && !cell.editing) {
141
+ ref.focus();
142
+ }
143
+ }
144
+ }}>
145
+ <div className={'theia-notebook-cell-marker' + (this.state.selectedCell === cell ? ' theia-notebook-cell-marker-selected' : '')}></div>
146
+ <div className='theia-notebook-cell-content'>
147
+ {this.renderCellContent(cell, index)}
148
+ </div>
149
+ {this.state.selectedCell === cell &&
150
+ this.props.toolbarRenderer.renderCellToolbar(NotebookCellActionContribution.ACTION_MENU, cell, {
151
+ contextMenuArgs: () => [cell], commandArgs: () => [this.props.notebookModel]
152
+ })
153
+ }
154
+ </li>
155
+ {this.shouldRenderDragOverIndicator(cell, 'bottom') && <CellDropIndicator />}
156
+ </React.Fragment>
157
+ )
158
+ }
159
+ <NotebookCellDivider
160
+ menuRegistry={this.props.menuRegistry}
161
+ isVisible={() => this.isEnabled()}
162
+ onAddNewCell={(commandId: string) => this.onAddNewCell(commandId, this.props.notebookModel.cells.length)}
163
+ onDrop={e => this.onDrop(e, this.props.notebookModel.cells.length - 1)}
164
+ onDragOver={e => this.onDragOver(e, this.props.notebookModel.cells[this.props.notebookModel.cells.length - 1], 'bottom')} />
165
+ </ul>;
166
+ }
167
+
168
+ renderCellContent(cell: NotebookCellModel, index: number): React.ReactNode {
169
+ const renderer = this.props.renderers.get(cell.cellKind);
170
+ if (!renderer) {
171
+ throw new Error(`No renderer found for cell type ${cell.cellKind}`);
172
+ }
173
+ return renderer.render(this.props.notebookModel, cell, index);
174
+ }
175
+
176
+ protected onDragStart(event: React.DragEvent<HTMLLIElement>, index: number, cell: NotebookCellModel): void {
177
+ event.stopPropagation();
178
+ if (!this.isEnabled()) {
179
+ event.preventDefault();
180
+ return;
181
+ }
182
+
183
+ NotebookCellListView.dragGhost = document.createElement('div');
184
+ NotebookCellListView.dragGhost.classList.add('theia-notebook-drag-ghost-image');
185
+ NotebookCellListView.dragGhost.appendChild(this.props.renderers.get(cell.cellKind)?.renderDragImage(cell) ?? document.createElement('div'));
186
+ document.body.appendChild(NotebookCellListView.dragGhost);
187
+ event.dataTransfer.setDragImage(NotebookCellListView.dragGhost, -10, 0);
188
+
189
+ event.dataTransfer.setData('text/theia-notebook-cell-index', index.toString());
190
+ event.dataTransfer.setData('text/plain', this.props.notebookModel.cells[index].source);
191
+ }
192
+
193
+ protected onDragOver(event: React.DragEvent<HTMLLIElement>, cell: NotebookCellModel, position?: 'top' | 'bottom'): void {
194
+ if (!this.isEnabled()) {
195
+ return;
196
+ }
197
+ event.preventDefault();
198
+ event.stopPropagation();
199
+ // show indicator
200
+ this.setState({ ...this.state, dragOverIndicator: { cell, position: position ?? event.nativeEvent.offsetY < event.currentTarget.clientHeight / 2 ? 'top' : 'bottom' } });
201
+ }
202
+
203
+ protected isEnabled(): boolean {
204
+ return !Boolean(this.props.notebookModel.readOnly);
205
+ }
206
+
207
+ protected onDrop(event: React.DragEvent<HTMLLIElement>, dropElementIndex: number): void {
208
+ if (!this.isEnabled()) {
209
+ this.setState({ dragOverIndicator: undefined });
210
+ return;
211
+ }
212
+ const index = parseInt(event.dataTransfer.getData('text/theia-notebook-cell-index'));
213
+ const isTargetBelow = index < dropElementIndex;
214
+ let newIdx = this.state.dragOverIndicator?.position === 'top' ? dropElementIndex : dropElementIndex + 1;
215
+ newIdx = isTargetBelow ? newIdx - 1 : newIdx;
216
+ if (index !== undefined && index !== dropElementIndex) {
217
+ this.props.notebookModel.applyEdits([{
218
+ editType: CellEditType.Move,
219
+ length: 1,
220
+ index,
221
+ newIdx
222
+ }], true);
223
+ }
224
+ this.setState({ ...this.state, dragOverIndicator: undefined });
225
+ }
226
+
227
+ protected onAddNewCell(commandId: string, index: number): void {
228
+ if (this.isEnabled()) {
229
+ this.props.commandRegistry.executeCommand(NotebookCommands.CHANGE_SELECTED_CELL.id, index - 1);
230
+ this.props.commandRegistry.executeCommand(commandId,
231
+ this.props.notebookModel,
232
+ index
233
+ );
234
+ }
235
+ }
236
+
237
+ protected shouldRenderDragOverIndicator(cell: NotebookCellModel, position: 'top' | 'bottom'): boolean {
238
+ return this.isEnabled() &&
239
+ this.state.dragOverIndicator !== undefined &&
240
+ this.state.dragOverIndicator.cell === cell &&
241
+ this.state.dragOverIndicator.position === position;
242
+ }
243
+
244
+ }
245
+
246
+ export interface NotebookCellDividerProps {
247
+ isVisible: () => boolean;
248
+ onAddNewCell: (commandId: string) => void;
249
+ onDrop: (event: React.DragEvent<HTMLLIElement>) => void;
250
+ onDragOver: (event: React.DragEvent<HTMLLIElement>) => void;
251
+ menuRegistry: MenuModelRegistry;
252
+ }
253
+
254
+ export function NotebookCellDivider({ isVisible, onAddNewCell, onDrop, onDragOver, menuRegistry }: NotebookCellDividerProps): React.JSX.Element {
255
+ const [hover, setHover] = React.useState(false);
256
+
257
+ const menuPath = NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_CELL_ADD_GROUP;
258
+ const menuItems = menuRegistry.getMenuNode(menuPath).children;
259
+
260
+ const renderItem = (item: MenuNode): React.ReactNode => <button
261
+ key={item.id}
262
+ className='theia-notebook-add-cell-button'
263
+ onClick={() => onAddNewCell(item.command || '')}
264
+ title={nls.localizeByDefault(`Add ${item.label} Cell`)}
265
+ >
266
+ <div className={item.icon + ' theia-notebook-add-cell-button-icon'} />
267
+ <div className='theia-notebook-add-cell-button-text'>{item.label}</div>
268
+ </button>;
269
+
270
+ return <li className='theia-notebook-cell-divider' onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} onDrop={onDrop} onDragOver={onDragOver}>
271
+ {hover && isVisible() && <div className='theia-notebook-add-cell-buttons'>
272
+ {menuItems.map((item: MenuNode) => renderItem(item))}
273
+ </div>}
274
+ </li>;
275
+ }
276
+
277
+ function CellDropIndicator(): React.JSX.Element {
278
+ return <div className='theia-notebook-cell-drop-indicator' />;
279
+ }