@lvce-editor/explorer-view 1.3.0 → 1.4.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.
@@ -69,7 +69,7 @@ class JsonRpcError extends Error {
69
69
  this.name = 'JsonRpcError';
70
70
  }
71
71
  }
72
- const NewLine$2 = '\n';
72
+ const NewLine$3 = '\n';
73
73
  const DomException = 'DOMException';
74
74
  const ReferenceError$1 = 'ReferenceError';
75
75
  const SyntaxError$1 = 'SyntaxError';
@@ -114,23 +114,23 @@ const constructError = (message, type, name) => {
114
114
  }
115
115
  return new ErrorConstructor(message);
116
116
  };
117
- const getNewLineIndex$1 = (string, startIndex = undefined) => {
118
- return string.indexOf(NewLine$2, startIndex);
117
+ const getNewLineIndex$2 = (string, startIndex = undefined) => {
118
+ return string.indexOf(NewLine$3, startIndex);
119
119
  };
120
120
  const getParentStack = error => {
121
121
  let parentStack = error.stack || error.data || error.message || '';
122
122
  if (parentStack.startsWith(' at')) {
123
- parentStack = error.message + NewLine$2 + parentStack;
123
+ parentStack = error.message + NewLine$3 + parentStack;
124
124
  }
125
125
  return parentStack;
126
126
  };
127
127
  const joinLines$1 = lines => {
128
- return lines.join(NewLine$2);
128
+ return lines.join(NewLine$3);
129
129
  };
130
130
  const MethodNotFound = -32601;
131
131
  const Custom = -32001;
132
132
  const splitLines$1 = lines => {
133
- return lines.split(NewLine$2);
133
+ return lines.split(NewLine$3);
134
134
  };
135
135
  const restoreJsonRpcError = error => {
136
136
  if (error && error instanceof Error) {
@@ -140,14 +140,14 @@ const restoreJsonRpcError = error => {
140
140
  if (error && error.code && error.code === MethodNotFound) {
141
141
  const restoredError = new JsonRpcError(error.message);
142
142
  const parentStack = getParentStack(error);
143
- restoredError.stack = parentStack + NewLine$2 + currentStack;
143
+ restoredError.stack = parentStack + NewLine$3 + currentStack;
144
144
  return restoredError;
145
145
  }
146
146
  if (error && error.message) {
147
147
  const restoredError = constructError(error.message, error.type, error.name);
148
148
  if (error.data) {
149
149
  if (error.data.stack && error.data.type && error.message) {
150
- restoredError.stack = error.data.type + ': ' + error.message + NewLine$2 + error.data.stack + NewLine$2 + currentStack;
150
+ restoredError.stack = error.data.type + ': ' + error.message + NewLine$3 + error.data.stack + NewLine$3 + currentStack;
151
151
  } else if (error.data.stack) {
152
152
  restoredError.stack = error.data.stack;
153
153
  }
@@ -167,7 +167,7 @@ const restoreJsonRpcError = error => {
167
167
  if (error.stack) {
168
168
  const lowerStack = restoredError.stack || '';
169
169
  // @ts-ignore
170
- const indexNewLine = getNewLineIndex$1(lowerStack);
170
+ const indexNewLine = getNewLineIndex$2(lowerStack);
171
171
  const parentStack = getParentStack(error);
172
172
  // @ts-ignore
173
173
  restoredError.stack = parentStack + lowerStack.slice(indexNewLine);
@@ -224,7 +224,7 @@ const getErrorProperty = (error, prettyError) => {
224
224
  }
225
225
  };
226
226
  };
227
- const create$1$1 = (message, error) => {
227
+ const create$1 = (message, error) => {
228
228
  return {
229
229
  jsonrpc: Two,
230
230
  id: message.id,
@@ -235,9 +235,9 @@ const getErrorResponse = (message, error, preparePrettyError, logError) => {
235
235
  const prettyError = preparePrettyError(error);
236
236
  logError(error, prettyError);
237
237
  const errorProperty = getErrorProperty(error, prettyError);
238
- return create$1$1(message, errorProperty);
238
+ return create$1(message, errorProperty);
239
239
  };
240
- const create = (message, result) => {
240
+ const create$5 = (message, result) => {
241
241
  return {
242
242
  jsonrpc: Two,
243
243
  id: message.id,
@@ -246,7 +246,7 @@ const create = (message, result) => {
246
246
  };
247
247
  const getSuccessResponse = (message, result) => {
248
248
  const resultProperty = result ?? null;
249
- return create(message, resultProperty);
249
+ return create$5(message, resultProperty);
250
250
  };
251
251
  const getResponse = async (message, ipc, execute, preparePrettyError, logError, requiresSocket) => {
252
252
  try {
@@ -584,7 +584,7 @@ const getHelpfulChildProcessError = (stdout, stderr) => {
584
584
  stack: rest
585
585
  };
586
586
  };
587
- const normalizeLine = line => {
587
+ const normalizeLine$1 = line => {
588
588
  if (line.startsWith('Error: ')) {
589
589
  return line.slice('Error: '.length);
590
590
  }
@@ -593,41 +593,41 @@ const normalizeLine = line => {
593
593
  }
594
594
  return line;
595
595
  };
596
- const getCombinedMessage = (error, message) => {
597
- const stringifiedError = normalizeLine(`${error}`);
596
+ const getCombinedMessage$1 = (error, message) => {
597
+ const stringifiedError = normalizeLine$1(`${error}`);
598
598
  if (message) {
599
599
  return `${message}: ${stringifiedError}`;
600
600
  }
601
601
  return stringifiedError;
602
602
  };
603
- const NewLine = '\n';
604
- const getNewLineIndex = (string, startIndex = undefined) => {
605
- return string.indexOf(NewLine, startIndex);
603
+ const NewLine$2 = '\n';
604
+ const getNewLineIndex$1 = (string, startIndex = undefined) => {
605
+ return string.indexOf(NewLine$2, startIndex);
606
606
  };
607
- const mergeStacks = (parent, child) => {
607
+ const mergeStacks$1 = (parent, child) => {
608
608
  if (!child) {
609
609
  return parent;
610
610
  }
611
- const parentNewLineIndex = getNewLineIndex(parent);
612
- const childNewLineIndex = getNewLineIndex(child);
611
+ const parentNewLineIndex = getNewLineIndex$1(parent);
612
+ const childNewLineIndex = getNewLineIndex$1(child);
613
613
  if (childNewLineIndex === -1) {
614
614
  return parent;
615
615
  }
616
616
  const parentFirstLine = parent.slice(0, parentNewLineIndex);
617
617
  const childRest = child.slice(childNewLineIndex);
618
- const childFirstLine = normalizeLine(child.slice(0, childNewLineIndex));
618
+ const childFirstLine = normalizeLine$1(child.slice(0, childNewLineIndex));
619
619
  if (parentFirstLine.includes(childFirstLine)) {
620
620
  return parentFirstLine + childRest;
621
621
  }
622
622
  return child;
623
623
  };
624
- class VError extends Error {
624
+ let VError$1 = class VError extends Error {
625
625
  constructor(error, message) {
626
- const combinedMessage = getCombinedMessage(error, message);
626
+ const combinedMessage = getCombinedMessage$1(error, message);
627
627
  super(combinedMessage);
628
628
  this.name = 'VError';
629
629
  if (error instanceof Error) {
630
- this.stack = mergeStacks(this.stack, error.stack);
630
+ this.stack = mergeStacks$1(this.stack, error.stack);
631
631
  }
632
632
  if (error.codeFrame) {
633
633
  // @ts-ignore
@@ -638,8 +638,8 @@ class VError extends Error {
638
638
  this.code = error.code;
639
639
  }
640
640
  }
641
- }
642
- class IpcError extends VError {
641
+ };
642
+ class IpcError extends VError$1 {
643
643
  // @ts-ignore
644
644
  constructor(betterMessage, stdout = '', stderr = '') {
645
645
  if (stdout || stderr) {
@@ -784,7 +784,7 @@ const listen$1 = async () => {
784
784
  const ipc = module.wrap(rawIpc);
785
785
  return ipc;
786
786
  };
787
- const create$1 = async ({
787
+ const create = async ({
788
788
  commandMap
789
789
  }) => {
790
790
  // TODO create a commandMap per rpc instance
@@ -796,7 +796,7 @@ const create$1 = async ({
796
796
  };
797
797
  const WebWorkerRpcClient = {
798
798
  __proto__: null,
799
- create: create$1
799
+ create
800
800
  };
801
801
 
802
802
  const RE_CHARACTERS = /^[a-zA-Z.-]+$/;
@@ -943,7 +943,7 @@ const computeExplorerRenamedDirent = (dirents, index, newName) => {
943
943
  };
944
944
  };
945
945
 
946
- const None$2 = 0;
946
+ const None$3 = 0;
947
947
  const CreateFile = 1;
948
948
  const CreateFolder = 2;
949
949
  const Rename = 3;
@@ -956,59 +956,7 @@ const stat = async dirent => {};
956
956
  const createFile = async uri => {};
957
957
  const mkdir = async uri => {};
958
958
  const rename$1 = async (oldUri, newUri) => {};
959
-
960
- class AssertionError extends Error {
961
- constructor(message) {
962
- super(message);
963
- this.name = 'AssertionError';
964
- }
965
- }
966
- const getType = value => {
967
- switch (typeof value) {
968
- case 'number':
969
- return 'number';
970
- case 'function':
971
- return 'function';
972
- case 'string':
973
- return 'string';
974
- case 'object':
975
- if (value === null) {
976
- return 'null';
977
- }
978
- if (Array.isArray(value)) {
979
- return 'array';
980
- }
981
- return 'object';
982
- case 'boolean':
983
- return 'boolean';
984
- default:
985
- return 'unknown';
986
- }
987
- };
988
- const object = value => {
989
- const type = getType(value);
990
- if (type !== 'object') {
991
- throw new AssertionError('expected value to be of type object');
992
- }
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
- };
1000
- const array = value => {
1001
- const type = getType(value);
1002
- if (type !== 'array') {
1003
- throw new AssertionError('expected value to be of type array');
1004
- }
1005
- };
1006
- const string = value => {
1007
- const type = getType(value);
1008
- if (type !== 'string') {
1009
- throw new AssertionError('expected value to be of type string');
1010
- }
1011
- };
959
+ const copy$1 = async (oldUri, newUri) => {};
1012
960
 
1013
961
  const dirname = (pathSeparator, path) => {
1014
962
  const index = path.lastIndexOf(pathSeparator);
@@ -1017,6 +965,12 @@ const dirname = (pathSeparator, path) => {
1017
965
  }
1018
966
  return path.slice(0, index);
1019
967
  };
968
+ const join = (pathSeparator, ...parts) => {
969
+ return parts.join(pathSeparator);
970
+ };
971
+ const getBaseName = (pathSeparator, path) => {
972
+ return path.slice(path.lastIndexOf(pathSeparator) + 1);
973
+ };
1020
974
 
1021
975
  const getParentFolder = (dirents, index, root) => {
1022
976
  if (index < 0) {
@@ -1097,7 +1051,7 @@ const acceptCreate = async (state, newDirentType, createFn) => {
1097
1051
  items: newDirents,
1098
1052
  editingIndex: -1,
1099
1053
  focusedIndex: insertIndex + 1,
1100
- editingType: None$2,
1054
+ editingType: None$3,
1101
1055
  maxLineY: newMaxlineY
1102
1056
  };
1103
1057
  };
@@ -1130,7 +1084,7 @@ const acceptRename = async state => {
1130
1084
  ...state,
1131
1085
  editingIndex: -1,
1132
1086
  editingValue: '',
1133
- editingType: None$2,
1087
+ editingType: None$3,
1134
1088
  editingIcon: '',
1135
1089
  focusedIndex,
1136
1090
  focused: true
@@ -1182,6 +1136,9 @@ const setRpc = rpc => {
1182
1136
  const writeText = async text => {
1183
1137
  await invoke('ClipBoard.writeText', /* text */text);
1184
1138
  };
1139
+ const readNativeFiles = async () => {
1140
+ return invoke('ClipBoard.readNativeFiles');
1141
+ };
1185
1142
 
1186
1143
  const copyRelativePath$1 = async state => {
1187
1144
  const dirent = getFocusedDirent$1(state);
@@ -1191,6 +1148,59 @@ const copyRelativePath$1 = async state => {
1191
1148
  return state;
1192
1149
  };
1193
1150
 
1151
+ class AssertionError extends Error {
1152
+ constructor(message) {
1153
+ super(message);
1154
+ this.name = 'AssertionError';
1155
+ }
1156
+ }
1157
+ const getType = value => {
1158
+ switch (typeof value) {
1159
+ case 'number':
1160
+ return 'number';
1161
+ case 'function':
1162
+ return 'function';
1163
+ case 'string':
1164
+ return 'string';
1165
+ case 'object':
1166
+ if (value === null) {
1167
+ return 'null';
1168
+ }
1169
+ if (Array.isArray(value)) {
1170
+ return 'array';
1171
+ }
1172
+ return 'object';
1173
+ case 'boolean':
1174
+ return 'boolean';
1175
+ default:
1176
+ return 'unknown';
1177
+ }
1178
+ };
1179
+ const object = value => {
1180
+ const type = getType(value);
1181
+ if (type !== 'object') {
1182
+ throw new AssertionError('expected value to be of type object');
1183
+ }
1184
+ };
1185
+ const number = value => {
1186
+ const type = getType(value);
1187
+ if (type !== 'number') {
1188
+ throw new AssertionError('expected value to be of type number');
1189
+ }
1190
+ };
1191
+ const array = value => {
1192
+ const type = getType(value);
1193
+ if (type !== 'array') {
1194
+ throw new AssertionError('expected value to be of type array');
1195
+ }
1196
+ };
1197
+ const string = value => {
1198
+ const type = getType(value);
1199
+ if (type !== 'string') {
1200
+ throw new AssertionError('expected value to be of type string');
1201
+ }
1202
+ };
1203
+
1194
1204
  const isSymbolicLink = dirent => {
1195
1205
  return dirent.type === Symlink;
1196
1206
  };
@@ -1532,7 +1542,7 @@ const focusPrevious = state => {
1532
1542
  }
1533
1543
  };
1534
1544
 
1535
- const None$1 = 'none';
1545
+ const None$2 = 'none';
1536
1546
  const Tree = 'tree';
1537
1547
  const TreeItem$1 = 'treeitem';
1538
1548
 
@@ -1646,7 +1656,7 @@ const getFileIconVirtualDom = icon => {
1646
1656
  type: Img,
1647
1657
  className: FileIcon,
1648
1658
  src: icon,
1649
- role: None$1,
1659
+ role: None$2,
1650
1660
  childCount: 0
1651
1661
  };
1652
1662
  };
@@ -1929,7 +1939,7 @@ const getKeyBindings = () => {
1929
1939
  };
1930
1940
 
1931
1941
  const Separator = 1;
1932
- const None = 0;
1942
+ const None$1 = 0;
1933
1943
  const RestoreFocus = 6;
1934
1944
 
1935
1945
  const menuEntrySeparator = {
@@ -1942,13 +1952,13 @@ const menuEntrySeparator = {
1942
1952
  const menuEntryNewFile = {
1943
1953
  id: 'newFile',
1944
1954
  label: newFile(),
1945
- flags: None,
1955
+ flags: None$1,
1946
1956
  command: 'Explorer.newFile'
1947
1957
  };
1948
1958
  const menuEntryNewFolder = {
1949
1959
  id: 'newFolder',
1950
1960
  label: newFolder(),
1951
- flags: None,
1961
+ flags: None$1,
1952
1962
  command: 'Explorer.newFolder'
1953
1963
  };
1954
1964
  const menuEntryOpenContainingFolder = {
@@ -1960,7 +1970,7 @@ const menuEntryOpenContainingFolder = {
1960
1970
  const menuEntryOpenInIntegratedTerminal = {
1961
1971
  id: 'openInIntegratedTerminal',
1962
1972
  label: openInIntegratedTerminal(),
1963
- flags: None,
1973
+ flags: None$1,
1964
1974
  command: /* TODO */-1
1965
1975
  };
1966
1976
  const menuEntryCut = {
@@ -1978,7 +1988,7 @@ const menuEntryCopy = {
1978
1988
  const menuEntryPaste = {
1979
1989
  id: 'paste',
1980
1990
  label: paste(),
1981
- flags: None,
1991
+ flags: None$1,
1982
1992
  command: 'Explorer.handlePaste'
1983
1993
  };
1984
1994
  const menuEntryCopyPath = {
@@ -1996,13 +2006,13 @@ const menuEntryCopyRelativePath = {
1996
2006
  const menuEntryRename = {
1997
2007
  id: 'rename',
1998
2008
  label: rename(),
1999
- flags: None,
2009
+ flags: None$1,
2000
2010
  command: 'Explorer.renameDirent'
2001
2011
  };
2002
2012
  const menuEntryDelete = {
2003
2013
  id: 'delete',
2004
2014
  label: deleteItem(),
2005
- flags: None,
2015
+ flags: None$1,
2006
2016
  command: 'Explorer.removeDirent'
2007
2017
  };
2008
2018
  const ALL_ENTRIES = [menuEntryNewFile, menuEntryNewFolder, menuEntryOpenContainingFolder, menuEntryOpenInIntegratedTerminal, menuEntrySeparator, menuEntryCut, menuEntryCopy, menuEntryPaste, menuEntrySeparator, menuEntryCopyPath, menuEntryCopyRelativePath, menuEntrySeparator, menuEntryRename, menuEntryDelete];
@@ -2063,7 +2073,7 @@ const getVisibleExplorerItems = (items, minLineY, maxLineY, focusedIndex, editin
2063
2073
  });
2064
2074
  }
2065
2075
  }
2066
- if (editingType !== None$2 && editingIndex === -1) {
2076
+ if (editingType !== None$3 && editingIndex === -1) {
2067
2077
  visible.push({
2068
2078
  depth: 3,
2069
2079
  posInSet: 1,
@@ -2079,36 +2089,19 @@ const getVisibleExplorerItems = (items, minLineY, maxLineY, focusedIndex, editin
2079
2089
  return visible;
2080
2090
  };
2081
2091
 
2082
- const getIndexFromPosition = (state, eventX, eventY) => {
2092
+ const handleBlur = state => {
2093
+ // TODO when blur event occurs because of context menu, focused index should stay the same
2094
+ // but focus outline should be removed
2083
2095
  const {
2084
- y,
2085
- itemHeight,
2086
- items
2096
+ editingType
2087
2097
  } = 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--;
2098
+ if (editingType !== None$3) {
2099
+ return state;
2103
2100
  }
2104
- return startIndex;
2105
- };
2106
-
2107
- const Keyboard = -1;
2108
- const LeftClick = 0;
2109
-
2110
- const openFolder = async () => {
2111
- // TODO
2101
+ return {
2102
+ ...state,
2103
+ focused: false
2104
+ };
2112
2105
  };
2113
2106
 
2114
2107
  // TODO viewlet should only have create and refresh functions
@@ -2120,66 +2113,236 @@ const openFolder = async () => {
2120
2113
 
2121
2114
  // TODO instead of root string, there should be a root dirent
2122
2115
 
2123
- const updateIcon = dirent => {
2124
- return {
2125
- ...dirent,
2126
- icon: getIcon()
2127
- };
2128
- };
2129
- const updateIcons = state => {
2130
- const newDirents = state.items.map(updateIcon);
2116
+ // TODO rename dirents to items, then can use virtual list component directly
2117
+
2118
+ // TODO support multiselection and removing multiple dirents
2119
+
2120
+ // TODO use posInSet and setSize properties to compute more effectively
2121
+
2122
+ // TODO much shared logic with newFolder
2123
+
2124
+ const handleClickFile$1 = async (state, dirent, index, keepFocus = false) => {
2125
+ // await Command.execute(/* Main.openAbsolutePath */ 'Main.openUri', /* absolutePath */ dirent.path, /* focus */ !keepFocus)
2131
2126
  return {
2132
2127
  ...state,
2133
- items: newDirents
2128
+ focusedIndex: index,
2129
+ focused: keepFocus
2134
2130
  };
2135
2131
  };
2136
- const handleIconThemeChange = state => {
2137
- return updateIcons(state);
2138
- };
2139
-
2140
- // TODO rename dirents to items, then can use virtual list component directly
2141
- const setDeltaY = (state, deltaY) => {
2142
- const {
2143
- itemHeight,
2144
- height,
2145
- items
2146
- } = state;
2147
- if (deltaY < 0) {
2148
- deltaY = 0;
2149
- } else if (deltaY > items.length * itemHeight - height) {
2150
- deltaY = Math.max(items.length * itemHeight - height, 0);
2132
+ const handleClickDirectory$1 = async (state, dirent, index, keepFocus) => {
2133
+ dirent.type = DirectoryExpanding;
2134
+ // TODO handle error
2135
+ const dirents = await getChildDirents(state.pathSeparator, dirent);
2136
+ const state2 = {};
2137
+ if (!state2) {
2138
+ return state;
2151
2139
  }
2152
- if (state.deltaY === deltaY) {
2140
+ // TODO use Viewlet.getState here and check if it exists
2141
+ const newIndex = state2.items.indexOf(dirent);
2142
+ // TODO if viewlet is disposed or root has changed, return
2143
+ if (newIndex === -1) {
2153
2144
  return state;
2154
2145
  }
2155
- const minLineY = Math.round(deltaY / itemHeight);
2156
- const maxLineY = minLineY + Math.round(height / itemHeight);
2146
+ const newDirents = [...state2.items];
2147
+ newDirents.splice(newIndex + 1, 0, ...dirents);
2148
+ dirent.type = DirectoryExpanded;
2149
+ dirent.icon = getIcon();
2150
+ const {
2151
+ height,
2152
+ itemHeight,
2153
+ minLineY
2154
+ } = state2;
2155
+ // TODO when focused index has changed while expanding, don't update it
2156
+ const maxLineY = getExplorerMaxLineY(minLineY, height, itemHeight, newDirents.length);
2157
2157
  return {
2158
2158
  ...state,
2159
- deltaY,
2160
- minLineY,
2159
+ items: newDirents,
2160
+ focusedIndex: newIndex,
2161
+ focused: keepFocus,
2161
2162
  maxLineY
2162
2163
  };
2163
2164
  };
2164
- const handleWheel = (state, deltaMode, deltaY) => {
2165
- return setDeltaY(state, state.deltaY + deltaY);
2165
+ const handleClickDirectoryExpanding = (state, dirent, index, keepFocus) => {
2166
+ dirent.type = Directory;
2167
+ dirent.icon = getIcon();
2168
+ return {
2169
+ ...state,
2170
+ focusedIndex: index,
2171
+ focused: keepFocus
2172
+ };
2166
2173
  };
2167
-
2168
- // TODO support multiselection and removing multiple dirents
2169
- const removeDirent = async state => {
2170
- if (state.focusedIndex < 0) {
2171
- return state;
2172
- }
2173
- const dirent = getFocusedDirent$1(state);
2174
- const absolutePath = dirent.path;
2175
- try {
2176
- // TODO handle error
2177
- await remove(absolutePath);
2178
- } catch (error) {
2179
- // TODO vscode shows error as alert (no stacktrace) and retry button
2180
- // maybe should show alert as well, but where to put stacktrace?
2181
- // on web should probably show notification (dialog)
2182
- // ErrorHandling.handleError(error)
2174
+ const handleClickDirectoryExpanded$1 = (state, dirent, index, keepFocus) => {
2175
+ const {
2176
+ minLineY,
2177
+ maxLineY,
2178
+ itemHeight
2179
+ } = state;
2180
+ dirent.type = Directory;
2181
+ dirent.icon = getIcon();
2182
+ const endIndex = getParentEndIndex(state.items, index);
2183
+ const removeCount = endIndex - index - 1;
2184
+ // TODO race conditions and side effects are everywhere
2185
+ const newDirents = [...state.items];
2186
+ newDirents.splice(index + 1, removeCount);
2187
+ const newTotal = newDirents.length;
2188
+ if (newTotal < maxLineY) {
2189
+ const visibleItems = Math.min(maxLineY - minLineY, newTotal);
2190
+ const newMaxLineY = Math.min(maxLineY, newTotal);
2191
+ const newMinLineY = newMaxLineY - visibleItems;
2192
+ const deltaY = newMinLineY * itemHeight;
2193
+ return {
2194
+ ...state,
2195
+ items: newDirents,
2196
+ focusedIndex: index,
2197
+ focused: keepFocus,
2198
+ minLineY: newMinLineY,
2199
+ maxLineY: newMaxLineY,
2200
+ deltaY
2201
+ };
2202
+ }
2203
+ return {
2204
+ ...state,
2205
+ items: newDirents,
2206
+ focusedIndex: index,
2207
+ focused: keepFocus
2208
+ };
2209
+ };
2210
+ const handleClickSymLink$1 = async (state, dirent, index) => {
2211
+ await getRealPath();
2212
+ const type = await stat();
2213
+ switch (type) {
2214
+ case File:
2215
+ return handleClickFile$1(state, dirent, index);
2216
+ default:
2217
+ throw new Error(`unsupported file type ${type}`);
2218
+ }
2219
+ };
2220
+ const getClickFn = direntType => {
2221
+ switch (direntType) {
2222
+ case File:
2223
+ case SymLinkFile:
2224
+ return handleClickFile$1;
2225
+ case Directory:
2226
+ case SymLinkFolder:
2227
+ return handleClickDirectory$1;
2228
+ case DirectoryExpanding:
2229
+ return handleClickDirectoryExpanding;
2230
+ case DirectoryExpanded:
2231
+ return handleClickDirectoryExpanded$1;
2232
+ case Symlink:
2233
+ return handleClickSymLink$1;
2234
+ case CharacterDevice:
2235
+ throw new Error('Cannot open character device files');
2236
+ case BlockDevice:
2237
+ throw new Error('Cannot open block device files');
2238
+ case Socket:
2239
+ throw new Error('Cannot open socket files');
2240
+ default:
2241
+ throw new Error(`unsupported dirent type ${direntType}`);
2242
+ }
2243
+ };
2244
+
2245
+ const getIndexFromPosition = (state, eventX, eventY) => {
2246
+ const {
2247
+ y,
2248
+ itemHeight,
2249
+ items
2250
+ } = state;
2251
+ const index = Math.floor((eventY - y) / itemHeight);
2252
+ if (index < 0) {
2253
+ return 0;
2254
+ }
2255
+ if (index >= items.length) {
2256
+ return -1;
2257
+ }
2258
+ return index;
2259
+ };
2260
+
2261
+ const getParentStartIndex = (dirents, index) => {
2262
+ const dirent = dirents[index];
2263
+ let startIndex = index - 1;
2264
+ while (startIndex >= 0 && dirents[startIndex].depth >= dirent.depth) {
2265
+ startIndex--;
2266
+ }
2267
+ return startIndex;
2268
+ };
2269
+
2270
+ const Keyboard = -1;
2271
+ const LeftClick = 0;
2272
+
2273
+ const openFolder = async () => {
2274
+ // TODO
2275
+ };
2276
+
2277
+ // TODO viewlet should only have create and refresh functions
2278
+ // every thing else can be in a separate module <viewlet>.lazy.js
2279
+ // and <viewlet>.ipc.js
2280
+
2281
+ // viewlet: creating | refreshing | done | disposed
2282
+ // TODO recycle viewlets (maybe)
2283
+
2284
+ // TODO instead of root string, there should be a root dirent
2285
+
2286
+ const updateIcon = dirent => {
2287
+ return {
2288
+ ...dirent,
2289
+ icon: getIcon()
2290
+ };
2291
+ };
2292
+ const updateIcons = state => {
2293
+ const newDirents = state.items.map(updateIcon);
2294
+ return {
2295
+ ...state,
2296
+ items: newDirents
2297
+ };
2298
+ };
2299
+ const handleIconThemeChange = state => {
2300
+ return updateIcons(state);
2301
+ };
2302
+
2303
+ // TODO rename dirents to items, then can use virtual list component directly
2304
+ const setDeltaY = (state, deltaY) => {
2305
+ const {
2306
+ itemHeight,
2307
+ height,
2308
+ items
2309
+ } = state;
2310
+ if (deltaY < 0) {
2311
+ deltaY = 0;
2312
+ } else if (deltaY > items.length * itemHeight - height) {
2313
+ deltaY = Math.max(items.length * itemHeight - height, 0);
2314
+ }
2315
+ if (state.deltaY === deltaY) {
2316
+ return state;
2317
+ }
2318
+ const minLineY = Math.round(deltaY / itemHeight);
2319
+ const maxLineY = minLineY + Math.round(height / itemHeight);
2320
+ return {
2321
+ ...state,
2322
+ deltaY,
2323
+ minLineY,
2324
+ maxLineY
2325
+ };
2326
+ };
2327
+ const handleWheel = (state, deltaMode, deltaY) => {
2328
+ return setDeltaY(state, state.deltaY + deltaY);
2329
+ };
2330
+
2331
+ // TODO support multiselection and removing multiple dirents
2332
+ const removeDirent = async state => {
2333
+ if (state.focusedIndex < 0) {
2334
+ return state;
2335
+ }
2336
+ const dirent = getFocusedDirent$1(state);
2337
+ const absolutePath = dirent.path;
2338
+ try {
2339
+ // TODO handle error
2340
+ await remove(absolutePath);
2341
+ } catch (error) {
2342
+ // TODO vscode shows error as alert (no stacktrace) and retry button
2343
+ // maybe should show alert as well, but where to put stacktrace?
2344
+ // on web should probably show notification (dialog)
2345
+ // ErrorHandling.handleError(error)
2183
2346
  // await ErrorHandling.showErrorDialog(error)
2184
2347
  return;
2185
2348
  }
@@ -2245,7 +2408,7 @@ const cancelEdit = state => {
2245
2408
  focused: true,
2246
2409
  editingIndex: -1,
2247
2410
  editingValue: '',
2248
- editingType: None$2
2411
+ editingType: None$3
2249
2412
  };
2250
2413
  };
2251
2414
 
@@ -2292,15 +2455,6 @@ const handleClickDirectory = async (state, dirent, index, keepFocus) => {
2292
2455
  maxLineY
2293
2456
  };
2294
2457
  };
2295
- const handleClickDirectoryExpanding = (state, dirent, index, keepFocus) => {
2296
- dirent.type = Directory;
2297
- dirent.icon = getIcon();
2298
- return {
2299
- ...state,
2300
- focusedIndex: index,
2301
- focused: keepFocus
2302
- };
2303
- };
2304
2458
  const handleClickDirectoryExpanded = (state, dirent, index, keepFocus) => {
2305
2459
  const {
2306
2460
  minLineY,
@@ -2337,30 +2491,6 @@ const handleClickDirectoryExpanded = (state, dirent, index, keepFocus) => {
2337
2491
  focused: keepFocus
2338
2492
  };
2339
2493
  };
2340
- const getClickFn = direntType => {
2341
- switch (direntType) {
2342
- case File:
2343
- case SymLinkFile:
2344
- return handleClickFile;
2345
- case Directory:
2346
- case SymLinkFolder:
2347
- return handleClickDirectory;
2348
- case DirectoryExpanding:
2349
- return handleClickDirectoryExpanding;
2350
- case DirectoryExpanded:
2351
- return handleClickDirectoryExpanded;
2352
- case Symlink:
2353
- return handleClickSymLink;
2354
- case CharacterDevice:
2355
- throw new Error('Cannot open character device files');
2356
- case BlockDevice:
2357
- throw new Error('Cannot open block device files');
2358
- case Socket:
2359
- throw new Error('Cannot open socket files');
2360
- default:
2361
- throw new Error(`unsupported dirent type ${direntType}`);
2362
- }
2363
- };
2364
2494
  const handleClick = (state, index, keepFocus = false) => {
2365
2495
  const {
2366
2496
  items,
@@ -2472,21 +2602,6 @@ const handleArrowLeft = state => {
2472
2602
  // TODO what happens when mouse leave and anther mouse enter event occur?
2473
2603
  // should update preview instead of closing and reopening
2474
2604
 
2475
- const handleBlur = state => {
2476
- // TODO when blur event occurs because of context menu, focused index should stay the same
2477
- // but focus outline should be removed
2478
- const {
2479
- editingType
2480
- } = state;
2481
- if (editingType !== None$2) {
2482
- return state;
2483
- }
2484
- return {
2485
- ...state,
2486
- focused: false
2487
- };
2488
- };
2489
-
2490
2605
  // TODO maybe just insert items into explorer and refresh whole explorer
2491
2606
 
2492
2607
  const handleClickOpenFolder = async state => {
@@ -2535,6 +2650,321 @@ const handleContextMenu = (state, button, x, y) => {
2535
2650
  }
2536
2651
  };
2537
2652
 
2653
+ const getFilePathElectron = async file => {
2654
+ return invoke('GetFilePathElectron.getFilePathElectron', file);
2655
+ };
2656
+
2657
+ const mergeDirents$2 = (oldDirents, newDirents) => {
2658
+ return newDirents;
2659
+ };
2660
+
2661
+ // TODO copy files in parallel
2662
+ const copyFilesElectron = async (root, pathSeparator, files) => {
2663
+ for (const file of files) {
2664
+ await getFilePathElectron(file);
2665
+ // const from = file.path
2666
+ join(pathSeparator, root, file.name);
2667
+ await copy$1();
2668
+ }
2669
+ };
2670
+ const getMergedDirents$2 = async (root, pathSeparator, dirents) => {
2671
+ const childDirents = await getChildDirents(pathSeparator, {
2672
+ path: root,
2673
+ depth: 0
2674
+ });
2675
+ const mergedDirents = mergeDirents$2(dirents, childDirents);
2676
+ return mergedDirents;
2677
+ };
2678
+ const handleDrop$2 = async (state, files) => {
2679
+ const {
2680
+ root,
2681
+ pathSeparator,
2682
+ items
2683
+ } = state;
2684
+ await copyFilesElectron(root, pathSeparator, files);
2685
+ const mergedDirents = await getMergedDirents$2(root, pathSeparator, items);
2686
+ return {
2687
+ ...state,
2688
+ items: mergedDirents,
2689
+ dropTargets: []
2690
+ };
2691
+ };
2692
+
2693
+ const uploadFileSystemHandles = async (root, pathSeparator, fileSystemHandles) => {
2694
+ // TODO send to renderer worker
2695
+ };
2696
+
2697
+ const mergeDirents$1 = (oldDirents, newDirents) => {
2698
+ return newDirents;
2699
+ };
2700
+ const getMergedDirents$1 = async (root, pathSeparator, dirents) => {
2701
+ const childDirents = await getChildDirents(pathSeparator, {
2702
+ path: root,
2703
+ depth: 0
2704
+ });
2705
+ const mergedDirents = mergeDirents$1(dirents, childDirents);
2706
+ return mergedDirents;
2707
+ };
2708
+ const handleDrop$1 = async (state, files) => {
2709
+ const {
2710
+ root,
2711
+ pathSeparator,
2712
+ items
2713
+ } = state;
2714
+ const handled = await uploadFileSystemHandles();
2715
+ if (handled) {
2716
+ return state;
2717
+ }
2718
+ const mergedDirents = await getMergedDirents$1(root, pathSeparator, items);
2719
+ return {
2720
+ ...state,
2721
+ items: mergedDirents,
2722
+ dropTargets: []
2723
+ };
2724
+ };
2725
+
2726
+ const getModule = isElectron => {
2727
+ if (isElectron) {
2728
+ return handleDrop$2;
2729
+ }
2730
+ return handleDrop$1;
2731
+ };
2732
+ const handleDropRoot = async (state, files) => {
2733
+ const fn = await getModule(state.isElectron);
2734
+ return fn(state, files);
2735
+ };
2736
+
2737
+ const getEndIndex = (items, index, dirent) => {
2738
+ for (let i = index + 1; i < items.length; i++) {
2739
+ if (items[i].depth === dirent.depth) {
2740
+ return i;
2741
+ }
2742
+ }
2743
+ return items.length;
2744
+ };
2745
+ const getMergedDirents = (items, index, dirent, childDirents) => {
2746
+ const startIndex = index;
2747
+ const endIndex = getEndIndex(items, index, dirent);
2748
+ const mergedDirents = [...items.slice(0, startIndex), {
2749
+ ...dirent,
2750
+ type: DirectoryExpanded
2751
+ }, ...childDirents, ...items.slice(endIndex)];
2752
+ return mergedDirents;
2753
+ };
2754
+ const handleDropIntoFolder = async (state, dirent, index, files) => {
2755
+ const {
2756
+ pathSeparator,
2757
+ items
2758
+ } = state;
2759
+ for (const file of files) {
2760
+ await copy$1();
2761
+ }
2762
+ const childDirents = await getChildDirents(pathSeparator, dirent);
2763
+ const mergedDirents = getMergedDirents(items, index, dirent, childDirents);
2764
+ // TODO update maxlineY
2765
+ return {
2766
+ ...state,
2767
+ items: mergedDirents,
2768
+ dropTargets: []
2769
+ };
2770
+ };
2771
+ const handleDropIntoFile = (state, dirent, index, files) => {
2772
+ const {
2773
+ items
2774
+ } = state;
2775
+ const parentIndex = getParentStartIndex(items, index);
2776
+ if (parentIndex === -1) {
2777
+ return handleDropRoot(state, files);
2778
+ }
2779
+ // @ts-ignore
2780
+ return handleDropIndex(parentIndex);
2781
+ };
2782
+ const handleDropIndex = (state, index, files) => {
2783
+ const {
2784
+ items
2785
+ } = state;
2786
+ const dirent = items[index];
2787
+ // TODO if it is a file, drop into the folder of the file
2788
+ // TODO if it is a folder, drop into the folder
2789
+ // TODO if it is a symlink, read symlink and determine if file can be dropped
2790
+ switch (dirent.type) {
2791
+ case Directory:
2792
+ case DirectoryExpanded:
2793
+ return handleDropIntoFolder(state, dirent, index, files);
2794
+ case File:
2795
+ return handleDropIntoFile(state, dirent, index, files);
2796
+ default:
2797
+ return state;
2798
+ }
2799
+ };
2800
+
2801
+ const normalizeLine = line => {
2802
+ if (line.startsWith('Error: ')) {
2803
+ return line.slice('Error: '.length);
2804
+ }
2805
+ if (line.startsWith('VError: ')) {
2806
+ return line.slice('VError: '.length);
2807
+ }
2808
+ return line;
2809
+ };
2810
+ const getCombinedMessage = (error, message) => {
2811
+ const stringifiedError = normalizeLine(`${error}`);
2812
+ if (message) {
2813
+ return `${message}: ${stringifiedError}`;
2814
+ }
2815
+ return stringifiedError;
2816
+ };
2817
+ const NewLine = '\n';
2818
+ const getNewLineIndex = (string, startIndex = undefined) => {
2819
+ return string.indexOf(NewLine, startIndex);
2820
+ };
2821
+ const mergeStacks = (parent, child) => {
2822
+ if (!child) {
2823
+ return parent;
2824
+ }
2825
+ const parentNewLineIndex = getNewLineIndex(parent);
2826
+ const childNewLineIndex = getNewLineIndex(child);
2827
+ if (childNewLineIndex === -1) {
2828
+ return parent;
2829
+ }
2830
+ const parentFirstLine = parent.slice(0, parentNewLineIndex);
2831
+ const childRest = child.slice(childNewLineIndex);
2832
+ const childFirstLine = normalizeLine(child.slice(0, childNewLineIndex));
2833
+ if (parentFirstLine.includes(childFirstLine)) {
2834
+ return parentFirstLine + childRest;
2835
+ }
2836
+ return child;
2837
+ };
2838
+ class VError extends Error {
2839
+ constructor(error, message) {
2840
+ const combinedMessage = getCombinedMessage(error, message);
2841
+ super(combinedMessage);
2842
+ this.name = 'VError';
2843
+ if (error instanceof Error) {
2844
+ this.stack = mergeStacks(this.stack, error.stack);
2845
+ }
2846
+ if (error.codeFrame) {
2847
+ // @ts-ignore
2848
+ this.codeFrame = error.codeFrame;
2849
+ }
2850
+ if (error.code) {
2851
+ // @ts-ignore
2852
+ this.code = error.code;
2853
+ }
2854
+ }
2855
+ }
2856
+
2857
+ const handleDrop = async (state, x, y, files) => {
2858
+ try {
2859
+ const index = getIndexFromPosition(state, x, y);
2860
+ switch (index) {
2861
+ case -1:
2862
+ return await handleDropRoot(state, files);
2863
+ default:
2864
+ return await handleDropIndex(state, index, files);
2865
+ }
2866
+ } catch (error) {
2867
+ throw new VError(error, 'Failed to drop files');
2868
+ }
2869
+ };
2870
+
2871
+ const getTopLevelDirents = (root, pathSeparator, excluded) => {
2872
+ if (!root) {
2873
+ return [];
2874
+ }
2875
+ return getChildDirents(pathSeparator, {
2876
+ depth: 0,
2877
+ path: root,
2878
+ type: Directory
2879
+ }, excluded);
2880
+ };
2881
+
2882
+ const mergeDirents = (oldDirents, newDirents) => {
2883
+ const merged = [];
2884
+ for (const newDirent of newDirents) {
2885
+ merged.push(newDirent);
2886
+ }
2887
+ return merged;
2888
+ };
2889
+
2890
+ // TODO add lots of tests for this
2891
+ const updateRoot = async state1 => {
2892
+ if (state1.disposed) {
2893
+ return state1;
2894
+ }
2895
+ // const file = nativeFiles.files[0]
2896
+ // @ts-ignore
2897
+ const topLevelDirents = await getTopLevelDirents(state1.root, state1.pathSeparator);
2898
+ // const state2 = Viewlet.getState('Explorer')
2899
+ // // TODO what if root changes while reading directories?
2900
+ // if (state2.disposed || state2.root !== state1.root) {
2901
+ // return state2
2902
+ // }
2903
+ const newDirents = mergeDirents(state1.items, topLevelDirents);
2904
+ const state3 = {
2905
+ ...state1,
2906
+ items: newDirents
2907
+ };
2908
+ return state3;
2909
+ };
2910
+
2911
+ const handlePasteCopy = async (state, nativeFiles) => {
2912
+ // TODO handle pasting files into nested folder
2913
+ // TODO handle pasting files into symlink
2914
+ // TODO handle pasting files into broken symlink
2915
+ // TODO handle pasting files into hardlink
2916
+ // TODO what if folder is big and it takes a long time
2917
+ for (const source of nativeFiles.files) {
2918
+ join(state.pathSeperator, state.root, getBaseName(state.pathSeparator, source));
2919
+ await copy$1();
2920
+ }
2921
+ // TODO only update folder at which level it changed
2922
+ return updateRoot(state);
2923
+ };
2924
+
2925
+ const handlePasteCut = async (state, nativeFiles) => {
2926
+ for (const source of nativeFiles.files) {
2927
+ `${state.root}${state.pathSeparator}${getBaseName(state.pathSeparator, source)}`;
2928
+ await rename$1();
2929
+ }
2930
+ return state;
2931
+ };
2932
+
2933
+ const handlePasteNone = (state, nativeFiles) => {
2934
+ console.info('[ViewletExplorer/handlePaste] no paths detected');
2935
+ return state;
2936
+ };
2937
+
2938
+ const None = 'none';
2939
+ const Copy = 'copy';
2940
+ const Cut = 'cut';
2941
+
2942
+ const handlePaste = async state => {
2943
+ const nativeFiles = await readNativeFiles();
2944
+ // TODO detect cut/paste event, not sure if that is possible
2945
+ // TODO check that pasted folder is not a parent folder of opened folder
2946
+ // TODO support pasting multiple paths
2947
+ // TODO what happens when pasting multiple paths, but some of them error?
2948
+ // how many error messages should be shown? Should the operation be undone?
2949
+ // TODO what if it is a large folder and takes a long time to copy? Should show progress
2950
+ // TODO what if there is a permission error? Probably should show a modal to ask for permission
2951
+ // TODO if error is EEXISTS, just rename the copy (e.g. file-copy-1.txt, file-copy-2.txt)
2952
+ // TODO actual target should be selected folder
2953
+ // TODO but what if a file is currently selected? Then maybe the parent folder
2954
+ // TODO but will it work if the folder is a symlink?
2955
+ // TODO handle error gracefully when copy fails
2956
+ switch (nativeFiles.type) {
2957
+ case None:
2958
+ return handlePasteNone(state);
2959
+ case Copy:
2960
+ return handlePasteCopy(state, nativeFiles);
2961
+ case Cut:
2962
+ return handlePasteCut(state, nativeFiles);
2963
+ default:
2964
+ throw new Error(`unexpected native paste type: ${nativeFiles.type}`);
2965
+ }
2966
+ };
2967
+
2538
2968
  const handlePointerDown = (state, button, x, y) => {
2539
2969
  const index = getIndexFromPosition(state, x, y);
2540
2970
  if (button === LeftClick && index === -1) {
@@ -2657,6 +3087,228 @@ const restoreState = savedState => {
2657
3087
  };
2658
3088
  };
2659
3089
 
3090
+ const getIndex = (dirents, uri) => {
3091
+ for (let i = 0; i < dirents.length; i++) {
3092
+ const dirent = dirents[i];
3093
+ if (dirent.path === uri) {
3094
+ return i;
3095
+ }
3096
+ }
3097
+ return -1;
3098
+ };
3099
+
3100
+ const getPathParts = (root, uri, pathSeparator) => {
3101
+ const parts = [];
3102
+ let index = root.length - 1;
3103
+ let depth = 0;
3104
+ while ((index = uri.indexOf('/', index + 1)) !== -1) {
3105
+ const partUri = uri.slice(0, index);
3106
+ parts.push({
3107
+ path: partUri,
3108
+ depth: depth++,
3109
+ root,
3110
+ pathSeparator
3111
+ });
3112
+ }
3113
+ return parts;
3114
+ };
3115
+
3116
+ const isTopLevel = dirent => {
3117
+ return dirent.depth === 1;
3118
+ };
3119
+ const orderDirents = dirents => {
3120
+ if (dirents.length === 0) {
3121
+ return dirents;
3122
+ }
3123
+ // const parentMap = Object.create(null)
3124
+ // for(const dirent of dirents){
3125
+ // const parentPath = dirent.slice(0, dirent.lastIndexOf('/'))
3126
+ // parentMap[parentPath]||=[]
3127
+ // parentMap[parentPath].push(dirent)
3128
+ // }
3129
+ const withDeepChildren = parent => {
3130
+ const children = [];
3131
+ for (const dirent of dirents) {
3132
+ if (dirent.depth === parent.depth + 1 && dirent.path.startsWith(parent.path)) {
3133
+ children.push(dirent, ...withDeepChildren(dirent));
3134
+ }
3135
+ }
3136
+ return [parent, ...children];
3137
+ };
3138
+ const topLevelDirents = dirents.filter(isTopLevel);
3139
+ const ordered = topLevelDirents.flatMap(withDeepChildren);
3140
+ return ordered;
3141
+ };
3142
+
3143
+ const scrollInto = (index, minLineY, maxLineY) => {
3144
+ const diff = maxLineY - minLineY;
3145
+ const smallerHalf = Math.floor(diff / 2);
3146
+ const largerHalf = diff - smallerHalf;
3147
+ if (index < minLineY) {
3148
+ return {
3149
+ newMinLineY: index - smallerHalf,
3150
+ newMaxLineY: index + largerHalf
3151
+ };
3152
+ }
3153
+ if (index >= maxLineY) {
3154
+ return {
3155
+ newMinLineY: index - smallerHalf,
3156
+ newMaxLineY: index + largerHalf
3157
+ };
3158
+ }
3159
+ return {
3160
+ newMinLineY: minLineY,
3161
+ newMaxLineY: maxLineY
3162
+ };
3163
+ };
3164
+
3165
+ const getPathPartsToReveal = (root, pathParts, dirents) => {
3166
+ for (let i = 0; i < pathParts.length; i++) {
3167
+ const pathPart = pathParts[i];
3168
+ const index = getIndex(dirents, pathPart.uri);
3169
+ if (index === -1) {
3170
+ continue;
3171
+ }
3172
+ return pathParts.slice(i);
3173
+ }
3174
+ return pathParts;
3175
+ };
3176
+ const getPathPartChildren = pathPart => {
3177
+ const children = getChildDirents(pathPart.pathSeparator, pathPart);
3178
+ return children;
3179
+ };
3180
+ const mergeVisibleWithHiddenItems = (visibleItems, hiddenItems) => {
3181
+ const merged = [...hiddenItems];
3182
+ const seen = Object.create(null);
3183
+ const unique = [];
3184
+ for (const item of merged) {
3185
+ if (seen[item.path]) {
3186
+ continue;
3187
+ }
3188
+ seen[item.path] = true;
3189
+ unique.push(item);
3190
+ }
3191
+
3192
+ // depth one
3193
+ // let depth=1
3194
+ // while(true){
3195
+ // for(const item of unique){
3196
+ // if(item.depth===depth){
3197
+ // ordered.push(item)
3198
+ // }
3199
+ // }
3200
+ // break
3201
+ // }
3202
+ // const getChildren = (path) => {
3203
+ // const children = []
3204
+ // for (const item of unique) {
3205
+ // if (item.path.startsWith(path) && item.path !== path) {
3206
+ // ordered.push(item)
3207
+ // }
3208
+ // }
3209
+ // return children
3210
+ // }
3211
+ // for (const item of unique) {
3212
+ // for (const potentialChild of unique) {
3213
+ // if (
3214
+ // potentialChild.path.startsWith(item.path) &&
3215
+ // potentialChild.path !== item.path
3216
+ // ) {
3217
+ // ordered.push(potentialChild)
3218
+ // }
3219
+ // }
3220
+ // }
3221
+ // const refreshedRoots = Object.create(null)
3222
+ // for (const hiddenItem of hiddenItems) {
3223
+ // const parent = hiddenItem.path.slice(0, hiddenItem.path.lastIndexOf('/'))
3224
+ // refreshedRoots[parent] = true
3225
+ // }
3226
+ // const mergedDirents = []
3227
+ // for(const v)
3228
+ // for (const visibleItem of visibleItems) {
3229
+ // if (visibleItem.path === hiddenItemRoot) {
3230
+ // // TODO update aria posinset and aria setsize
3231
+ // mergedDirents.push(...hiddenItems)
3232
+ // } else {
3233
+ // for (const hiddenItem of hiddenItems) {
3234
+ // if (hiddenItem.path === visibleItem.path) {
3235
+ // continue
3236
+ // }
3237
+ // }
3238
+ // mergedDirents.push(visibleItem)
3239
+ // }
3240
+ // }
3241
+
3242
+ return unique;
3243
+ };
3244
+
3245
+ // TODO maybe just insert items into explorer and refresh whole explorer
3246
+ const revealItemHidden = async (state, uri) => {
3247
+ const {
3248
+ root,
3249
+ pathSeparator,
3250
+ items,
3251
+ minLineY,
3252
+ maxLineY
3253
+ } = state;
3254
+ const pathParts = getPathParts(root, uri, pathSeparator);
3255
+ if (pathParts.length === 0) {
3256
+ return state;
3257
+ }
3258
+ const pathPartsToReveal = getPathPartsToReveal(root, pathParts, items);
3259
+ const pathPartsChildren = await Promise.all(pathPartsToReveal.map(getPathPartChildren));
3260
+ const pathPartsChildrenFlat = pathPartsChildren.flat(1);
3261
+ const orderedPathParts = orderDirents(pathPartsChildrenFlat);
3262
+ const mergedDirents = mergeVisibleWithHiddenItems(items, orderedPathParts);
3263
+ const index = getIndex(mergedDirents, uri);
3264
+ if (index === -1) {
3265
+ throw new Error(`File not found in explorer ${uri}`);
3266
+ }
3267
+ const {
3268
+ newMinLineY,
3269
+ newMaxLineY
3270
+ } = scrollInto(index, minLineY, maxLineY);
3271
+ return {
3272
+ ...state,
3273
+ items: mergedDirents,
3274
+ focused: true,
3275
+ focusedIndex: index,
3276
+ minLineY: newMinLineY,
3277
+ maxLineY: newMaxLineY
3278
+ };
3279
+ };
3280
+
3281
+ const revealItemVisible = (state, index) => {
3282
+ const {
3283
+ minLineY,
3284
+ maxLineY
3285
+ } = state;
3286
+ const {
3287
+ newMinLineY,
3288
+ newMaxLineY
3289
+ } = scrollInto(index, minLineY, maxLineY);
3290
+ return {
3291
+ ...state,
3292
+ focused: true,
3293
+ focusedIndex: index,
3294
+ minLineY: newMinLineY,
3295
+ maxLineY: newMaxLineY
3296
+ };
3297
+ };
3298
+
3299
+ const revealItem = async (state, uri) => {
3300
+ object(state);
3301
+ string(uri);
3302
+ const {
3303
+ items
3304
+ } = state;
3305
+ const index = getIndex(items, uri);
3306
+ if (index === -1) {
3307
+ return revealItemHidden(state, uri);
3308
+ }
3309
+ return revealItemVisible(state, index);
3310
+ };
3311
+
2660
3312
  const isExpandedDirectory = dirent => {
2661
3313
  return dirent.type === DirectoryExpanded;
2662
3314
  };
@@ -2705,7 +3357,9 @@ const commandMap = {
2705
3357
  'Explorer.handleClickCurrentButKeepFocus': handleClickCurrentButKeepFocus,
2706
3358
  'Explorer.handleClickOpenFolder': handleClickOpenFolder,
2707
3359
  'Explorer.handleContextMenu': handleContextMenu,
3360
+ 'Explorer.handleDrop': handleDrop,
2708
3361
  'Explorer.handleIconThemeChange': handleIconThemeChange,
3362
+ 'Explorer.handlePaste': handlePaste,
2709
3363
  'Explorer.handlePointerDown': handlePointerDown,
2710
3364
  'Explorer.handleWheel': handleWheel,
2711
3365
  'Explorer.loadContent': loadContent,
@@ -2713,6 +3367,7 @@ const commandMap = {
2713
3367
  'Explorer.removeDirent': removeDirent,
2714
3368
  'Explorer.renameDirent': renameDirent,
2715
3369
  'Explorer.restoreState': restoreState,
3370
+ 'Explorer.revealItem': revealItem,
2716
3371
  'Explorer.saveState': saveState
2717
3372
  };
2718
3373
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/explorer-view",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Explorer Worker",
5
5
  "main": "dist/explorerViewWorkerMain.js",
6
6
  "type": "module",