vzcode 2.3.0 → 2.5.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-DuUjtrt6.js → index-DB93Pffa.js} +84 -84
- package/dist/index.html +1 -1
- package/package.json +3 -2
- package/src/cli.ts +208 -0
- package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +42 -0
- package/src/client/AIAssist/AIAssistWidget/index.tsx +135 -0
- package/src/client/AIAssist/AIAssistWidget/style.scss +47 -0
- package/src/client/AIAssist/Spinner.jsx +76 -0
- package/src/client/AIAssist/formatFile.ts +27 -0
- package/src/client/AIAssist/generateFilesContext.ts +25 -0
- package/src/client/AIAssist/index.ts +2 -0
- package/src/client/AIAssist/startAIAssist.ts +95 -0
- package/src/client/App/index.tsx +152 -0
- package/src/client/App/style.scss +82 -0
- package/src/client/App/usePending.ts +51 -0
- package/src/client/App/useShareDB.ts +161 -0
- package/src/client/CodeEditor/Copilot/index.ts +74 -0
- package/src/client/CodeEditor/InteractiveWidgets/boolToggler.ts +12 -0
- package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +68 -0
- package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +174 -0
- package/src/client/CodeEditor/InteractiveWidgets/highlightWidgets.ts +79 -0
- package/src/client/CodeEditor/InteractiveWidgets/index.ts +44 -0
- package/src/client/CodeEditor/InteractiveWidgets/markdownCheckboxToggler.ts +12 -0
- package/src/client/CodeEditor/InteractiveWidgets/numberDragger.ts +20 -0
- package/src/client/CodeEditor/InteractiveWidgets/rotateWidget.ts +143 -0
- package/src/client/CodeEditor/InteractiveWidgets/urlClicker.test.ts +48 -0
- package/src/client/CodeEditor/InteractiveWidgets/urlClicker.ts +12 -0
- package/src/client/CodeEditor/InteractiveWidgets/vec2Slider.ts +25 -0
- package/src/client/CodeEditor/basicSetup.ts +73 -0
- package/src/client/CodeEditor/getOrCreateEditor.tsx +615 -0
- package/src/client/CodeEditor/index.tsx +202 -0
- package/src/client/CodeEditor/json1PresenceBroadcast.ts +41 -0
- package/src/client/CodeEditor/json1PresenceDisplay.ts +350 -0
- package/src/client/CodeEditor/rainbowBrackets.ts +122 -0
- package/src/client/CodeEditor/style.scss +156 -0
- package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
- package/src/client/CodeErrorOverlay/index.tsx +77 -0
- package/src/client/CodeErrorOverlay/style.scss +33 -0
- package/src/client/Fonts/fonts.ts +8 -0
- package/src/client/Icons/AdjustmentSVG.tsx +19 -0
- package/src/client/Icons/BugSVG.tsx +20 -0
- package/src/client/Icons/CloseSVG.tsx +19 -0
- package/src/client/Icons/CssSVG.tsx +16 -0
- package/src/client/Icons/DirectoryArrowSVG.tsx +19 -0
- package/src/client/Icons/EditSVG.tsx +19 -0
- package/src/client/Icons/FileSVG.tsx +19 -0
- package/src/client/Icons/FolderSVG.tsx +20 -0
- package/src/client/Icons/GearSVG.tsx +16 -0
- package/src/client/Icons/HtmlSVG.tsx +16 -0
- package/src/client/Icons/JavaScriptSVG.tsx +16 -0
- package/src/client/Icons/JsonSVG.tsx +16 -0
- package/src/client/Icons/MarkdownSVG.tsx +16 -0
- package/src/client/Icons/MicOffSVG.tsx +35 -0
- package/src/client/Icons/MicSVG.tsx +25 -0
- package/src/client/Icons/NewSVG.tsx +19 -0
- package/src/client/Icons/PinSVG.tsx +14 -0
- package/src/client/Icons/PlaySVG.tsx +16 -0
- package/src/client/Icons/QuestionMarkSVG.tsx +18 -0
- package/src/client/Icons/ReactSVG.tsx +16 -0
- package/src/client/Icons/SearchSVG.tsx +20 -0
- package/src/client/Icons/SparklesSVG.tsx +23 -0
- package/src/client/Icons/SplitEditorSVG.tsx +34 -0
- package/src/client/Icons/StopSVG.tsx +15 -0
- package/src/client/Icons/SvelteSVG.tsx +16 -0
- package/src/client/Icons/TrashSVG.tsx +19 -0
- package/src/client/Icons/TypeScriptSVG.tsx +16 -0
- package/src/client/Icons/index.tsx +26 -0
- package/src/client/ImageViewer/index.tsx +91 -0
- package/src/client/ImageViewer/style.scss +41 -0
- package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
- package/src/client/PresenceNotifications/index.tsx +158 -0
- package/src/client/PresenceNotifications/style.scss +17 -0
- package/src/client/RunCodeWidget/index.tsx +117 -0
- package/src/client/RunCodeWidget/style.scss +24 -0
- package/src/client/SplitPaneResizeContext.tsx +159 -0
- package/src/client/TabList/Tab.tsx +73 -0
- package/src/client/TabList/index.tsx +35 -0
- package/src/client/TabList/style.scss +51 -0
- package/src/client/VZCodeContext/index.tsx +29 -0
- package/src/client/VZCodeContext/types.ts +251 -0
- package/src/client/VZCodeContext/useVZCodeState.ts +606 -0
- package/src/client/VZKeyboardShortcutsDoc.tsx +148 -0
- package/src/client/VZLeft.tsx +28 -0
- package/src/client/VZMiddle.tsx +172 -0
- package/src/client/VZResizer/index.tsx +128 -0
- package/src/client/VZResizer/styles.scss +33 -0
- package/src/client/VZRight.tsx +113 -0
- package/src/client/VZSettings.tsx +275 -0
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +243 -0
- package/src/client/VZSidebar/AIChat/DiffView.scss +136 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +176 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +79 -0
- package/src/client/VZSidebar/AIChat/MessageList.tsx +190 -0
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +35 -0
- package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +156 -0
- package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +19 -0
- package/src/client/VZSidebar/AIChat/index.tsx +359 -0
- package/src/client/VZSidebar/AIChat/styles.scss +646 -0
- package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +117 -0
- package/src/client/VZSidebar/CreateDirModal.tsx +111 -0
- package/src/client/VZSidebar/CreateFileModal.tsx +111 -0
- package/src/client/VZSidebar/DeleteConfirmationModal.tsx +76 -0
- package/src/client/VZSidebar/DirectoryListing.tsx +89 -0
- package/src/client/VZSidebar/EmptyState.tsx +11 -0
- package/src/client/VZSidebar/FileListing.tsx +91 -0
- package/src/client/VZSidebar/FileTypeIcon.tsx +49 -0
- package/src/client/VZSidebar/Item.tsx +237 -0
- package/src/client/VZSidebar/Listing.tsx +47 -0
- package/src/client/VZSidebar/Search.tsx +527 -0
- package/src/client/VZSidebar/VisualEditor.tsx +282 -0
- package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
- package/src/client/VZSidebar/aiCopyPaste.ts +199 -0
- package/src/client/VZSidebar/index.tsx +728 -0
- package/src/client/VZSidebar/styles.scss +612 -0
- package/src/client/VZSidebar/useDragAndDrop.tsx +408 -0
- package/src/client/bootstrap.ts +26 -0
- package/src/client/buildWorker.ts +3 -0
- package/src/client/featureFlags.ts +8 -0
- package/src/client/generateRequestId.ts +6 -0
- package/src/client/getFileTree.ts +53 -0
- package/src/client/index.ts +19 -0
- package/src/client/main.jsx +10 -0
- package/src/client/presenceColor.ts +8 -0
- package/src/client/shouldTriggerRun.ts +31 -0
- package/src/client/sortFileTree.ts +21 -0
- package/src/client/tabsSearchParameters.test.ts +79 -0
- package/src/client/tabsSearchParameters.ts +137 -0
- package/src/client/themes/index.ts +7 -0
- package/src/client/themes/themes.ts +77 -0
- package/src/client/themes/uiwImports.ts +32 -0
- package/src/client/themes/useDynamicTheme.ts +29 -0
- package/src/client/themes/vizhubTheme/colors.ts +80 -0
- package/src/client/themes/vizhubTheme/index.ts +193 -0
- package/src/client/useActions.ts +288 -0
- package/src/client/useESLint/index.ts +72 -0
- package/src/client/useESLint/worker.ts +113 -0
- package/src/client/useEditorCache.ts +35 -0
- package/src/client/useFileCRUD.ts +228 -0
- package/src/client/useKeyboardShortcuts.ts +451 -0
- package/src/client/useOpenDirectories.ts +99 -0
- package/src/client/usePresenceAutoFollow.ts +85 -0
- package/src/client/usePrettier/index.ts +178 -0
- package/src/client/usePrettier/worker.ts +109 -0
- package/src/client/useRunCode.tsx +15 -0
- package/src/client/useRuntimeError.ts +32 -0
- package/src/client/useSubmitOperation.ts +15 -0
- package/src/client/useURLSync.ts +137 -0
- package/src/client/usernameLocalStorage.ts +43 -0
- package/src/client/utils/fileExtension.ts +10 -0
- package/src/client/utils/isImageFile.ts +12 -0
- package/src/client/vzReducer/aiChatReducer.ts +44 -0
- package/src/client/vzReducer/closeTabReducer.test.ts +205 -0
- package/src/client/vzReducer/closeTabsReducer.ts +96 -0
- package/src/client/vzReducer/createInitialState.ts +38 -0
- package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
- package/src/client/vzReducer/findPane.ts +19 -0
- package/src/client/vzReducer/index.ts +250 -0
- package/src/client/vzReducer/openTabReducer.test.ts +127 -0
- package/src/client/vzReducer/openTabReducer.ts +74 -0
- package/src/client/vzReducer/searchReducer.test.ts +292 -0
- package/src/client/vzReducer/searchReducer.ts +181 -0
- package/src/client/vzReducer/setActiveFileIdReducer.ts +28 -0
- package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +76 -0
- package/src/client/vzReducer/setIsDocOpenReducer.ts +9 -0
- package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
- package/src/client/vzReducer/setThemeReducer.ts +9 -0
- package/src/client/vzReducer/setUsernameReducer.ts +9 -0
- package/src/client/vzReducer/splitCurrentPaneReducer.ts +66 -0
- package/src/client/vzReducer/toggleAutoFollowReducer.ts +12 -0
- package/src/client/vzReducer/updatePane.ts +40 -0
- package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +30 -0
- package/src/client/vzReducer/visualEditorReducer.ts +9 -0
- package/src/ot.ts +24 -0
- package/src/randomId.ts +15 -0
- package/src/reactRouterExports.ts +15 -0
- package/src/runCode.ts +27 -0
- package/src/server/aiChatHandler/aiEditing.ts +151 -0
- package/src/server/aiChatHandler/chatOperations.ts +476 -0
- package/src/server/aiChatHandler/errorHandling.ts +66 -0
- package/src/server/aiChatHandler/index.ts +199 -0
- package/src/server/aiChatHandler/llmStreaming.ts +419 -0
- package/src/server/aiChatHandler/validation.ts +24 -0
- package/src/server/computeInitialDocument.ts +220 -0
- package/src/server/config.ts +24 -0
- package/src/server/featureFlags.ts +8 -0
- package/src/server/generateAIResponse.ts +188 -0
- package/src/server/handleAIAssist.ts +53 -0
- package/src/server/handleAIChatMessage.ts +2 -0
- package/src/server/handleAICopilot.ts +97 -0
- package/src/server/index.ts +379 -0
- package/src/server/isDirectory.ts +1 -0
- package/src/server/livekit.ts +24 -0
- package/src/server/prettier.ts +115 -0
- package/src/server/setupEnv.ts +8 -0
- package/src/submitOperation.ts +25 -0
- package/src/types.ts +205 -0
- package/src/utils/fileDiff.ts +117 -0
- package/src/vite-env.d.ts +10 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { useContext, useState } from 'react';
|
|
2
|
+
import ShareDBClient from 'sharedb-client-browser/dist/sharedb-client-umd.cjs';
|
|
3
|
+
import { json1Presence } from '../../ot';
|
|
4
|
+
import { Username } from '../../types';
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import PrettierWorker from '../usePrettier/worker?worker';
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
import {
|
|
9
|
+
LiveKitRoom,
|
|
10
|
+
RoomAudioRenderer,
|
|
11
|
+
} from '@livekit/components-react';
|
|
12
|
+
import { v4 } from 'uuid';
|
|
13
|
+
import { SplitPaneResizeProvider } from '../SplitPaneResizeContext';
|
|
14
|
+
import {
|
|
15
|
+
VZCodeContext,
|
|
16
|
+
VZCodeProvider,
|
|
17
|
+
} from '../VZCodeContext';
|
|
18
|
+
import { VZLeft } from '../VZLeft';
|
|
19
|
+
import { VZMiddle } from '../VZMiddle';
|
|
20
|
+
import { VZResizer } from '../VZResizer';
|
|
21
|
+
import { VZRight } from '../VZRight';
|
|
22
|
+
import {
|
|
23
|
+
useInitialUsername,
|
|
24
|
+
usePersistUsername,
|
|
25
|
+
} from '../usernameLocalStorage';
|
|
26
|
+
import './style.scss';
|
|
27
|
+
import { useShareDB } from './useShareDB';
|
|
28
|
+
import { useESLint } from '../useESLint';
|
|
29
|
+
|
|
30
|
+
// Instantiate the Prettier worker.
|
|
31
|
+
const prettierWorker = new PrettierWorker();
|
|
32
|
+
|
|
33
|
+
// Register our custom JSON1 OT type that supports presence.
|
|
34
|
+
// See https://github.com/vizhub-core/json1-presence
|
|
35
|
+
ShareDBClient.types.register(json1Presence.type);
|
|
36
|
+
|
|
37
|
+
// Establish the singleton ShareDB connection over WebSockets.
|
|
38
|
+
// TODO consider using reconnecting WebSocket
|
|
39
|
+
const { Connection } = ShareDBClient;
|
|
40
|
+
const wsProtocol =
|
|
41
|
+
window.location.protocol === 'https:'
|
|
42
|
+
? 'wss://'
|
|
43
|
+
: 'ws://';
|
|
44
|
+
const socket = new WebSocket(
|
|
45
|
+
wsProtocol + window.location.host + '/ws',
|
|
46
|
+
);
|
|
47
|
+
const connection = new Connection(socket);
|
|
48
|
+
|
|
49
|
+
// Stores the username to local storage.
|
|
50
|
+
// TODO consider if there's a cleaner pattern for this.
|
|
51
|
+
// This makes sense in VZCode itself, but for an app with
|
|
52
|
+
// authentication that wraps VZCode, it is not required.
|
|
53
|
+
const PersistUsername = () => {
|
|
54
|
+
const { username } = useContext(VZCodeContext);
|
|
55
|
+
usePersistUsername(username);
|
|
56
|
+
return null;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function App() {
|
|
60
|
+
const {
|
|
61
|
+
shareDBDoc,
|
|
62
|
+
content,
|
|
63
|
+
localPresence,
|
|
64
|
+
docPresence,
|
|
65
|
+
submitOperation,
|
|
66
|
+
connected,
|
|
67
|
+
pending,
|
|
68
|
+
} = useShareDB({
|
|
69
|
+
connection,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
//state for the voice channel
|
|
73
|
+
const [liveKitToken, setLiveKitToken] =
|
|
74
|
+
useState(undefined);
|
|
75
|
+
const [liveKitRoomName, setLiveKitRoomName] =
|
|
76
|
+
useState(v4()); //default room name will be an arbitrary uuid
|
|
77
|
+
const [liveKitConnection, setLiveKitConnection] =
|
|
78
|
+
useState(false);
|
|
79
|
+
const { esLintSource } = useESLint();
|
|
80
|
+
// Get the initial username from localStorage.
|
|
81
|
+
const initialUsername: Username = useInitialUsername();
|
|
82
|
+
|
|
83
|
+
//@ts-ignore
|
|
84
|
+
const serverUrl = import.meta.env.VITE_LIVEKIT_URL; //Vite only parses VITE_ prefixed environment variables
|
|
85
|
+
|
|
86
|
+
// Feature flag for enabling the right-side panel.
|
|
87
|
+
// Useful for debugging dual split pane functionality.
|
|
88
|
+
// We may want to add this as an actual VZCode feature,
|
|
89
|
+
// for running the code that the VZCode user is developing
|
|
90
|
+
// in the same browser window as the VZCode editor,
|
|
91
|
+
// so that multiple browser windows are not required.
|
|
92
|
+
const enableRightPanel = true;
|
|
93
|
+
|
|
94
|
+
// TODO: enable this when the AI copilot is ready
|
|
95
|
+
// Currently it's glitchy and frustrating to use.
|
|
96
|
+
// It appears to be bugs in the upstream CodeMirror copilot code.
|
|
97
|
+
// Ghost text appears after just moving the cursor, not typing.
|
|
98
|
+
// It's hard to figure out how to dismiss it.
|
|
99
|
+
// Clicking on it accepts it but should not.
|
|
100
|
+
const enableCopilot = false;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<SplitPaneResizeProvider>
|
|
104
|
+
<VZCodeProvider
|
|
105
|
+
content={content}
|
|
106
|
+
shareDBDoc={shareDBDoc}
|
|
107
|
+
submitOperation={submitOperation}
|
|
108
|
+
localPresence={localPresence}
|
|
109
|
+
docPresence={docPresence}
|
|
110
|
+
prettierWorker={prettierWorker}
|
|
111
|
+
initialUsername={initialUsername}
|
|
112
|
+
connected={connected}
|
|
113
|
+
pending={pending}
|
|
114
|
+
liveKitRoomName={liveKitRoomName}
|
|
115
|
+
setLiveKitRoom={setLiveKitRoomName}
|
|
116
|
+
liveKitToken={liveKitToken}
|
|
117
|
+
setLiveKitToken={setLiveKitToken}
|
|
118
|
+
liveKitConnection={liveKitConnection}
|
|
119
|
+
setLiveKitConnection={setLiveKitConnection}
|
|
120
|
+
aiChatEndpoint="/ai-chat-message"
|
|
121
|
+
aiChatOptions={{}}
|
|
122
|
+
>
|
|
123
|
+
<LiveKitRoom
|
|
124
|
+
audio={true}
|
|
125
|
+
token={liveKitToken}
|
|
126
|
+
serverUrl={serverUrl}
|
|
127
|
+
connect={liveKitConnection}
|
|
128
|
+
style={{ height: '100%' }}
|
|
129
|
+
>
|
|
130
|
+
<div className="app">
|
|
131
|
+
<VZLeft />
|
|
132
|
+
<VZMiddle
|
|
133
|
+
aiCopilotEndpoint={
|
|
134
|
+
enableCopilot ? '/ai-copilot' : null
|
|
135
|
+
}
|
|
136
|
+
esLintSource={esLintSource}
|
|
137
|
+
/>
|
|
138
|
+
{enableRightPanel ? <VZRight /> : null}
|
|
139
|
+
<VZResizer side="left" />
|
|
140
|
+
{enableRightPanel ? (
|
|
141
|
+
<VZResizer side="right" />
|
|
142
|
+
) : null}
|
|
143
|
+
</div>
|
|
144
|
+
<PersistUsername />
|
|
145
|
+
<RoomAudioRenderer />
|
|
146
|
+
</LiveKitRoom>
|
|
147
|
+
</VZCodeProvider>
|
|
148
|
+
</SplitPaneResizeProvider>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default App;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap');
|
|
2
|
+
@import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@200..900&display=swap');
|
|
3
|
+
@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap');
|
|
4
|
+
@import url('https://fonts.googleapis.com/css2?family=Ubuntu+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap');
|
|
5
|
+
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap');
|
|
6
|
+
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&display=swap');
|
|
7
|
+
:root {
|
|
8
|
+
// --vzcode-font-family: ui-monospace, SFMono-Regular,
|
|
9
|
+
// 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace !important;
|
|
10
|
+
|
|
11
|
+
--vzcode-font-family: 'Roboto Mono', monospace !important;
|
|
12
|
+
--vzcode-font-size: 16px;
|
|
13
|
+
|
|
14
|
+
/* Variable for the background color */
|
|
15
|
+
--vz-bg-color: rgb(
|
|
16
|
+
41,
|
|
17
|
+
44,
|
|
18
|
+
52
|
|
19
|
+
); /* Variable for the background color */
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Scrollbar Styles */
|
|
23
|
+
::-webkit-scrollbar {
|
|
24
|
+
width: 16px;
|
|
25
|
+
height: 16px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ::-webkit-scrollbar-track {
|
|
29
|
+
// background: rgb(255, 255, 255, 0.1);
|
|
30
|
+
// }
|
|
31
|
+
|
|
32
|
+
::-webkit-scrollbar-thumb {
|
|
33
|
+
background: rgb(255, 255, 255, 0.3);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
::-webkit-scrollbar-thumb:hover {
|
|
37
|
+
background: #555;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Layout Styles */
|
|
41
|
+
html,
|
|
42
|
+
body,
|
|
43
|
+
#root,
|
|
44
|
+
.app {
|
|
45
|
+
height: 100%;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.app {
|
|
49
|
+
background-color: var(--vz-bg-color);
|
|
50
|
+
// font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
51
|
+
// 'Noto Sans', Helvetica, Arial, sans-serif,
|
|
52
|
+
// 'Apple Color Emoji', 'Segoe UI Emoji';
|
|
53
|
+
display: flex;
|
|
54
|
+
|
|
55
|
+
/* To support Resizer, which is position: absolute */
|
|
56
|
+
position: relative;
|
|
57
|
+
|
|
58
|
+
.left,
|
|
59
|
+
.middle {
|
|
60
|
+
display: flex;
|
|
61
|
+
|
|
62
|
+
/* To suport PrettierErrorOverlay, which is position: absolute */
|
|
63
|
+
position: relative;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.middle {
|
|
67
|
+
// flex: 1;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
min-width: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.right {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex: 1;
|
|
75
|
+
background-color: white;
|
|
76
|
+
|
|
77
|
+
iframe {
|
|
78
|
+
flex: 1;
|
|
79
|
+
border: none;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect,
|
|
4
|
+
useCallback,
|
|
5
|
+
useRef,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import { ShareDBDoc } from '../../types';
|
|
8
|
+
import { VizContent } from '@vizhub/viz-types';
|
|
9
|
+
|
|
10
|
+
// import { runDelay } from '../constants';
|
|
11
|
+
// TODO understand what this part is for
|
|
12
|
+
const runDelay = 1000;
|
|
13
|
+
|
|
14
|
+
export const usePending = (
|
|
15
|
+
shareDBDoc: ShareDBDoc<VizContent>,
|
|
16
|
+
) => {
|
|
17
|
+
const lastOpTimerRef = useRef<NodeJS.Timeout>();
|
|
18
|
+
|
|
19
|
+
// TODO rename this to isPending,
|
|
20
|
+
// and also rename connected to isConnected.
|
|
21
|
+
const [pending, setPending] = useState<boolean>(false);
|
|
22
|
+
|
|
23
|
+
const resetOnNoPending = useCallback(() => {
|
|
24
|
+
if (!shareDBDoc) return;
|
|
25
|
+
|
|
26
|
+
// This happens AFTER the changes have been
|
|
27
|
+
// acknowledged by the server.
|
|
28
|
+
shareDBDoc.whenNothingPending(() => {
|
|
29
|
+
setPending(false);
|
|
30
|
+
});
|
|
31
|
+
}, [shareDBDoc, setPending]);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!shareDBDoc) return;
|
|
35
|
+
|
|
36
|
+
// This happens when you type, but BEFORE
|
|
37
|
+
// the changes are sent to the server.
|
|
38
|
+
shareDBDoc.on('before op', () => {
|
|
39
|
+
setPending(true);
|
|
40
|
+
|
|
41
|
+
clearTimeout(lastOpTimerRef.current);
|
|
42
|
+
|
|
43
|
+
lastOpTimerRef.current = setTimeout(() => {
|
|
44
|
+
// should be triggered only once, cause upcoming ops unschedule previous resetOnNoPending
|
|
45
|
+
resetOnNoPending();
|
|
46
|
+
}, runDelay);
|
|
47
|
+
});
|
|
48
|
+
}, [shareDBDoc, resetOnNoPending]);
|
|
49
|
+
|
|
50
|
+
return pending;
|
|
51
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { useEffect, useState, useRef } from 'react';
|
|
2
|
+
import { randomId } from '../../randomId';
|
|
3
|
+
import { ShareDBDoc } from '../../types';
|
|
4
|
+
import { VizContent } from '@vizhub/viz-types';
|
|
5
|
+
import { useSubmitOperation } from '../useSubmitOperation';
|
|
6
|
+
import { usePending } from './usePending';
|
|
7
|
+
|
|
8
|
+
// Set up the connection to ShareDB.
|
|
9
|
+
export const useShareDB = ({
|
|
10
|
+
connection,
|
|
11
|
+
}: {
|
|
12
|
+
connection: any;
|
|
13
|
+
}) => {
|
|
14
|
+
// The ShareDB document.
|
|
15
|
+
const [shareDBDoc, setShareDBDoc] =
|
|
16
|
+
useState<ShareDBDoc<VizContent> | null>(null);
|
|
17
|
+
|
|
18
|
+
// Local ShareDB presence, for broadcasting our cursor position
|
|
19
|
+
// so other clients can see it.
|
|
20
|
+
// See https://share.github.io/sharedb/api/local-presence
|
|
21
|
+
const [localPresence, setLocalPresence] = useState(null);
|
|
22
|
+
|
|
23
|
+
// The document-level presence object, which emits
|
|
24
|
+
// changes in remote presence.
|
|
25
|
+
const [docPresence, setDocPresence] = useState(null);
|
|
26
|
+
|
|
27
|
+
// The `doc.data` part of the ShareDB document,
|
|
28
|
+
// updated on each change to decouple rendering from ShareDB.
|
|
29
|
+
// Starts out as `null` until the document is loaded.
|
|
30
|
+
const [content, setContent] = useState<VizContent | null>(
|
|
31
|
+
null,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const [connected, setConnected] =
|
|
35
|
+
useState<boolean>(false);
|
|
36
|
+
|
|
37
|
+
// const [isSaving, setIsSaving] = useState<boolean>(false);
|
|
38
|
+
// const [isSaved, setIsSaved] = useState<boolean>(false);
|
|
39
|
+
|
|
40
|
+
// const debounceTimeoutRef = useRef<ReturnType<
|
|
41
|
+
// typeof setTimeout
|
|
42
|
+
// > | null>(null);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
// Listen for connection state changes
|
|
46
|
+
connection.on('connected', () => {
|
|
47
|
+
setConnected(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
connection.on('disconnected', () => {
|
|
51
|
+
setConnected(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Since there is only ever a single document,
|
|
55
|
+
// these things are pretty arbitrary.
|
|
56
|
+
// * `collection` - the ShareDB collection to use
|
|
57
|
+
// * `id` - the id of the ShareDB document to use
|
|
58
|
+
const collection = 'documents';
|
|
59
|
+
const id = '1';
|
|
60
|
+
|
|
61
|
+
// Initialize the ShareDB document.
|
|
62
|
+
const shareDBDoc = connection.get(collection, id);
|
|
63
|
+
|
|
64
|
+
// Subscribe to the document to get updates.
|
|
65
|
+
// This callback gets called once only.
|
|
66
|
+
shareDBDoc.subscribe(() => {
|
|
67
|
+
// Expose ShareDB doc to downstream logic.
|
|
68
|
+
setShareDBDoc(shareDBDoc);
|
|
69
|
+
|
|
70
|
+
// Set initial data.
|
|
71
|
+
setContent(shareDBDoc.data);
|
|
72
|
+
|
|
73
|
+
// Listen for all changes and update `data`.
|
|
74
|
+
// This decouples rendering logic from ShareDB.
|
|
75
|
+
// This callback gets called on each change.
|
|
76
|
+
shareDBDoc.on('op', () => {
|
|
77
|
+
// TODO consider excluding file contents from this,
|
|
78
|
+
// because currently we are re-rendering the entire
|
|
79
|
+
// document on each file change (each keystroke while editing).
|
|
80
|
+
// This is not required, because no part of the app outside
|
|
81
|
+
// of the CodeMirror integration uses those file contents.
|
|
82
|
+
// The Sidebar only needs to know file names, not contents.
|
|
83
|
+
// Start saving process when there’s an operation.
|
|
84
|
+
// setIsSaving(true);
|
|
85
|
+
// setIsSaved(false);
|
|
86
|
+
|
|
87
|
+
setContent(shareDBDoc.data);
|
|
88
|
+
|
|
89
|
+
// // Simulate a saving process and switch status to "saved"
|
|
90
|
+
// if (debounceTimeoutRef.current) {
|
|
91
|
+
// clearTimeout(debounceTimeoutRef.current);
|
|
92
|
+
// }
|
|
93
|
+
|
|
94
|
+
// // Set up a debounce timer to switch from "saving" to "saved" after 2 seconds of inactivity.
|
|
95
|
+
// debounceTimeoutRef.current = setTimeout(() => {
|
|
96
|
+
// setIsSaving(false);
|
|
97
|
+
// setIsSaved(true); // Only set isSaved to true after 2 seconds of inactivity
|
|
98
|
+
|
|
99
|
+
// const resetSavedTimeout = setTimeout(() => {
|
|
100
|
+
// setIsSaved(false);
|
|
101
|
+
// }, 1000); // 1 second delay after `isSaved` becomes true.
|
|
102
|
+
|
|
103
|
+
// return () => clearTimeout(resetSavedTimeout);
|
|
104
|
+
// }, 1800); // 1.8-second delay for detecting inactivity
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Set up presence.
|
|
108
|
+
// See https://github.com/share/sharedb/blob/master/examples/rich-text-presence/client.js#L53
|
|
109
|
+
const docPresence = connection.getDocPresence(
|
|
110
|
+
collection,
|
|
111
|
+
id,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Subscribe to receive remote presence updates.
|
|
115
|
+
docPresence.subscribe(function (error) {
|
|
116
|
+
if (error) throw error;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Set up our local presence for broadcasting this client's presence.
|
|
120
|
+
const generateTimestampedId = () => {
|
|
121
|
+
const timestamp = Date.now().toString(36);
|
|
122
|
+
const randomPart = randomId();
|
|
123
|
+
return `${timestamp}-${randomPart}`;
|
|
124
|
+
};
|
|
125
|
+
setLocalPresence(
|
|
126
|
+
docPresence.create(generateTimestampedId()),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Store docPresence so child components can listen for changes.
|
|
130
|
+
setDocPresence(docPresence);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// TODO unsubscribe from presence
|
|
134
|
+
// TODO unsubscribe from doc
|
|
135
|
+
return () => {
|
|
136
|
+
// if (debounceTimeoutRef.current)
|
|
137
|
+
// clearTimeout(debounceTimeoutRef.current);
|
|
138
|
+
// shareDBDoc.destroy();
|
|
139
|
+
// docPresence.destroy();
|
|
140
|
+
};
|
|
141
|
+
}, []);
|
|
142
|
+
|
|
143
|
+
// True for "Saving..."
|
|
144
|
+
// False for "Saved"
|
|
145
|
+
const pending = usePending(shareDBDoc);
|
|
146
|
+
|
|
147
|
+
// A convenience function for mutating the ShareDB document
|
|
148
|
+
// based submitting OT ops generated by diffing the JSON.
|
|
149
|
+
const submitOperation =
|
|
150
|
+
useSubmitOperation<VizContent>(shareDBDoc);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
shareDBDoc,
|
|
154
|
+
content,
|
|
155
|
+
localPresence,
|
|
156
|
+
docPresence,
|
|
157
|
+
submitOperation,
|
|
158
|
+
connected,
|
|
159
|
+
pending,
|
|
160
|
+
};
|
|
161
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { inlineCopilot } from 'codemirror-copilot';
|
|
2
|
+
|
|
3
|
+
const defaultAICopilotEndpoint = '/ai-copilot';
|
|
4
|
+
|
|
5
|
+
const DEBUG = false;
|
|
6
|
+
|
|
7
|
+
// If the endpoint is broken, we don't want to keep sending requests
|
|
8
|
+
// to it. This flag will be set to true if we get an error from the
|
|
9
|
+
// server.
|
|
10
|
+
let isEndpointBroken = false;
|
|
11
|
+
|
|
12
|
+
export const copilot = ({
|
|
13
|
+
aiCopilotEndpoint = defaultAICopilotEndpoint,
|
|
14
|
+
}: {
|
|
15
|
+
aiCopilotEndpoint: string;
|
|
16
|
+
}) => {
|
|
17
|
+
return inlineCopilot(async (prefix, suffix) => {
|
|
18
|
+
DEBUG &&
|
|
19
|
+
console.log('[copilot] Executing AI completion...');
|
|
20
|
+
if (isEndpointBroken) {
|
|
21
|
+
DEBUG &&
|
|
22
|
+
console.log(
|
|
23
|
+
'[copilot] Endpoint is broken, not sending request',
|
|
24
|
+
);
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
DEBUG &&
|
|
29
|
+
console.log('[copilot] Sending request to server...');
|
|
30
|
+
const response = await fetch(aiCopilotEndpoint, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: { 'Content-Type': 'application/json' },
|
|
33
|
+
body: JSON.stringify({ prefix, suffix }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
DEBUG &&
|
|
38
|
+
console.error(
|
|
39
|
+
'[copilot] Error from server:',
|
|
40
|
+
response.statusText,
|
|
41
|
+
);
|
|
42
|
+
DEBUG &&
|
|
43
|
+
console.error(
|
|
44
|
+
'[copilot] Disabling future requests to the server',
|
|
45
|
+
);
|
|
46
|
+
isEndpointBroken = true;
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const responseJson = await response.json();
|
|
51
|
+
|
|
52
|
+
DEBUG &&
|
|
53
|
+
console.log(
|
|
54
|
+
'[copilot] Response from server:',
|
|
55
|
+
response,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (responseJson.error) {
|
|
59
|
+
DEBUG &&
|
|
60
|
+
console.error(
|
|
61
|
+
'[copilot] error from server:',
|
|
62
|
+
responseJson.error,
|
|
63
|
+
);
|
|
64
|
+
DEBUG &&
|
|
65
|
+
console.error(
|
|
66
|
+
'[copilot] Disabling future requests to the server',
|
|
67
|
+
);
|
|
68
|
+
isEndpointBroken = true;
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return responseJson.text;
|
|
73
|
+
});
|
|
74
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { InteractRule } from '@replit/codemirror-interact';
|
|
2
|
+
export const boolTogglerRegex = /true|false/g;
|
|
3
|
+
export const boolToggler = (
|
|
4
|
+
onInteract?: () => void,
|
|
5
|
+
): InteractRule => ({
|
|
6
|
+
regexp: boolTogglerRegex,
|
|
7
|
+
cursor: 'pointer',
|
|
8
|
+
onClick: (text, setText) => {
|
|
9
|
+
setText(text === 'true' ? 'false' : 'true');
|
|
10
|
+
if (onInteract) onInteract();
|
|
11
|
+
},
|
|
12
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { InteractRule } from '@replit/codemirror-interact';
|
|
2
|
+
|
|
3
|
+
// Regular expression for hex colors.
|
|
4
|
+
export const colorPickerRegex = /#[0-9A-Fa-f]{6}/g;
|
|
5
|
+
|
|
6
|
+
// hex color picker
|
|
7
|
+
// Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
|
|
8
|
+
// Works without quotes to support CSS.
|
|
9
|
+
export const colorPicker = (
|
|
10
|
+
onInteract?: () => void,
|
|
11
|
+
): InteractRule => ({
|
|
12
|
+
regexp: colorPickerRegex,
|
|
13
|
+
cursor: 'pointer',
|
|
14
|
+
onClick(
|
|
15
|
+
text: string,
|
|
16
|
+
setText: (newText: string) => void,
|
|
17
|
+
event: MouseEvent,
|
|
18
|
+
) {
|
|
19
|
+
const startingColor: string = text;
|
|
20
|
+
|
|
21
|
+
const sel: HTMLInputElement =
|
|
22
|
+
document.createElement('input');
|
|
23
|
+
sel.type = 'color';
|
|
24
|
+
sel.value = startingColor.toLowerCase();
|
|
25
|
+
|
|
26
|
+
// `valueIsUpper` maintains the style of the user's code.
|
|
27
|
+
// It keeps the case of a-f the same case as the original.
|
|
28
|
+
const valueIsUpper: boolean =
|
|
29
|
+
startingColor.toUpperCase() === startingColor;
|
|
30
|
+
|
|
31
|
+
const updateHex = (e: Event) => {
|
|
32
|
+
const el: HTMLInputElement =
|
|
33
|
+
e.target as HTMLInputElement;
|
|
34
|
+
if (el.value) {
|
|
35
|
+
setText(
|
|
36
|
+
valueIsUpper ? el.value.toUpperCase() : el.value,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
if (onInteract) onInteract();
|
|
40
|
+
};
|
|
41
|
+
sel.addEventListener('input', updateHex);
|
|
42
|
+
|
|
43
|
+
// Position the color input at the mouse cursor
|
|
44
|
+
sel.style.position = 'absolute';
|
|
45
|
+
sel.style.left = `${event.clientX}px`;
|
|
46
|
+
sel.style.top = `${event.clientY}px`;
|
|
47
|
+
|
|
48
|
+
// Make it invisible
|
|
49
|
+
sel.style.opacity = '0';
|
|
50
|
+
sel.style.height = '0';
|
|
51
|
+
sel.style.width = '0';
|
|
52
|
+
sel.style.border = 'none';
|
|
53
|
+
sel.style.padding = '0';
|
|
54
|
+
sel.style.margin = '0';
|
|
55
|
+
document.body.appendChild(sel);
|
|
56
|
+
|
|
57
|
+
// Click the input after a delay, so that
|
|
58
|
+
// it gets the correct position
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
sel.click();
|
|
61
|
+
}, 10);
|
|
62
|
+
|
|
63
|
+
// Remove the input after interaction
|
|
64
|
+
sel.addEventListener('blur', () => {
|
|
65
|
+
document.body.removeChild(sel);
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
});
|