vzcode 0.28.0 → 0.29.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-c7f894a9.js +115 -0
- package/dist/assets/worker-a2979249.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 +251 -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 +104 -0
- package/src/client/{style.scss → App/style.scss} +14 -3
- package/src/client/App/useShareDB.ts +103 -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 +195 -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/useTypeScript/index.ts +44 -0
- package/src/client/useTypeScript/requestTypes.ts +29 -0
- package/src/client/useTypeScript/worker.ts +222 -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
package/dist/index.html
CHANGED
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
href="https://unpkg.com/boxicons@2.0.7/css/boxicons.min.css"
|
|
25
25
|
rel="stylesheet"
|
|
26
26
|
/>
|
|
27
|
-
<script type="module" crossorigin src="/assets/index-
|
|
28
|
-
<link rel="stylesheet" href="/assets/index-
|
|
27
|
+
<script type="module" crossorigin src="/assets/index-c7f894a9.js"></script>
|
|
28
|
+
<link rel="stylesheet" href="/assets/index-836bdce7.css">
|
|
29
29
|
</head>
|
|
30
30
|
<body>
|
|
31
31
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vzcode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Multiplayer code editor system",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -45,16 +45,19 @@
|
|
|
45
45
|
},
|
|
46
46
|
"homepage": "https://github.com/vizhub-core/vzcode#readme",
|
|
47
47
|
"dependencies": {
|
|
48
|
+
"@codemirror/autocomplete": "^6.11.0",
|
|
48
49
|
"@codemirror/lang-css": "^6.2.1",
|
|
49
50
|
"@codemirror/lang-html": "^6.4.6",
|
|
50
51
|
"@codemirror/lang-javascript": "^6.2.1",
|
|
52
|
+
"@codemirror/lang-json": "^6.0.1",
|
|
51
53
|
"@codemirror/lang-markdown": "^6.2.2",
|
|
52
54
|
"@codemirror/state": "^6.3.1",
|
|
53
55
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
54
|
-
"@codemirror/view": "^6.
|
|
56
|
+
"@codemirror/view": "^6.22.0",
|
|
55
57
|
"@lezer/highlight": "^1.1.6",
|
|
56
58
|
"@replit/codemirror-interact": "^6.3.0",
|
|
57
59
|
"@teamwork/websocket-json-stream": "^2.0.0",
|
|
60
|
+
"@typescript/vfs": "^1.5.0",
|
|
58
61
|
"@uiw/codemirror-theme-abcdef": "^4.21.20",
|
|
59
62
|
"@uiw/codemirror-theme-dracula": "^4.21.20",
|
|
60
63
|
"@uiw/codemirror-theme-eclipse": "^4.21.20",
|
|
@@ -67,6 +70,7 @@
|
|
|
67
70
|
"body-parser": "^1.20.2",
|
|
68
71
|
"codemirror": "^6.0.1",
|
|
69
72
|
"codemirror-ot": "^4.1.0",
|
|
73
|
+
"color-hash": "^2.0.2",
|
|
70
74
|
"d3-array": "^3.2.4",
|
|
71
75
|
"diff-match-patch": "^1.0.5",
|
|
72
76
|
"dotenv": "^16.3.1",
|
|
@@ -74,7 +78,7 @@
|
|
|
74
78
|
"json0-ot-diff": "^1.1.2",
|
|
75
79
|
"ngrok": "^4.3.3",
|
|
76
80
|
"open": "^9.1.0",
|
|
77
|
-
"openai": "^4.
|
|
81
|
+
"openai": "^4.17.4",
|
|
78
82
|
"ot-json1": "^1.0.2",
|
|
79
83
|
"react": "^18.2.0",
|
|
80
84
|
"react-bootstrap": "^2.9.1",
|
|
@@ -85,9 +89,9 @@
|
|
|
85
89
|
"ws": "^8.14.2"
|
|
86
90
|
},
|
|
87
91
|
"devDependencies": {
|
|
88
|
-
"@types/react": "^18.2.
|
|
89
|
-
"@types/react-dom": "^18.2.
|
|
90
|
-
"@vitejs/plugin-react": "^4.1.
|
|
92
|
+
"@types/react": "^18.2.37",
|
|
93
|
+
"@types/react-dom": "^18.2.15",
|
|
94
|
+
"@vitejs/plugin-react": "^4.1.1",
|
|
91
95
|
"prettier": "^3.0.3",
|
|
92
96
|
"sass": "^1.69.5",
|
|
93
97
|
"typescript": "^5.2.2",
|
package/src/client/AIAssist.ts
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { EditorView, keymap } from '@codemirror/view';
|
|
2
|
+
import {
|
|
3
|
+
FileId,
|
|
4
|
+
File,
|
|
5
|
+
ShareDBDoc,
|
|
6
|
+
VZCodeContent,
|
|
7
|
+
} from '../types';
|
|
8
|
+
import { generateRequestId } from './CodeEditor/typeScriptCompletions';
|
|
9
|
+
import { insertOp, replaceOp } from 'ot-json1';
|
|
10
|
+
|
|
11
|
+
import { TabState } from './vzReducer';
|
|
2
12
|
|
|
3
13
|
export const AIAssist = ({
|
|
14
|
+
shareDBDoc,
|
|
4
15
|
// The file id of the file the AI should assist with.
|
|
5
16
|
fileId,
|
|
17
|
+
tabList,
|
|
6
18
|
|
|
7
19
|
// Optional endpoint override.
|
|
8
20
|
aiAssistEndpoint = '/AIAssist',
|
|
@@ -10,37 +22,116 @@ export const AIAssist = ({
|
|
|
10
22
|
// Optional additional options to pass to the endpoint.
|
|
11
23
|
aiAssistOptions = {},
|
|
12
24
|
}: {
|
|
25
|
+
shareDBDoc: ShareDBDoc<VZCodeContent>;
|
|
13
26
|
fileId: string;
|
|
14
27
|
aiAssistEndpoint: string;
|
|
15
28
|
aiAssistOptions?: {
|
|
16
29
|
[key: string]: any;
|
|
17
30
|
};
|
|
31
|
+
tabList: Array<TabState>;
|
|
18
32
|
}) =>
|
|
19
33
|
keymap.of([
|
|
20
34
|
{
|
|
21
35
|
key: 'control-m',
|
|
22
36
|
run: (view: EditorView) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Pass additional options to the AI Assist endpoint.
|
|
35
|
-
...aiAssistOptions,
|
|
36
|
-
|
|
37
|
-
text: textToSend,
|
|
38
|
-
fileId,
|
|
39
|
-
cursorLocation: view.state.selection.main.to,
|
|
40
|
-
}),
|
|
41
|
-
});
|
|
37
|
+
if (
|
|
38
|
+
shareDBDoc.data.aiStreams === undefined ||
|
|
39
|
+
shareDBDoc.data.aiStreams[mostRecentStreamId] ===
|
|
40
|
+
null ||
|
|
41
|
+
shareDBDoc.data.aiStreams[mostRecentStreamId]
|
|
42
|
+
?.AIStreamStatus.serverIsRunning !== true
|
|
43
|
+
) {
|
|
44
|
+
startAIAssist(view, shareDBDoc, fileId, tabList);
|
|
45
|
+
} else {
|
|
46
|
+
haltAIAssist(shareDBDoc);
|
|
47
|
+
}
|
|
42
48
|
|
|
43
49
|
return true;
|
|
44
50
|
},
|
|
45
51
|
},
|
|
46
52
|
]);
|
|
53
|
+
|
|
54
|
+
// TODO use ref forwarding instead of `export let`
|
|
55
|
+
export let mostRecentStreamId = null;
|
|
56
|
+
|
|
57
|
+
export const startAIAssist = async (
|
|
58
|
+
view: EditorView,
|
|
59
|
+
shareDBDoc: ShareDBDoc<VZCodeContent>,
|
|
60
|
+
fileId: FileId,
|
|
61
|
+
tabList: Array<TabState>,
|
|
62
|
+
// TODO mostRecentStreamIdRef: React.MutableRefObject<string>,
|
|
63
|
+
) => {
|
|
64
|
+
const textToSend =
|
|
65
|
+
(await generateFilesContext(
|
|
66
|
+
tabList.map(
|
|
67
|
+
(tabState) =>
|
|
68
|
+
shareDBDoc.data.files[tabState.fileId],
|
|
69
|
+
),
|
|
70
|
+
)) +
|
|
71
|
+
'Current File:\n' +
|
|
72
|
+
view.state.sliceDoc(0, view.state.selection.main.to);
|
|
73
|
+
|
|
74
|
+
console.log(textToSend);
|
|
75
|
+
|
|
76
|
+
// TODO mostRecentStreamIdRef.current = generateRequestId();
|
|
77
|
+
mostRecentStreamId = generateRequestId();
|
|
78
|
+
|
|
79
|
+
if (shareDBDoc.data['aiStreams'] === undefined) {
|
|
80
|
+
shareDBDoc.submitOp(insertOp(['aiStreams'], {}), {
|
|
81
|
+
source: 'AIClient',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
shareDBDoc.submitOp(
|
|
86
|
+
insertOp(['aiStreams', mostRecentStreamId], {
|
|
87
|
+
AIStreamStatus: {
|
|
88
|
+
clientWantsToStart: true,
|
|
89
|
+
serverIsRunning: false,
|
|
90
|
+
text: textToSend,
|
|
91
|
+
insertionCursor: view.state.selection.main.to,
|
|
92
|
+
fileId: fileId,
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
{ source: 'AIClient' },
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const haltAIAssist = (
|
|
100
|
+
shareDBDoc: ShareDBDoc<VZCodeContent>,
|
|
101
|
+
) => {
|
|
102
|
+
const haltGenerationOp = replaceOp(
|
|
103
|
+
[
|
|
104
|
+
'aiStreams',
|
|
105
|
+
mostRecentStreamId,
|
|
106
|
+
'AIStreamStatus',
|
|
107
|
+
'clientWantsToStart',
|
|
108
|
+
],
|
|
109
|
+
true,
|
|
110
|
+
false,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
shareDBDoc.submitOp(haltGenerationOp, {
|
|
114
|
+
source: 'AIClient',
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const generateFilesContext = async (
|
|
119
|
+
goodFiles: File[],
|
|
120
|
+
): Promise<string> => {
|
|
121
|
+
const input = goodFiles
|
|
122
|
+
.map((file) => {
|
|
123
|
+
const nameSubstring = file.name
|
|
124
|
+
// ?.substring(0, maxFileNameLength)
|
|
125
|
+
.trim();
|
|
126
|
+
|
|
127
|
+
const textSubstring = file.text
|
|
128
|
+
// ?.substring(0, maxFileTextLength)
|
|
129
|
+
.trim();
|
|
130
|
+
|
|
131
|
+
// Generate Markdown that AI will understand.
|
|
132
|
+
return `File \`${nameSubstring}\`:\n\`\`\`${textSubstring}\`\`\``;
|
|
133
|
+
})
|
|
134
|
+
.join('\n\n');
|
|
135
|
+
|
|
136
|
+
return input;
|
|
137
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { Button } from '../bootstrap';
|
|
3
|
+
import './style.scss';
|
|
4
|
+
import {
|
|
5
|
+
mostRecentStreamId,
|
|
6
|
+
haltAIAssist,
|
|
7
|
+
startAIAssist,
|
|
8
|
+
} from '../AIAssist';
|
|
9
|
+
import {
|
|
10
|
+
FileId,
|
|
11
|
+
ShareDBDoc,
|
|
12
|
+
VZCodeContent,
|
|
13
|
+
} from '../../types';
|
|
14
|
+
import { EditorCache } from '../useEditorCache';
|
|
15
|
+
import { TabState } from '../vzReducer';
|
|
16
|
+
// From
|
|
17
|
+
// https://primer.style/foundations/icons/zap-24
|
|
18
|
+
const ZapSVG = () => (
|
|
19
|
+
<svg
|
|
20
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
21
|
+
viewBox="0 0 24 24"
|
|
22
|
+
width="24"
|
|
23
|
+
height="24"
|
|
24
|
+
>
|
|
25
|
+
<path d="M15.716 1.329a1.341 1.341 0 0 1 2.109 1.55L15.147 9h4.161c1.623 0 2.372 2.016 1.143 3.075L8.102 22.721a1.148 1.148 0 0 1-1.81-1.317L8.996 15H4.674c-1.619 0-2.37-2.008-1.148-3.07l12.19-10.6Zm.452 1.595L4.51 13.061a.25.25 0 0 0 .164.439h5.45a.749.749 0 0 1 .692 1.041l-2.559 6.066 11.215-9.668a.25.25 0 0 0-.164-.439H14a.75.75 0 0 1-.687-1.05Z"></path>
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// From Wikimedia
|
|
30
|
+
// https://commons.wikimedia.org/wiki/File:Stopsign.svg
|
|
31
|
+
const StopSVG = () => (
|
|
32
|
+
<svg
|
|
33
|
+
width="15pt"
|
|
34
|
+
height="15pt"
|
|
35
|
+
version="1.1"
|
|
36
|
+
viewBox="0 0 15 15"
|
|
37
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
38
|
+
>
|
|
39
|
+
<g transform="scale(.75)" fill="#fff" stroke="#000">
|
|
40
|
+
<path d="m17.5 0.5c1.1055 0 2 0.89453 2 2v15c0 1.1055-0.89453 2-2 2h-15c-1.1055 0-2-0.89453-2-2v-15c0-1.1055 0.89453-2 2-2z" />
|
|
41
|
+
</g>
|
|
42
|
+
</svg>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export const AIAssistWidget = ({
|
|
46
|
+
activeFileId,
|
|
47
|
+
shareDBDoc,
|
|
48
|
+
editorCache,
|
|
49
|
+
tabList,
|
|
50
|
+
}: {
|
|
51
|
+
activeFileId: FileId;
|
|
52
|
+
shareDBDoc: ShareDBDoc<VZCodeContent>;
|
|
53
|
+
editorCache: EditorCache;
|
|
54
|
+
tabList: Array<TabState>;
|
|
55
|
+
}) => {
|
|
56
|
+
const [AIAssistRunning, setAIAssistRunning] =
|
|
57
|
+
useState(false);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const handleOp = (op, source) => {
|
|
61
|
+
if (op !== null && op[0] === 'aiStreams') {
|
|
62
|
+
// The check if the value is true is required
|
|
63
|
+
// because the value could be null rather than false.
|
|
64
|
+
setAIAssistRunning(
|
|
65
|
+
shareDBDoc.data.aiStreams[mostRecentStreamId]
|
|
66
|
+
?.AIStreamStatus.serverIsRunning === true,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
shareDBDoc.on('op', handleOp);
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
shareDBDoc.off('op', handleOp);
|
|
74
|
+
};
|
|
75
|
+
}, [shareDBDoc]);
|
|
76
|
+
|
|
77
|
+
const handleClick = useCallback(() => {
|
|
78
|
+
if (!AIAssistRunning) {
|
|
79
|
+
startAIAssist(
|
|
80
|
+
editorCache.get(activeFileId).editor,
|
|
81
|
+
shareDBDoc,
|
|
82
|
+
activeFileId,
|
|
83
|
+
tabList,
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
haltAIAssist(shareDBDoc);
|
|
87
|
+
}
|
|
88
|
+
}, [activeFileId, AIAssistRunning]);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="vz-code-ai-assist-widget">
|
|
92
|
+
<Button
|
|
93
|
+
variant="light"
|
|
94
|
+
title="Start AI Assist"
|
|
95
|
+
onClick={handleClick}
|
|
96
|
+
>
|
|
97
|
+
{AIAssistRunning ? <StopSVG /> : <ZapSVG />}
|
|
98
|
+
</Button>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { createContext, useReducer } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Files,
|
|
4
|
+
ShareDBDoc,
|
|
5
|
+
Username,
|
|
6
|
+
VZCodeContent,
|
|
7
|
+
} from '../../types';
|
|
8
|
+
import { useSubmitOperation } from '../useSubmitOperation';
|
|
9
|
+
import { usePrettier } from '../usePrettier';
|
|
10
|
+
import { useTypeScript } from '../useTypeScript';
|
|
11
|
+
import {
|
|
12
|
+
TabState,
|
|
13
|
+
createInitialState,
|
|
14
|
+
vzReducer,
|
|
15
|
+
} from '../vzReducer';
|
|
16
|
+
import {
|
|
17
|
+
ThemeLabel,
|
|
18
|
+
defaultTheme,
|
|
19
|
+
useDynamicTheme,
|
|
20
|
+
} from '../themes';
|
|
21
|
+
import { useActions } from '../useActions';
|
|
22
|
+
import { useOpenDirectories } from '../useOpenDirectories';
|
|
23
|
+
import { useFileCRUD } from '../useFileCRUD';
|
|
24
|
+
import {
|
|
25
|
+
EditorCache,
|
|
26
|
+
useEditorCache,
|
|
27
|
+
} from '../useEditorCache';
|
|
28
|
+
|
|
29
|
+
// This context centralizes all the "smart" logic
|
|
30
|
+
// to do with the application state. This includes
|
|
31
|
+
// * Accessing and manipulating ShareDB data
|
|
32
|
+
// * Centralized application state
|
|
33
|
+
export const VZCodeContext =
|
|
34
|
+
createContext<VZCodeContextValue>(null);
|
|
35
|
+
|
|
36
|
+
// The type of the object provided by this context.
|
|
37
|
+
export type VZCodeContextValue = {
|
|
38
|
+
content: VZCodeContent | null;
|
|
39
|
+
shareDBDoc: ShareDBDoc<VZCodeContent> | null;
|
|
40
|
+
submitOperation: (
|
|
41
|
+
next: (content: VZCodeContent) => VZCodeContent,
|
|
42
|
+
) => void;
|
|
43
|
+
localPresence: any;
|
|
44
|
+
docPresence: any;
|
|
45
|
+
|
|
46
|
+
files: Files | null;
|
|
47
|
+
createFile: (fileName: string) => void;
|
|
48
|
+
renameFile: (fileId: string, fileName: string) => void;
|
|
49
|
+
deleteFile: (fileId: string) => void;
|
|
50
|
+
deleteDirectory: (directoryId: string) => void;
|
|
51
|
+
|
|
52
|
+
activeFileId: string | null;
|
|
53
|
+
setActiveFileId: (fileId: string | null) => void;
|
|
54
|
+
|
|
55
|
+
tabList: Array<TabState>;
|
|
56
|
+
openTab: ({
|
|
57
|
+
fileId,
|
|
58
|
+
isTransient,
|
|
59
|
+
}: {
|
|
60
|
+
fileId: string;
|
|
61
|
+
isTransient?: boolean;
|
|
62
|
+
}) => void;
|
|
63
|
+
closeTabs: (fileIds: string[]) => void;
|
|
64
|
+
|
|
65
|
+
isSettingsOpen: boolean;
|
|
66
|
+
setIsSettingsOpen: (isSettingsOpen: boolean) => void;
|
|
67
|
+
closeSettings: () => void;
|
|
68
|
+
|
|
69
|
+
theme: ThemeLabel;
|
|
70
|
+
setTheme: (theme: ThemeLabel) => void;
|
|
71
|
+
|
|
72
|
+
username: Username;
|
|
73
|
+
setUsername: (username: Username) => void;
|
|
74
|
+
|
|
75
|
+
isDirectoryOpen: (path: string) => boolean;
|
|
76
|
+
toggleDirectory: (path: string) => void;
|
|
77
|
+
|
|
78
|
+
editorCache: EditorCache;
|
|
79
|
+
editorWantsFocus: boolean;
|
|
80
|
+
editorNoLongerWantsFocus: () => void;
|
|
81
|
+
|
|
82
|
+
errorMessage: string | null;
|
|
83
|
+
|
|
84
|
+
typeScriptWorker: Worker | null;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const VZCodeProvider = ({
|
|
88
|
+
content,
|
|
89
|
+
shareDBDoc,
|
|
90
|
+
localPresence,
|
|
91
|
+
docPresence,
|
|
92
|
+
prettierWorker,
|
|
93
|
+
typeScriptWorker,
|
|
94
|
+
initialUsername,
|
|
95
|
+
children,
|
|
96
|
+
codeError = null,
|
|
97
|
+
}: {
|
|
98
|
+
content: VZCodeContent;
|
|
99
|
+
shareDBDoc: ShareDBDoc<VZCodeContent>;
|
|
100
|
+
localPresence: any;
|
|
101
|
+
docPresence: any;
|
|
102
|
+
prettierWorker: Worker;
|
|
103
|
+
typeScriptWorker: Worker;
|
|
104
|
+
initialUsername: Username;
|
|
105
|
+
children: React.ReactNode;
|
|
106
|
+
codeError?: string | null;
|
|
107
|
+
}) => {
|
|
108
|
+
// A convenience function for mutating the ShareDB document
|
|
109
|
+
// based submitting OT ops generated by diffing the JSON.
|
|
110
|
+
const submitOperation: (
|
|
111
|
+
next: (content: VZCodeContent) => VZCodeContent,
|
|
112
|
+
) => void = useSubmitOperation(shareDBDoc);
|
|
113
|
+
|
|
114
|
+
// Auto-run Pretter after local changes.
|
|
115
|
+
const {
|
|
116
|
+
prettierError,
|
|
117
|
+
}: {
|
|
118
|
+
prettierError: string | null;
|
|
119
|
+
} = usePrettier(
|
|
120
|
+
shareDBDoc,
|
|
121
|
+
submitOperation,
|
|
122
|
+
prettierWorker,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// The error message shows either:
|
|
126
|
+
// * `prettierError` - errors from Prettier, client-side only
|
|
127
|
+
// * `codeError` - errors from an external source, such as
|
|
128
|
+
// build-time errors or intercepted runtime errors.
|
|
129
|
+
// Since `prettierError` surfaces syntax errors, it's more likely to be
|
|
130
|
+
// useful to the user, so we prioritize it.
|
|
131
|
+
const errorMessage: string | null = prettierError
|
|
132
|
+
? prettierError
|
|
133
|
+
: codeError;
|
|
134
|
+
|
|
135
|
+
// Set up the TypeScript Language Server worker.
|
|
136
|
+
// This acts as a central "brain" for features powered
|
|
137
|
+
// by the TypeScript Language Server including:
|
|
138
|
+
// * Completions
|
|
139
|
+
// * Linting
|
|
140
|
+
useTypeScript({
|
|
141
|
+
content,
|
|
142
|
+
typeScriptWorker,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Set up the reducer that manages much of the application state.
|
|
146
|
+
// See https://react.dev/reference/react/useReducer
|
|
147
|
+
const [state, dispatch] = useReducer(
|
|
148
|
+
vzReducer,
|
|
149
|
+
{ defaultTheme, initialUsername },
|
|
150
|
+
createInitialState,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Unpack state.
|
|
154
|
+
const {
|
|
155
|
+
tabList,
|
|
156
|
+
activeFileId,
|
|
157
|
+
theme,
|
|
158
|
+
isSettingsOpen,
|
|
159
|
+
editorWantsFocus,
|
|
160
|
+
username,
|
|
161
|
+
} = state;
|
|
162
|
+
|
|
163
|
+
// Functions for dispatching actions to the reducer.
|
|
164
|
+
const {
|
|
165
|
+
setActiveFileId,
|
|
166
|
+
openTab,
|
|
167
|
+
closeTabs,
|
|
168
|
+
setTheme,
|
|
169
|
+
setIsSettingsOpen,
|
|
170
|
+
closeSettings,
|
|
171
|
+
editorNoLongerWantsFocus,
|
|
172
|
+
setUsername,
|
|
173
|
+
} = useActions(dispatch);
|
|
174
|
+
|
|
175
|
+
// The set of open directories.
|
|
176
|
+
// TODO move this into reducer/useActions
|
|
177
|
+
const { isDirectoryOpen, toggleDirectory } =
|
|
178
|
+
useOpenDirectories();
|
|
179
|
+
|
|
180
|
+
// Cache of CodeMirror editors by file id.
|
|
181
|
+
const editorCache: EditorCache = useEditorCache();
|
|
182
|
+
|
|
183
|
+
// Handle dynamic theme changes.
|
|
184
|
+
useDynamicTheme(editorCache, theme);
|
|
185
|
+
|
|
186
|
+
// Handle file CRUD operations (Create, Read, Update, Delete)
|
|
187
|
+
const {
|
|
188
|
+
createFile,
|
|
189
|
+
renameFile,
|
|
190
|
+
deleteFile,
|
|
191
|
+
deleteDirectory,
|
|
192
|
+
} = useFileCRUD({
|
|
193
|
+
submitOperation,
|
|
194
|
+
closeTabs,
|
|
195
|
+
openTab,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Isolate the files object from the document.
|
|
199
|
+
const files: Files | null = content
|
|
200
|
+
? content.files
|
|
201
|
+
: null;
|
|
202
|
+
|
|
203
|
+
// The value provided by this context.
|
|
204
|
+
const value: VZCodeContextValue = {
|
|
205
|
+
content,
|
|
206
|
+
shareDBDoc,
|
|
207
|
+
submitOperation,
|
|
208
|
+
localPresence,
|
|
209
|
+
docPresence,
|
|
210
|
+
|
|
211
|
+
files,
|
|
212
|
+
createFile,
|
|
213
|
+
renameFile,
|
|
214
|
+
deleteFile,
|
|
215
|
+
deleteDirectory,
|
|
216
|
+
|
|
217
|
+
activeFileId,
|
|
218
|
+
setActiveFileId,
|
|
219
|
+
|
|
220
|
+
tabList,
|
|
221
|
+
openTab,
|
|
222
|
+
closeTabs,
|
|
223
|
+
|
|
224
|
+
isSettingsOpen,
|
|
225
|
+
setIsSettingsOpen,
|
|
226
|
+
closeSettings,
|
|
227
|
+
|
|
228
|
+
theme,
|
|
229
|
+
setTheme,
|
|
230
|
+
|
|
231
|
+
username,
|
|
232
|
+
setUsername,
|
|
233
|
+
|
|
234
|
+
isDirectoryOpen,
|
|
235
|
+
toggleDirectory,
|
|
236
|
+
|
|
237
|
+
editorCache,
|
|
238
|
+
editorWantsFocus,
|
|
239
|
+
editorNoLongerWantsFocus,
|
|
240
|
+
|
|
241
|
+
errorMessage,
|
|
242
|
+
|
|
243
|
+
typeScriptWorker,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<VZCodeContext.Provider value={value}>
|
|
248
|
+
{children}
|
|
249
|
+
</VZCodeContext.Provider>
|
|
250
|
+
);
|
|
251
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { VZSettings } from '../VZSettings';
|
|
3
|
+
import { VZSidebar } from '../VZSidebar';
|
|
4
|
+
import { VZCodeContext } from './VZCodeContext';
|
|
5
|
+
|
|
6
|
+
// The middle portion of the VZCode environment, containing:
|
|
7
|
+
// * The sidebar
|
|
8
|
+
// * The settings modal
|
|
9
|
+
export const VZLeft = () => {
|
|
10
|
+
// TODO leverage this context in deeper levels of the component tree.
|
|
11
|
+
const {
|
|
12
|
+
files,
|
|
13
|
+
createFile,
|
|
14
|
+
renameFile,
|
|
15
|
+
deleteFile,
|
|
16
|
+
deleteDirectory,
|
|
17
|
+
activeFileId,
|
|
18
|
+
openTab,
|
|
19
|
+
closeTabs,
|
|
20
|
+
isSettingsOpen,
|
|
21
|
+
setIsSettingsOpen,
|
|
22
|
+
closeSettings,
|
|
23
|
+
theme,
|
|
24
|
+
setTheme,
|
|
25
|
+
username,
|
|
26
|
+
setUsername,
|
|
27
|
+
isDirectoryOpen,
|
|
28
|
+
toggleDirectory,
|
|
29
|
+
} = useContext(VZCodeContext);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="left">
|
|
33
|
+
<VZSidebar
|
|
34
|
+
files={files}
|
|
35
|
+
createFile={createFile}
|
|
36
|
+
renameFile={renameFile}
|
|
37
|
+
deleteFile={deleteFile}
|
|
38
|
+
deleteDirectory={deleteDirectory}
|
|
39
|
+
openTab={openTab}
|
|
40
|
+
closeTabs={closeTabs}
|
|
41
|
+
setIsSettingsOpen={setIsSettingsOpen}
|
|
42
|
+
isDirectoryOpen={isDirectoryOpen}
|
|
43
|
+
toggleDirectory={toggleDirectory}
|
|
44
|
+
activeFileId={activeFileId}
|
|
45
|
+
/>
|
|
46
|
+
<VZSettings
|
|
47
|
+
show={isSettingsOpen}
|
|
48
|
+
onClose={closeSettings}
|
|
49
|
+
theme={theme}
|
|
50
|
+
setTheme={setTheme}
|
|
51
|
+
username={username}
|
|
52
|
+
setUsername={setUsername}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
};
|