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.
- package/dist/assets/index-284b7339.js +111 -0
- package/dist/assets/index-e160f758.css +1 -0
- package/dist/index.html +2 -6
- package/package.json +5 -5
- package/src/client/App.tsx +45 -225
- package/src/client/CodeEditor/index.tsx +36 -6
- package/src/client/CodeEditor/{json1PresenceBroadcast.js → json1PresenceBroadcast.ts} +5 -3
- package/src/client/CodeEditor/{json1PresenceDisplay.js → json1PresenceDisplay.ts} +9 -3
- package/src/client/PresenceNotifications/index.tsx +0 -2
- package/src/client/TabList/Tab.tsx +56 -0
- package/src/client/TabList/index.tsx +11 -62
- package/src/client/TabList/style.scss +21 -9
- package/src/client/{Settings.tsx → VZSettings.tsx} +1 -1
- package/src/client/{Sidebar → VZSidebar}/FileListing.tsx +3 -0
- package/src/client/{Sidebar → VZSidebar}/Item.tsx +7 -4
- package/src/client/{Sidebar → VZSidebar}/Listing.tsx +3 -0
- package/src/client/{Sidebar → VZSidebar}/index.tsx +5 -6
- package/src/client/{Sidebar → VZSidebar}/styles.scss +17 -12
- package/src/client/bootstrap.ts +3 -0
- package/src/client/style.scss +5 -2
- package/src/client/useActions.ts +74 -0
- package/src/client/useFileCRUD.ts +101 -0
- package/src/client/useOpenDirectories.ts +25 -7
- package/src/client/useSubmitOperation.ts +25 -0
- package/src/client/{reducer.ts → vzReducer.ts} +42 -43
- package/src/index.ts +34 -0
- package/src/server/index.js +13 -2
- package/dist/assets/index-8628ebab.js +0 -111
- package/dist/assets/index-cf19de3b.css +0 -1
- /package/src/client/{Sidebar → VZSidebar}/ConfirmationBox.tsx +0 -0
- /package/src/client/{Sidebar → VZSidebar}/DirectoryArrowSVG.jsx +0 -0
- /package/src/client/{Sidebar → VZSidebar}/DirectoryListing.tsx +0 -0
package/src/client/App.tsx
CHANGED
|
@@ -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 {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
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 =
|
|
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(
|
|
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
|
-
//
|
|
167
|
-
const tabList =
|
|
168
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
//
|
|
252
|
-
const {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
204
|
+
<VZSettings
|
|
385
205
|
show={isSettingsOpen}
|
|
386
|
-
onClose={
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
12
|
+
closeTabs,
|
|
65
13
|
}: {
|
|
66
14
|
files: Files;
|
|
67
15
|
tabList: FileId[];
|
|
68
16
|
activeFileId: FileId;
|
|
69
17
|
setActiveFileId: (fileId: FileId) => void;
|
|
70
|
-
|
|
18
|
+
closeTabs: (fileIds: FileId[]) => void;
|
|
71
19
|
}) => {
|
|
72
|
-
//
|
|
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
|
-
|
|
25
|
+
closeTabs([activeFileId]);
|
|
78
26
|
}
|
|
79
27
|
}
|
|
80
28
|
},
|
|
81
|
-
[
|
|
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
|
-
|
|
52
|
+
closeTabs={closeTabs}
|
|
104
53
|
fileName={files[fileId].name}
|
|
105
54
|
/>
|
|
106
55
|
))}
|