vzcode 0.25.0 → 0.26.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.
Files changed (32) hide show
  1. package/dist/assets/index-284b7339.js +111 -0
  2. package/dist/assets/index-e160f758.css +1 -0
  3. package/dist/index.html +2 -6
  4. package/package.json +5 -5
  5. package/src/client/App.tsx +45 -225
  6. package/src/client/CodeEditor/index.tsx +36 -6
  7. package/src/client/CodeEditor/{json1PresenceBroadcast.js → json1PresenceBroadcast.ts} +5 -3
  8. package/src/client/CodeEditor/{json1PresenceDisplay.js → json1PresenceDisplay.ts} +9 -3
  9. package/src/client/PresenceNotifications/index.tsx +0 -2
  10. package/src/client/TabList/Tab.tsx +56 -0
  11. package/src/client/TabList/index.tsx +11 -62
  12. package/src/client/TabList/style.scss +21 -9
  13. package/src/client/{Settings.tsx → VZSettings.tsx} +1 -1
  14. package/src/client/{Sidebar → VZSidebar}/FileListing.tsx +3 -0
  15. package/src/client/{Sidebar → VZSidebar}/Item.tsx +7 -4
  16. package/src/client/{Sidebar → VZSidebar}/Listing.tsx +3 -0
  17. package/src/client/{Sidebar → VZSidebar}/index.tsx +5 -6
  18. package/src/client/{Sidebar → VZSidebar}/styles.scss +17 -12
  19. package/src/client/bootstrap.ts +3 -0
  20. package/src/client/style.scss +5 -2
  21. package/src/client/useActions.ts +74 -0
  22. package/src/client/useFileCRUD.ts +101 -0
  23. package/src/client/useOpenDirectories.ts +25 -7
  24. package/src/client/useSubmitOperation.ts +25 -0
  25. package/src/client/{reducer.ts → vzReducer.ts} +42 -43
  26. package/src/index.ts +34 -0
  27. package/src/server/index.js +13 -2
  28. package/dist/assets/index-8628ebab.js +0 -111
  29. package/dist/assets/index-cf19de3b.css +0 -1
  30. /package/src/client/{Sidebar → VZSidebar}/ConfirmationBox.tsx +0 -0
  31. /package/src/client/{Sidebar → VZSidebar}/DirectoryArrowSVG.jsx +0 -0
  32. /package/src/client/{Sidebar → VZSidebar}/DirectoryListing.tsx +0 -0
@@ -9,22 +9,12 @@ import ShareDBClient from 'sharedb-client-browser/dist/sharedb-client-umd.cjs';
9
9
  import { json1Presence } from '../ot';
10
10
  import { randomId } from '../randomId';
11
11
  import { CodeEditor } from './CodeEditor';
12
- import { diff } from './diff';
13
- import { Settings } from './Settings';
14
- import { Sidebar } from './Sidebar';
15
- import {
16
- FileId,
17
- Files,
18
- ShareDBDoc,
19
- VZCodeContent,
20
- } from '../types';
12
+ import { VZSettings } from './VZSettings';
13
+ import { VZSidebar } from './VZSidebar';
14
+ import { Files, ShareDBDoc, VZCodeContent } from '../types';
21
15
  import { TabList } from './TabList';
22
16
  import { useOpenDirectories } from './useOpenDirectories';
23
- import {
24
- ThemeLabel,
25
- defaultTheme,
26
- useDynamicTheme,
27
- } from './themes';
17
+ import { defaultTheme, useDynamicTheme } from './themes';
28
18
  import { useEditorCache } from './useEditorCache';
29
19
  import { usePrettier } from './usePrettier';
30
20
  // @ts-ignore
@@ -32,9 +22,12 @@ import PrettierWorker from './usePrettier/worker?worker';
32
22
  import { SplitPaneResizeProvider } from './SplitPaneResizeContext';
33
23
  import { Resizer } from './Resizer';
34
24
  import { PresenceNotifications } from './PresenceNotifications';
35
- import { reducer } from './reducer';
36
- import './style.scss';
37
25
  import { PrettierErrorOverlay } from './PrettierErrorOverlay';
26
+ import { vzReducer } from './vzReducer';
27
+ import { useActions } from './useActions';
28
+ import { useFileCRUD } from './useFileCRUD';
29
+ import './style.scss';
30
+ import { useSubmitOperation } from './useSubmitOperation';
38
31
 
39
32
  // Instantiate the Prettier worker.
40
33
  const prettierWorker = new PrettierWorker();
@@ -60,20 +53,9 @@ function App() {
60
53
  const [shareDBDoc, setShareDBDoc] =
61
54
  useState<ShareDBDoc<VZCodeContent> | null>(null);
62
55
 
63
- // A helper function to submit operations to the ShareDB document
64
-
65
56
  const submitOperation: (
66
57
  next: (content: VZCodeContent) => VZCodeContent,
67
- ) => void = useCallback(
68
- (next) => {
69
- const content: VZCodeContent = shareDBDoc.data;
70
- const op = diff(content, next(content));
71
- if (op && shareDBDoc) {
72
- shareDBDoc.submitOp(op);
73
- }
74
- },
75
- [shareDBDoc],
76
- );
58
+ ) => void = useSubmitOperation(shareDBDoc);
77
59
 
78
60
  // Auto-run Pretter after local changes.
79
61
  const { prettierError } = usePrettier(
@@ -82,8 +64,6 @@ function App() {
82
64
  prettierWorker,
83
65
  );
84
66
 
85
- console.log('prettierError', prettierError);
86
-
87
67
  // Local ShareDB presence, for broadcasting our cursor position
88
68
  // so other clients can see it.
89
69
  // See https://share.github.io/sharedb/api/local-presence
@@ -125,6 +105,12 @@ function App() {
125
105
  // This decouples rendering logic from ShareDB.
126
106
  // This callback gets called on each change.
127
107
  shareDBDoc.on('op', () => {
108
+ // TODO consider excluding file contents from this,
109
+ // because currently we are re-rendering the entire
110
+ // document on each file change (each keystroke while editing).
111
+ // This is not required, because no part of the app outside
112
+ // of the CodeMirror integration uses those file contents.
113
+ // The Sidebar only needs to know file names, not contents.
128
114
  setContent(shareDBDoc.data);
129
115
  });
130
116
 
@@ -156,91 +142,31 @@ function App() {
156
142
  }, []);
157
143
 
158
144
  // https://react.dev/reference/react/useReducer
159
- const [state, dispatch] = useReducer(reducer, {
145
+ const [state, dispatch] = useReducer(vzReducer, {
160
146
  tabList: [],
161
147
  activeFileId: null,
162
148
  theme: defaultTheme,
163
149
  isSettingsOpen: false,
164
150
  });
165
151
 
166
- //Reducer states
167
- const tabList = state.tabList;
168
- const activeFileId = state.activeFileId;
169
- const theme = state.theme;
170
- const isSettingsOpen = state.isSettingsOpen;
171
- // TODO phase this out as we complete the refactoring
172
- // It's here now for backwards compatibility
173
-
174
- const setTabList = useCallback(
175
- (tabList) => {
176
- dispatch({
177
- type: 'set_tab_list',
178
- tabList,
179
- });
180
- },
181
- [dispatch],
182
- );
183
-
184
- const setActiveFileId = useCallback(
185
- (activeFileId) => {
186
- dispatch({
187
- type: 'set_active_fileId',
188
- activeFileId,
189
- });
190
- },
191
- [dispatch],
192
- );
193
-
194
- const openTab = useCallback(
195
- (fileId: FileId) => {
196
- dispatch({
197
- type: 'open_tab',
198
- fileId,
199
- });
200
- },
201
- [dispatch],
202
- );
203
-
204
- const closeTab = useCallback(
205
- (fileIdToRemove: FileId) => {
206
- dispatch({
207
- type: 'close_tab',
208
- fileIdToRemove,
209
- });
210
- },
211
- [dispatch],
212
- );
213
-
214
- const multiCloseTab = useCallback(
215
- (idsToDelete: Array<FileId>) => {
216
- dispatch({
217
- type: 'multi_close_tab',
218
- idsToDelete,
219
- });
220
- },
221
- [dispatch],
222
- );
152
+ // Unpack state.
153
+ const { tabList, activeFileId, theme, isSettingsOpen } =
154
+ state;
223
155
 
224
- const setTheme = useCallback(
225
- (themeLabel: ThemeLabel) => {
226
- dispatch({
227
- type: 'set_Theme',
228
- themeLabel: themeLabel,
229
- });
230
- },
231
- [dispatch],
232
- );
156
+ // Functions for dispatching actions to the reducer.
157
+ const {
158
+ setActiveFileId,
159
+ openTab,
160
+ closeTabs,
161
+ setTheme,
162
+ setIsSettingsOpen,
163
+ closeSettings,
164
+ } = useActions(dispatch);
233
165
 
234
- // True to show the settings modal.
235
- const setIsSettingsOpen = useCallback(
236
- (value: boolean) => {
237
- dispatch({
238
- type: 'set_Is_Settings_Open',
239
- value: value,
240
- });
241
- },
242
- [dispatch],
243
- );
166
+ // The set of open directories.
167
+ // TODO move this into reducer/useActions
168
+ const { isDirectoryOpen, toggleDirectory } =
169
+ useOpenDirectories();
244
170
 
245
171
  // Cache of CodeMirror editors by file id.
246
172
  const editorCache = useEditorCache();
@@ -248,119 +174,12 @@ function App() {
248
174
  // Handle dynamic theme changes.
249
175
  useDynamicTheme(editorCache, theme);
250
176
 
251
- // The set of open directories.
252
- const { isDirectoryOpen, toggleDirectory } =
253
- useOpenDirectories();
254
-
255
- // TODO move this logic to a hook called `useFileCRUD`
256
- // Include
257
- // - `createFile`
258
- // - `handleRenameFileClick`
259
- // - `deleteFile`
260
- // - `handleDeleteFileClick`
261
- const createFile = useCallback(() => {
262
- const name = prompt('Enter new file name');
263
- if (name) {
264
- submitOperation((document) => ({
265
- ...document,
266
- files: {
267
- ...document.files,
268
- [randomId()]: { name, text: '' },
269
- },
270
- }));
271
- }
272
- }, [submitOperation]);
273
-
274
- // Called when a file in the sidebar is renamed.
275
- const handleRenameFileClick = useCallback(
276
- (fileId: FileId, newName: string) => {
277
- submitOperation((document) => ({
278
- ...document,
279
- files: {
280
- ...document.files,
281
- [fileId]: {
282
- ...document.files[fileId],
283
- name: newName,
284
- },
285
- },
286
- }));
287
- },
288
- [submitOperation],
289
- );
290
-
291
- const deleteFile = useCallback(
292
- (fileId: FileId) => {
293
- submitOperation((document) => {
294
- const updatedFiles = { ...document.files };
295
- delete updatedFiles[fileId];
296
- return { ...document, files: updatedFiles };
297
- });
298
- closeTab(fileId);
299
- },
300
- [submitOperation, closeTab],
301
- );
302
-
303
- const deleteDirectory = useCallback(
304
- (path: FileId) => {
305
- let tabsToDelete: Array<FileId> = [];
306
- submitOperation((document) => {
307
- const updatedFiles = { ...document.files };
308
- for (const key in updatedFiles) {
309
- if (updatedFiles[key].name.includes(path)) {
310
- tabsToDelete.push(key);
311
- delete updatedFiles[key];
312
- }
313
- }
314
- return { ...document, files: updatedFiles };
315
- });
316
- multiCloseTab(tabsToDelete);
317
- },
318
- [submitOperation, multiCloseTab],
319
- );
320
-
321
- const handleDeleteClick = useCallback(
322
- (key: string) => {
323
- //Regex to identify if the key is a file path or a file id.
324
- if (/^[0-9]*$/.test(key)) {
325
- if (key.length == 8) {
326
- deleteFile(key);
327
- } else {
328
- deleteDirectory(key);
329
- }
330
- } else {
331
- deleteDirectory(key);
332
- }
333
- },
334
- [deleteFile, deleteDirectory],
335
- );
336
-
337
- const handleSettingsClose = useCallback(() => {
338
- setIsSettingsOpen(false);
339
- }, []);
340
-
341
- // Set `doc.data.isInteracting` to `true` when the user is interacting
342
- // via interactive code widgets (e.g. Alt+drag), and `false` when they are not.
343
- const interactTimeoutRef = useRef(null);
344
- const handleInteract = useCallback(() => {
345
- // Set `isInteracting: true` if not already set.
346
- if (!interactTimeoutRef.current) {
347
- submitOperation((document) => ({
348
- ...document,
349
- isInteracting: true,
350
- }));
351
- } else {
352
- clearTimeout(interactTimeoutRef.current);
353
- }
354
-
355
- // Set `isInteracting: false` after a delay.
356
- interactTimeoutRef.current = setTimeout(() => {
357
- interactTimeoutRef.current = null;
358
- submitOperation((document) => ({
359
- ...document,
360
- isInteracting: false,
361
- }));
362
- }, 800);
363
- }, [submitOperation]);
177
+ // Handle file CRUD operations.
178
+ const {
179
+ createFile,
180
+ handleRenameFileClick,
181
+ handleDeleteClick,
182
+ } = useFileCRUD({ submitOperation, closeTabs });
364
183
 
365
184
  // Isolate the files object from the document.
366
185
  const files: Files | null = content
@@ -371,7 +190,7 @@ function App() {
371
190
  <SplitPaneResizeProvider>
372
191
  <div className="app">
373
192
  <div className="left">
374
- <Sidebar
193
+ <VZSidebar
375
194
  createFile={createFile}
376
195
  files={files}
377
196
  handleRenameFileClick={handleRenameFileClick}
@@ -380,10 +199,11 @@ function App() {
380
199
  setIsSettingsOpen={setIsSettingsOpen}
381
200
  isDirectoryOpen={isDirectoryOpen}
382
201
  toggleDirectory={toggleDirectory}
202
+ activeFileId={activeFileId}
383
203
  />
384
- <Settings
204
+ <VZSettings
385
205
  show={isSettingsOpen}
386
- onClose={handleSettingsClose}
206
+ onClose={closeSettings}
387
207
  theme={theme}
388
208
  setTheme={setTheme}
389
209
  />
@@ -394,16 +214,16 @@ function App() {
394
214
  tabList={tabList}
395
215
  activeFileId={activeFileId}
396
216
  setActiveFileId={setActiveFileId}
397
- closeTab={closeTab}
217
+ closeTabs={closeTabs}
398
218
  />
399
219
  {content && activeFileId ? (
400
220
  <CodeEditor
401
221
  shareDBDoc={shareDBDoc}
222
+ submitOperation={submitOperation}
402
223
  localPresence={localPresence}
403
224
  docPresence={docPresence}
404
225
  activeFileId={activeFileId}
405
226
  theme={theme}
406
- onInteract={handleInteract}
407
227
  editorCache={editorCache}
408
228
  />
409
229
  ) : null}
@@ -1,4 +1,8 @@
1
- import { useRef, useLayoutEffect, useContext } from 'react';
1
+ import {
2
+ useRef,
3
+ useLayoutEffect,
4
+ useCallback,
5
+ } from 'react';
2
6
  import {
3
7
  FileId,
4
8
  ShareDBDoc,
@@ -11,20 +15,22 @@ import {
11
15
  } from '../useEditorCache';
12
16
  import { getOrCreateEditor } from './getOrCreateEditor';
13
17
  import './style.scss';
14
- // import { usePrettier } from '../usePrettier';
15
18
 
16
19
  export const CodeEditor = ({
17
20
  activeFileId,
18
21
  shareDBDoc,
22
+ submitOperation,
19
23
  localPresence,
20
24
  docPresence,
21
25
  theme = defaultTheme,
22
26
  filesPath = ['files'],
23
- onInteract,
24
27
  editorCache,
25
28
  }: {
26
29
  activeFileId: FileId;
27
30
  shareDBDoc: ShareDBDoc<VZCodeContent> | null;
31
+ submitOperation: (
32
+ next: (content: VZCodeContent) => VZCodeContent,
33
+ ) => void;
28
34
  localPresence?: any;
29
35
  docPresence?: any;
30
36
  theme?: ThemeLabel;
@@ -32,14 +38,38 @@ export const CodeEditor = ({
32
38
  // The path of the files object in the ShareDB document.
33
39
  // Defaults to `files` if not provided.
34
40
  filesPath?: string[];
35
- onInteract?: () => void;
36
41
  editorCache: EditorCache;
37
42
  }) => {
38
43
  const ref = useRef<HTMLDivElement>(null);
44
+
45
+ // Set `doc.data.isInteracting` to `true` when the user is interacting
46
+ // via interactive code widgets (e.g. Alt+drag), and `false` when they are not.
47
+ const interactTimeoutRef = useRef(null);
48
+
49
+ const onInteract = useCallback(() => {
50
+ // Set `isInteracting: true` if not already set.
51
+ if (!interactTimeoutRef.current) {
52
+ submitOperation((document) => ({
53
+ ...document,
54
+ isInteracting: true,
55
+ }));
56
+ } else {
57
+ clearTimeout(interactTimeoutRef.current);
58
+ }
59
+
60
+ // Set `isInteracting: false` after a delay.
61
+ interactTimeoutRef.current = setTimeout(() => {
62
+ interactTimeoutRef.current = null;
63
+ submitOperation((document) => ({
64
+ ...document,
65
+ isInteracting: false,
66
+ }));
67
+ }, 800);
68
+ }, [submitOperation]);
69
+
39
70
  // Every time the active file switches from one file to another,
40
71
  // the editor corresponding to the old file is removed from the DOM,
41
72
  // and the editor corresponding to the new file is added to the DOM.
42
-
43
73
  useLayoutEffect(() => {
44
74
  // Guard against cases where page is still loading.
45
75
  if (!ref.current) return;
@@ -67,7 +97,7 @@ export const CodeEditor = ({
67
97
  // This happens every time `activeFileId` changes.
68
98
  ref.current.removeChild(editorCacheValue.editor.dom);
69
99
  };
70
- }, [shareDBDoc, activeFileId]);
100
+ }, [shareDBDoc, activeFileId, onInteract]);
71
101
 
72
102
  return <div className="vz-code-editor" ref={ref}></div>;
73
103
  };
@@ -1,5 +1,5 @@
1
+ import { ViewUpdate } from '@codemirror/view';
1
2
  import { EditorView } from 'codemirror';
2
- import { username } from '../Settings.tsx';
3
3
 
4
4
  // Deals with broadcasting changes in cursor location and selection.
5
5
  export const json1PresenceBroadcast = ({
@@ -7,7 +7,7 @@ export const json1PresenceBroadcast = ({
7
7
  localPresence,
8
8
  }) =>
9
9
  // See https://discuss.codemirror.net/t/codemirror-6-proper-way-to-listen-for-changes/2395/10
10
- EditorView.updateListener.of((viewUpdate) => {
10
+ EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
11
11
  // If this update modified the cursor / selection,
12
12
  // we broadcast the selection update via ShareDB presence.
13
13
  if (viewUpdate.selectionSet) {
@@ -23,7 +23,9 @@ export const json1PresenceBroadcast = ({
23
23
  const presence = {
24
24
  start: [...path, from],
25
25
  end: [...path, to],
26
- username: username,
26
+
27
+ // TODO fill this in with the user's actual username.
28
+ username: 'anonymous',
27
29
  };
28
30
 
29
31
  // Broadcast presence to remote clients!
@@ -20,7 +20,12 @@ export const json1PresenceDisplay = ({
20
20
  }) => [
21
21
  ViewPlugin.fromClass(
22
22
  class {
23
- constructor(view) {
23
+ // The decorations to display.
24
+ // This is a RangeSet of Decoration objects.
25
+ // See https://codemirror.net/6/docs/ref/#view.Decoration
26
+ decorations: RangeSet<Decoration>;
27
+
28
+ constructor(view: EditorView) {
24
29
  // Initialize decorations to empty array so CodeMirror doesn't crash.
25
30
  this.decorations = RangeSet.of([]);
26
31
 
@@ -161,12 +166,13 @@ const pathMatches = (path, presence) => {
161
166
 
162
167
  // Displays a single remote presence cursor.
163
168
  class PresenceWidget extends WidgetType {
164
- constructor(id) {
169
+ id: string;
170
+ constructor(id: string) {
165
171
  super();
166
172
  this.id = id;
167
173
  }
168
174
 
169
- eq(other) {
175
+ eq(other: PresenceWidget) {
170
176
  return other.id === this.id;
171
177
  }
172
178
 
@@ -66,8 +66,6 @@ export const PresenceNotifications = ({
66
66
  // `false` means user has left the session.
67
67
  const join = update !== null;
68
68
 
69
- console.log('join', join);
70
-
71
69
  // Figure out if we have ever seen this user before.
72
70
 
73
71
  // if (join && docPresence.remotePresences[presenceId] === undefined){
@@ -0,0 +1,56 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { FileId } from '../../types';
3
+
4
+ // Supports adding the file's containing folder to the tab name
5
+ const fileNameSplit = (fileName: string) => {
6
+ const split = fileName.split('/');
7
+ if (split.length === 1) return split[split.length - 1];
8
+ return (
9
+ split[split.length - 2] + '/' + split[split.length - 1]
10
+ );
11
+ };
12
+
13
+ export const Tab = ({
14
+ fileId,
15
+ isActive,
16
+ setActiveFileId,
17
+ closeTabs,
18
+ fileName,
19
+ }: {
20
+ fileId: FileId;
21
+ isActive: boolean;
22
+ setActiveFileId: (fileId: FileId) => void;
23
+ closeTabs: (fileIds: FileId[]) => void;
24
+ fileName: string;
25
+ }) => {
26
+ const handleCloseClick = useCallback(
27
+ (event: React.MouseEvent) => {
28
+ // Stop propagation so that the outer listener doesn't fire.
29
+ event.stopPropagation();
30
+
31
+ closeTabs([fileId]);
32
+ },
33
+ [closeTabs, fileId],
34
+ );
35
+
36
+ const tabName = useMemo(
37
+ () => fileNameSplit(fileName),
38
+ [fileName],
39
+ );
40
+
41
+ return (
42
+ <div
43
+ className={isActive ? 'tab active' : 'tab'}
44
+ onClick={() => {
45
+ setActiveFileId(fileId);
46
+ }}
47
+ >
48
+ {tabName}
49
+ <div
50
+ // className={isActive ? 'bx bx-x tab-close' : ''}
51
+ className={'bx bx-x tab-close'}
52
+ onClick={handleCloseClick}
53
+ ></div>
54
+ </div>
55
+ );
56
+ };
@@ -1,89 +1,37 @@
1
- import { useCallback, useMemo, useEffect } from 'react';
1
+ import { useCallback, useEffect } from 'react';
2
2
  import { FileId, Files } from '../../types';
3
+ import { Tab } from './Tab';
3
4
  import './style.scss';
4
5
 
5
- // Supports adding the file's containing folder to the tab name
6
- const fileNameSplit = (fileName) => {
7
- const split = fileName.split('/');
8
- if (split.length === 1) return split[split.length - 1];
9
- return (
10
- split[split.length - 2] + '/' + split[split.length - 1]
11
- );
12
- };
13
-
14
- const Tab = ({
15
- fileId,
16
- isActive,
17
- setActiveFileId,
18
- closeTab,
19
- fileName,
20
- }: {
21
- fileId: FileId;
22
- isActive: boolean;
23
- setActiveFileId: (fileId: FileId) => void;
24
- closeTab: (fileId: FileId) => void;
25
- fileName: string;
26
- }) => {
27
- const handleCloseClick = useCallback(
28
- (event: React.MouseEvent) => {
29
- // Stop propagation so that the outer listener doesn't fire.
30
- event.stopPropagation();
31
-
32
- closeTab(fileId);
33
- },
34
- [closeTab, fileId],
35
- );
36
-
37
- const tabName = useMemo(
38
- () => fileNameSplit(fileName),
39
- [fileName],
40
- );
41
-
42
- return (
43
- <div
44
- className={isActive ? 'tab active' : 'tab'}
45
- onClick={() => {
46
- setActiveFileId(fileId);
47
- }}
48
- >
49
- {tabName}
50
- <div
51
- className={isActive ? 'bx bx-x tab-close' : ''}
52
- onClick={handleCloseClick}
53
- ></div>
54
- </div>
55
- );
56
- };
57
-
58
6
  // Displays the list of tabs above the code editor.
59
7
  export const TabList = ({
60
8
  files,
61
9
  tabList,
62
10
  activeFileId,
63
11
  setActiveFileId,
64
- closeTab,
12
+ closeTabs,
65
13
  }: {
66
14
  files: Files;
67
15
  tabList: FileId[];
68
16
  activeFileId: FileId;
69
17
  setActiveFileId: (fileId: FileId) => void;
70
- closeTab: (fileId: FileId) => void;
18
+ closeTabs: (fileIds: FileId[]) => void;
71
19
  }) => {
72
- //Create a function to perform the logic to delete the current tab
20
+ // Close the active tab on alt+w
73
21
  const handleKeyPress = useCallback(
74
22
  (event: { altKey: boolean; key: string }) => {
75
23
  if (event.altKey == true) {
76
24
  if (event.key == 'w') {
77
- closeTab(activeFileId);
25
+ closeTabs([activeFileId]);
78
26
  }
79
27
  }
80
28
  },
81
- [closeTab, activeFileId],
29
+ [closeTabs, activeFileId],
82
30
  );
31
+
32
+ // Add the global keydown event listener
83
33
  useEffect(() => {
84
- // attach the event listener
85
34
  document.addEventListener('keydown', handleKeyPress);
86
- // remove the event listener
87
35
  return () => {
88
36
  document.removeEventListener(
89
37
  'keydown',
@@ -91,6 +39,7 @@ export const TabList = ({
91
39
  );
92
40
  };
93
41
  }, [handleKeyPress]);
42
+
94
43
  return (
95
44
  <div className="vz-tab-list">
96
45
  {files &&
@@ -100,7 +49,7 @@ export const TabList = ({
100
49
  fileId={fileId}
101
50
  isActive={fileId === activeFileId}
102
51
  setActiveFileId={setActiveFileId}
103
- closeTab={closeTab}
52
+ closeTabs={closeTabs}
104
53
  fileName={files[fileId].name}
105
54
  />
106
55
  ))}