@lvce-editor/explorer-view 1.1.0 → 1.3.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.
@@ -6,17 +6,17 @@ const create$4 = (method, params) => {
6
6
  params
7
7
  };
8
8
  };
9
- const state = {
9
+ const state$1 = {
10
10
  callbacks: Object.create(null)
11
11
  };
12
12
  const set = (id, fn) => {
13
- state.callbacks[id] = fn;
13
+ state$1.callbacks[id] = fn;
14
14
  };
15
15
  const get = id => {
16
- return state.callbacks[id];
16
+ return state$1.callbacks[id];
17
17
  };
18
18
  const remove$1 = id => {
19
- delete state.callbacks[id];
19
+ delete state$1.callbacks[id];
20
20
  };
21
21
  let id = 0;
22
22
  const create$3 = () => {
@@ -339,7 +339,7 @@ const send = (transport, method, ...params) => {
339
339
  const message = create$4(method, params);
340
340
  transport.send(message);
341
341
  };
342
- const invoke = (ipc, method, ...params) => {
342
+ const invoke$1 = (ipc, method, ...params) => {
343
343
  return invokeHelper(ipc, method, params, false);
344
344
  };
345
345
  const invokeAndTransfer = (ipc, method, ...params) => {
@@ -753,7 +753,7 @@ const createRpc = ipc => {
753
753
  send(ipc, method, ...params);
754
754
  },
755
755
  invoke(method, ...params) {
756
- return invoke(ipc, method, ...params);
756
+ return invoke$1(ipc, method, ...params);
757
757
  },
758
758
  invokeAndTransfer(method, ...params) {
759
759
  return invokeAndTransfer(ipc, method, ...params);
@@ -799,6 +799,16 @@ const WebWorkerRpcClient = {
799
799
  create: create$1
800
800
  };
801
801
 
802
+ const RE_CHARACTERS = /^[a-zA-Z.-]+$/;
803
+ const compareStringNumeric = (a, b) => {
804
+ if (RE_CHARACTERS.test(a) && RE_CHARACTERS.test(b)) {
805
+ return a < b ? -1 : 1;
806
+ }
807
+ return a.localeCompare(b, 'en', {
808
+ numeric: true
809
+ });
810
+ };
811
+
802
812
  const BlockDevice = 1;
803
813
  const CharacterDevice = 2;
804
814
  const Directory = 3;
@@ -811,6 +821,142 @@ const SymLinkFile = 10;
811
821
  const SymLinkFolder = 11;
812
822
  const Unknown = 12;
813
823
 
824
+ const priorityMapFoldersFirst = {
825
+ [Directory]: 1,
826
+ [SymLinkFolder]: 1,
827
+ [File]: 0,
828
+ [SymLinkFile]: 0,
829
+ [Unknown]: 0,
830
+ [Socket]: 0
831
+ };
832
+ const compareDirentType = (direntA, direntB) => {
833
+ return priorityMapFoldersFirst[direntB.type] - priorityMapFoldersFirst[direntA.type];
834
+ };
835
+ const compareDirentName = (direntA, direntB) => {
836
+ return compareStringNumeric(direntA.name, direntB.name);
837
+ };
838
+ const compareDirent = (direntA, direntB) => {
839
+ return compareDirentType(direntA, direntB) || compareDirentName(direntA, direntB);
840
+ };
841
+
842
+ const getFileIcon = ({
843
+ name
844
+ }) => {
845
+ return '';
846
+ };
847
+ const getIcon = dirent => {
848
+ return '';
849
+ };
850
+
851
+ // TODO use posInSet and setSize properties to compute more effectively
852
+ const computeExplorerRenamedDirent = (dirents, index, newName) => {
853
+ let startIndex = index;
854
+ let innerEndIndex = index + 1;
855
+ let insertIndex = -1;
856
+ let posInSet = -1;
857
+ const oldDirent = dirents[index];
858
+ const newDirent = {
859
+ ...oldDirent,
860
+ name: newName,
861
+ path: oldDirent.path.slice(0, -oldDirent.name.length) + newName,
862
+ icon: getFileIcon({
863
+ name: newName
864
+ })
865
+ };
866
+ const depth = newDirent.depth;
867
+ // TODO
868
+ for (; startIndex >= 0; startIndex--) {
869
+ const dirent = dirents[startIndex];
870
+ if (dirent.depth > depth) {
871
+ continue;
872
+ }
873
+ if (dirent.depth < depth) {
874
+ break;
875
+ }
876
+ if (compareDirent(dirent, newDirent) === 1) {
877
+ insertIndex = startIndex;
878
+ posInSet = dirent.posInSet;
879
+ // dirent.posInSet++
880
+ }
881
+ }
882
+ startIndex++;
883
+ for (; innerEndIndex < dirents.length; innerEndIndex++) {
884
+ const dirent = dirents[innerEndIndex];
885
+ if (dirent.depth <= depth) {
886
+ break;
887
+ }
888
+ dirent.path = newDirent.path + dirent.path.slice(oldDirent.path.length);
889
+ }
890
+ innerEndIndex--;
891
+ let endIndex = innerEndIndex + 1;
892
+ for (; endIndex < dirents.length; endIndex++) {
893
+ const dirent = dirents[endIndex];
894
+ if (dirent.depth > depth) {
895
+ continue;
896
+ }
897
+ if (dirent.depth < depth) {
898
+ break;
899
+ }
900
+ if (insertIndex === -1 && compareDirent(dirent, newDirent === -1)) {
901
+ for (; endIndex < dirents.length; endIndex++) {
902
+ }
903
+ insertIndex = endIndex;
904
+ posInSet = dirent.posInSet + 1;
905
+ }
906
+ }
907
+ endIndex--;
908
+ for (let j = startIndex; j < index; j++) {
909
+ const dirent = dirents[j];
910
+ if (dirent.depth === depth) {
911
+ dirent.posInSet++;
912
+ }
913
+ }
914
+ for (let j = index; j < endIndex; j++) {
915
+ const dirent = dirents[j];
916
+ if (dirent.depth === depth) {
917
+ dirent.posInSet--;
918
+ }
919
+ }
920
+
921
+ // for (let j = startIndex; j < index; j++) {
922
+ // const dirent = dirents[j]
923
+ // dirent.posInSet++
924
+ // }
925
+
926
+ if (insertIndex === -1) {
927
+ insertIndex = index;
928
+ return {
929
+ focusedIndex: index,
930
+ newDirents: [...dirents.slice(0, index), newDirent, ...dirents.slice(index + 1)]
931
+ };
932
+ }
933
+ newDirent.posInSet = posInSet;
934
+ const newDirents = [...dirents];
935
+ if (index < insertIndex) {
936
+ insertIndex--;
937
+ }
938
+ newDirents.splice(index, 1);
939
+ newDirents.splice(insertIndex, 0, newDirent);
940
+ return {
941
+ newDirents,
942
+ focusedIndex: insertIndex
943
+ };
944
+ };
945
+
946
+ const None$2 = 0;
947
+ const CreateFile = 1;
948
+ const CreateFolder = 2;
949
+ const Rename = 3;
950
+
951
+ const remove = async diren => {};
952
+ const readDirWithFileTypes = async uri => {};
953
+ const getPathSeparator$1 = async root => {};
954
+ const getRealPath = async path => {};
955
+ const stat = async dirent => {};
956
+ const createFile = async uri => {};
957
+ const mkdir = async uri => {};
958
+ const rename$1 = async (oldUri, newUri) => {};
959
+
814
960
  class AssertionError extends Error {
815
961
  constructor(message) {
816
962
  super(message);
@@ -845,6 +991,12 @@ const object = value => {
845
991
  throw new AssertionError('expected value to be of type object');
846
992
  }
847
993
  };
994
+ const number = value => {
995
+ const type = getType(value);
996
+ if (type !== 'number') {
997
+ throw new AssertionError('expected value to be of type number');
998
+ }
999
+ };
848
1000
  const array = value => {
849
1001
  const type = getType(value);
850
1002
  if (type !== 'array') {
@@ -858,119 +1010,193 @@ const string = value => {
858
1010
  }
859
1011
  };
860
1012
 
861
- const ENOENT = 'ENOENT';
862
-
863
- const remove = async diren => {};
864
- const readDirWithFileTypes = async uri => {};
865
- const getPathSeparator$1 = async root => {};
866
- const getRealPath = async path => {};
867
- const stat = async dirent => {};
868
-
869
- const getFileIcon = ({
870
- name
871
- }) => {
872
- return '';
873
- };
874
- const getIcon = dirent => {
875
- return '';
876
- };
877
-
878
- const RE_CHARACTERS = /^[a-zA-Z.-]+$/;
879
- const compareStringNumeric = (a, b) => {
880
- if (RE_CHARACTERS.test(a) && RE_CHARACTERS.test(b)) {
881
- return a < b ? -1 : 1;
1013
+ const dirname = (pathSeparator, path) => {
1014
+ const index = path.lastIndexOf(pathSeparator);
1015
+ if (index === -1) {
1016
+ return path;
882
1017
  }
883
- return a.localeCompare(b, 'en', {
884
- numeric: true
885
- });
1018
+ return path.slice(0, index);
886
1019
  };
887
1020
 
888
- const priorityMapFoldersFirst = {
889
- [Directory]: 1,
890
- [SymLinkFolder]: 1,
891
- [File]: 0,
892
- [SymLinkFile]: 0,
893
- [Unknown]: 0,
894
- [Socket]: 0
895
- };
896
- const compareDirentType = (direntA, direntB) => {
897
- return priorityMapFoldersFirst[direntB.type] - priorityMapFoldersFirst[direntA.type];
898
- };
899
- const compareDirentName = (direntA, direntB) => {
900
- return compareStringNumeric(direntA.name, direntB.name);
901
- };
902
- const compareDirent = (direntA, direntB) => {
903
- return compareDirentType(direntA, direntB) || compareDirentName(direntA, direntB);
904
- };
905
- const sortExplorerItems = rawDirents => {
906
- rawDirents.sort(compareDirent);
1021
+ const getParentFolder = (dirents, index, root) => {
1022
+ if (index < 0) {
1023
+ return root;
1024
+ }
1025
+ return dirents[index].path;
907
1026
  };
908
-
909
- const toDisplayDirents = (pathSeparator, rawDirents, parentDirent, excluded) => {
910
- sortExplorerItems(rawDirents);
911
- // TODO figure out whether this uses too much memory (name,path -> redundant, depth could be computed on demand)
912
- const toDisplayDirent = (rawDirent, index) => {
913
- const path = [parentDirent.path, rawDirent.name].join(pathSeparator);
914
- return {
915
- name: rawDirent.name,
916
- posInSet: index + 1,
917
- setSize: rawDirents.length,
918
- depth: parentDirent.depth + 1,
919
- type: rawDirent.type,
920
- path,
921
- // TODO storing absolute path might be too costly, could also store relative path here
922
- icon: getIcon()
923
- };
1027
+ const acceptCreate = async (state, newDirentType, createFn) => {
1028
+ const {
1029
+ focusedIndex,
1030
+ editingValue
1031
+ } = state;
1032
+ const newFileName = editingValue;
1033
+ if (!newFileName) {
1034
+ // TODO show error message that file name must not be empty
1035
+ // below input box
1036
+ // await ErrorHandling.showErrorDialog(new Error('file name must not be empty'))
1037
+ return state;
1038
+ }
1039
+ const parentFolder = getParentFolder(state.items, focusedIndex, state.root);
1040
+ const absolutePath = [parentFolder, newFileName].join(state.pathSeparator);
1041
+ // TODO better handle error
1042
+ try {
1043
+ await createFn(absolutePath);
1044
+ } catch (error) {
1045
+ // TODO display error
1046
+ return state;
1047
+ }
1048
+ const parentDirent = focusedIndex >= 0 ? state.items[focusedIndex] : {
1049
+ depth: 0,
1050
+ path: state.root
924
1051
  };
925
- const result = [];
926
- let i = 0;
927
- for (const rawDirent of rawDirents) {
928
- if (excluded.includes(rawDirent.name)) {
929
- continue;
1052
+ const depth = parentDirent.depth + 1;
1053
+ const newDirent = {
1054
+ path: absolutePath,
1055
+ posInSet: -1,
1056
+ setSize: 1,
1057
+ depth,
1058
+ name: newFileName,
1059
+ type: newDirentType,
1060
+ icon: ''
1061
+ };
1062
+ newDirent.icon = getIcon();
1063
+ let insertIndex = state.focusedIndex;
1064
+ let deltaPosInSet = 0;
1065
+ let posInSet = 1;
1066
+ let setSize = 1;
1067
+ let i = Math.max(state.focusedIndex, -1) + 1;
1068
+ const {
1069
+ items
1070
+ } = state;
1071
+ // TODO update posinset and setsize of all affected dirents
1072
+ for (; i < items.length; i++) {
1073
+ const dirent = items[i];
1074
+ if (dirent.depth !== depth) {
1075
+ break;
930
1076
  }
931
- result.push(toDisplayDirent(rawDirent, i));
932
- i++;
1077
+ const compareResult = compareDirent(dirent, newDirent);
1078
+ if (compareResult === 1) {
1079
+ insertIndex = i - 1;
1080
+ deltaPosInSet = 1 - 1;
1081
+ break;
1082
+ } else {
1083
+ posInSet = dirent.posInSet + 1;
1084
+ setSize = dirent.setSize + 1;
1085
+ insertIndex = i;
1086
+ }
1087
+ dirent.setSize++;
1088
+ dirent.posInSet += deltaPosInSet;
933
1089
  }
934
- return result;
1090
+ newDirent.setSize = setSize;
1091
+ newDirent.posInSet = posInSet;
1092
+ items.splice(insertIndex + 1, 0, newDirent);
1093
+ const newDirents = [...items];
1094
+ const newMaxlineY = Math.max(state.maxLineY, newDirents.length);
1095
+ return {
1096
+ ...state,
1097
+ items: newDirents,
1098
+ editingIndex: -1,
1099
+ focusedIndex: insertIndex + 1,
1100
+ editingType: None$2,
1101
+ maxLineY: newMaxlineY
1102
+ };
935
1103
  };
936
-
937
- const getIndexFromPosition = (state, eventX, eventY) => {
1104
+ const acceptRename = async state => {
938
1105
  const {
939
- y,
940
- itemHeight,
941
- items
1106
+ editingIndex,
1107
+ editingValue,
1108
+ items,
1109
+ pathSeparator
942
1110
  } = state;
943
- const index = Math.floor((eventY - y) / itemHeight);
944
- if (index < 0) {
945
- return 0;
946
- }
947
- if (index >= items.length) {
948
- return -1;
1111
+ const renamedDirent = items[editingIndex];
1112
+ try {
1113
+ // TODO this does not work with rename of nested file
1114
+ const oldAbsolutePath = renamedDirent.path;
1115
+ const oldParentPath = dirname(pathSeparator, oldAbsolutePath);
1116
+ const newAbsolutePath = [oldParentPath, editingValue].join(pathSeparator);
1117
+ await rename$1(oldAbsolutePath, newAbsolutePath);
1118
+ } catch (error) {
1119
+ // TODO
1120
+ // await ErrorHandling.showErrorDialog(error)
1121
+ return state;
949
1122
  }
950
- return index;
1123
+ const {
1124
+ newDirents,
1125
+ focusedIndex
1126
+ } = computeExplorerRenamedDirent(items, editingIndex, editingValue);
1127
+ // TODO move focused index
1128
+ state.items = newDirents;
1129
+ return {
1130
+ ...state,
1131
+ editingIndex: -1,
1132
+ editingValue: '',
1133
+ editingType: None$2,
1134
+ editingIcon: '',
1135
+ focusedIndex,
1136
+ focused: true
1137
+ };
951
1138
  };
952
- const getParentStartIndex = (dirents, index) => {
953
- const dirent = dirents[index];
954
- let startIndex = index - 1;
955
- while (startIndex >= 0 && dirents[startIndex].depth >= dirent.depth) {
956
- startIndex--;
1139
+ const acceptEdit = state => {
1140
+ const {
1141
+ editingType
1142
+ } = state;
1143
+ switch (editingType) {
1144
+ case CreateFile:
1145
+ return acceptCreate(state, File, createFile);
1146
+ case CreateFolder:
1147
+ return acceptCreate(state, Directory, mkdir);
1148
+ case Rename:
1149
+ return acceptRename(state);
1150
+ default:
1151
+ return state;
957
1152
  }
958
- return startIndex;
959
1153
  };
960
- const getParentEndIndex = (dirents, index) => {
961
- const dirent = dirents[index];
962
- let endIndex = index + 1;
963
- while (endIndex < dirents.length && dirents[endIndex].depth > dirent.depth) {
964
- endIndex++;
965
- }
966
- return endIndex;
1154
+
1155
+ const getFocusedDirent$1 = state => {
1156
+ const {
1157
+ focusedIndex,
1158
+ minLineY,
1159
+ items
1160
+ } = state;
1161
+ const dirent = items[focusedIndex + minLineY];
1162
+ return dirent;
967
1163
  };
1164
+
1165
+ const copyPath$1 = async state => {
1166
+ // await Command.execute(RendererWorkerCommandType.ClipBoardWriteText, /* text */ path)
1167
+ return state;
1168
+ };
1169
+
1170
+ const state = {
1171
+ rpc: undefined
1172
+ };
1173
+ const invoke = (method, ...params) => {
1174
+ const rpc = state.rpc;
1175
+ // @ts-ignore
1176
+ return rpc.invoke(method, ...params);
1177
+ };
1178
+ const setRpc = rpc => {
1179
+ state.rpc = rpc;
1180
+ };
1181
+
1182
+ const writeText = async text => {
1183
+ await invoke('ClipBoard.writeText', /* text */text);
1184
+ };
1185
+
1186
+ const copyRelativePath$1 = async state => {
1187
+ const dirent = getFocusedDirent$1(state);
1188
+ const relativePath = dirent.path.slice(1);
1189
+ // TODO handle error
1190
+ await writeText(relativePath);
1191
+ return state;
1192
+ };
1193
+
968
1194
  const isSymbolicLink = dirent => {
969
1195
  return dirent.type === Symlink;
970
1196
  };
971
- const hasSymbolicLinks = rawDirents => {
972
- return rawDirents.some(isSymbolicLink);
973
- };
1197
+
1198
+ const ENOENT = 'ENOENT';
1199
+
974
1200
  const getSymlinkType = type => {
975
1201
  switch (type) {
976
1202
  case File:
@@ -981,6 +1207,7 @@ const getSymlinkType = type => {
981
1207
  return Symlink;
982
1208
  }
983
1209
  };
1210
+
984
1211
  // TODO maybe resolving of symbolic links should happen in shared process?
985
1212
  // so that there is less code and less work in the frontend
986
1213
  const resolveSymbolicLink = async (uri, rawDirent) => {
@@ -1018,6 +1245,42 @@ const resolveSymbolicLinks = async (uri, rawDirents) => {
1018
1245
  const resolvedDirents = await Promise.all(promises);
1019
1246
  return resolvedDirents;
1020
1247
  };
1248
+
1249
+ const sortExplorerItems = rawDirents => {
1250
+ rawDirents.sort(compareDirent);
1251
+ };
1252
+
1253
+ const toDisplayDirents = (pathSeparator, rawDirents, parentDirent, excluded) => {
1254
+ sortExplorerItems(rawDirents);
1255
+ // TODO figure out whether this uses too much memory (name,path -> redundant, depth could be computed on demand)
1256
+ const toDisplayDirent = (rawDirent, index) => {
1257
+ const path = [parentDirent.path, rawDirent.name].join(pathSeparator);
1258
+ return {
1259
+ name: rawDirent.name,
1260
+ posInSet: index + 1,
1261
+ setSize: rawDirents.length,
1262
+ depth: parentDirent.depth + 1,
1263
+ type: rawDirent.type,
1264
+ path,
1265
+ // TODO storing absolute path might be too costly, could also store relative path here
1266
+ icon: getIcon()
1267
+ };
1268
+ };
1269
+ const result = [];
1270
+ let i = 0;
1271
+ for (const rawDirent of rawDirents) {
1272
+ if (excluded.includes(rawDirent.name)) {
1273
+ continue;
1274
+ }
1275
+ result.push(toDisplayDirent(rawDirent, i));
1276
+ i++;
1277
+ }
1278
+ return result;
1279
+ };
1280
+
1281
+ const hasSymbolicLinks = rawDirents => {
1282
+ return rawDirents.some(isSymbolicLink);
1283
+ };
1021
1284
  const getChildDirentsRaw = async uri => {
1022
1285
  const rawDirents = await readDirWithFileTypes();
1023
1286
  array(rawDirents);
@@ -1095,6 +1358,85 @@ const expandAll = async state => {
1095
1358
  };
1096
1359
  };
1097
1360
 
1361
+ const getParentEndIndex = (dirents, index) => {
1362
+ const dirent = dirents[index];
1363
+ let endIndex = index + 1;
1364
+ while (endIndex < dirents.length && dirents[endIndex].depth > dirent.depth) {
1365
+ endIndex++;
1366
+ }
1367
+ return endIndex;
1368
+ };
1369
+
1370
+ const makeExpanded = dirent => {
1371
+ if (dirent.type === Directory) {
1372
+ return {
1373
+ ...dirent,
1374
+ type: DirectoryExpanded
1375
+ };
1376
+ }
1377
+ return dirent;
1378
+ };
1379
+ const expandRecursively = async state => {
1380
+ const {
1381
+ items,
1382
+ focusedIndex,
1383
+ pathSeparator,
1384
+ root,
1385
+ height,
1386
+ itemHeight,
1387
+ minLineY
1388
+ } = state;
1389
+ const dirent = focusedIndex < 0 ? {
1390
+ type: Directory,
1391
+ path: root,
1392
+ depth: 0
1393
+ } : items[focusedIndex];
1394
+ if (dirent.type !== Directory && dirent.type !== DirectoryExpanding && dirent.type !== DirectoryExpanded) {
1395
+ return state;
1396
+ }
1397
+ // TODO this is very inefficient
1398
+ const getChildDirentsRecursively = async dirent => {
1399
+ switch (dirent.type) {
1400
+ case File:
1401
+ return [dirent];
1402
+ case Directory:
1403
+ case DirectoryExpanding:
1404
+ case DirectoryExpanded:
1405
+ const childDirents = await getChildDirents(pathSeparator, dirent);
1406
+ const all = [makeExpanded(dirent)];
1407
+ for (const childDirent of childDirents) {
1408
+ const childAll = await getChildDirentsRecursively(childDirent);
1409
+ all.push(...childAll);
1410
+ }
1411
+ return all;
1412
+ default:
1413
+ return [];
1414
+ }
1415
+ };
1416
+ // TODO race condition: what if folder is being collapse while it is recursively expanding?
1417
+ // TODO race condition: what if folder is being deleted while it is recursively expanding?
1418
+ // TODO race condition: what if a new file/folder is created while the folder is recursively expanding?
1419
+ const childDirents = await getChildDirentsRecursively(dirent);
1420
+ const startIndex = focusedIndex;
1421
+ if (focusedIndex >= 0) {
1422
+ const endIndex = getParentEndIndex(items, focusedIndex);
1423
+ const newDirents = [...items.slice(0, startIndex), ...childDirents, ...items.slice(endIndex)];
1424
+ const maxLineY = getExplorerMaxLineY(minLineY, height, itemHeight, newDirents.length);
1425
+ return {
1426
+ ...state,
1427
+ items: newDirents,
1428
+ maxLineY
1429
+ };
1430
+ }
1431
+ const newDirents = childDirents.slice(1);
1432
+ const maxLineY = getExplorerMaxLineY(minLineY, height, itemHeight, newDirents.length);
1433
+ return {
1434
+ ...state,
1435
+ items: newDirents,
1436
+ maxLineY
1437
+ };
1438
+ };
1439
+
1098
1440
  const focusIndex = (state, index) => {
1099
1441
  const {
1100
1442
  minLineY,
@@ -1196,7 +1538,7 @@ const TreeItem$1 = 'treeitem';
1196
1538
 
1197
1539
  const Button$1 = 'Button';
1198
1540
  const ButtonPrimary = 'ButtonPrimary';
1199
- const Explorer = 'Explorer';
1541
+ const Explorer$1 = 'Explorer';
1200
1542
  const FileIcon = 'FileIcon';
1201
1543
  const InputBox = 'InputBox';
1202
1544
  const Label = 'Label';
@@ -1249,6 +1591,39 @@ const UiStrings = {
1249
1591
  OpenFolder: 'Open folder',
1250
1592
  NoFolderOpen: 'No Folder Open'
1251
1593
  };
1594
+ const newFile = () => {
1595
+ return i18nString(UiStrings.NewFile);
1596
+ };
1597
+ const newFolder = () => {
1598
+ return i18nString(UiStrings.NewFolder);
1599
+ };
1600
+ const openContainingFolder$1 = () => {
1601
+ return i18nString(UiStrings.OpenContainingFolder);
1602
+ };
1603
+ const openInIntegratedTerminal = () => {
1604
+ return i18nString(UiStrings.OpenInIntegratedTerminal);
1605
+ };
1606
+ const cut = () => {
1607
+ return i18nString(UiStrings.Cut);
1608
+ };
1609
+ const copy = () => {
1610
+ return i18nString(UiStrings.Copy);
1611
+ };
1612
+ const paste = () => {
1613
+ return i18nString(UiStrings.Paste);
1614
+ };
1615
+ const copyPath = () => {
1616
+ return i18nString(UiStrings.CopyPath);
1617
+ };
1618
+ const copyRelativePath = () => {
1619
+ return i18nString(UiStrings.CopyRelativePath);
1620
+ };
1621
+ const rename = () => {
1622
+ return i18nString(UiStrings.Rename);
1623
+ };
1624
+ const deleteItem = () => {
1625
+ return i18nString(UiStrings.Delete);
1626
+ };
1252
1627
  const filesExplorer = () => {
1253
1628
  return i18nString(UiStrings.FilesExplorer);
1254
1629
  };
@@ -1413,7 +1788,7 @@ const mergeClassNames = (...classNames) => {
1413
1788
  const getExplorerWelcomeVirtualDom = isWide => {
1414
1789
  return [{
1415
1790
  type: Div,
1416
- className: mergeClassNames(Viewlet, Explorer),
1791
+ className: mergeClassNames(Viewlet, Explorer$1),
1417
1792
  tabIndex: 0,
1418
1793
  childCount: 1
1419
1794
  }, {
@@ -1439,7 +1814,7 @@ const getExplorerVirtualDom = (visibleItems, focusedIndex, root, isWide) => {
1439
1814
  const dom = [];
1440
1815
  dom.push({
1441
1816
  type: Div,
1442
- className: mergeClassNames(Viewlet, Explorer),
1817
+ className: mergeClassNames(Viewlet, Explorer$1),
1443
1818
  tabIndex: 0,
1444
1819
  role: Tree,
1445
1820
  ariaLabel: filesExplorer(),
@@ -1553,8 +1928,120 @@ const getKeyBindings = () => {
1553
1928
  }];
1554
1929
  };
1555
1930
 
1931
+ const Separator = 1;
1556
1932
  const None = 0;
1557
- const Rename = 3;
1933
+ const RestoreFocus = 6;
1934
+
1935
+ const menuEntrySeparator = {
1936
+ id: 'separator',
1937
+ label: '',
1938
+ flags: Separator,
1939
+ command: ''
1940
+ };
1941
+
1942
+ const menuEntryNewFile = {
1943
+ id: 'newFile',
1944
+ label: newFile(),
1945
+ flags: None,
1946
+ command: 'Explorer.newFile'
1947
+ };
1948
+ const menuEntryNewFolder = {
1949
+ id: 'newFolder',
1950
+ label: newFolder(),
1951
+ flags: None,
1952
+ command: 'Explorer.newFolder'
1953
+ };
1954
+ const menuEntryOpenContainingFolder = {
1955
+ id: 'openContainingFolder',
1956
+ label: openContainingFolder$1(),
1957
+ flags: RestoreFocus,
1958
+ command: 'Explorer.openContainingFolder'
1959
+ };
1960
+ const menuEntryOpenInIntegratedTerminal = {
1961
+ id: 'openInIntegratedTerminal',
1962
+ label: openInIntegratedTerminal(),
1963
+ flags: None,
1964
+ command: /* TODO */-1
1965
+ };
1966
+ const menuEntryCut = {
1967
+ id: 'cut',
1968
+ label: cut(),
1969
+ flags: RestoreFocus,
1970
+ command: /* TODO */-1
1971
+ };
1972
+ const menuEntryCopy = {
1973
+ id: 'copy',
1974
+ label: copy(),
1975
+ flags: RestoreFocus,
1976
+ command: 'Explorer.handleCopy'
1977
+ };
1978
+ const menuEntryPaste = {
1979
+ id: 'paste',
1980
+ label: paste(),
1981
+ flags: None,
1982
+ command: 'Explorer.handlePaste'
1983
+ };
1984
+ const menuEntryCopyPath = {
1985
+ id: 'copyPath',
1986
+ label: copyPath(),
1987
+ flags: RestoreFocus,
1988
+ command: 'Explorer.copyPath'
1989
+ };
1990
+ const menuEntryCopyRelativePath = {
1991
+ id: 'copyRelativePath',
1992
+ label: copyRelativePath(),
1993
+ flags: RestoreFocus,
1994
+ command: 'Explorer.copyRelativePath'
1995
+ };
1996
+ const menuEntryRename = {
1997
+ id: 'rename',
1998
+ label: rename(),
1999
+ flags: None,
2000
+ command: 'Explorer.renameDirent'
2001
+ };
2002
+ const menuEntryDelete = {
2003
+ id: 'delete',
2004
+ label: deleteItem(),
2005
+ flags: None,
2006
+ command: 'Explorer.removeDirent'
2007
+ };
2008
+ const ALL_ENTRIES = [menuEntryNewFile, menuEntryNewFolder, menuEntryOpenContainingFolder, menuEntryOpenInIntegratedTerminal, menuEntrySeparator, menuEntryCut, menuEntryCopy, menuEntryPaste, menuEntrySeparator, menuEntryCopyPath, menuEntryCopyRelativePath, menuEntrySeparator, menuEntryRename, menuEntryDelete];
2009
+
2010
+ // TODO there are two possible ways of getting the focused dirent of explorer
2011
+ // 1. directly access state of explorer (bad because it directly accesses state of another component)
2012
+ // 2. expose getFocusedDirent method in explorer (bad because explorer code should not know about for menuEntriesExplorer, which needs that method)
2013
+ const getFocusedDirent = explorerState => {
2014
+ if (!explorerState || explorerState.focusedIndex < 0) {
2015
+ return undefined;
2016
+ }
2017
+ return explorerState.items[explorerState.focusedIndex];
2018
+ };
2019
+ const getMenuEntriesDirectory = () => {
2020
+ return ALL_ENTRIES;
2021
+ };
2022
+ const getMenuEntriesFile = () => {
2023
+ return [menuEntryOpenContainingFolder, menuEntryOpenInIntegratedTerminal, menuEntrySeparator, menuEntryCut, menuEntryCopy, menuEntryPaste, menuEntrySeparator, menuEntryCopyPath, menuEntryCopyRelativePath, menuEntrySeparator, menuEntryRename, menuEntryDelete];
2024
+ };
2025
+ const getMenuEntriesDefault = () => {
2026
+ return ALL_ENTRIES;
2027
+ };
2028
+ const getMenuEntriesRoot = () => {
2029
+ return [menuEntryNewFile, menuEntryNewFolder, menuEntryOpenContainingFolder, menuEntryOpenInIntegratedTerminal, menuEntrySeparator, menuEntryPaste, menuEntrySeparator, menuEntryCopyPath, menuEntryCopyRelativePath];
2030
+ };
2031
+ const getMenuEntries = state => {
2032
+ const focusedDirent = getFocusedDirent(state);
2033
+ if (!focusedDirent) {
2034
+ return getMenuEntriesRoot();
2035
+ }
2036
+ switch (focusedDirent.type) {
2037
+ case Directory:
2038
+ return getMenuEntriesDirectory();
2039
+ case File:
2040
+ return getMenuEntriesFile();
2041
+ default:
2042
+ return getMenuEntriesDefault();
2043
+ }
2044
+ };
1558
2045
 
1559
2046
  const getVisibleExplorerItems = (items, minLineY, maxLineY, focusedIndex, editingIndex, editingType, editingValue) => {
1560
2047
  const visible = [];
@@ -1576,7 +2063,7 @@ const getVisibleExplorerItems = (items, minLineY, maxLineY, focusedIndex, editin
1576
2063
  });
1577
2064
  }
1578
2065
  }
1579
- if (editingType !== None && editingIndex === -1) {
2066
+ if (editingType !== None$2 && editingIndex === -1) {
1580
2067
  visible.push({
1581
2068
  depth: 3,
1582
2069
  posInSet: 1,
@@ -1592,6 +2079,32 @@ const getVisibleExplorerItems = (items, minLineY, maxLineY, focusedIndex, editin
1592
2079
  return visible;
1593
2080
  };
1594
2081
 
2082
+ const getIndexFromPosition = (state, eventX, eventY) => {
2083
+ const {
2084
+ y,
2085
+ itemHeight,
2086
+ items
2087
+ } = state;
2088
+ const index = Math.floor((eventY - y) / itemHeight);
2089
+ if (index < 0) {
2090
+ return 0;
2091
+ }
2092
+ if (index >= items.length) {
2093
+ return -1;
2094
+ }
2095
+ return index;
2096
+ };
2097
+
2098
+ const getParentStartIndex = (dirents, index) => {
2099
+ const dirent = dirents[index];
2100
+ let startIndex = index - 1;
2101
+ while (startIndex >= 0 && dirents[startIndex].depth >= dirent.depth) {
2102
+ startIndex--;
2103
+ }
2104
+ return startIndex;
2105
+ };
2106
+
2107
+ const Keyboard = -1;
1595
2108
  const LeftClick = 0;
1596
2109
 
1597
2110
  const openFolder = async () => {
@@ -1651,22 +2164,13 @@ const setDeltaY = (state, deltaY) => {
1651
2164
  const handleWheel = (state, deltaMode, deltaY) => {
1652
2165
  return setDeltaY(state, state.deltaY + deltaY);
1653
2166
  };
1654
- const getFocusedDirent = state => {
1655
- const {
1656
- focusedIndex,
1657
- minLineY,
1658
- items
1659
- } = state;
1660
- const dirent = items[focusedIndex + minLineY];
1661
- return dirent;
1662
- };
1663
2167
 
1664
2168
  // TODO support multiselection and removing multiple dirents
1665
2169
  const removeDirent = async state => {
1666
2170
  if (state.focusedIndex < 0) {
1667
2171
  return state;
1668
2172
  }
1669
- const dirent = getFocusedDirent(state);
2173
+ const dirent = getFocusedDirent$1(state);
1670
2174
  const absolutePath = dirent.path;
1671
2175
  try {
1672
2176
  // TODO handle error
@@ -1741,22 +2245,9 @@ const cancelEdit = state => {
1741
2245
  focused: true,
1742
2246
  editingIndex: -1,
1743
2247
  editingValue: '',
1744
- editingType: None
2248
+ editingType: None$2
1745
2249
  };
1746
2250
  };
1747
- const copyRelativePath = async state => {
1748
- const dirent = getFocusedDirent(state);
1749
- // @ts-ignore
1750
- dirent.path.slice(1);
1751
- // TODO handle error
1752
-
1753
- // await Command.execute(RendererWorkerCommandType.ClipBoardWriteText, /* text */ relativePath)
1754
- return state;
1755
- };
1756
- const copyPath = async state => {
1757
- // await Command.execute(RendererWorkerCommandType.ClipBoardWriteText, /* text */ path)
1758
- return state;
1759
- };
1760
2251
 
1761
2252
  // TODO much shared logic with newFolder
1762
2253
 
@@ -1987,7 +2478,7 @@ const handleBlur = state => {
1987
2478
  const {
1988
2479
  editingType
1989
2480
  } = state;
1990
- if (editingType !== None) {
2481
+ if (editingType !== None$2) {
1991
2482
  return state;
1992
2483
  }
1993
2484
  return {
@@ -2003,6 +2494,59 @@ const handleClickOpenFolder = async state => {
2003
2494
  return state;
2004
2495
  };
2005
2496
 
2497
+ const show = async (x, y, id, ...args) => {
2498
+ return invoke('ContextMenu.show', x, y, id, ...args);
2499
+ };
2500
+
2501
+ const Explorer = 4;
2502
+
2503
+ const handleContextMenuKeyboard = async state => {
2504
+ const {
2505
+ focusedIndex,
2506
+ x,
2507
+ y,
2508
+ minLineY,
2509
+ itemHeight
2510
+ } = state;
2511
+ const menuX = x;
2512
+ const menuY = y + (focusedIndex - minLineY + 1) * itemHeight;
2513
+ await show(menuX, menuY, Explorer);
2514
+ return state;
2515
+ };
2516
+
2517
+ const handleContextMenuMouseAt = async (state, x, y) => {
2518
+ number(x);
2519
+ number(y);
2520
+ const focusedIndex = getIndexFromPosition(state, x, y);
2521
+ await show(x, y, Explorer);
2522
+ return {
2523
+ ...state,
2524
+ focusedIndex,
2525
+ focused: false
2526
+ };
2527
+ };
2528
+
2529
+ const handleContextMenu = (state, button, x, y) => {
2530
+ switch (button) {
2531
+ case Keyboard:
2532
+ return handleContextMenuKeyboard(state);
2533
+ default:
2534
+ return handleContextMenuMouseAt(state, x, y);
2535
+ }
2536
+ };
2537
+
2538
+ const handlePointerDown = (state, button, x, y) => {
2539
+ const index = getIndexFromPosition(state, x, y);
2540
+ if (button === LeftClick && index === -1) {
2541
+ return {
2542
+ ...state,
2543
+ focused: true,
2544
+ focusedIndex: -1
2545
+ };
2546
+ }
2547
+ return state;
2548
+ };
2549
+
2006
2550
  // TODO viewlet should only have create and refresh functions
2007
2551
  // every thing else can be in a separate module <viewlet>.lazy.js
2008
2552
  // and <viewlet>.ipc.js
@@ -2030,11 +2574,11 @@ const getExcluded = () => {
2030
2574
  }
2031
2575
  return excluded;
2032
2576
  };
2033
- const getSavedRoot = (savedState, workspacePath) => {
2577
+ const getSavedRoot$1 = (savedState, workspacePath) => {
2034
2578
  return workspacePath;
2035
2579
  };
2036
2580
  const loadContent = async (state, savedState) => {
2037
- const root = getSavedRoot(savedState, '');
2581
+ const root = getSavedRoot$1(savedState, '');
2038
2582
  // TODO path separator could be restored from saved state
2039
2583
  const pathSeparator = await getPathSeparator(); // TODO only load path separator once
2040
2584
  const excluded = getExcluded();
@@ -2064,17 +2608,93 @@ const loadContent = async (state, savedState) => {
2064
2608
  };
2065
2609
  };
2066
2610
 
2611
+ const getContaingingFolder = (root, dirents, focusedIndex, pathSeparator) => {
2612
+ if (focusedIndex < 0) {
2613
+ return root;
2614
+ }
2615
+ const dirent = dirents[focusedIndex];
2616
+ const direntPath = dirent.path;
2617
+ const direntParentPath = direntPath.slice(0, -(dirent.name.length + 1));
2618
+ const path = `${direntParentPath}`;
2619
+ return path;
2620
+ };
2621
+ const openContainingFolder = async state => {
2622
+ const {
2623
+ focusedIndex,
2624
+ root,
2625
+ items,
2626
+ pathSeparator
2627
+ } = state;
2628
+ const path = getContaingingFolder(root, items, focusedIndex);
2629
+ await invoke('OpenNativeFolder.openNativeFolder', /* path */path);
2630
+ return state;
2631
+ };
2632
+
2633
+ const getSavedRoot = (savedState, workspacePath) => {
2634
+ return workspacePath;
2635
+ };
2636
+ const restoreState = savedState => {
2637
+ if (!savedState) {
2638
+ return {
2639
+ minLineY: 0,
2640
+ deltaY: 0
2641
+ };
2642
+ }
2643
+ const root = getSavedRoot(savedState, savedState.workspacePath || '');
2644
+ let minLineY = 0;
2645
+ if (savedState && typeof savedState.minLineY === 'number') {
2646
+ minLineY = savedState.minLineY;
2647
+ }
2648
+ let deltaY = 0;
2649
+ if (savedState && typeof savedState.deltaY === 'number') {
2650
+ deltaY = savedState.deltaY;
2651
+ }
2652
+ return {
2653
+ ...savedState,
2654
+ root,
2655
+ minLineY,
2656
+ deltaY
2657
+ };
2658
+ };
2659
+
2660
+ const isExpandedDirectory = dirent => {
2661
+ return dirent.type === DirectoryExpanded;
2662
+ };
2663
+ const getPath = dirent => {
2664
+ return dirent.path;
2665
+ };
2666
+ const saveState = state => {
2667
+ const {
2668
+ items,
2669
+ root,
2670
+ deltaY,
2671
+ minLineY,
2672
+ maxLineY
2673
+ } = state;
2674
+ const expandedPaths = items.filter(isExpandedDirectory).map(getPath);
2675
+ return {
2676
+ expandedPaths,
2677
+ root,
2678
+ minLineY,
2679
+ maxLineY,
2680
+ deltaY
2681
+ };
2682
+ };
2683
+
2067
2684
  const commandMap = {
2685
+ 'Explorer.acceptEdit': acceptEdit,
2068
2686
  'Explorer.cancelEdit': cancelEdit,
2069
- 'Explorer.copyPath': copyPath,
2070
- 'Explorer.copyRelativePath': copyRelativePath,
2687
+ 'Explorer.copyPath': copyPath$1,
2688
+ 'Explorer.copyRelativePath': copyRelativePath$1,
2071
2689
  'Explorer.expandAll': expandAll,
2690
+ 'Explorer.expandRecursively': expandRecursively,
2072
2691
  'Explorer.focusFirst': focusFirst,
2073
2692
  'Explorer.focusIndex': focusIndex,
2074
2693
  'Explorer.focusLast': focusLast,
2075
2694
  'Explorer.focusNext': focusNext,
2076
2695
  'Explorer.focusPrevious': focusPrevious,
2077
2696
  'Explorer.getKeyBindings': getKeyBindings,
2697
+ 'Explorer.getMenuEntries': getMenuEntries,
2078
2698
  'Explorer.getVirtualDom': getExplorerVirtualDom,
2079
2699
  'Explorer.getVisibleItems': getVisibleExplorerItems,
2080
2700
  'Explorer.handleArrowLeft': handleArrowLeft,
@@ -2084,17 +2704,23 @@ const commandMap = {
2084
2704
  'Explorer.handleClickAt': handleClickAt,
2085
2705
  'Explorer.handleClickCurrentButKeepFocus': handleClickCurrentButKeepFocus,
2086
2706
  'Explorer.handleClickOpenFolder': handleClickOpenFolder,
2707
+ 'Explorer.handleContextMenu': handleContextMenu,
2087
2708
  'Explorer.handleIconThemeChange': handleIconThemeChange,
2709
+ 'Explorer.handlePointerDown': handlePointerDown,
2088
2710
  'Explorer.handleWheel': handleWheel,
2089
2711
  'Explorer.loadContent': loadContent,
2712
+ 'Explorer.openContainingFolder': openContainingFolder,
2090
2713
  'Explorer.removeDirent': removeDirent,
2091
- 'Explorer.renameDirent': renameDirent
2714
+ 'Explorer.renameDirent': renameDirent,
2715
+ 'Explorer.restoreState': restoreState,
2716
+ 'Explorer.saveState': saveState
2092
2717
  };
2093
2718
 
2094
2719
  const listen = async () => {
2095
- await WebWorkerRpcClient.create({
2720
+ const rpc = await WebWorkerRpcClient.create({
2096
2721
  commandMap: commandMap
2097
2722
  });
2723
+ setRpc(rpc);
2098
2724
  };
2099
2725
 
2100
2726
  const main = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/explorer-view",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Explorer Worker",
5
5
  "main": "dist/explorerViewWorkerMain.js",
6
6
  "type": "module",