@opensumi/ide-file-tree-next 3.7.1 → 3.7.2-next-1739859371.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.
- package/lib/browser/dialog/file-dialog.service.d.ts +4 -0
- package/lib/browser/dialog/file-dialog.service.d.ts.map +1 -1
- package/lib/browser/dialog/file-dialog.service.js +7 -1
- package/lib/browser/dialog/file-dialog.service.js.map +1 -1
- package/lib/browser/dialog/file-dialog.view.d.ts.map +1 -1
- package/lib/browser/dialog/file-dialog.view.js +44 -9
- package/lib/browser/dialog/file-dialog.view.js.map +1 -1
- package/lib/browser/file-tree-contribution.d.ts.map +1 -1
- package/lib/browser/file-tree-contribution.js +8 -1
- package/lib/browser/file-tree-contribution.js.map +1 -1
- package/lib/browser/services/file-tree-api.service.d.ts +3 -2
- package/lib/browser/services/file-tree-api.service.d.ts.map +1 -1
- package/lib/browser/services/file-tree-api.service.js +3 -1
- package/lib/browser/services/file-tree-api.service.js.map +1 -1
- package/lib/browser/services/file-tree-model.service.d.ts.map +1 -1
- package/lib/browser/services/file-tree-model.service.js +29 -8
- package/lib/browser/services/file-tree-model.service.js.map +1 -1
- package/lib/common/index.d.ts +2 -1
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js.map +1 -1
- package/package.json +18 -17
- package/src/browser/dialog/file-dialog.service.ts +12 -1
- package/src/browser/dialog/file-dialog.view.tsx +69 -9
- package/src/browser/file-tree-contribution.ts +8 -1
- package/src/browser/services/file-tree-api.service.ts +5 -2
- package/src/browser/services/file-tree-model.service.ts +34 -12
- 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 (
|
|
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!,
|
|
84
|
+
newFilePath: path.join(filePath!, currentSaveFileName.current),
|
|
79
85
|
});
|
|
80
86
|
}
|
|
81
87
|
|
|
82
|
-
dialogService.hide([path.join(filePath!,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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='
|
|
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={
|
|
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
|
|
984
|
-
|
|
994
|
+
const MAX_FILES = 10;
|
|
995
|
+
let deleteFilesMessage = uris
|
|
996
|
+
.slice(0, MAX_FILES)
|
|
985
997
|
.map((uri) => uri.displayName)
|
|
986
|
-
.join('
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/common/index.ts
CHANGED
|
@@ -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>;
|