@difizen/libro-sql-cell 0.2.34
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 +1 -0
- package/es/index.d.ts +2 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +1 -0
- package/es/index.less +116 -0
- package/es/libro-formatter-sql-magic-contribution.d.ts +25 -0
- package/es/libro-formatter-sql-magic-contribution.d.ts.map +1 -0
- package/es/libro-formatter-sql-magic-contribution.js +50 -0
- package/es/libro-formatter-sql-trans-contribution.d.ts +13 -0
- package/es/libro-formatter-sql-trans-contribution.d.ts.map +1 -0
- package/es/libro-formatter-sql-trans-contribution.js +26 -0
- package/es/libro-sql-cell-color-registry.d.ts +6 -0
- package/es/libro-sql-cell-color-registry.d.ts.map +1 -0
- package/es/libro-sql-cell-color-registry.js +57 -0
- package/es/libro-sql-cell-contribution.d.ts +15 -0
- package/es/libro-sql-cell-contribution.d.ts.map +1 -0
- package/es/libro-sql-cell-contribution.js +75 -0
- package/es/libro-sql-cell-model.d.ts +33 -0
- package/es/libro-sql-cell-model.d.ts.map +1 -0
- package/es/libro-sql-cell-model.js +161 -0
- package/es/libro-sql-cell-protocol.d.ts +16 -0
- package/es/libro-sql-cell-protocol.d.ts.map +1 -0
- package/es/libro-sql-cell-protocol.js +1 -0
- package/es/libro-sql-cell-script.d.ts +4 -0
- package/es/libro-sql-cell-script.d.ts.map +1 -0
- package/es/libro-sql-cell-script.js +12 -0
- package/es/libro-sql-cell-view.d.ts +52 -0
- package/es/libro-sql-cell-view.d.ts.map +1 -0
- package/es/libro-sql-cell-view.js +712 -0
- package/es/libro-sql-utils.d.ts +3 -0
- package/es/libro-sql-utils.d.ts.map +1 -0
- package/es/libro-sql-utils.js +33 -0
- package/es/module.d.ts +3 -0
- package/es/module.d.ts.map +1 -0
- package/es/module.js +25 -0
- package/package.json +65 -0
- package/src/index.less +116 -0
- package/src/index.spec.ts +9 -0
- package/src/index.ts +1 -0
- package/src/libro-formatter-sql-magic-contribution.ts +53 -0
- package/src/libro-formatter-sql-trans-contribution.ts +22 -0
- package/src/libro-sql-cell-color-registry.ts +35 -0
- package/src/libro-sql-cell-contribution.ts +46 -0
- package/src/libro-sql-cell-model.ts +111 -0
- package/src/libro-sql-cell-protocol.ts +19 -0
- package/src/libro-sql-cell-script.ts +7 -0
- package/src/libro-sql-cell-view.tsx +600 -0
- package/src/libro-sql-utils.ts +22 -0
- package/src/module.ts +38 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
import { EditFilled, DatabaseOutlined } from '@ant-design/icons';
|
|
2
|
+
import type { CodeEditorViewOptions, CodeEditorView } from '@difizen/libro-code-editor';
|
|
3
|
+
import { CodeEditorManager } from '@difizen/libro-code-editor';
|
|
4
|
+
import type { ICodeCell, IOutput } from '@difizen/libro-common';
|
|
5
|
+
import { CellUri, isOutput } from '@difizen/libro-common';
|
|
6
|
+
import type {
|
|
7
|
+
CellViewOptions,
|
|
8
|
+
ExecutionMeta,
|
|
9
|
+
IOutputAreaOption,
|
|
10
|
+
LibroCell,
|
|
11
|
+
KernelMessage,
|
|
12
|
+
} from '@difizen/libro-jupyter';
|
|
13
|
+
import {
|
|
14
|
+
CellService,
|
|
15
|
+
EditorStatus,
|
|
16
|
+
LibroContextKey,
|
|
17
|
+
LibroExecutableCellView,
|
|
18
|
+
LibroViewTracker,
|
|
19
|
+
VirtualizedManagerHelper,
|
|
20
|
+
KernelError,
|
|
21
|
+
LibroJupyterModel,
|
|
22
|
+
LibroOutputArea,
|
|
23
|
+
} from '@difizen/libro-jupyter';
|
|
24
|
+
import type { ViewSize } from '@difizen/mana-app';
|
|
25
|
+
import {
|
|
26
|
+
Deferred,
|
|
27
|
+
Disposable,
|
|
28
|
+
DisposableCollection,
|
|
29
|
+
getOrigin,
|
|
30
|
+
inject,
|
|
31
|
+
prop,
|
|
32
|
+
transient,
|
|
33
|
+
useInject,
|
|
34
|
+
view,
|
|
35
|
+
ViewInstance,
|
|
36
|
+
ViewManager,
|
|
37
|
+
ViewOption,
|
|
38
|
+
ViewRender,
|
|
39
|
+
watch,
|
|
40
|
+
} from '@difizen/mana-app';
|
|
41
|
+
import { l10n } from '@difizen/mana-l10n';
|
|
42
|
+
import { Input, Popover } from 'antd';
|
|
43
|
+
import React from 'react';
|
|
44
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
45
|
+
|
|
46
|
+
import './index.less';
|
|
47
|
+
import { LibroSqlCellModel } from './libro-sql-cell-model.js';
|
|
48
|
+
import type { DatabaseConfig } from './libro-sql-cell-protocol.js';
|
|
49
|
+
import { SqlScript } from './libro-sql-cell-script.js';
|
|
50
|
+
import { getDfVariableName } from './libro-sql-utils.js';
|
|
51
|
+
|
|
52
|
+
function countLines(inputString: string) {
|
|
53
|
+
const lines = inputString.split('\n');
|
|
54
|
+
return lines.length;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const CellEditor: React.FC = () => {
|
|
58
|
+
const instance = useInject<LibroSqlCellView>(ViewInstance);
|
|
59
|
+
const virtualizedManagerHelper = useInject(VirtualizedManagerHelper);
|
|
60
|
+
const virtualizedManager = virtualizedManagerHelper.getOrCreate(
|
|
61
|
+
instance.parent.model,
|
|
62
|
+
);
|
|
63
|
+
const editorRef = useRef(null);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (instance.editorView?.editor) {
|
|
67
|
+
instance.editor = getOrigin(instance.editorView?.editor);
|
|
68
|
+
}
|
|
69
|
+
}, [instance, instance.editorView?.editor]);
|
|
70
|
+
|
|
71
|
+
if (virtualizedManager.isVirtualized) {
|
|
72
|
+
instance.renderEditorIntoVirtualized = true;
|
|
73
|
+
|
|
74
|
+
const editorAreaHeight = instance.calcEditorAreaHeight();
|
|
75
|
+
if (instance.setEditorHost) {
|
|
76
|
+
instance.setEditorHost(editorRef);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
style={{
|
|
82
|
+
height: `${editorAreaHeight || 0}px`,
|
|
83
|
+
width: '100%',
|
|
84
|
+
}}
|
|
85
|
+
ref={editorRef}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
} else {
|
|
89
|
+
return <>{instance.editorView && <ViewRender view={instance.editorView} />}</>;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
interface LibroSqlVariableProps {
|
|
94
|
+
handCancel: () => void; // 定义 handCancel 是一个函数类型
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const LibroSqlVariableNameInput: React.FC<LibroSqlVariableProps> = ({
|
|
98
|
+
handCancel,
|
|
99
|
+
}: LibroSqlVariableProps) => {
|
|
100
|
+
const cellView = useInject<LibroSqlCellView>(ViewInstance);
|
|
101
|
+
const [resultVariableAvailable, setVariableNameAvailable] = useState(true);
|
|
102
|
+
const [resultVariable, setVariableName] = useState(cellView.model.resultVariable);
|
|
103
|
+
const handleValueChange = useCallback(
|
|
104
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
105
|
+
if (
|
|
106
|
+
cellView.parent.model.cells.findIndex(
|
|
107
|
+
(cell) =>
|
|
108
|
+
cell.model instanceof LibroSqlCellModel &&
|
|
109
|
+
cell.model.resultVariable === e.target.value,
|
|
110
|
+
) > -1
|
|
111
|
+
) {
|
|
112
|
+
setVariableNameAvailable(false);
|
|
113
|
+
} else {
|
|
114
|
+
setVariableNameAvailable(true);
|
|
115
|
+
}
|
|
116
|
+
setVariableName(e.target.value);
|
|
117
|
+
},
|
|
118
|
+
[cellView.parent.model.cells],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const handValueSave = useCallback(() => {
|
|
122
|
+
cellView.model.resultVariable = getDfVariableName(
|
|
123
|
+
cellView.parent.model.cells.filter(
|
|
124
|
+
(cell) => cell.model.type === 'sql',
|
|
125
|
+
) as LibroSqlCellView[],
|
|
126
|
+
);
|
|
127
|
+
cellView.model.resultVariable = resultVariable;
|
|
128
|
+
if (cellView.parent.model.onChange) {
|
|
129
|
+
cellView.parent.model.onChange();
|
|
130
|
+
}
|
|
131
|
+
handCancel();
|
|
132
|
+
}, [cellView.model, cellView.parent.model, handCancel, resultVariable]);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<>
|
|
136
|
+
<Input
|
|
137
|
+
status={`${resultVariableAvailable ? '' : 'warning'}`}
|
|
138
|
+
className="libro-sql-variable-name-input"
|
|
139
|
+
onChange={handleValueChange}
|
|
140
|
+
defaultValue={cellView.model.resultVariable}
|
|
141
|
+
/>
|
|
142
|
+
|
|
143
|
+
{!resultVariableAvailable && (
|
|
144
|
+
<span className="libro-sql-input-warning-text">
|
|
145
|
+
{l10n.t('当前变量名已存在')}
|
|
146
|
+
</span>
|
|
147
|
+
)}
|
|
148
|
+
|
|
149
|
+
<div className="libro-sql-input-button">
|
|
150
|
+
<span onClick={handCancel} className="libro-sql-input-cancel">
|
|
151
|
+
{l10n.t('取消')}
|
|
152
|
+
</span>
|
|
153
|
+
<span onClick={handValueSave} className="libro-sql-input-save">
|
|
154
|
+
{l10n.t('保存')}
|
|
155
|
+
</span>
|
|
156
|
+
</div>
|
|
157
|
+
</>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const LibroSqlCell = React.forwardRef<HTMLDivElement>(
|
|
162
|
+
function SqlEditorViewComponent(props, ref) {
|
|
163
|
+
const [isVariableNameEdit, setIsVariableNameEdit] = useState(false);
|
|
164
|
+
const instance = useInject<LibroSqlCellView>(ViewInstance);
|
|
165
|
+
const contextKey = useInject(LibroContextKey);
|
|
166
|
+
|
|
167
|
+
const handCancelEdit = () => {
|
|
168
|
+
contextKey.enableCommandMode();
|
|
169
|
+
setIsVariableNameEdit(false);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div tabIndex={10} ref={ref} className={instance.className}>
|
|
174
|
+
<div className="libro-sql-cell-header">
|
|
175
|
+
<div className="libro-sql-source">
|
|
176
|
+
<span className="libro-sql-source-title">
|
|
177
|
+
<DatabaseOutlined />
|
|
178
|
+
</span>
|
|
179
|
+
<span className="libro-sql-source-content">
|
|
180
|
+
{instance.databaseConfig
|
|
181
|
+
? instance.databaseConfig.db_type +
|
|
182
|
+
': ' +
|
|
183
|
+
instance.databaseConfig.database
|
|
184
|
+
: '暂未配置数据库'}
|
|
185
|
+
</span>
|
|
186
|
+
</div>
|
|
187
|
+
<div className="libro-sql-variable-name">
|
|
188
|
+
<span className="libro-sql-variable-name-title">Name: </span>
|
|
189
|
+
<span className="libro-sql-variable-content">
|
|
190
|
+
{instance.model.resultVariable}
|
|
191
|
+
</span>
|
|
192
|
+
<div
|
|
193
|
+
className="libro-sql-variable-name-popover"
|
|
194
|
+
style={{ display: 'inline-block' }}
|
|
195
|
+
>
|
|
196
|
+
<Popover
|
|
197
|
+
content={<LibroSqlVariableNameInput handCancel={handCancelEdit} />}
|
|
198
|
+
placement="bottomLeft"
|
|
199
|
+
open={instance.parent.model.inputEditable ? isVariableNameEdit : false}
|
|
200
|
+
onOpenChange={(visible) => {
|
|
201
|
+
if (visible) {
|
|
202
|
+
contextKey.disableCommandMode();
|
|
203
|
+
} else {
|
|
204
|
+
contextKey.enableCommandMode();
|
|
205
|
+
}
|
|
206
|
+
setIsVariableNameEdit(visible);
|
|
207
|
+
}}
|
|
208
|
+
getPopupContainer={() => {
|
|
209
|
+
return instance.container?.current?.getElementsByClassName(
|
|
210
|
+
'libro-sql-variable-name',
|
|
211
|
+
)[0] as HTMLElement;
|
|
212
|
+
}}
|
|
213
|
+
trigger="click"
|
|
214
|
+
overlayClassName="libro-sql-popover-container"
|
|
215
|
+
>
|
|
216
|
+
<EditFilled className="libro-sql-edit-icon" />
|
|
217
|
+
</Popover>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
<CellEditor />
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
},
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
@transient()
|
|
228
|
+
@view('libro-sql-cell-view')
|
|
229
|
+
export class LibroSqlCellView extends LibroExecutableCellView {
|
|
230
|
+
override view = LibroSqlCell;
|
|
231
|
+
declare model: LibroSqlCellModel;
|
|
232
|
+
libroViewTracker: LibroViewTracker;
|
|
233
|
+
codeEditorManager: CodeEditorManager;
|
|
234
|
+
protected toDisposeOnEditor = new DisposableCollection();
|
|
235
|
+
|
|
236
|
+
@inject(LibroContextKey) protected readonly libroContextKey: LibroContextKey;
|
|
237
|
+
|
|
238
|
+
outputs: IOutput[];
|
|
239
|
+
|
|
240
|
+
@prop()
|
|
241
|
+
editorView?: CodeEditorView;
|
|
242
|
+
|
|
243
|
+
@prop()
|
|
244
|
+
databaseConfig?: DatabaseConfig;
|
|
245
|
+
|
|
246
|
+
@prop()
|
|
247
|
+
override editorStatus: EditorStatus = EditorStatus.NOTLOADED;
|
|
248
|
+
|
|
249
|
+
@prop()
|
|
250
|
+
editorAreaHeight = 0;
|
|
251
|
+
|
|
252
|
+
@prop()
|
|
253
|
+
override noEditorAreaHeight = 0;
|
|
254
|
+
|
|
255
|
+
@inject(SqlScript) sqlScript: SqlScript;
|
|
256
|
+
|
|
257
|
+
protected editorViewReadyDeferred: Deferred<void> = new Deferred<void>();
|
|
258
|
+
|
|
259
|
+
get editorReady() {
|
|
260
|
+
return this.editorViewReadyDeferred.promise;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
override renderEditor = () => {
|
|
264
|
+
if (this.editorView) {
|
|
265
|
+
return <ViewRender view={this.editorView} />;
|
|
266
|
+
} else {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// 计算编辑器区相对于编辑器区垂直方向的偏移量
|
|
272
|
+
override calcEditorOffset() {
|
|
273
|
+
return super.calcEditorOffset() + 36;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
calcEditorAreaHeight() {
|
|
277
|
+
if (
|
|
278
|
+
this.editorStatus === EditorStatus.NOTLOADED ||
|
|
279
|
+
this.editorStatus === EditorStatus.LOADING
|
|
280
|
+
) {
|
|
281
|
+
const { lineHeight, paddingTop, paddingBottom } =
|
|
282
|
+
this.codeEditorManager.getUserEditorConfig(this.model);
|
|
283
|
+
const codeHeight = countLines(this.model.value) * lineHeight!;
|
|
284
|
+
const editorPadding = paddingTop + paddingBottom;
|
|
285
|
+
|
|
286
|
+
const scrollbarHeight = 12;
|
|
287
|
+
|
|
288
|
+
// TODO: 滚动条有条件显示
|
|
289
|
+
|
|
290
|
+
const editorAreaHeight = codeHeight + editorPadding + scrollbarHeight;
|
|
291
|
+
|
|
292
|
+
this.editorAreaHeight = editorAreaHeight;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 编辑器已经加载的情况下cell高度都由对它的高度监听得到。
|
|
296
|
+
return this.editorAreaHeight;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
protected outputAreaDeferred = new Deferred<LibroOutputArea>();
|
|
300
|
+
get outputAreaReady() {
|
|
301
|
+
return this.outputAreaDeferred.promise;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
constructor(
|
|
305
|
+
@inject(ViewOption) options: CellViewOptions,
|
|
306
|
+
@inject(CellService) cellService: CellService,
|
|
307
|
+
@inject(ViewManager) viewManager: ViewManager,
|
|
308
|
+
@inject(LibroViewTracker) libroViewTracker: LibroViewTracker,
|
|
309
|
+
@inject(CodeEditorManager) codeEditorManager: CodeEditorManager,
|
|
310
|
+
) {
|
|
311
|
+
super(options, cellService);
|
|
312
|
+
this.codeEditorManager = codeEditorManager;
|
|
313
|
+
|
|
314
|
+
this.outputs = options.cell?.outputs as IOutput[];
|
|
315
|
+
this.className = this.className + ' sql';
|
|
316
|
+
|
|
317
|
+
viewManager
|
|
318
|
+
.getOrCreateView<LibroOutputArea, IOutputAreaOption>(LibroOutputArea, {
|
|
319
|
+
cellId: this.id,
|
|
320
|
+
cell: this,
|
|
321
|
+
})
|
|
322
|
+
.then(async (outputArea) => {
|
|
323
|
+
this.outputArea = outputArea;
|
|
324
|
+
const output = this.outputs;
|
|
325
|
+
if (isOutput(output)) {
|
|
326
|
+
await this.outputArea.fromJSON(output);
|
|
327
|
+
}
|
|
328
|
+
this.outputAreaDeferred.resolve(outputArea);
|
|
329
|
+
this.outputWatch();
|
|
330
|
+
return;
|
|
331
|
+
})
|
|
332
|
+
.catch(() => {
|
|
333
|
+
//
|
|
334
|
+
});
|
|
335
|
+
this.libroViewTracker = libroViewTracker;
|
|
336
|
+
if (!this.model.resultVariable) {
|
|
337
|
+
this.model.resultVariable = getDfVariableName(
|
|
338
|
+
this.libroViewTracker.viewCache
|
|
339
|
+
.get(options.parentId)
|
|
340
|
+
?.model.cells.filter(
|
|
341
|
+
(cell) => cell.model.type === 'sql',
|
|
342
|
+
) as LibroSqlCellView[],
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
override onViewMount = async () => {
|
|
348
|
+
await this.createEditor();
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
setEditorHost(ref: any) {
|
|
352
|
+
const editorHostId = this.parent.id + this.id;
|
|
353
|
+
this.codeEditorManager.setEditorHostRef(editorHostId, ref);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async createEditor() {
|
|
357
|
+
const editorHostId = this.parent.id + this.id;
|
|
358
|
+
const option: CodeEditorViewOptions = {
|
|
359
|
+
uuid: CellUri.from(this.parent.model.id, this.model.id).toString(),
|
|
360
|
+
editorHostId: editorHostId,
|
|
361
|
+
model: this.model,
|
|
362
|
+
config: {
|
|
363
|
+
readOnly: !this.parent.model.inputEditable,
|
|
364
|
+
editable: this.parent.model.inputEditable,
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
// 防止虚拟滚动中编辑器被频繁创建
|
|
368
|
+
if (this.editorView) {
|
|
369
|
+
this.editorStatus = EditorStatus.LOADED;
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const editorView = await this.codeEditorManager.getOrCreateEditorView(option);
|
|
374
|
+
|
|
375
|
+
this.editorView = editorView;
|
|
376
|
+
this.editorStatus = EditorStatus.LOADED;
|
|
377
|
+
this.editorViewReadyDeferred.resolve();
|
|
378
|
+
editorView.onEditorStatusChange((e) => {
|
|
379
|
+
if (e.status === 'ready') {
|
|
380
|
+
this.editor = this.editorView!.editor;
|
|
381
|
+
this.afterEditorReady();
|
|
382
|
+
} else if (e.status === 'disposed') {
|
|
383
|
+
this.toDisposeOnEditor.dispose();
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
protected async afterEditorReady() {
|
|
389
|
+
this.focusEditor();
|
|
390
|
+
this.toDisposeOnEditor.push(
|
|
391
|
+
watch(this.parent.model, 'inputEditable', () => {
|
|
392
|
+
this.editorView?.editor?.setOption(
|
|
393
|
+
'readOnly',
|
|
394
|
+
getOrigin(!this.parent.model.inputEditable),
|
|
395
|
+
);
|
|
396
|
+
}),
|
|
397
|
+
);
|
|
398
|
+
this.toDisposeOnEditor.push(
|
|
399
|
+
this.editorView?.onModalChange((val) => (this.hasModal = val)) ?? Disposable.NONE,
|
|
400
|
+
);
|
|
401
|
+
this.toDisposeOnEditor.push(
|
|
402
|
+
this.editor?.onModelContentChanged?.((e) => {
|
|
403
|
+
this.parent.model.onCellContentChange({ cell: this, changes: e });
|
|
404
|
+
}) ?? Disposable.NONE,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
protected focusEditor() {
|
|
409
|
+
//选中cell、编辑模式、非只读时才focus
|
|
410
|
+
if (
|
|
411
|
+
this.editorView?.editor &&
|
|
412
|
+
this.editorView.editorStatus === 'ready' &&
|
|
413
|
+
this.parent.model.active?.id === this.id &&
|
|
414
|
+
!this.parent.model.commandMode &&
|
|
415
|
+
this.libroContextKey.commandModeEnabled === true && // 排除弹窗等情况
|
|
416
|
+
this.parent.model.inputEditable
|
|
417
|
+
) {
|
|
418
|
+
this.editorView?.editor.setOption('styleActiveLine', true);
|
|
419
|
+
this.editorView?.editor.setOption('highlightActiveLineGutter', true);
|
|
420
|
+
this.editorView?.editor.focus();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
override onViewResize = (size: ViewSize): void => {
|
|
425
|
+
// 把 header 部分高度也放在这部分,用来撑开高度
|
|
426
|
+
if (size.height) {
|
|
427
|
+
this.editorAreaHeight = size.height + 36;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
override toJSON(): LibroCell {
|
|
432
|
+
const meta = super.toJSON();
|
|
433
|
+
return {
|
|
434
|
+
...meta,
|
|
435
|
+
source: meta.source ?? this.options.cell.source,
|
|
436
|
+
outputs: this.outputArea?.toJSON() ?? this.outputs,
|
|
437
|
+
} as ICodeCell;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
override focus = (toEdit: boolean) => {
|
|
441
|
+
if (toEdit) {
|
|
442
|
+
this.focusEditor();
|
|
443
|
+
this.getDatabaseConfig();
|
|
444
|
+
} else {
|
|
445
|
+
if (this.container?.current?.parentElement?.contains(document.activeElement)) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
this.container?.current?.parentElement?.focus();
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
override blur = () => {
|
|
453
|
+
this.editorView?.editor?.setOption('styleActiveLine', false);
|
|
454
|
+
this.editorView?.editor?.setOption('highlightActiveLineGutter', false);
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
override clearExecution = () => {
|
|
458
|
+
this.model.clearExecution();
|
|
459
|
+
this.outputArea.clear();
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
override async run() {
|
|
463
|
+
const libroModel = this.parent.model;
|
|
464
|
+
if (
|
|
465
|
+
!libroModel ||
|
|
466
|
+
!(libroModel instanceof LibroJupyterModel) ||
|
|
467
|
+
!libroModel.kernelConnection ||
|
|
468
|
+
libroModel.kernelConnection.isDisposed
|
|
469
|
+
) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
const kernelConnection = getOrigin(libroModel.kernelConnection);
|
|
473
|
+
const cellContent = this.model.source;
|
|
474
|
+
try {
|
|
475
|
+
// Promise.resolve().then(() => {
|
|
476
|
+
this.clearExecution();
|
|
477
|
+
// });
|
|
478
|
+
const future = kernelConnection.requestExecute({
|
|
479
|
+
code: cellContent,
|
|
480
|
+
});
|
|
481
|
+
let startTimeStr = null;
|
|
482
|
+
this.model.executing = true;
|
|
483
|
+
this.model.metadata['execution'] = {
|
|
484
|
+
'shell.execute_reply.started': '',
|
|
485
|
+
'shell.execute_reply.end': '',
|
|
486
|
+
to_execute: new Date().toISOString(),
|
|
487
|
+
} as ExecutionMeta;
|
|
488
|
+
// Handle iopub messages
|
|
489
|
+
future.onIOPub = (msg: any) => {
|
|
490
|
+
this.model.msgChangeEmitter.fire(msg);
|
|
491
|
+
if (msg.header.msg_type === 'execute_input') {
|
|
492
|
+
this.model.kernelExecuting = true;
|
|
493
|
+
startTimeStr = msg.header.date as string;
|
|
494
|
+
const meta = this.model.metadata['execution'] as ExecutionMeta;
|
|
495
|
+
if (meta) {
|
|
496
|
+
meta['shell.execute_reply.started'] = startTimeStr;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (msg.header.msg_type === 'execute_result') {
|
|
500
|
+
this.model.metadata['isVariableSaved'] =
|
|
501
|
+
msg.content.data['application/vnd.libro.sql+json'].isVariableSaved;
|
|
502
|
+
}
|
|
503
|
+
if (msg.header.msg_type === 'error') {
|
|
504
|
+
this.model.hasExecutedError = true;
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
const msgPromise = await future.done;
|
|
508
|
+
this.model.executing = false;
|
|
509
|
+
this.model.kernelExecuting = false;
|
|
510
|
+
this.model.hasExecutedSuccess = !this.model.hasExecutedError;
|
|
511
|
+
startTimeStr = msgPromise.metadata['started'] as string; // 更新startTimeStr
|
|
512
|
+
const endTimeStr = msgPromise.header.date;
|
|
513
|
+
this.model.metadata['execution']['shell.execute_reply.started'] = startTimeStr;
|
|
514
|
+
this.model.metadata['execution']['shell.execute_reply.end'] = endTimeStr;
|
|
515
|
+
if (!msgPromise) {
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
if (msgPromise.content.status === 'ok') {
|
|
519
|
+
return true;
|
|
520
|
+
} else {
|
|
521
|
+
throw new KernelError(msgPromise.content);
|
|
522
|
+
}
|
|
523
|
+
} catch (reason: any) {
|
|
524
|
+
if (reason.message.startsWith('Canceled')) {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
throw reason;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
fetch = async (
|
|
532
|
+
content: KernelMessage.IExecuteRequestMsg['content'],
|
|
533
|
+
ioCallback: (msg: KernelMessage.IIOPubMessage) => any,
|
|
534
|
+
) => {
|
|
535
|
+
const model = this.parent?.model as LibroJupyterModel;
|
|
536
|
+
await model.kcReady;
|
|
537
|
+
const connection = model.kernelConnection!;
|
|
538
|
+
const future = connection.requestExecute(content);
|
|
539
|
+
future.onIOPub = (msg) => {
|
|
540
|
+
ioCallback(msg);
|
|
541
|
+
};
|
|
542
|
+
return future.done as Promise<KernelMessage.IExecuteReplyMsg>;
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
handleQueryResponse = (
|
|
546
|
+
response: KernelMessage.IIOPubMessage,
|
|
547
|
+
cb: (result: string) => void,
|
|
548
|
+
) => {
|
|
549
|
+
const msgType = response.header.msg_type;
|
|
550
|
+
switch (msgType) {
|
|
551
|
+
case 'execute_result':
|
|
552
|
+
case 'display_data': {
|
|
553
|
+
const payload = response as KernelMessage.IExecuteResultMsg;
|
|
554
|
+
let content: string = payload.content.data['text/plain'] as string;
|
|
555
|
+
if (content.slice(0, 1) === "'" || content.slice(0, 1) === '"') {
|
|
556
|
+
content = content.slice(1, -1);
|
|
557
|
+
content = content.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
cb(content);
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
case 'stream': {
|
|
564
|
+
const payloadDisplay = response as KernelMessage.IStreamMsg;
|
|
565
|
+
let contentStream: string = payloadDisplay.content.text as string;
|
|
566
|
+
if (contentStream.slice(0, 1) === "'" || contentStream.slice(0, 1) === '"') {
|
|
567
|
+
contentStream = contentStream.slice(1, -1);
|
|
568
|
+
contentStream = contentStream.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
569
|
+
}
|
|
570
|
+
cb(contentStream);
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
default:
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
getDatabaseConfig = async () => {
|
|
579
|
+
return this.fetch(
|
|
580
|
+
{
|
|
581
|
+
code: this.sqlScript.getDbConfig,
|
|
582
|
+
store_history: false,
|
|
583
|
+
},
|
|
584
|
+
(msg) =>
|
|
585
|
+
this.handleQueryResponse(msg, (result) => {
|
|
586
|
+
try {
|
|
587
|
+
this.databaseConfig = JSON.parse(result);
|
|
588
|
+
} catch (e) {
|
|
589
|
+
this.databaseConfig = undefined;
|
|
590
|
+
}
|
|
591
|
+
}),
|
|
592
|
+
);
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
override shouldEnterEditorMode(e: React.FocusEvent<HTMLElement>) {
|
|
596
|
+
return getOrigin(this.editorView)?.editor?.host?.contains(e.target as HTMLElement)
|
|
597
|
+
? true
|
|
598
|
+
: false;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LibroSqlCellView } from './libro-sql-cell-view.js';
|
|
2
|
+
|
|
3
|
+
export const getDfVariableName = (cells: LibroSqlCellView[]) => {
|
|
4
|
+
let maxNumber = 0; // 记录目前找到的最大数字
|
|
5
|
+
|
|
6
|
+
// 遍历数组中的每个变量名
|
|
7
|
+
for (const cell of cells) {
|
|
8
|
+
// 检查变量名是否符合 "df_" 开头,并且后面跟着数字的格式
|
|
9
|
+
if (cell.model.resultVariable?.startsWith('df_')) {
|
|
10
|
+
const numberPart = cell.model.resultVariable.slice(3); // 提取 "df_" 后面的部分
|
|
11
|
+
const num = parseInt(numberPart, 10); // 将提取出来的部分转成数字
|
|
12
|
+
|
|
13
|
+
// 确保提取出来的是有效数字,并更新最大数字
|
|
14
|
+
if (!isNaN(num)) {
|
|
15
|
+
maxNumber = Math.max(maxNumber, num);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 返回下一个可用的变量名
|
|
21
|
+
return `df_${maxNumber + 1}`;
|
|
22
|
+
};
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CellOptions, LibroModule } from '@difizen/libro-jupyter';
|
|
2
|
+
import { LibroRenderMimeModule } from '@difizen/libro-rendermime';
|
|
3
|
+
import { ManaModule } from '@difizen/mana-app';
|
|
4
|
+
|
|
5
|
+
import { FormatterSqlMagicContribution } from './libro-formatter-sql-magic-contribution.js';
|
|
6
|
+
import { FormatterStringTransSqlContribution } from './libro-formatter-sql-trans-contribution.js';
|
|
7
|
+
import { LibroSQLCellColorRegistry } from './libro-sql-cell-color-registry.js';
|
|
8
|
+
import { SqlCellContribution } from './libro-sql-cell-contribution.js';
|
|
9
|
+
import { LibroSqlCellModel } from './libro-sql-cell-model.js';
|
|
10
|
+
import { LibroSqlCellModelFactory } from './libro-sql-cell-protocol.js';
|
|
11
|
+
import { SqlScript } from './libro-sql-cell-script.js';
|
|
12
|
+
import { LibroSqlCellView } from './libro-sql-cell-view.js';
|
|
13
|
+
|
|
14
|
+
export const LibroSqlCellModule = ManaModule.create()
|
|
15
|
+
.register(
|
|
16
|
+
SqlCellContribution,
|
|
17
|
+
LibroSqlCellView,
|
|
18
|
+
LibroSqlCellModel,
|
|
19
|
+
LibroSQLCellColorRegistry,
|
|
20
|
+
FormatterSqlMagicContribution,
|
|
21
|
+
FormatterStringTransSqlContribution,
|
|
22
|
+
SqlScript,
|
|
23
|
+
{
|
|
24
|
+
token: LibroSqlCellModelFactory,
|
|
25
|
+
useFactory: (ctx) => {
|
|
26
|
+
return (options: CellOptions) => {
|
|
27
|
+
const child = ctx.container.createChild();
|
|
28
|
+
child.register({
|
|
29
|
+
token: CellOptions,
|
|
30
|
+
useValue: options,
|
|
31
|
+
});
|
|
32
|
+
const model = child.get(LibroSqlCellModel);
|
|
33
|
+
return model;
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
.dependOn(LibroModule, LibroRenderMimeModule);
|