@difizen/libro-code-cell 0.1.2
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/LICENSE +21 -0
- package/README.md +3 -0
- package/es/code-cell-contribution.d.ts +12 -0
- package/es/code-cell-contribution.d.ts.map +1 -0
- package/es/code-cell-contribution.js +66 -0
- package/es/code-cell-model.d.ts +26 -0
- package/es/code-cell-model.d.ts.map +1 -0
- package/es/code-cell-model.js +107 -0
- package/es/code-cell-protocol.d.ts +5 -0
- package/es/code-cell-protocol.d.ts.map +1 -0
- package/es/code-cell-protocol.js +1 -0
- package/es/code-cell-view.d.ts +42 -0
- package/es/code-cell-view.d.ts.map +1 -0
- package/es/code-cell-view.js +399 -0
- package/es/index.d.ts +6 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +5 -0
- package/es/module.d.ts +3 -0
- package/es/module.d.ts.map +1 -0
- package/es/module.js +21 -0
- package/package.json +59 -0
- package/src/code-cell-contribution.ts +32 -0
- package/src/code-cell-model.ts +73 -0
- package/src/code-cell-protocol.ts +6 -0
- package/src/code-cell-view.tsx +331 -0
- package/src/index.spec.ts +10 -0
- package/src/index.ts +5 -0
- package/src/module.ts +25 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-parameter-properties */
|
|
3
|
+
/* eslint-disable @typescript-eslint/parameter-properties */
|
|
4
|
+
import type { CodeEditorViewOptions, CodeEditorView } from '@difizen/libro-code-editor';
|
|
5
|
+
import { CodeEditorManager, CodeEditorSettings } from '@difizen/libro-code-editor';
|
|
6
|
+
import type { ICodeCell, IOutput } from '@difizen/libro-common';
|
|
7
|
+
import { isOutput } from '@difizen/libro-common';
|
|
8
|
+
import type {
|
|
9
|
+
IOutputAreaOption,
|
|
10
|
+
LibroCell,
|
|
11
|
+
CellViewOptions,
|
|
12
|
+
} from '@difizen/libro-core';
|
|
13
|
+
import {
|
|
14
|
+
CellService,
|
|
15
|
+
EditorStatus,
|
|
16
|
+
LibroExecutableCellView,
|
|
17
|
+
LibroOutputArea,
|
|
18
|
+
VirtualizedManagerHelper,
|
|
19
|
+
} from '@difizen/libro-core';
|
|
20
|
+
import {
|
|
21
|
+
getOrigin,
|
|
22
|
+
inject,
|
|
23
|
+
prop,
|
|
24
|
+
transient,
|
|
25
|
+
useInject,
|
|
26
|
+
view,
|
|
27
|
+
ViewInstance,
|
|
28
|
+
ViewManager,
|
|
29
|
+
ViewOption,
|
|
30
|
+
ViewRender,
|
|
31
|
+
watch,
|
|
32
|
+
} from '@difizen/mana-app';
|
|
33
|
+
import { Deferred } from '@difizen/mana-app';
|
|
34
|
+
import { useEffect, useMemo, useRef, memo, forwardRef } from 'react';
|
|
35
|
+
|
|
36
|
+
import type { LibroCodeCellModel } from './code-cell-model.js';
|
|
37
|
+
|
|
38
|
+
function countLines(inputString: string) {
|
|
39
|
+
const lines = inputString.split('\n');
|
|
40
|
+
return lines.length;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const CellEditor: React.FC = () => {
|
|
44
|
+
const instance = useInject<LibroCodeCellView>(ViewInstance);
|
|
45
|
+
const virtualizedManagerHelper = useInject(VirtualizedManagerHelper);
|
|
46
|
+
const virtualizedManager = virtualizedManagerHelper.getOrCreate(
|
|
47
|
+
instance.parent.model,
|
|
48
|
+
);
|
|
49
|
+
const editorRef = useRef(null);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (instance.editorView?.editor) {
|
|
53
|
+
instance.editor = getOrigin(instance.editorView?.editor);
|
|
54
|
+
}
|
|
55
|
+
}, [instance, instance.editorView?.editor]);
|
|
56
|
+
|
|
57
|
+
const editorAreaHeight = useMemo(() => {
|
|
58
|
+
return instance.calcEditorAreaHeight();
|
|
59
|
+
}, [instance.model.value]);
|
|
60
|
+
|
|
61
|
+
if (virtualizedManager.isVirtualized) {
|
|
62
|
+
instance.renderEditorIntoVirtualized = true;
|
|
63
|
+
if (instance.setEditorHost) {
|
|
64
|
+
instance.setEditorHost(editorRef);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
style={{
|
|
70
|
+
height: `${editorAreaHeight || 0}px`,
|
|
71
|
+
width: '100%',
|
|
72
|
+
}}
|
|
73
|
+
ref={editorRef}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
return <>{instance.editorView && <ViewRender view={instance.editorView} />}</>;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const CellEditorMemo = memo(CellEditor);
|
|
82
|
+
|
|
83
|
+
const CodeEditorViewComponent = forwardRef<HTMLDivElement>(
|
|
84
|
+
function CodeEditorViewComponent(props, ref) {
|
|
85
|
+
const instance = useInject<LibroCodeCellView>(ViewInstance);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
className="libro-codemirror-cell-editor"
|
|
90
|
+
ref={ref}
|
|
91
|
+
tabIndex={10}
|
|
92
|
+
onBlur={instance.blur}
|
|
93
|
+
>
|
|
94
|
+
<CellEditorMemo />
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
@transient()
|
|
101
|
+
@view('code-editor-cell-view')
|
|
102
|
+
export class LibroCodeCellView extends LibroExecutableCellView {
|
|
103
|
+
override view = CodeEditorViewComponent;
|
|
104
|
+
|
|
105
|
+
viewManager: ViewManager;
|
|
106
|
+
|
|
107
|
+
codeEditorManager: CodeEditorManager;
|
|
108
|
+
|
|
109
|
+
declare model: LibroCodeCellModel;
|
|
110
|
+
|
|
111
|
+
outputs: IOutput[];
|
|
112
|
+
|
|
113
|
+
@prop()
|
|
114
|
+
editorView?: CodeEditorView;
|
|
115
|
+
|
|
116
|
+
@prop()
|
|
117
|
+
editorAreaHeight = 0;
|
|
118
|
+
|
|
119
|
+
@prop()
|
|
120
|
+
override noEditorAreaHeight = 0;
|
|
121
|
+
|
|
122
|
+
@prop()
|
|
123
|
+
override cellViewTopPos = 0;
|
|
124
|
+
|
|
125
|
+
@prop()
|
|
126
|
+
override editorStatus: EditorStatus = EditorStatus.NOTLOADED;
|
|
127
|
+
|
|
128
|
+
protected editorViewReadyDeferred: Deferred<void> = new Deferred<void>();
|
|
129
|
+
|
|
130
|
+
get editorReady() {
|
|
131
|
+
return this.editorViewReadyDeferred.promise;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected outputAreaDeferred = new Deferred<LibroOutputArea>();
|
|
135
|
+
get outputAreaReady() {
|
|
136
|
+
return this.outputAreaDeferred.promise;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
protected codeEditorSettings: CodeEditorSettings;
|
|
140
|
+
|
|
141
|
+
override renderEditor = () => {
|
|
142
|
+
if (this.editorView) {
|
|
143
|
+
return <ViewRender view={this.editorView} />;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// onViewResize(size: ViewSize) {
|
|
149
|
+
// if (size.height) this.editorAreaHeight = size.height;
|
|
150
|
+
// }
|
|
151
|
+
|
|
152
|
+
calcEditorAreaHeight() {
|
|
153
|
+
// if (
|
|
154
|
+
// this.editorStatus === EditorStatus.NOTLOADED ||
|
|
155
|
+
// this.editorStatus === EditorStatus.LOADING
|
|
156
|
+
// ) {
|
|
157
|
+
|
|
158
|
+
const { lineHeight, paddingTop, paddingBottom, scrollBarHeight } =
|
|
159
|
+
this.codeEditorManager.getUserEditorConfig(this.model);
|
|
160
|
+
|
|
161
|
+
const codeHeight = countLines(this.model.value) * (lineHeight || 20);
|
|
162
|
+
const editorPadding = paddingTop + paddingBottom;
|
|
163
|
+
|
|
164
|
+
const editorAreaHeight = codeHeight + editorPadding + scrollBarHeight;
|
|
165
|
+
|
|
166
|
+
this.editorAreaHeight = editorAreaHeight;
|
|
167
|
+
|
|
168
|
+
// 编辑器已经加载的情况下cell高度都由对它的高度监听得到。
|
|
169
|
+
return this.editorAreaHeight;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
constructor(
|
|
173
|
+
@inject(ViewOption) options: CellViewOptions,
|
|
174
|
+
@inject(CellService) cellService: CellService,
|
|
175
|
+
@inject(ViewManager) viewManager: ViewManager,
|
|
176
|
+
@inject(CodeEditorManager) codeEditorManager: CodeEditorManager,
|
|
177
|
+
@inject(CodeEditorSettings) codeEditorSettings: CodeEditorSettings,
|
|
178
|
+
) {
|
|
179
|
+
super(options, cellService);
|
|
180
|
+
this.options = options;
|
|
181
|
+
this.viewManager = viewManager;
|
|
182
|
+
this.codeEditorManager = codeEditorManager;
|
|
183
|
+
this.codeEditorSettings = codeEditorSettings;
|
|
184
|
+
|
|
185
|
+
this.outputs = options.cell?.outputs as IOutput[];
|
|
186
|
+
this.className = this.className + ' code';
|
|
187
|
+
|
|
188
|
+
// 创建outputArea
|
|
189
|
+
this.viewManager
|
|
190
|
+
.getOrCreateView<LibroOutputArea, IOutputAreaOption>(LibroOutputArea, {
|
|
191
|
+
cellId: this.id,
|
|
192
|
+
cell: this,
|
|
193
|
+
})
|
|
194
|
+
.then(async (outputArea) => {
|
|
195
|
+
this.outputArea = outputArea;
|
|
196
|
+
const output = this.outputs;
|
|
197
|
+
if (isOutput(output)) {
|
|
198
|
+
await this.outputArea.fromJSON(output);
|
|
199
|
+
}
|
|
200
|
+
this.outputAreaDeferred.resolve(outputArea);
|
|
201
|
+
this.outputWatch();
|
|
202
|
+
return;
|
|
203
|
+
})
|
|
204
|
+
.catch(console.error);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
override outputWatch() {
|
|
208
|
+
this.toDispose.push(
|
|
209
|
+
watch(this.outputArea, 'outputs', () => {
|
|
210
|
+
this.parent.model.onChange?.();
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
override toJSON(): LibroCell {
|
|
216
|
+
const meta = super.toJSON();
|
|
217
|
+
return {
|
|
218
|
+
...meta,
|
|
219
|
+
outputs: this.outputArea?.toJSON() ?? this.outputs,
|
|
220
|
+
} as ICodeCell;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
override onViewMount() {
|
|
224
|
+
this.createEditor();
|
|
225
|
+
//选中cell时才focus
|
|
226
|
+
if (this.parent.model.active?.id === this.id) {
|
|
227
|
+
this.focus(!this.parent.model.commandMode);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setEditorHost(ref: any) {
|
|
232
|
+
const editorHostId = this.parent.id + this.id;
|
|
233
|
+
|
|
234
|
+
this.codeEditorManager.setEditorHostRef(editorHostId, ref);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
protected getEditorOption(): CodeEditorViewOptions {
|
|
238
|
+
const option: CodeEditorViewOptions = {
|
|
239
|
+
editorHostId: this.parent.id + this.id,
|
|
240
|
+
model: this.model,
|
|
241
|
+
config: {
|
|
242
|
+
readOnly: this.parent.model.readOnly,
|
|
243
|
+
editable: !this.parent.model.readOnly,
|
|
244
|
+
placeholder: '请输入代码',
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
return option;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
protected async createEditor() {
|
|
251
|
+
const option = this.getEditorOption();
|
|
252
|
+
|
|
253
|
+
this.editorStatus = EditorStatus.LOADING;
|
|
254
|
+
|
|
255
|
+
// 防止虚拟滚动中编辑器被频繁创建
|
|
256
|
+
if (this.editorView) {
|
|
257
|
+
this.editorStatus = EditorStatus.LOADED;
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const editorView = await this.codeEditorManager.getOrCreateEditorView(option);
|
|
262
|
+
|
|
263
|
+
this.editorView = editorView;
|
|
264
|
+
this.editorViewReadyDeferred.resolve();
|
|
265
|
+
this.editorStatus = EditorStatus.LOADED;
|
|
266
|
+
|
|
267
|
+
await this.afterEditorReady();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
protected async afterEditorReady() {
|
|
271
|
+
watch(this.parent.model, 'readOnly', () => {
|
|
272
|
+
this.editorView?.editor?.setOption(
|
|
273
|
+
'readOnly',
|
|
274
|
+
getOrigin(this.parent.model.readOnly),
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
this.editorView?.onModalChange((val) => (this.hasModal = val));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
override shouldEnterEditorMode(e: React.FocusEvent<HTMLElement>) {
|
|
281
|
+
return getOrigin(this.editorView)?.editor?.host?.contains(e.target as HTMLElement)
|
|
282
|
+
? true
|
|
283
|
+
: false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
override blur = () => {
|
|
287
|
+
this.editorView?.editor?.setOption('styleActiveLine', false);
|
|
288
|
+
this.editorView?.editor?.setOption('highlightActiveLineGutter', false);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
override focus = (toEdit: boolean) => {
|
|
292
|
+
if (toEdit) {
|
|
293
|
+
if (this.parent.model.readOnly === true) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!this.editorView) {
|
|
297
|
+
this.editorReady
|
|
298
|
+
.then(() => {
|
|
299
|
+
this.editorView?.editorReady.then(() => {
|
|
300
|
+
this.editorView?.editor?.setOption('styleActiveLine', true);
|
|
301
|
+
this.editorView?.editor?.setOption('highlightActiveLineGutter', true);
|
|
302
|
+
if (this.editorView?.editor?.hasFocus()) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
this.editorView?.editor?.focus();
|
|
306
|
+
return;
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
})
|
|
310
|
+
.catch(console.error);
|
|
311
|
+
} else {
|
|
312
|
+
this.editorView?.editor?.setOption('styleActiveLine', true);
|
|
313
|
+
this.editorView?.editor?.setOption('highlightActiveLineGutter', true);
|
|
314
|
+
if (this.editorView?.editor?.hasFocus()) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
this.editorView?.editor?.focus();
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
if (this.container?.current?.parentElement?.contains(document.activeElement)) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
this.container?.current?.parentElement?.focus();
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
override clearExecution = () => {
|
|
328
|
+
this.model.clearExecution();
|
|
329
|
+
this.outputArea.clear();
|
|
330
|
+
};
|
|
331
|
+
}
|
package/src/index.ts
ADDED
package/src/module.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CodeEditorModule } from '@difizen/libro-code-editor';
|
|
2
|
+
import { CellOptions } from '@difizen/libro-core';
|
|
3
|
+
import { ManaModule } from '@difizen/mana-app';
|
|
4
|
+
|
|
5
|
+
import { CodeEditorCellContribution } from './code-cell-contribution.js';
|
|
6
|
+
import { LibroCodeCellModel } from './code-cell-model.js';
|
|
7
|
+
import { CodeCellModelFactory } from './code-cell-protocol.js';
|
|
8
|
+
import { LibroCodeCellView } from './code-cell-view.js';
|
|
9
|
+
|
|
10
|
+
export const CodeCellModule = ManaModule.create()
|
|
11
|
+
.register(CodeEditorCellContribution, LibroCodeCellView, LibroCodeCellModel, {
|
|
12
|
+
token: CodeCellModelFactory,
|
|
13
|
+
useFactory: (ctx) => {
|
|
14
|
+
return (options: CellOptions) => {
|
|
15
|
+
const child = ctx.container.createChild();
|
|
16
|
+
child.register({
|
|
17
|
+
token: CellOptions,
|
|
18
|
+
useValue: options,
|
|
19
|
+
});
|
|
20
|
+
const model = child.get(LibroCodeCellModel);
|
|
21
|
+
return model;
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
.dependOn(CodeEditorModule);
|