@opensumi/ide-file-tree-next 3.7.1 → 3.7.2-next-1739848467.0

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 (27) hide show
  1. package/lib/browser/dialog/file-dialog.service.d.ts +4 -0
  2. package/lib/browser/dialog/file-dialog.service.d.ts.map +1 -1
  3. package/lib/browser/dialog/file-dialog.service.js +7 -1
  4. package/lib/browser/dialog/file-dialog.service.js.map +1 -1
  5. package/lib/browser/dialog/file-dialog.view.d.ts.map +1 -1
  6. package/lib/browser/dialog/file-dialog.view.js +44 -9
  7. package/lib/browser/dialog/file-dialog.view.js.map +1 -1
  8. package/lib/browser/file-tree-contribution.d.ts.map +1 -1
  9. package/lib/browser/file-tree-contribution.js +8 -1
  10. package/lib/browser/file-tree-contribution.js.map +1 -1
  11. package/lib/browser/services/file-tree-api.service.d.ts +3 -2
  12. package/lib/browser/services/file-tree-api.service.d.ts.map +1 -1
  13. package/lib/browser/services/file-tree-api.service.js +3 -1
  14. package/lib/browser/services/file-tree-api.service.js.map +1 -1
  15. package/lib/browser/services/file-tree-model.service.d.ts.map +1 -1
  16. package/lib/browser/services/file-tree-model.service.js +29 -8
  17. package/lib/browser/services/file-tree-model.service.js.map +1 -1
  18. package/lib/common/index.d.ts +2 -1
  19. package/lib/common/index.d.ts.map +1 -1
  20. package/lib/common/index.js.map +1 -1
  21. package/package.json +18 -17
  22. package/src/browser/dialog/file-dialog.service.ts +12 -1
  23. package/src/browser/dialog/file-dialog.view.tsx +69 -9
  24. package/src/browser/file-tree-contribution.ts +8 -1
  25. package/src/browser/services/file-tree-api.service.ts +5 -2
  26. package/src/browser/services/file-tree-model.service.ts +34 -12
  27. package/src/common/index.ts +3 -1
@@ -42,6 +42,7 @@ export const FileDialog = ({
42
42
  const [isReady, setIsReady] = useState<boolean>(false);
43
43
  const [selectPath, setSelectPath] = useState<string>('');
44
44
  const [directoryList, setDirectoryList] = useState<string[]>([]);
45
+ const currentSaveFileName = useRef<string>((options as ISaveDialogOptions).defaultFileName || '');
45
46
 
46
47
  useEffect(() => {
47
48
  if (model) {
@@ -56,7 +57,8 @@ export const FileDialog = ({
56
57
 
57
58
  useEffect(() => {
58
59
  if ((options as ISaveDialogOptions).defaultFileName) {
59
- setFileName((options as ISaveDialogOptions).defaultFileName!);
60
+ setFileName((options as ISaveDialogOptions).defaultFileName || '');
61
+ currentSaveFileName.current = (options as ISaveDialogOptions).defaultFileName || '';
60
62
  }
61
63
  }, [options]);
62
64
 
@@ -70,19 +72,25 @@ export const FileDialog = ({
70
72
  const ensure = useCallback(() => {
71
73
  const value: string[] = model.selectedFiles.map((file) => file.uri.path.toString());
72
74
  // 如果有文件名的,说明肯定是保存文件的情况
73
- if (fileName && (options as ISaveDialogOptions).showNameInput && (value?.length === 1 || options.defaultUri)) {
75
+ if (
76
+ currentSaveFileName.current &&
77
+ (options as ISaveDialogOptions).showNameInput &&
78
+ (value?.length === 1 || options.defaultUri)
79
+ ) {
74
80
  const filePath = value?.length === 1 ? value[0] : options.defaultUri!.path.toString();
75
81
  if ((options as ISaveDialogOptions & { saveAs?: boolean | undefined })?.saveAs) {
76
82
  fileService.saveAs({
77
83
  oldFilePath: path.join(filePath!, (options as ISaveDialogOptions)?.defaultFileName || ''),
78
- newFilePath: path.join(filePath!, fileName),
84
+ newFilePath: path.join(filePath!, currentSaveFileName.current),
79
85
  });
80
86
  }
81
87
 
82
- dialogService.hide([path.join(filePath!, fileName)]);
88
+ dialogService.hide([path.join(filePath!, currentSaveFileName.current)]);
83
89
  } else {
84
90
  if (value.length > 0) {
85
91
  dialogService.hide(value);
92
+ } else if (selectPath) {
93
+ dialogService.hide([selectPath]);
86
94
  } else if (options.defaultUri) {
87
95
  dialogService.hide([options.defaultUri!.path.toString()]);
88
96
  } else if (model.treeModel && model.treeModel.root) {
@@ -93,7 +101,17 @@ export const FileDialog = ({
93
101
  }
94
102
  setIsReady(false);
95
103
  fileService.contextKey.fileDialogViewVisibleContext.set(false);
96
- }, [isReady, dialogService, model, fileName, options]);
104
+ }, [isReady, dialogService, model, options, selectPath]);
105
+
106
+ const getDefaultPath = async (model) => {
107
+ let defaultPath = (model.treeModel.root as Directory).uri.codeUri.fsPath;
108
+
109
+ if (fileService.getDefaultFilePath) {
110
+ defaultPath = await fileService.getDefaultFilePath(model, defaultPath);
111
+ }
112
+
113
+ return defaultPath;
114
+ };
97
115
 
98
116
  const close = useCallback(() => {
99
117
  setIsReady(false);
@@ -106,7 +124,9 @@ export const FileDialog = ({
106
124
  // 确保数据初始化完毕,减少初始化数据过程中多次刷新视图
107
125
  // 这里需要重新取一下treeModel的值确保为最新的TreeModel
108
126
  await model.treeModel.ensureReady;
109
- setSelectPath((model.treeModel.root as Directory).uri.codeUri.fsPath);
127
+ const path = await getDefaultPath(model);
128
+
129
+ setSelectPath(path);
110
130
  setIsReady(true);
111
131
  }, [model, selectPath, isReady]);
112
132
 
@@ -201,6 +221,24 @@ export const FileDialog = ({
201
221
  [model, isReady, selectPath],
202
222
  );
203
223
 
224
+ const onSearchChangeHandler = useCallback(
225
+ async (value: string) => {
226
+ setIsReady(false);
227
+ setSelectPath(value);
228
+ await model.updateTreeModel(value);
229
+ setIsReady(true);
230
+ },
231
+ [model, isReady, selectPath, directoryList],
232
+ );
233
+
234
+ const renderCustomMsg = useCallback(() => {
235
+ if (fileService.renderCustomMsg) {
236
+ return fileService.renderCustomMsg();
237
+ } else {
238
+ return null;
239
+ }
240
+ }, [fileService.renderCustomMsg]);
241
+
204
242
  const renderDialogTreeNode = useCallback(
205
243
  (props: INodeRendererProps) => (
206
244
  <FileTreeDialogNode
@@ -234,10 +272,23 @@ export const FileDialog = ({
234
272
  }
235
273
  }, [isReady, model]);
236
274
 
275
+ const showFilePathSearch = useMemo(
276
+ () => (fileService.showFilePathSearch === false ? false : true),
277
+ [fileService.showFilePathSearch],
278
+ );
279
+
237
280
  const renderDirectorySelection = useCallback(() => {
238
281
  if (directoryList.length > 0) {
239
282
  return (
240
- <Select onChange={onRootChangeHandler} className={styles.select_control} size={'small'} value={selectPath}>
283
+ <Select
284
+ onChange={onRootChangeHandler}
285
+ onSearchChange={onSearchChangeHandler}
286
+ className={styles.select_control}
287
+ size='large'
288
+ searchPlaceholder={selectPath}
289
+ value={selectPath}
290
+ showSearch={showFilePathSearch}
291
+ >
241
292
  {directoryList.map((item, idx) => (
242
293
  <Option value={item} key={`${idx} - ${item}`}>
243
294
  {item}
@@ -273,6 +324,7 @@ export const FileDialog = ({
273
324
  const DialogContent = useMemo(
274
325
  () => (
275
326
  <>
327
+ {renderCustomMsg()}
276
328
  <div className={styles.file_dialog_directory}>{renderDirectorySelection()}</div>
277
329
  <div className={styles.file_dialog_content}>{renderDialogTree()}</div>
278
330
  </>
@@ -283,7 +335,7 @@ export const FileDialog = ({
283
335
  const DialogButtons = useMemo(
284
336
  () => (
285
337
  <div className={styles.file_dialog_buttons}>
286
- <Button onClick={close} type='secondary' className={styles.button}>
338
+ <Button onClick={close} type='ghost' className={styles.button}>
287
339
  {localize('dialog.file.close')}
288
340
  </Button>
289
341
  <Button
@@ -301,6 +353,14 @@ export const FileDialog = ({
301
353
  [close, ensure, isSaveDialog, isOpenDialog, fileName, options],
302
354
  );
303
355
 
356
+ const handleSaveInputChange = useCallback(
357
+ (event: React.ChangeEvent<HTMLInputElement>) => {
358
+ setFileName(event.target.value);
359
+ currentSaveFileName.current = event.target.value;
360
+ },
361
+ [fileName],
362
+ );
363
+
304
364
  return (
305
365
  <div className={styles.file_dialog_wrapper} ref={wrapperRef}>
306
366
  <div className={styles.file_dialog_directory_title}>
@@ -319,7 +379,7 @@ export const FileDialog = ({
319
379
  value={fileName}
320
380
  autoFocus={true}
321
381
  selection={{ start: 0, end: fileName.indexOf('.') || fileName.length }}
322
- onChange={(event) => setFileName(event.target.value)}
382
+ onChange={handleSaveInputChange}
323
383
  />
324
384
  </div>
325
385
  )}
@@ -50,6 +50,7 @@ import { EXPLORER_CONTAINER_ID } from '@opensumi/ide-explorer/lib/browser/explor
50
50
  import { IMainLayoutService, IViewsRegistry, MainLayoutContribution } from '@opensumi/ide-main-layout';
51
51
  import { ViewContentGroups } from '@opensumi/ide-main-layout/lib/browser/views-registry';
52
52
  import { IOpenDialogOptions, ISaveDialogOptions, IWindowDialogService } from '@opensumi/ide-overlay';
53
+ import { Path } from '@opensumi/ide-utils/lib/path';
53
54
  import { IWorkspaceService, UNTITLED_WORKSPACE } from '@opensumi/ide-workspace';
54
55
 
55
56
  import { IFileTreeService, PasteTypes, RESOURCE_VIEW_ID } from '../common';
@@ -778,9 +779,15 @@ export class FileTreeContribution
778
779
  }
779
780
  if (uri.scheme === DIFF_SCHEME) {
780
781
  const query = uri.getParsedQuery();
781
- // 需要file scheme才能与工作区计算相对路径
782
782
  uri = new URI(query.modified).withScheme('file');
783
783
  }
784
+ const node = this.fileTreeService.getNodeByPathOrUri(uri);
785
+ if (node) {
786
+ if (node.filestat.isInSymbolicDirectory) {
787
+ // 软链接文件需要通过直接通过文件树 Path 获取
788
+ return await this.clipboardService.writeText(node.path.split(Path.separator).slice(2).join(Path.separator));
789
+ }
790
+ }
784
791
  let rootUri: URI;
785
792
  if (this.fileTreeService.isMultipleWorkspace) {
786
793
  // 多工作区额外处理
@@ -1,7 +1,7 @@
1
1
  import { Autowired, Injectable } from '@opensumi/di';
2
2
  import { ITree } from '@opensumi/ide-components';
3
3
  import { CorePreferences, EDITOR_COMMANDS } from '@opensumi/ide-core-browser';
4
- import { CommandService, URI, formatLocalize, localize, path } from '@opensumi/ide-core-common';
4
+ import { CommandService, Emitter, Event, URI, formatLocalize, localize, path } from '@opensumi/ide-core-common';
5
5
  import { FileStat } from '@opensumi/ide-file-service';
6
6
  import { IFileServiceClient } from '@opensumi/ide-file-service/lib/common';
7
7
  import { IDialogService } from '@opensumi/ide-overlay';
@@ -12,6 +12,9 @@ import { Directory, File } from '../../common/file-tree-node.define';
12
12
 
13
13
  @Injectable()
14
14
  export class FileTreeAPI implements IFileTreeAPI {
15
+ private readonly onDidResolveChildrenEmitter: Emitter<string> = new Emitter();
16
+ onDidResolveChildren: Event<string> = this.onDidResolveChildrenEmitter.event;
17
+
15
18
  @Autowired(IFileServiceClient)
16
19
  protected fileServiceClient: IFileServiceClient;
17
20
 
@@ -26,7 +29,6 @@ export class FileTreeAPI implements IFileTreeAPI {
26
29
 
27
30
  @Autowired(IDialogService)
28
31
  private readonly dialogService: IDialogService;
29
- private cacheFileStat: Map<string, FileStat> = new Map();
30
32
 
31
33
  private userhomePath: URI;
32
34
 
@@ -45,6 +47,7 @@ export class FileTreeAPI implements IFileTreeAPI {
45
47
  }
46
48
 
47
49
  if (file) {
50
+ this.onDidResolveChildrenEmitter.fire(file.uri.toString());
48
51
  if (file.children?.length === 1 && file.children[0].isDirectory && compact) {
49
52
  return await this.resolveChildren(tree, file.children[0].uri, parent, compact);
50
53
  } else {
@@ -23,6 +23,7 @@ import {
23
23
  IClipboardService,
24
24
  IContextKey,
25
25
  IStorage,
26
+ MessageType,
26
27
  STORAGE_NAMESPACE,
27
28
  StorageProvider,
28
29
  ThrottledDelayer,
@@ -30,9 +31,11 @@ import {
30
31
  URI,
31
32
  arrays,
32
33
  formatLocalize,
34
+ isLinux,
33
35
  localize,
34
36
  path,
35
37
  strings,
38
+ toMarkdown,
36
39
  } from '@opensumi/ide-core-browser';
37
40
  import { ResourceContextKey } from '@opensumi/ide-core-browser/lib/contextkey/resource';
38
41
  import { AbstractContextMenuService, ICtxMenuRenderer, MenuId } from '@opensumi/ide-core-browser/lib/menu/next';
@@ -725,6 +728,10 @@ export class FileTreeModelService {
725
728
  };
726
729
 
727
730
  handleItemRangeClick = (item: File | Directory, type: TreeNodeType) => {
731
+ // 由于文件树存在选择根目录的逻辑,使用 Shift+Click 时需要清理根目录选中态
732
+ if (this.selectedFiles.length === 1 && Directory.isRoot(this.selectedFiles[0])) {
733
+ this.clearFileSelectedDecoration();
734
+ }
728
735
  if (!this.focusedFile) {
729
736
  this.handleItemClick(item, type);
730
737
  } else if (this.focusedFile && this.focusedFile !== item) {
@@ -744,7 +751,10 @@ export class FileTreeModelService {
744
751
  if (type !== TreeNodeType.CompositeTreeNode && type !== TreeNodeType.TreeNode) {
745
752
  return;
746
753
  }
747
-
754
+ // 由于文件树存在选择根目录的逻辑,使用 Cmd/Ctrl+Click 时需要清理根目录选中态
755
+ if (this.selectedFiles.length === 1 && Directory.isRoot(this.selectedFiles[0])) {
756
+ this.clearFileSelectedDecoration();
757
+ }
748
758
  // 根据节点的选中态进行复选操作
749
759
  this.toggleFileSelectedDecoration(item);
750
760
  };
@@ -977,18 +987,29 @@ export class FileTreeModelService {
977
987
  if (uris.length === 0) {
978
988
  return;
979
989
  }
990
+ // 默认过滤掉根目录的选择
980
991
  if (this.corePreferences['explorer.confirmDelete']) {
981
- const ok = localize('file.confirm.delete.ok');
992
+ const ok = isLinux ? localize('file.confirm.delete.ok') : localize('file.confirm.moveToTrash.ok');
982
993
  const cancel = localize('file.confirm.delete.cancel');
983
- const deleteFilesMessage = `[ ${uris
984
- .slice(0, 5)
994
+ const MAX_FILES = 10;
995
+ let deleteFilesMessage = uris
996
+ .slice(0, MAX_FILES)
985
997
  .map((uri) => uri.displayName)
986
- .join(',')}${uris.length > 5 ? ' ...' : ''} ]`;
987
-
988
- const confirm = await this.dialogService.warning(formatLocalize('file.confirm.delete', deleteFilesMessage), [
989
- cancel,
990
- ok,
991
- ]);
998
+ .join(' \n');
999
+ if (uris.length > MAX_FILES) {
1000
+ deleteFilesMessage += ' \n...';
1001
+ }
1002
+ if (!isLinux) {
1003
+ deleteFilesMessage += `\n\n<small>${localize('file.confirm.deleteTips')}</small>`;
1004
+ }
1005
+ const confirm = await this.dialogService.open({
1006
+ message: toMarkdown(formatLocalize('file.confirm.delete', uris.length, deleteFilesMessage)),
1007
+ type: MessageType.Warning,
1008
+ props: {
1009
+ width: 580,
1010
+ },
1011
+ buttons: [cancel, ok],
1012
+ });
992
1013
  if (confirm !== ok) {
993
1014
  return;
994
1015
  }
@@ -1709,7 +1730,8 @@ export class FileTreeModelService {
1709
1730
  this._nextLocationTarget = undefined;
1710
1731
  }
1711
1732
  };
1712
- selectChildNode(uris: URI[]) {
1733
+
1734
+ public selectChildNode(uris: URI[]) {
1713
1735
  for (const uri of uris) {
1714
1736
  const file = this.fileTreeService.getNodeByPathOrUri(uri);
1715
1737
 
@@ -1721,7 +1743,7 @@ export class FileTreeModelService {
1721
1743
  const last = children[children.length - 1];
1722
1744
  const firstIndex = this.treeModel.root.getIndexAtTreeNode(first);
1723
1745
  const lastIndex = this.treeModel.root.getIndexAtTreeNode(last);
1724
-
1746
+ this._isMultiSelected = true;
1725
1747
  this.activeFileDecorationByRange(firstIndex, lastIndex);
1726
1748
  }
1727
1749
  }
@@ -1,5 +1,5 @@
1
1
  import { ITree, ITreeNode } from '@opensumi/ide-components';
2
- import { BasicEvent, IDisposable, URI } from '@opensumi/ide-core-common';
2
+ import { BasicEvent, Event, IDisposable, URI } from '@opensumi/ide-core-common';
3
3
  import { FileStat } from '@opensumi/ide-file-service';
4
4
 
5
5
  import { Directory, File } from './file-tree-node.define';
@@ -19,6 +19,8 @@ export interface IMoveFileMetadata {
19
19
  }
20
20
 
21
21
  export interface IFileTreeAPI {
22
+ onDidResolveChildren: Event<string>;
23
+
22
24
  copyFile(from: URI, to: URI): Promise<FileStat | string | void>;
23
25
  createFile(newUri: URI, content?: string): Promise<string | void>;
24
26
  createDirectory(newUri: URI): Promise<string | void>;