vzcode 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/package.json +3 -2
  2. package/src/cli.ts +208 -0
  3. package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +42 -0
  4. package/src/client/AIAssist/AIAssistWidget/index.tsx +135 -0
  5. package/src/client/AIAssist/AIAssistWidget/style.scss +47 -0
  6. package/src/client/AIAssist/Spinner.jsx +76 -0
  7. package/src/client/AIAssist/formatFile.ts +27 -0
  8. package/src/client/AIAssist/generateFilesContext.ts +25 -0
  9. package/src/client/AIAssist/index.ts +2 -0
  10. package/src/client/AIAssist/startAIAssist.ts +95 -0
  11. package/src/client/App/index.tsx +152 -0
  12. package/src/client/App/style.scss +82 -0
  13. package/src/client/App/usePending.ts +51 -0
  14. package/src/client/App/useShareDB.ts +161 -0
  15. package/src/client/CodeEditor/Copilot/index.ts +74 -0
  16. package/src/client/CodeEditor/InteractiveWidgets/boolToggler.ts +12 -0
  17. package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +68 -0
  18. package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +174 -0
  19. package/src/client/CodeEditor/InteractiveWidgets/highlightWidgets.ts +79 -0
  20. package/src/client/CodeEditor/InteractiveWidgets/index.ts +44 -0
  21. package/src/client/CodeEditor/InteractiveWidgets/markdownCheckboxToggler.ts +12 -0
  22. package/src/client/CodeEditor/InteractiveWidgets/numberDragger.ts +20 -0
  23. package/src/client/CodeEditor/InteractiveWidgets/rotateWidget.ts +143 -0
  24. package/src/client/CodeEditor/InteractiveWidgets/urlClicker.test.ts +48 -0
  25. package/src/client/CodeEditor/InteractiveWidgets/urlClicker.ts +12 -0
  26. package/src/client/CodeEditor/InteractiveWidgets/vec2Slider.ts +25 -0
  27. package/src/client/CodeEditor/basicSetup.ts +73 -0
  28. package/src/client/CodeEditor/getOrCreateEditor.tsx +615 -0
  29. package/src/client/CodeEditor/index.tsx +202 -0
  30. package/src/client/CodeEditor/json1PresenceBroadcast.ts +41 -0
  31. package/src/client/CodeEditor/json1PresenceDisplay.ts +350 -0
  32. package/src/client/CodeEditor/rainbowBrackets.ts +122 -0
  33. package/src/client/CodeEditor/style.scss +156 -0
  34. package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
  35. package/src/client/CodeErrorOverlay/index.tsx +77 -0
  36. package/src/client/CodeErrorOverlay/style.scss +33 -0
  37. package/src/client/Fonts/fonts.ts +8 -0
  38. package/src/client/Icons/AdjustmentSVG.tsx +19 -0
  39. package/src/client/Icons/BugSVG.tsx +20 -0
  40. package/src/client/Icons/CloseSVG.tsx +19 -0
  41. package/src/client/Icons/CssSVG.tsx +16 -0
  42. package/src/client/Icons/DirectoryArrowSVG.tsx +19 -0
  43. package/src/client/Icons/EditSVG.tsx +19 -0
  44. package/src/client/Icons/FileSVG.tsx +19 -0
  45. package/src/client/Icons/FolderSVG.tsx +20 -0
  46. package/src/client/Icons/GearSVG.tsx +16 -0
  47. package/src/client/Icons/HtmlSVG.tsx +16 -0
  48. package/src/client/Icons/JavaScriptSVG.tsx +16 -0
  49. package/src/client/Icons/JsonSVG.tsx +16 -0
  50. package/src/client/Icons/MarkdownSVG.tsx +16 -0
  51. package/src/client/Icons/MicOffSVG.tsx +35 -0
  52. package/src/client/Icons/MicSVG.tsx +25 -0
  53. package/src/client/Icons/NewSVG.tsx +19 -0
  54. package/src/client/Icons/PinSVG.tsx +14 -0
  55. package/src/client/Icons/PlaySVG.tsx +16 -0
  56. package/src/client/Icons/QuestionMarkSVG.tsx +18 -0
  57. package/src/client/Icons/ReactSVG.tsx +16 -0
  58. package/src/client/Icons/SearchSVG.tsx +20 -0
  59. package/src/client/Icons/SparklesSVG.tsx +23 -0
  60. package/src/client/Icons/SplitEditorSVG.tsx +34 -0
  61. package/src/client/Icons/StopSVG.tsx +15 -0
  62. package/src/client/Icons/SvelteSVG.tsx +16 -0
  63. package/src/client/Icons/TrashSVG.tsx +19 -0
  64. package/src/client/Icons/TypeScriptSVG.tsx +16 -0
  65. package/src/client/Icons/index.tsx +26 -0
  66. package/src/client/ImageViewer/index.tsx +91 -0
  67. package/src/client/ImageViewer/style.scss +41 -0
  68. package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
  69. package/src/client/PresenceNotifications/index.tsx +158 -0
  70. package/src/client/PresenceNotifications/style.scss +17 -0
  71. package/src/client/RunCodeWidget/index.tsx +117 -0
  72. package/src/client/RunCodeWidget/style.scss +24 -0
  73. package/src/client/SplitPaneResizeContext.tsx +159 -0
  74. package/src/client/TabList/Tab.tsx +73 -0
  75. package/src/client/TabList/index.tsx +35 -0
  76. package/src/client/TabList/style.scss +51 -0
  77. package/src/client/VZCodeContext/index.tsx +29 -0
  78. package/src/client/VZCodeContext/types.ts +248 -0
  79. package/src/client/VZCodeContext/useVZCodeState.ts +602 -0
  80. package/src/client/VZKeyboardShortcutsDoc.tsx +148 -0
  81. package/src/client/VZLeft.tsx +28 -0
  82. package/src/client/VZMiddle.tsx +172 -0
  83. package/src/client/VZResizer/index.tsx +128 -0
  84. package/src/client/VZResizer/styles.scss +33 -0
  85. package/src/client/VZRight.tsx +113 -0
  86. package/src/client/VZSettings.tsx +275 -0
  87. package/src/client/VZSidebar/AIChat/ChatInput.tsx +243 -0
  88. package/src/client/VZSidebar/AIChat/DiffView.scss +136 -0
  89. package/src/client/VZSidebar/AIChat/DiffView.tsx +176 -0
  90. package/src/client/VZSidebar/AIChat/Message.tsx +79 -0
  91. package/src/client/VZSidebar/AIChat/MessageList.tsx +190 -0
  92. package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +35 -0
  93. package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +156 -0
  94. package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +19 -0
  95. package/src/client/VZSidebar/AIChat/index.tsx +357 -0
  96. package/src/client/VZSidebar/AIChat/styles.scss +646 -0
  97. package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +117 -0
  98. package/src/client/VZSidebar/CreateDirModal.tsx +111 -0
  99. package/src/client/VZSidebar/CreateFileModal.tsx +111 -0
  100. package/src/client/VZSidebar/DeleteConfirmationModal.tsx +76 -0
  101. package/src/client/VZSidebar/DirectoryListing.tsx +89 -0
  102. package/src/client/VZSidebar/EmptyState.tsx +11 -0
  103. package/src/client/VZSidebar/FileListing.tsx +91 -0
  104. package/src/client/VZSidebar/FileTypeIcon.tsx +49 -0
  105. package/src/client/VZSidebar/Item.tsx +237 -0
  106. package/src/client/VZSidebar/Listing.tsx +47 -0
  107. package/src/client/VZSidebar/Search.tsx +527 -0
  108. package/src/client/VZSidebar/VisualEditor.tsx +282 -0
  109. package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
  110. package/src/client/VZSidebar/aiCopyPaste.ts +199 -0
  111. package/src/client/VZSidebar/index.tsx +728 -0
  112. package/src/client/VZSidebar/styles.scss +612 -0
  113. package/src/client/VZSidebar/useDragAndDrop.tsx +408 -0
  114. package/src/client/bootstrap.ts +26 -0
  115. package/src/client/buildWorker.ts +3 -0
  116. package/src/client/featureFlags.ts +8 -0
  117. package/src/client/generateRequestId.ts +6 -0
  118. package/src/client/getFileTree.ts +53 -0
  119. package/src/client/index.ts +19 -0
  120. package/src/client/main.jsx +10 -0
  121. package/src/client/presenceColor.ts +8 -0
  122. package/src/client/shouldTriggerRun.ts +31 -0
  123. package/src/client/sortFileTree.ts +21 -0
  124. package/src/client/tabsSearchParameters.test.ts +79 -0
  125. package/src/client/tabsSearchParameters.ts +137 -0
  126. package/src/client/themes/index.ts +7 -0
  127. package/src/client/themes/themes.ts +77 -0
  128. package/src/client/themes/uiwImports.ts +32 -0
  129. package/src/client/themes/useDynamicTheme.ts +29 -0
  130. package/src/client/themes/vizhubTheme/colors.ts +80 -0
  131. package/src/client/themes/vizhubTheme/index.ts +193 -0
  132. package/src/client/useActions.ts +288 -0
  133. package/src/client/useESLint/index.ts +72 -0
  134. package/src/client/useESLint/worker.ts +113 -0
  135. package/src/client/useEditorCache.ts +35 -0
  136. package/src/client/useFileCRUD.ts +228 -0
  137. package/src/client/useKeyboardShortcuts.ts +451 -0
  138. package/src/client/useOpenDirectories.ts +99 -0
  139. package/src/client/usePresenceAutoFollow.ts +85 -0
  140. package/src/client/usePrettier/index.ts +178 -0
  141. package/src/client/usePrettier/worker.ts +109 -0
  142. package/src/client/useRunCode.tsx +15 -0
  143. package/src/client/useRuntimeError.ts +32 -0
  144. package/src/client/useSubmitOperation.ts +15 -0
  145. package/src/client/useURLSync.ts +137 -0
  146. package/src/client/usernameLocalStorage.ts +43 -0
  147. package/src/client/utils/fileExtension.ts +10 -0
  148. package/src/client/utils/isImageFile.ts +12 -0
  149. package/src/client/vzReducer/aiChatReducer.ts +44 -0
  150. package/src/client/vzReducer/closeTabReducer.test.ts +205 -0
  151. package/src/client/vzReducer/closeTabsReducer.ts +96 -0
  152. package/src/client/vzReducer/createInitialState.ts +38 -0
  153. package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
  154. package/src/client/vzReducer/findPane.ts +19 -0
  155. package/src/client/vzReducer/index.ts +250 -0
  156. package/src/client/vzReducer/openTabReducer.test.ts +127 -0
  157. package/src/client/vzReducer/openTabReducer.ts +74 -0
  158. package/src/client/vzReducer/searchReducer.test.ts +292 -0
  159. package/src/client/vzReducer/searchReducer.ts +181 -0
  160. package/src/client/vzReducer/setActiveFileIdReducer.ts +28 -0
  161. package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +76 -0
  162. package/src/client/vzReducer/setIsDocOpenReducer.ts +9 -0
  163. package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
  164. package/src/client/vzReducer/setThemeReducer.ts +9 -0
  165. package/src/client/vzReducer/setUsernameReducer.ts +9 -0
  166. package/src/client/vzReducer/splitCurrentPaneReducer.ts +66 -0
  167. package/src/client/vzReducer/toggleAutoFollowReducer.ts +12 -0
  168. package/src/client/vzReducer/updatePane.ts +40 -0
  169. package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +30 -0
  170. package/src/client/vzReducer/visualEditorReducer.ts +9 -0
  171. package/src/ot.ts +24 -0
  172. package/src/randomId.ts +15 -0
  173. package/src/reactRouterExports.ts +15 -0
  174. package/src/runCode.ts +27 -0
  175. package/src/server/aiChatHandler/aiEditing.ts +151 -0
  176. package/src/server/aiChatHandler/chatOperations.ts +476 -0
  177. package/src/server/aiChatHandler/errorHandling.ts +66 -0
  178. package/src/server/aiChatHandler/index.ts +199 -0
  179. package/src/server/aiChatHandler/llmStreaming.ts +419 -0
  180. package/src/server/aiChatHandler/validation.ts +24 -0
  181. package/src/server/computeInitialDocument.ts +220 -0
  182. package/src/server/config.ts +24 -0
  183. package/src/server/featureFlags.ts +8 -0
  184. package/src/server/generateAIResponse.ts +188 -0
  185. package/src/server/handleAIAssist.ts +53 -0
  186. package/src/server/handleAIChatMessage.ts +2 -0
  187. package/src/server/handleAICopilot.ts +97 -0
  188. package/src/server/index.ts +379 -0
  189. package/src/server/isDirectory.ts +1 -0
  190. package/src/server/livekit.ts +24 -0
  191. package/src/server/prettier.ts +115 -0
  192. package/src/server/setupEnv.ts +8 -0
  193. package/src/submitOperation.ts +25 -0
  194. package/src/types.ts +205 -0
  195. package/src/utils/fileDiff.ts +117 -0
  196. 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
+ });