vzcode 1.11.0 → 1.13.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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/index-Bm65AzMP.js +425 -0
  3. package/dist/assets/index-BpeGnq7b.js +157 -0
  4. package/dist/assets/{index-BKRpkJER.css → index-Wxh-Jaj6.css} +1 -1
  5. package/dist/assets/worker-CJWrLBN2.js +286 -0
  6. package/dist/index.html +2 -2
  7. package/package.json +18 -17
  8. package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +5 -2
  9. package/src/client/AIAssist/startAIAssist.ts +1 -1
  10. package/src/client/CodeEditor/getOrCreateEditor.ts +11 -4
  11. package/src/client/CodeEditor/index.tsx +2 -0
  12. package/src/client/CodeEditor/json1PresenceDisplay.ts +1 -1
  13. package/src/client/Icons/SplitEditorSVG.tsx +34 -0
  14. package/src/client/RunCodeWidget/index.tsx +48 -16
  15. package/src/client/RunCodeWidget/style.scss +6 -1
  16. package/src/client/TabList/Tab.tsx +1 -2
  17. package/src/client/TabList/index.tsx +1 -1
  18. package/src/client/VZCodeContext.tsx +30 -7
  19. package/src/client/VZKeyboardShortcutsDoc.tsx +2 -1
  20. package/src/client/VZSidebar/DirectoryListing.tsx +2 -1
  21. package/src/client/VZSidebar/FileListing.tsx +23 -3
  22. package/src/client/VZSidebar/Item.tsx +2 -2
  23. package/src/client/VZSidebar/Listing.tsx +8 -1
  24. package/src/client/VZSidebar/Search.tsx +7 -2
  25. package/src/client/VZSidebar/index.tsx +130 -24
  26. package/src/client/VZSidebar/styles.scss +25 -0
  27. package/src/client/tabsSearchParameters.ts +1 -2
  28. package/src/client/useActions.ts +29 -1
  29. package/src/client/useKeyboardShortcuts.ts +31 -1
  30. package/src/client/useURLSync.ts +1 -2
  31. package/src/client/vzReducer/closeTabReducer.test.ts +49 -33
  32. package/src/client/vzReducer/closeTabsReducer.ts +51 -19
  33. package/src/client/vzReducer/createInitialState.ts +9 -3
  34. package/src/client/vzReducer/findPane.ts +19 -0
  35. package/src/client/vzReducer/index.ts +38 -27
  36. package/src/client/vzReducer/openTabReducer.test.ts +38 -21
  37. package/src/client/vzReducer/openTabReducer.ts +26 -10
  38. package/src/client/vzReducer/searchReducer.ts +21 -1
  39. package/src/client/vzReducer/setActiveFileIdReducer.ts +23 -4
  40. package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +37 -8
  41. package/src/client/vzReducer/splitCurrentPaneReducer.ts +71 -0
  42. package/src/client/vzReducer/updatePane.ts +44 -0
  43. package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +30 -0
  44. package/src/server/index.js +1 -0
  45. package/src/types.ts +86 -1
  46. package/dist/assets/index--bvX81xc.js +0 -425
  47. package/dist/assets/index-BSuijdti.js +0 -157
  48. package/dist/assets/worker-Cs0ZU3m1.js +0 -291
package/dist/index.html CHANGED
@@ -20,8 +20,8 @@
20
20
  href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
21
21
  rel="stylesheet"
22
22
  />
23
- <script type="module" crossorigin src="/assets/index-BSuijdti.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-BKRpkJER.css">
23
+ <script type="module" crossorigin src="/assets/index-BpeGnq7b.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-Wxh-Jaj6.css">
25
25
  </head>
26
26
  <body>
27
27
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -14,7 +14,7 @@
14
14
  "test-interactive": "cd test/sampleDirectories/kitchenSink && node ../../../src/server/index.js",
15
15
  "prettier": "prettier {*.*,**/*.*} --write",
16
16
  "tsc": "tsc --noEmit",
17
- "dev": "set EDITOR_PORT=5173 && concurrently \"npm run test-interactive\" \"vite\"",
17
+ "dev": "cross-env EDITOR_PORT=5173 concurrently \"npm run test-interactive\" \"vite\"",
18
18
  "build": "vite build",
19
19
  "build-release": "vite build --mode release",
20
20
  "preview": "concurrently \"npm run test-interactive\" \"vite preview\"",
@@ -111,14 +111,14 @@
111
111
  "@codemirror/lang-markdown": "^6.2.5",
112
112
  "@codemirror/state": "^6.4.1",
113
113
  "@codemirror/theme-one-dark": "^6.1.2",
114
- "@codemirror/view": "^6.28.4",
114
+ "@codemirror/view": "^6.29.1",
115
115
  "@lezer/highlight": "^1.2.0",
116
- "@replit/codemirror-indentation-markers": "^6.5.2",
116
+ "@replit/codemirror-indentation-markers": "^6.5.3",
117
117
  "@replit/codemirror-interact": "^6.3.1",
118
118
  "@replit/codemirror-lang-svelte": "^6.0.0",
119
119
  "@replit/codemirror-vscode-keymap": "^6.0.2",
120
120
  "@teamwork/websocket-json-stream": "^2.0.0",
121
- "@typescript/vfs": "^1.5.3",
121
+ "@typescript/vfs": "^1.6.0",
122
122
  "@uiw/codemirror-theme-abcdef": "^4.23.0",
123
123
  "@uiw/codemirror-theme-dracula": "^4.23.0",
124
124
  "@uiw/codemirror-theme-eclipse": "^4.23.0",
@@ -140,14 +140,14 @@
140
140
  "json0-ot-diff": "^1.1.2",
141
141
  "ngrok": "^4.3.3",
142
142
  "open": "^10.1.0",
143
- "openai": "^4.52.5",
144
- "prettier-plugin-svelte": "^3.2.5",
143
+ "openai": "^4.53.2",
144
+ "prettier-plugin-svelte": "^3.2.6",
145
145
  "react": "^18.3.1",
146
146
  "react-bootstrap": "^2.10.4",
147
147
  "react-dom": "^18.3.1",
148
- "react-router-dom": "^6.24.1",
149
- "sharedb": "^5.0.2",
150
- "sharedb-client-browser": "^5.0.2",
148
+ "react-router-dom": "^6.25.1",
149
+ "sharedb": "^5.0.3",
150
+ "sharedb-client-browser": "^5.0.3",
151
151
  "vizhub-ui": "^3.23.0",
152
152
  "ws": "^8.18.0"
153
153
  },
@@ -156,14 +156,15 @@
156
156
  "@types/react-dom": "^18.3.0",
157
157
  "@vitejs/plugin-react": "^4.3.1",
158
158
  "concurrently": "^8.2.2",
159
- "prettier": "^3.3.2",
160
- "sass": "^1.77.7",
161
- "typescript": "^5.5.3",
162
- "vite": "^5.3.3",
163
- "vitest": "^2.0.2"
159
+ "cross-env": "^7.0.3",
160
+ "prettier": "^3.3.3",
161
+ "sass": "^1.77.8",
162
+ "typescript": "^5.5.4",
163
+ "vite": "^5.3.5",
164
+ "vitest": "^2.0.4"
164
165
  },
165
166
  "optionalDependencies": {
166
- "@rollup/rollup-darwin-arm64": "^4.18.1",
167
- "@rollup/rollup-win32-x64-msvc": "^4.18.1"
167
+ "@rollup/rollup-darwin-arm64": "^4.19.1",
168
+ "@rollup/rollup-win32-x64-msvc": "^4.19.1"
168
169
  }
169
170
  }
@@ -1,6 +1,9 @@
1
1
  import { EditorView, keymap } from '@codemirror/view';
2
- import { ShareDBDoc, VZCodeContent } from '../../types';
3
- import { TabState } from '../vzReducer';
2
+ import {
3
+ ShareDBDoc,
4
+ TabState,
5
+ VZCodeContent,
6
+ } from '../../types';
4
7
 
5
8
  export const AIAssistCodeMirrorKeyMap = ({
6
9
  shareDBDoc,
@@ -3,9 +3,9 @@ import {
3
3
  File,
4
4
  FileId,
5
5
  ShareDBDoc,
6
+ TabState,
6
7
  VZCodeContent,
7
8
  } from '../../types';
8
- import { TabState } from '../vzReducer';
9
9
  import { RequestId } from '../generateRequestId';
10
10
  import { formatFile } from './formatFile';
11
11
 
@@ -18,7 +18,9 @@ import { Diagnostic, linter } from '@codemirror/lint';
18
18
  import { json1Presence, textUnicode } from '../../ot';
19
19
  import {
20
20
  FileId,
21
+ PaneId,
21
22
  ShareDBDoc,
23
+ TabState,
22
24
  Username,
23
25
  VZCodeContent,
24
26
  } from '../../types';
@@ -40,7 +42,6 @@ import { keymap } from '@codemirror/view';
40
42
  import { basicSetup } from './basicSetup';
41
43
  import { InteractRule } from '@replit/codemirror-interact';
42
44
  import rainbowBrackets from '../CodeEditor/rainbowBrackets';
43
- import { TabState } from '../vzReducer';
44
45
  import { cssLanguage } from '@codemirror/lang-css';
45
46
  import { javascriptLanguage } from '@codemirror/lang-javascript';
46
47
 
@@ -102,6 +103,7 @@ const getAtPath = (obj, path) => {
102
103
  // Gets or creates an `editorCache` entry for the given file id.
103
104
  // Looks in `editorCache` first, and if not found, creates a new editor.
104
105
  export const getOrCreateEditor = ({
106
+ paneId = 'root',
105
107
  fileId,
106
108
  shareDBDoc,
107
109
  content,
@@ -118,6 +120,8 @@ export const getOrCreateEditor = ({
118
120
  enableAutoFollowRef,
119
121
  openTab,
120
122
  }: {
123
+ // TODO pass this in from the outside
124
+ paneId?: PaneId;
121
125
  fileId: FileId;
122
126
 
123
127
  // The ShareDB document that contains the file.
@@ -153,8 +157,11 @@ export const getOrCreateEditor = ({
153
157
  openTab: (tabState: TabState) => void;
154
158
  }): EditorCacheValue => {
155
159
  // Cache hit
156
- if (editorCache.has(fileId)) {
157
- return editorCache.get(fileId);
160
+
161
+ const cacheKey = fileId + '|' + paneId;
162
+
163
+ if (editorCache.has(cacheKey)) {
164
+ return editorCache.get(cacheKey);
158
165
  }
159
166
 
160
167
  // Cache miss
@@ -385,7 +392,7 @@ export const getOrCreateEditor = ({
385
392
  const editorCacheValue = { editor, themeCompartment };
386
393
 
387
394
  // Populate the cache.
388
- editorCache.set(fileId, editorCacheValue);
395
+ editorCache.set(cacheKey, editorCacheValue);
389
396
 
390
397
  return editorCacheValue;
391
398
  };
@@ -99,6 +99,7 @@ export const CodeEditor = ({
99
99
  allowGlobals,
100
100
  enableAutoFollowRef,
101
101
  openTab,
102
+ // splitPaneView,
102
103
  }),
103
104
  [
104
105
  activeFileId,
@@ -121,6 +122,7 @@ export const CodeEditor = ({
121
122
  // Guard against cases where page is still loading.
122
123
  if (!codeEditorRef.current) return;
123
124
  if (!content) return;
125
+ // if (openSplitPane) return;
124
126
 
125
127
  // Add the editor and apply the prior scroll position to the DOM.
126
128
  codeEditorRef.current.appendChild(
@@ -10,9 +10,9 @@ import {
10
10
  FileId,
11
11
  Presence,
12
12
  PresenceId,
13
+ TabState,
13
14
  Username,
14
15
  } from '../../types';
15
- import { TabState } from '../vzReducer';
16
16
 
17
17
  const debug = false;
18
18
 
@@ -0,0 +1,34 @@
1
+ export const SplitEditorSVG = () => {
2
+ return (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ width="24"
6
+ height="24"
7
+ viewBox="0 0 24 24"
8
+ fill="none"
9
+ >
10
+ <rect
11
+ x="5"
12
+ y="4"
13
+ width="7.5"
14
+ height="16"
15
+ rx="1"
16
+ ry="1"
17
+ stroke="currentColor"
18
+ strokeWidth="1.5"
19
+ fill="none"
20
+ />
21
+ <rect
22
+ x="12"
23
+ y="4"
24
+ width="7.5"
25
+ height="16"
26
+ rx="1"
27
+ ry="1"
28
+ stroke="currentColor"
29
+ strokeWidth="1.5"
30
+ fill="none"
31
+ />
32
+ </svg>
33
+ );
34
+ };
@@ -1,9 +1,14 @@
1
1
  import { useCallback, useContext, useState } from 'react';
2
2
  import { PlaySVG } from '../Icons';
3
+ import { SplitEditorSVG } from '../Icons/SplitEditorSVG';
3
4
  import { VZCodeContext } from '../VZCodeContext';
4
5
  import { OverlayTrigger, Tooltip } from '../bootstrap';
6
+
5
7
  import './style.scss';
6
8
 
9
+ // Feature flag for split pane feature (WIP)
10
+ const enableSplitPane = false;
11
+
7
12
  export const RunCodeWidget = ({
8
13
  runCodeWidgetTooltipText = (
9
14
  <>
@@ -14,7 +19,7 @@ export const RunCodeWidget = ({
14
19
  }: {
15
20
  runCodeWidgetTooltipText?: JSX.Element;
16
21
  }) => {
17
- const { runCodeRef, runPrettierRef } =
22
+ const { runCodeRef, runPrettierRef, splitCurrentPane } =
18
23
  useContext(VZCodeContext);
19
24
  const [isRunning, setIsRunning] = useState(false);
20
25
 
@@ -37,23 +42,50 @@ export const RunCodeWidget = ({
37
42
  setTimeout(() => setIsRunning(false), 1000);
38
43
  }, []);
39
44
 
45
+ const handleSplitEditor = useCallback(() => {
46
+ console.log('Split Editor');
47
+ console.log(splitCurrentPane);
48
+ splitCurrentPane();
49
+ }, [splitCurrentPane]);
50
+
40
51
  return (
41
- <div className="vz-code-run-code-widget">
42
- <OverlayTrigger
43
- placement="left"
44
- overlay={
45
- <Tooltip id="run-code-widget-tooltip">
46
- {runCodeWidgetTooltipText}
47
- </Tooltip>
48
- }
49
- >
50
- <i
51
- className={`icon-button icon-button-dark ${isRunning ? 'rotate-icon' : ''}`}
52
- onClick={handleClick}
52
+ <div>
53
+ <div className="vz-code-split-view-widget">
54
+ {enableSplitPane && (
55
+ <OverlayTrigger
56
+ placement="bottom"
57
+ overlay={
58
+ <Tooltip id="fork-file-tooltip">
59
+ Split Editor
60
+ </Tooltip>
61
+ }
62
+ >
63
+ <i
64
+ onClick={handleSplitEditor}
65
+ className="icon-button icon-button-dark"
66
+ >
67
+ <SplitEditorSVG />
68
+ </i>
69
+ </OverlayTrigger>
70
+ )}
71
+ </div>
72
+ <div className="vz-code-run-code-widget">
73
+ <OverlayTrigger
74
+ placement="bottom"
75
+ overlay={
76
+ <Tooltip id="run-code-widget-tooltip">
77
+ {runCodeWidgetTooltipText}
78
+ </Tooltip>
79
+ }
53
80
  >
54
- <PlaySVG />
55
- </i>
56
- </OverlayTrigger>
81
+ <i
82
+ className={`icon-button icon-button-dark ${isRunning ? 'rotate-icon' : ''}`}
83
+ onClick={handleClick}
84
+ >
85
+ <PlaySVG />
86
+ </i>
87
+ </OverlayTrigger>
88
+ </div>
57
89
  </div>
58
90
  );
59
91
  };
@@ -3,11 +3,16 @@
3
3
  right: 16px;
4
4
  top: 9px;
5
5
  color: var(--vh-color-neutral-04);
6
-
7
6
  .rotate-icon {
8
7
  animation: spin 1s linear;
9
8
  }
10
9
  }
10
+ .vz-code-split-view-widget {
11
+ position: absolute;
12
+ right: 51px;
13
+ top: 9px;
14
+ color: var(--vh-color-neutral-04);
15
+ }
11
16
 
12
17
  @keyframes spin {
13
18
  from {
@@ -1,6 +1,5 @@
1
1
  import { useCallback, useMemo } from 'react';
2
- import type { FileId } from '../../types';
3
- import type { TabState } from '../vzReducer';
2
+ import type { FileId, TabState } from '../../types';
4
3
  import { CloseSVG } from '../Icons';
5
4
 
6
5
  // Supports adding the file's containing folder to the tab name
@@ -1,8 +1,8 @@
1
1
  import { useContext } from 'react';
2
2
  import { VZCodeContext } from '../VZCodeContext';
3
- import { TabState } from '../vzReducer';
4
3
  import { Tab } from './Tab';
5
4
  import './style.scss';
5
+ import { TabState } from '../../types';
6
6
 
7
7
  // Displays the list of tabs above the code editor.
8
8
  export const TabList = () => {
@@ -14,14 +14,12 @@ import {
14
14
  VZCodeContent,
15
15
  SearchResults,
16
16
  SearchFileVisibility,
17
+ TabState,
18
+ PresenceIndicator,
17
19
  } from '../types';
18
20
  import { usePrettier } from './usePrettier';
19
21
  import { useTypeScript } from './useTypeScript';
20
- import {
21
- TabState,
22
- createInitialState,
23
- vzReducer,
24
- } from './vzReducer';
22
+ import { createInitialState, vzReducer } from './vzReducer';
25
23
  import {
26
24
  ThemeLabel,
27
25
  defaultTheme,
@@ -121,6 +119,7 @@ export type VZCodeContextValue = {
121
119
  id: string,
122
120
  visibility: SearchFileVisibility,
123
121
  ) => void;
122
+ toggleSearchFocused: () => void;
124
123
 
125
124
  isCreateFileModalOpen: boolean;
126
125
  handleOpenCreateFileModal: () => void;
@@ -148,6 +147,13 @@ export type VZCodeContextValue = {
148
147
 
149
148
  enableAutoFollow: boolean;
150
149
  toggleAutoFollow: () => void;
150
+
151
+ updatePresenceIndicator: (
152
+ presenceIndicator: PresenceIndicator,
153
+ ) => void;
154
+
155
+ sidebarPresenceIndicators: Array<PresenceIndicator>;
156
+ splitCurrentPane: () => void;
151
157
  };
152
158
 
153
159
  export const VZCodeProvider = ({
@@ -225,9 +231,10 @@ export const VZCodeProvider = ({
225
231
  );
226
232
 
227
233
  // Unpack state.
234
+ // print the state object to see what it contains
235
+ // console.log('state: ', state);
228
236
  const {
229
- tabList,
230
- activeFileId,
237
+ pane,
231
238
  theme,
232
239
  search,
233
240
  isSearchOpen,
@@ -236,8 +243,16 @@ export const VZCodeProvider = ({
236
243
  editorWantsFocus,
237
244
  username,
238
245
  enableAutoFollow,
246
+ sidebarPresenceIndicators,
239
247
  } = state;
240
248
 
249
+ // TODO support splitPane type
250
+ if (pane.type !== 'leafPane') {
251
+ throw new Error('Expected leafPane');
252
+ }
253
+ const tabList = pane.tabList;
254
+ const activeFileId = pane.activeFileId;
255
+
241
256
  // Functions for dispatching actions to the reducer.
242
257
  const {
243
258
  setActiveFileId,
@@ -250,6 +265,7 @@ export const VZCodeProvider = ({
250
265
  setSearch,
251
266
  setSearchResults,
252
267
  setSearchFileVisibility,
268
+ toggleSearchFocused,
253
269
  setIsSettingsOpen,
254
270
  setIsDocOpen,
255
271
  closeSettings,
@@ -257,6 +273,8 @@ export const VZCodeProvider = ({
257
273
  editorNoLongerWantsFocus,
258
274
  setUsername,
259
275
  toggleAutoFollow,
276
+ updatePresenceIndicator,
277
+ splitCurrentPane,
260
278
  } = useActions(dispatch);
261
279
 
262
280
  // Sync tab state to the URL.
@@ -344,6 +362,7 @@ export const VZCodeProvider = ({
344
362
  handleOpenCreateFileModal,
345
363
  setActiveFileLeft,
346
364
  setActiveFileRight,
365
+ toggleSearchFocused,
347
366
  runPrettierRef,
348
367
  runCodeRef,
349
368
  sidebarRef,
@@ -386,6 +405,7 @@ export const VZCodeProvider = ({
386
405
  setSearch,
387
406
  setSearchResults,
388
407
  setSearchFileVisibility,
408
+ toggleSearchFocused,
389
409
 
390
410
  isSettingsOpen,
391
411
  setIsSettingsOpen,
@@ -432,6 +452,9 @@ export const VZCodeProvider = ({
432
452
 
433
453
  enableAutoFollow,
434
454
  toggleAutoFollow,
455
+ updatePresenceIndicator,
456
+ sidebarPresenceIndicators,
457
+ splitCurrentPane,
435
458
  };
436
459
 
437
460
  return (
@@ -69,7 +69,8 @@ export const VZKeyboardShortcutsDoc = ({
69
69
  <span>Close the current tab</span>
70
70
  </li>
71
71
  <li>
72
- <strong>Alt + n</strong> <br />
72
+ <strong>Alt + n or Ctrl + Shift + n</strong>{' '}
73
+ <br />
73
74
  <span>Open the create file modal</span>
74
75
  </li>
75
76
  <li>
@@ -23,6 +23,7 @@ export const DirectoryListing = ({
23
23
  deleteDirectory,
24
24
  isDirectoryOpen,
25
25
  toggleDirectory,
26
+ sidebarPresenceIndicators,
26
27
  } = useContext(VZCodeContext);
27
28
 
28
29
  const handleClick = useCallback(() => {
@@ -63,7 +64,7 @@ export const DirectoryListing = ({
63
64
  >
64
65
  <DirectoryArrowSVG />
65
66
  </div>
66
- {name}
67
+ <div className="name">{name}</div>
67
68
  </Item>
68
69
  {children && isOpen ? (
69
70
  <div className="indentation">
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useContext } from 'react';
2
2
  import { Item } from './Item';
3
- import { FileId } from '../../types';
3
+ import { FileId, PresenceIndicator } from '../../types';
4
4
  import {
5
5
  FileSVG,
6
6
  JavaScriptSVG,
@@ -46,12 +46,14 @@ export const FileListing = ({
46
46
  handleFileClick,
47
47
  handleFileDoubleClick,
48
48
  isActive,
49
+ presence,
49
50
  }: {
50
51
  name: string;
51
52
  fileId: FileId;
52
53
  handleFileClick: (fileId: FileId) => void;
53
54
  handleFileDoubleClick: (fileId: FileId) => void;
54
55
  isActive: boolean;
56
+ presence: PresenceIndicator[];
55
57
  }) => {
56
58
  const { renameFile, deleteFile } =
57
59
  useContext(VZCodeContext);
@@ -85,8 +87,26 @@ export const FileListing = ({
85
87
  handleRenameClick={handleRenameClick}
86
88
  isActive={isActive}
87
89
  >
88
- <i className="file-icon">{getExtensionIcon(name)}</i>
89
- {name}
90
+ <div className="name">
91
+ {/* Render presence indicators to the left of the file name */}
92
+ {presence.length > 0 && (
93
+ <div className="presence-indicators">
94
+ {presence.map((indicator, index) => (
95
+ <div
96
+ key={index}
97
+ className="presence-indicator"
98
+ >
99
+ {indicator.username[0]}{' '}
100
+ {/* Display the first letter of the username */}
101
+ </div>
102
+ ))}
103
+ </div>
104
+ )}
105
+ <i className="file-icon">
106
+ {getExtensionIcon(name)}
107
+ </i>
108
+ {name}
109
+ </div>
90
110
  </Item>
91
111
  );
92
112
  };
@@ -13,9 +13,9 @@ import { DeleteConfirmationModal } from './DeleteConfirmationModal';
13
13
  import { VZCodeContext } from '../VZCodeContext';
14
14
  import { ItemId } from '../../types';
15
15
  import { getExtensionIcon } from './FileListing';
16
+ import { SplitEditorSVG } from '../Icons/SplitEditorSVG';
17
+ import { SplitPaneResizeProvider } from '../SplitPaneResizeContext';
16
18
 
17
- // TODO support renaming directories
18
- // See https://github.com/vizhub-core/vzcode/issues/103
19
19
  const enableRenameDirectory = true;
20
20
 
21
21
  // A file or directory in the sidebar.
@@ -19,10 +19,16 @@ export const Listing = ({
19
19
  handleFileClick: (fileId: FileId) => void;
20
20
  handleFileDoubleClick: (fileId: FileId) => void;
21
21
  }) => {
22
- const { activeFileId } = useContext(VZCodeContext);
22
+ const { activeFileId, sidebarPresenceIndicators } =
23
+ useContext(VZCodeContext);
23
24
  const { name, file, fileId } = entity as FileTreeFile;
24
25
  const { path, children } = entity as FileTree;
25
26
 
27
+ const presence =
28
+ sidebarPresenceIndicators?.filter(
29
+ (indicator) => indicator.fileId === fileId,
30
+ ) || [];
31
+
26
32
  return file ? (
27
33
  <FileListing
28
34
  fileId={fileId}
@@ -30,6 +36,7 @@ export const Listing = ({
30
36
  handleFileClick={handleFileClick}
31
37
  handleFileDoubleClick={handleFileDoubleClick}
32
38
  isActive={fileId === activeFileId}
39
+ presence={presence}
33
40
  />
34
41
  ) : (
35
42
  <DirectoryListing
@@ -46,7 +46,7 @@ export const Search = () => {
46
46
  shareDBDoc,
47
47
  editorCache,
48
48
  } = useContext(VZCodeContext);
49
- const { pattern, results } = search;
49
+ const { pattern, results, focused } = search;
50
50
 
51
51
  useEffect(() => {
52
52
  if (isMounted) {
@@ -67,10 +67,15 @@ export const Search = () => {
67
67
  return () => clearTimeout(delaySearch);
68
68
  } else {
69
69
  setIsMounted(true);
70
- inputRef.current.focus();
71
70
  }
72
71
  }, [pattern]);
73
72
 
73
+ useEffect(() => {
74
+ if (inputRef.current) {
75
+ inputRef.current.focus();
76
+ }
77
+ }, [focused]);
78
+
74
79
  return (
75
80
  <div>
76
81
  <Form.Group