@lvce-editor/explorer-view 1.5.0 → 1.7.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.
@@ -949,7 +949,7 @@ const computeExplorerRenamedDirent = (dirents, index, newName) => {
949
949
  const None$3 = 0;
950
950
  const CreateFile = 1;
951
951
  const CreateFolder = 2;
952
- const Rename = 3;
952
+ const Rename$1 = 3;
953
953
 
954
954
  const state = {
955
955
  rpc: undefined
@@ -981,6 +981,9 @@ const stat = async dirent => {
981
981
  const createFile = async uri => {
982
982
  return invoke('FileSystem.createFile', uri);
983
983
  };
984
+ const writeFile = async (uri, content) => {
985
+ return invoke('FileSystem.writeFile', uri, content);
986
+ };
984
987
  const mkdir = async uri => {
985
988
  return invoke('FileSystem.mkdir', uri);
986
989
  };
@@ -1132,13 +1135,57 @@ const acceptEdit = state => {
1132
1135
  return acceptCreate(state, File, createFile);
1133
1136
  case CreateFolder:
1134
1137
  return acceptCreate(state, Directory, mkdir);
1135
- case Rename:
1138
+ case Rename$1:
1136
1139
  return acceptRename(state);
1137
1140
  default:
1138
1141
  return state;
1139
1142
  }
1140
1143
  };
1141
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
+
1142
1189
  const getFocusedDirent$1 = state => {
1143
1190
  const {
1144
1191
  focusedIndex,
@@ -1160,6 +1207,9 @@ const writeText = async text => {
1160
1207
  const readNativeFiles = async () => {
1161
1208
  return invoke('ClipBoard.readNativeFiles');
1162
1209
  };
1210
+ const writeNativeFiles = async (type, files) => {
1211
+ return invoke('ClipBoard.writeNativeFiles', type, files);
1212
+ };
1163
1213
 
1164
1214
  const copyRelativePath$1 = async state => {
1165
1215
  const dirent = getFocusedDirent$1(state);
@@ -1407,6 +1457,7 @@ const makeExpanded = dirent => {
1407
1457
  }
1408
1458
  return dirent;
1409
1459
  };
1460
+
1410
1461
  const expandRecursively = async state => {
1411
1462
  const {
1412
1463
  items,
@@ -1563,29 +1614,12 @@ const focusPrevious = state => {
1563
1614
  }
1564
1615
  };
1565
1616
 
1566
- const None$2 = 'none';
1567
- const Tree = 'tree';
1568
- const TreeItem$1 = 'treeitem';
1569
-
1570
- const Button$1 = 'Button';
1571
- const ButtonPrimary = 'ButtonPrimary';
1572
- const Explorer$1 = 'Explorer';
1573
- const FileIcon = 'FileIcon';
1574
- const InputBox = 'InputBox';
1575
- const Label = 'Label';
1576
- const TreeItem = 'TreeItem';
1577
- const TreeItemActive = 'TreeItemActive';
1578
- const Viewlet = 'Viewlet';
1579
- const Welcome = 'Welcome';
1580
- const WelcomeMessage = 'WelcomeMessage';
1617
+ const Button$2 = 1;
1581
1618
 
1582
- const HandleBlur = 'handleBlur';
1583
- const HandleClick = 'handleClick';
1584
- const handleClickOpenFolder$1 = 'handleClickOpenFolder';
1585
- const HandleContextMenu = 'handleContextMenu';
1586
- const HandleFocus = 'handleFocus';
1587
- const HandlePointerDown = 'handlePointerDown';
1588
- const HandleWheel = 'handleWheel';
1619
+ const CollapseAll = 'CollapseAll';
1620
+ const NewFile$1 = 'NewFile';
1621
+ const NewFolder$1 = 'NewFolder';
1622
+ const Refresh = 'Refresh';
1589
1623
 
1590
1624
  const emptyObject = {};
1591
1625
  const RE_PLACEHOLDER = /\{(PH\d+)\}/g;
@@ -1599,72 +1633,126 @@ const i18nString = (key, placeholders = emptyObject) => {
1599
1633
  return key.replaceAll(RE_PLACEHOLDER, replacer);
1600
1634
  };
1601
1635
 
1602
- /**
1603
- * @enum {string}
1604
- */
1605
- const UiStrings = {
1606
- NewFile: 'New File...',
1607
- NewFolder: 'New Folder...',
1608
- OpenContainingFolder: 'Open Containing Folder',
1609
- OpenInIntegratedTerminal: 'Open in integrated Terminal',
1610
- Cut: 'Cut',
1611
- Copy: 'Copy',
1612
- Paste: 'Paste',
1613
- CopyPath: 'Copy Path',
1614
- CopyRelativePath: 'Copy Relative Path',
1615
- Rename: 'Rename',
1616
- Delete: 'Delete',
1617
- RefreshExplorer: 'Refresh Explorer',
1618
- CollapseAllFoldersInExplorer: 'Collapse All Folders in Explorer',
1619
- Explorer: 'Explorer',
1620
- FilesExplorer: 'Files Explorer',
1621
- YouHaveNotYetOpenedAFolder: 'You have not yet opened a folder',
1622
- OpenFolder: 'Open folder',
1623
- NoFolderOpen: 'No Folder Open'
1624
- };
1625
- const newFile = () => {
1626
- return i18nString(UiStrings.NewFile);
1627
- };
1628
- const newFolder = () => {
1629
- 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);
1630
1658
  };
1631
1659
  const openContainingFolder$1 = () => {
1632
- return i18nString(UiStrings.OpenContainingFolder);
1660
+ return i18nString(OpenContainingFolder);
1633
1661
  };
1634
1662
  const openInIntegratedTerminal = () => {
1635
- return i18nString(UiStrings.OpenInIntegratedTerminal);
1663
+ return i18nString(OpenInIntegratedTerminal);
1636
1664
  };
1637
1665
  const cut = () => {
1638
- return i18nString(UiStrings.Cut);
1666
+ return i18nString(Cut$1);
1639
1667
  };
1640
1668
  const copy = () => {
1641
- return i18nString(UiStrings.Copy);
1669
+ return i18nString(Copy$1);
1642
1670
  };
1643
1671
  const paste = () => {
1644
- return i18nString(UiStrings.Paste);
1672
+ return i18nString(Paste);
1645
1673
  };
1646
1674
  const copyPath = () => {
1647
- return i18nString(UiStrings.CopyPath);
1675
+ return i18nString(CopyPath);
1648
1676
  };
1649
1677
  const copyRelativePath = () => {
1650
- return i18nString(UiStrings.CopyRelativePath);
1678
+ return i18nString(CopyRelativePath);
1651
1679
  };
1652
1680
  const rename = () => {
1653
- return i18nString(UiStrings.Rename);
1681
+ return i18nString(Rename);
1654
1682
  };
1655
1683
  const deleteItem = () => {
1656
- 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);
1657
1691
  };
1658
1692
  const filesExplorer = () => {
1659
- return i18nString(UiStrings.FilesExplorer);
1693
+ return i18nString(FilesExplorer);
1660
1694
  };
1661
1695
  const youHaveNotYetOpenedAFolder = () => {
1662
- return i18nString(UiStrings.YouHaveNotYetOpenedAFolder);
1696
+ return i18nString(YouHaveNotYetOpenedAFolder);
1663
1697
  };
1664
1698
  const openFolder$1 = () => {
1665
- return i18nString(UiStrings.OpenFolder);
1699
+ return i18nString(OpenFolder);
1666
1700
  };
1667
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
+ }];
1727
+ };
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
+
1668
1756
  const Button = 1;
1669
1757
  const Div = 4;
1670
1758
  const Input = 6;
@@ -1972,13 +2060,13 @@ const menuEntrySeparator = {
1972
2060
 
1973
2061
  const menuEntryNewFile = {
1974
2062
  id: 'newFile',
1975
- label: newFile(),
2063
+ label: newFile$1(),
1976
2064
  flags: None$1,
1977
2065
  command: 'Explorer.newFile'
1978
2066
  };
1979
2067
  const menuEntryNewFolder = {
1980
2068
  id: 'newFolder',
1981
- label: newFolder(),
2069
+ label: newFolder$1(),
1982
2070
  flags: None$1,
1983
2071
  command: 'Explorer.newFolder'
1984
2072
  };
@@ -2125,36 +2213,11 @@ const handleBlur = state => {
2125
2213
  };
2126
2214
  };
2127
2215
 
2128
- // TODO viewlet should only have create and refresh functions
2129
- // every thing else can be in a separate module <viewlet>.lazy.js
2130
- // and <viewlet>.ipc.js
2131
-
2132
- // viewlet: creating | refreshing | done | disposed
2133
- // TODO recycle viewlets (maybe)
2134
-
2135
- // TODO instead of root string, there should be a root dirent
2136
-
2137
- // TODO rename dirents to items, then can use virtual list component directly
2138
-
2139
- // TODO support multiselection and removing multiple dirents
2140
-
2141
- // TODO use posInSet and setSize properties to compute more effectively
2142
-
2143
- // TODO much shared logic with newFolder
2144
-
2145
- const handleClickFile$1 = async (state, dirent, index, keepFocus = false) => {
2146
- // await Command.execute(/* Main.openAbsolutePath */ 'Main.openUri', /* absolutePath */ dirent.path, /* focus */ !keepFocus)
2147
- return {
2148
- ...state,
2149
- focusedIndex: index,
2150
- focused: keepFocus
2151
- };
2152
- };
2153
- const handleClickDirectory$1 = async (state, dirent, index, keepFocus) => {
2216
+ const handleClickDirectory = async (state, dirent, index, keepFocus) => {
2154
2217
  dirent.type = DirectoryExpanding;
2155
2218
  // TODO handle error
2156
2219
  const dirents = await getChildDirents(state.pathSeparator, dirent);
2157
- const state2 = {};
2220
+ const state2 = state;
2158
2221
  if (!state2) {
2159
2222
  return state;
2160
2223
  }
@@ -2183,15 +2246,7 @@ const handleClickDirectory$1 = async (state, dirent, index, keepFocus) => {
2183
2246
  maxLineY
2184
2247
  };
2185
2248
  };
2186
- const handleClickDirectoryExpanding = (state, dirent, index, keepFocus) => {
2187
- dirent.type = Directory;
2188
- dirent.icon = getIcon();
2189
- return {
2190
- ...state,
2191
- focusedIndex: index,
2192
- focused: keepFocus
2193
- };
2194
- };
2249
+
2195
2250
  const handleClickDirectoryExpanded$1 = (state, dirent, index, keepFocus) => {
2196
2251
  const {
2197
2252
  minLineY,
@@ -2228,6 +2283,26 @@ const handleClickDirectoryExpanded$1 = (state, dirent, index, keepFocus) => {
2228
2283
  focused: keepFocus
2229
2284
  };
2230
2285
  };
2286
+
2287
+ const handleClickDirectoryExpanding = (state, dirent, index, keepFocus) => {
2288
+ dirent.type = Directory;
2289
+ dirent.icon = getIcon();
2290
+ return {
2291
+ ...state,
2292
+ focusedIndex: index,
2293
+ focused: keepFocus
2294
+ };
2295
+ };
2296
+
2297
+ const handleClickFile$1 = async (state, dirent, index, keepFocus = false) => {
2298
+ // await Command.execute(/* Main.openAbsolutePath */ 'Main.openUri', /* absolutePath */ dirent.path, /* focus */ !keepFocus)
2299
+ return {
2300
+ ...state,
2301
+ focusedIndex: index,
2302
+ focused: keepFocus
2303
+ };
2304
+ };
2305
+
2231
2306
  const handleClickSymLink$1 = async (state, dirent, index) => {
2232
2307
  const realPath = await getRealPath(dirent.path);
2233
2308
  const type = await stat(realPath);
@@ -2238,6 +2313,24 @@ const handleClickSymLink$1 = async (state, dirent, index) => {
2238
2313
  throw new Error(`unsupported file type ${type}`);
2239
2314
  }
2240
2315
  };
2316
+
2317
+ // TODO viewlet should only have create and refresh functions
2318
+ // every thing else can be in a separate module <viewlet>.lazy.js
2319
+ // and <viewlet>.ipc.js
2320
+
2321
+ // viewlet: creating | refreshing | done | disposed
2322
+ // TODO recycle viewlets (maybe)
2323
+
2324
+ // TODO instead of root string, there should be a root dirent
2325
+
2326
+ // TODO rename dirents to items, then can use virtual list component directly
2327
+
2328
+ // TODO support multiselection and removing multiple dirents
2329
+
2330
+ // TODO use posInSet and setSize properties to compute more effectively
2331
+
2332
+ // TODO much shared logic with newFolder
2333
+
2241
2334
  const getClickFn = direntType => {
2242
2335
  switch (direntType) {
2243
2336
  case File:
@@ -2245,7 +2338,7 @@ const getClickFn = direntType => {
2245
2338
  return handleClickFile$1;
2246
2339
  case Directory:
2247
2340
  case SymLinkFolder:
2248
- return handleClickDirectory$1;
2341
+ return handleClickDirectory;
2249
2342
  case DirectoryExpanding:
2250
2343
  return handleClickDirectoryExpanding;
2251
2344
  case DirectoryExpanded:
@@ -2291,10 +2384,6 @@ const getParentStartIndex = (dirents, index) => {
2291
2384
  const Keyboard = -1;
2292
2385
  const LeftClick = 0;
2293
2386
 
2294
- const openFolder = async () => {
2295
- // TODO
2296
- };
2297
-
2298
2387
  // TODO viewlet should only have create and refresh functions
2299
2388
  // every thing else can be in a separate module <viewlet>.lazy.js
2300
2389
  // and <viewlet>.ipc.js
@@ -2304,25 +2393,25 @@ const openFolder = async () => {
2304
2393
 
2305
2394
  // TODO instead of root string, there should be a root dirent
2306
2395
 
2307
- const updateIcon = dirent => {
2396
+ const updateIcon$1 = dirent => {
2308
2397
  return {
2309
2398
  ...dirent,
2310
2399
  icon: getIcon()
2311
2400
  };
2312
2401
  };
2313
- const updateIcons = state => {
2314
- const newDirents = state.items.map(updateIcon);
2402
+ const updateIcons$1 = state => {
2403
+ const newDirents = state.items.map(updateIcon$1);
2315
2404
  return {
2316
2405
  ...state,
2317
2406
  items: newDirents
2318
2407
  };
2319
2408
  };
2320
2409
  const handleIconThemeChange = state => {
2321
- return updateIcons(state);
2410
+ return updateIcons$1(state);
2322
2411
  };
2323
2412
 
2324
2413
  // TODO rename dirents to items, then can use virtual list component directly
2325
- const setDeltaY = (state, deltaY) => {
2414
+ const setDeltaY$1 = (state, deltaY) => {
2326
2415
  const {
2327
2416
  itemHeight,
2328
2417
  height,
@@ -2346,93 +2435,11 @@ const setDeltaY = (state, deltaY) => {
2346
2435
  };
2347
2436
  };
2348
2437
  const handleWheel = (state, deltaMode, deltaY) => {
2349
- return setDeltaY(state, state.deltaY + deltaY);
2350
- };
2351
-
2352
- // TODO support multiselection and removing multiple dirents
2353
- const removeDirent = async state => {
2354
- if (state.focusedIndex < 0) {
2355
- return state;
2356
- }
2357
- const dirent = getFocusedDirent$1(state);
2358
- const absolutePath = dirent.path;
2359
- try {
2360
- // TODO handle error
2361
- await remove(absolutePath);
2362
- } catch (error) {
2363
- // TODO vscode shows error as alert (no stacktrace) and retry button
2364
- // maybe should show alert as well, but where to put stacktrace?
2365
- // on web should probably show notification (dialog)
2366
- // ErrorHandling.handleError(error)
2367
- // await ErrorHandling.showErrorDialog(error)
2368
- return;
2369
- }
2370
- // TODO avoid state mutation
2371
- const newVersion = ++state.version;
2372
- // TODO race condition
2373
- // const newState = await loadContent(state:any)
2374
- if (state.version !== newVersion || state.disposed) {
2375
- return state;
2376
- }
2377
- // TODO is it possible to make this more functional instead of mutating state?
2378
- // maybe every function returns a new state?
2379
- const index = state.items.indexOf(dirent);
2380
- let deleteEnd = index + 1;
2381
- for (; deleteEnd < state.items.length; deleteEnd++) {
2382
- if (state.items[deleteEnd].depth <= dirent.depth) {
2383
- break;
2384
- }
2385
- }
2386
- const deleteCount = deleteEnd - index;
2387
- const newDirents = [...state.items];
2388
- newDirents.splice(index, deleteCount);
2389
- let indexToFocus = -1;
2390
- if (newDirents.length === 0) {
2391
- indexToFocus = -1;
2392
- } else if (index < state.focusedIndex) {
2393
- indexToFocus = state.focusedIndex - 1;
2394
- } else if (index === state.focusedIndex) {
2395
- indexToFocus = Math.max(state.focusedIndex - 1, 0);
2396
- } else {
2397
- indexToFocus = Math.max(state.focusedIndex - 1, 0);
2398
- }
2399
- return {
2400
- ...state,
2401
- items: newDirents,
2402
- focusedIndex: indexToFocus
2403
- };
2404
- };
2405
- const renameDirent = state => {
2406
- const {
2407
- focusedIndex,
2408
- items
2409
- } = state;
2410
- const item = items[focusedIndex];
2411
- // Focus.setFocus(FocusKey.ExplorerEditBox)
2412
- return {
2413
- ...state,
2414
- editingIndex: focusedIndex,
2415
- editingType: Rename,
2416
- editingValue: item.name
2417
- };
2438
+ return setDeltaY$1(state, state.deltaY + deltaY);
2418
2439
  };
2419
2440
 
2420
2441
  // TODO use posInSet and setSize properties to compute more effectively
2421
2442
 
2422
- const cancelEdit = state => {
2423
- const {
2424
- editingIndex
2425
- } = state;
2426
- return {
2427
- ...state,
2428
- focusedIndex: editingIndex,
2429
- focused: true,
2430
- editingIndex: -1,
2431
- editingValue: '',
2432
- editingType: None$3
2433
- };
2434
- };
2435
-
2436
2443
  // TODO much shared logic with newFolder
2437
2444
 
2438
2445
  const handleClickFile = async (state, dirent, index, keepFocus = false) => {
@@ -2443,39 +2450,6 @@ const handleClickFile = async (state, dirent, index, keepFocus = false) => {
2443
2450
  focused: keepFocus
2444
2451
  };
2445
2452
  };
2446
- const handleClickDirectory = async (state, dirent, index, keepFocus) => {
2447
- dirent.type = DirectoryExpanding;
2448
- // TODO handle error
2449
- const dirents = await getChildDirents(state.pathSeparator, dirent);
2450
- const state2 = {};
2451
- if (!state2) {
2452
- return state;
2453
- }
2454
- // TODO use Viewlet.getState here and check if it exists
2455
- const newIndex = state2.items.indexOf(dirent);
2456
- // TODO if viewlet is disposed or root has changed, return
2457
- if (newIndex === -1) {
2458
- return state;
2459
- }
2460
- const newDirents = [...state2.items];
2461
- newDirents.splice(newIndex + 1, 0, ...dirents);
2462
- dirent.type = DirectoryExpanded;
2463
- dirent.icon = getIcon();
2464
- const {
2465
- height,
2466
- itemHeight,
2467
- minLineY
2468
- } = state2;
2469
- // TODO when focused index has changed while expanding, don't update it
2470
- const maxLineY = getExplorerMaxLineY(minLineY, height, itemHeight, newDirents.length);
2471
- return {
2472
- ...state,
2473
- items: newDirents,
2474
- focusedIndex: newIndex,
2475
- focused: keepFocus,
2476
- maxLineY
2477
- };
2478
- };
2479
2453
  const handleClickDirectoryExpanded = (state, dirent, index, keepFocus) => {
2480
2454
  const {
2481
2455
  minLineY,
@@ -2625,6 +2599,10 @@ const handleArrowLeft = state => {
2625
2599
 
2626
2600
  // TODO maybe just insert items into explorer and refresh whole explorer
2627
2601
 
2602
+ const openFolder = async () => {
2603
+ // TODO
2604
+ };
2605
+
2628
2606
  const handleClickOpenFolder = async state => {
2629
2607
  await openFolder();
2630
2608
  return state;
@@ -2671,6 +2649,21 @@ const handleContextMenu = (state, button, x, y) => {
2671
2649
  }
2672
2650
  };
2673
2651
 
2652
+ const handleCopy = async state => {
2653
+ // TODO handle multiple files
2654
+ // TODO if not file is selected, what happens?
2655
+ const dirent = getFocusedDirent$1(state);
2656
+ if (!dirent) {
2657
+ console.info('[ViewletExplorer/handleCopy] no dirent selected');
2658
+ return;
2659
+ }
2660
+ const absolutePath = dirent.path;
2661
+ // TODO handle copy error gracefully
2662
+ const files = [absolutePath];
2663
+ await writeNativeFiles('copy', files);
2664
+ return state;
2665
+ };
2666
+
2674
2667
  const getFilePathElectron = async file => {
2675
2668
  return invoke('GetFilePathElectron.getFilePathElectron', file);
2676
2669
  };
@@ -2963,8 +2956,7 @@ const None = 'none';
2963
2956
  const Copy = 'copy';
2964
2957
  const Cut = 'cut';
2965
2958
 
2966
- const handlePaste = async state => {
2967
- const nativeFiles = await readNativeFiles();
2959
+ const getPasteHandler = type => {
2968
2960
  // TODO detect cut/paste event, not sure if that is possible
2969
2961
  // TODO check that pasted folder is not a parent folder of opened folder
2970
2962
  // TODO support pasting multiple paths
@@ -2977,18 +2969,36 @@ const handlePaste = async state => {
2977
2969
  // TODO but what if a file is currently selected? Then maybe the parent folder
2978
2970
  // TODO but will it work if the folder is a symlink?
2979
2971
  // TODO handle error gracefully when copy fails
2980
- switch (nativeFiles.type) {
2972
+ switch (type) {
2981
2973
  case None:
2982
- return handlePasteNone(state);
2974
+ return handlePasteNone;
2983
2975
  case Copy:
2984
- return handlePasteCopy(state, nativeFiles);
2976
+ return handlePasteCopy;
2985
2977
  case Cut:
2986
- return handlePasteCut(state, nativeFiles);
2978
+ return handlePasteCut;
2987
2979
  default:
2988
- throw new Error(`unexpected native paste type: ${nativeFiles.type}`);
2980
+ throw new Error(`unexpected native paste type: ${type}`);
2989
2981
  }
2990
2982
  };
2991
2983
 
2984
+ const handlePaste = async state => {
2985
+ const nativeFiles = await readNativeFiles();
2986
+ // TODO detect cut/paste event, not sure if that is possible
2987
+ // TODO check that pasted folder is not a parent folder of opened folder
2988
+ // TODO support pasting multiple paths
2989
+ // TODO what happens when pasting multiple paths, but some of them error?
2990
+ // how many error messages should be shown? Should the operation be undone?
2991
+ // TODO what if it is a large folder and takes a long time to copy? Should show progress
2992
+ // TODO what if there is a permission error? Probably should show a modal to ask for permission
2993
+ // TODO if error is EEXISTS, just rename the copy (e.g. file-copy-1.txt, file-copy-2.txt)
2994
+ // TODO actual target should be selected folder
2995
+ // TODO but what if a file is currently selected? Then maybe the parent folder
2996
+ // TODO but will it work if the folder is a symlink?
2997
+ // TODO handle error gracefully when copy fails
2998
+ const fn = getPasteHandler(nativeFiles.type);
2999
+ return fn(state, nativeFiles);
3000
+ };
3001
+
2992
3002
  const handlePointerDown = (state, button, x, y) => {
2993
3003
  const index = getIndexFromPosition(state, x, y);
2994
3004
  if (button === LeftClick && index === -1) {
@@ -3001,15 +3011,34 @@ const handlePointerDown = (state, button, x, y) => {
3001
3011
  return state;
3002
3012
  };
3003
3013
 
3004
- const EmptyString = '';
3005
-
3006
- const Fulfilled = 'fulfilled';
3007
- const Rejected = 'rejected';
3014
+ const handleUpload = async (state, dirents) => {
3015
+ const {
3016
+ root,
3017
+ pathSeparator
3018
+ } = state;
3019
+ for (const dirent of dirents) {
3020
+ // TODO switch
3021
+ // TODO symlink might not be possible to be copied
3022
+ // TODO create folder if type is 2
3023
+ if (dirent.type === /* File */1) {
3024
+ // TODO reading text might be inefficient for binary files
3025
+ // but not sure how else to send them via jsonrpc
3026
+ const content = await dirent.file.text();
3027
+ const absolutePath = [root, dirent.file.name].join(pathSeparator);
3028
+ await writeFile(absolutePath, content);
3029
+ }
3030
+ }
3031
+ };
3008
3032
 
3009
3033
  const getWorkspacePath = () => {
3010
3034
  return invoke('Workspace.getPath');
3011
3035
  };
3012
3036
 
3037
+ const EmptyString = '';
3038
+
3039
+ const Fulfilled = 'fulfilled';
3040
+ const Rejected = 'rejected';
3041
+
3013
3042
  // TODO viewlet should only have create and refresh functions
3014
3043
  // every thing else can be in a separate module <viewlet>.lazy.js
3015
3044
  // and <viewlet>.ipc.js
@@ -3155,6 +3184,55 @@ const loadContent = async (state, savedState) => {
3155
3184
  };
3156
3185
  };
3157
3186
 
3187
+ const handleWorkspaceChange = async state => {
3188
+ const newRoot = await getWorkspacePath();
3189
+ const state1 = {
3190
+ ...state,
3191
+ root: newRoot
3192
+ };
3193
+ const newState = await loadContent(state1, undefined);
3194
+ return newState;
3195
+ };
3196
+
3197
+ const ExplorerEditBox = FocusExplorerEditBox;
3198
+
3199
+ const setFocus = key => {
3200
+ return invoke('Focus.setFocus', key);
3201
+ };
3202
+
3203
+ const newDirent = async (state, editingType) => {
3204
+ // TODO make focus functional instead of side effect
3205
+ await setFocus(ExplorerEditBox);
3206
+ // TODO do it like vscode, select position between folders and files
3207
+ const {
3208
+ focusedIndex,
3209
+ items
3210
+ } = state;
3211
+ if (focusedIndex >= 0) {
3212
+ const dirent = items[focusedIndex];
3213
+ if (dirent.type === Directory) {
3214
+ // TODO handle error
3215
+ // @ts-ignore
3216
+ await handleClickDirectory(state, dirent);
3217
+ }
3218
+ }
3219
+ return {
3220
+ ...state,
3221
+ editingIndex: focusedIndex,
3222
+ editingType,
3223
+ editingValue: ''
3224
+ };
3225
+ };
3226
+
3227
+ // TODO much shared logic with newFolder
3228
+ const newFile = state => {
3229
+ return newDirent(state, CreateFile);
3230
+ };
3231
+
3232
+ const newFolder = state => {
3233
+ return newDirent(state, CreateFolder);
3234
+ };
3235
+
3158
3236
  const getContaingingFolder = (root, dirents, focusedIndex, pathSeparator) => {
3159
3237
  if (focusedIndex < 0) {
3160
3238
  return root;
@@ -3177,9 +3255,127 @@ const openContainingFolder = async state => {
3177
3255
  return state;
3178
3256
  };
3179
3257
 
3258
+ // TODO support multiselection and removing multiple dirents
3259
+ const removeDirent = async state => {
3260
+ if (state.focusedIndex < 0) {
3261
+ return state;
3262
+ }
3263
+ const dirent = getFocusedDirent$1(state);
3264
+ const absolutePath = dirent.path;
3265
+ try {
3266
+ // TODO handle error
3267
+ await remove(absolutePath);
3268
+ } catch (error) {
3269
+ // TODO vscode shows error as alert (no stacktrace) and retry button
3270
+ // maybe should show alert as well, but where to put stacktrace?
3271
+ // on web should probably show notification (dialog)
3272
+ // ErrorHandling.handleError(error)
3273
+ // await ErrorHandling.showErrorDialog(error)
3274
+ return;
3275
+ }
3276
+ // TODO avoid state mutation
3277
+ const newVersion = ++state.version;
3278
+ // TODO race condition
3279
+ // const newState = await loadContent(state:any)
3280
+ if (state.version !== newVersion || state.disposed) {
3281
+ return state;
3282
+ }
3283
+ // TODO is it possible to make this more functional instead of mutating state?
3284
+ // maybe every function returns a new state?
3285
+ const index = state.items.indexOf(dirent);
3286
+ let deleteEnd = index + 1;
3287
+ for (; deleteEnd < state.items.length; deleteEnd++) {
3288
+ if (state.items[deleteEnd].depth <= dirent.depth) {
3289
+ break;
3290
+ }
3291
+ }
3292
+ const deleteCount = deleteEnd - index;
3293
+ const newDirents = [...state.items];
3294
+ newDirents.splice(index, deleteCount);
3295
+ let indexToFocus = -1;
3296
+ if (newDirents.length === 0) {
3297
+ indexToFocus = -1;
3298
+ } else if (index < state.focusedIndex) {
3299
+ indexToFocus = state.focusedIndex - 1;
3300
+ } else if (index === state.focusedIndex) {
3301
+ indexToFocus = Math.max(state.focusedIndex - 1, 0);
3302
+ } else {
3303
+ indexToFocus = Math.max(state.focusedIndex - 1, 0);
3304
+ }
3305
+ return {
3306
+ ...state,
3307
+ items: newDirents,
3308
+ focusedIndex: indexToFocus
3309
+ };
3310
+ };
3311
+
3312
+ const renameDirent = state => {
3313
+ const {
3314
+ focusedIndex,
3315
+ items
3316
+ } = state;
3317
+ const item = items[focusedIndex];
3318
+ // Focus.setFocus(FocusKey.ExplorerEditBox)
3319
+ return {
3320
+ ...state,
3321
+ editingIndex: focusedIndex,
3322
+ editingType: Rename$1,
3323
+ editingValue: item.name
3324
+ };
3325
+ };
3326
+
3327
+ const getIconVirtualDom = (icon, type = Div) => {
3328
+ return {
3329
+ type,
3330
+ className: `MaskIcon MaskIcon${icon}`,
3331
+ role: None$2,
3332
+ childCount: 0
3333
+ };
3334
+ };
3335
+
3336
+ const getActionButtonVirtualDom = action => {
3337
+ const {
3338
+ id,
3339
+ icon,
3340
+ command
3341
+ } = action;
3342
+ return [{
3343
+ type: Button,
3344
+ className: IconButton,
3345
+ title: id,
3346
+ 'data-command': command,
3347
+ childCount: 1
3348
+ }, getIconVirtualDom(icon)];
3349
+ };
3350
+
3351
+ const getActionVirtualDom = action => {
3352
+ switch (action.type) {
3353
+ case Button$2:
3354
+ return getActionButtonVirtualDom(action);
3355
+ default:
3356
+ return [];
3357
+ }
3358
+ };
3359
+
3360
+ const getActionsVirtualDom = actions => {
3361
+ return [{
3362
+ type: Div,
3363
+ className: Actions,
3364
+ role: ToolBar,
3365
+ childCount: actions.length
3366
+ }, ...actions.flatMap(getActionVirtualDom)];
3367
+ };
3368
+
3369
+ const renderActions = state => {
3370
+ const actions = getActions(state.root);
3371
+ const dom = getActionsVirtualDom(actions);
3372
+ return dom;
3373
+ };
3374
+
3180
3375
  const getSavedRoot = (savedState, workspacePath) => {
3181
3376
  return workspacePath;
3182
3377
  };
3378
+
3183
3379
  const restoreState = savedState => {
3184
3380
  if (!savedState) {
3185
3381
  return {
@@ -3204,6 +3400,21 @@ const restoreState = savedState => {
3204
3400
  };
3205
3401
  };
3206
3402
 
3403
+ const updateIcon = dirent => {
3404
+ return {
3405
+ ...dirent,
3406
+ icon: getIcon()
3407
+ };
3408
+ };
3409
+
3410
+ const updateIcons = state => {
3411
+ const newDirents = state.items.map(updateIcon);
3412
+ return {
3413
+ ...state,
3414
+ items: newDirents
3415
+ };
3416
+ };
3417
+
3207
3418
  const getIndex = (dirents, uri) => {
3208
3419
  for (let i = 0; i < dirents.length; i++) {
3209
3420
  const dirent = dirents[i];
@@ -3452,9 +3663,45 @@ const saveState = state => {
3452
3663
  };
3453
3664
  };
3454
3665
 
3666
+ const setDeltaY = (state, deltaY) => {
3667
+ const {
3668
+ itemHeight,
3669
+ height,
3670
+ items
3671
+ } = state;
3672
+ if (deltaY < 0) {
3673
+ deltaY = 0;
3674
+ } else if (deltaY > items.length * itemHeight - height) {
3675
+ deltaY = Math.max(items.length * itemHeight - height, 0);
3676
+ }
3677
+ if (state.deltaY === deltaY) {
3678
+ return state;
3679
+ }
3680
+ const minLineY = Math.round(deltaY / itemHeight);
3681
+ const maxLineY = minLineY + Math.round(height / itemHeight);
3682
+ return {
3683
+ ...state,
3684
+ deltaY,
3685
+ minLineY,
3686
+ maxLineY
3687
+ };
3688
+ };
3689
+
3690
+ const updateEditingValue = (state, value) => {
3691
+ const editingIcon = getFileIcon({
3692
+ name: value
3693
+ });
3694
+ return {
3695
+ ...state,
3696
+ editingValue: value,
3697
+ editingIcon
3698
+ };
3699
+ };
3700
+
3455
3701
  const commandMap = {
3456
3702
  'Explorer.acceptEdit': acceptEdit,
3457
3703
  'Explorer.cancelEdit': cancelEdit,
3704
+ 'Explorer.collapseAll': collapseAll$1,
3458
3705
  'Explorer.copyPath': copyPath$1,
3459
3706
  'Explorer.copyRelativePath': copyRelativePath$1,
3460
3707
  'Explorer.expandAll': expandAll,
@@ -3464,6 +3711,7 @@ const commandMap = {
3464
3711
  'Explorer.focusLast': focusLast,
3465
3712
  'Explorer.focusNext': focusNext,
3466
3713
  'Explorer.focusPrevious': focusPrevious,
3714
+ 'Explorer.getActions': getActions,
3467
3715
  'Explorer.getKeyBindings': getKeyBindings,
3468
3716
  'Explorer.getMenuEntries': getMenuEntries,
3469
3717
  'Explorer.getVirtualDom': getExplorerVirtualDom,
@@ -3476,18 +3724,27 @@ const commandMap = {
3476
3724
  'Explorer.handleClickCurrentButKeepFocus': handleClickCurrentButKeepFocus,
3477
3725
  'Explorer.handleClickOpenFolder': handleClickOpenFolder,
3478
3726
  'Explorer.handleContextMenu': handleContextMenu,
3727
+ 'Explorer.handleCopy': handleCopy,
3479
3728
  'Explorer.handleDrop': handleDrop,
3480
3729
  'Explorer.handleIconThemeChange': handleIconThemeChange,
3481
3730
  'Explorer.handlePaste': handlePaste,
3482
3731
  'Explorer.handlePointerDown': handlePointerDown,
3732
+ 'Explorer.handleUpload': handleUpload,
3483
3733
  'Explorer.handleWheel': handleWheel,
3734
+ 'Explorer.handleWorkspaceChange': handleWorkspaceChange,
3484
3735
  'Explorer.loadContent': loadContent,
3736
+ 'Explorer.newFile': newFile,
3737
+ 'Explorer.newFolder': newFolder,
3485
3738
  'Explorer.openContainingFolder': openContainingFolder,
3486
3739
  'Explorer.removeDirent': removeDirent,
3487
3740
  'Explorer.renameDirent': renameDirent,
3741
+ 'Explorer.renderActions': renderActions,
3488
3742
  'Explorer.restoreState': restoreState,
3489
3743
  'Explorer.revealItem': revealItem,
3490
- 'Explorer.saveState': saveState
3744
+ 'Explorer.saveState': saveState,
3745
+ 'Explorer.setDeltaY': setDeltaY,
3746
+ 'Explorer.updateEditingValue': updateEditingValue,
3747
+ 'Explorer.updateIcons': updateIcons
3491
3748
  };
3492
3749
 
3493
3750
  const listen = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/explorer-view",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Explorer Worker",
5
5
  "main": "dist/explorerViewWorkerMain.js",
6
6
  "type": "module",