vzcode 1.12.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.
- package/dist/assets/index-Bm65AzMP.js +425 -0
- package/dist/assets/index-BpeGnq7b.js +157 -0
- package/dist/assets/{index-BKRpkJER.css → index-Wxh-Jaj6.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +10 -10
- package/src/client/CodeEditor/getOrCreateEditor.ts +10 -3
- package/src/client/CodeEditor/index.tsx +2 -0
- package/src/client/Icons/SplitEditorSVG.tsx +34 -0
- package/src/client/RunCodeWidget/index.tsx +48 -16
- package/src/client/RunCodeWidget/style.scss +6 -1
- package/src/client/VZCodeContext.tsx +16 -0
- package/src/client/VZSidebar/DirectoryListing.tsx +2 -1
- package/src/client/VZSidebar/FileListing.tsx +23 -3
- package/src/client/VZSidebar/Item.tsx +2 -2
- package/src/client/VZSidebar/Listing.tsx +8 -1
- package/src/client/VZSidebar/index.tsx +43 -0
- package/src/client/VZSidebar/styles.scss +25 -0
- package/src/client/useActions.ts +19 -0
- package/src/client/vzReducer/createInitialState.ts +1 -0
- package/src/client/vzReducer/index.ts +21 -2
- package/src/client/vzReducer/splitCurrentPaneReducer.ts +71 -0
- package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +30 -0
- package/src/types.ts +24 -1
- package/dist/assets/index-4cYZN5Uf.js +0 -157
- package/dist/assets/index-CvSVuMne.js +0 -425
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-
|
|
24
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Multiplayer code editor system",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -111,7 +111,7 @@
|
|
|
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.
|
|
114
|
+
"@codemirror/view": "^6.29.1",
|
|
115
115
|
"@lezer/highlight": "^1.2.0",
|
|
116
116
|
"@replit/codemirror-indentation-markers": "^6.5.3",
|
|
117
117
|
"@replit/codemirror-interact": "^6.3.1",
|
|
@@ -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.
|
|
143
|
+
"openai": "^4.53.2",
|
|
144
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
148
|
"react-router-dom": "^6.25.1",
|
|
149
|
-
"sharedb": "^5.0.
|
|
150
|
-
"sharedb-client-browser": "^5.0.
|
|
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
|
},
|
|
@@ -159,12 +159,12 @@
|
|
|
159
159
|
"cross-env": "^7.0.3",
|
|
160
160
|
"prettier": "^3.3.3",
|
|
161
161
|
"sass": "^1.77.8",
|
|
162
|
-
"typescript": "^5.5.
|
|
163
|
-
"vite": "^5.3.
|
|
164
|
-
"vitest": "^2.0.
|
|
162
|
+
"typescript": "^5.5.4",
|
|
163
|
+
"vite": "^5.3.5",
|
|
164
|
+
"vitest": "^2.0.4"
|
|
165
165
|
},
|
|
166
166
|
"optionalDependencies": {
|
|
167
|
-
"@rollup/rollup-darwin-arm64": "^4.
|
|
168
|
-
"@rollup/rollup-win32-x64-msvc": "^4.
|
|
167
|
+
"@rollup/rollup-darwin-arm64": "^4.19.1",
|
|
168
|
+
"@rollup/rollup-win32-x64-msvc": "^4.19.1"
|
|
169
169
|
}
|
|
170
170
|
}
|
|
@@ -18,6 +18,7 @@ import { Diagnostic, linter } from '@codemirror/lint';
|
|
|
18
18
|
import { json1Presence, textUnicode } from '../../ot';
|
|
19
19
|
import {
|
|
20
20
|
FileId,
|
|
21
|
+
PaneId,
|
|
21
22
|
ShareDBDoc,
|
|
22
23
|
TabState,
|
|
23
24
|
Username,
|
|
@@ -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
|
-
|
|
157
|
-
|
|
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(
|
|
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(
|
|
@@ -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
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
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 {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
SearchResults,
|
|
16
16
|
SearchFileVisibility,
|
|
17
17
|
TabState,
|
|
18
|
+
PresenceIndicator,
|
|
18
19
|
} from '../types';
|
|
19
20
|
import { usePrettier } from './usePrettier';
|
|
20
21
|
import { useTypeScript } from './useTypeScript';
|
|
@@ -146,6 +147,13 @@ export type VZCodeContextValue = {
|
|
|
146
147
|
|
|
147
148
|
enableAutoFollow: boolean;
|
|
148
149
|
toggleAutoFollow: () => void;
|
|
150
|
+
|
|
151
|
+
updatePresenceIndicator: (
|
|
152
|
+
presenceIndicator: PresenceIndicator,
|
|
153
|
+
) => void;
|
|
154
|
+
|
|
155
|
+
sidebarPresenceIndicators: Array<PresenceIndicator>;
|
|
156
|
+
splitCurrentPane: () => void;
|
|
149
157
|
};
|
|
150
158
|
|
|
151
159
|
export const VZCodeProvider = ({
|
|
@@ -223,6 +231,8 @@ export const VZCodeProvider = ({
|
|
|
223
231
|
);
|
|
224
232
|
|
|
225
233
|
// Unpack state.
|
|
234
|
+
// print the state object to see what it contains
|
|
235
|
+
// console.log('state: ', state);
|
|
226
236
|
const {
|
|
227
237
|
pane,
|
|
228
238
|
theme,
|
|
@@ -233,6 +243,7 @@ export const VZCodeProvider = ({
|
|
|
233
243
|
editorWantsFocus,
|
|
234
244
|
username,
|
|
235
245
|
enableAutoFollow,
|
|
246
|
+
sidebarPresenceIndicators,
|
|
236
247
|
} = state;
|
|
237
248
|
|
|
238
249
|
// TODO support splitPane type
|
|
@@ -262,6 +273,8 @@ export const VZCodeProvider = ({
|
|
|
262
273
|
editorNoLongerWantsFocus,
|
|
263
274
|
setUsername,
|
|
264
275
|
toggleAutoFollow,
|
|
276
|
+
updatePresenceIndicator,
|
|
277
|
+
splitCurrentPane,
|
|
265
278
|
} = useActions(dispatch);
|
|
266
279
|
|
|
267
280
|
// Sync tab state to the URL.
|
|
@@ -439,6 +452,9 @@ export const VZCodeProvider = ({
|
|
|
439
452
|
|
|
440
453
|
enableAutoFollow,
|
|
441
454
|
toggleAutoFollow,
|
|
455
|
+
updatePresenceIndicator,
|
|
456
|
+
sidebarPresenceIndicators,
|
|
457
|
+
splitCurrentPane,
|
|
442
458
|
};
|
|
443
459
|
|
|
444
460
|
return (
|
|
@@ -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
|
-
<
|
|
89
|
-
|
|
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 } =
|
|
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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useCallback,
|
|
3
3
|
useContext,
|
|
4
|
+
useEffect,
|
|
4
5
|
useMemo,
|
|
5
6
|
useState,
|
|
6
7
|
} from 'react';
|
|
@@ -8,6 +9,9 @@ import {
|
|
|
8
9
|
FileId,
|
|
9
10
|
FileTree,
|
|
10
11
|
FileTreeFile,
|
|
12
|
+
Presence,
|
|
13
|
+
PresenceId,
|
|
14
|
+
PresenceIndicator,
|
|
11
15
|
} from '../../types';
|
|
12
16
|
import { Tooltip, OverlayTrigger } from '../bootstrap';
|
|
13
17
|
import { Search } from './Search';
|
|
@@ -117,6 +121,9 @@ export const VZSidebar = ({
|
|
|
117
121
|
sidebarRef,
|
|
118
122
|
enableAutoFollow,
|
|
119
123
|
toggleAutoFollow,
|
|
124
|
+
docPresence,
|
|
125
|
+
updatePresenceIndicator,
|
|
126
|
+
sidebarPresenceIndicators,
|
|
120
127
|
} = useContext(VZCodeContext);
|
|
121
128
|
|
|
122
129
|
const fileTree = useMemo(
|
|
@@ -164,6 +171,42 @@ export const VZSidebar = ({
|
|
|
164
171
|
handleDrop,
|
|
165
172
|
} = useDragAndDrop();
|
|
166
173
|
|
|
174
|
+
// Track presence of remote users across files
|
|
175
|
+
// so that they can be displayed in the sidebar.
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
docPresence;
|
|
178
|
+
}, []);
|
|
179
|
+
|
|
180
|
+
// Track the presence indicators for display in sidebar
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (docPresence) {
|
|
183
|
+
const handleReceive = (
|
|
184
|
+
presenceId: PresenceId,
|
|
185
|
+
update: Presence,
|
|
186
|
+
) => {
|
|
187
|
+
const presenceIndicator: PresenceIndicator = {
|
|
188
|
+
username: update.username,
|
|
189
|
+
fileId: update.start[1] as FileId,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
console.log('Got presence!');
|
|
193
|
+
// console.log({presenceId,update})
|
|
194
|
+
console.log(
|
|
195
|
+
JSON.stringify(presenceIndicator, null, 2),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
updatePresenceIndicator(presenceIndicator);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
docPresence.on('receive', handleReceive);
|
|
202
|
+
return () => {
|
|
203
|
+
docPresence.off('receive', handleReceive);
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}, [docPresence]);
|
|
207
|
+
|
|
208
|
+
console.log(sidebarPresenceIndicators);
|
|
209
|
+
|
|
167
210
|
return (
|
|
168
211
|
<div
|
|
169
212
|
className="vz-sidebar"
|
|
@@ -253,4 +253,29 @@
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
|
+
.name {
|
|
257
|
+
display: flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
white-space: nowrap;
|
|
260
|
+
overflow: hidden;
|
|
261
|
+
gap: 6px;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.presence-indicators {
|
|
265
|
+
display: flex;
|
|
266
|
+
gap: 4px;
|
|
267
|
+
margin-right: 6px; /* Space between the indicators and the file name */
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.presence-indicator {
|
|
271
|
+
width: 16px;
|
|
272
|
+
height: 16px;
|
|
273
|
+
border-radius: 50%;
|
|
274
|
+
display: flex;
|
|
275
|
+
align-items: center;
|
|
276
|
+
justify-content: center;
|
|
277
|
+
font-size: 10px;
|
|
278
|
+
background-color: #ff5733;
|
|
279
|
+
color: #ffffff;
|
|
280
|
+
}
|
|
256
281
|
}
|
package/src/client/useActions.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { useCallback } from 'react';
|
|
|
2
2
|
import { ThemeLabel } from './themes';
|
|
3
3
|
import {
|
|
4
4
|
FileId,
|
|
5
|
+
PresenceIndicator,
|
|
5
6
|
SearchFileVisibility,
|
|
6
7
|
ShareDBDoc,
|
|
7
8
|
TabState,
|
|
@@ -172,6 +173,22 @@ export const useActions = (
|
|
|
172
173
|
});
|
|
173
174
|
}, [dispatch]);
|
|
174
175
|
|
|
176
|
+
const updatePresenceIndicator = useCallback(
|
|
177
|
+
(presenceIndicator: PresenceIndicator) => {
|
|
178
|
+
dispatch({
|
|
179
|
+
type: 'update_presence_indicator',
|
|
180
|
+
presenceIndicator,
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
[dispatch],
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const splitCurrentPane = useCallback(() => {
|
|
187
|
+
dispatch({
|
|
188
|
+
type: 'split_current_pane',
|
|
189
|
+
});
|
|
190
|
+
}, [dispatch]);
|
|
191
|
+
|
|
175
192
|
return {
|
|
176
193
|
setActiveFileId,
|
|
177
194
|
setActiveFileLeft,
|
|
@@ -191,5 +208,7 @@ export const useActions = (
|
|
|
191
208
|
editorNoLongerWantsFocus,
|
|
192
209
|
setUsername,
|
|
193
210
|
toggleAutoFollow,
|
|
211
|
+
updatePresenceIndicator,
|
|
212
|
+
splitCurrentPane,
|
|
194
213
|
};
|
|
195
214
|
};
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
FileId,
|
|
3
3
|
Pane,
|
|
4
4
|
PaneId,
|
|
5
|
+
PresenceIndicator,
|
|
5
6
|
SearchFileVisibility,
|
|
6
7
|
SearchResults,
|
|
7
8
|
ShareDBDoc,
|
|
@@ -29,6 +30,8 @@ import {
|
|
|
29
30
|
toggleSearchFocusedReducer,
|
|
30
31
|
} from './searchReducer';
|
|
31
32
|
import { toggleAutoFollowReducer } from './toggleAutoFollowReducer';
|
|
33
|
+
import { updatePresenceIndicatorReducer } from './updatePresenceIndicatorReducer';
|
|
34
|
+
import { splitCurrentPaneReducer } from './splitCurrentPaneReducer';
|
|
32
35
|
export { createInitialState } from './createInitialState';
|
|
33
36
|
|
|
34
37
|
// The shape of the state managed by the reducer.
|
|
@@ -63,6 +66,10 @@ export type VZState = {
|
|
|
63
66
|
// This is a feature that automatically scrolls the editor to the
|
|
64
67
|
// currently active remote user's cursor position.
|
|
65
68
|
enableAutoFollow: boolean;
|
|
69
|
+
|
|
70
|
+
// The list of remote users and which file they have open,
|
|
71
|
+
// for presence display in the sidebar.
|
|
72
|
+
sidebarPresenceIndicators: Array<PresenceIndicator>;
|
|
66
73
|
};
|
|
67
74
|
|
|
68
75
|
// The shape of the actions that can be dispatched to the reducer.
|
|
@@ -139,9 +146,19 @@ export type VZAction =
|
|
|
139
146
|
// * Sets the username.
|
|
140
147
|
| { type: 'set_username'; username: Username }
|
|
141
148
|
|
|
142
|
-
// `toggle_auto_follow
|
|
149
|
+
// `toggle_auto_follow`
|
|
143
150
|
// * Toggles the auto-follow feature.
|
|
144
|
-
| { type: 'toggle_auto_follow' }
|
|
151
|
+
| { type: 'toggle_auto_follow' }
|
|
152
|
+
|
|
153
|
+
// `update_presence_indicator
|
|
154
|
+
// * Updates a single presence indicator
|
|
155
|
+
| {
|
|
156
|
+
type: 'update_presence_indicator';
|
|
157
|
+
presenceIndicator: PresenceIndicator;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// `split_current_pane`
|
|
161
|
+
| { type: 'split_current_pane' };
|
|
145
162
|
|
|
146
163
|
const reducers = [
|
|
147
164
|
setActiveFileIdReducer,
|
|
@@ -160,6 +177,8 @@ const reducers = [
|
|
|
160
177
|
editorNoLongerWantsFocusReducer,
|
|
161
178
|
setUsernameReducer,
|
|
162
179
|
toggleAutoFollowReducer,
|
|
180
|
+
updatePresenceIndicatorReducer,
|
|
181
|
+
splitCurrentPaneReducer,
|
|
163
182
|
];
|
|
164
183
|
|
|
165
184
|
export const vzReducer = (
|