@difizen/libro-jupyter 0.0.2-alpha.0 → 0.1.1

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 (103) hide show
  1. package/es/add-between-cell/add-between-cell-command-contribution.js +2 -2
  2. package/es/add-between-cell/add-between-cell.js +5 -5
  3. package/es/cell/jupyter-code-cell-model.js +1 -1
  4. package/es/cell/jupyter-code-cell-view.js +6 -6
  5. package/es/command/command-contribution.js +2 -2
  6. package/es/command/keybinding-contribution.js +1 -1
  7. package/es/components/index.less +1 -0
  8. package/es/config/config-contribution.js +1 -1
  9. package/es/configuration/libro-configuration-contribution.js +1 -1
  10. package/es/contents/content-contribution.js +2 -2
  11. package/es/file/file-command.d.ts +21 -0
  12. package/es/file/file-command.d.ts.map +1 -0
  13. package/es/file/file-command.js +469 -0
  14. package/es/file/file-create-modal-contribution.d.ts +5 -0
  15. package/es/file/file-create-modal-contribution.d.ts.map +1 -0
  16. package/es/file/file-create-modal-contribution.js +23 -0
  17. package/es/file/file-create-modal.d.ts +12 -0
  18. package/es/file/file-create-modal.d.ts.map +1 -0
  19. package/es/file/file-create-modal.js +223 -0
  20. package/es/file/file-createdir-modal-contribution.d.ts +5 -0
  21. package/es/file/file-createdir-modal-contribution.d.ts.map +1 -0
  22. package/es/file/file-createdir-modal-contribution.js +23 -0
  23. package/es/file/file-createdir-modal.d.ts +9 -0
  24. package/es/file/file-createdir-modal.d.ts.map +1 -0
  25. package/es/file/file-createdir-modal.js +168 -0
  26. package/es/file/file-icon.d.ts +11 -0
  27. package/es/file/file-icon.d.ts.map +1 -0
  28. package/es/file/file-icon.js +475 -0
  29. package/es/file/file-name-alias.js +1 -1
  30. package/es/file/file-protocol.d.ts +6 -0
  31. package/es/file/file-protocol.d.ts.map +1 -1
  32. package/es/file/file-protocol.js +7 -1
  33. package/es/file/file-rename-modal-contribution.d.ts +5 -0
  34. package/es/file/file-rename-modal-contribution.d.ts.map +1 -0
  35. package/es/file/file-rename-modal-contribution.js +23 -0
  36. package/es/file/file-rename-modal.d.ts +10 -0
  37. package/es/file/file-rename-modal.d.ts.map +1 -0
  38. package/es/file/file-rename-modal.js +150 -0
  39. package/es/file/file-service.d.ts +7 -3
  40. package/es/file/file-service.d.ts.map +1 -1
  41. package/es/file/file-service.js +155 -40
  42. package/es/file/file-tree-label-provider.js +1 -1
  43. package/es/file/file-view/index.d.ts +1 -1
  44. package/es/file/file-view/index.d.ts.map +1 -1
  45. package/es/file/file-view/index.js +7 -2
  46. package/es/file/index.d.ts +3 -0
  47. package/es/file/index.d.ts.map +1 -1
  48. package/es/file/index.js +4 -1
  49. package/es/file/index.less +98 -0
  50. package/es/file/module.d.ts.map +1 -1
  51. package/es/file/module.js +5 -1
  52. package/es/file/navigatable-view.d.ts +9 -3
  53. package/es/file/navigatable-view.d.ts.map +1 -1
  54. package/es/file/navigatable-view.js +46 -13
  55. package/es/file/open-handler-contribution.js +4 -4
  56. package/es/file/utils.d.ts +2 -0
  57. package/es/file/utils.d.ts.map +1 -0
  58. package/es/file/utils.js +43 -0
  59. package/es/index.d.ts +1 -0
  60. package/es/index.d.ts.map +1 -1
  61. package/es/index.js +1 -0
  62. package/es/keybind-instructions/keybind-instructions-contribution.js +2 -2
  63. package/es/keybind-instructions/keybind-instructions-items.js +1 -1
  64. package/es/keybind-instructions/keybind-instructions-view.js +2 -2
  65. package/es/libro-jupyter-file-service.js +3 -3
  66. package/es/libro-jupyter-model.js +2 -2
  67. package/es/libro-jupyter-protocol.d.ts +0 -4
  68. package/es/libro-jupyter-protocol.d.ts.map +1 -1
  69. package/es/libro-jupyter-protocol.js +1 -2
  70. package/es/libro-jupyter-server-launch-manager.d.ts +1 -3
  71. package/es/libro-jupyter-server-launch-manager.d.ts.map +1 -1
  72. package/es/libro-jupyter-server-launch-manager.js +4 -11
  73. package/es/libro-jupyter-view.js +1 -1
  74. package/es/output/libro-jupyter-outputarea.js +3 -3
  75. package/es/rendermime/index.less +1 -10
  76. package/es/rendermime/plotly-renderers.js +4 -4
  77. package/es/rendermime/plotly-rendermime-contribution.js +1 -1
  78. package/es/theme/color-registry.js +1 -1
  79. package/es/toolbar/kernel-selector-dropdown.js +3 -3
  80. package/es/toolbar/save-file-error-contribution.js +1 -1
  81. package/es/toolbar/toolbar-contribution.js +1 -1
  82. package/package.json +16 -16
  83. package/src/components/index.less +1 -0
  84. package/src/file/file-command.tsx +325 -0
  85. package/src/file/file-create-modal-contribution.ts +10 -0
  86. package/src/file/file-create-modal.tsx +176 -0
  87. package/src/file/file-createdir-modal-contribution.ts +10 -0
  88. package/src/file/file-createdir-modal.tsx +108 -0
  89. package/src/file/file-icon.tsx +502 -0
  90. package/src/file/file-protocol.ts +17 -0
  91. package/src/file/file-rename-modal-contribution.ts +10 -0
  92. package/src/file/file-rename-modal.tsx +103 -0
  93. package/src/file/file-service.ts +69 -33
  94. package/src/file/file-view/index.tsx +4 -1
  95. package/src/file/index.less +98 -0
  96. package/src/file/index.ts +3 -0
  97. package/src/file/module.ts +8 -0
  98. package/src/file/navigatable-view.tsx +39 -3
  99. package/src/file/utils.ts +51 -0
  100. package/src/index.spec.ts +11 -0
  101. package/src/index.ts +1 -0
  102. package/src/libro-jupyter-protocol.ts +0 -5
  103. package/src/libro-jupyter-server-launch-manager.ts +2 -11
@@ -0,0 +1,325 @@
1
+ import pathUtil from 'path';
2
+
3
+ import { ReloadOutlined } from '@ant-design/icons';
4
+ import type {
5
+ CommandRegistry,
6
+ MenuPath,
7
+ MenuRegistry,
8
+ ToolbarRegistry,
9
+ } from '@difizen/mana-app';
10
+ import { ViewManager } from '@difizen/mana-app';
11
+ import {
12
+ CommandContribution,
13
+ FileStatNode,
14
+ FileTreeCommand,
15
+ inject,
16
+ MenuContribution,
17
+ ModalService,
18
+ OpenerService,
19
+ singleton,
20
+ ToolbarContribution,
21
+ URI,
22
+ } from '@difizen/mana-app';
23
+ import { message, Modal } from 'antd';
24
+
25
+ import { FileCreateModal } from './file-create-modal.js';
26
+ import { FileDirCreateModal } from './file-createdir-modal.js';
27
+ import { FileRenameModal } from './file-rename-modal.js';
28
+ import { JupyterFileService } from './file-service.js';
29
+ import { FileView } from './file-view/index.js';
30
+ import { copy2clipboard } from './utils.js';
31
+ import './index.less';
32
+
33
+ const FileCommands = {
34
+ OPEN_FILE: {
35
+ id: 'fileTree.command.openfile',
36
+ label: '打开',
37
+ },
38
+ COPY: {
39
+ id: 'fileTree.command.copy',
40
+ label: '复制',
41
+ },
42
+ PASTE: {
43
+ id: 'fileTree.command.paste',
44
+ label: '粘贴',
45
+ },
46
+ CUT: {
47
+ id: 'fileTree.command.cut',
48
+ label: '剪切',
49
+ },
50
+ RENAME: {
51
+ id: 'fileTree.command.rename',
52
+ label: '重命名',
53
+ },
54
+ COPY_PATH: {
55
+ id: 'fileTree.command.copyPath',
56
+ label: '复制路径',
57
+ },
58
+ COPY_RELATIVE_PATH: {
59
+ id: 'fileTree.command.copyRelativePath',
60
+ label: '复制相对路径',
61
+ },
62
+ CREATE_FILE: {
63
+ id: 'fileTree.command.createfile',
64
+ label: '新建文件',
65
+ },
66
+ CREATE_DIR: {
67
+ id: 'fileTree.command.createdir',
68
+ label: '新建文件夹',
69
+ },
70
+ REFRESH: {
71
+ id: 'fileTree.command.refresh',
72
+ label: '刷新',
73
+ },
74
+ };
75
+ export const FileTreeContextMenuPath: MenuPath = ['file-tree-context-menu'];
76
+
77
+ @singleton({
78
+ contrib: [CommandContribution, MenuContribution, ToolbarContribution],
79
+ })
80
+ export class FileCommandContribution
81
+ implements CommandContribution, MenuContribution, ToolbarContribution
82
+ {
83
+ protected viewManager: ViewManager;
84
+ @inject(JupyterFileService) fileService: JupyterFileService;
85
+ @inject(ModalService) modalService: ModalService;
86
+ @inject(OpenerService) protected openService: OpenerService;
87
+ fileView: FileView;
88
+ lastAction: 'COPY' | 'CUT';
89
+ lastActionNode: FileStatNode;
90
+
91
+ constructor(@inject(ViewManager) viewManager: ViewManager) {
92
+ this.viewManager = viewManager;
93
+ this.viewManager
94
+ .getOrCreateView(FileView)
95
+ .then((view) => {
96
+ this.fileView = view;
97
+ return;
98
+ })
99
+ .catch(() => {
100
+ //
101
+ });
102
+ }
103
+
104
+ registerMenus(menu: MenuRegistry) {
105
+ menu.registerMenuAction(FileTreeContextMenuPath, {
106
+ id: FileCommands.CREATE_FILE.id,
107
+ command: FileCommands.CREATE_FILE.id,
108
+ order: 'a',
109
+ });
110
+ menu.registerMenuAction(FileTreeContextMenuPath, {
111
+ id: FileCommands.CREATE_DIR.id,
112
+ command: FileCommands.CREATE_DIR.id,
113
+ order: 'a',
114
+ });
115
+ menu.registerMenuAction(FileTreeContextMenuPath, {
116
+ id: FileCommands.OPEN_FILE.id,
117
+ command: FileCommands.OPEN_FILE.id,
118
+ order: 'a',
119
+ });
120
+ menu.registerMenuAction(FileTreeContextMenuPath, {
121
+ id: FileCommands.COPY.id,
122
+ command: FileCommands.COPY.id,
123
+ order: 'b',
124
+ });
125
+ menu.registerMenuAction(FileTreeContextMenuPath, {
126
+ id: FileCommands.PASTE.id,
127
+ command: FileCommands.PASTE.id,
128
+ order: 'c',
129
+ });
130
+ menu.registerMenuAction(FileTreeContextMenuPath, {
131
+ id: FileCommands.CUT.id,
132
+ command: FileCommands.CUT.id,
133
+ order: 'd',
134
+ });
135
+ menu.registerMenuAction(FileTreeContextMenuPath, {
136
+ id: FileCommands.RENAME.id,
137
+ command: FileCommands.RENAME.id,
138
+ order: 'e',
139
+ });
140
+ menu.registerMenuAction(FileTreeContextMenuPath, {
141
+ id: FileCommands.COPY_PATH.id,
142
+ command: FileCommands.COPY_PATH.id,
143
+ order: 'g',
144
+ });
145
+ menu.registerMenuAction(FileTreeContextMenuPath, {
146
+ id: FileCommands.COPY_RELATIVE_PATH.id,
147
+ command: FileCommands.COPY_RELATIVE_PATH.id,
148
+ order: 'g',
149
+ });
150
+ }
151
+ registerCommands(command: CommandRegistry): void {
152
+ command.registerCommand(FileCommands.OPEN_FILE, {
153
+ execute: (node) => {
154
+ try {
155
+ if (node.fileStat.isFile) {
156
+ this.openService
157
+ .getOpener(node.uri)
158
+ .then((opener) => {
159
+ if (opener) {
160
+ opener.open(node.uri, {
161
+ viewOptions: {
162
+ name: node.fileStat.name,
163
+ },
164
+ });
165
+ }
166
+ return;
167
+ })
168
+ .catch(() => {
169
+ throw Error();
170
+ });
171
+ }
172
+ } catch {
173
+ message.error('文件打开失败');
174
+ }
175
+ },
176
+ isVisible: (node) => {
177
+ return FileStatNode.is(node) && node.fileStat.isFile;
178
+ },
179
+ });
180
+ command.registerHandler(FileTreeCommand.REMOVE.id, {
181
+ execute: (node) => {
182
+ if (FileStatNode.is(node)) {
183
+ const filePath = node.uri.path.toString();
184
+ Modal.confirm({
185
+ width: 424,
186
+ title: '确认要删除这个文件/文件夹吗?',
187
+ content: `请确认是否删除文件 ${filePath} ,删除后将不可恢复,请谨慎操作。`,
188
+ wrapClassName: 'libro-remove-file-modal',
189
+ cancelText: '取消',
190
+ okText: '确定',
191
+ onOk: async () => {
192
+ try {
193
+ await this.fileService.delete(node.uri);
194
+ } catch {
195
+ message.error('删除文件失败!');
196
+ }
197
+ this.fileView.model.refresh();
198
+ },
199
+ });
200
+ }
201
+ },
202
+ isVisible: (node) => {
203
+ return FileStatNode.is(node);
204
+ },
205
+ });
206
+ command.registerCommand(FileCommands.COPY, {
207
+ execute: (node) => {
208
+ this.lastAction = 'COPY';
209
+ this.lastActionNode = node;
210
+ },
211
+ isVisible: (node) => {
212
+ return FileStatNode.is(node) && node.fileStat.isFile;
213
+ },
214
+ });
215
+ command.registerCommand(FileCommands.CUT, {
216
+ execute: (node) => {
217
+ this.lastAction = 'CUT';
218
+ this.lastActionNode = node;
219
+ },
220
+ isVisible: (node) => {
221
+ return FileStatNode.is(node) && node.fileStat.isFile;
222
+ },
223
+ });
224
+ command.registerCommand(FileCommands.PASTE, {
225
+ execute: async (data) => {
226
+ try {
227
+ if (FileStatNode.is(data)) {
228
+ const targetUri = data.fileStat.isDirectory ? data.uri : data.uri.parent;
229
+ await this.fileService.copy(this.lastActionNode.uri, targetUri);
230
+ } else if (data instanceof FileView) {
231
+ const targetPath = '/';
232
+ await this.fileService.copy(this.lastActionNode.uri, new URI(targetPath));
233
+ }
234
+ if (this.lastAction === 'CUT') {
235
+ await this.fileService.delete(this.lastActionNode.uri);
236
+ }
237
+ this.fileView.model.refresh();
238
+ return;
239
+ } catch {
240
+ message.error('粘贴失败!');
241
+ }
242
+ },
243
+ isVisible: () => {
244
+ return this.lastAction === 'CUT' || this.lastAction === 'COPY';
245
+ },
246
+ });
247
+ command.registerCommand(FileCommands.RENAME, {
248
+ execute: async (node) => {
249
+ this.modalService.openModal(FileRenameModal, {
250
+ resource: node.uri,
251
+ fileName: node.uri.path.base,
252
+ });
253
+ },
254
+ isVisible: (node) => {
255
+ return FileStatNode.is(node);
256
+ },
257
+ });
258
+ command.registerCommand(FileCommands.CREATE_FILE, {
259
+ execute: async (data) => {
260
+ let path = this.fileView.model.location?.path.toString() || '/';
261
+ if (FileStatNode.is(data)) {
262
+ path = data.fileStat.isDirectory
263
+ ? data.uri.path.toString()
264
+ : data.uri.path.dir.toString();
265
+ }
266
+ this.modalService.openModal(FileCreateModal, {
267
+ path,
268
+ });
269
+ },
270
+ });
271
+ command.registerCommand(FileCommands.CREATE_DIR, {
272
+ execute: async (data) => {
273
+ let path = this.fileView.model.location?.path.toString() || '/';
274
+ if (FileStatNode.is(data)) {
275
+ path = data.fileStat.isDirectory
276
+ ? data.uri.path.toString()
277
+ : data.uri.path.dir.toString();
278
+ }
279
+ this.modalService.openModal(FileDirCreateModal, {
280
+ path,
281
+ });
282
+ },
283
+ });
284
+
285
+ command.registerCommand(FileCommands.COPY_PATH, {
286
+ execute: async (data) => {
287
+ let path = this.fileView.model.location?.path.toString() || '/';
288
+ if (FileStatNode.is(data)) {
289
+ path = data.uri.path.toString();
290
+ }
291
+ copy2clipboard(path);
292
+ },
293
+ });
294
+
295
+ command.registerCommand(FileCommands.COPY_RELATIVE_PATH, {
296
+ execute: async (data) => {
297
+ let relative = '';
298
+ if (FileStatNode.is(data)) {
299
+ relative = pathUtil.relative('/', data.uri.path.toString());
300
+ }
301
+ copy2clipboard(relative);
302
+ },
303
+ });
304
+
305
+ command.registerCommand(FileCommands.REFRESH, {
306
+ execute: async (view) => {
307
+ if (view instanceof FileView) {
308
+ view.model.refresh();
309
+ }
310
+ },
311
+ isVisible: (view) => {
312
+ return view instanceof FileView;
313
+ },
314
+ });
315
+ }
316
+
317
+ registerToolbarItems(toolbarRegistry: ToolbarRegistry): void {
318
+ toolbarRegistry.registerItem({
319
+ id: FileCommands.REFRESH.id,
320
+ command: FileCommands.REFRESH.id,
321
+ icon: <ReloadOutlined />,
322
+ tooltip: '刷新',
323
+ });
324
+ }
325
+ }
@@ -0,0 +1,10 @@
1
+ import { ModalContribution, singleton } from '@difizen/mana-app';
2
+
3
+ import { FileCreateModal } from './file-create-modal.js';
4
+
5
+ @singleton({ contrib: ModalContribution })
6
+ export class FileCreateModalContribution implements ModalContribution {
7
+ registerModal() {
8
+ return FileCreateModal;
9
+ }
10
+ }
@@ -0,0 +1,176 @@
1
+ import type { ModalItemProps, ModalItem } from '@difizen/mana-app';
2
+ import { URI, useInject, ViewManager } from '@difizen/mana-app';
3
+ import { Col, Form, message, Row, Input, Modal } from 'antd';
4
+ import type { InputRef } from 'antd';
5
+ import { useEffect, useRef, useState } from 'react';
6
+ import './index.less';
7
+
8
+ import {
9
+ FileView,
10
+ JSONIcon,
11
+ JupyterFileService,
12
+ MoreIcon,
13
+ NotebookIcon,
14
+ PythonIcon,
15
+ } from './index.js';
16
+
17
+ export interface ModalItemType {
18
+ path: string;
19
+ fileType?: FileType;
20
+ }
21
+
22
+ type FileType = '.ipynb' | '.py' | '.json' | '.sql' | undefined;
23
+
24
+ export const FileCreateModalComponent: React.FC<ModalItemProps<ModalItemType>> = ({
25
+ visible,
26
+ close,
27
+ data,
28
+ }: ModalItemProps<ModalItemType>) => {
29
+ const fileService = useInject(JupyterFileService);
30
+ const viewManager = useInject(ViewManager);
31
+ const [fileType, setFileType] = useState<FileType>(data.fileType);
32
+ const [fileView, setFileView] = useState<FileView>();
33
+ const inputRef = useRef<InputRef>(null);
34
+ const [form] = Form.useForm();
35
+
36
+ const onFinish = async (values: { fileName: string }) => {
37
+ await form.validateFields();
38
+ close();
39
+ try {
40
+ await fileService.newFile(values.fileName + fileType || '', new URI(data.path));
41
+ if (fileView) {
42
+ fileView.model.refresh();
43
+ }
44
+ } catch {
45
+ message.error('新建文件失败');
46
+ }
47
+ };
48
+
49
+ const validateFileName = async (rule: any, value: string, callback: any) => {
50
+ if (!value || !value.length) {
51
+ throw new Error('请输入文件名');
52
+ } else {
53
+ const targetURI = new URI(data.path + value + (fileType || ''));
54
+ const fileRes = await fileService.resolve(targetURI);
55
+ if (fileRes.isFile) {
56
+ throw new Error('文件名称已存在,请重新输入');
57
+ }
58
+ }
59
+ };
60
+
61
+ useEffect(() => {
62
+ viewManager
63
+ .getOrCreateView(FileView)
64
+ .then((view) => {
65
+ setFileView(view);
66
+ return;
67
+ })
68
+ .catch(() => {
69
+ //
70
+ });
71
+ inputRef.current?.focus();
72
+ });
73
+ return (
74
+ <Modal
75
+ title="新建文件"
76
+ open={visible}
77
+ onCancel={close}
78
+ width={788}
79
+ cancelText="取消"
80
+ okText="确定"
81
+ onOk={() => {
82
+ form.submit();
83
+ }}
84
+ keyboard={true}
85
+ wrapClassName="libro-create-file-modal"
86
+ >
87
+ <div className="libro-create-file-path-container">
88
+ <div className="libro-create-file-des">创建位置:</div>
89
+ <span className="libro-create-file-path">{data.path}</span>
90
+ </div>
91
+ <div className="libro-create-file-des">文件类型:</div>
92
+ <Row>
93
+ <Col
94
+ className="gutter-row"
95
+ style={{ paddingLeft: 'unset', paddingRight: '16px' }}
96
+ >
97
+ <div
98
+ className={`libro-create-file-type ${
99
+ fileType === '.ipynb' ? 'active' : ''
100
+ }`}
101
+ onClick={() => {
102
+ setFileType('.ipynb');
103
+ }}
104
+ >
105
+ <NotebookIcon />
106
+ <span className="libro-create-file-type-text">Notebook</span>
107
+ </div>
108
+ </Col>
109
+ <Col
110
+ className="gutter-row"
111
+ style={{ paddingLeft: 'unset', paddingRight: '16px' }}
112
+ >
113
+ <div
114
+ className={`libro-create-file-type ${fileType === '.py' ? 'active' : ''}`}
115
+ onClick={() => {
116
+ setFileType('.py');
117
+ }}
118
+ >
119
+ <PythonIcon />
120
+ <span className="libro-create-file-type-text">Python</span>
121
+ </div>
122
+ </Col>
123
+ <Col
124
+ className="gutter-row"
125
+ style={{ paddingLeft: 'unset', paddingRight: '16px' }}
126
+ >
127
+ <div
128
+ className={`libro-create-file-type ${fileType === '.json' ? 'active' : ''}`}
129
+ onClick={() => {
130
+ setFileType('.json');
131
+ }}
132
+ >
133
+ <JSONIcon />
134
+ <span className="libro-create-file-type-text">JSON</span>
135
+ </div>
136
+ </Col>
137
+ <Col
138
+ className="gutter-row"
139
+ style={{ paddingLeft: 'unset', paddingRight: '16px' }}
140
+ >
141
+ <div
142
+ className={`libro-create-file-type ${
143
+ fileType === undefined ? 'active' : ''
144
+ }`}
145
+ onClick={() => {
146
+ setFileType(undefined);
147
+ }}
148
+ >
149
+ <MoreIcon />
150
+ <span className="libro-create-file-type-text">其他</span>
151
+ </div>
152
+ </Col>
153
+ </Row>
154
+ <Form
155
+ layout="vertical"
156
+ autoComplete="off"
157
+ form={form}
158
+ onFinish={onFinish}
159
+ className="libro-create-file-form"
160
+ >
161
+ <Form.Item
162
+ name="fileName"
163
+ label="文件名称"
164
+ rules={[{ required: true, validator: validateFileName }]}
165
+ >
166
+ <Input addonAfter={fileType || ''} />
167
+ </Form.Item>
168
+ </Form>
169
+ </Modal>
170
+ );
171
+ };
172
+
173
+ export const FileCreateModal: ModalItem<ModalItemType> = {
174
+ id: 'file.create.modal',
175
+ component: FileCreateModalComponent,
176
+ };
@@ -0,0 +1,10 @@
1
+ import { ModalContribution, singleton } from '@difizen/mana-app';
2
+
3
+ import { FileDirCreateModal } from './file-createdir-modal.js';
4
+
5
+ @singleton({ contrib: ModalContribution })
6
+ export class FileCreateDirModalContribution implements ModalContribution {
7
+ registerModal() {
8
+ return FileDirCreateModal;
9
+ }
10
+ }
@@ -0,0 +1,108 @@
1
+ import type { ModalItem, ModalItemProps } from '@difizen/mana-app';
2
+ import { URI } from '@difizen/mana-app';
3
+ import { ViewManager } from '@difizen/mana-app';
4
+ import { useInject } from '@difizen/mana-app';
5
+ import { Form, message, Input, Modal } from 'antd';
6
+ import type { InputRef } from 'antd';
7
+ import { useEffect, useRef, useState } from 'react';
8
+
9
+ import { JupyterFileService } from './file-service.js';
10
+ import { FileView } from './file-view/index.js';
11
+ import './index.less';
12
+
13
+ export interface FileDirCreateModalItemType {
14
+ path: string;
15
+ }
16
+
17
+ export const FileCreateDirModalComponent: React.FC<
18
+ ModalItemProps<FileDirCreateModalItemType>
19
+ > = ({ visible, close, data }: ModalItemProps<FileDirCreateModalItemType>) => {
20
+ const fileService = useInject(JupyterFileService);
21
+ const viewManager = useInject(ViewManager);
22
+ const inputRef = useRef<InputRef>(null);
23
+ const [fileView, setFileView] = useState<FileView>();
24
+ const [form] = Form.useForm();
25
+
26
+ const onFinish = async (values: { dirName: string }) => {
27
+ await form.validateFields();
28
+ close();
29
+ try {
30
+ await fileService.newFileDir(values.dirName, new URI(data.path));
31
+ if (fileView) {
32
+ fileView.model.refresh();
33
+ }
34
+ } catch {
35
+ message.error('新建文件夹失败');
36
+ }
37
+ };
38
+
39
+ const validateDirName = async (rule: any, value: string, callback: any) => {
40
+ if (!value || !value.length) {
41
+ throw new Error('请输入文件夹名');
42
+ } else {
43
+ const targetURI = new URI(data.path + value);
44
+ const fileRes = await fileService.resolve(targetURI);
45
+ if (fileRes.isDirectory) {
46
+ throw new Error('文件夹名称已存在,请重新输入');
47
+ }
48
+ }
49
+ };
50
+
51
+ useEffect(() => {
52
+ viewManager
53
+ .getOrCreateView(FileView)
54
+ .then((view) => {
55
+ setFileView(view);
56
+ return;
57
+ })
58
+ .catch(() => {
59
+ //
60
+ });
61
+ inputRef.current?.focus();
62
+ });
63
+ return (
64
+ <Modal
65
+ title="新建文件夹"
66
+ open={visible}
67
+ onCancel={close}
68
+ cancelText="取消"
69
+ okText="确定"
70
+ onOk={() => {
71
+ form.submit();
72
+ }}
73
+ keyboard={true}
74
+ wrapClassName="libro-create-dir-modal"
75
+ width={524}
76
+ >
77
+ <div className="libro-create-file-des">创建位置:</div>
78
+ <span className="libro-create-file-path">{data.path}</span>
79
+ <Form
80
+ layout="vertical"
81
+ autoComplete="off"
82
+ form={form}
83
+ onFinish={onFinish}
84
+ className="libro-create-dir-file-form"
85
+ >
86
+ <Form.Item
87
+ name="dirName"
88
+ label="文件夹名称"
89
+ rules={[{ required: true, validator: validateDirName }]}
90
+ >
91
+ <Input
92
+ ref={inputRef}
93
+ onKeyDown={async (e) => {
94
+ if (e.keyCode === 13) {
95
+ form.submit();
96
+ }
97
+ }}
98
+ />
99
+ </Form.Item>
100
+ </Form>
101
+ </Modal>
102
+ );
103
+ };
104
+
105
+ export const FileDirCreateModal: ModalItem<FileDirCreateModalItemType> = {
106
+ id: 'file.createdir.modal',
107
+ component: FileCreateDirModalComponent,
108
+ };