vzcode 0.25.0 → 0.27.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 (55) hide show
  1. package/dist/assets/index-5f9f376e.css +1 -0
  2. package/dist/assets/index-70b6d947.js +111 -0
  3. package/dist/index.html +2 -6
  4. package/package.json +7 -7
  5. package/src/client/App.tsx +110 -247
  6. package/src/client/CodeEditor/getOrCreateEditor.ts +6 -1
  7. package/src/client/CodeEditor/index.tsx +94 -16
  8. package/src/client/CodeEditor/{json1PresenceBroadcast.js → json1PresenceBroadcast.ts} +9 -3
  9. package/src/client/CodeEditor/{json1PresenceDisplay.js → json1PresenceDisplay.ts} +9 -48
  10. package/src/client/CodeEditor/style.scss +5 -1
  11. package/src/client/CodeErrorOverlay/index.tsx +27 -0
  12. package/src/client/CodeErrorOverlay/style.scss +30 -0
  13. package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
  14. package/src/client/PresenceNotifications/index.tsx +119 -106
  15. package/src/client/SplitPaneResizeContext.tsx +12 -7
  16. package/src/client/TabList/Tab.tsx +70 -0
  17. package/src/client/TabList/index.tsx +28 -69
  18. package/src/client/TabList/style.scss +26 -9
  19. package/src/client/{Settings.tsx → VZSettings.tsx} +34 -21
  20. package/src/client/{Sidebar/ConfirmationBox.tsx → VZSidebar/DeleteConfirmationModal.tsx} +17 -14
  21. package/src/client/{Sidebar → VZSidebar}/DirectoryListing.tsx +27 -26
  22. package/src/client/{Sidebar → VZSidebar}/FileListing.tsx +19 -19
  23. package/src/client/VZSidebar/Item.tsx +179 -0
  24. package/src/client/{Sidebar → VZSidebar}/Listing.tsx +20 -14
  25. package/src/client/{Sidebar → VZSidebar}/index.tsx +48 -30
  26. package/src/client/{Sidebar → VZSidebar}/styles.scss +17 -12
  27. package/src/client/bootstrap.ts +3 -0
  28. package/src/client/style.scss +8 -2
  29. package/src/client/useActions.ts +93 -0
  30. package/src/client/useEditorCache.ts +2 -1
  31. package/src/client/useFileCRUD.ts +103 -0
  32. package/src/client/useOpenDirectories.ts +25 -7
  33. package/src/client/useSubmitOperation.ts +25 -0
  34. package/src/client/usernameLocalStorage.ts +43 -0
  35. package/src/client/vzReducer/closeTabReducer.test.ts +177 -0
  36. package/src/client/vzReducer/closeTabsReducer.ts +68 -0
  37. package/src/client/vzReducer/createInitialState.ts +19 -0
  38. package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
  39. package/src/client/vzReducer/index.ts +107 -0
  40. package/src/client/vzReducer/openTabReducer.test.ts +110 -0
  41. package/src/client/vzReducer/openTabReducer.ts +57 -0
  42. package/src/client/vzReducer/setActiveFileIdReducer.ts +9 -0
  43. package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
  44. package/src/client/vzReducer/setThemeReducer.ts +9 -0
  45. package/src/client/vzReducer/setUsernameReducer.ts +9 -0
  46. package/src/index.ts +40 -0
  47. package/src/server/index.js +13 -2
  48. package/src/types.ts +12 -5
  49. package/dist/assets/index-8628ebab.js +0 -111
  50. package/dist/assets/index-cf19de3b.css +0 -1
  51. package/src/client/PrettierErrorOverlay/index.tsx +0 -9
  52. package/src/client/PrettierErrorOverlay/style.scss +0 -9
  53. package/src/client/Sidebar/Item.tsx +0 -130
  54. package/src/client/reducer.ts +0 -119
  55. /package/src/client/{Sidebar → VZSidebar}/DirectoryArrowSVG.jsx +0 -0
@@ -1,145 +1,158 @@
1
1
  import { useState, useEffect, useRef } from 'react';
2
+ import { PresenceId, Username } from '../../types';
2
3
  import './style.scss';
3
4
 
4
- type PresenceId = string;
5
-
6
- // A presence notification is a message that is displayed when a user joins or leaves a session.
7
- // It has the following properties:
8
- // - `user` is the username of the user who joined or left the session.
9
- // - `join` is a boolean value that indicates whether the user joined or left the session.
10
- // - `true` means user has joined the session.
11
- // - `false` means user has left the session.
12
5
  type PresenceNotification = {
13
- user: string;
6
+ user: Username;
14
7
  join: boolean;
15
8
  presenceId: PresenceId;
16
9
  };
17
10
 
11
+ export const extractTimestampFromId = (
12
+ id: string,
13
+ ): number => {
14
+ const [timestampPart] = id.split('-');
15
+ return parseInt(timestampPart, 36);
16
+ };
17
+
18
+ export const shouldShowNotification = (
19
+ remotePresenceId: PresenceId,
20
+ localPresenceId: PresenceId,
21
+ ): boolean => {
22
+ const remotePresenceTimestamp = extractTimestampFromId(
23
+ remotePresenceId,
24
+ );
25
+ const localPresenceTimestamp =
26
+ extractTimestampFromId(localPresenceId);
27
+ return remotePresenceTimestamp > localPresenceTimestamp;
28
+ };
29
+
30
+ export const handleJoin = (
31
+ presenceId: PresenceId,
32
+ username: Username,
33
+ alreadyJoined: React.MutableRefObject<
34
+ Map<PresenceId, Username>
35
+ >,
36
+ setPresenceNotifications: any,
37
+ ) => {
38
+ if (!alreadyJoined.current.has(presenceId)) {
39
+ alreadyJoined.current.set(presenceId, username);
40
+ setPresenceNotifications(
41
+ (prev: PresenceNotification[]) => [
42
+ ...prev,
43
+ { user: username, join: true, presenceId },
44
+ ],
45
+ );
46
+ } else {
47
+ alreadyJoined.current.set(presenceId, username); // Update in case username changed
48
+ }
49
+ };
50
+
51
+ export const handleLeave = (
52
+ presenceId: PresenceId,
53
+ alreadyJoined: React.MutableRefObject<
54
+ Map<PresenceId, Username>
55
+ >,
56
+ setPresenceNotifications: any,
57
+ ) => {
58
+ const user = alreadyJoined.current.get(presenceId);
59
+ if (user) {
60
+ alreadyJoined.current.delete(presenceId);
61
+ setPresenceNotifications(
62
+ (prev: PresenceNotification[]) => [
63
+ ...prev,
64
+ { user, join: false, presenceId },
65
+ ],
66
+ );
67
+ }
68
+ };
69
+
18
70
  export const PresenceNotifications = ({
19
71
  docPresence,
72
+ localPresence,
20
73
  }: {
21
74
  docPresence?: any;
75
+ localPresence?: any;
22
76
  }) => {
77
+ // `presenceNotifications`:
78
+ // - State variable containing a list of user presence notifications.
79
+ // - Each notification indicates if a user has joined or left a session.
23
80
  const [presenceNotifications, setPresenceNotifications] =
24
- useState<Array<PresenceNotification>>([]);
25
-
26
- // const [presenceIds, setPresenceIds] = useState<
27
- // Array<any>
28
- // >([]);
29
- const alreadyJoinedPresenceIds = useRef<Set<PresenceId>>(
30
- new Set(),
81
+ useState<PresenceNotification[]>([]);
82
+
83
+ // `alreadyJoined`:
84
+ // - Ref (persistent across re-renders) containing a Map.
85
+ // - Map's key: `PresenceId` (unique identifier for user's presence).
86
+ // - Map's value: `Username` of the associated user.
87
+ // - Tracks users who have joined the session and their possible username changes.
88
+ const alreadyJoined = useRef<Map<PresenceId, Username>>(
89
+ new Map(),
31
90
  );
32
91
 
33
92
  useEffect(() => {
34
- // console.log('useEffect triggered.');
35
93
  if (docPresence) {
36
- // See https://share.github.io/sharedb/presence
37
- docPresence.on('receive', (presenceId, update) => {
38
- // Reason for notifications appearing on every cursor movement:
39
- // code doesn't check if a notification about a user joining was previously sent
40
-
41
- // Attempted solutions:
42
- // - create an array called presenceIds that keeps track of the presences that join and leave
43
- // - if presenceId is in presenceIds: local user was already alerted that a remote user joined
44
- // - if presenceId is not in presenceIds: local user needs to be notified about a new user
45
- // - problems:
46
- // - the array is always empty inside useEffect (array works outside of useEffect)
47
- // - causes check to fail
48
- // - check docPresence.remotePresences, which keeps track of all the remote presences on the doc
49
- // - see https://share.github.io/sharedb/api/presence
50
- // - if presenceId is in docPresence.remotePresences: local user was already alerted that a remote user joined
51
- // - if presenceId is not in docPresence.remotePresences: local user needs to be notified about a new user
52
- // - problems:
53
- // - useEffects is called when docPresence changes (in some cases, it's when the remote presence changes)
54
- // - remotePresences already has the remote presence in the array, so check fails
55
- // - could add another property to presence (true: already notified, false: need to be notified)
56
- // - doesn't seem like an ideal solution
57
-
58
- // console.log('docPresence:', docPresence);
59
- // console.log('docPresence remotePresences:', docPresence.remotePresences);
60
- // console.log('check for presence:', docPresence.remotePresences[presenceId]);
61
-
62
- // TODO figure out how to get username from `presenceId`.
63
- const user = 'someone';
64
-
65
- // `true` means user has joined the session.
66
- // `false` means user has left the session.
94
+ const handleReceive = (
95
+ presenceId: PresenceId,
96
+ update: any,
97
+ ) => {
67
98
  const join = update !== null;
68
-
69
- console.log('join', join);
70
-
71
- // Figure out if we have ever seen this user before.
72
-
73
- // if (join && docPresence.remotePresences[presenceId] === undefined){
74
- if (join) {
75
- if (
76
- !alreadyJoinedPresenceIds.current.has(
99
+ if (
100
+ shouldShowNotification(
101
+ presenceId,
102
+ localPresence.presenceId,
103
+ )
104
+ ) {
105
+ if (join) {
106
+ const username =
107
+ docPresence.remotePresences[presenceId]
108
+ ?.username;
109
+ handleJoin(
77
110
  presenceId,
78
- )
79
- ) {
80
- alreadyJoinedPresenceIds.current.add(
111
+ username,
112
+ alreadyJoined,
113
+ setPresenceNotifications,
114
+ );
115
+ } else {
116
+ handleLeave(
81
117
  presenceId,
118
+ alreadyJoined,
119
+ setPresenceNotifications,
82
120
  );
83
- setPresenceNotifications((prev) => [
84
- ...prev,
85
- { user, join, presenceId },
86
- ]);
87
-
88
- // Remove it after 5 seconds.
89
- setTimeout(() => {
90
- setPresenceNotifications((prev) =>
91
- prev.filter(
92
- (notification) =>
93
- notification.presenceId !==
94
- presenceId &&
95
- notification.join !== join,
96
- ),
97
- );
98
- }, 5000);
99
121
  }
100
- } else if (!join) {
101
- setPresenceNotifications((prev) => [
102
- ...prev,
103
- { user, join, presenceId },
104
- ]);
105
122
 
106
- // Remove it after 5 seconds.
107
- // TODO clean up this duplication.
108
- setTimeout(() => {
123
+ const timeoutId = setTimeout(() => {
109
124
  setPresenceNotifications((prev) =>
110
- prev.filter(
111
- (notification) =>
112
- notification.presenceId !== presenceId &&
113
- notification.join !== join,
114
- ),
125
+ prev.slice(1),
115
126
  );
116
127
  }, 5000);
128
+
129
+ return () => {
130
+ clearTimeout(timeoutId);
131
+ };
117
132
  }
118
- });
119
- }
120
- }, [docPresence]);
133
+ };
121
134
 
122
- // console.log(
123
- // 'presenceNotifications',
124
- // presenceNotifications,
125
- // );
126
- // console.log(
127
- // 'presenceIds',
128
- // presenceIds,
129
- // );
135
+ docPresence.on('receive', handleReceive);
136
+ return () => {
137
+ docPresence.off('receive', handleReceive);
138
+ };
139
+ }
140
+ }, [docPresence, localPresence]);
130
141
 
131
142
  return presenceNotifications.length > 0 ? (
132
143
  <div className="vz-notification">
133
- {presenceNotifications.map((notification, i) => {
134
- const { user, join } = notification;
135
- return (
136
- <div key={i} className="vz-notification-item">
144
+ {presenceNotifications.map(
145
+ ({ user, join, presenceId }) => (
146
+ <div
147
+ key={presenceId}
148
+ className="vz-notification-item"
149
+ >
137
150
  {join
138
151
  ? `${user} has joined the session.`
139
152
  : `${user} has left the session.`}
140
153
  </div>
141
- );
142
- })}
154
+ ),
155
+ )}
143
156
  </div>
144
157
  ) : null;
145
158
  };
@@ -6,13 +6,6 @@ import {
6
6
  useEffect,
7
7
  } from 'react';
8
8
 
9
- export const SplitPaneResizeContext = createContext<{
10
- codeEditorWidth: number;
11
- moveSplitPane: (a: number) => void;
12
- isDragging: boolean;
13
- setIsDragging: (a: boolean) => void;
14
- }>(null);
15
-
16
9
  // The amount of time to wait idle before writing to localStorage.
17
10
  // This is to avoid writing to localStorage on every resize event.
18
11
  // MS = milliseconds
@@ -38,6 +31,18 @@ if (typeof window !== 'undefined') {
38
31
  // If we're not in the browser, use the default initial width.
39
32
  }
40
33
 
34
+ export const SplitPaneResizeContext = createContext<{
35
+ codeEditorWidth: number;
36
+ moveSplitPane: (a: number) => void;
37
+ isDragging: boolean;
38
+ setIsDragging: (a: boolean) => void;
39
+ }>({
40
+ codeEditorWidth: initialWidth,
41
+ moveSplitPane: () => {},
42
+ isDragging: false,
43
+ setIsDragging: () => {},
44
+ });
45
+
41
46
  const add = (a: number, b: number) => a + b;
42
47
 
43
48
  export const SplitPaneResizeProvider = ({ children }) => {
@@ -0,0 +1,70 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import type { FileId } from '../../types';
3
+ import type { TabState } from '../vzReducer';
4
+
5
+ // Supports adding the file's containing folder to the tab name
6
+ const fileNameSplit = (fileName: string) => {
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
+ export const Tab = ({
15
+ fileId,
16
+ isTransient,
17
+ isActive,
18
+ setActiveFileId,
19
+ openTab,
20
+ closeTabs,
21
+ fileName,
22
+ }: {
23
+ fileId: FileId;
24
+ isTransient: boolean;
25
+ isActive: boolean;
26
+ setActiveFileId: (fileId: FileId) => void;
27
+ openTab: (tabState: TabState) => void;
28
+ closeTabs: (fileIds: FileId[]) => void;
29
+ fileName: string;
30
+ }) => {
31
+ const handleCloseClick = useCallback(
32
+ (event: React.MouseEvent) => {
33
+ // Stop propagation so that the outer listener doesn't fire.
34
+ event.stopPropagation();
35
+
36
+ closeTabs([fileId]);
37
+ },
38
+ [closeTabs, fileId],
39
+ );
40
+
41
+ const tabName = useMemo(
42
+ () => fileNameSplit(fileName),
43
+ [fileName],
44
+ );
45
+
46
+ const handleClick = useCallback(() => {
47
+ setActiveFileId(fileId);
48
+ }, [fileId, setActiveFileId]);
49
+
50
+ // Double-clicking a transient tab makes it a persistent tab.
51
+ const handleDoubleClick = useCallback(() => {
52
+ openTab({ fileId, isTransient: false });
53
+ }, [fileId, openTab]);
54
+
55
+ return (
56
+ <div
57
+ className={`tab user-select-none ${
58
+ isActive ? 'active' : ''
59
+ } ${isTransient ? 'transient' : ''}`}
60
+ onClick={handleClick}
61
+ onDoubleClick={handleDoubleClick}
62
+ >
63
+ {tabName}
64
+ <div
65
+ className={'bx bx-x tab-close'}
66
+ onClick={handleCloseClick}
67
+ ></div>
68
+ </div>
69
+ );
70
+ };
@@ -1,89 +1,45 @@
1
- import { useCallback, useMemo, useEffect } from 'react';
2
- import { FileId, Files } from '../../types';
1
+ import { useCallback, useEffect } from 'react';
2
+ import type { TabState } from '../vzReducer';
3
+ import type { FileId, Files } from '../../types';
4
+ import { Tab } from './Tab';
3
5
  import './style.scss';
4
6
 
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
7
  // Displays the list of tabs above the code editor.
59
8
  export const TabList = ({
60
9
  files,
61
10
  tabList,
62
11
  activeFileId,
63
12
  setActiveFileId,
64
- closeTab,
13
+ openTab,
14
+ closeTabs,
15
+ createFile,
65
16
  }: {
66
17
  files: Files;
67
- tabList: FileId[];
18
+ tabList: Array<TabState>;
68
19
  activeFileId: FileId;
69
20
  setActiveFileId: (fileId: FileId) => void;
70
- closeTab: (fileId: FileId) => void;
21
+ openTab: (tabState: TabState) => void;
22
+ closeTabs: (fileIds: FileId[]) => void;
23
+ createFile: () => void;
71
24
  }) => {
72
- //Create a function to perform the logic to delete the current tab
25
+ // Close the active tab on alt+w
73
26
  const handleKeyPress = useCallback(
74
27
  (event: { altKey: boolean; key: string }) => {
75
28
  if (event.altKey == true) {
76
29
  if (event.key == 'w') {
77
- closeTab(activeFileId);
30
+ closeTabs([activeFileId]);
31
+ }
32
+ if (event.key == 'n') {
33
+ createFile();
78
34
  }
79
35
  }
80
36
  },
81
- [closeTab, activeFileId],
37
+ [createFile, closeTabs, activeFileId],
82
38
  );
39
+
40
+ // Add the global keydown event listener
83
41
  useEffect(() => {
84
- // attach the event listener
85
42
  document.addEventListener('keydown', handleKeyPress);
86
- // remove the event listener
87
43
  return () => {
88
44
  document.removeEventListener(
89
45
  'keydown',
@@ -91,17 +47,20 @@ export const TabList = ({
91
47
  );
92
48
  };
93
49
  }, [handleKeyPress]);
50
+
94
51
  return (
95
52
  <div className="vz-tab-list">
96
53
  {files &&
97
- tabList.map((fileId: FileId) => (
54
+ tabList.map((tabState: TabState) => (
98
55
  <Tab
99
- key={fileId}
100
- fileId={fileId}
101
- isActive={fileId === activeFileId}
56
+ key={tabState.fileId}
57
+ fileId={tabState.fileId}
58
+ isTransient={tabState.isTransient}
59
+ isActive={tabState.fileId === activeFileId}
102
60
  setActiveFileId={setActiveFileId}
103
- closeTab={closeTab}
104
- fileName={files[fileId].name}
61
+ openTab={openTab}
62
+ closeTabs={closeTabs}
63
+ fileName={files[tabState.fileId].name}
105
64
  />
106
65
  ))}
107
66
  </div>
@@ -1,20 +1,20 @@
1
1
  .vz-tab-list {
2
2
  display: flex;
3
- background-color: rgb(37, 37, 38);
3
+ background-color: #33415b;
4
4
  // When the tab list becomes too large, a horizontal scroll bar appears.
5
5
  overflow-x: auto;
6
6
 
7
7
  .tab {
8
- background: rgb(45, 45, 45);
8
+ // background: #47546b;
9
9
  color: #fff;
10
10
  font-family: var(--vzcode-font-family);
11
11
  align-items: center;
12
12
  display: flex;
13
13
  padding-top: 2px;
14
- padding-left: 10px;
15
- padding-right: 10px;
16
- border-top-left-radius: 10px;
17
- border-top-right-radius: 10px;
14
+ padding-left: 8px;
15
+ padding-right: 5px;
16
+ border-top-left-radius: 12px;
17
+ border-top-right-radius: 12px;
18
18
  margin-top: 5px;
19
19
  margin-left: 2px;
20
20
  margin-right: 2px;
@@ -22,15 +22,32 @@
22
22
  // Make sure text doesn't wrap within tabs.
23
23
  white-space: nowrap;
24
24
 
25
+ // TODO unify with styles in code error overlay dismiss button
25
26
  .tab-close {
26
27
  margin-left: 5px;
28
+ margin-bottom: 2px;
27
29
  }
28
30
  .tab-close:hover {
29
- border-radius: 30%;
30
- background-color: rgb(37, 37, 38);
31
+ border-radius: 50%;
32
+ background-color: rgba(255, 255, 255, 0.2);
33
+ }
34
+
35
+ // No left margin for first child
36
+ &:first-child {
37
+ margin-left: 0;
38
+ }
39
+
40
+ // on hover
41
+ &:hover {
42
+ background-color: #47546b;
31
43
  }
32
44
  }
45
+
33
46
  .tab.active {
34
- background-color: rgb(41, 44, 52);
47
+ background-color: #202e46;
48
+ }
49
+
50
+ .tab.transient {
51
+ font-style: italic;
35
52
  }
36
53
  }
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useRef } from 'react';
2
2
  import { Button, Modal, Form } from './bootstrap';
3
3
  import { ThemeLabel, themes } from './themes';
4
+ import { Username } from '../types';
4
5
 
5
6
  // const saveTimes = [
6
7
  // { value: 1, label: '1 second' },
@@ -9,18 +10,25 @@ import { ThemeLabel, themes } from './themes';
9
10
  // { value: 30, label: '30 seconds' },
10
11
  // ];
11
12
 
12
- export let username = 'Anonymous';
13
-
14
- export const Settings = ({
13
+ export const VZSettings = ({
15
14
  show,
16
15
  onClose,
17
- setTheme,
18
16
  theme,
17
+ setTheme,
18
+ username,
19
+ setUsername,
20
+
21
+ enableUsernameField = true,
19
22
  }: {
20
23
  show: boolean;
21
24
  onClose: () => void;
22
- setTheme: (theme: ThemeLabel) => void;
23
25
  theme: ThemeLabel;
26
+ setTheme: (theme: ThemeLabel) => void;
27
+ username: Username;
28
+ setUsername: (username: Username) => void;
29
+
30
+ // Feature flag to enable/disable username field
31
+ enableUsernameField?: boolean;
24
32
  }) => {
25
33
  const handleThemeChange = useCallback((event) => {
26
34
  const selectedValue = event.target.value;
@@ -44,10 +52,10 @@ export const Settings = ({
44
52
  // );
45
53
 
46
54
  const usernameRef = useRef<HTMLInputElement>(null);
47
- const handleUsernameChange = () => {
48
- const usernameValue = usernameRef.current?.value;
49
- username = usernameValue;
50
- };
55
+
56
+ const handleUsernameChange = useCallback(() => {
57
+ setUsername(usernameRef.current?.value || '');
58
+ }, [setUsername]);
51
59
 
52
60
  return show ? (
53
61
  <Modal
@@ -60,18 +68,23 @@ export const Settings = ({
60
68
  <Modal.Title>Settings</Modal.Title>
61
69
  </Modal.Header>
62
70
  <Modal.Body>
63
- <Form.Group className="mb-3" controlId="formFork">
64
- <Form.Label>Username</Form.Label>
65
- <Form.Control
66
- type="text"
67
- ref={usernameRef}
68
- onChange={handleUsernameChange}
69
- placeholder="Enter username"
70
- />
71
- <Form.Text className="text-muted">
72
- Enter a username to be displayed on your cursor
73
- </Form.Text>
74
- </Form.Group>
71
+ {enableUsernameField ? (
72
+ <Form.Group className="mb-3" controlId="formFork">
73
+ <Form.Label>Username</Form.Label>
74
+ <Form.Control
75
+ type="text"
76
+ ref={usernameRef}
77
+ onChange={handleUsernameChange}
78
+ placeholder="Enter username"
79
+ value={username}
80
+ />
81
+ <Form.Text className="text-muted">
82
+ Enter a username to be displayed on your
83
+ cursor
84
+ </Form.Text>
85
+ </Form.Group>
86
+ ) : null}
87
+
75
88
  <Form.Group className="mb-3" controlId="formFork">
76
89
  <Form.Label>Theme</Form.Label>
77
90