@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.
- package/dist/explorerViewWorkerMain.js +306 -254
- package/package.json +1 -1
|
@@ -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
|
|
1190
|
-
|
|
1191
|
-
return root;
|
|
1192
|
-
}
|
|
1193
|
-
return dirents[index].path;
|
|
1289
|
+
const sortExplorerItems = rawDirents => {
|
|
1290
|
+
return rawDirents.toSorted(compareDirent);
|
|
1194
1291
|
};
|
|
1195
1292
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
const
|
|
1250
|
-
|
|
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
|
-
|
|
1253
|
-
|
|
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
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
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 =
|
|
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
|
|
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(
|
|
4405
|
+
return mergeClassNames(ExplorerInputBox, InputValidationError);
|
|
4332
4406
|
}
|
|
4333
|
-
return
|
|
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
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
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
|
|
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
|
|
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
|
|
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);
|