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,99 @@
1
+ import { useState, useCallback, useEffect } from 'react';
2
+ import type { Pane } from '../types';
3
+ import { VizContent } from '@vizhub/viz-types';
4
+
5
+ // TODO bring this feature back
6
+ // When a page is opened with an active file,
7
+ // make sure all the directories leading to that file
8
+ // are opened automatically.
9
+ export const initialOpenDirectories = (
10
+ activeFile: string | null,
11
+ ): Set<VZPath> => {
12
+ const openDirectories = new Set<VZPath>();
13
+ if (activeFile) {
14
+ const path = activeFile.split('/');
15
+ for (let i = 1; i < path.length; i++) {
16
+ openDirectories.add(path.slice(0, i).join('/'));
17
+ }
18
+ }
19
+ return openDirectories;
20
+ };
21
+
22
+ type VZPath = string;
23
+
24
+ // Inspired by
25
+ // https://github.com/vizhub-core/vizhub/blob/main/vizhub-v2/packages/neoFrontend/src/pages/VizPage/Body/Editor/FilesSection/useOpenDirectories.js
26
+ // TODO move this into reducer
27
+ export const useOpenDirectories = ({
28
+ activePane,
29
+ content,
30
+ }: {
31
+ activePane: Pane;
32
+ content: VizContent;
33
+ }): {
34
+ isDirectoryOpen: (path: VZPath) => boolean;
35
+ toggleDirectory: (path: VZPath) => void;
36
+ } => {
37
+ // The set of open directories by path/
38
+ const [openDirectories, setOpenDirectories] = useState<
39
+ Set<VZPath>
40
+ >(new Set());
41
+
42
+ // (client-side only) initialize the open directories
43
+ // based on the open files from the URL params.
44
+ useEffect(() => {
45
+ if (
46
+ activePane.type === 'leafPane' &&
47
+ activePane?.activeFileId &&
48
+ content.files[activePane.activeFileId]
49
+ ) {
50
+ const activeFileId = activePane.activeFileId;
51
+ const fileName = content.files[activeFileId].name;
52
+ const dirsToOpen = initialOpenDirectories(fileName);
53
+ setOpenDirectories((prev) => {
54
+ const updated = new Set(prev);
55
+ for (const dir of dirsToOpen) {
56
+ updated.add(dir);
57
+ }
58
+ return updated;
59
+ });
60
+ }
61
+ }, [activePane]);
62
+
63
+ // Whether a directory is open.
64
+ const isDirectoryOpen: (path: VZPath) => boolean =
65
+ useCallback(
66
+ (path) => openDirectories.has(path),
67
+ [openDirectories],
68
+ );
69
+
70
+ /**
71
+ * Toggles the state of a directory (open/close).
72
+ * If the directory is currently open, it will close it.
73
+ * If it's closed, it will open it.
74
+ */
75
+ const toggleDirectory = useCallback(
76
+ (path: VZPath) => {
77
+ // Create a copy of the currently open directories
78
+ const updatedOpenDirectories = new Set(
79
+ openDirectories,
80
+ );
81
+
82
+ // Check if the directory is currently open
83
+ const directoryIsOpen = isDirectoryOpen(path);
84
+
85
+ // Toggle the directory's state
86
+ if (directoryIsOpen) {
87
+ updatedOpenDirectories.delete(path);
88
+ } else {
89
+ updatedOpenDirectories.add(path);
90
+ }
91
+
92
+ // Update the state with the modified set of open directories
93
+ setOpenDirectories(updatedOpenDirectories);
94
+ },
95
+ [openDirectories, isDirectoryOpen],
96
+ );
97
+
98
+ return { isDirectoryOpen, toggleDirectory };
99
+ };
@@ -0,0 +1,85 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { Presence, PresenceId } from '../types';
3
+ import { VizFileId } from '@vizhub/viz-types';
4
+
5
+ const DEBUG = false;
6
+
7
+ /**
8
+ * Custom hook that handles auto-following users by opening tabs when presence
9
+ * is received on files that are not currently open. This logic is independent
10
+ * of CodeMirror extensions and works regardless of editor cache state.
11
+ */
12
+ export const usePresenceAutoFollow = ({
13
+ docPresence,
14
+ enableAutoFollow,
15
+ openTab,
16
+ activePane,
17
+ }) => {
18
+ // Track enableAutoFollow on a ref for use in event handlers
19
+ const enableAutoFollowRef = useRef(enableAutoFollow);
20
+ useEffect(() => {
21
+ enableAutoFollowRef.current = enableAutoFollow;
22
+ }, [enableAutoFollow]);
23
+
24
+ // Track active file for comparison
25
+ const activeFileIdRef = useRef(activePane?.activeFileId);
26
+ useEffect(() => {
27
+ activeFileIdRef.current = activePane?.activeFileId;
28
+ }, [activePane?.activeFileId]);
29
+
30
+ useEffect(() => {
31
+ if (!docPresence) return;
32
+
33
+ const handlePresenceReceive = (
34
+ id: PresenceId,
35
+ presence: Presence,
36
+ ) => {
37
+ if (DEBUG) {
38
+ console.log(
39
+ `[usePresenceAutoFollow] Received presence for id ${id}`,
40
+ presence,
41
+ );
42
+ }
43
+
44
+ // If presence is null, the user has disconnected
45
+ if (!presence) {
46
+ return;
47
+ }
48
+
49
+ // If auto-follow is not enabled, do nothing
50
+ if (!enableAutoFollowRef.current) {
51
+ return;
52
+ }
53
+
54
+ // Extract the file ID from the presence path
55
+ // Presence path structure: [path, to, file, fileId, position]
56
+ // We want the fileId which should be at index 1 for files
57
+ const presenceFileId = presence.start[1] as VizFileId;
58
+
59
+ // If the presence is for the currently active file, do nothing
60
+ // (the CodeMirror extension will handle displaying the cursor)
61
+ if (presenceFileId === activeFileIdRef.current) {
62
+ return;
63
+ }
64
+
65
+ // If the presence is for a different file, open that tab
66
+ DEBUG &&
67
+ console.log(
68
+ `[usePresenceAutoFollow] Auto-opening tab for ${presence.username} in file ${presenceFileId}`,
69
+ );
70
+
71
+ openTab({
72
+ fileId: presenceFileId,
73
+ isTransient: true,
74
+ });
75
+ };
76
+
77
+ // Subscribe to presence changes
78
+ docPresence.on('receive', handlePresenceReceive);
79
+
80
+ // Cleanup subscription on unmount
81
+ return () => {
82
+ docPresence.off('receive', handlePresenceReceive);
83
+ };
84
+ }, [docPresence, openTab]);
85
+ };
@@ -0,0 +1,178 @@
1
+ import { VizFileId, VizContent } from '@vizhub/viz-types';
2
+ import { JSONOp, ShareDBDoc } from '../../types';
3
+ import { useEffect, useRef, useState } from 'react';
4
+
5
+ // The time in milliseconds by which auto-saving is debounced.
6
+ export const autoPrettierDebounceTimeMS = 1200;
7
+
8
+ // Computes a file extension from a file name.
9
+ // Example: 'foo.js' => '.js'
10
+ const extension = (fileName: string) => {
11
+ const match = fileName.match(/\.([^.]+)$/);
12
+ if (match) {
13
+ return match[1];
14
+ } else {
15
+ return '';
16
+ }
17
+ };
18
+
19
+ // export const shouldTriggerRun = (event: KeyboardEvent) => {
20
+ // if(event.ctrlKey && event.key === 's') {
21
+ // return true;
22
+ // }
23
+ // }
24
+
25
+ export const usePrettier = ({
26
+ shareDBDoc,
27
+ submitOperation,
28
+ prettierWorker,
29
+ }: {
30
+ shareDBDoc: ShareDBDoc<VizContent> | null;
31
+ submitOperation: (
32
+ next: (content: VizContent) => VizContent,
33
+ ) => void;
34
+ prettierWorker: Worker;
35
+ }) => {
36
+ // The set of files that have been modified
37
+ // since the last Prettier run.
38
+ const dirtyFilesRef = useRef<Set<VizFileId>>(new Set());
39
+
40
+ // State to hold the error from Prettier
41
+ // `null` means no errors
42
+ // If this is a string, it's the
43
+ // error message from Prettier.
44
+ const [prettierError, setPrettierError] = useState<
45
+ string | null
46
+ >(null);
47
+
48
+ // A ref to keep track of whether an op is being applied.
49
+ // This is used to prevent Prettier from running
50
+ // when the op is coming from Prettier itself.
51
+ const isApplyingOpRef = useRef(false);
52
+
53
+ // We use a ref to keep track of the runPrettier function.
54
+ const runPrettierRef = useRef(null);
55
+
56
+ useEffect(() => {
57
+ if (!shareDBDoc) {
58
+ return;
59
+ }
60
+
61
+ // Get the message that comes back from the worker.
62
+ // This is the prettified text for a specific file.
63
+ const handleMessage = (event) => {
64
+ const { fileId, error, fileTextPrettified } =
65
+ event.data;
66
+
67
+ // Handle syntax errors
68
+ if (error) {
69
+ // If there's an error, set the error state
70
+ setPrettierError(error);
71
+
72
+ // Return early as there is no text change to apply.
73
+ return;
74
+ } else {
75
+ // Make sure the error overlay goes away
76
+ // when Prettier runs successfully with no errors.
77
+ setPrettierError(null);
78
+ }
79
+
80
+ isApplyingOpRef.current = true;
81
+ submitOperation((document) => ({
82
+ ...document,
83
+ files: {
84
+ ...document.files,
85
+ [fileId]: {
86
+ ...document.files[fileId],
87
+ text: fileTextPrettified,
88
+ },
89
+ },
90
+ }));
91
+ isApplyingOpRef.current = false;
92
+ };
93
+
94
+ // Listen for messages from the worker.
95
+ prettierWorker.addEventListener(
96
+ 'message',
97
+ handleMessage,
98
+ );
99
+
100
+ const runPrettier = async () => {
101
+ // Get the content of the document
102
+ const content = shareDBDoc.data;
103
+
104
+ // Get the files
105
+ const files = content.files;
106
+
107
+ // Get the set of dirty files
108
+ const dirtyFiles = dirtyFilesRef.current;
109
+
110
+ // Get the dirty files
111
+ const dirtyFileIds = Array.from(dirtyFiles);
112
+
113
+ // Clear the set of dirty files
114
+ dirtyFiles.clear();
115
+
116
+ // Run Prettier on each dirty file
117
+ for (const fileId of dirtyFileIds) {
118
+ // Get the file
119
+ const file = files[fileId];
120
+
121
+ // Craft the data to send to the worker
122
+ const data = {
123
+ fileText: file.text,
124
+ fileExtension: extension(file.name),
125
+ fileId,
126
+ };
127
+
128
+ // Run Prettier for this file
129
+ prettierWorker.postMessage(data);
130
+ }
131
+ };
132
+ runPrettierRef.current = runPrettier;
133
+
134
+ const handleOpBatch = (op: JSONOp) => {
135
+ // Only act on changes coming from the client
136
+ // OR changes coming from the AI Assist that we
137
+ // if (isLocal) {
138
+ // If the op is coming from Prettier itself,
139
+ // then do nothing.
140
+ if (isApplyingOpRef.current) {
141
+ // console.log('ignoring op from Prettier');
142
+ return;
143
+ }
144
+
145
+ // Check if the path of this op is the text content of a file
146
+ if (
147
+ op &&
148
+ op.length === 4 &&
149
+ op[0] === 'files' &&
150
+ typeof op[1] === 'string' &&
151
+ op[2] === 'text'
152
+ ) {
153
+ // Get the file id
154
+ const fileId: VizFileId = op[1];
155
+
156
+ // Add the file id to the set of dirty files
157
+ dirtyFilesRef.current.add(fileId);
158
+ }
159
+ };
160
+
161
+ // Listen for changes
162
+ shareDBDoc.on('op batch', handleOpBatch);
163
+
164
+ // Clean up the event listeners
165
+ return () => {
166
+ prettierWorker.removeEventListener(
167
+ 'message',
168
+ handleMessage,
169
+ );
170
+
171
+ shareDBDoc.removeListener('op batch', handleOpBatch);
172
+ };
173
+ }, [shareDBDoc]);
174
+
175
+ // Return the errors and run prettier function ref
176
+ // for use elsewhere.
177
+ return { prettierError, runPrettierRef };
178
+ };
@@ -0,0 +1,109 @@
1
+ import { format } from 'prettier/standalone';
2
+ import * as prettierPluginBabel from 'prettier/plugins/babel';
3
+ import * as prettierPluginEstree from 'prettier/plugins/estree';
4
+ import * as prettierPluginHtml from 'prettier/plugins/html';
5
+ import * as prettierPluginMarkdown from 'prettier/plugins/markdown';
6
+ import * as prettierPluginCSS from 'prettier/plugins/postcss';
7
+ import * as prettierPluginTypescript from 'prettier/plugins/typescript';
8
+ import * as prettierPluginSvelte from 'prettier-plugin-svelte/browser';
9
+
10
+ import { VizFileId } from '@vizhub/viz-types';
11
+
12
+ const enableSvelte = true;
13
+
14
+ const parsers = {
15
+ js: 'babel',
16
+ jsx: 'babel',
17
+ mjs: 'babel',
18
+ cjs: 'babel',
19
+ ts: 'typescript',
20
+ tsx: 'typescript',
21
+ css: 'css',
22
+ // '.less': 'less',
23
+ // '.scss': 'scss',
24
+ html: 'html',
25
+ json: 'json',
26
+ json5: 'json5',
27
+ md: 'markdown',
28
+ markdown: 'markdown',
29
+ // '.vue': 'vue',
30
+ };
31
+
32
+ if (enableSvelte) {
33
+ // @ts-ignore
34
+ parsers.svelte = 'svelte';
35
+ }
36
+
37
+ const plugins = [
38
+ prettierPluginBabel,
39
+ prettierPluginEstree,
40
+ prettierPluginHtml,
41
+ prettierPluginMarkdown,
42
+ prettierPluginTypescript,
43
+ prettierPluginCSS,
44
+ ] as any; // Cast to any to resolve type conflict
45
+
46
+ if (enableSvelte) {
47
+ // @ts-ignore
48
+ plugins.push(prettierPluginSvelte);
49
+ }
50
+
51
+ onmessage = async ({
52
+ data,
53
+ }: {
54
+ data: {
55
+ // The text content of the file
56
+ fileText: string;
57
+
58
+ // The file extension
59
+ // Supported extensions: https://prettier.io/docs/en/options.html#parser
60
+ // The editor only supports
61
+ // - JavaScript
62
+ // - TypeScript
63
+ // - HTML
64
+ // - CSS
65
+ // - JSON
66
+ // - Markdown
67
+
68
+ fileExtension: string;
69
+
70
+ // The file id
71
+ fileId: VizFileId;
72
+ };
73
+ }) => {
74
+ const { fileExtension, fileText, fileId } = data;
75
+ const parser = parsers[fileExtension];
76
+
77
+ // If no parser is found, just do nothing.
78
+ // For example, if the user opens a CSV file,
79
+ // then we don't want to run Prettier on it,
80
+ // and we also don't want to show an error.
81
+ if (!parser) {
82
+ return;
83
+ }
84
+
85
+ try {
86
+ const fileTextPrettified = await format(fileText, {
87
+ parser,
88
+ plugins,
89
+
90
+ // This helps with Markdown files
91
+ proseWrap: 'always',
92
+
93
+ // Opinionated code style for JavaScript
94
+ singleQuote: true,
95
+ printWidth: 60,
96
+ });
97
+
98
+ postMessage({
99
+ fileId,
100
+ fileTextPrettified,
101
+ });
102
+ } catch (error) {
103
+ console.error(error);
104
+ postMessage({
105
+ fileId,
106
+ error: error.toString(),
107
+ });
108
+ }
109
+ };
@@ -0,0 +1,15 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { SubmitOperation } from '../types';
3
+ import { createRunCodeFunction } from '../runCode';
4
+
5
+ // Runs the code by flashing `isInteracting` to `true`.
6
+ export const useRunCode = (
7
+ submitOperation: SubmitOperation,
8
+ ) => {
9
+ const runCodeRef = useRef(null);
10
+ useEffect(() => {
11
+ const runCode = createRunCodeFunction(submitOperation);
12
+ runCodeRef.current = runCode;
13
+ }, [submitOperation]);
14
+ return runCodeRef;
15
+ };
@@ -0,0 +1,32 @@
1
+ import { useCallback, useState } from 'react';
2
+
3
+ // Custom hook to manage runtime error state
4
+ export const useRuntimeError = () => {
5
+ // State to hold the runtime error message
6
+ // `null` means no errors
7
+ // If this is a string, it's the
8
+ // error message from runtime execution.
9
+ const [runtimeError, setRuntimeError] = useState<
10
+ string | null
11
+ >(null);
12
+
13
+ // Callback function to handle runtime errors
14
+ // This will be passed to the createRuntime function
15
+ const handleRuntimeError = useCallback(
16
+ (formattedErrorMessage: string) => {
17
+ setRuntimeError(formattedErrorMessage);
18
+ },
19
+ [],
20
+ );
21
+
22
+ // Function to clear runtime errors
23
+ const clearRuntimeError = useCallback(() => {
24
+ setRuntimeError(null);
25
+ }, []);
26
+
27
+ return {
28
+ runtimeError,
29
+ handleRuntimeError,
30
+ clearRuntimeError,
31
+ };
32
+ };
@@ -0,0 +1,15 @@
1
+ import { useCallback } from 'react';
2
+ import { ShareDBDoc } from '../types'; // Assuming ShareDBDoc is already generic
3
+ import { createSubmitOperation } from '../submitOperation';
4
+
5
+ // A generic helper function to submit diff-based operations to ShareDB
6
+ export const useSubmitOperation = <T>(
7
+ shareDBDoc: ShareDBDoc<T>,
8
+ ): ((next: (data: T) => T) => void) => {
9
+ const submitOperation: (next: (data: T) => T) => void =
10
+ useCallback(() => {
11
+ return createSubmitOperation(shareDBDoc);
12
+ }, [shareDBDoc])();
13
+
14
+ return submitOperation;
15
+ };
@@ -0,0 +1,137 @@
1
+ import { TabState } from '../types';
2
+ import { VizFileId, VizContent } from '@vizhub/viz-types';
3
+ import { useEffect, useMemo, useRef } from 'react';
4
+ import {
5
+ TabStateParams,
6
+ decodeTabs,
7
+ encodeTabs,
8
+ } from './tabsSearchParameters';
9
+ import { useSearchParams } from '../reactRouterExports';
10
+
11
+ // Synchronizes the tab state with the URL parameters.
12
+ export const useURLSync = ({
13
+ content,
14
+ openTab,
15
+ setActiveFileId,
16
+ tabList,
17
+ activeFileId,
18
+ }: {
19
+ content: VizContent | null;
20
+ openTab: (tabState: TabState) => void;
21
+ setActiveFileId: (activeFileId: VizFileId) => void;
22
+ tabList: Array<TabState>;
23
+ activeFileId: VizFileId | null;
24
+ }) => {
25
+ // Use React router to get and set the search parameters.
26
+ const [searchParams, setSearchParams] = useSearchParams();
27
+
28
+ // Extract the tab state parameters from the search parameters.
29
+ const tabStateParams: TabStateParams = useMemo(
30
+ () => ({
31
+ file: searchParams.get('file'),
32
+ tabs: searchParams.get('tabs'),
33
+ }),
34
+ [searchParams],
35
+ );
36
+
37
+ // `true` after the state is updated based on the
38
+ // initial search parameters from the URL.
39
+ const isInitialized = useRef(false);
40
+
41
+ // Initialize the state based on the search parameters.
42
+ useEffect(() => {
43
+ // Do nothing if the state has already been initialized.
44
+ if (isInitialized.current) {
45
+ return;
46
+ }
47
+
48
+ // Wait for the content to be loaded.
49
+ if (!content) {
50
+ return;
51
+ }
52
+
53
+ // Decode the search parameters.
54
+ const { tabList, activeFileId } = decodeTabs({
55
+ tabStateParams,
56
+ content,
57
+ });
58
+
59
+ // Mark the state as initialized.
60
+ isInitialized.current = true;
61
+
62
+ // Update the state.
63
+ tabList.forEach(openTab);
64
+ setActiveFileId(activeFileId);
65
+ }, [
66
+ content,
67
+ searchParams,
68
+ setActiveFileId,
69
+ openTab,
70
+ isInitialized,
71
+ ]);
72
+
73
+ // Track content in a ref so that we can use it in the
74
+ // effect below without triggering the effect when
75
+ // the content changes.
76
+ const contentRef = useRef(content);
77
+ useEffect(() => {
78
+ contentRef.current = content;
79
+ }, [content]);
80
+
81
+ // track setSearchParams in a ref so that we can use it in the
82
+ // effect below without triggering the effect on each render.
83
+ // The definition of `setSearchParams` changes on every render.
84
+ const setSearchParamsRef = useRef(setSearchParams);
85
+ useEffect(() => {
86
+ setSearchParamsRef.current = setSearchParams;
87
+ }, [setSearchParams]);
88
+
89
+ // Update the URL to match the current state whenever
90
+ // the active file or tab list changes.
91
+ useEffect(() => {
92
+ // Safety check to make sure the content is loaded.
93
+ // Should never happen.
94
+ if (!contentRef.current) {
95
+ return;
96
+ }
97
+
98
+ const newTabStateParams: TabStateParams = encodeTabs({
99
+ tabList,
100
+ activeFileId,
101
+ content: contentRef.current,
102
+ });
103
+
104
+ // Update the URL if the search parameters have changed.
105
+ setSearchParamsRef.current(
106
+ (oldSearchParams: URLSearchParams) => {
107
+ // Create a copy of the old search params
108
+ const updatedSearchParams = new URLSearchParams(
109
+ oldSearchParams,
110
+ );
111
+
112
+ // Set the 'file' parameter
113
+ if (newTabStateParams.file) {
114
+ updatedSearchParams.set(
115
+ 'file',
116
+ newTabStateParams.file,
117
+ );
118
+ } else {
119
+ updatedSearchParams.delete('file'); // Remove 'file' if it's not present in newTabStateParams
120
+ }
121
+
122
+ // Set the 'tabs' parameter
123
+ if (newTabStateParams.tabs) {
124
+ updatedSearchParams.set(
125
+ 'tabs',
126
+ newTabStateParams.tabs,
127
+ );
128
+ } else {
129
+ updatedSearchParams.delete('tabs'); // Remove 'tabs' if it's not present in newTabStateParams
130
+ }
131
+
132
+ // Return the updated search params
133
+ return updatedSearchParams;
134
+ },
135
+ );
136
+ }, [tabList, activeFileId]);
137
+ };