vzcode 1.12.0 → 1.14.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-DGNI0PAo.js +157 -0
- package/dist/assets/{index-BKRpkJER.css → index-Wxh-Jaj6.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +11 -11
- package/src/client/AIAssist/AIAssistWidget/index.tsx +14 -7
- package/src/client/CodeEditor/getOrCreateEditor.ts +11 -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 +23 -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/useEditorCache.ts +12 -2
- package/src/client/vzReducer/createInitialState.ts +1 -0
- package/src/client/vzReducer/index.ts +21 -2
- package/src/client/vzReducer/splitCurrentPaneReducer.ts +66 -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-DGNI0PAo.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.14.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
|
-
"react-router-dom": "^6.
|
|
149
|
-
"sharedb": "^5.0.
|
|
150
|
-
"sharedb-client-browser": "^5.0.
|
|
148
|
+
"react-router-dom": "^6.26.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.5"
|
|
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.2",
|
|
168
|
+
"@rollup/rollup-win32-x64-msvc": "^4.19.2"
|
|
169
169
|
}
|
|
170
170
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { SparklesSVG } from '../../Icons';
|
|
9
9
|
import { startAIAssist } from '../startAIAssist';
|
|
10
10
|
import { Spinner } from '../Spinner';
|
|
11
|
+
import { editorCacheKey } from '../../useEditorCache';
|
|
11
12
|
import './style.scss';
|
|
12
13
|
|
|
13
14
|
const enableStopGeneration = false;
|
|
@@ -23,8 +24,15 @@ export const AIAssistWidget = ({
|
|
|
23
24
|
aiAssistTooltipText?: string;
|
|
24
25
|
aiAssistClickOverride?: () => void;
|
|
25
26
|
}) => {
|
|
26
|
-
const {
|
|
27
|
-
|
|
27
|
+
const {
|
|
28
|
+
shareDBDoc,
|
|
29
|
+
activeFileId,
|
|
30
|
+
activePaneId,
|
|
31
|
+
tabList,
|
|
32
|
+
editorCache,
|
|
33
|
+
runPrettierRef,
|
|
34
|
+
runCodeRef,
|
|
35
|
+
} = useContext(VZCodeContext);
|
|
28
36
|
|
|
29
37
|
// The stream ID of the most recent request.
|
|
30
38
|
// * If `null`, no request has been made yet.
|
|
@@ -33,9 +41,6 @@ export const AIAssistWidget = ({
|
|
|
33
41
|
const [aiStreamId, setAiStreamId] =
|
|
34
42
|
useState<RequestId | null>(null);
|
|
35
43
|
|
|
36
|
-
const { runPrettierRef, runCodeRef } =
|
|
37
|
-
useContext(VZCodeContext);
|
|
38
|
-
|
|
39
44
|
const handleClick = useCallback(async () => {
|
|
40
45
|
const isCurrentlyGenerationg = aiStreamId !== null;
|
|
41
46
|
if (isCurrentlyGenerationg) {
|
|
@@ -46,7 +51,9 @@ export const AIAssistWidget = ({
|
|
|
46
51
|
|
|
47
52
|
// Wait for generation to finish.
|
|
48
53
|
await startAIAssist({
|
|
49
|
-
view: editorCache.get(
|
|
54
|
+
view: editorCache.get(
|
|
55
|
+
editorCacheKey(activeFileId, activePaneId),
|
|
56
|
+
).editor,
|
|
50
57
|
shareDBDoc,
|
|
51
58
|
fileId: activeFileId,
|
|
52
59
|
tabList,
|
|
@@ -89,7 +96,7 @@ export const AIAssistWidget = ({
|
|
|
89
96
|
: currentAIStreamId,
|
|
90
97
|
);
|
|
91
98
|
}
|
|
92
|
-
}, [activeFileId, aiStreamId]);
|
|
99
|
+
}, [activeFileId, activePaneId, aiStreamId]);
|
|
93
100
|
|
|
94
101
|
const showWidget = enableStopGeneration
|
|
95
102
|
? true
|
|
@@ -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,
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
} from './InteractiveWidgets';
|
|
33
34
|
import {
|
|
34
35
|
EditorCache,
|
|
36
|
+
editorCacheKey,
|
|
35
37
|
EditorCacheValue,
|
|
36
38
|
} from '../useEditorCache';
|
|
37
39
|
import { ThemeLabel, themeOptionsByLabel } from '../themes';
|
|
@@ -102,6 +104,7 @@ const getAtPath = (obj, path) => {
|
|
|
102
104
|
// Gets or creates an `editorCache` entry for the given file id.
|
|
103
105
|
// Looks in `editorCache` first, and if not found, creates a new editor.
|
|
104
106
|
export const getOrCreateEditor = ({
|
|
107
|
+
paneId = 'root',
|
|
105
108
|
fileId,
|
|
106
109
|
shareDBDoc,
|
|
107
110
|
content,
|
|
@@ -118,6 +121,8 @@ export const getOrCreateEditor = ({
|
|
|
118
121
|
enableAutoFollowRef,
|
|
119
122
|
openTab,
|
|
120
123
|
}: {
|
|
124
|
+
// TODO pass this in from the outside
|
|
125
|
+
paneId?: PaneId;
|
|
121
126
|
fileId: FileId;
|
|
122
127
|
|
|
123
128
|
// The ShareDB document that contains the file.
|
|
@@ -153,8 +158,11 @@ export const getOrCreateEditor = ({
|
|
|
153
158
|
openTab: (tabState: TabState) => void;
|
|
154
159
|
}): EditorCacheValue => {
|
|
155
160
|
// Cache hit
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
|
|
162
|
+
const cacheKey = editorCacheKey(fileId, paneId);
|
|
163
|
+
|
|
164
|
+
if (editorCache.has(cacheKey)) {
|
|
165
|
+
return editorCache.get(cacheKey);
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
// Cache miss
|
|
@@ -385,7 +393,7 @@ export const getOrCreateEditor = ({
|
|
|
385
393
|
const editorCacheValue = { editor, themeCompartment };
|
|
386
394
|
|
|
387
395
|
// Populate the cache.
|
|
388
|
-
editorCache.set(
|
|
396
|
+
editorCache.set(cacheKey, editorCacheValue);
|
|
389
397
|
|
|
390
398
|
return editorCacheValue;
|
|
391
399
|
};
|
|
@@ -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,9 @@ import {
|
|
|
15
15
|
SearchResults,
|
|
16
16
|
SearchFileVisibility,
|
|
17
17
|
TabState,
|
|
18
|
+
PresenceIndicator,
|
|
19
|
+
Pane,
|
|
20
|
+
PaneId,
|
|
18
21
|
} from '../types';
|
|
19
22
|
import { usePrettier } from './usePrettier';
|
|
20
23
|
import { useTypeScript } from './useTypeScript';
|
|
@@ -74,6 +77,8 @@ export type VZCodeContextValue = {
|
|
|
74
77
|
setActiveFileLeft: () => void;
|
|
75
78
|
setActiveFileRight: () => void;
|
|
76
79
|
|
|
80
|
+
activePaneId: PaneId;
|
|
81
|
+
|
|
77
82
|
tabList: Array<TabState>;
|
|
78
83
|
openTab: (tabState: TabState) => void;
|
|
79
84
|
closeTabs: (fileIds: string[]) => void;
|
|
@@ -146,6 +151,13 @@ export type VZCodeContextValue = {
|
|
|
146
151
|
|
|
147
152
|
enableAutoFollow: boolean;
|
|
148
153
|
toggleAutoFollow: () => void;
|
|
154
|
+
|
|
155
|
+
updatePresenceIndicator: (
|
|
156
|
+
presenceIndicator: PresenceIndicator,
|
|
157
|
+
) => void;
|
|
158
|
+
|
|
159
|
+
sidebarPresenceIndicators: Array<PresenceIndicator>;
|
|
160
|
+
splitCurrentPane: () => void;
|
|
149
161
|
};
|
|
150
162
|
|
|
151
163
|
export const VZCodeProvider = ({
|
|
@@ -223,8 +235,11 @@ export const VZCodeProvider = ({
|
|
|
223
235
|
);
|
|
224
236
|
|
|
225
237
|
// Unpack state.
|
|
238
|
+
// print the state object to see what it contains
|
|
239
|
+
// console.log('state: ', state);
|
|
226
240
|
const {
|
|
227
241
|
pane,
|
|
242
|
+
activePaneId,
|
|
228
243
|
theme,
|
|
229
244
|
search,
|
|
230
245
|
isSearchOpen,
|
|
@@ -233,6 +248,7 @@ export const VZCodeProvider = ({
|
|
|
233
248
|
editorWantsFocus,
|
|
234
249
|
username,
|
|
235
250
|
enableAutoFollow,
|
|
251
|
+
sidebarPresenceIndicators,
|
|
236
252
|
} = state;
|
|
237
253
|
|
|
238
254
|
// TODO support splitPane type
|
|
@@ -262,6 +278,8 @@ export const VZCodeProvider = ({
|
|
|
262
278
|
editorNoLongerWantsFocus,
|
|
263
279
|
setUsername,
|
|
264
280
|
toggleAutoFollow,
|
|
281
|
+
updatePresenceIndicator,
|
|
282
|
+
splitCurrentPane,
|
|
265
283
|
} = useActions(dispatch);
|
|
266
284
|
|
|
267
285
|
// Sync tab state to the URL.
|
|
@@ -382,6 +400,8 @@ export const VZCodeProvider = ({
|
|
|
382
400
|
setActiveFileLeft,
|
|
383
401
|
setActiveFileRight,
|
|
384
402
|
|
|
403
|
+
activePaneId,
|
|
404
|
+
|
|
385
405
|
tabList,
|
|
386
406
|
openTab,
|
|
387
407
|
closeTabs,
|
|
@@ -439,6 +459,9 @@ export const VZCodeProvider = ({
|
|
|
439
459
|
|
|
440
460
|
enableAutoFollow,
|
|
441
461
|
toggleAutoFollow,
|
|
462
|
+
updatePresenceIndicator,
|
|
463
|
+
sidebarPresenceIndicators,
|
|
464
|
+
splitCurrentPane,
|
|
442
465
|
};
|
|
443
466
|
|
|
444
467
|
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
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRef } from 'react';
|
|
2
|
-
import { FileId } from '../types';
|
|
2
|
+
import { FileId, PaneId } from '../types';
|
|
3
3
|
import { EditorView } from 'codemirror';
|
|
4
4
|
|
|
5
5
|
export type EditorCacheValue = {
|
|
@@ -8,7 +8,17 @@ export type EditorCacheValue = {
|
|
|
8
8
|
scrollPosition?: number;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export type
|
|
11
|
+
export type EditorCacheKey = string;
|
|
12
|
+
|
|
13
|
+
export const editorCacheKey = (
|
|
14
|
+
fileId: FileId,
|
|
15
|
+
paneId: PaneId,
|
|
16
|
+
): EditorCacheKey => fileId + '|' + paneId;
|
|
17
|
+
|
|
18
|
+
export type EditorCache = Map<
|
|
19
|
+
EditorCacheKey,
|
|
20
|
+
EditorCacheValue
|
|
21
|
+
>;
|
|
12
22
|
|
|
13
23
|
export const useEditorCache = () => {
|
|
14
24
|
// Singleton cache of CodeMirror instances
|