vzcode 1.30.1 → 1.32.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-zzK6rRiK.css → index-DTtcN2MP.css} +1 -1
- package/dist/assets/index-sreAszZh.js +240 -0
- package/dist/assets/worker-BKfYWklR.js +136 -0
- package/dist/assets/{index-Dd8gk_1S.js → worker-COyDRdqW.js} +109 -103
- package/dist/assets/{worker-CaP6zYz7.js → worker-CWsWXMyk.js} +70 -70
- package/dist/index.html +2 -2
- package/package.json +42 -36
- package/src/client/AIAssist/startAIAssist.ts +0 -24
- package/src/client/App/index.tsx +14 -7
- package/src/client/CodeEditor/getOrCreateEditor.ts +80 -56
- package/src/client/CodeEditor/index.tsx +56 -30
- package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
- package/src/client/RunCodeWidget/index.tsx +0 -2
- package/src/client/VZCodeContext.tsx +8 -25
- package/src/client/VZMiddle.tsx +11 -5
- package/src/client/VZSidebar/Search.tsx +11 -0
- package/src/client/VZSidebar/index.tsx +18 -11
- package/src/client/VZSidebar/styles.scss +27 -7
- package/src/client/VZSidebar/useDragAndDrop.tsx +237 -35
- package/src/client/main.jsx +0 -2
- package/src/client/useESLint/index.ts +72 -0
- package/src/client/useESLint/worker.ts +113 -0
- package/src/client/useFileCRUD.ts +0 -2
- package/src/server/featureFlags.js +4 -6
- package/src/server/setupEnv.js +0 -10
- package/dist/assets/index-BOiH3_fC.js +0 -234
- package/src/client/CodeEditor/typeScriptCompletions.ts +0 -134
- package/src/client/CodeEditor/typeScriptLinter.ts +0 -47
- package/src/client/useTypeScript/index.ts +0 -48
- package/src/client/useTypeScript/worker/constants.ts +0 -94
- package/src/client/useTypeScript/worker/getTSFileName.ts +0 -4
- package/src/client/useTypeScript/worker/handleMessageAutocompleteRequest.ts +0 -63
- package/src/client/useTypeScript/worker/handleMessageLintRequest.ts +0 -84
- package/src/client/useTypeScript/worker/handleMessageTranspileRequest.ts +0 -20
- package/src/client/useTypeScript/worker/handleMessageUpdateContent.ts +0 -42
- package/src/client/useTypeScript/worker/index.ts +0 -110
- package/src/client/useTypeScript/worker/isTS.ts +0 -3
- package/src/client/useTypeScript/worker/requestTypes.ts +0 -31
|
@@ -34,7 +34,6 @@ import { useKeyboardShortcuts } from './useKeyboardShortcuts';
|
|
|
34
34
|
import { useOpenDirectories } from './useOpenDirectories';
|
|
35
35
|
import { usePrettier } from './usePrettier';
|
|
36
36
|
import { useRunCode } from './useRunCode';
|
|
37
|
-
import { useTypeScript } from './useTypeScript';
|
|
38
37
|
import { useURLSync } from './useURLSync';
|
|
39
38
|
import { createInitialState, vzReducer } from './vzReducer';
|
|
40
39
|
import { findPane } from './vzReducer/findPane';
|
|
@@ -110,8 +109,6 @@ export type VZCodeContextValue = {
|
|
|
110
109
|
|
|
111
110
|
errorMessage: string | null;
|
|
112
111
|
|
|
113
|
-
typeScriptWorker: Worker | null;
|
|
114
|
-
|
|
115
112
|
search: SearchResults;
|
|
116
113
|
isSearchOpen: boolean;
|
|
117
114
|
setIsSearchOpen: (isSearchOpen: boolean) => void;
|
|
@@ -185,7 +182,6 @@ export const VZCodeProvider = ({
|
|
|
185
182
|
localPresence,
|
|
186
183
|
docPresence,
|
|
187
184
|
prettierWorker,
|
|
188
|
-
typeScriptWorker,
|
|
189
185
|
initialUsername,
|
|
190
186
|
children,
|
|
191
187
|
codeError = null,
|
|
@@ -204,18 +200,17 @@ export const VZCodeProvider = ({
|
|
|
204
200
|
localPresence: any;
|
|
205
201
|
docPresence: any;
|
|
206
202
|
prettierWorker: Worker;
|
|
207
|
-
typeScriptWorker: Worker;
|
|
208
203
|
initialUsername: Username;
|
|
209
204
|
children: React.ReactNode;
|
|
210
205
|
codeError?: string | null;
|
|
211
|
-
connected
|
|
212
|
-
pending
|
|
213
|
-
liveKitToken
|
|
214
|
-
setLiveKitToken
|
|
215
|
-
liveKitRoomName
|
|
216
|
-
setLiveKitRoom
|
|
217
|
-
liveKitConnection
|
|
218
|
-
setLiveKitConnection
|
|
206
|
+
connected?: boolean;
|
|
207
|
+
pending?: boolean;
|
|
208
|
+
liveKitToken?: string | undefined;
|
|
209
|
+
setLiveKitToken?: (state: string) => void;
|
|
210
|
+
liveKitRoomName?: string | undefined;
|
|
211
|
+
setLiveKitRoom?: (state: string) => void;
|
|
212
|
+
liveKitConnection?: boolean;
|
|
213
|
+
setLiveKitConnection?: (state: boolean) => void;
|
|
219
214
|
}) => {
|
|
220
215
|
// Auto-run Pretter after local changes.
|
|
221
216
|
const { prettierError, runPrettierRef } = usePrettier({
|
|
@@ -240,16 +235,6 @@ export const VZCodeProvider = ({
|
|
|
240
235
|
? prettierError
|
|
241
236
|
: codeError;
|
|
242
237
|
|
|
243
|
-
// Set up the TypeScript Language Server worker.
|
|
244
|
-
// This acts as a central "brain" for features powered
|
|
245
|
-
// by the TypeScript Language Server including:
|
|
246
|
-
// * Completions
|
|
247
|
-
// * Linting
|
|
248
|
-
useTypeScript({
|
|
249
|
-
content,
|
|
250
|
-
typeScriptWorker,
|
|
251
|
-
});
|
|
252
|
-
|
|
253
238
|
// Set up the reducer that manages much of the application state.
|
|
254
239
|
// See https://react.dev/reference/react/useReducer
|
|
255
240
|
const [state, dispatch] = useReducer(
|
|
@@ -473,8 +458,6 @@ export const VZCodeProvider = ({
|
|
|
473
458
|
|
|
474
459
|
errorMessage,
|
|
475
460
|
|
|
476
|
-
typeScriptWorker,
|
|
477
|
-
|
|
478
461
|
isCreateFileModalOpen,
|
|
479
462
|
handleOpenCreateFileModal,
|
|
480
463
|
handleCloseCreateFileModal,
|
package/src/client/VZMiddle.tsx
CHANGED
|
@@ -8,6 +8,10 @@ import { AIAssistWidget } from './AIAssist/AIAssistWidget';
|
|
|
8
8
|
import { VZCodeContext } from './VZCodeContext';
|
|
9
9
|
import { RunCodeWidget } from './RunCodeWidget';
|
|
10
10
|
import { InteractRule } from '@replit/codemirror-interact';
|
|
11
|
+
import { EditorView } from '@codemirror/view';
|
|
12
|
+
import { Diagnostic } from '@codemirror/lint';
|
|
13
|
+
import { VizContent } from '@vizhub/viz-types';
|
|
14
|
+
import { LeafPane } from '../types';
|
|
11
15
|
|
|
12
16
|
// TODO modify this to handle the SplitPane type
|
|
13
17
|
// Recursive structure?
|
|
@@ -17,13 +21,13 @@ const PaneView = ({
|
|
|
17
21
|
pane,
|
|
18
22
|
content,
|
|
19
23
|
customInteractRules,
|
|
20
|
-
allowGlobals,
|
|
21
24
|
enableAIAssist,
|
|
22
25
|
aiAssistEndpoint,
|
|
23
26
|
aiAssistOptions,
|
|
24
27
|
aiAssistTooltipText,
|
|
25
28
|
aiAssistClickOverride,
|
|
26
29
|
aiCopilotEndpoint,
|
|
30
|
+
esLintSource,
|
|
27
31
|
}) => {
|
|
28
32
|
// This prevents the CodeEditor from rendering
|
|
29
33
|
// during SSR.
|
|
@@ -49,8 +53,8 @@ const PaneView = ({
|
|
|
49
53
|
{isClient && content && pane.activeFileId && (
|
|
50
54
|
<CodeEditor
|
|
51
55
|
customInteractRules={customInteractRules}
|
|
52
|
-
allowGlobals={allowGlobals}
|
|
53
56
|
aiCopilotEndpoint={aiCopilotEndpoint}
|
|
57
|
+
esLintSource={esLintSource}
|
|
54
58
|
/>
|
|
55
59
|
)}
|
|
56
60
|
{isClient &&
|
|
@@ -86,7 +90,7 @@ export const VZMiddle = ({
|
|
|
86
90
|
aiAssistClickOverride,
|
|
87
91
|
aiCopilotEndpoint,
|
|
88
92
|
customInteractRules,
|
|
89
|
-
|
|
93
|
+
esLintSource,
|
|
90
94
|
}: {
|
|
91
95
|
enableAIAssist?: boolean;
|
|
92
96
|
aiAssistEndpoint?: string;
|
|
@@ -95,7 +99,9 @@ export const VZMiddle = ({
|
|
|
95
99
|
aiAssistClickOverride?: () => void;
|
|
96
100
|
aiCopilotEndpoint?: string;
|
|
97
101
|
customInteractRules?: Array<InteractRule>;
|
|
98
|
-
|
|
102
|
+
esLintSource: (
|
|
103
|
+
view: EditorView,
|
|
104
|
+
) => Promise<readonly Diagnostic[]>;
|
|
99
105
|
}) => {
|
|
100
106
|
const { codeEditorWidth } = useContext(
|
|
101
107
|
SplitPaneResizeContext,
|
|
@@ -126,13 +132,13 @@ export const VZMiddle = ({
|
|
|
126
132
|
pane={pane}
|
|
127
133
|
content={content}
|
|
128
134
|
customInteractRules={customInteractRules}
|
|
129
|
-
allowGlobals={allowGlobals}
|
|
130
135
|
enableAIAssist={enableAIAssist}
|
|
131
136
|
aiAssistEndpoint={aiAssistEndpoint}
|
|
132
137
|
aiAssistOptions={aiAssistOptions}
|
|
133
138
|
aiAssistTooltipText={aiAssistTooltipText}
|
|
134
139
|
aiAssistClickOverride={aiAssistClickOverride}
|
|
135
140
|
aiCopilotEndpoint={aiCopilotEndpoint}
|
|
141
|
+
esLintSource={esLintSource}
|
|
136
142
|
/>
|
|
137
143
|
<CodeErrorOverlay
|
|
138
144
|
errorMessage={errorMessage}
|
|
@@ -64,6 +64,7 @@ export const Search = () => {
|
|
|
64
64
|
setSearchFocusedIndex,
|
|
65
65
|
shareDBDoc,
|
|
66
66
|
editorCache,
|
|
67
|
+
openTab,
|
|
67
68
|
} = useContext(VZCodeContext);
|
|
68
69
|
const {
|
|
69
70
|
pattern,
|
|
@@ -227,6 +228,9 @@ export const Search = () => {
|
|
|
227
228
|
case 'Enter':
|
|
228
229
|
case ' ':
|
|
229
230
|
const fileId: VizFileId = files[focusedIndex][0];
|
|
231
|
+
|
|
232
|
+
openTab({ fileId, isTransient: true });
|
|
233
|
+
|
|
230
234
|
setActiveFileId(fileId);
|
|
231
235
|
|
|
232
236
|
if (focusedChildIndex !== null) {
|
|
@@ -441,6 +445,13 @@ export const Search = () => {
|
|
|
441
445
|
childIndex,
|
|
442
446
|
);
|
|
443
447
|
|
|
448
|
+
openTab({
|
|
449
|
+
fileId,
|
|
450
|
+
isTransient: true,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
setActiveFileId(fileId);
|
|
454
|
+
|
|
444
455
|
const cacheKey =
|
|
445
456
|
editorCacheKey(
|
|
446
457
|
fileId,
|
|
@@ -32,9 +32,9 @@ import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
|
|
|
32
32
|
import { VZCodeContext } from '../VZCodeContext';
|
|
33
33
|
import { Listing } from './Listing';
|
|
34
34
|
import { Search } from './Search';
|
|
35
|
-
import './styles.scss';
|
|
36
35
|
import { useDragAndDrop } from './useDragAndDrop';
|
|
37
36
|
import { enableLiveKit } from '../featureFlags';
|
|
37
|
+
import './styles.scss';
|
|
38
38
|
|
|
39
39
|
// TODO turn this UI back on when we are actually detecting
|
|
40
40
|
// the connection status.
|
|
@@ -252,10 +252,6 @@ export const VZSidebar = ({
|
|
|
252
252
|
<div
|
|
253
253
|
className="vz-sidebar"
|
|
254
254
|
style={{ width: sidebarWidth + 'px' }}
|
|
255
|
-
onDragEnter={handleDragEnter}
|
|
256
|
-
onDragOver={handleDragOver}
|
|
257
|
-
onDragLeave={handleDragLeave}
|
|
258
|
-
onDrop={handleDrop}
|
|
259
255
|
>
|
|
260
256
|
<div
|
|
261
257
|
className="full-box"
|
|
@@ -426,10 +422,6 @@ export const VZSidebar = ({
|
|
|
426
422
|
id="mic-icon"
|
|
427
423
|
className="icon-button icon-button-dark"
|
|
428
424
|
onClick={() => {
|
|
429
|
-
console.log(
|
|
430
|
-
'clicking',
|
|
431
|
-
liveKitConnection,
|
|
432
|
-
);
|
|
433
425
|
setVoiceChatModalOpen(true);
|
|
434
426
|
}}
|
|
435
427
|
>
|
|
@@ -438,11 +430,23 @@ export const VZSidebar = ({
|
|
|
438
430
|
</OverlayTrigger>
|
|
439
431
|
)}
|
|
440
432
|
</div>
|
|
441
|
-
<div
|
|
433
|
+
<div
|
|
434
|
+
className="files"
|
|
435
|
+
id="sidebar-view-container"
|
|
436
|
+
onDragEnter={handleDragEnter}
|
|
437
|
+
onDragOver={handleDragOver}
|
|
438
|
+
onDragLeave={handleDragLeave}
|
|
439
|
+
onDrop={handleDrop}
|
|
440
|
+
>
|
|
442
441
|
{!isSearchOpen ? (
|
|
443
442
|
<div className="sidebar-files">
|
|
444
443
|
{isDragOver ? (
|
|
445
|
-
<div className="empty">
|
|
444
|
+
<div className="empty drag-over">
|
|
445
|
+
<div className="empty-text">
|
|
446
|
+
It looks like you don't have any files
|
|
447
|
+
yet! Click the "Create file" button
|
|
448
|
+
above to create your first file.
|
|
449
|
+
</div>
|
|
446
450
|
<div className="empty-text">
|
|
447
451
|
Drop files here!
|
|
448
452
|
</div>
|
|
@@ -470,6 +474,9 @@ export const VZSidebar = ({
|
|
|
470
474
|
yet! Click the "Create file" button
|
|
471
475
|
above to create your first file.
|
|
472
476
|
</div>
|
|
477
|
+
<div className="empty-text">
|
|
478
|
+
Drop files here!
|
|
479
|
+
</div>
|
|
473
480
|
</div>
|
|
474
481
|
)}
|
|
475
482
|
</div>
|
|
@@ -228,21 +228,41 @@
|
|
|
228
228
|
flex: 1;
|
|
229
229
|
flex-direction: column;
|
|
230
230
|
align-items: center;
|
|
231
|
-
justify-content:
|
|
231
|
+
justify-content: center;
|
|
232
232
|
margin: 10px;
|
|
233
233
|
padding: 20px;
|
|
234
|
-
border: 1px dashed rgba(255, 255, 255, 0.5);
|
|
234
|
+
border: 1px dashed rgba(255, 255, 255, 0.5);
|
|
235
235
|
border-radius: 10px;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
* This fixes some issues where the drop zone for files
|
|
239
|
-
* was flickering when dragging files over the sidebar.
|
|
240
|
-
*/
|
|
236
|
+
min-height: 200px;
|
|
237
|
+
position: relative;
|
|
241
238
|
pointer-events: none;
|
|
242
239
|
|
|
243
240
|
.empty-text {
|
|
244
241
|
font-size: 16px;
|
|
245
242
|
font-weight: 500;
|
|
243
|
+
pointer-events: none;
|
|
244
|
+
position: absolute;
|
|
245
|
+
text-align: center;
|
|
246
|
+
left: 50%;
|
|
247
|
+
top: 50%;
|
|
248
|
+
transform: translate(-50%, -50%);
|
|
249
|
+
width: 80%;
|
|
250
|
+
opacity: 1;
|
|
251
|
+
transition: opacity 0.2s ease;
|
|
252
|
+
|
|
253
|
+
&:not(:first-child) {
|
|
254
|
+
opacity: 0;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
&.drag-over {
|
|
259
|
+
.empty-text {
|
|
260
|
+
opacity: 0;
|
|
261
|
+
|
|
262
|
+
&:last-child {
|
|
263
|
+
opacity: 1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
246
266
|
}
|
|
247
267
|
}
|
|
248
268
|
|
|
@@ -2,22 +2,42 @@ import { useState, useContext, useCallback } from 'react';
|
|
|
2
2
|
import { VZCodeContext } from '../VZCodeContext';
|
|
3
3
|
import './styles.scss';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
interface FileSystemEntry {
|
|
6
|
+
isFile: boolean;
|
|
7
|
+
isDirectory: boolean;
|
|
8
|
+
name: string;
|
|
9
|
+
createReader: () => FileSystemDirectoryReader;
|
|
10
|
+
file: (callback: (file: File) => void) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface FileSystemDirectoryReader {
|
|
14
|
+
readEntries: (
|
|
15
|
+
callback: (entries: FileSystemEntry[]) => void,
|
|
16
|
+
) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DEBUG = false;
|
|
6
20
|
|
|
7
21
|
export const useDragAndDrop = () => {
|
|
8
22
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
23
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
9
24
|
const { createFile } = useContext(VZCodeContext);
|
|
10
25
|
|
|
11
|
-
const handleDragEnter = useCallback(
|
|
12
|
-
event.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
const handleDragEnter = useCallback(
|
|
27
|
+
(event: React.DragEvent) => {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
event.stopPropagation();
|
|
30
|
+
DEBUG && console.log('[useDragAndDrop] Drag Enter');
|
|
31
|
+
setIsDragOver(true);
|
|
32
|
+
},
|
|
33
|
+
[],
|
|
34
|
+
);
|
|
16
35
|
|
|
17
36
|
const handleDragOver = useCallback(
|
|
18
|
-
(event) => {
|
|
37
|
+
(event: React.DragEvent) => {
|
|
19
38
|
event.preventDefault();
|
|
20
39
|
event.stopPropagation();
|
|
40
|
+
DEBUG && console.log('[useDragAndDrop] Drag Over');
|
|
21
41
|
if (!isDragOver) setIsDragOver(true);
|
|
22
42
|
},
|
|
23
43
|
[isDragOver],
|
|
@@ -27,52 +47,234 @@ export const useDragAndDrop = () => {
|
|
|
27
47
|
(event: React.DragEvent<HTMLDivElement>) => {
|
|
28
48
|
event.preventDefault();
|
|
29
49
|
event.stopPropagation();
|
|
50
|
+
DEBUG && console.log('[useDragAndDrop] Drag Leave');
|
|
30
51
|
setIsDragOver(false);
|
|
31
52
|
},
|
|
32
53
|
[],
|
|
33
54
|
);
|
|
34
55
|
|
|
56
|
+
const isTextFile = (file: File): boolean => {
|
|
57
|
+
// Add more text file types as needed
|
|
58
|
+
const textTypes = [
|
|
59
|
+
'text/',
|
|
60
|
+
'application/json',
|
|
61
|
+
'application/javascript',
|
|
62
|
+
'application/typescript',
|
|
63
|
+
'application/xml',
|
|
64
|
+
'application/x-httpd-php',
|
|
65
|
+
];
|
|
66
|
+
const isText =
|
|
67
|
+
textTypes.some((type) =>
|
|
68
|
+
file.type.startsWith(type),
|
|
69
|
+
) ||
|
|
70
|
+
file.name.match(
|
|
71
|
+
/\.(txt|js|jsx|ts|tsx|md|css|scss|html|xml|json|php|py|rb|java|c|cpp|h|hpp)$/i,
|
|
72
|
+
) !== null;
|
|
73
|
+
|
|
74
|
+
DEBUG &&
|
|
75
|
+
console.log(
|
|
76
|
+
`[useDragAndDrop] File ${file.name} is${isText ? '' : ' not'} a text file`,
|
|
77
|
+
);
|
|
78
|
+
return isText;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const readFileAsText = (file: File): Promise<string> => {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
DEBUG &&
|
|
84
|
+
console.log(
|
|
85
|
+
`[useDragAndDrop] Reading file: ${file.name}`,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (!isTextFile(file)) {
|
|
89
|
+
DEBUG &&
|
|
90
|
+
console.log(
|
|
91
|
+
`[useDragAndDrop] Rejected non-text file: ${file.name}`,
|
|
92
|
+
);
|
|
93
|
+
reject(
|
|
94
|
+
new Error(`File ${file.name} is not a text file`),
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const reader = new FileReader();
|
|
100
|
+
reader.onload = (e) => {
|
|
101
|
+
if (e.target?.result) {
|
|
102
|
+
DEBUG &&
|
|
103
|
+
console.log(
|
|
104
|
+
`[useDragAndDrop] Successfully read file: ${file.name}`,
|
|
105
|
+
);
|
|
106
|
+
resolve(e.target.result as string);
|
|
107
|
+
} else {
|
|
108
|
+
DEBUG &&
|
|
109
|
+
console.log(
|
|
110
|
+
`[useDragAndDrop] Failed to read file: ${file.name}`,
|
|
111
|
+
);
|
|
112
|
+
reject(
|
|
113
|
+
new Error(`Failed to read file ${file.name}`),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
reader.onerror = () => {
|
|
118
|
+
DEBUG &&
|
|
119
|
+
console.log(
|
|
120
|
+
`[useDragAndDrop] Error reading file: ${file.name}`,
|
|
121
|
+
);
|
|
122
|
+
reject(
|
|
123
|
+
new Error(`Error reading file ${file.name}`),
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
reader.readAsText(file);
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const processEntry = async (
|
|
131
|
+
entry: FileSystemEntry,
|
|
132
|
+
path: string,
|
|
133
|
+
): Promise<void> => {
|
|
134
|
+
try {
|
|
135
|
+
DEBUG &&
|
|
136
|
+
console.log(
|
|
137
|
+
`[useDragAndDrop] Processing entry: ${entry.name} at path: ${path}`,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (entry.isFile) {
|
|
141
|
+
DEBUG &&
|
|
142
|
+
console.log(
|
|
143
|
+
`[useDragAndDrop] Processing file: ${entry.name}`,
|
|
144
|
+
);
|
|
145
|
+
entry.file(async (file) => {
|
|
146
|
+
try {
|
|
147
|
+
const content = await readFileAsText(file);
|
|
148
|
+
DEBUG &&
|
|
149
|
+
console.log(
|
|
150
|
+
`[useDragAndDrop] Creating file: ${path}${file.name}`,
|
|
151
|
+
);
|
|
152
|
+
createFile(`${path}${file.name}`, content);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(
|
|
155
|
+
`Error processing file ${file.name}:`,
|
|
156
|
+
error,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
} else if (entry.isDirectory) {
|
|
161
|
+
DEBUG &&
|
|
162
|
+
console.log(
|
|
163
|
+
`[useDragAndDrop] Processing directory: ${entry.name}`,
|
|
164
|
+
);
|
|
165
|
+
return new Promise<void>((resolve) => {
|
|
166
|
+
const dirReader = entry.createReader();
|
|
167
|
+
const readEntries = () => {
|
|
168
|
+
dirReader.readEntries(async (entries) => {
|
|
169
|
+
if (entries.length === 0) {
|
|
170
|
+
DEBUG &&
|
|
171
|
+
console.log(
|
|
172
|
+
`[useDragAndDrop] Finished reading directory: ${entry.name}`,
|
|
173
|
+
);
|
|
174
|
+
resolve();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const newPath = `${path}${entry.name}/`;
|
|
179
|
+
DEBUG &&
|
|
180
|
+
console.log(
|
|
181
|
+
`[useDragAndDrop] Reading ${entries.length} entries in directory: ${newPath}`,
|
|
182
|
+
);
|
|
183
|
+
await Promise.all(
|
|
184
|
+
entries.map((entry) =>
|
|
185
|
+
processEntry(entry, newPath),
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
readEntries(); // Continue reading if there are more entries
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
readEntries();
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error(
|
|
196
|
+
`Error processing entry ${entry.name}:`,
|
|
197
|
+
error,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
35
202
|
const handleDrop = useCallback(
|
|
36
|
-
(event: React.DragEvent<HTMLDivElement>) => {
|
|
203
|
+
async (event: React.DragEvent<HTMLDivElement>) => {
|
|
37
204
|
event.preventDefault();
|
|
38
205
|
event.stopPropagation();
|
|
206
|
+
DEBUG &&
|
|
207
|
+
console.log('[useDragAndDrop] Drop event started');
|
|
39
208
|
setIsDragOver(false);
|
|
209
|
+
setIsProcessing(true);
|
|
40
210
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
211
|
+
try {
|
|
212
|
+
const items = event.dataTransfer.items;
|
|
213
|
+
DEBUG &&
|
|
214
|
+
console.log(
|
|
215
|
+
`[useDragAndDrop] Processing ${items.length} dropped items`,
|
|
216
|
+
);
|
|
217
|
+
const entries: FileSystemEntry[] = [];
|
|
218
|
+
|
|
219
|
+
for (const item of Array.from(items)) {
|
|
220
|
+
// Try webkit first, then fallback to other methods
|
|
221
|
+
const entry =
|
|
222
|
+
item.webkitGetAsEntry?.() ||
|
|
223
|
+
(item as any).getAsEntry?.() ||
|
|
224
|
+
(item as any).createEntry?.();
|
|
225
|
+
|
|
226
|
+
if (entry) {
|
|
227
|
+
DEBUG &&
|
|
228
|
+
console.log(
|
|
229
|
+
`[useDragAndDrop] Got entry for: ${entry.name}`,
|
|
230
|
+
);
|
|
231
|
+
entries.push(entry as FileSystemEntry);
|
|
232
|
+
} else if (item.kind === 'file') {
|
|
233
|
+
const file = item.getAsFile();
|
|
234
|
+
if (file) {
|
|
235
|
+
DEBUG &&
|
|
236
|
+
console.log(
|
|
237
|
+
`[useDragAndDrop] Processing dropped file: ${file.name}`,
|
|
238
|
+
);
|
|
239
|
+
try {
|
|
240
|
+
const content = await readFileAsText(file);
|
|
241
|
+
createFile(file.name, content);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(
|
|
244
|
+
`Error processing file ${file.name}:`,
|
|
245
|
+
error,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
59
248
|
}
|
|
60
|
-
}
|
|
249
|
+
}
|
|
61
250
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
251
|
+
|
|
252
|
+
DEBUG &&
|
|
253
|
+
console.log(
|
|
254
|
+
`[useDragAndDrop] Processing ${entries.length} entries`,
|
|
255
|
+
);
|
|
256
|
+
await Promise.all(
|
|
257
|
+
entries.map((entry) => processEntry(entry, '')),
|
|
258
|
+
);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error(
|
|
261
|
+
'Error processing dropped items:',
|
|
262
|
+
error,
|
|
263
|
+
);
|
|
264
|
+
} finally {
|
|
265
|
+
DEBUG &&
|
|
266
|
+
console.log(
|
|
267
|
+
'[useDragAndDrop] Drop processing completed',
|
|
268
|
+
);
|
|
269
|
+
setIsProcessing(false);
|
|
270
|
+
}
|
|
70
271
|
},
|
|
71
272
|
[createFile],
|
|
72
273
|
);
|
|
73
274
|
|
|
74
275
|
return {
|
|
75
276
|
isDragOver,
|
|
277
|
+
isProcessing,
|
|
76
278
|
handleDragEnter,
|
|
77
279
|
handleDragOver,
|
|
78
280
|
handleDragLeave,
|
package/src/client/main.jsx
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { EditorView } from '@codemirror/view';
|
|
3
|
+
import { Diagnostic } from '@codemirror/lint';
|
|
4
|
+
// @ts-ignore - Worker import
|
|
5
|
+
import ESLintWorker from './worker?worker';
|
|
6
|
+
import { enableESLint } from '../../server/featureFlags';
|
|
7
|
+
import { fileNameStateField } from '../CodeEditor/getOrCreateEditor';
|
|
8
|
+
|
|
9
|
+
let requestCounter = 0;
|
|
10
|
+
const pendingRequests = new Map();
|
|
11
|
+
|
|
12
|
+
export const useESLint = () => {
|
|
13
|
+
const [worker, setWorker] = useState(null);
|
|
14
|
+
|
|
15
|
+
// Initialize the work in an effect so it does not run during SSR.
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
setWorker(new ESLintWorker());
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
// Wait for the worker to be initialized
|
|
22
|
+
if (!worker) return;
|
|
23
|
+
|
|
24
|
+
const handleMessage = (event: MessageEvent) => {
|
|
25
|
+
const { diagnostics, requestId, error } = event.data;
|
|
26
|
+
|
|
27
|
+
if (error) {
|
|
28
|
+
console.error('[ESLint] Error:', error);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const resolve = pendingRequests.get(requestId);
|
|
32
|
+
if (resolve) {
|
|
33
|
+
resolve(diagnostics);
|
|
34
|
+
pendingRequests.delete(requestId);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
worker.addEventListener('message', handleMessage);
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
worker.removeEventListener('message', handleMessage);
|
|
42
|
+
worker.terminate();
|
|
43
|
+
};
|
|
44
|
+
}, [worker]);
|
|
45
|
+
|
|
46
|
+
const esLintSource = useCallback(
|
|
47
|
+
async (
|
|
48
|
+
view: EditorView,
|
|
49
|
+
): Promise<readonly Diagnostic[]> => {
|
|
50
|
+
if (!enableESLint) return [];
|
|
51
|
+
|
|
52
|
+
// Retrieve the file name from the editor's state
|
|
53
|
+
const fileName = view.state.field(fileNameStateField);
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const code = view.state.doc.toString();
|
|
57
|
+
const requestId = requestCounter++;
|
|
58
|
+
|
|
59
|
+
return new Promise<Diagnostic[]>((resolve) => {
|
|
60
|
+
pendingRequests.set(requestId, resolve);
|
|
61
|
+
worker.postMessage({ code, requestId, fileName });
|
|
62
|
+
});
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error('[ESLint] Error in linter:', e);
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
[worker],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return { esLintSource };
|
|
72
|
+
};
|