@lvce-editor/explorer-view 2.36.0 → 2.37.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.
@@ -932,6 +932,9 @@ const dirname = (pathSeparator, path) => {
932
932
  }
933
933
  return path.slice(0, index);
934
934
  };
935
+ const dirname2 = path => {
936
+ return dirname('/', path);
937
+ };
935
938
  const join = (pathSeparator, ...parts) => {
936
939
  return parts.join(pathSeparator);
937
940
  };
@@ -960,6 +963,21 @@ const createNewDirentsAccept = async (newFileName, pathSeparator, absolutePath,
960
963
  }
961
964
  };
962
965
 
966
+ const createTree = (items, root) => {
967
+ const tree = Object.create(null);
968
+ const rootLength = root.length;
969
+ for (const item of items) {
970
+ const relativePath = item.path.slice(rootLength);
971
+ const dirname = dirname2(relativePath);
972
+ tree[dirname] ||= [];
973
+ tree[dirname].push({
974
+ name: item.name,
975
+ type: item.type
976
+ });
977
+ }
978
+ return tree;
979
+ };
980
+
963
981
  const None$5 = 0;
964
982
  const CreateFile = 1;
965
983
  const CreateFolder = 2;
@@ -1158,6 +1176,88 @@ const getFileIcons = async (dirents, fileIconCache) => {
1158
1176
  };
1159
1177
  };
1160
1178
 
1179
+ const getParentFolder = (dirents, index, root) => {
1180
+ if (index < 0) {
1181
+ return root;
1182
+ }
1183
+ return dirents[index].path;
1184
+ };
1185
+
1186
+ const getPathParts = (root, uri, pathSeparator) => {
1187
+ const parts = [];
1188
+ let index = root.length - 1;
1189
+ let depth = 0;
1190
+ while ((index = uri.indexOf('/', index + 1)) !== -1) {
1191
+ const partUri = uri.slice(0, index);
1192
+ parts.push({
1193
+ path: partUri,
1194
+ depth: depth++,
1195
+ root,
1196
+ pathSeparator
1197
+ });
1198
+ }
1199
+ return parts;
1200
+ };
1201
+
1202
+ const isSymbolicLink = dirent => {
1203
+ return dirent.type === Symlink;
1204
+ };
1205
+
1206
+ const hasSymbolicLinks = rawDirents => {
1207
+ return rawDirents.some(isSymbolicLink);
1208
+ };
1209
+
1210
+ const ENOENT = 'ENOENT';
1211
+
1212
+ const getSymlinkType = type => {
1213
+ switch (type) {
1214
+ case File:
1215
+ return SymLinkFile;
1216
+ case Directory:
1217
+ return SymLinkFolder;
1218
+ default:
1219
+ return Symlink;
1220
+ }
1221
+ };
1222
+
1223
+ // TODO maybe resolving of symbolic links should happen in shared process?
1224
+ // so that there is less code and less work in the frontend
1225
+ const resolveSymbolicLink = async (uri, rawDirent) => {
1226
+ try {
1227
+ // TODO support windows paths
1228
+ const absolutePath = uri + '/' + rawDirent.name;
1229
+ const type = await stat(absolutePath);
1230
+ const symLinkType = getSymlinkType(type);
1231
+ return {
1232
+ name: rawDirent.name,
1233
+ type: symLinkType
1234
+ };
1235
+ } catch (error) {
1236
+ // @ts-ignore
1237
+ if (error && error.code === ENOENT) {
1238
+ return {
1239
+ name: rawDirent.name,
1240
+ type: SymLinkFile
1241
+ };
1242
+ }
1243
+ console.error(`Failed to resolve symbolic link for ${rawDirent.name}: ${error}`);
1244
+ return rawDirent;
1245
+ }
1246
+ };
1247
+ const resolveSymbolicLinks = async (uri, rawDirents) => {
1248
+ const promises = [];
1249
+ for (const rawDirent of rawDirents) {
1250
+ if (isSymbolicLink(rawDirent)) {
1251
+ const resolvedDirent = resolveSymbolicLink(uri, rawDirent);
1252
+ promises.push(resolvedDirent);
1253
+ } else {
1254
+ promises.push(rawDirent);
1255
+ }
1256
+ }
1257
+ const resolvedDirents = await Promise.all(promises);
1258
+ return resolvedDirents;
1259
+ };
1260
+
1161
1261
  const RE_CHARACTERS = /^[a-zA-Z.-]+$/;
1162
1262
  const compareStringNumeric = (a, b) => {
1163
1263
  if (RE_CHARACTERS.test(a) && RE_CHARACTERS.test(b)) {
@@ -1186,72 +1286,138 @@ const compareDirent = (direntA, direntB) => {
1186
1286
  return compareDirentType(direntA, direntB) || compareDirentName(direntA, direntB);
1187
1287
  };
1188
1288
 
1189
- const getParentFolder = (dirents, index, root) => {
1190
- if (index < 0) {
1191
- return root;
1192
- }
1193
- return dirents[index].path;
1289
+ const sortExplorerItems = rawDirents => {
1290
+ return rawDirents.toSorted(compareDirent);
1194
1291
  };
1195
1292
 
1196
- const getNewDirentsAccept = (items, focusedIndex, editingValue, root, pathSeparator, newDirentType) => {
1197
- const newFileName = editingValue;
1198
- const parentFolder = getParentFolder(items, focusedIndex, root);
1199
- const absolutePath = [parentFolder, newFileName].join(pathSeparator);
1200
- const parentDirent = focusedIndex >= 0 ? items[focusedIndex] : {
1201
- depth: 0};
1202
- const depth = parentDirent.depth + 1;
1203
- const newDirent = {
1204
- path: absolutePath,
1205
- // @ts-ignore
1206
- posInSet: -1,
1207
- setSize: 1,
1208
- depth,
1209
- name: newFileName,
1210
- type: newDirentType,
1293
+ // TODO figure out whether this uses too much memory (name,path -> redundant, depth could be computed on demand)
1294
+ const toDisplayDirent = (parentDirent, rawDirent, index, length) => {
1295
+ const path = join2(parentDirent.path, rawDirent.name);
1296
+ return {
1297
+ name: rawDirent.name,
1298
+ posInSet: index + 1,
1299
+ setSize: length,
1300
+ depth: parentDirent.depth + 1,
1301
+ type: rawDirent.type,
1302
+ path,
1303
+ // TODO storing absolute path might be too costly, could also store relative path here
1211
1304
  icon: '',
1212
1305
  selected: false
1213
1306
  };
1214
- // @ts-ignore
1215
- newDirent.icon = '';
1216
- let insertIndex = focusedIndex;
1217
- let deltaPosInSet = 0;
1218
- let posInSet = 1;
1219
- let setSize = 1;
1220
- let i = Math.max(focusedIndex, -1) + 1;
1221
- // TODO update posinset and setsize of all affected dirents
1222
- for (; i < items.length; i++) {
1223
- const dirent = items[i];
1224
- if (dirent.depth !== depth) {
1225
- break;
1226
- }
1227
- const compareResult = compareDirent(dirent, newDirent);
1228
- if (compareResult === 1) {
1229
- insertIndex = i - 1;
1230
- deltaPosInSet = 1 - 1;
1231
- break;
1232
- } else {
1233
- // @ts-ignore
1234
- posInSet = dirent.posInSet + 1;
1235
- // @ts-ignore
1236
- setSize = dirent.setSize + 1;
1237
- // @ts-ignore
1238
- insertIndex = i;
1307
+ };
1308
+
1309
+ const toDisplayDirents = (pathSeparator, rawDirents, parentDirent, excluded) => {
1310
+ rawDirents = sortExplorerItems(rawDirents);
1311
+ const result = [];
1312
+ const visibleItems = rawDirents.filter(item => {
1313
+ if (excluded.includes(item.name)) {
1314
+ return false;
1239
1315
  }
1240
- // @ts-ignore
1241
- dirent.setSize++;
1242
- // @ts-ignore
1243
- dirent.posInSet += deltaPosInSet;
1316
+ return true;
1317
+ });
1318
+ const count = visibleItems.length;
1319
+ for (let i = 0; i < visibleItems.length; i++) {
1320
+ const rawDirent = visibleItems[i];
1321
+ result.push(toDisplayDirent(parentDirent, rawDirent, i, count));
1244
1322
  }
1245
- // @ts-ignore
1246
- newDirent.setSize = setSize;
1247
- // @ts-ignore
1248
- newDirent.posInSet = posInSet;
1249
- const newItems = [...items];
1250
- newItems.splice(insertIndex + 1, 0, newDirent);
1323
+ return result;
1324
+ };
1325
+
1326
+ const getChildDirentsRaw = async uri => {
1327
+ const rawDirents = await readDirWithFileTypes(uri);
1328
+ array(rawDirents);
1329
+ if (hasSymbolicLinks(rawDirents)) {
1330
+ return resolveSymbolicLinks(uri, rawDirents);
1331
+ }
1332
+ return rawDirents;
1333
+ };
1334
+ const getChildDirents = async (pathSeparator, parentDirent, excluded = []) => {
1335
+ string(pathSeparator);
1336
+ object(parentDirent);
1337
+ // TODO use event/actor based code instead, this is impossible to cancel right now
1338
+ // also cancel updating when opening new folder
1339
+ // const dispose = state => state.pendingRequests.forEach(cancelRequest)
1340
+ // TODO should use FileSystem directly in this case because it is globally available anyway
1341
+ // and more typesafe than Command.execute
1342
+ // and more performant
1343
+ const uri = parentDirent.path;
1344
+ const rawDirents = await getChildDirentsRaw(uri);
1345
+ const displayDirents = toDisplayDirents(pathSeparator, rawDirents, parentDirent, excluded);
1346
+ return displayDirents;
1347
+ };
1348
+
1349
+ const isTopLevel = dirent => {
1350
+ return dirent.depth === 1;
1351
+ };
1352
+
1353
+ const orderDirents = dirents => {
1354
+ if (dirents.length === 0) {
1355
+ return dirents;
1356
+ }
1357
+ const withDeepChildren = (parent, processed) => {
1358
+ if (processed.has(parent.path)) {
1359
+ return [];
1360
+ }
1361
+ processed.add(parent.path);
1362
+ const children = [];
1363
+ for (const dirent of dirents) {
1364
+ if (dirent.depth === parent.depth + 1 && dirent.path.startsWith(parent.path)) {
1365
+ children.push(...withDeepChildren(dirent, processed));
1366
+ }
1367
+ }
1368
+ return [parent, ...children];
1369
+ };
1370
+ const topLevelDirents = dirents.filter(isTopLevel);
1371
+ const processed = new Set();
1372
+ const ordered = topLevelDirents.flatMap(dirent => withDeepChildren(dirent, processed));
1373
+ return ordered;
1374
+ };
1375
+
1376
+ const getPathPartChildren = async pathPart => {
1377
+ const children = await getChildDirents(pathPart.pathSeparator, pathPart);
1378
+ return children;
1379
+ };
1380
+ const getPathPartsChildren = async pathparts => {
1381
+ const pathPartsChildren = await Promise.all(pathparts.map(getPathPartChildren));
1382
+ const pathPartsChildrenFlat = pathPartsChildren.flat();
1383
+ const orderedPathParts = orderDirents(pathPartsChildrenFlat);
1384
+ return orderedPathParts;
1385
+ };
1386
+
1387
+ const mergeTrees = (a, b) => {
1251
1388
  return {
1252
- dirents: newItems,
1253
- newFocusedIndex: insertIndex + 1
1389
+ ...a,
1390
+ ...b
1391
+ };
1392
+ };
1393
+
1394
+ const treeToArray = (map, root) => {
1395
+ const items = [];
1396
+ const processChildren = (path, depth) => {
1397
+ const children = map[path];
1398
+ if (!children) {
1399
+ return;
1400
+ }
1401
+ const count = children.length;
1402
+ for (let i = 0; i < count; i++) {
1403
+ const child = children[i];
1404
+ const childPath = join2(path, child.name);
1405
+ const absolutePath = `${root}${childPath}`;
1406
+ items.push({
1407
+ depth,
1408
+ posInSet: i + 1,
1409
+ setSize: count,
1410
+ icon: '',
1411
+ path: absolutePath,
1412
+ selected: false,
1413
+ name: child.name,
1414
+ type: child.type
1415
+ });
1416
+ processChildren(childPath, depth + 1);
1417
+ }
1254
1418
  };
1419
+ processChildren('', 0);
1420
+ return items;
1255
1421
  };
1256
1422
 
1257
1423
  const acceptCreate = async (state, newDirentType, createFn) => {
@@ -1283,10 +1449,14 @@ const acceptCreate = async (state, newDirentType, createFn) => {
1283
1449
  if (!successful) {
1284
1450
  return state;
1285
1451
  }
1286
- const {
1287
- dirents,
1288
- newFocusedIndex
1289
- } = getNewDirentsAccept(items, focusedIndex, editingValue, root, pathSeparator, newDirentType);
1452
+ const pathPaths = getPathParts(root, absolutePath, pathSeparator);
1453
+ const children = await getPathPartsChildren(pathPaths);
1454
+ const tree = createTree(items, root);
1455
+ const childTree = createTree(children, root);
1456
+ const merged = mergeTrees(tree, childTree);
1457
+ const newItems = treeToArray(merged, root);
1458
+ const dirents = newItems;
1459
+ const newFocusedIndex = newItems.findIndex(dirent => dirent.path === absolutePath);
1290
1460
  const maxLineY = getExplorerMaxLineY(minLineY, height, itemHeight, dirents.length);
1291
1461
  const visible = dirents.slice(minLineY, maxLineY);
1292
1462
  const {
@@ -1413,7 +1583,7 @@ const createNewDirentsRename = async (renamedDirent, editingValue, pathSeparator
1413
1583
  try {
1414
1584
  // TODO this does not work with rename of nested file
1415
1585
  const oldAbsolutePath = renamedDirent.path;
1416
- const oldParentPath = dirname(pathSeparator, oldAbsolutePath);
1586
+ const oldParentPath = dirname2(oldAbsolutePath);
1417
1587
  const newAbsolutePath = join2(oldParentPath, editingValue);
1418
1588
  await rename$1(oldAbsolutePath, newAbsolutePath);
1419
1589
  } catch (error) {
@@ -1427,11 +1597,9 @@ const acceptRename = async state => {
1427
1597
  const {
1428
1598
  editingIndex,
1429
1599
  editingValue,
1430
- items,
1431
- pathSeparator
1432
- } = state;
1600
+ items} = state;
1433
1601
  const renamedDirent = items[editingIndex];
1434
- const successful = await createNewDirentsRename(renamedDirent, editingValue, pathSeparator);
1602
+ const successful = await createNewDirentsRename(renamedDirent, editingValue);
1435
1603
  if (!successful) {
1436
1604
  return state;
1437
1605
  }
@@ -1546,10 +1714,6 @@ const cancelTypeAhead = state => {
1546
1714
  };
1547
1715
  };
1548
1716
 
1549
- const isTopLevel = dirent => {
1550
- return dirent.depth === 1;
1551
- };
1552
-
1553
1717
  const toCollapsedDirent = dirent => {
1554
1718
  if (dirent.type === DirectoryExpanded) {
1555
1719
  return {
@@ -1827,120 +1991,6 @@ const diff2 = uid => {
1827
1991
  return result;
1828
1992
  };
1829
1993
 
1830
- const isSymbolicLink = dirent => {
1831
- return dirent.type === Symlink;
1832
- };
1833
-
1834
- const hasSymbolicLinks = rawDirents => {
1835
- return rawDirents.some(isSymbolicLink);
1836
- };
1837
-
1838
- const ENOENT = 'ENOENT';
1839
-
1840
- const getSymlinkType = type => {
1841
- switch (type) {
1842
- case File:
1843
- return SymLinkFile;
1844
- case Directory:
1845
- return SymLinkFolder;
1846
- default:
1847
- return Symlink;
1848
- }
1849
- };
1850
-
1851
- // TODO maybe resolving of symbolic links should happen in shared process?
1852
- // so that there is less code and less work in the frontend
1853
- const resolveSymbolicLink = async (uri, rawDirent) => {
1854
- try {
1855
- // TODO support windows paths
1856
- const absolutePath = uri + '/' + rawDirent.name;
1857
- const type = await stat(absolutePath);
1858
- const symLinkType = getSymlinkType(type);
1859
- return {
1860
- name: rawDirent.name,
1861
- type: symLinkType
1862
- };
1863
- } catch (error) {
1864
- // @ts-ignore
1865
- if (error && error.code === ENOENT) {
1866
- return {
1867
- name: rawDirent.name,
1868
- type: SymLinkFile
1869
- };
1870
- }
1871
- console.error(`Failed to resolve symbolic link for ${rawDirent.name}: ${error}`);
1872
- return rawDirent;
1873
- }
1874
- };
1875
- const resolveSymbolicLinks = async (uri, rawDirents) => {
1876
- const promises = [];
1877
- for (const rawDirent of rawDirents) {
1878
- if (isSymbolicLink(rawDirent)) {
1879
- const resolvedDirent = resolveSymbolicLink(uri, rawDirent);
1880
- promises.push(resolvedDirent);
1881
- } else {
1882
- promises.push(rawDirent);
1883
- }
1884
- }
1885
- const resolvedDirents = await Promise.all(promises);
1886
- return resolvedDirents;
1887
- };
1888
-
1889
- const sortExplorerItems = rawDirents => {
1890
- return rawDirents.toSorted(compareDirent);
1891
- };
1892
-
1893
- const toDisplayDirents = (pathSeparator, rawDirents, parentDirent, excluded) => {
1894
- rawDirents = sortExplorerItems(rawDirents);
1895
- // TODO figure out whether this uses too much memory (name,path -> redundant, depth could be computed on demand)
1896
- const toDisplayDirent = (rawDirent, index) => {
1897
- const path = [parentDirent.path, rawDirent.name].join(pathSeparator);
1898
- return {
1899
- name: rawDirent.name,
1900
- posInSet: index + 1,
1901
- setSize: rawDirents.length,
1902
- depth: parentDirent.depth + 1,
1903
- type: rawDirent.type,
1904
- path,
1905
- // TODO storing absolute path might be too costly, could also store relative path here
1906
- icon: ''
1907
- };
1908
- };
1909
- const result = [];
1910
- let i = 0;
1911
- for (const rawDirent of rawDirents) {
1912
- if (excluded.includes(rawDirent.name)) {
1913
- continue;
1914
- }
1915
- result.push(toDisplayDirent(rawDirent, i));
1916
- i++;
1917
- }
1918
- return result;
1919
- };
1920
-
1921
- const getChildDirentsRaw = async uri => {
1922
- const rawDirents = await readDirWithFileTypes(uri);
1923
- array(rawDirents);
1924
- if (hasSymbolicLinks(rawDirents)) {
1925
- return resolveSymbolicLinks(uri, rawDirents);
1926
- }
1927
- return rawDirents;
1928
- };
1929
- const getChildDirents = async (pathSeparator, parentDirent, excluded = []) => {
1930
- string(pathSeparator);
1931
- object(parentDirent);
1932
- // TODO use event/actor based code instead, this is impossible to cancel right now
1933
- // also cancel updating when opening new folder
1934
- // const dispose = state => state.pendingRequests.forEach(cancelRequest)
1935
- // TODO should use FileSystem directly in this case because it is globally available anyway
1936
- // and more typesafe than Command.execute
1937
- // and more performant
1938
- const uri = parentDirent.path;
1939
- const rawDirents = await getChildDirentsRaw(uri);
1940
- const displayDirents = toDisplayDirents(pathSeparator, rawDirents, parentDirent, excluded);
1941
- return displayDirents;
1942
- };
1943
-
1944
1994
  const expandAll = async state => {
1945
1995
  const {
1946
1996
  items,
@@ -4847,22 +4897,6 @@ const getIndex = (dirents, uri) => {
4847
4897
  return -1;
4848
4898
  };
4849
4899
 
4850
- const getPathParts = (root, uri, pathSeparator) => {
4851
- const parts = [];
4852
- let index = root.length - 1;
4853
- let depth = 0;
4854
- while ((index = uri.indexOf('/', index + 1)) !== -1) {
4855
- const partUri = uri.slice(0, index);
4856
- parts.push({
4857
- path: partUri,
4858
- depth: depth++,
4859
- root,
4860
- pathSeparator
4861
- });
4862
- }
4863
- return parts;
4864
- };
4865
-
4866
4900
  const getPathPartsToReveal = (root, pathParts, dirents) => {
4867
4901
  for (let i = 0; i < pathParts.length; i++) {
4868
4902
  const pathPart = pathParts[i];
@@ -4889,29 +4923,6 @@ const mergeVisibleWithHiddenItems = (visibleItems, hiddenItems) => {
4889
4923
  return unique;
4890
4924
  };
4891
4925
 
4892
- const orderDirents = dirents => {
4893
- if (dirents.length === 0) {
4894
- return dirents;
4895
- }
4896
- const withDeepChildren = (parent, processed) => {
4897
- if (processed.has(parent.path)) {
4898
- return [];
4899
- }
4900
- processed.add(parent.path);
4901
- const children = [];
4902
- for (const dirent of dirents) {
4903
- if (dirent.depth === parent.depth + 1 && dirent.path.startsWith(parent.path)) {
4904
- children.push(...withDeepChildren(dirent, processed));
4905
- }
4906
- }
4907
- return [parent, ...children];
4908
- };
4909
- const topLevelDirents = dirents.filter(isTopLevel);
4910
- const processed = new Set();
4911
- const ordered = topLevelDirents.flatMap(dirent => withDeepChildren(dirent, processed));
4912
- return ordered;
4913
- };
4914
-
4915
4926
  const scrollInto = (index, minLineY, maxLineY) => {
4916
4927
  const diff = maxLineY - minLineY;
4917
4928
  const smallerHalf = Math.floor(diff / 2);
@@ -4934,11 +4945,6 @@ const scrollInto = (index, minLineY, maxLineY) => {
4934
4945
  };
4935
4946
  };
4936
4947
 
4937
- const getPathPartChildren = async pathPart => {
4938
- const children = await getChildDirents(pathPart.pathSeparator, pathPart);
4939
- return children;
4940
- };
4941
-
4942
4948
  // TODO maybe just insert items into explorer and refresh whole explorer
4943
4949
  const revealItemHidden = async (state, uri) => {
4944
4950
  const {
@@ -4953,7 +4959,7 @@ const revealItemHidden = async (state, uri) => {
4953
4959
  return state;
4954
4960
  }
4955
4961
  const pathPartsToReveal = getPathPartsToReveal(root, pathParts, items);
4956
- const pathPartsChildren = await Promise.all(pathPartsToReveal.map(getPathPartChildren));
4962
+ const pathPartsChildren = await getPathPartsChildren(pathPartsToReveal);
4957
4963
  const pathPartsChildrenFlat = pathPartsChildren.flat();
4958
4964
  const orderedPathParts = orderDirents(pathPartsChildrenFlat);
4959
4965
  const mergedDirents = mergeVisibleWithHiddenItems(items, orderedPathParts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/explorer-view",
3
- "version": "2.36.0",
3
+ "version": "2.37.0",
4
4
  "description": "Explorer Worker",
5
5
  "repository": {
6
6
  "type": "git",