@lvce-editor/explorer-view 2.36.0 → 2.38.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,74 +1286,140 @@ 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
1254
1391
  };
1255
1392
  };
1256
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
+ }
1418
+ };
1419
+ processChildren('', 0);
1420
+ return items;
1421
+ };
1422
+
1257
1423
  const acceptCreate = async (state, newDirentType, createFn) => {
1258
1424
  const {
1259
1425
  editingValue,
@@ -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
  }
@@ -1485,6 +1653,7 @@ const cancelEditCreate = (state, keepFocus) => {
1485
1653
  focused: keepFocus,
1486
1654
  editingIndex: -1,
1487
1655
  editingValue: '',
1656
+ editingErrorMessage: '',
1488
1657
  editingType: None$5,
1489
1658
  focus: List
1490
1659
  };
@@ -1521,6 +1690,7 @@ const cancelEditRename = (state, keepFocus) => {
1521
1690
  editingIndex: -1,
1522
1691
  editingValue: '',
1523
1692
  editingType: None$5,
1693
+ editingErrorMessage: '',
1524
1694
  focus: List
1525
1695
  };
1526
1696
  };
@@ -1546,10 +1716,6 @@ const cancelTypeAhead = state => {
1546
1716
  };
1547
1717
  };
1548
1718
 
1549
- const isTopLevel = dirent => {
1550
- return dirent.depth === 1;
1551
- };
1552
-
1553
1719
  const toCollapsedDirent = dirent => {
1554
1720
  if (dirent.type === DirectoryExpanded) {
1555
1721
  return {
@@ -1827,120 +1993,6 @@ const diff2 = uid => {
1827
1993
  return result;
1828
1994
  };
1829
1995
 
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
1996
  const expandAll = async state => {
1945
1997
  const {
1946
1998
  items,
@@ -4194,12 +4246,22 @@ const renderFocus = (oldState, newState) => {
4194
4246
  return [];
4195
4247
  };
4196
4248
 
4249
+ const getErrorMessagePosition = (itemHeight, focusedIndex, minLineY, depth, indent, fileIconWidth, padding) => {
4250
+ const top = itemHeight * (focusedIndex - minLineY + 1);
4251
+ const left = depth * indent + fileIconWidth + padding;
4252
+ return {
4253
+ top,
4254
+ left
4255
+ };
4256
+ };
4257
+
4197
4258
  const Actions = 'Actions';
4198
4259
  const Button$2 = 'Button';
4199
4260
  const ButtonNarrow = 'ButtonNarrow';
4200
4261
  const ButtonPrimary = 'ButtonPrimary';
4201
4262
  const ButtonWide = 'ButtonWide';
4202
4263
  const Chevron = 'Chevron';
4264
+ const ExplorerErrorMessage = 'ExplorerErrorMessage';
4203
4265
  const Empty = '';
4204
4266
  const Explorer = 'Explorer';
4205
4267
  const ExplorerDropTarget = 'DropTarget';
@@ -4207,7 +4269,6 @@ const ExplorerInputBox = 'ExplorerInputBox';
4207
4269
  const FileIcon = 'FileIcon';
4208
4270
  const FocusOutline = 'FocusOutline';
4209
4271
  const IconButton = 'IconButton';
4210
- const InputBox = 'InputBox';
4211
4272
  const InputValidationError = 'InputValidationError';
4212
4273
  const Label = 'Label';
4213
4274
  const ListItems = 'ListItems';
@@ -4234,20 +4295,6 @@ const WelcomeMessage = 'WelcomeMessage';
4234
4295
  // only numbers are compared. it could also make rendering faster,
4235
4296
  // since less data is transferred to renderer process
4236
4297
 
4237
- const HandleClick = 'handleClick';
4238
- const HandleClickOpenFolder = 'handleClickOpenFolder';
4239
- const HandleContextMenu = 'handleContextMenu';
4240
- const HandleDragLeave = 'handleDragLeave';
4241
- const HandleDragOver = 'handleDragOver';
4242
- const HandleDrop = 'handleDrop';
4243
- const HandleEditingInput = 'handleEditingInput';
4244
- const HandleInputBlur = 'handleInputBlur';
4245
- const HandleInputClick = 'handleInputClick';
4246
- const HandleListBlur = 'handleListBlur';
4247
- const HandleListFocus = 'handleListFocus';
4248
- const HandlePointerDown = 'handlePointerDown';
4249
- const HandleWheel = 'handleWheel';
4250
-
4251
4298
  const mergeClassNames = (...classNames) => {
4252
4299
  return classNames.filter(Boolean).join(' ');
4253
4300
  };
@@ -4272,6 +4319,33 @@ const Input = 6;
4272
4319
  const Img = 17;
4273
4320
  const P = 50;
4274
4321
 
4322
+ const getErrorMessageDom = (errorMessage, errorMessageLeft, errorMessageTop) => {
4323
+ if (!errorMessage) {
4324
+ return [];
4325
+ }
4326
+ const translateString = position(errorMessageLeft, errorMessageTop);
4327
+ return [{
4328
+ type: Div,
4329
+ className: mergeClassNames(ExplorerErrorMessage),
4330
+ childCount: 1,
4331
+ translate: translateString
4332
+ }, text(errorMessage)];
4333
+ };
4334
+
4335
+ const HandleClick = 'handleClick';
4336
+ const HandleClickOpenFolder = 'handleClickOpenFolder';
4337
+ const HandleContextMenu = 'handleContextMenu';
4338
+ const HandleDragLeave = 'handleDragLeave';
4339
+ const HandleDragOver = 'handleDragOver';
4340
+ const HandleDrop = 'handleDrop';
4341
+ const HandleEditingInput = 'handleEditingInput';
4342
+ const HandleInputBlur = 'handleInputBlur';
4343
+ const HandleInputClick = 'handleInputClick';
4344
+ const HandleListBlur = 'handleListBlur';
4345
+ const HandleListFocus = 'handleListFocus';
4346
+ const HandlePointerDown = 'handlePointerDown';
4347
+ const HandleWheel = 'handleWheel';
4348
+
4275
4349
  const getExplorerWelcomeVirtualDom = isWide => {
4276
4350
  return [{
4277
4351
  type: Div,
@@ -4328,9 +4402,9 @@ const getFileIconVirtualDom = icon => {
4328
4402
 
4329
4403
  const getInputClassName = hasEditingError => {
4330
4404
  if (hasEditingError) {
4331
- return mergeClassNames(InputBox, ExplorerInputBox, InputValidationError);
4405
+ return mergeClassNames(ExplorerInputBox, InputValidationError);
4332
4406
  }
4333
- return mergeClassNames(InputBox, ExplorerInputBox);
4407
+ return ExplorerInputBox;
4334
4408
  };
4335
4409
 
4336
4410
  const getInputDom = hasEditingError => {
@@ -4464,20 +4538,32 @@ const getScrollBarVirtualDom = (scrollBarHeight, scrollBarTop) => {
4464
4538
  }];
4465
4539
  };
4466
4540
 
4467
- const parentNode = {
4468
- type: Div,
4469
- childCount: 2,
4470
- className: mergeClassNames(Viewlet, Explorer),
4471
- role: 'none'
4541
+ const getChildCount = (scrollBarDomLength, errorDomLength) => {
4542
+ let childCount = 1;
4543
+ if (scrollBarDomLength > 0) {
4544
+ childCount++;
4545
+ }
4546
+ if (errorDomLength > 0) {
4547
+ childCount++;
4548
+ }
4549
+ return childCount;
4472
4550
  };
4473
- const getExplorerVirtualDom = (visibleItems, focusedIndex, root, isWide, focused, dropTargets, height, contentHeight, scrollTop) => {
4551
+ const getExplorerVirtualDom = (visibleItems, focusedIndex, root, isWide, focused, dropTargets, height, contentHeight, scrollTop, errorMessage, errorMessageTop, errorMessageLeft) => {
4474
4552
  if (!root) {
4475
4553
  return getExplorerWelcomeVirtualDom(isWide);
4476
4554
  }
4477
4555
  const scrollBarHeight = getScrollBarSize(height, contentHeight, 20);
4478
4556
  const scrollBarTop = Math.round(scrollTop / contentHeight * height);
4479
4557
  const scrollBarDom = getScrollBarVirtualDom(scrollBarHeight, scrollBarTop);
4480
- const dom = [parentNode, ...getListItemsVirtualDom(visibleItems, focusedIndex, focused, dropTargets), ...scrollBarDom];
4558
+ const errorDom = getErrorMessageDom(errorMessage, errorMessageLeft, errorMessageTop);
4559
+ const childCount = getChildCount(scrollBarDom.length, errorDom.length);
4560
+ const parentNode = {
4561
+ type: Div,
4562
+ childCount,
4563
+ className: mergeClassNames(Viewlet, Explorer),
4564
+ role: 'none'
4565
+ };
4566
+ const dom = [parentNode, ...getListItemsVirtualDom(visibleItems, focusedIndex, focused, dropTargets), ...scrollBarDom, ...errorDom];
4481
4567
  return dom;
4482
4568
  };
4483
4569
 
@@ -4597,7 +4683,17 @@ const renderItems = (oldState, newState) => {
4597
4683
  const visibleDirents = getVisibleExplorerItems(newState.items, newState.minLineY, newState.maxLineY, newState.focusedIndex, newState.editingIndex, newState.editingType, newState.editingValue, newState.editingErrorMessage, newState.icons, newState.useChevrons, newState.dropTargets, newState.editingIcon);
4598
4684
  const isWide = newState.width > 450;
4599
4685
  const contentHeight = newState.items.length * newState.itemHeight;
4600
- const dom = getExplorerVirtualDom(visibleDirents, newState.focusedIndex, newState.root, isWide, newState.focused, newState.dropTargets, newState.height, contentHeight, newState.deltaY);
4686
+ const depth = newState.items[newState.focusedIndex]?.depth || 0;
4687
+ const indent = 8;
4688
+ const padding = 10;
4689
+ const fileIconWidth = 16;
4690
+ const defaultPaddingLeft = 0;
4691
+ const chevronSpace = 22;
4692
+ const {
4693
+ top,
4694
+ left
4695
+ } = getErrorMessagePosition(newState.itemHeight, newState.focusedIndex, newState.minLineY, depth, indent, fileIconWidth, padding + defaultPaddingLeft + chevronSpace);
4696
+ const dom = getExplorerVirtualDom(visibleDirents, newState.focusedIndex, newState.root, isWide, newState.focused, newState.dropTargets, newState.height, contentHeight, newState.deltaY, newState.editingErrorMessage, top, left);
4601
4697
  return ['Viewlet.setDom2', dom];
4602
4698
  };
4603
4699
 
@@ -4847,22 +4943,6 @@ const getIndex = (dirents, uri) => {
4847
4943
  return -1;
4848
4944
  };
4849
4945
 
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
4946
  const getPathPartsToReveal = (root, pathParts, dirents) => {
4867
4947
  for (let i = 0; i < pathParts.length; i++) {
4868
4948
  const pathPart = pathParts[i];
@@ -4889,29 +4969,6 @@ const mergeVisibleWithHiddenItems = (visibleItems, hiddenItems) => {
4889
4969
  return unique;
4890
4970
  };
4891
4971
 
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
4972
  const scrollInto = (index, minLineY, maxLineY) => {
4916
4973
  const diff = maxLineY - minLineY;
4917
4974
  const smallerHalf = Math.floor(diff / 2);
@@ -4934,11 +4991,6 @@ const scrollInto = (index, minLineY, maxLineY) => {
4934
4991
  };
4935
4992
  };
4936
4993
 
4937
- const getPathPartChildren = async pathPart => {
4938
- const children = await getChildDirents(pathPart.pathSeparator, pathPart);
4939
- return children;
4940
- };
4941
-
4942
4994
  // TODO maybe just insert items into explorer and refresh whole explorer
4943
4995
  const revealItemHidden = async (state, uri) => {
4944
4996
  const {
@@ -4953,7 +5005,7 @@ const revealItemHidden = async (state, uri) => {
4953
5005
  return state;
4954
5006
  }
4955
5007
  const pathPartsToReveal = getPathPartsToReveal(root, pathParts, items);
4956
- const pathPartsChildren = await Promise.all(pathPartsToReveal.map(getPathPartChildren));
5008
+ const pathPartsChildren = await getPathPartsChildren(pathPartsToReveal);
4957
5009
  const pathPartsChildrenFlat = pathPartsChildren.flat();
4958
5010
  const orderedPathParts = orderDirents(pathPartsChildrenFlat);
4959
5011
  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.38.0",
4
4
  "description": "Explorer Worker",
5
5
  "repository": {
6
6
  "type": "git",