vzcode 0.28.0 → 0.30.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 (46) hide show
  1. package/dist/assets/index-836bdce7.css +1 -0
  2. package/dist/assets/index-c3a6c525.js +115 -0
  3. package/dist/assets/worker-ed0e2778.js +366 -0
  4. package/dist/index.html +2 -2
  5. package/package.json +10 -6
  6. package/src/client/AIAssist.ts +110 -19
  7. package/src/client/AIAssistWidget/index.tsx +101 -0
  8. package/src/client/AIAssistWidget/style.scss +10 -0
  9. package/src/client/App/VZCodeContext.tsx +249 -0
  10. package/src/client/App/VZLeft.tsx +56 -0
  11. package/src/client/App/VZMiddle.tsx +91 -0
  12. package/src/client/App/VZRight.tsx +15 -0
  13. package/src/client/App/index.tsx +106 -0
  14. package/src/client/{style.scss → App/style.scss} +14 -3
  15. package/src/client/App/useShareDB.ts +110 -0
  16. package/src/client/CodeEditor/getOrCreateEditor.ts +54 -6
  17. package/src/client/CodeEditor/index.tsx +9 -0
  18. package/src/client/CodeEditor/json1PresenceDisplay.ts +38 -9
  19. package/src/client/CodeEditor/typeScriptCompletions.ts +108 -0
  20. package/src/client/CodeEditor/typeScriptLinter.ts +45 -0
  21. package/src/client/CodeErrorOverlay/index.tsx +27 -1
  22. package/src/client/CodeErrorOverlay/style.scss +11 -11
  23. package/src/client/SplitPaneResizeContext.tsx +198 -39
  24. package/src/client/TabList/index.tsx +1 -27
  25. package/src/client/{Resizer → VZResizer}/index.tsx +37 -13
  26. package/src/client/VZSettings.tsx +1 -1
  27. package/src/client/VZSidebar/CreateFileModal.tsx +93 -0
  28. package/src/client/VZSidebar/DeleteConfirmationModal.tsx +42 -24
  29. package/src/client/VZSidebar/index.tsx +69 -9
  30. package/src/client/useFileCRUD.ts +19 -19
  31. package/src/client/useOpenDirectories.ts +12 -8
  32. package/src/client/usePrettier/index.ts +13 -1
  33. package/src/client/useSubmitOperation.ts +16 -19
  34. package/src/client/useTypeScript/index.ts +44 -0
  35. package/src/client/useTypeScript/requestTypes.ts +29 -0
  36. package/src/client/useTypeScript/worker.ts +242 -0
  37. package/src/client/usernameLocalStorage.ts +1 -1
  38. package/src/client/vzReducer/createInitialState.ts +4 -5
  39. package/src/index.ts +5 -1
  40. package/src/server/handleAIAssist.js +76 -12
  41. package/src/server/index.js +55 -2
  42. package/src/types.ts +78 -1
  43. package/dist/assets/index-a9585cbc.js +0 -111
  44. package/dist/assets/index-d30c9f1b.css +0 -1
  45. package/src/client/App.tsx +0 -286
  46. /package/src/client/{Resizer → VZResizer}/styles.scss +0 -0
@@ -0,0 +1,108 @@
1
+ import {
2
+ CompletionContext,
3
+ CompletionSource,
4
+ } from '@codemirror/autocomplete';
5
+ import {
6
+ AutocompleteRequest,
7
+ AutocompleteResponse,
8
+ } from '../useTypeScript/requestTypes';
9
+
10
+ type RequestId = string;
11
+
12
+ export const generateRequestId = (): RequestId =>
13
+ (Math.random() + '').slice(2);
14
+
15
+ export const typeScriptCompletions = ({
16
+ typeScriptWorker,
17
+ fileName,
18
+ }): CompletionSource => {
19
+ const tsComplete: CompletionSource = async (
20
+ completionContext: CompletionContext,
21
+ ) => {
22
+ // A random unique ID for this request.
23
+ const requestId = generateRequestId();
24
+
25
+ const fileContent =
26
+ completionContext.state.doc.toString();
27
+
28
+ const autocompleteRequest: AutocompleteRequest = {
29
+ event: 'autocomplete-request',
30
+
31
+ // Location is the file path (string)
32
+ fileName,
33
+
34
+ // Latest file content (string)
35
+ fileContent,
36
+
37
+ // Cursor position (integer, like index in a string)
38
+ position: completionContext.pos,
39
+
40
+ // Unique ID for this request
41
+ requestId,
42
+ };
43
+
44
+ //Post message to our sharedWorker to get completions.
45
+ typeScriptWorker.postMessage(autocompleteRequest);
46
+
47
+ //An async promise to ensure that we are getting our completion entries
48
+ const tsCompletions = await new Promise((resolve) => {
49
+ typeScriptWorker.onmessage = (message: {
50
+ data: AutocompleteResponse;
51
+ }) => {
52
+ const autocompleteResponse: AutocompleteResponse =
53
+ message.data;
54
+ const { event, requestId, completions } =
55
+ autocompleteResponse;
56
+ if (
57
+ event === 'post-completions' &&
58
+ requestId === requestId
59
+ ) {
60
+ resolve(completions);
61
+ }
62
+ };
63
+ });
64
+
65
+ if (!tsCompletions) {
66
+ // console.log('Unable to get completions');
67
+ return { from: completionContext.pos, options: [] };
68
+ }
69
+
70
+ // Logic to get the text and cursor location in between punctuation.
71
+ // Taken from https://codemirror.net/examples/autocompletion/
72
+ // Also inspired by
73
+ // https://stackblitz.com/edit/codemirror-6-typescript?file=client%2Findex.ts%3AL86
74
+ const from = completionContext.matchBefore(/\w*/).from;
75
+
76
+ // `lastWord` represents the word that the user has partially typed
77
+ // and is currently at the end of the text, immediately before
78
+ // the cursor position.
79
+ const lastWord =
80
+ completionContext.matchBefore(/\w*/).text;
81
+ if (lastWord) {
82
+ // @ts-ignore
83
+ tsCompletions.entries = tsCompletions.entries.filter(
84
+ (completion) =>
85
+ completion.name.startsWith(lastWord),
86
+ );
87
+ }
88
+
89
+ return {
90
+ from: completionContext.pos,
91
+ // @ts-ignore
92
+ options: tsCompletions.entries.map((completion) => ({
93
+ label: completion.name,
94
+ // Applies autocompletions to be seen in the code Editor
95
+ apply: (view) => {
96
+ view.dispatch({
97
+ changes: {
98
+ from,
99
+ to: completionContext.pos,
100
+ insert: completion.name,
101
+ },
102
+ });
103
+ },
104
+ })),
105
+ };
106
+ };
107
+ return tsComplete;
108
+ };
@@ -0,0 +1,45 @@
1
+ import { generateRequestId } from './typeScriptCompletions';
2
+ import {
3
+ LinterResponse,
4
+ LinterRequest,
5
+ } from '../useTypeScript/requestTypes';
6
+ import ts from 'typescript';
7
+
8
+ export const typeScriptLinter = ({
9
+ typeScriptWorker,
10
+ fileName,
11
+ shareDBDoc,
12
+ fileId,
13
+ }) => {
14
+ return async () => {
15
+ const requestId = generateRequestId();
16
+ //Get the fileContent from shareDB to pass to web worker
17
+ const fileContent = shareDBDoc.data.files[fileId].text;
18
+ const linterRequest: LinterRequest = {
19
+ event: 'lint-request',
20
+ fileName,
21
+ fileContent,
22
+ requestId,
23
+ };
24
+ typeScriptWorker.postMessage(linterRequest);
25
+
26
+ //An array of diagnostic (CodeMirror) objects.
27
+ const tsErrors: ts.Diagnostic[] = await new Promise(
28
+ (resolve) => {
29
+ typeScriptWorker.onmessage = (message: {
30
+ data: LinterResponse;
31
+ }) => {
32
+ const ErrorData: LinterResponse = message.data;
33
+ const { event, requestId, tsErrors } = ErrorData;
34
+ if (
35
+ event === 'post-error-linter' &&
36
+ requestId === requestId
37
+ ) {
38
+ resolve(tsErrors);
39
+ }
40
+ };
41
+ },
42
+ );
43
+ return tsErrors;
44
+ };
45
+ };
@@ -18,14 +18,40 @@ export const CodeErrorOverlay = ({
18
18
  }
19
19
  }, [errorMessage]);
20
20
 
21
+ useEffect(() => {
22
+ document.addEventListener(
23
+ 'keydown',
24
+ detectKeyDown,
25
+ true,
26
+ );
27
+
28
+ return () => {
29
+ // Remove the event listener when the component unmounts
30
+ document.removeEventListener(
31
+ 'keydown',
32
+ detectKeyDown,
33
+ true,
34
+ );
35
+ };
36
+ }, []);
37
+
21
38
  const handleCloseClick = useCallback(() => {
22
39
  // Set the visibility state to false when the button is clicked
23
40
  setIsOverlayVisible(false);
24
41
  }, []);
25
42
 
43
+ const detectKeyDown = useCallback(
44
+ (event: KeyboardEvent) => {
45
+ if (event.key === 'Escape') {
46
+ handleCloseClick();
47
+ }
48
+ },
49
+ [handleCloseClick],
50
+ );
51
+
26
52
  return isOverlayVisible && errorMessage !== null ? (
27
53
  <div className="vz-code-error-overlay">
28
- {errorMessage}
54
+ <pre>{errorMessage}</pre>
29
55
  {enableErrorDismiss ? (
30
56
  <div
31
57
  className={'bx bx-x error-dismiss-button'}
@@ -1,26 +1,26 @@
1
1
  .vz-code-error-overlay {
2
2
  color: rgb(255, 35, 35);
3
- font-family: var(--vzcode-font-family);
4
- font-size: var(--vzcode-font-size);
5
- background-color: rgba(0, 0, 0, 0.6);
3
+
4
+ background-color: rgba(0, 0, 0, 0.9);
6
5
  position: absolute;
7
6
  bottom: 0;
8
7
  left: 0;
9
8
  right: 0;
10
9
  // pointer-events: none;
11
10
  margin: 0;
12
- padding: 8px;
11
+ padding: 8px 12px;
13
12
  border-top-left-radius: 12px;
14
13
  border-top-right-radius: 12px;
15
14
 
16
- /* Define your custom styles here */
17
- /* For example, change the font size and color */
18
- font-size: 16px;
19
- color: #FF5733;
20
-
21
15
  /* Add a box shadow for a subtle effect */
22
16
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
23
17
 
18
+ /* Style the monospace font */
19
+ pre {
20
+ font-family: var(--vzcode-font-family);
21
+ font-size: var(--vzcode-font-size);
22
+ }
23
+
24
24
  /* Customize the dismiss button style */
25
25
  .error-dismiss-button {
26
26
  position: absolute;
@@ -30,12 +30,12 @@
30
30
  padding: 5px; /* Adjust padding for the button */
31
31
  cursor: pointer;
32
32
  color: white;
33
- background-color: #FF5733; /* Change the button's background color */
33
+ background-color: #ff5733; /* Change the button's background color */
34
34
  border-radius: 50%; /* Make it circular */
35
35
  }
36
36
 
37
37
  /* Style the button on hover */
38
38
  .error-dismiss-button:hover {
39
- background-color: #FF826D; /* Change the background color on hover */
39
+ background-color: #ff826d; /* Change the background color on hover */
40
40
  }
41
41
  }
@@ -1,81 +1,240 @@
1
1
  // From https://github.com/vizhub-core/vizhub/edit/main/vizhub-v2/packages/neoFrontend/src/pages/VizPage/SplitPaneResizeContext/index.js
2
2
  import {
3
- useState,
4
3
  createContext,
5
4
  useReducer,
6
5
  useEffect,
6
+ useCallback,
7
+ useLayoutEffect,
7
8
  } from 'react';
8
9
 
10
+ export type Side = 'left' | 'right';
11
+
12
+ export type SplitPaneResizeContextValue = {
13
+ codeEditorWidth: number;
14
+ sidebarWidth: number;
15
+ moveSplitPane: (a: number, side: Side) => void;
16
+ isDraggingRight: boolean;
17
+ isDraggingLeft: boolean;
18
+ setIsDragging: (a: boolean, side: Side) => void;
19
+ };
20
+
21
+ // Feature flag for developers to use when testing.
22
+ const enableLocalStorage = true;
23
+
9
24
  // The amount of time to wait idle before writing to localStorage.
10
25
  // This is to avoid writing to localStorage on every resize event.
11
26
  // MS = milliseconds
12
27
  const localStorageWriteDebounceMS = 800;
13
28
 
14
- const localStoragePropertyName = 'vizHubCodeEditorWidth';
29
+ // Properties in LocalStorage
30
+ const sidebarWidthProperty = 'vzCodeSidebarWidth';
31
+ const codeEditorWidthProperty = 'vzCodeCodeEditorWidth';
32
+
33
+ const initialSidebarWidthDefault = 220;
34
+ let initialSidebarWidth: number =
35
+ initialSidebarWidthDefault;
15
36
 
16
- const initialWidthDefault = 220;
17
- let initialWidth: number = initialWidthDefault;
37
+ const initialCodeEditorWidthDefault = 800;
38
+ let initialCodeEditorWidth: number =
39
+ initialCodeEditorWidthDefault;
40
+
41
+ // If no value is found in localStorage, we'll use the default.
42
+ let needsInitialization = false;
18
43
 
19
44
  // If we're in the browser,
20
- if (typeof window !== 'undefined') {
45
+ if (typeof window !== 'undefined' && enableLocalStorage) {
21
46
  //check localStorage for a previously stored width.
22
- const initialWidthFromLocalStorage: string | null =
23
- window.localStorage.getItem(localStoragePropertyName);
47
+ const sidebarWidthLocal: string | null =
48
+ window.localStorage.getItem(sidebarWidthProperty);
49
+
50
+ const codeEditorWidthLocal: string | null =
51
+ window.localStorage.getItem(codeEditorWidthProperty);
24
52
 
25
53
  // If there is a previously stored width,
26
- if (initialWidthFromLocalStorage !== null) {
54
+ if (sidebarWidthLocal !== null) {
55
+ // use it as the initial width.
56
+ initialSidebarWidth = +sidebarWidthLocal;
57
+ needsInitialization = true;
58
+ }
59
+
60
+ if (codeEditorWidthLocal !== null) {
27
61
  // use it as the initial width.
28
- initialWidth = +initialWidthFromLocalStorage;
62
+ initialCodeEditorWidth = +codeEditorWidthLocal;
63
+ needsInitialization = true;
29
64
  }
30
65
  } else {
31
66
  // If we're not in the browser, use the default initial width.
32
67
  }
33
68
 
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,
69
+ const initialValue: SplitPaneResizeContextValue = {
70
+ codeEditorWidth: initialCodeEditorWidth,
71
+ sidebarWidth: initialSidebarWidth,
41
72
  moveSplitPane: () => {},
42
- isDragging: false,
73
+ isDraggingRight: false,
74
+ isDraggingLeft: false,
43
75
  setIsDragging: () => {},
44
- });
76
+ };
77
+
78
+ export const SplitPaneResizeContext =
79
+ createContext<SplitPaneResizeContextValue>(initialValue);
80
+
81
+ type SplitPaneState = {
82
+ // The width of the sidebar, in pixels.
83
+ sidebarWidth: number;
84
+ codeEditorWidth: number;
85
+ isDraggingLeft: boolean;
86
+ isDraggingRight: boolean;
87
+
88
+ // False on first render
89
+ isInitialized: boolean;
90
+ };
91
+
92
+ const initialState: SplitPaneState = {
93
+ // sidebarWidth: initialSidebarWidth,
94
+ // codeEditorWidth: initialCodeEditorWidth,
95
+
96
+ // These need to match between SSR and the first
97
+ // client-side render (hydration), to avoid
98
+ // React hydration errors like this one:
99
+ // * Warning: Prop `style` did not match.
100
+ // * Server: "left:210px;width:20px"
101
+ // * Client: "left:218px;width:20px"
102
+
103
+ sidebarWidth: initialSidebarWidthDefault,
104
+ codeEditorWidth: initialCodeEditorWidthDefault,
105
+ isDraggingLeft: false,
106
+ isDraggingRight: false,
107
+
108
+ // Solution for hydration errors: we set the value from localStorage
109
+ // in a layoutEffect that happens after first render.
110
+ isInitialized: false,
111
+ };
45
112
 
46
- const add = (a: number, b: number) => a + b;
113
+ type SplitPaneAction =
114
+ | {
115
+ type: 'move';
116
+ side: Side;
117
+ movementX: number;
118
+ }
119
+ | {
120
+ type: 'setIsDragging';
121
+ side: Side;
122
+ isDragging: boolean;
123
+ }
124
+ | {
125
+ type: 'initialize';
126
+ };
127
+
128
+ const splitPaneReducer = (
129
+ state: SplitPaneState,
130
+ action: SplitPaneAction,
131
+ ) => {
132
+ switch (action.type) {
133
+ case 'initialize':
134
+ return {
135
+ ...state,
136
+ sidebarWidth: initialSidebarWidth,
137
+ codeEditorWidth: initialCodeEditorWidth,
138
+ isInitialized: true,
139
+ };
140
+ case 'move':
141
+ return action.side === 'left'
142
+ ? {
143
+ ...state,
144
+ sidebarWidth:
145
+ state.sidebarWidth + action.movementX,
146
+ }
147
+ : {
148
+ ...state,
149
+ codeEditorWidth:
150
+ state.codeEditorWidth + action.movementX,
151
+ };
152
+ // Handle setIsDragging
153
+ case 'setIsDragging':
154
+ return action.side === 'left'
155
+ ? {
156
+ ...state,
157
+ isDraggingLeft: action.isDragging,
158
+ }
159
+ : {
160
+ ...state,
161
+ isDraggingRight: action.isDragging,
162
+ };
163
+ default:
164
+ return state;
165
+ }
166
+ };
47
167
 
48
168
  export const SplitPaneResizeProvider = ({ children }) => {
49
- const [codeEditorWidth, moveSplitPane] = useReducer(
50
- add,
51
- initialWidth,
169
+ const [state, dispatch] = useReducer(
170
+ splitPaneReducer,
171
+ initialState,
172
+ );
173
+
174
+ const setIsDragging = useCallback(
175
+ (isDragging: boolean, side: Side) => {
176
+ dispatch({ type: 'setIsDragging', isDragging, side });
177
+ },
178
+ [dispatch],
179
+ );
180
+
181
+ const moveSplitPane = useCallback(
182
+ (movementX: number, side: Side) => {
183
+ dispatch({ type: 'move', movementX, side });
184
+ },
185
+ [dispatch],
52
186
  );
53
- const [isDragging, setIsDragging] = useState(false);
54
187
 
188
+ const {
189
+ sidebarWidth,
190
+ codeEditorWidth,
191
+ isDraggingLeft,
192
+ isDraggingRight,
193
+ isInitialized,
194
+ } = state;
195
+
196
+ // Logic around initializing from localStorage.
197
+ // Only run this on the client.
198
+ if (typeof window !== 'undefined') {
199
+ useLayoutEffect(() => {
200
+ if (needsInitialization && !isInitialized) {
201
+ dispatch({ type: 'initialize' });
202
+ }
203
+ }, [isInitialized]);
204
+ }
205
+
206
+ // Logic around storing the values in localStorage.
55
207
  useEffect(() => {
56
- if (codeEditorWidth !== initialWidth) {
57
- const timeout = setTimeout(() => {
208
+ if (sidebarWidth !== initialSidebarWidth) {
209
+ setTimeout(() => {
58
210
  window.localStorage.setItem(
59
- localStoragePropertyName,
60
- '' + codeEditorWidth,
211
+ sidebarWidthProperty,
212
+ '' + sidebarWidth,
61
213
  );
62
214
  }, localStorageWriteDebounceMS);
215
+ }
63
216
 
64
- return () => {
65
- clearTimeout(timeout);
66
- };
217
+ if (codeEditorWidth !== initialCodeEditorWidth) {
218
+ setTimeout(() => {
219
+ window.localStorage.setItem(
220
+ codeEditorWidthProperty,
221
+ '' + codeEditorWidth,
222
+ );
223
+ }, localStorageWriteDebounceMS);
67
224
  }
68
- }, [codeEditorWidth]);
225
+ }, [codeEditorWidth, sidebarWidth]);
226
+
227
+ const value: SplitPaneResizeContextValue = {
228
+ codeEditorWidth,
229
+ sidebarWidth,
230
+ moveSplitPane,
231
+ isDraggingLeft,
232
+ isDraggingRight,
233
+ setIsDragging,
234
+ };
69
235
 
70
236
  return (
71
- <SplitPaneResizeContext.Provider
72
- value={{
73
- codeEditorWidth,
74
- moveSplitPane,
75
- isDragging,
76
- setIsDragging,
77
- }}
78
- >
237
+ <SplitPaneResizeContext.Provider value={value}>
79
238
  {children}
80
239
  </SplitPaneResizeContext.Provider>
81
240
  );
@@ -20,34 +20,8 @@ export const TabList = ({
20
20
  setActiveFileId: (fileId: FileId) => void;
21
21
  openTab: (tabState: TabState) => void;
22
22
  closeTabs: (fileIds: FileId[]) => void;
23
- createFile: () => void;
23
+ createFile: (fileName: string) => void;
24
24
  }) => {
25
- // Close the active tab on alt+w
26
- const handleKeyPress = useCallback(
27
- (event: { altKey: boolean; key: string }) => {
28
- if (event.altKey == true) {
29
- if (event.key == 'w') {
30
- closeTabs([activeFileId]);
31
- }
32
- if (event.key == 'n') {
33
- createFile();
34
- }
35
- }
36
- },
37
- [createFile, closeTabs, activeFileId],
38
- );
39
-
40
- // Add the global keydown event listener
41
- useEffect(() => {
42
- document.addEventListener('keydown', handleKeyPress);
43
- return () => {
44
- document.removeEventListener(
45
- 'keydown',
46
- handleKeyPress,
47
- );
48
- };
49
- }, [handleKeyPress]);
50
-
51
25
  return (
52
26
  <div className="vz-tab-list">
53
27
  {files &&
@@ -7,8 +7,12 @@ import {
7
7
  useRef,
8
8
  } from 'react';
9
9
 
10
- import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
10
+ import {
11
+ Side,
12
+ SplitPaneResizeContext,
13
+ } from '../SplitPaneResizeContext';
11
14
  import './styles.scss';
15
+ import { VZCodeContext } from '../App/VZCodeContext';
12
16
 
13
17
  // This is the width of the resizer interaction surface.
14
18
  // On this surface, the cursor changes to a resize cursor.
@@ -22,40 +26,54 @@ const resizerInteractionSurfaceWidthWhileDragging = 200;
22
26
  // This is the part of the resizer that is visible to the user.
23
27
  const resizerThumbWidth = 4;
24
28
 
25
- export const Resizer = () => {
29
+ export const VZResizer = ({ side }: { side: Side }) => {
26
30
  const {
31
+ sidebarWidth,
27
32
  codeEditorWidth,
28
33
  moveSplitPane,
29
- isDragging,
34
+ isDraggingRight,
35
+ isDraggingLeft,
30
36
  setIsDragging,
31
37
  } = useContext(SplitPaneResizeContext);
38
+
39
+ // If there is no active file, don't render the resizer
40
+ // for the right side.
41
+ const { activeFileId } = useContext(VZCodeContext);
42
+ const shouldRenderResizer =
43
+ side === 'left' || activeFileId;
44
+
32
45
  const previousClientX = useRef<number>(0);
33
46
 
34
47
  const onMouseDown = useCallback(
35
48
  (event: React.MouseEvent) => {
36
49
  event.preventDefault();
37
50
  previousClientX.current = event.clientX;
38
- setIsDragging(true);
51
+ setIsDragging(true, side);
39
52
  document.body.style.cursor = 'col-resize';
40
53
  },
41
- [setIsDragging],
54
+ [setIsDragging, side],
42
55
  );
43
56
 
44
57
  const onMouseMove = useCallback(
45
58
  (event: MouseEvent) => {
46
59
  event.preventDefault();
47
- const movementClientX =
60
+ const movementX =
48
61
  event.clientX - previousClientX.current;
49
62
  previousClientX.current = event.clientX;
50
- moveSplitPane(movementClientX);
63
+ moveSplitPane(movementX, side);
51
64
  },
52
- [moveSplitPane],
65
+ [moveSplitPane, side],
53
66
  );
54
67
 
55
68
  const onMouseUp = useCallback(() => {
56
- setIsDragging(false);
69
+ setIsDragging(false, side);
57
70
  document.body.style.cursor = 'default';
58
- }, [setIsDragging]);
71
+ }, [setIsDragging, side]);
72
+
73
+ // Is the resizer currently being dragged?
74
+ const isDragging =
75
+ (side === 'left' && isDraggingLeft) ||
76
+ (side === 'right' && isDraggingRight);
59
77
 
60
78
  useEffect(() => {
61
79
  if (isDragging) {
@@ -75,12 +93,18 @@ export const Resizer = () => {
75
93
  ? resizerInteractionSurfaceWidthWhileDragging
76
94
  : resizerInteractionSurfaceWidth;
77
95
 
78
- return (
96
+ const left =
97
+ (side === 'left'
98
+ ? sidebarWidth
99
+ : sidebarWidth + codeEditorWidth) -
100
+ resizerWidth / 2;
101
+
102
+ return shouldRenderResizer ? (
79
103
  <div
80
104
  className="vz-resizer"
81
105
  onMouseDown={onMouseDown}
82
106
  style={{
83
- left: codeEditorWidth - resizerWidth / 2,
107
+ left,
84
108
  width: resizerWidth,
85
109
  }}
86
110
  >
@@ -92,5 +116,5 @@ export const Resizer = () => {
92
116
  }}
93
117
  />
94
118
  </div>
95
- );
119
+ ) : null;
96
120
  };
@@ -65,7 +65,7 @@ export const VZSettings = ({
65
65
  animation={false}
66
66
  >
67
67
  <Modal.Header closeButton>
68
- <Modal.Title>Settings</Modal.Title>
68
+ <Modal.Title>Editor Settings</Modal.Title>
69
69
  </Modal.Header>
70
70
  <Modal.Body>
71
71
  {enableUsernameField ? (