@lvce-editor/explorer-view 1.4.0 → 1.6.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/README.md CHANGED
@@ -1 +1,3 @@
1
1
  # Explorer View
2
+
3
+ WebWorker for the explorer view functionality in Lvce Editor.
@@ -847,6 +847,9 @@ const getFileIcon = ({
847
847
  const getIcon = dirent => {
848
848
  return '';
849
849
  };
850
+ const getFolderIcon = dirent => {
851
+ return '';
852
+ };
850
853
 
851
854
  // TODO use posInSet and setSize properties to compute more effectively
852
855
  const computeExplorerRenamedDirent = (dirents, index, newName) => {
@@ -946,17 +949,50 @@ const computeExplorerRenamedDirent = (dirents, index, newName) => {
946
949
  const None$3 = 0;
947
950
  const CreateFile = 1;
948
951
  const CreateFolder = 2;
949
- const Rename = 3;
950
-
951
- const remove = async diren => {};
952
- const readDirWithFileTypes = async uri => {};
953
- const getPathSeparator$1 = async root => {};
954
- const getRealPath = async path => {};
955
- const stat = async dirent => {};
956
- const createFile = async uri => {};
957
- const mkdir = async uri => {};
958
- const rename$1 = async (oldUri, newUri) => {};
959
- const copy$1 = async (oldUri, newUri) => {};
952
+ const Rename$1 = 3;
953
+
954
+ const state = {
955
+ rpc: undefined
956
+ };
957
+ const invoke = (method, ...params) => {
958
+ const rpc = state.rpc;
959
+ // @ts-ignore
960
+ return rpc.invoke(method, ...params);
961
+ };
962
+ const setRpc = rpc => {
963
+ state.rpc = rpc;
964
+ };
965
+
966
+ const remove = async dirent => {
967
+ return invoke('FileSystem.remove', dirent);
968
+ };
969
+ const readDirWithFileTypes = async uri => {
970
+ return invoke('FileSystem.readDirWithFileTypes', uri);
971
+ };
972
+ const getPathSeparator$1 = async root => {
973
+ return invoke('FileSystem.getPathSeparator', root);
974
+ };
975
+ const getRealPath = async path => {
976
+ return invoke('FileSystem.getRealPath', path);
977
+ };
978
+ const stat = async dirent => {
979
+ return invoke('FileSystem.stat', dirent);
980
+ };
981
+ const createFile = async uri => {
982
+ return invoke('FileSystem.createFile', uri);
983
+ };
984
+ const writeFile = async (uri, content) => {
985
+ return invoke('FileSystem.writeFile', uri, content);
986
+ };
987
+ const mkdir = async uri => {
988
+ return invoke('FileSystem.mkdir', uri);
989
+ };
990
+ const rename$1 = async (oldUri, newUri) => {
991
+ return invoke('FileSystem.rename', oldUri, newUri);
992
+ };
993
+ const copy$1 = async (oldUri, newUri) => {
994
+ return invoke('FileSystem.copy', oldUri, newUri);
995
+ };
960
996
 
961
997
  const dirname = (pathSeparator, path) => {
962
998
  const index = path.lastIndexOf(pathSeparator);
@@ -1099,13 +1135,57 @@ const acceptEdit = state => {
1099
1135
  return acceptCreate(state, File, createFile);
1100
1136
  case CreateFolder:
1101
1137
  return acceptCreate(state, Directory, mkdir);
1102
- case Rename:
1138
+ case Rename$1:
1103
1139
  return acceptRename(state);
1104
1140
  default:
1105
1141
  return state;
1106
1142
  }
1107
1143
  };
1108
1144
 
1145
+ const cancelEdit = state => {
1146
+ const {
1147
+ editingIndex
1148
+ } = state;
1149
+ return {
1150
+ ...state,
1151
+ focusedIndex: editingIndex,
1152
+ focused: true,
1153
+ editingIndex: -1,
1154
+ editingValue: '',
1155
+ editingType: None$3
1156
+ };
1157
+ };
1158
+
1159
+ const isTopLevel$1 = dirent => {
1160
+ return dirent.depth === 1;
1161
+ };
1162
+
1163
+ const IsTopLevel = {
1164
+ __proto__: null,
1165
+ isTopLevel: isTopLevel$1
1166
+ };
1167
+
1168
+ const toCollapsedDirent = dirent => {
1169
+ if (dirent.type === DirectoryExpanded) {
1170
+ return {
1171
+ ...dirent,
1172
+ type: Directory
1173
+ };
1174
+ }
1175
+ return dirent;
1176
+ };
1177
+
1178
+ const collapseAll$1 = state => {
1179
+ const {
1180
+ items
1181
+ } = state;
1182
+ const newDirents = items.filter(IsTopLevel).map(toCollapsedDirent);
1183
+ return {
1184
+ ...state,
1185
+ items: newDirents
1186
+ };
1187
+ };
1188
+
1109
1189
  const getFocusedDirent$1 = state => {
1110
1190
  const {
1111
1191
  focusedIndex,
@@ -1121,24 +1201,15 @@ const copyPath$1 = async state => {
1121
1201
  return state;
1122
1202
  };
1123
1203
 
1124
- const state = {
1125
- rpc: undefined
1126
- };
1127
- const invoke = (method, ...params) => {
1128
- const rpc = state.rpc;
1129
- // @ts-ignore
1130
- return rpc.invoke(method, ...params);
1131
- };
1132
- const setRpc = rpc => {
1133
- state.rpc = rpc;
1134
- };
1135
-
1136
1204
  const writeText = async text => {
1137
1205
  await invoke('ClipBoard.writeText', /* text */text);
1138
1206
  };
1139
1207
  const readNativeFiles = async () => {
1140
1208
  return invoke('ClipBoard.readNativeFiles');
1141
1209
  };
1210
+ const writeNativeFiles = async (type, files) => {
1211
+ return invoke('ClipBoard.writeNativeFiles', type, files);
1212
+ };
1142
1213
 
1143
1214
  const copyRelativePath$1 = async state => {
1144
1215
  const dirent = getFocusedDirent$1(state);
@@ -1292,7 +1363,7 @@ const hasSymbolicLinks = rawDirents => {
1292
1363
  return rawDirents.some(isSymbolicLink);
1293
1364
  };
1294
1365
  const getChildDirentsRaw = async uri => {
1295
- const rawDirents = await readDirWithFileTypes();
1366
+ const rawDirents = await readDirWithFileTypes(uri);
1296
1367
  array(rawDirents);
1297
1368
  if (hasSymbolicLinks(rawDirents)) {
1298
1369
  return resolveSymbolicLinks(uri, rawDirents);
@@ -1386,6 +1457,7 @@ const makeExpanded = dirent => {
1386
1457
  }
1387
1458
  return dirent;
1388
1459
  };
1460
+
1389
1461
  const expandRecursively = async state => {
1390
1462
  const {
1391
1463
  items,
@@ -1542,29 +1614,12 @@ const focusPrevious = state => {
1542
1614
  }
1543
1615
  };
1544
1616
 
1545
- const None$2 = 'none';
1546
- const Tree = 'tree';
1547
- const TreeItem$1 = 'treeitem';
1548
-
1549
- const Button$1 = 'Button';
1550
- const ButtonPrimary = 'ButtonPrimary';
1551
- const Explorer$1 = 'Explorer';
1552
- const FileIcon = 'FileIcon';
1553
- const InputBox = 'InputBox';
1554
- const Label = 'Label';
1555
- const TreeItem = 'TreeItem';
1556
- const TreeItemActive = 'TreeItemActive';
1557
- const Viewlet = 'Viewlet';
1558
- const Welcome = 'Welcome';
1559
- const WelcomeMessage = 'WelcomeMessage';
1617
+ const Button$2 = 1;
1560
1618
 
1561
- const HandleBlur = 'handleBlur';
1562
- const HandleClick = 'handleClick';
1563
- const handleClickOpenFolder$1 = 'handleClickOpenFolder';
1564
- const HandleContextMenu = 'handleContextMenu';
1565
- const HandleFocus = 'handleFocus';
1566
- const HandlePointerDown = 'handlePointerDown';
1567
- const HandleWheel = 'handleWheel';
1619
+ const CollapseAll = 'CollapseAll';
1620
+ const NewFile$1 = 'NewFile';
1621
+ const NewFolder$1 = 'NewFolder';
1622
+ const Refresh = 'Refresh';
1568
1623
 
1569
1624
  const emptyObject = {};
1570
1625
  const RE_PLACEHOLDER = /\{(PH\d+)\}/g;
@@ -1578,72 +1633,126 @@ const i18nString = (key, placeholders = emptyObject) => {
1578
1633
  return key.replaceAll(RE_PLACEHOLDER, replacer);
1579
1634
  };
1580
1635
 
1581
- /**
1582
- * @enum {string}
1583
- */
1584
- const UiStrings = {
1585
- NewFile: 'New File...',
1586
- NewFolder: 'New Folder...',
1587
- OpenContainingFolder: 'Open Containing Folder',
1588
- OpenInIntegratedTerminal: 'Open in integrated Terminal',
1589
- Cut: 'Cut',
1590
- Copy: 'Copy',
1591
- Paste: 'Paste',
1592
- CopyPath: 'Copy Path',
1593
- CopyRelativePath: 'Copy Relative Path',
1594
- Rename: 'Rename',
1595
- Delete: 'Delete',
1596
- RefreshExplorer: 'Refresh Explorer',
1597
- CollapseAllFoldersInExplorer: 'Collapse All Folders in Explorer',
1598
- Explorer: 'Explorer',
1599
- FilesExplorer: 'Files Explorer',
1600
- YouHaveNotYetOpenedAFolder: 'You have not yet opened a folder',
1601
- OpenFolder: 'Open folder',
1602
- NoFolderOpen: 'No Folder Open'
1603
- };
1604
- const newFile = () => {
1605
- return i18nString(UiStrings.NewFile);
1606
- };
1607
- const newFolder = () => {
1608
- return i18nString(UiStrings.NewFolder);
1636
+ const NewFile = 'New File...';
1637
+ const NewFolder = 'New Folder...';
1638
+ const OpenContainingFolder = 'Open Containing Folder';
1639
+ const OpenInIntegratedTerminal = 'Open in integrated Terminal';
1640
+ const Cut$1 = 'Cut';
1641
+ const Copy$1 = 'Copy';
1642
+ const Paste = 'Paste';
1643
+ const CopyPath = 'Copy Path';
1644
+ const CopyRelativePath = 'Copy Relative Path';
1645
+ const Rename = 'Rename';
1646
+ const Delete$1 = 'Delete';
1647
+ const RefreshExplorer = 'Refresh Explorer';
1648
+ const CollapseAllFoldersInExplorer = 'Collapse All Folders in Explorer';
1649
+ const FilesExplorer = 'Files Explorer';
1650
+ const YouHaveNotYetOpenedAFolder = 'You have not yet opened a folder';
1651
+ const OpenFolder = 'Open folder';
1652
+
1653
+ const newFile$1 = () => {
1654
+ return i18nString(NewFile);
1655
+ };
1656
+ const newFolder$1 = () => {
1657
+ return i18nString(NewFolder);
1609
1658
  };
1610
1659
  const openContainingFolder$1 = () => {
1611
- return i18nString(UiStrings.OpenContainingFolder);
1660
+ return i18nString(OpenContainingFolder);
1612
1661
  };
1613
1662
  const openInIntegratedTerminal = () => {
1614
- return i18nString(UiStrings.OpenInIntegratedTerminal);
1663
+ return i18nString(OpenInIntegratedTerminal);
1615
1664
  };
1616
1665
  const cut = () => {
1617
- return i18nString(UiStrings.Cut);
1666
+ return i18nString(Cut$1);
1618
1667
  };
1619
1668
  const copy = () => {
1620
- return i18nString(UiStrings.Copy);
1669
+ return i18nString(Copy$1);
1621
1670
  };
1622
1671
  const paste = () => {
1623
- return i18nString(UiStrings.Paste);
1672
+ return i18nString(Paste);
1624
1673
  };
1625
1674
  const copyPath = () => {
1626
- return i18nString(UiStrings.CopyPath);
1675
+ return i18nString(CopyPath);
1627
1676
  };
1628
1677
  const copyRelativePath = () => {
1629
- return i18nString(UiStrings.CopyRelativePath);
1678
+ return i18nString(CopyRelativePath);
1630
1679
  };
1631
1680
  const rename = () => {
1632
- return i18nString(UiStrings.Rename);
1681
+ return i18nString(Rename);
1633
1682
  };
1634
1683
  const deleteItem = () => {
1635
- return i18nString(UiStrings.Delete);
1684
+ return i18nString(Delete$1);
1685
+ };
1686
+ const refresh = () => {
1687
+ return i18nString(RefreshExplorer);
1688
+ };
1689
+ const collapseAll = () => {
1690
+ return i18nString(CollapseAllFoldersInExplorer);
1636
1691
  };
1637
1692
  const filesExplorer = () => {
1638
- return i18nString(UiStrings.FilesExplorer);
1693
+ return i18nString(FilesExplorer);
1639
1694
  };
1640
1695
  const youHaveNotYetOpenedAFolder = () => {
1641
- return i18nString(UiStrings.YouHaveNotYetOpenedAFolder);
1696
+ return i18nString(YouHaveNotYetOpenedAFolder);
1642
1697
  };
1643
1698
  const openFolder$1 = () => {
1644
- return i18nString(UiStrings.OpenFolder);
1699
+ return i18nString(OpenFolder);
1700
+ };
1701
+
1702
+ const getActions = root => {
1703
+ if (!root) {
1704
+ return [];
1705
+ }
1706
+ return [{
1707
+ type: Button$2,
1708
+ id: newFile$1(),
1709
+ icon: NewFile$1,
1710
+ command: 'newFile'
1711
+ }, {
1712
+ type: Button$2,
1713
+ id: newFolder$1(),
1714
+ icon: NewFolder$1,
1715
+ command: 'newFolder'
1716
+ }, {
1717
+ type: Button$2,
1718
+ id: refresh(),
1719
+ icon: Refresh,
1720
+ command: 'refresh'
1721
+ }, {
1722
+ type: Button$2,
1723
+ id: collapseAll(),
1724
+ icon: CollapseAll,
1725
+ command: 'collapseAll'
1726
+ }];
1645
1727
  };
1646
1728
 
1729
+ const None$2 = 'none';
1730
+ const ToolBar = 'toolbar';
1731
+ const Tree = 'tree';
1732
+ const TreeItem$1 = 'treeitem';
1733
+
1734
+ const Actions = 'Actions';
1735
+ const Button$1 = 'Button';
1736
+ const ButtonPrimary = 'ButtonPrimary';
1737
+ const Explorer$1 = 'Explorer';
1738
+ const FileIcon = 'FileIcon';
1739
+ const IconButton = 'IconButton';
1740
+ const InputBox = 'InputBox';
1741
+ const Label = 'Label';
1742
+ const TreeItem = 'TreeItem';
1743
+ const TreeItemActive = 'TreeItemActive';
1744
+ const Viewlet = 'Viewlet';
1745
+ const Welcome = 'Welcome';
1746
+ const WelcomeMessage = 'WelcomeMessage';
1747
+
1748
+ const HandleBlur = 'handleBlur';
1749
+ const HandleClick = 'handleClick';
1750
+ const handleClickOpenFolder$1 = 'handleClickOpenFolder';
1751
+ const HandleContextMenu = 'handleContextMenu';
1752
+ const HandleFocus = 'handleFocus';
1753
+ const HandlePointerDown = 'handlePointerDown';
1754
+ const HandleWheel = 'handleWheel';
1755
+
1647
1756
  const Button = 1;
1648
1757
  const Div = 4;
1649
1758
  const Input = 6;
@@ -1951,13 +2060,13 @@ const menuEntrySeparator = {
1951
2060
 
1952
2061
  const menuEntryNewFile = {
1953
2062
  id: 'newFile',
1954
- label: newFile(),
2063
+ label: newFile$1(),
1955
2064
  flags: None$1,
1956
2065
  command: 'Explorer.newFile'
1957
2066
  };
1958
2067
  const menuEntryNewFolder = {
1959
2068
  id: 'newFolder',
1960
- label: newFolder(),
2069
+ label: newFolder$1(),
1961
2070
  flags: None$1,
1962
2071
  command: 'Explorer.newFolder'
1963
2072
  };
@@ -2208,8 +2317,8 @@ const handleClickDirectoryExpanded$1 = (state, dirent, index, keepFocus) => {
2208
2317
  };
2209
2318
  };
2210
2319
  const handleClickSymLink$1 = async (state, dirent, index) => {
2211
- await getRealPath();
2212
- const type = await stat();
2320
+ const realPath = await getRealPath(dirent.path);
2321
+ const type = await stat(realPath);
2213
2322
  switch (type) {
2214
2323
  case File:
2215
2324
  return handleClickFile$1(state, dirent, index);
@@ -2258,6 +2367,40 @@ const getIndexFromPosition = (state, eventX, eventY) => {
2258
2367
  return index;
2259
2368
  };
2260
2369
 
2370
+ const handleClickDirectory = async (state, dirent, index, keepFocus) => {
2371
+ dirent.type = DirectoryExpanding;
2372
+ // TODO handle error
2373
+ const dirents = await getChildDirents(state.pathSeparator, dirent);
2374
+ const state2 = state;
2375
+ if (!state2) {
2376
+ return state;
2377
+ }
2378
+ // TODO use Viewlet.getState here and check if it exists
2379
+ const newIndex = state2.items.indexOf(dirent);
2380
+ // TODO if viewlet is disposed or root has changed, return
2381
+ if (newIndex === -1) {
2382
+ return state;
2383
+ }
2384
+ const newDirents = [...state2.items];
2385
+ newDirents.splice(newIndex + 1, 0, ...dirents);
2386
+ dirent.type = DirectoryExpanded;
2387
+ dirent.icon = getIcon();
2388
+ const {
2389
+ height,
2390
+ itemHeight,
2391
+ minLineY
2392
+ } = state2;
2393
+ // TODO when focused index has changed while expanding, don't update it
2394
+ const maxLineY = getExplorerMaxLineY(minLineY, height, itemHeight, newDirents.length);
2395
+ return {
2396
+ ...state,
2397
+ items: newDirents,
2398
+ focusedIndex: newIndex,
2399
+ focused: keepFocus,
2400
+ maxLineY
2401
+ };
2402
+ };
2403
+
2261
2404
  const getParentStartIndex = (dirents, index) => {
2262
2405
  const dirent = dirents[index];
2263
2406
  let startIndex = index - 1;
@@ -2270,10 +2413,6 @@ const getParentStartIndex = (dirents, index) => {
2270
2413
  const Keyboard = -1;
2271
2414
  const LeftClick = 0;
2272
2415
 
2273
- const openFolder = async () => {
2274
- // TODO
2275
- };
2276
-
2277
2416
  // TODO viewlet should only have create and refresh functions
2278
2417
  // every thing else can be in a separate module <viewlet>.lazy.js
2279
2418
  // and <viewlet>.ipc.js
@@ -2301,7 +2440,7 @@ const handleIconThemeChange = state => {
2301
2440
  };
2302
2441
 
2303
2442
  // TODO rename dirents to items, then can use virtual list component directly
2304
- const setDeltaY = (state, deltaY) => {
2443
+ const setDeltaY$1 = (state, deltaY) => {
2305
2444
  const {
2306
2445
  itemHeight,
2307
2446
  height,
@@ -2325,93 +2464,11 @@ const setDeltaY = (state, deltaY) => {
2325
2464
  };
2326
2465
  };
2327
2466
  const handleWheel = (state, deltaMode, deltaY) => {
2328
- return setDeltaY(state, state.deltaY + deltaY);
2329
- };
2330
-
2331
- // TODO support multiselection and removing multiple dirents
2332
- const removeDirent = async state => {
2333
- if (state.focusedIndex < 0) {
2334
- return state;
2335
- }
2336
- const dirent = getFocusedDirent$1(state);
2337
- const absolutePath = dirent.path;
2338
- try {
2339
- // TODO handle error
2340
- await remove(absolutePath);
2341
- } catch (error) {
2342
- // TODO vscode shows error as alert (no stacktrace) and retry button
2343
- // maybe should show alert as well, but where to put stacktrace?
2344
- // on web should probably show notification (dialog)
2345
- // ErrorHandling.handleError(error)
2346
- // await ErrorHandling.showErrorDialog(error)
2347
- return;
2348
- }
2349
- // TODO avoid state mutation
2350
- const newVersion = ++state.version;
2351
- // TODO race condition
2352
- // const newState = await loadContent(state:any)
2353
- if (state.version !== newVersion || state.disposed) {
2354
- return state;
2355
- }
2356
- // TODO is it possible to make this more functional instead of mutating state?
2357
- // maybe every function returns a new state?
2358
- const index = state.items.indexOf(dirent);
2359
- let deleteEnd = index + 1;
2360
- for (; deleteEnd < state.items.length; deleteEnd++) {
2361
- if (state.items[deleteEnd].depth <= dirent.depth) {
2362
- break;
2363
- }
2364
- }
2365
- const deleteCount = deleteEnd - index;
2366
- const newDirents = [...state.items];
2367
- newDirents.splice(index, deleteCount);
2368
- let indexToFocus = -1;
2369
- if (newDirents.length === 0) {
2370
- indexToFocus = -1;
2371
- } else if (index < state.focusedIndex) {
2372
- indexToFocus = state.focusedIndex - 1;
2373
- } else if (index === state.focusedIndex) {
2374
- indexToFocus = Math.max(state.focusedIndex - 1, 0);
2375
- } else {
2376
- indexToFocus = Math.max(state.focusedIndex - 1, 0);
2377
- }
2378
- return {
2379
- ...state,
2380
- items: newDirents,
2381
- focusedIndex: indexToFocus
2382
- };
2383
- };
2384
- const renameDirent = state => {
2385
- const {
2386
- focusedIndex,
2387
- items
2388
- } = state;
2389
- const item = items[focusedIndex];
2390
- // Focus.setFocus(FocusKey.ExplorerEditBox)
2391
- return {
2392
- ...state,
2393
- editingIndex: focusedIndex,
2394
- editingType: Rename,
2395
- editingValue: item.name
2396
- };
2467
+ return setDeltaY$1(state, state.deltaY + deltaY);
2397
2468
  };
2398
2469
 
2399
2470
  // TODO use posInSet and setSize properties to compute more effectively
2400
2471
 
2401
- const cancelEdit = state => {
2402
- const {
2403
- editingIndex
2404
- } = state;
2405
- return {
2406
- ...state,
2407
- focusedIndex: editingIndex,
2408
- focused: true,
2409
- editingIndex: -1,
2410
- editingValue: '',
2411
- editingType: None$3
2412
- };
2413
- };
2414
-
2415
2472
  // TODO much shared logic with newFolder
2416
2473
 
2417
2474
  const handleClickFile = async (state, dirent, index, keepFocus = false) => {
@@ -2422,39 +2479,6 @@ const handleClickFile = async (state, dirent, index, keepFocus = false) => {
2422
2479
  focused: keepFocus
2423
2480
  };
2424
2481
  };
2425
- const handleClickDirectory = async (state, dirent, index, keepFocus) => {
2426
- dirent.type = DirectoryExpanding;
2427
- // TODO handle error
2428
- const dirents = await getChildDirents(state.pathSeparator, dirent);
2429
- const state2 = {};
2430
- if (!state2) {
2431
- return state;
2432
- }
2433
- // TODO use Viewlet.getState here and check if it exists
2434
- const newIndex = state2.items.indexOf(dirent);
2435
- // TODO if viewlet is disposed or root has changed, return
2436
- if (newIndex === -1) {
2437
- return state;
2438
- }
2439
- const newDirents = [...state2.items];
2440
- newDirents.splice(newIndex + 1, 0, ...dirents);
2441
- dirent.type = DirectoryExpanded;
2442
- dirent.icon = getIcon();
2443
- const {
2444
- height,
2445
- itemHeight,
2446
- minLineY
2447
- } = state2;
2448
- // TODO when focused index has changed while expanding, don't update it
2449
- const maxLineY = getExplorerMaxLineY(minLineY, height, itemHeight, newDirents.length);
2450
- return {
2451
- ...state,
2452
- items: newDirents,
2453
- focusedIndex: newIndex,
2454
- focused: keepFocus,
2455
- maxLineY
2456
- };
2457
- };
2458
2482
  const handleClickDirectoryExpanded = (state, dirent, index, keepFocus) => {
2459
2483
  const {
2460
2484
  minLineY,
@@ -2522,8 +2546,8 @@ const handleClickCurrentButKeepFocus = state => {
2522
2546
  // export const handleBlur=()=>{}
2523
2547
 
2524
2548
  const handleClickSymLink = async (state, dirent, index) => {
2525
- await getRealPath();
2526
- const type = await stat();
2549
+ const realPath = await getRealPath(dirent.path);
2550
+ const type = await stat(realPath);
2527
2551
  switch (type) {
2528
2552
  case File:
2529
2553
  return handleClickFile(state, dirent, index);
@@ -2604,6 +2628,10 @@ const handleArrowLeft = state => {
2604
2628
 
2605
2629
  // TODO maybe just insert items into explorer and refresh whole explorer
2606
2630
 
2631
+ const openFolder = async () => {
2632
+ // TODO
2633
+ };
2634
+
2607
2635
  const handleClickOpenFolder = async state => {
2608
2636
  await openFolder();
2609
2637
  return state;
@@ -2650,6 +2678,21 @@ const handleContextMenu = (state, button, x, y) => {
2650
2678
  }
2651
2679
  };
2652
2680
 
2681
+ const handleCopy = async state => {
2682
+ // TODO handle multiple files
2683
+ // TODO if not file is selected, what happens?
2684
+ const dirent = getFocusedDirent$1(state);
2685
+ if (!dirent) {
2686
+ console.info('[ViewletExplorer/handleCopy] no dirent selected');
2687
+ return;
2688
+ }
2689
+ const absolutePath = dirent.path;
2690
+ // TODO handle copy error gracefully
2691
+ const files = [absolutePath];
2692
+ await writeNativeFiles('copy', files);
2693
+ return state;
2694
+ };
2695
+
2653
2696
  const getFilePathElectron = async file => {
2654
2697
  return invoke('GetFilePathElectron.getFilePathElectron', file);
2655
2698
  };
@@ -2661,10 +2704,10 @@ const mergeDirents$2 = (oldDirents, newDirents) => {
2661
2704
  // TODO copy files in parallel
2662
2705
  const copyFilesElectron = async (root, pathSeparator, files) => {
2663
2706
  for (const file of files) {
2664
- await getFilePathElectron(file);
2707
+ const from = await getFilePathElectron(file);
2665
2708
  // const from = file.path
2666
- join(pathSeparator, root, file.name);
2667
- await copy$1();
2709
+ const to = join(pathSeparator, root, file.name);
2710
+ await copy$1(from, to);
2668
2711
  }
2669
2712
  };
2670
2713
  const getMergedDirents$2 = async (root, pathSeparator, dirents) => {
@@ -2757,7 +2800,10 @@ const handleDropIntoFolder = async (state, dirent, index, files) => {
2757
2800
  items
2758
2801
  } = state;
2759
2802
  for (const file of files) {
2760
- await copy$1();
2803
+ // TODO path basename
2804
+ const baseName = file;
2805
+ const to = dirent.path + pathSeparator + baseName;
2806
+ await copy$1(file, to);
2761
2807
  }
2762
2808
  const childDirents = await getChildDirents(pathSeparator, dirent);
2763
2809
  const mergedDirents = getMergedDirents(items, index, dirent, childDirents);
@@ -2915,8 +2961,8 @@ const handlePasteCopy = async (state, nativeFiles) => {
2915
2961
  // TODO handle pasting files into hardlink
2916
2962
  // TODO what if folder is big and it takes a long time
2917
2963
  for (const source of nativeFiles.files) {
2918
- join(state.pathSeperator, state.root, getBaseName(state.pathSeparator, source));
2919
- await copy$1();
2964
+ const target = join(state.pathSeperator, state.root, getBaseName(state.pathSeparator, source));
2965
+ await copy$1(source, target);
2920
2966
  }
2921
2967
  // TODO only update folder at which level it changed
2922
2968
  return updateRoot(state);
@@ -2924,8 +2970,8 @@ const handlePasteCopy = async (state, nativeFiles) => {
2924
2970
 
2925
2971
  const handlePasteCut = async (state, nativeFiles) => {
2926
2972
  for (const source of nativeFiles.files) {
2927
- `${state.root}${state.pathSeparator}${getBaseName(state.pathSeparator, source)}`;
2928
- await rename$1();
2973
+ const target = `${state.root}${state.pathSeparator}${getBaseName(state.pathSeparator, source)}`;
2974
+ await rename$1(source, target);
2929
2975
  }
2930
2976
  return state;
2931
2977
  };
@@ -2977,6 +3023,34 @@ const handlePointerDown = (state, button, x, y) => {
2977
3023
  return state;
2978
3024
  };
2979
3025
 
3026
+ const handleUpload = async (state, dirents) => {
3027
+ const {
3028
+ root,
3029
+ pathSeparator
3030
+ } = state;
3031
+ for (const dirent of dirents) {
3032
+ // TODO switch
3033
+ // TODO symlink might not be possible to be copied
3034
+ // TODO create folder if type is 2
3035
+ if (dirent.type === /* File */1) {
3036
+ // TODO reading text might be inefficient for binary files
3037
+ // but not sure how else to send them via jsonrpc
3038
+ const content = await dirent.file.text();
3039
+ const absolutePath = [root, dirent.file.name].join(pathSeparator);
3040
+ await writeFile(absolutePath, content);
3041
+ }
3042
+ }
3043
+ };
3044
+
3045
+ const EmptyString = '';
3046
+
3047
+ const Fulfilled = 'fulfilled';
3048
+ const Rejected = 'rejected';
3049
+
3050
+ const getWorkspacePath = () => {
3051
+ return invoke('Workspace.getPath');
3052
+ };
3053
+
2980
3054
  // TODO viewlet should only have create and refresh functions
2981
3055
  // every thing else can be in a separate module <viewlet>.lazy.js
2982
3056
  // and <viewlet>.ipc.js
@@ -2987,12 +3061,95 @@ const handlePointerDown = (state, button, x, y) => {
2987
3061
  // TODO instead of root string, there should be a root dirent
2988
3062
 
2989
3063
  const getPathSeparator = root => {
2990
- return getPathSeparator$1();
3064
+ return getPathSeparator$1(root);
3065
+ };
3066
+ const getSavedChildDirents = (map, path, depth, excluded, pathSeparator) => {
3067
+ const children = map[path];
3068
+ if (!children) {
3069
+ return [];
3070
+ }
3071
+ const dirents = [];
3072
+ sortExplorerItems(children);
3073
+ const visible = [];
3074
+ const displayRoot = path.endsWith(pathSeparator) ? path : path + pathSeparator;
3075
+ for (const child of children) {
3076
+ if (excluded.includes(child.name)) {
3077
+ continue;
3078
+ }
3079
+ visible.push(child);
3080
+ }
3081
+ const visibleLength = visible.length;
3082
+ for (let i = 0; i < visibleLength; i++) {
3083
+ const child = visible[i];
3084
+ const {
3085
+ name,
3086
+ type
3087
+ } = child;
3088
+ const childPath = displayRoot + name;
3089
+ if ((child.type === Directory || child.type === SymLinkFolder) && childPath in map) {
3090
+ dirents.push({
3091
+ depth,
3092
+ posInSet: i + 1,
3093
+ setSize: visibleLength,
3094
+ icon: getFolderIcon(),
3095
+ name,
3096
+ path: childPath,
3097
+ type: DirectoryExpanded
3098
+ });
3099
+ dirents.push(...getSavedChildDirents(map, childPath, depth + 1, excluded, pathSeparator));
3100
+ } else {
3101
+ dirents.push({
3102
+ depth,
3103
+ posInSet: i + 1,
3104
+ setSize: visibleLength,
3105
+ icon: getIcon(),
3106
+ name,
3107
+ path: childPath,
3108
+ type
3109
+ });
3110
+ }
3111
+ }
3112
+ return dirents;
3113
+ };
3114
+ const createDirents = (root, expandedDirentPaths, expandedDirentChildren, excluded, pathSeparator) => {
3115
+ const dirents = [];
3116
+ const map = Object.create(null);
3117
+ for (let i = 0; i < expandedDirentPaths.length; i++) {
3118
+ const path = expandedDirentPaths[i];
3119
+ const children = expandedDirentChildren[i];
3120
+ if (children.status === Fulfilled) {
3121
+ map[path] = children.value;
3122
+ }
3123
+ }
3124
+ dirents.push(...getSavedChildDirents(map, root, 1, excluded, pathSeparator));
3125
+ return dirents;
3126
+ };
3127
+ const getSavedExpandedPaths = (savedState, root) => {
3128
+ if (savedState && savedState.root !== root) {
3129
+ return [];
3130
+ }
3131
+ if (savedState && savedState.expandedPaths && Array.isArray(savedState.expandedPaths)) {
3132
+ return savedState.expandedPaths;
3133
+ }
3134
+ return [];
2991
3135
  };
2992
3136
  const restoreExpandedState = async (savedState, root, pathSeparator, excluded) => {
2993
- {
3137
+ // TODO read all opened folders in parallel
3138
+ // ignore ENOENT errors
3139
+ // ignore ENOTDIR errors
3140
+ // merge all dirents
3141
+ // restore scroll location
3142
+ const expandedPaths = getSavedExpandedPaths(savedState, root);
3143
+ if (root === EmptyString) {
2994
3144
  return [];
2995
3145
  }
3146
+ const expandedDirentPaths = [root, ...expandedPaths];
3147
+ const expandedDirentChildren = await Promise.allSettled(expandedDirentPaths.map(getChildDirentsRaw));
3148
+ if (expandedDirentChildren[0].status === Rejected) {
3149
+ throw expandedDirentChildren[0].reason;
3150
+ }
3151
+ const dirents = createDirents(root, expandedDirentPaths, expandedDirentChildren, excluded, pathSeparator);
3152
+ return dirents;
2996
3153
  };
2997
3154
  const getExcluded = () => {
2998
3155
  const excludedObject = {};
@@ -3008,11 +3165,12 @@ const getSavedRoot$1 = (savedState, workspacePath) => {
3008
3165
  return workspacePath;
3009
3166
  };
3010
3167
  const loadContent = async (state, savedState) => {
3011
- const root = getSavedRoot$1(savedState, '');
3168
+ const workspacePath = await getWorkspacePath();
3169
+ const root = getSavedRoot$1(savedState, workspacePath);
3012
3170
  // TODO path separator could be restored from saved state
3013
- const pathSeparator = await getPathSeparator(); // TODO only load path separator once
3171
+ const pathSeparator = await getPathSeparator(root); // TODO only load path separator once
3014
3172
  const excluded = getExcluded();
3015
- const restoredDirents = await restoreExpandedState();
3173
+ const restoredDirents = await restoreExpandedState(savedState, root, pathSeparator, excluded);
3016
3174
  const {
3017
3175
  itemHeight,
3018
3176
  height
@@ -3038,6 +3196,45 @@ const loadContent = async (state, savedState) => {
3038
3196
  };
3039
3197
  };
3040
3198
 
3199
+ const ExplorerEditBox = FocusExplorerEditBox;
3200
+
3201
+ const setFocus = key => {
3202
+ return invoke('Focus.setFocus', key);
3203
+ };
3204
+
3205
+ const newDirent = async (state, editingType) => {
3206
+ // TODO make focus functional instead of side effect
3207
+ await setFocus(ExplorerEditBox);
3208
+ // TODO do it like vscode, select position between folders and files
3209
+ const {
3210
+ focusedIndex,
3211
+ items
3212
+ } = state;
3213
+ if (focusedIndex >= 0) {
3214
+ const dirent = items[focusedIndex];
3215
+ if (dirent.type === Directory) {
3216
+ // TODO handle error
3217
+ // @ts-ignore
3218
+ await undefined(state, dirent, focusedIndex);
3219
+ }
3220
+ }
3221
+ return {
3222
+ ...state,
3223
+ editingIndex: focusedIndex,
3224
+ editingType,
3225
+ editingValue: ''
3226
+ };
3227
+ };
3228
+
3229
+ // TODO much shared logic with newFolder
3230
+ const newFile = state => {
3231
+ return newDirent(state, CreateFile);
3232
+ };
3233
+
3234
+ const newFolder = state => {
3235
+ return newDirent(state, CreateFolder);
3236
+ };
3237
+
3041
3238
  const getContaingingFolder = (root, dirents, focusedIndex, pathSeparator) => {
3042
3239
  if (focusedIndex < 0) {
3043
3240
  return root;
@@ -3060,6 +3257,123 @@ const openContainingFolder = async state => {
3060
3257
  return state;
3061
3258
  };
3062
3259
 
3260
+ // TODO support multiselection and removing multiple dirents
3261
+ const removeDirent = async state => {
3262
+ if (state.focusedIndex < 0) {
3263
+ return state;
3264
+ }
3265
+ const dirent = getFocusedDirent$1(state);
3266
+ const absolutePath = dirent.path;
3267
+ try {
3268
+ // TODO handle error
3269
+ await remove(absolutePath);
3270
+ } catch (error) {
3271
+ // TODO vscode shows error as alert (no stacktrace) and retry button
3272
+ // maybe should show alert as well, but where to put stacktrace?
3273
+ // on web should probably show notification (dialog)
3274
+ // ErrorHandling.handleError(error)
3275
+ // await ErrorHandling.showErrorDialog(error)
3276
+ return;
3277
+ }
3278
+ // TODO avoid state mutation
3279
+ const newVersion = ++state.version;
3280
+ // TODO race condition
3281
+ // const newState = await loadContent(state:any)
3282
+ if (state.version !== newVersion || state.disposed) {
3283
+ return state;
3284
+ }
3285
+ // TODO is it possible to make this more functional instead of mutating state?
3286
+ // maybe every function returns a new state?
3287
+ const index = state.items.indexOf(dirent);
3288
+ let deleteEnd = index + 1;
3289
+ for (; deleteEnd < state.items.length; deleteEnd++) {
3290
+ if (state.items[deleteEnd].depth <= dirent.depth) {
3291
+ break;
3292
+ }
3293
+ }
3294
+ const deleteCount = deleteEnd - index;
3295
+ const newDirents = [...state.items];
3296
+ newDirents.splice(index, deleteCount);
3297
+ let indexToFocus = -1;
3298
+ if (newDirents.length === 0) {
3299
+ indexToFocus = -1;
3300
+ } else if (index < state.focusedIndex) {
3301
+ indexToFocus = state.focusedIndex - 1;
3302
+ } else if (index === state.focusedIndex) {
3303
+ indexToFocus = Math.max(state.focusedIndex - 1, 0);
3304
+ } else {
3305
+ indexToFocus = Math.max(state.focusedIndex - 1, 0);
3306
+ }
3307
+ return {
3308
+ ...state,
3309
+ items: newDirents,
3310
+ focusedIndex: indexToFocus
3311
+ };
3312
+ };
3313
+
3314
+ const renameDirent = state => {
3315
+ const {
3316
+ focusedIndex,
3317
+ items
3318
+ } = state;
3319
+ const item = items[focusedIndex];
3320
+ // Focus.setFocus(FocusKey.ExplorerEditBox)
3321
+ return {
3322
+ ...state,
3323
+ editingIndex: focusedIndex,
3324
+ editingType: Rename$1,
3325
+ editingValue: item.name
3326
+ };
3327
+ };
3328
+
3329
+ const getIconVirtualDom = (icon, type = Div) => {
3330
+ return {
3331
+ type,
3332
+ className: `MaskIcon MaskIcon${icon}`,
3333
+ role: None$2,
3334
+ childCount: 0
3335
+ };
3336
+ };
3337
+
3338
+ const getActionButtonVirtualDom = action => {
3339
+ const {
3340
+ id,
3341
+ icon,
3342
+ command
3343
+ } = action;
3344
+ return [{
3345
+ type: Button,
3346
+ className: IconButton,
3347
+ title: id,
3348
+ 'data-command': command,
3349
+ childCount: 1
3350
+ }, getIconVirtualDom(icon)];
3351
+ };
3352
+
3353
+ const getActionVirtualDom = action => {
3354
+ switch (action.type) {
3355
+ case Button$2:
3356
+ return getActionButtonVirtualDom(action);
3357
+ default:
3358
+ return [];
3359
+ }
3360
+ };
3361
+
3362
+ const getActionsVirtualDom = actions => {
3363
+ return [{
3364
+ type: Div,
3365
+ className: Actions,
3366
+ role: ToolBar,
3367
+ childCount: actions.length
3368
+ }, ...actions.flatMap(getActionVirtualDom)];
3369
+ };
3370
+
3371
+ const renderActions = state => {
3372
+ const actions = getActions(state.root);
3373
+ const dom = getActionsVirtualDom(actions);
3374
+ return dom;
3375
+ };
3376
+
3063
3377
  const getSavedRoot = (savedState, workspacePath) => {
3064
3378
  return workspacePath;
3065
3379
  };
@@ -3309,12 +3623,14 @@ const revealItem = async (state, uri) => {
3309
3623
  return revealItemVisible(state, index);
3310
3624
  };
3311
3625
 
3312
- const isExpandedDirectory = dirent => {
3313
- return dirent.type === DirectoryExpanded;
3314
- };
3315
3626
  const getPath = dirent => {
3316
3627
  return dirent.path;
3317
3628
  };
3629
+
3630
+ const isExpandedDirectory = dirent => {
3631
+ return dirent.type === DirectoryExpanded;
3632
+ };
3633
+
3318
3634
  const saveState = state => {
3319
3635
  const {
3320
3636
  items,
@@ -3333,9 +3649,45 @@ const saveState = state => {
3333
3649
  };
3334
3650
  };
3335
3651
 
3652
+ const setDeltaY = (state, deltaY) => {
3653
+ const {
3654
+ itemHeight,
3655
+ height,
3656
+ items
3657
+ } = state;
3658
+ if (deltaY < 0) {
3659
+ deltaY = 0;
3660
+ } else if (deltaY > items.length * itemHeight - height) {
3661
+ deltaY = Math.max(items.length * itemHeight - height, 0);
3662
+ }
3663
+ if (state.deltaY === deltaY) {
3664
+ return state;
3665
+ }
3666
+ const minLineY = Math.round(deltaY / itemHeight);
3667
+ const maxLineY = minLineY + Math.round(height / itemHeight);
3668
+ return {
3669
+ ...state,
3670
+ deltaY,
3671
+ minLineY,
3672
+ maxLineY
3673
+ };
3674
+ };
3675
+
3676
+ const updateEditingValue = (state, value) => {
3677
+ const editingIcon = getFileIcon({
3678
+ name: value
3679
+ });
3680
+ return {
3681
+ ...state,
3682
+ editingValue: value,
3683
+ editingIcon
3684
+ };
3685
+ };
3686
+
3336
3687
  const commandMap = {
3337
3688
  'Explorer.acceptEdit': acceptEdit,
3338
3689
  'Explorer.cancelEdit': cancelEdit,
3690
+ 'Explorer.collapseAll': collapseAll$1,
3339
3691
  'Explorer.copyPath': copyPath$1,
3340
3692
  'Explorer.copyRelativePath': copyRelativePath$1,
3341
3693
  'Explorer.expandAll': expandAll,
@@ -3345,6 +3697,7 @@ const commandMap = {
3345
3697
  'Explorer.focusLast': focusLast,
3346
3698
  'Explorer.focusNext': focusNext,
3347
3699
  'Explorer.focusPrevious': focusPrevious,
3700
+ 'Explorer.getActions': getActions,
3348
3701
  'Explorer.getKeyBindings': getKeyBindings,
3349
3702
  'Explorer.getMenuEntries': getMenuEntries,
3350
3703
  'Explorer.getVirtualDom': getExplorerVirtualDom,
@@ -3357,18 +3710,25 @@ const commandMap = {
3357
3710
  'Explorer.handleClickCurrentButKeepFocus': handleClickCurrentButKeepFocus,
3358
3711
  'Explorer.handleClickOpenFolder': handleClickOpenFolder,
3359
3712
  'Explorer.handleContextMenu': handleContextMenu,
3713
+ 'Explorer.handleCopy': handleCopy,
3360
3714
  'Explorer.handleDrop': handleDrop,
3361
3715
  'Explorer.handleIconThemeChange': handleIconThemeChange,
3362
3716
  'Explorer.handlePaste': handlePaste,
3363
3717
  'Explorer.handlePointerDown': handlePointerDown,
3718
+ 'Explorer.handleUpload': handleUpload,
3364
3719
  'Explorer.handleWheel': handleWheel,
3365
3720
  'Explorer.loadContent': loadContent,
3721
+ 'Explorer.newFile': newFile,
3722
+ 'Explorer.newFolder': newFolder,
3366
3723
  'Explorer.openContainingFolder': openContainingFolder,
3367
3724
  'Explorer.removeDirent': removeDirent,
3368
3725
  'Explorer.renameDirent': renameDirent,
3726
+ 'Explorer.renderActions': renderActions,
3369
3727
  'Explorer.restoreState': restoreState,
3370
3728
  'Explorer.revealItem': revealItem,
3371
- 'Explorer.saveState': saveState
3729
+ 'Explorer.saveState': saveState,
3730
+ 'Explorer.setDeltaY': setDeltaY,
3731
+ 'Explorer.updateEditingValue': updateEditingValue
3372
3732
  };
3373
3733
 
3374
3734
  const listen = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/explorer-view",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Explorer Worker",
5
5
  "main": "dist/explorerViewWorkerMain.js",
6
6
  "type": "module",