vzcode 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/assets/{index-9c8bbae0.js → index-284b7339.js} +50 -50
  2. package/dist/assets/index-e160f758.css +1 -0
  3. package/dist/index.html +2 -6
  4. package/package.json +7 -5
  5. package/src/client/App.tsx +47 -213
  6. package/src/client/CodeEditor/getOrCreateEditor.ts +3 -0
  7. package/src/client/CodeEditor/index.tsx +36 -6
  8. package/src/client/CodeEditor/{json1PresenceBroadcast.js → json1PresenceBroadcast.ts} +5 -3
  9. package/src/client/CodeEditor/{json1PresenceDisplay.js → json1PresenceDisplay.ts} +9 -3
  10. package/src/client/CodeEditor/widgets.ts +112 -10
  11. package/src/client/PresenceNotifications/index.tsx +0 -2
  12. package/src/client/TabList/Tab.tsx +56 -0
  13. package/src/client/TabList/index.tsx +11 -62
  14. package/src/client/TabList/style.scss +21 -9
  15. package/src/client/{Settings.tsx → VZSettings.tsx} +1 -1
  16. package/src/client/{Sidebar → VZSidebar}/FileListing.tsx +3 -0
  17. package/src/client/{Sidebar → VZSidebar}/Item.tsx +10 -7
  18. package/src/client/{Sidebar → VZSidebar}/Listing.tsx +3 -0
  19. package/src/client/{Sidebar → VZSidebar}/index.tsx +5 -6
  20. package/src/client/{Sidebar → VZSidebar}/styles.scss +17 -12
  21. package/src/client/bootstrap.ts +3 -0
  22. package/src/client/style.scss +5 -2
  23. package/src/client/themes/index.ts +7 -0
  24. package/src/client/{themes.ts → themes/themes.ts} +5 -5
  25. package/src/client/{useDynamicTheme.ts → themes/useDynamicTheme.ts} +1 -1
  26. package/src/client/themes/vizhubTheme/colors.ts +80 -0
  27. package/src/client/themes/vizhubTheme/index.ts +166 -0
  28. package/src/client/useActions.ts +74 -0
  29. package/src/client/useFileCRUD.ts +101 -0
  30. package/src/client/useOpenDirectories.ts +25 -7
  31. package/src/client/useSubmitOperation.ts +25 -0
  32. package/src/client/vzReducer.ts +118 -0
  33. package/src/index.ts +34 -0
  34. package/src/server/index.js +13 -2
  35. package/dist/assets/index-cf19de3b.css +0 -1
  36. package/src/client/reducer.ts +0 -122
  37. package/src/client/useTabsState.ts +0 -95
  38. package/src/client/vizhubTheme.js +0 -1
  39. /package/src/client/{Sidebar → VZSidebar}/ConfirmationBox.tsx +0 -0
  40. /package/src/client/{Sidebar → VZSidebar}/DirectoryArrowSVG.jsx +0 -0
  41. /package/src/client/{Sidebar → VZSidebar}/DirectoryListing.tsx +0 -0
@@ -9,30 +9,25 @@ import ShareDBClient from 'sharedb-client-browser/dist/sharedb-client-umd.cjs';
9
9
  import { json1Presence } from '../ot';
10
10
  import { randomId } from '../randomId';
11
11
  import { CodeEditor } from './CodeEditor';
12
- import { diff } from './diff';
13
- import { Settings } from './Settings';
14
- import { Sidebar } from './Sidebar';
15
- import {
16
- FileId,
17
- Files,
18
- ShareDBDoc,
19
- VZCodeContent,
20
- } from '../types';
12
+ import { VZSettings } from './VZSettings';
13
+ import { VZSidebar } from './VZSidebar';
14
+ import { Files, ShareDBDoc, VZCodeContent } from '../types';
21
15
  import { TabList } from './TabList';
22
16
  import { useOpenDirectories } from './useOpenDirectories';
23
- import { useTabsState } from './useTabsState';
24
- import { ThemeLabel, defaultTheme } from './themes';
17
+ import { defaultTheme, useDynamicTheme } from './themes';
25
18
  import { useEditorCache } from './useEditorCache';
26
- import { useDynamicTheme } from './useDynamicTheme';
27
19
  import { usePrettier } from './usePrettier';
28
20
  // @ts-ignore
29
21
  import PrettierWorker from './usePrettier/worker?worker';
30
22
  import { SplitPaneResizeProvider } from './SplitPaneResizeContext';
31
23
  import { Resizer } from './Resizer';
32
24
  import { PresenceNotifications } from './PresenceNotifications';
33
- import { reducer } from './reducer';
34
- import './style.scss';
35
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';
36
31
 
37
32
  // Instantiate the Prettier worker.
38
33
  const prettierWorker = new PrettierWorker();
@@ -58,20 +53,9 @@ function App() {
58
53
  const [shareDBDoc, setShareDBDoc] =
59
54
  useState<ShareDBDoc<VZCodeContent> | null>(null);
60
55
 
61
- // A helper function to submit operations to the ShareDB document
62
-
63
56
  const submitOperation: (
64
57
  next: (content: VZCodeContent) => VZCodeContent,
65
- ) => void = useCallback(
66
- (next) => {
67
- const content: VZCodeContent = shareDBDoc.data;
68
- const op = diff(content, next(content));
69
- if (op && shareDBDoc) {
70
- shareDBDoc.submitOp(op);
71
- }
72
- },
73
- [shareDBDoc],
74
- );
58
+ ) => void = useSubmitOperation(shareDBDoc);
75
59
 
76
60
  // Auto-run Pretter after local changes.
77
61
  const { prettierError } = usePrettier(
@@ -80,8 +64,6 @@ function App() {
80
64
  prettierWorker,
81
65
  );
82
66
 
83
- console.log('prettierError', prettierError);
84
-
85
67
  // Local ShareDB presence, for broadcasting our cursor position
86
68
  // so other clients can see it.
87
69
  // See https://share.github.io/sharedb/api/local-presence
@@ -123,6 +105,12 @@ function App() {
123
105
  // This decouples rendering logic from ShareDB.
124
106
  // This callback gets called on each change.
125
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.
126
114
  setContent(shareDBDoc.data);
127
115
  });
128
116
 
@@ -153,76 +141,32 @@ function App() {
153
141
  };
154
142
  }, []);
155
143
 
156
- // The id of the currently open file tab.
157
- // TODO make this a URL param
158
- // Migrated to useReducer
159
- //const [activeFileId, setActiveFileId] =
160
- //useState<FileId | null>(null);
161
-
162
- // The ordered list of tabs.
163
- // TODO make this a URL param
164
144
  // https://react.dev/reference/react/useReducer
165
- // const [tabList, setTabList] = useState<Array<FileId>>([]);
166
- const [state, dispatch] = useReducer(reducer, {
167
- submitOperation,
145
+ const [state, dispatch] = useReducer(vzReducer, {
168
146
  tabList: [],
169
- // activeFileId: null,
170
- // theme: defaultTheme,
171
- // isSettingsOpen:false
147
+ activeFileId: null,
148
+ theme: defaultTheme,
149
+ isSettingsOpen: false,
172
150
  });
173
151
 
174
- //Reducer states
175
- const tabList = state.tabList;
176
- const activeFileId = state.activeFileId;
177
- // TODO phase this out as we complete the refactoring
178
- // It's here now for backwards compatibility
179
- const setTabList = (tabList) => {
180
- dispatch({
181
- type: 'set_tab_list',
182
- tabList,
183
- });
184
- };
185
-
186
- const setActiveFileId = (activeFileId) => {
187
- dispatch({
188
- type: 'set_active_fileId',
189
- activeFileId,
190
- });
191
- };
192
- const openTab = (fileId: FileId) => {
193
- dispatch({
194
- type: 'open_tab',
195
- fileId,
196
- });
197
- };
198
-
199
- const closeTab = (fileIdToRemove: FileId) => {
200
- dispatch({
201
- type: 'close_tab',
202
- fileIdToRemove,
203
- });
204
- };
205
-
206
- const multiCloseTab = (idsToDelete: Array<FileId>) => {
207
- dispatch({
208
- type: 'multi_close_tab',
209
- idsToDelete,
210
- });
211
- };
152
+ // Unpack state.
153
+ const { tabList, activeFileId, theme, isSettingsOpen } =
154
+ state;
212
155
 
213
- // Logic for opening and closing tabs. Migrated to useReducer
214
- /*
215
- const {} = useTabsState(
216
- activeFileId,
156
+ // Functions for dispatching actions to the reducer.
157
+ const {
217
158
  setActiveFileId,
218
- tabList,
219
- setTabList,
220
- ); */
159
+ openTab,
160
+ closeTabs,
161
+ setTheme,
162
+ setIsSettingsOpen,
163
+ closeSettings,
164
+ } = useActions(dispatch);
221
165
 
222
- // The current theme.
223
- // TODO persist this in local storage
224
- const [theme, setTheme] =
225
- useState<ThemeLabel>(defaultTheme);
166
+ // The set of open directories.
167
+ // TODO move this into reducer/useActions
168
+ const { isDirectoryOpen, toggleDirectory } =
169
+ useOpenDirectories();
226
170
 
227
171
  // Cache of CodeMirror editors by file id.
228
172
  const editorCache = useEditorCache();
@@ -230,123 +174,12 @@ function App() {
230
174
  // Handle dynamic theme changes.
231
175
  useDynamicTheme(editorCache, theme);
232
176
 
233
- // True to show the settings modal.
234
- const [isSettingsOpen, setIsSettingsOpen] =
235
- useState(false);
236
-
237
- // The set of open directories.
238
- const { isDirectoryOpen, toggleDirectory } =
239
- useOpenDirectories();
240
-
241
- // TODO move this logic to a hook called `useFileCRUD`
242
- // Include
243
- // - `createFile`
244
- // - `handleRenameFileClick`
245
- // - `deleteFile`
246
- // - `handleDeleteFileClick`
247
- const createFile = useCallback(() => {
248
- const name = prompt('Enter new file name');
249
- if (name) {
250
- submitOperation((document) => ({
251
- ...document,
252
- files: {
253
- ...document.files,
254
- [randomId()]: { name, text: '' },
255
- },
256
- }));
257
- }
258
- }, [submitOperation]);
259
-
260
- // Called when a file in the sidebar is renamed.
261
- const handleRenameFileClick = useCallback(
262
- (fileId: FileId, newName: string) => {
263
- submitOperation((document) => ({
264
- ...document,
265
- files: {
266
- ...document.files,
267
- [fileId]: {
268
- ...document.files[fileId],
269
- name: newName,
270
- },
271
- },
272
- }));
273
- },
274
- [submitOperation],
275
- );
276
-
277
- const deleteFile = useCallback(
278
- (fileId: FileId) => {
279
- submitOperation((document) => {
280
- const updatedFiles = { ...document.files };
281
- delete updatedFiles[fileId];
282
- return { ...document, files: updatedFiles };
283
- });
284
- closeTab(fileId);
285
- },
286
- [submitOperation, closeTab],
287
- );
288
-
289
- const deleteDirectory = useCallback(
290
- (path: FileId) => {
291
- let tabsToDelete: Array<FileId> = [];
292
- submitOperation((document) => {
293
- const updatedFiles = { ...document.files };
294
- for (const key in updatedFiles) {
295
- if (updatedFiles[key].name.includes(path)) {
296
- tabsToDelete.push(key);
297
- delete updatedFiles[key];
298
- }
299
- }
300
- return { ...document, files: updatedFiles };
301
- });
302
- multiCloseTab(tabsToDelete);
303
- },
304
- [submitOperation, multiCloseTab],
305
- );
306
-
307
- const handleDeleteClick = useCallback(
308
- (key: string) => {
309
- //Regex to identify if the key is a file path or a file id.
310
- if (/^[0-9]*$/.test(key)) {
311
- if (key.length == 8) {
312
- deleteFile(key);
313
- } else {
314
- deleteDirectory(key);
315
- }
316
- } else {
317
- deleteDirectory(key);
318
- }
319
- },
320
- [deleteFile, deleteDirectory],
321
- );
322
-
323
- const handleSettingsClose = useCallback(() => {
324
- setIsSettingsOpen(false);
325
- }, []);
326
-
327
- // Set `doc.data.isInteracting` to `true` when the user is interacting
328
- // via interactive code widgets (e.g. Alt+drag), and `false` when they are not.
329
- const interactTimeoutRef = useRef(null);
330
- const handleInteract = useCallback(() => {
331
- // Set `isInteracting: true` if not already set.
332
- if (!interactTimeoutRef.current) {
333
- submitOperation((document) => ({
334
- ...document,
335
- isInteracting: true,
336
- }));
337
- } else {
338
- clearTimeout(interactTimeoutRef.current);
339
- }
340
-
341
- // Set `isInteracting: false` after a delay.
342
- interactTimeoutRef.current = setTimeout(() => {
343
- interactTimeoutRef.current = null;
344
- submitOperation((document) => ({
345
- ...document,
346
- isInteracting: false,
347
- }));
348
- }, 800);
349
- }, [submitOperation]);
177
+ // Handle file CRUD operations.
178
+ const {
179
+ createFile,
180
+ handleRenameFileClick,
181
+ handleDeleteClick,
182
+ } = useFileCRUD({ submitOperation, closeTabs });
350
183
 
351
184
  // Isolate the files object from the document.
352
185
  const files: Files | null = content
@@ -357,7 +190,7 @@ function App() {
357
190
  <SplitPaneResizeProvider>
358
191
  <div className="app">
359
192
  <div className="left">
360
- <Sidebar
193
+ <VZSidebar
361
194
  createFile={createFile}
362
195
  files={files}
363
196
  handleRenameFileClick={handleRenameFileClick}
@@ -366,10 +199,11 @@ function App() {
366
199
  setIsSettingsOpen={setIsSettingsOpen}
367
200
  isDirectoryOpen={isDirectoryOpen}
368
201
  toggleDirectory={toggleDirectory}
202
+ activeFileId={activeFileId}
369
203
  />
370
- <Settings
204
+ <VZSettings
371
205
  show={isSettingsOpen}
372
- onClose={handleSettingsClose}
206
+ onClose={closeSettings}
373
207
  theme={theme}
374
208
  setTheme={setTheme}
375
209
  />
@@ -380,16 +214,16 @@ function App() {
380
214
  tabList={tabList}
381
215
  activeFileId={activeFileId}
382
216
  setActiveFileId={setActiveFileId}
383
- closeTab={closeTab}
217
+ closeTabs={closeTabs}
384
218
  />
385
219
  {content && activeFileId ? (
386
220
  <CodeEditor
387
221
  shareDBDoc={shareDBDoc}
222
+ submitOperation={submitOperation}
388
223
  localPresence={localPresence}
389
224
  docPresence={docPresence}
390
225
  activeFileId={activeFileId}
391
226
  theme={theme}
392
- onInteract={handleInteract}
393
227
  editorCache={editorCache}
394
228
  />
395
229
  ) : null}
@@ -19,6 +19,7 @@ import { json1PresenceDisplay } from './json1PresenceDisplay';
19
19
  import {
20
20
  colorsInTextPlugin,
21
21
  highlightWidgets,
22
+ rotationIndicator,
22
23
  widgets,
23
24
  } from './widgets';
24
25
  import {
@@ -192,6 +193,8 @@ export const getOrCreateEditor = ({
192
193
 
193
194
  extensions.push(highlightWidgets);
194
195
 
196
+ extensions.push(rotationIndicator);
197
+
195
198
  const editor = new EditorView({
196
199
  state: EditorState.create({
197
200
  doc: text,
@@ -1,4 +1,8 @@
1
- import { useRef, useLayoutEffect, useContext } from 'react';
1
+ import {
2
+ useRef,
3
+ useLayoutEffect,
4
+ useCallback,
5
+ } from 'react';
2
6
  import {
3
7
  FileId,
4
8
  ShareDBDoc,
@@ -11,20 +15,22 @@ import {
11
15
  } from '../useEditorCache';
12
16
  import { getOrCreateEditor } from './getOrCreateEditor';
13
17
  import './style.scss';
14
- // import { usePrettier } from '../usePrettier';
15
18
 
16
19
  export const CodeEditor = ({
17
20
  activeFileId,
18
21
  shareDBDoc,
22
+ submitOperation,
19
23
  localPresence,
20
24
  docPresence,
21
25
  theme = defaultTheme,
22
26
  filesPath = ['files'],
23
- onInteract,
24
27
  editorCache,
25
28
  }: {
26
29
  activeFileId: FileId;
27
30
  shareDBDoc: ShareDBDoc<VZCodeContent> | null;
31
+ submitOperation: (
32
+ next: (content: VZCodeContent) => VZCodeContent,
33
+ ) => void;
28
34
  localPresence?: any;
29
35
  docPresence?: any;
30
36
  theme?: ThemeLabel;
@@ -32,14 +38,38 @@ export const CodeEditor = ({
32
38
  // The path of the files object in the ShareDB document.
33
39
  // Defaults to `files` if not provided.
34
40
  filesPath?: string[];
35
- onInteract?: () => void;
36
41
  editorCache: EditorCache;
37
42
  }) => {
38
43
  const ref = useRef<HTMLDivElement>(null);
44
+
45
+ // Set `doc.data.isInteracting` to `true` when the user is interacting
46
+ // via interactive code widgets (e.g. Alt+drag), and `false` when they are not.
47
+ const interactTimeoutRef = useRef(null);
48
+
49
+ const onInteract = useCallback(() => {
50
+ // Set `isInteracting: true` if not already set.
51
+ if (!interactTimeoutRef.current) {
52
+ submitOperation((document) => ({
53
+ ...document,
54
+ isInteracting: true,
55
+ }));
56
+ } else {
57
+ clearTimeout(interactTimeoutRef.current);
58
+ }
59
+
60
+ // Set `isInteracting: false` after a delay.
61
+ interactTimeoutRef.current = setTimeout(() => {
62
+ interactTimeoutRef.current = null;
63
+ submitOperation((document) => ({
64
+ ...document,
65
+ isInteracting: false,
66
+ }));
67
+ }, 800);
68
+ }, [submitOperation]);
69
+
39
70
  // Every time the active file switches from one file to another,
40
71
  // the editor corresponding to the old file is removed from the DOM,
41
72
  // and the editor corresponding to the new file is added to the DOM.
42
-
43
73
  useLayoutEffect(() => {
44
74
  // Guard against cases where page is still loading.
45
75
  if (!ref.current) return;
@@ -67,7 +97,7 @@ export const CodeEditor = ({
67
97
  // This happens every time `activeFileId` changes.
68
98
  ref.current.removeChild(editorCacheValue.editor.dom);
69
99
  };
70
- }, [shareDBDoc, activeFileId]);
100
+ }, [shareDBDoc, activeFileId, onInteract]);
71
101
 
72
102
  return <div className="vz-code-editor" ref={ref}></div>;
73
103
  };
@@ -1,5 +1,5 @@
1
+ import { ViewUpdate } from '@codemirror/view';
1
2
  import { EditorView } from 'codemirror';
2
- import { username } from '../Settings.tsx';
3
3
 
4
4
  // Deals with broadcasting changes in cursor location and selection.
5
5
  export const json1PresenceBroadcast = ({
@@ -7,7 +7,7 @@ export const json1PresenceBroadcast = ({
7
7
  localPresence,
8
8
  }) =>
9
9
  // See https://discuss.codemirror.net/t/codemirror-6-proper-way-to-listen-for-changes/2395/10
10
- EditorView.updateListener.of((viewUpdate) => {
10
+ EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
11
11
  // If this update modified the cursor / selection,
12
12
  // we broadcast the selection update via ShareDB presence.
13
13
  if (viewUpdate.selectionSet) {
@@ -23,7 +23,9 @@ export const json1PresenceBroadcast = ({
23
23
  const presence = {
24
24
  start: [...path, from],
25
25
  end: [...path, to],
26
- username: username,
26
+
27
+ // TODO fill this in with the user's actual username.
28
+ username: 'anonymous',
27
29
  };
28
30
 
29
31
  // Broadcast presence to remote clients!
@@ -20,7 +20,12 @@ export const json1PresenceDisplay = ({
20
20
  }) => [
21
21
  ViewPlugin.fromClass(
22
22
  class {
23
- constructor(view) {
23
+ // The decorations to display.
24
+ // This is a RangeSet of Decoration objects.
25
+ // See https://codemirror.net/6/docs/ref/#view.Decoration
26
+ decorations: RangeSet<Decoration>;
27
+
28
+ constructor(view: EditorView) {
24
29
  // Initialize decorations to empty array so CodeMirror doesn't crash.
25
30
  this.decorations = RangeSet.of([]);
26
31
 
@@ -161,12 +166,13 @@ const pathMatches = (path, presence) => {
161
166
 
162
167
  // Displays a single remote presence cursor.
163
168
  class PresenceWidget extends WidgetType {
164
- constructor(id) {
169
+ id: string;
170
+ constructor(id: string) {
165
171
  super();
166
172
  this.id = id;
167
173
  }
168
174
 
169
- eq(other) {
175
+ eq(other: PresenceWidget) {
170
176
  return other.id === this.id;
171
177
  }
172
178
 
@@ -166,22 +166,27 @@ export const widgets = ({
166
166
  regexp: /rotate\(-?\d*\.?\d*\)/g,
167
167
  cursor: 'move',
168
168
  onDragStart(text, setText, e) {
169
- rotationOrigin = { x: e.screenX, y: e.screenY };
169
+ rotationOrigin = { x: e.clientX, y: e.clientY };
170
170
  },
171
171
 
172
172
  onDrag(text, setText, e) {
173
173
  if (rotationOrigin == null) return;
174
+ const rotationDegree = Math.round(
175
+ (Math.atan2(
176
+ rotationOrigin.y - e.clientY,
177
+ e.clientX - rotationOrigin.x,
178
+ ) *
179
+ 180) /
180
+ Math.PI,
181
+ );
174
182
  //Calculate the angle between the x axis and a line from where the user first clicks to the current location of the mouse.
175
- setText(
176
- `rotate(${Math.round(
177
- (Math.atan2(
178
- rotationOrigin.y - e.screenY,
179
- e.screenX - rotationOrigin.x,
180
- ) *
181
- 180) /
182
- Math.PI,
183
- )})`,
183
+ setText(`rotate(${rotationDegree})`);
184
+ const updateDragEvent = new CustomEvent(
185
+ 'updateRotateDrag',
186
+ { detail: rotationDegree },
184
187
  );
188
+
189
+ document.dispatchEvent(updateDragEvent);
185
190
  },
186
191
  onDragEnd(text, setText) {
187
192
  rotationOrigin = null;
@@ -383,3 +388,100 @@ export const highlightWidgets = ViewPlugin.fromClass(
383
388
  },
384
389
  },
385
390
  );
391
+
392
+ export const rotationIndicator = ViewPlugin.fromClass(
393
+ class {
394
+ view: EditorView;
395
+ textPosition?: number;
396
+ rotation: number;
397
+ constructor(view) {
398
+ this.view = view;
399
+ this.textPosition = null;
400
+ this.rotation = 0;
401
+
402
+ document.addEventListener(
403
+ 'updateRotateDrag',
404
+ (e: CustomEvent) => {
405
+ this.textPosition = this.view.posAtCoords(
406
+ rotationOrigin,
407
+ false,
408
+ );
409
+
410
+ this.rotation = e.detail;
411
+ },
412
+ );
413
+ }
414
+ },
415
+ {
416
+ decorations: (v) => {
417
+ if (rotationOrigin === null) {
418
+ return Decoration.none;
419
+ }
420
+ return Decoration.set([
421
+ {
422
+ from: v.textPosition,
423
+ to: v.textPosition,
424
+ value: Decoration.widget({
425
+ side: -1,
426
+ widget: new RotationCircle(v.rotation),
427
+ }),
428
+ },
429
+ ]);
430
+ },
431
+ },
432
+ );
433
+
434
+ class RotationCircle extends WidgetType {
435
+ angle: number;
436
+ constructor(angle: number) {
437
+ super();
438
+ this.angle = angle;
439
+ }
440
+
441
+ toDOM(view: EditorView): HTMLElement {
442
+ const parent = document.createElement('div');
443
+
444
+ parent.setAttribute('style', 'width:20px;height:20px');
445
+ parent.className = 'color-circle-parent';
446
+ const svg = document.createElementNS(
447
+ 'http://www.w3.org/2000/svg',
448
+ 'svg',
449
+ );
450
+ const colorCircle = document.createElementNS(
451
+ 'http://www.w3.org/2000/svg',
452
+ 'circle',
453
+ );
454
+ colorCircle.setAttributeNS(null, 'fill', '#808080');
455
+ colorCircle.setAttributeNS(null, 'r', '5');
456
+ colorCircle.setAttributeNS(null, 'cx', '5');
457
+ colorCircle.setAttributeNS(null, 'cy', '5');
458
+
459
+ const indicatorLine = document.createElementNS(
460
+ 'http://www.w3.org/2000/svg',
461
+ 'line',
462
+ );
463
+ indicatorLine.setAttributeNS(null, 'x1', '10');
464
+ indicatorLine.setAttributeNS(null, 'x2', '5');
465
+ indicatorLine.setAttributeNS(null, 'y1', '5');
466
+ indicatorLine.setAttributeNS(null, 'y2', '5');
467
+ indicatorLine.setAttributeNS(null, 'stroke', 'black');
468
+ indicatorLine.setAttributeNS(
469
+ null,
470
+ 'transform',
471
+ `rotate(${360 - this.angle},5,5)`,
472
+ );
473
+
474
+ svg.setAttributeNS(null, 'viewBox', '0 0 10 10');
475
+ svg.setAttributeNS(null, 'width', '20');
476
+ svg.setAttributeNS(null, 'height', '20');
477
+
478
+ svg.appendChild(colorCircle);
479
+ svg.appendChild(indicatorLine);
480
+
481
+ parent.appendChild(svg);
482
+ return parent;
483
+ }
484
+ ignoreEvent() {
485
+ return false;
486
+ }
487
+ }
@@ -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){