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.
- package/dist/assets/index-836bdce7.css +1 -0
- package/dist/assets/index-c3a6c525.js +115 -0
- package/dist/assets/worker-ed0e2778.js +366 -0
- package/dist/index.html +2 -2
- package/package.json +10 -6
- package/src/client/AIAssist.ts +110 -19
- package/src/client/AIAssistWidget/index.tsx +101 -0
- package/src/client/AIAssistWidget/style.scss +10 -0
- package/src/client/App/VZCodeContext.tsx +249 -0
- package/src/client/App/VZLeft.tsx +56 -0
- package/src/client/App/VZMiddle.tsx +91 -0
- package/src/client/App/VZRight.tsx +15 -0
- package/src/client/App/index.tsx +106 -0
- package/src/client/{style.scss → App/style.scss} +14 -3
- package/src/client/App/useShareDB.ts +110 -0
- package/src/client/CodeEditor/getOrCreateEditor.ts +54 -6
- package/src/client/CodeEditor/index.tsx +9 -0
- package/src/client/CodeEditor/json1PresenceDisplay.ts +38 -9
- package/src/client/CodeEditor/typeScriptCompletions.ts +108 -0
- package/src/client/CodeEditor/typeScriptLinter.ts +45 -0
- package/src/client/CodeErrorOverlay/index.tsx +27 -1
- package/src/client/CodeErrorOverlay/style.scss +11 -11
- package/src/client/SplitPaneResizeContext.tsx +198 -39
- package/src/client/TabList/index.tsx +1 -27
- package/src/client/{Resizer → VZResizer}/index.tsx +37 -13
- package/src/client/VZSettings.tsx +1 -1
- package/src/client/VZSidebar/CreateFileModal.tsx +93 -0
- package/src/client/VZSidebar/DeleteConfirmationModal.tsx +42 -24
- package/src/client/VZSidebar/index.tsx +69 -9
- package/src/client/useFileCRUD.ts +19 -19
- package/src/client/useOpenDirectories.ts +12 -8
- package/src/client/usePrettier/index.ts +13 -1
- package/src/client/useSubmitOperation.ts +16 -19
- package/src/client/useTypeScript/index.ts +44 -0
- package/src/client/useTypeScript/requestTypes.ts +29 -0
- package/src/client/useTypeScript/worker.ts +242 -0
- package/src/client/usernameLocalStorage.ts +1 -1
- package/src/client/vzReducer/createInitialState.ts +4 -5
- package/src/index.ts +5 -1
- package/src/server/handleAIAssist.js +76 -12
- package/src/server/index.js +55 -2
- package/src/types.ts +78 -1
- package/dist/assets/index-a9585cbc.js +0 -111
- package/dist/assets/index-d30c9f1b.css +0 -1
- package/src/client/App.tsx +0 -286
- /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
|
-
|
|
4
|
-
|
|
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: #
|
|
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: #
|
|
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
|
-
|
|
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
|
|
17
|
-
let
|
|
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
|
|
23
|
-
window.localStorage.getItem(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
codeEditorWidth:
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 [
|
|
50
|
-
|
|
51
|
-
|
|
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 (
|
|
57
|
-
|
|
208
|
+
if (sidebarWidth !== initialSidebarWidth) {
|
|
209
|
+
setTimeout(() => {
|
|
58
210
|
window.localStorage.setItem(
|
|
59
|
-
|
|
60
|
-
'' +
|
|
211
|
+
sidebarWidthProperty,
|
|
212
|
+
'' + sidebarWidth,
|
|
61
213
|
);
|
|
62
214
|
}, localStorageWriteDebounceMS);
|
|
215
|
+
}
|
|
63
216
|
|
|
64
|
-
|
|
65
|
-
|
|
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 {
|
|
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
|
|
29
|
+
export const VZResizer = ({ side }: { side: Side }) => {
|
|
26
30
|
const {
|
|
31
|
+
sidebarWidth,
|
|
27
32
|
codeEditorWidth,
|
|
28
33
|
moveSplitPane,
|
|
29
|
-
|
|
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
|
|
60
|
+
const movementX =
|
|
48
61
|
event.clientX - previousClientX.current;
|
|
49
62
|
previousClientX.current = event.clientX;
|
|
50
|
-
moveSplitPane(
|
|
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
|
-
|
|
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
|
|
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
|
};
|