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,117 @@
1
+ import {
2
+ useState,
3
+ useEffect,
4
+ useCallback,
5
+ Dispatch,
6
+ SetStateAction,
7
+ } from 'react';
8
+
9
+ export interface UseSpeechRecognitionResult {
10
+ isSpeaking: boolean;
11
+ toggleSpeechRecognition: () => void;
12
+ stopSpeaking: () => void;
13
+ }
14
+
15
+ export const useSpeechRecognition = (
16
+ onTranscriptChange: Dispatch<SetStateAction<string>>,
17
+ ): UseSpeechRecognitionResult => {
18
+ const [isSpeaking, setIsSpeaking] =
19
+ useState<boolean>(false);
20
+ const [recognition, setRecognition] =
21
+ // @ts-ignore
22
+ useState<SpeechRecognition | null>(null);
23
+ const [finalTranscript, setFinalTranscript] =
24
+ useState<string>('');
25
+
26
+ // Speech recognition setup
27
+ useEffect(() => {
28
+ // Check if browser supports SpeechRecognition
29
+ const SpeechRecognition =
30
+ // @ts-ignore
31
+ window.SpeechRecognition ||
32
+ // @ts-ignore
33
+ window.webkitSpeechRecognition;
34
+
35
+ if (SpeechRecognition) {
36
+ const recognitionInstance = new SpeechRecognition();
37
+ recognitionInstance.continuous = true;
38
+ recognitionInstance.interimResults = true;
39
+
40
+ recognitionInstance.onresult = (event) => {
41
+ let finalText = '';
42
+ let interimText = '';
43
+
44
+ // Process all results to separate final and interim text
45
+ for (let i = 0; i < event.results.length; i++) {
46
+ const result = event.results[i];
47
+ if (result.isFinal) {
48
+ finalText += result[0].transcript;
49
+ } else {
50
+ interimText += result[0].transcript;
51
+ }
52
+ }
53
+
54
+ // Update our final transcript state
55
+ setFinalTranscript(finalText);
56
+
57
+ // Combine final and interim text and update the prompt
58
+ const fullTranscript = finalText + interimText;
59
+ onTranscriptChange(fullTranscript);
60
+ };
61
+
62
+ recognitionInstance.onerror = (event) => {
63
+ console.error(
64
+ 'Speech recognition error',
65
+ event.error,
66
+ );
67
+ setIsSpeaking(false);
68
+ };
69
+
70
+ recognitionInstance.onend = () => {
71
+ setIsSpeaking(false);
72
+ // Reset final transcript when recognition ends
73
+ setFinalTranscript('');
74
+ };
75
+
76
+ setRecognition(recognitionInstance);
77
+
78
+ return () => {
79
+ recognitionInstance.abort();
80
+ };
81
+ }
82
+ }, [onTranscriptChange]);
83
+
84
+ const toggleSpeechRecognition = useCallback(() => {
85
+ if (!recognition) {
86
+ console.error(
87
+ 'Speech recognition not supported in this browser',
88
+ );
89
+ return;
90
+ }
91
+
92
+ if (isSpeaking) {
93
+ recognition.stop();
94
+ setIsSpeaking(false);
95
+ } else {
96
+ // Reset final transcript when starting new recognition
97
+ setFinalTranscript('');
98
+ recognition.start();
99
+ setIsSpeaking(true);
100
+ }
101
+ }, [isSpeaking, recognition]);
102
+
103
+ const stopSpeaking = useCallback(() => {
104
+ if (recognition && isSpeaking) {
105
+ recognition.stop();
106
+ setIsSpeaking(false);
107
+ // Reset final transcript when manually stopping
108
+ setFinalTranscript('');
109
+ }
110
+ }, [isSpeaking, recognition]);
111
+
112
+ return {
113
+ isSpeaking,
114
+ toggleSpeechRecognition,
115
+ stopSpeaking,
116
+ };
117
+ };
@@ -0,0 +1,111 @@
1
+ import {
2
+ useState,
3
+ useCallback,
4
+ useRef,
5
+ useEffect,
6
+ useContext,
7
+ } from 'react';
8
+ import { Modal, Form, Button } from '../bootstrap';
9
+ import { VZCodeContext } from '../VZCodeContext';
10
+
11
+ export const CreateDirModal = ({
12
+ initialFileName = '',
13
+ }) => {
14
+ const [newName, setNewName] = useState(initialFileName);
15
+ const inputRef = useRef(null);
16
+
17
+ const {
18
+ files,
19
+ isCreateDirModalOpen,
20
+ handleCloseCreateDirModal,
21
+ handleCreateDirClick,
22
+ } = useContext(VZCodeContext);
23
+
24
+ useEffect(() => {
25
+ if (isCreateDirModalOpen && inputRef.current) {
26
+ inputRef.current.focus();
27
+ }
28
+ }, [isCreateDirModalOpen]);
29
+
30
+ const handleNameChange = useCallback((event) => {
31
+ setNewName(event.target.value);
32
+ }, []);
33
+
34
+ const onCreateClick = useCallback(() => {
35
+ handleCreateDirClick(newName);
36
+ setNewName('');
37
+ }, [newName, handleCreateDirClick]);
38
+
39
+ // Returns true if file name is valid, false otherwise.
40
+ const validateFileName = useCallback(
41
+ (fileName: string) => {
42
+ let valid;
43
+ //General Character Check
44
+ const regex =
45
+ /^[a-zA-Z0-9](?:[a-zA-Z0-9 ./+=_-]*[a-zA-Z0-9])?$/;
46
+ valid = regex.test(fileName);
47
+
48
+ //Check for Duplicate Filename
49
+ for (const key in files) {
50
+ if (fileName + '/' === files[key].name) {
51
+ valid = false;
52
+ }
53
+ }
54
+
55
+ return valid;
56
+ },
57
+ [files],
58
+ );
59
+
60
+ const handleKeyDown = useCallback(
61
+ (e) => {
62
+ if (
63
+ e.key === 'Enter' &&
64
+ validateFileName(newName) &&
65
+ (e.ctrlKey ||
66
+ e.shiftKey ||
67
+ (!e.altKey && !e.metaKey))
68
+ ) {
69
+ onCreateClick();
70
+ }
71
+ },
72
+ [onCreateClick, validateFileName, newName],
73
+ );
74
+
75
+ return isCreateDirModalOpen ? (
76
+ <Modal
77
+ show={isCreateDirModalOpen}
78
+ onHide={handleCloseCreateDirModal}
79
+ animation={false}
80
+ onKeyDown={handleKeyDown}
81
+ >
82
+ <Modal.Header closeButton>
83
+ <Modal.Title>Create Directory</Modal.Title>
84
+ </Modal.Header>
85
+ <Modal.Body>
86
+ <Form.Group className="mb-3" controlId="fileName">
87
+ <Form.Label>Directory Name</Form.Label>
88
+ <Form.Control
89
+ type="text"
90
+ value={newName}
91
+ onChange={handleNameChange}
92
+ ref={inputRef}
93
+ spellCheck="false"
94
+ />
95
+ <Form.Text className="text-muted">
96
+ Enter the name for your file
97
+ </Form.Text>
98
+ </Form.Group>
99
+ </Modal.Body>
100
+ <Modal.Footer>
101
+ <Button
102
+ variant="primary"
103
+ onClick={onCreateClick}
104
+ disabled={!validateFileName(newName)}
105
+ >
106
+ Create Directory
107
+ </Button>
108
+ </Modal.Footer>
109
+ </Modal>
110
+ ) : null;
111
+ };
@@ -0,0 +1,111 @@
1
+ import {
2
+ useState,
3
+ useCallback,
4
+ useRef,
5
+ useEffect,
6
+ useContext,
7
+ } from 'react';
8
+ import { Modal, Form, Button } from '../bootstrap';
9
+ import { VZCodeContext } from '../VZCodeContext';
10
+
11
+ export const CreateFileModal = ({
12
+ initialFileName = '',
13
+ }) => {
14
+ const [newName, setNewName] = useState(initialFileName);
15
+ const inputRef = useRef(null);
16
+
17
+ const {
18
+ files,
19
+ isCreateFileModalOpen,
20
+ handleCloseCreateFileModal,
21
+ handleCreateFileClick,
22
+ } = useContext(VZCodeContext);
23
+
24
+ useEffect(() => {
25
+ if (isCreateFileModalOpen && inputRef.current) {
26
+ inputRef.current.focus();
27
+ }
28
+ }, [isCreateFileModalOpen]);
29
+
30
+ const handleNameChange = useCallback((event) => {
31
+ setNewName(event.target.value);
32
+ }, []);
33
+
34
+ const onCreateClick = useCallback(() => {
35
+ handleCreateFileClick(newName);
36
+ setNewName('');
37
+ }, [newName, handleCreateFileClick]);
38
+
39
+ // Returns true if file name is valid, false otherwise.
40
+ const validateFileName = useCallback(
41
+ (fileName: string) => {
42
+ let valid;
43
+ //General Character Check
44
+ const regex =
45
+ /^[a-zA-Z0-9](?:[a-zA-Z0-9 ./+=_-]*[a-zA-Z0-9])?$/;
46
+ valid = regex.test(fileName);
47
+
48
+ //Check for Duplicate Filename
49
+ for (const key in files) {
50
+ if (fileName === files[key].name) {
51
+ valid = false;
52
+ }
53
+ }
54
+
55
+ return valid;
56
+ },
57
+ [files],
58
+ );
59
+
60
+ const handleKeyDown = useCallback(
61
+ (e) => {
62
+ if (
63
+ e.key === 'Enter' &&
64
+ validateFileName(newName) &&
65
+ (e.ctrlKey ||
66
+ e.shiftKey ||
67
+ (!e.altKey && !e.metaKey))
68
+ ) {
69
+ onCreateClick();
70
+ }
71
+ },
72
+ [onCreateClick, validateFileName, newName],
73
+ );
74
+
75
+ return isCreateFileModalOpen ? (
76
+ <Modal
77
+ show={isCreateFileModalOpen}
78
+ onHide={handleCloseCreateFileModal}
79
+ animation={false}
80
+ onKeyDown={handleKeyDown}
81
+ >
82
+ <Modal.Header closeButton>
83
+ <Modal.Title>Create File</Modal.Title>
84
+ </Modal.Header>
85
+ <Modal.Body>
86
+ <Form.Group className="mb-3" controlId="fileName">
87
+ <Form.Label>File Name</Form.Label>
88
+ <Form.Control
89
+ type="text"
90
+ value={newName}
91
+ onChange={handleNameChange}
92
+ ref={inputRef}
93
+ spellCheck="false"
94
+ />
95
+ <Form.Text className="text-muted">
96
+ Enter the name for your file
97
+ </Form.Text>
98
+ </Form.Group>
99
+ </Modal.Body>
100
+ <Modal.Footer>
101
+ <Button
102
+ variant="primary"
103
+ onClick={onCreateClick}
104
+ disabled={!validateFileName(newName)}
105
+ >
106
+ Create File
107
+ </Button>
108
+ </Modal.Footer>
109
+ </Modal>
110
+ ) : null;
111
+ };
@@ -0,0 +1,76 @@
1
+ import { Button, Modal } from '../bootstrap';
2
+ import { useCallback, useEffect } from 'react';
3
+
4
+ // Confirmation modal for deleting a file or directory.
5
+ // Inspired by https://react-bootstrap.netlify.app/docs/components/modal/#live-demo
6
+
7
+ export const DeleteConfirmationModal = ({
8
+ show,
9
+ onClose,
10
+ onConfirm,
11
+ isDirectory,
12
+ name,
13
+ }) => {
14
+ const handleEnterKey = useCallback(
15
+ (event: KeyboardEvent) => {
16
+ if (event.key === 'Enter') {
17
+ onConfirm();
18
+ }
19
+ },
20
+ [onConfirm],
21
+ );
22
+
23
+ useEffect(() => {
24
+ if (show) {
25
+ window.addEventListener('keydown', handleEnterKey);
26
+ } else {
27
+ window.removeEventListener('keydown', handleEnterKey);
28
+ }
29
+
30
+ return () => {
31
+ window.removeEventListener('keydown', handleEnterKey);
32
+ };
33
+ }, [show, handleEnterKey]);
34
+
35
+ return (
36
+ <Modal show={show} onHide={onClose} centered>
37
+ <Modal.Header closeButton onClick={onClose}>
38
+ <Modal.Title>
39
+ Delete{' '}
40
+ {name === 'all files'
41
+ ? 'All Files'
42
+ : isDirectory
43
+ ? 'Directory'
44
+ : 'File'}
45
+ </Modal.Title>
46
+ </Modal.Header>
47
+
48
+ <Modal.Body>
49
+ <p>
50
+ {name === 'all files' ? (
51
+ 'This will delete all files in the project.'
52
+ ) : (
53
+ <>
54
+ This will delete the{' '}
55
+ {isDirectory ? 'directory' : 'file'}{' '}
56
+ <strong>{name}</strong>.
57
+ </>
58
+ )}
59
+ </p>
60
+ </Modal.Body>
61
+
62
+ <Modal.Footer>
63
+ <Button
64
+ variant="secondary"
65
+ type="button"
66
+ onClick={onClose}
67
+ >
68
+ Cancel
69
+ </Button>
70
+ <Button variant="primary" onClick={onConfirm}>
71
+ Delete
72
+ </Button>
73
+ </Modal.Footer>
74
+ </Modal>
75
+ );
76
+ };
@@ -0,0 +1,89 @@
1
+ import { useCallback, useContext, useMemo } from 'react';
2
+ import type { FileTree, FileTreeFile } from '../../types';
3
+ import { DirectoryArrowSVG } from '../Icons';
4
+ import { VZCodeContext } from '../VZCodeContext';
5
+ import { Item } from './Item';
6
+ import { Listing } from './Listing';
7
+
8
+ export const DirectoryListing = ({
9
+ name,
10
+ path,
11
+ children,
12
+ handleFileClick,
13
+ handleFileDoubleClick,
14
+ }: {
15
+ name: string;
16
+ path: string;
17
+ children: Array<FileTree | FileTreeFile>;
18
+ handleFileClick: (fileId: string) => void;
19
+ handleFileDoubleClick: (fileId: string) => void;
20
+ }) => {
21
+ const {
22
+ renameDirectory,
23
+ deleteDirectory,
24
+ isDirectoryOpen,
25
+ toggleDirectory,
26
+ sidebarPresenceIndicators,
27
+ } = useContext(VZCodeContext);
28
+
29
+ const handleClick = useCallback(() => {
30
+ toggleDirectory(path);
31
+ }, [toggleDirectory, path]);
32
+
33
+ const handleDeleteClick = useCallback(() => {
34
+ deleteDirectory(path);
35
+ }, [deleteDirectory, path]);
36
+
37
+ const handleRenameClick = useCallback(
38
+ (newName: string) => {
39
+ renameDirectory(path, name, newName);
40
+ },
41
+ [renameDirectory, path, name],
42
+ );
43
+
44
+ const isOpen = useMemo(
45
+ () => isDirectoryOpen(path),
46
+ [isDirectoryOpen, path],
47
+ );
48
+
49
+ return (
50
+ <>
51
+ <Item
52
+ id={path}
53
+ name={name}
54
+ handleClick={handleClick}
55
+ handleDeleteClick={handleDeleteClick}
56
+ handleRenameClick={handleRenameClick}
57
+ isDirectory={true}
58
+ >
59
+ <div
60
+ className="arrow-wrapper"
61
+ style={{
62
+ transform: `rotate(${isOpen ? 90 : 0}deg)`,
63
+ }}
64
+ >
65
+ <DirectoryArrowSVG />
66
+ </div>
67
+ <div className="name">{name}</div>
68
+ </Item>
69
+ {children && isOpen ? (
70
+ <div className="indentation">
71
+ {children.map((entity) => {
72
+ const { fileId } = entity as FileTreeFile;
73
+ const { path } = entity as FileTree;
74
+ return (
75
+ <Listing
76
+ entity={entity}
77
+ key={fileId || path}
78
+ handleFileClick={handleFileClick}
79
+ handleFileDoubleClick={
80
+ handleFileDoubleClick
81
+ }
82
+ />
83
+ );
84
+ })}
85
+ </div>
86
+ ) : null}
87
+ </>
88
+ );
89
+ };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+
3
+ interface EmptyStateProps {
4
+ children: React.ReactNode;
5
+ }
6
+
7
+ export const EmptyState: React.FC<EmptyStateProps> = ({
8
+ children,
9
+ }) => {
10
+ return <div className="empty-state">{children}</div>;
11
+ };
@@ -0,0 +1,91 @@
1
+ import { useCallback, useContext } from 'react';
2
+ import { Item } from './Item';
3
+ import { PresenceIndicator } from '../../types';
4
+ import { VizFileId } from '@vizhub/viz-types';
5
+ import { VZCodeContext } from '../VZCodeContext';
6
+ import { FileTypeIcon } from './FileTypeIcon';
7
+ import { assignUserColor } from '../presenceColor';
8
+
9
+ const PresenceIndicators = ({
10
+ presence,
11
+ }: {
12
+ presence: PresenceIndicator[];
13
+ }) => {
14
+ return (
15
+ <div className="presence-indicators">
16
+ {presence.map((indicator, index) => {
17
+ // Get the raw RGB values from assignUserColor
18
+ const color = assignUserColor(indicator.username);
19
+
20
+ // Format it as `rgb()` directly in the component
21
+ const formattedColor = `rgb(${color})`;
22
+
23
+ //console.log(`Username: ${indicator.username}, Assigned Color: ${formattedColor}`);
24
+
25
+ return (
26
+ <div
27
+ key={index}
28
+ className="presence-indicator"
29
+ style={{ backgroundColor: formattedColor }}
30
+ >
31
+ {indicator.username[0]}
32
+ </div>
33
+ );
34
+ })}
35
+ </div>
36
+ );
37
+ };
38
+
39
+ export const FileListing = ({
40
+ name,
41
+ fileId,
42
+ handleFileClick,
43
+ handleFileDoubleClick,
44
+ isActive,
45
+ presence,
46
+ }: {
47
+ name: string;
48
+ fileId: VizFileId;
49
+ handleFileClick: (fileId: VizFileId) => void;
50
+ handleFileDoubleClick: (fileId: VizFileId) => void;
51
+ isActive: boolean;
52
+ presence: PresenceIndicator[];
53
+ }) => {
54
+ const { renameFile, deleteFile } =
55
+ useContext(VZCodeContext);
56
+
57
+ const handleClick = useCallback(() => {
58
+ handleFileClick(fileId);
59
+ }, [fileId, handleFileClick]);
60
+
61
+ const handleDoubleClick = useCallback(() => {
62
+ handleFileDoubleClick(fileId);
63
+ }, [fileId, handleFileDoubleClick]);
64
+
65
+ const handleDeleteClick = useCallback(() => {
66
+ deleteFile(fileId);
67
+ }, [fileId, deleteFile]);
68
+
69
+ const handleRenameClick = useCallback(
70
+ (newName: string) => {
71
+ renameFile(fileId, newName);
72
+ },
73
+ [fileId, renameFile],
74
+ );
75
+
76
+ return (
77
+ <Item
78
+ id={fileId}
79
+ name={name}
80
+ handleClick={handleClick}
81
+ handleDoubleClick={handleDoubleClick}
82
+ handleDeleteClick={handleDeleteClick}
83
+ handleRenameClick={handleRenameClick}
84
+ isActive={isActive}
85
+ >
86
+ <PresenceIndicators presence={presence} />
87
+ <FileTypeIcon name={name} />
88
+ {name}
89
+ </Item>
90
+ );
91
+ };
@@ -0,0 +1,49 @@
1
+ import { useMemo } from 'react';
2
+ import {
3
+ FileSVG,
4
+ JavaScriptSVG,
5
+ TypeScriptSVG,
6
+ ReactSVG,
7
+ SvelteSVG,
8
+ JsonSVG,
9
+ MarkdownSVG,
10
+ HtmlSVG,
11
+ CssSVG,
12
+ } from '../Icons';
13
+
14
+ export function getExtensionIcon(fileName: string) {
15
+ const extension = fileName.split('.');
16
+
17
+ switch (extension[extension.length - 1]) {
18
+ case 'jsx':
19
+ case 'tsx':
20
+ return <ReactSVG />;
21
+ case 'js':
22
+ return <JavaScriptSVG />;
23
+ case 'ts':
24
+ return <TypeScriptSVG />;
25
+ case 'json':
26
+ return <JsonSVG />;
27
+ case 'md':
28
+ return <MarkdownSVG />;
29
+ case 'html':
30
+ return <HtmlSVG />;
31
+ case 'css':
32
+ return <CssSVG />;
33
+ case 'svelte':
34
+ return <SvelteSVG />;
35
+ default:
36
+ return <FileSVG />;
37
+ }
38
+ }
39
+
40
+ // Various neat icons based on the file extension
41
+ export const FileTypeIcon = ({
42
+ name,
43
+ }: {
44
+ name: string;
45
+ }) => (
46
+ <i className="file-icon">
47
+ {useMemo(() => getExtensionIcon(name), [name])}
48
+ </i>
49
+ );