@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
@@ -5,3 +5,20 @@ export enum FileType {
5
5
  SymbolicLink = 64,
6
6
  }
7
7
  export type DirItem = [string, FileType];
8
+
9
+ export interface EditorView {
10
+ dirty: boolean;
11
+ }
12
+
13
+ export const EditorView = {
14
+ is: (data?: Record<string, any>): data is EditorView => {
15
+ return (
16
+ !!data &&
17
+ typeof data === 'object' &&
18
+ 'id' in data &&
19
+ 'view' in data &&
20
+ 'dirty' in data &&
21
+ typeof data['view'] === 'function'
22
+ );
23
+ },
24
+ };
@@ -0,0 +1,10 @@
1
+ import { ModalContribution, singleton } from '@difizen/mana-app';
2
+
3
+ import { FileRenameModal } from './file-rename-modal.js';
4
+
5
+ @singleton({ contrib: ModalContribution })
6
+ export class FileRenameModalContribution implements ModalContribution {
7
+ registerModal() {
8
+ return FileRenameModal;
9
+ }
10
+ }
@@ -0,0 +1,103 @@
1
+ import { useInject, ViewManager } from '@difizen/mana-app';
2
+ import type { ModalItem, ModalItemProps, URI } from '@difizen/mana-app';
3
+ import { Form, message, Input, Modal } from 'antd';
4
+ import type { InputRef } from 'antd';
5
+ import { useEffect, useRef, useState } from 'react';
6
+
7
+ import { JupyterFileService } from './file-service.js';
8
+ import { FileView } from './file-view/index.js';
9
+ import './index.less';
10
+
11
+ export interface ModalItemType {
12
+ resource: URI;
13
+ fileName: string;
14
+ }
15
+
16
+ export const FileRenameModalComponent: React.FC<ModalItemProps<ModalItemType>> = ({
17
+ visible,
18
+ close,
19
+ data,
20
+ }: ModalItemProps<ModalItemType>) => {
21
+ const fileService = useInject(JupyterFileService);
22
+ const viewManager = useInject(ViewManager);
23
+ const inputRef = useRef<InputRef>(null);
24
+ const [form] = Form.useForm();
25
+ const [fileView, setFileView] = useState<FileView>();
26
+ useEffect(() => {
27
+ viewManager
28
+ .getOrCreateView(FileView)
29
+ .then((view) => {
30
+ setFileView(view);
31
+ return;
32
+ })
33
+ .catch(() => {
34
+ //
35
+ });
36
+ inputRef.current?.focus();
37
+ });
38
+
39
+ const onFinish = async (values: { rename: string }) => {
40
+ await form.validateFields();
41
+ close();
42
+ try {
43
+ await fileService.rename(data.resource, values.rename);
44
+
45
+ if (fileView) {
46
+ fileView.model.refresh();
47
+ }
48
+ } catch {
49
+ message.error('重命名文件/文件夹失败');
50
+ }
51
+ };
52
+
53
+ const validateRename = async (rule: any, value: string, callback: any) => {
54
+ if (!value || !value.length) {
55
+ throw new Error('请输入文件夹名');
56
+ } else {
57
+ if (value === data.fileName) {
58
+ throw new Error('文件/文件夹名称已存在,请重新输入');
59
+ }
60
+ }
61
+ };
62
+
63
+ return (
64
+ <Modal
65
+ title="文件重命名"
66
+ open={visible}
67
+ onCancel={close}
68
+ onOk={() => {
69
+ form.submit();
70
+ }}
71
+ wrapClassName="libro-rename-file-modal"
72
+ >
73
+ <Form
74
+ layout="vertical"
75
+ autoComplete="off"
76
+ form={form}
77
+ onFinish={onFinish}
78
+ className="libro-rename-file-form"
79
+ >
80
+ <Form.Item
81
+ name="rename"
82
+ label="文件/文件夹名称"
83
+ rules={[{ required: true, validator: validateRename }]}
84
+ initialValue={data.fileName}
85
+ >
86
+ <Input
87
+ ref={inputRef}
88
+ onKeyDown={async (e) => {
89
+ if (e.keyCode === 13) {
90
+ form.submit();
91
+ }
92
+ }}
93
+ />
94
+ </Form.Item>
95
+ </Form>
96
+ </Modal>
97
+ );
98
+ };
99
+
100
+ export const FileRenameModal: ModalItem<ModalItemType> = {
101
+ id: 'file.rename.modal',
102
+ component: FileRenameModalComponent,
103
+ };
@@ -1,3 +1,5 @@
1
+ import pathUtil from 'path';
2
+
1
3
  import type { IContentsModel } from '@difizen/libro-kernel';
2
4
  import { ContentsManager } from '@difizen/libro-kernel';
3
5
  import type {
@@ -7,6 +9,7 @@ import type {
7
9
  ResolveFileOptions,
8
10
  } from '@difizen/mana-app';
9
11
  import { FileService, URI, inject, singleton } from '@difizen/mana-app';
12
+ import { message } from 'antd';
10
13
 
11
14
  import { FileNameAlias } from './file-name-alias.js';
12
15
  import type { DirItem } from './file-protocol.js';
@@ -50,41 +53,29 @@ export class JupyterFileService extends FileService {
50
53
  }
51
54
 
52
55
  async write(filePath: string, content: string): Promise<string | undefined> {
53
- try {
54
- await this.contentsManager.save(filePath, {
55
- content,
56
- });
57
- return filePath;
58
- } catch (_e) {
59
- //
60
- }
61
- return undefined;
56
+ await this.contentsManager.save(filePath, {
57
+ content,
58
+ });
59
+ return filePath;
62
60
  }
63
61
 
64
62
  async readDir(dirPath: string): Promise<DirItem[]> {
65
63
  let children: DirItem[] = [];
66
- try {
67
- const res = await this.contentsManager.get(dirPath, { type: 'directory' });
68
- if (res && this.isDirectory(res)) {
69
- const content = res.content;
70
- children = content.map((item) => {
71
- return [item.path, this.isDirectory(item) ? 2 : 1];
72
- });
73
- }
74
- } catch (_e) {
75
- //
64
+ const res = await this.contentsManager.get(dirPath, { type: 'directory' });
65
+ if (res && this.isDirectory(res)) {
66
+ const content = res.content;
67
+ children = content.map((item) => {
68
+ return [item.path, this.isDirectory(item) ? 2 : 1];
69
+ });
76
70
  }
77
71
  return children;
78
72
  }
73
+
79
74
  async read(filePath: string): Promise<string | undefined> {
80
75
  let content: string | undefined = undefined;
81
- try {
82
- const res = await this.contentsManager.get(filePath);
83
- if (res && !this.isDirectory(res)) {
84
- content = res.content as string;
85
- }
86
- } catch (_e) {
87
- //
76
+ const res = await this.contentsManager.get(filePath);
77
+ if (res && !this.isDirectory(res)) {
78
+ content = res.content as string;
88
79
  }
89
80
  return content;
90
81
  }
@@ -94,7 +85,7 @@ export class JupyterFileService extends FileService {
94
85
  try {
95
86
  const res = await this.contentsManager.get(filePath);
96
87
  stat = this.toFileMeta(res);
97
- } catch (_e) {
88
+ } catch {
98
89
  //
99
90
  }
100
91
  return stat;
@@ -132,22 +123,22 @@ export class JupyterFileService extends FileService {
132
123
 
133
124
  override async copy(
134
125
  source: URI,
135
- target: URI,
136
- options?: CopyFileOptions,
126
+ _target: URI,
127
+ _options?: CopyFileOptions,
137
128
  ): Promise<FileStatWithMetadata> {
129
+ await this.contentsManager.copy(source.path.toString(), _target.path.toString());
138
130
  return this.resolve(source);
139
131
  }
140
132
  override async move(
141
133
  source: URI,
142
- target: URI,
143
- options?: MoveFileOptions,
134
+ _target: URI,
135
+ _options?: MoveFileOptions,
144
136
  ): Promise<FileStatWithMetadata> {
145
137
  return this.resolve(source);
146
138
  }
147
139
 
148
140
  toFileStatMeta(meta: FileMeta): FileStatWithMetadata {
149
141
  const uri = URI.withScheme(new URI(meta.resource), 'file');
150
-
151
142
  return {
152
143
  ...meta,
153
144
  resource: uri,
@@ -158,7 +149,7 @@ export class JupyterFileService extends FileService {
158
149
 
159
150
  override async resolve(
160
151
  resource: URI,
161
- options?: ResolveFileOptions | undefined,
152
+ _options?: ResolveFileOptions | undefined,
162
153
  ): Promise<FileStatWithMetadata> {
163
154
  const resolved = await this.doResolve(resource.path.toString());
164
155
  if (resolved) {
@@ -176,4 +167,49 @@ export class JupyterFileService extends FileService {
176
167
  isSymbolicLink: false,
177
168
  };
178
169
  }
170
+
171
+ async delete(
172
+ resource: URI,
173
+ _options?: ResolveFileOptions | undefined,
174
+ ): Promise<FileStatWithMetadata> {
175
+ await this.contentsManager.delete(resource.path.toString());
176
+ return this.resolve(resource);
177
+ }
178
+
179
+ async rename(resource: URI, newName: string): Promise<FileStatWithMetadata> {
180
+ const newPath = pathUtil.join(resource.path.dir.toString(), newName);
181
+ await this.contentsManager.rename(resource.path.toString(), newPath);
182
+
183
+ return this.resolve(resource);
184
+ }
185
+
186
+ async newFile(fileName: string, target: URI): Promise<FileStatWithMetadata> {
187
+ const targetFileUri = new URI(pathUtil.join(target.path.toString(), fileName));
188
+ if ((await this.resolve(targetFileUri)).isFile) {
189
+ message.error('文件名重复');
190
+ return this.resolve(target);
191
+ }
192
+ const fileNameArr = fileName.split('.');
193
+ const ext = fileNameArr[fileNameArr.length - 1];
194
+ const res = await this.contentsManager.newUntitled({
195
+ path: target.path.toString(),
196
+ ext,
197
+ });
198
+ await this.rename(new URI(res.path.toString()), fileName);
199
+ return this.resolve(target);
200
+ }
201
+
202
+ async newFileDir(dirName: string, target: URI): Promise<FileStatWithMetadata> {
203
+ const targetFileUri = new URI(pathUtil.join(target.path.toString(), dirName));
204
+ if ((await this.resolve(targetFileUri)).isDirectory) {
205
+ message.error('文件夹重复');
206
+ return this.resolve(target);
207
+ }
208
+ const res = await this.contentsManager.newUntitled({
209
+ path: target.path.toString(),
210
+ type: 'directory',
211
+ });
212
+ await this.rename(new URI(res.path.toString()), dirName);
213
+ return this.resolve(target);
214
+ }
179
215
  }
@@ -1,3 +1,4 @@
1
+ import { FolderFilled } from '@ant-design/icons';
1
2
  import type { TreeNode, ViewOpenHandler } from '@difizen/mana-app';
2
3
  import { FileTreeViewFactory } from '@difizen/mana-app';
3
4
  import {
@@ -19,7 +20,7 @@ import {
19
20
  inject,
20
21
  singleton,
21
22
  } from '@difizen/mana-app';
22
- import type React from 'react';
23
+ import React from 'react';
23
24
 
24
25
  import type { LibroNavigatableView } from '../navigatable-view.js';
25
26
 
@@ -53,6 +54,8 @@ export class FileView extends FileTreeView {
53
54
  labelProvider,
54
55
  decoratorService,
55
56
  );
57
+ this.title.label = '文件导航';
58
+ this.title.icon = <FolderFilled />;
56
59
  this.toDispose.push(this.model.onOpenNode(this.openNode));
57
60
  }
58
61
 
@@ -0,0 +1,98 @@
1
+ .libro-create-file-type {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ flex-direction: column;
6
+ margin-bottom: 24px;
7
+ height: 100px;
8
+ width: 100px;
9
+ border: 1px solid rgba(0, 10, 26, 10%);
10
+ border-radius: 4px;
11
+ cursor: pointer;
12
+
13
+ &.active {
14
+ border: 1px solid #1677ff;
15
+ background-color: rgba(22, 122, 255, 9%);
16
+ }
17
+
18
+ &.active::before {
19
+ content: '';
20
+ position: absolute;
21
+ top: 2px;
22
+ right: 18px;
23
+ height: 0;
24
+ width: 0;
25
+ border-top: 12px solid #1677ff;
26
+ border-left: 12px solid transparent;
27
+ }
28
+ }
29
+
30
+ .libro-create-file-modal,
31
+ .libro-create-dir-modal,
32
+ .libro-rename-file-modal {
33
+ .ant-modal-content {
34
+ padding: unset;
35
+ }
36
+
37
+ .ant-modal-body {
38
+ padding: 20px 24px;
39
+ }
40
+
41
+ .ant-modal-header {
42
+ padding: 16px 24px;
43
+ border-bottom: 1px solid rgba(0, 10, 26, 7%);
44
+ margin: unset;
45
+ }
46
+
47
+ .ant-modal-footer {
48
+ margin: unset;
49
+ padding: 10px 24px;
50
+ border-top: 1px solid rgba(0, 10, 26, 7%);
51
+ }
52
+
53
+ .ant-modal-title {
54
+ font-weight: 500;
55
+ color: rgba(0, 10, 26, 89%);
56
+ }
57
+ }
58
+
59
+ .libro-remove-file-modal {
60
+ .ant-modal-confirm-title {
61
+ font-weight: 500;
62
+ color: rgba(0, 0, 0, 88%);
63
+ }
64
+ }
65
+
66
+ .libro-create-file-modal {
67
+ .ant-form-item-control-input {
68
+ width: 370px;
69
+ }
70
+ }
71
+
72
+ .libro-create-dir-modal,
73
+ .libro-rename-file-modal {
74
+ .ant-form-item-control-input {
75
+ width: 316px;
76
+ }
77
+ }
78
+
79
+ .libro-create-file-path-container {
80
+ margin-bottom: 8px;
81
+ }
82
+
83
+ .libro-create-file-des {
84
+ margin-bottom: 8px;
85
+ display: inline-block;
86
+ color: rgba(0, 0, 0, 85%);
87
+ letter-spacing: 0;
88
+ line-height: 22px;
89
+ }
90
+
91
+ .libro-create-file-path {
92
+ color: rgba(0, 0, 0, 65%);
93
+ }
94
+
95
+ .libro-create-file-type-text {
96
+ margin-top: 8px;
97
+ color: rgba(0, 10, 26, 68%);
98
+ }
package/src/file/index.ts CHANGED
@@ -4,3 +4,6 @@ export * from './navigatable-view.js';
4
4
  export * from './open-handler-contribution.js';
5
5
  export * from './file-service.js';
6
6
  export * from './file-protocol.js';
7
+ export * from './file-icon.js';
8
+ export * from './file-create-modal.js';
9
+ export * from './file-createdir-modal.js';
@@ -1,6 +1,10 @@
1
1
  import { FileTreeModule, ManaModule } from '@difizen/mana-app';
2
2
 
3
+ import { FileCommandContribution } from './file-command.js';
4
+ import { FileCreateModalContribution } from './file-create-modal-contribution.js';
5
+ import { FileCreateDirModalContribution } from './file-createdir-modal-contribution.js';
3
6
  import { FileNameAlias } from './file-name-alias.js';
7
+ import { FileRenameModalContribution } from './file-rename-modal-contribution.js';
4
8
  import { JupyterFileService } from './file-service.js';
5
9
  import { FileTreeLabelProvider } from './file-tree-label-provider.js';
6
10
  import { FileView } from './file-view/index.js';
@@ -15,5 +19,9 @@ export const LibroJupyterFileModule = ManaModule.create()
15
19
  FileTreeLabelProvider,
16
20
  LibroNavigatableView,
17
21
  LibroJupyterOpenHandler,
22
+ FileCommandContribution,
23
+ FileCreateModalContribution,
24
+ FileCreateDirModalContribution,
25
+ FileRenameModalContribution,
18
26
  )
19
27
  .dependOn(FileTreeModule);
@@ -1,6 +1,6 @@
1
1
  import type { LibroView } from '@difizen/libro-core';
2
- import { LibroService } from '@difizen/libro-core';
3
- import type { NavigatableView } from '@difizen/mana-app';
2
+ import { LibroService, DocumentCommands } from '@difizen/libro-core';
3
+ import type { NavigatableView, Saveable } from '@difizen/mana-app';
4
4
  import {
5
5
  BaseView,
6
6
  inject,
@@ -16,6 +16,8 @@ import {
16
16
  ViewRender,
17
17
  Deferred,
18
18
  URI,
19
+ CommandRegistry,
20
+ Emitter,
19
21
  } from '@difizen/mana-app';
20
22
  import { createRef, forwardRef } from 'react';
21
23
 
@@ -32,15 +34,31 @@ export const LibroEditorComponent = forwardRef(function LibroEditorComponent() {
32
34
  export const LibroNavigatableViewFactoryId = 'libro-navigatable-view-factory';
33
35
  @transient()
34
36
  @view(LibroNavigatableViewFactoryId)
35
- export class LibroNavigatableView extends BaseView implements NavigatableView {
37
+ export class LibroNavigatableView
38
+ extends BaseView
39
+ implements NavigatableView, Saveable
40
+ {
36
41
  @inject(LibroService) protected libroService: LibroService;
37
42
 
43
+ @inject(CommandRegistry) commandRegistry: CommandRegistry;
44
+
38
45
  override view = LibroEditorComponent;
39
46
 
40
47
  codeRef = createRef<HTMLDivElement>();
41
48
 
42
49
  @prop() filePath?: string;
43
50
 
51
+ dirtyEmitter = new Emitter<void>();
52
+
53
+ get onDirtyChanged() {
54
+ return this.dirtyEmitter.event;
55
+ }
56
+
57
+ readonly autoSave = 'off';
58
+
59
+ @prop()
60
+ dirty: boolean;
61
+
44
62
  @prop()
45
63
  libroView?: LibroView;
46
64
 
@@ -56,6 +74,7 @@ export class LibroNavigatableView extends BaseView implements NavigatableView {
56
74
  ) {
57
75
  super();
58
76
  this.filePath = options.path;
77
+ this.dirty = false;
59
78
  this.title.caption = options.path;
60
79
  const uri = new URI(options.path);
61
80
  const uriRef = URIIconReference.create('file', new VScodeURI(options.path));
@@ -68,6 +87,14 @@ export class LibroNavigatableView extends BaseView implements NavigatableView {
68
87
  this.getOrCreateLibroView();
69
88
  }
70
89
 
90
+ save = () => {
91
+ this.commandRegistry.executeCommand(
92
+ DocumentCommands['Save'].id,
93
+ undefined,
94
+ this.libroView,
95
+ );
96
+ };
97
+
71
98
  protected async getOrCreateLibroView() {
72
99
  const libroView = await this.libroService.getOrCreateView({
73
100
  id: this.filePath,
@@ -77,7 +104,16 @@ export class LibroNavigatableView extends BaseView implements NavigatableView {
77
104
  return;
78
105
  }
79
106
  this.libroView = libroView;
107
+ this.libroView.model.onContentChanged(() => {
108
+ this.dirty = true;
109
+ this.dirtyEmitter.fire();
110
+ });
111
+ this.libroView.onSave(() => {
112
+ this.dirty = false;
113
+ this.dirtyEmitter.fire();
114
+ });
80
115
  await this.libroView.initialized;
116
+ this.libroView.focus();
81
117
  this.defer.resolve();
82
118
  }
83
119
 
@@ -0,0 +1,51 @@
1
+ import { message } from 'antd';
2
+
3
+ function copyFallback(string: string) {
4
+ function handler(event: ClipboardEvent) {
5
+ const clipboardData = event.clipboardData || (window as any).clipboardData;
6
+ clipboardData.setData('text/plain', string);
7
+ event.preventDefault();
8
+ document.removeEventListener('copy', handler, true);
9
+ }
10
+
11
+ document.addEventListener('copy', handler, true);
12
+ const successful = document.execCommand('copy');
13
+ if (successful) {
14
+ message.success('复制成功');
15
+ } else {
16
+ message.warning('复制失败');
17
+ }
18
+ }
19
+
20
+ // 复制到剪贴板
21
+ export const copy2clipboard = (string: string) => {
22
+ navigator.permissions
23
+ .query({
24
+ name: 'clipboard-write' as any,
25
+ })
26
+ .then((result) => {
27
+ if (result.state === 'granted' || result.state === 'prompt') {
28
+ if (window.navigator && window.navigator.clipboard) {
29
+ window.navigator.clipboard
30
+ .writeText(string)
31
+ .then(() => {
32
+ message.success('复制成功');
33
+ return;
34
+ })
35
+ .catch((err) => {
36
+ message.warning('复制失败');
37
+ console.error('Could not copy text: ', err);
38
+ });
39
+ } else {
40
+ console.warn('navigator is not exist');
41
+ }
42
+ } else {
43
+ console.warn('浏览器权限不允许复制');
44
+ copyFallback(string);
45
+ }
46
+ return;
47
+ })
48
+ .catch(() => {
49
+ //
50
+ });
51
+ };
@@ -0,0 +1,11 @@
1
+ import assert from 'assert';
2
+
3
+ import { JupyterFileService, LibroJupyterModel } from './index.js';
4
+ import 'reflect-metadata';
5
+
6
+ describe('libro-jupyter', () => {
7
+ it('#import', () => {
8
+ assert(JupyterFileService);
9
+ assert(LibroJupyterModel);
10
+ });
11
+ });
package/src/index.ts CHANGED
@@ -26,6 +26,7 @@ export * from './theme/index.js';
26
26
  export * from './toolbar/index.js';
27
27
  export * from './libro-jupyter-protocol.js';
28
28
  export * from './libro-jupyter-model.js';
29
+ export * from './libro-jupyter-view.js';
29
30
  export * from './libro-jupyter-file-service.js';
30
31
  export * from './libro-jupyter-server-launch-manager.js';
31
32
  export * from './file/index.js';
@@ -52,8 +52,3 @@ export interface LibroFileService {
52
52
  currentFileContents: IContentsModel,
53
53
  ) => Promise<IContentsModel | undefined>;
54
54
  }
55
-
56
- export const ServerLaunchManager = Symbol('ServerLaunchManager');
57
- export interface ServerLaunchManager {
58
- launch: () => Promise<any>;
59
- }
@@ -2,12 +2,8 @@ import { ServerManager, ServerConnection } from '@difizen/libro-kernel';
2
2
  import { inject, singleton } from '@difizen/mana-app';
3
3
  import { ApplicationContribution } from '@difizen/mana-app';
4
4
 
5
- import { ServerLaunchManager } from './libro-jupyter-protocol.js';
6
-
7
- @singleton({ contrib: [ServerLaunchManager, ApplicationContribution] })
8
- export class JupyterServerLaunchManager
9
- implements ServerLaunchManager, ApplicationContribution
10
- {
5
+ @singleton({ contrib: [ApplicationContribution] })
6
+ export class JupyterServerLaunchManager implements ApplicationContribution {
11
7
  protected serverManager: ServerManager;
12
8
  protected serverConnection: ServerConnection;
13
9
 
@@ -27,10 +23,5 @@ export class JupyterServerLaunchManager
27
23
  baseUrl: `http://${host}`,
28
24
  wsUrl: `ws://${host}`,
29
25
  });
30
- this.launch();
31
- }
32
-
33
- launch() {
34
- return this.serverManager.launch();
35
26
  }
36
27
  }