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,158 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { PresenceId, Username } from '../../types';
3
+ import './style.scss';
4
+
5
+ type PresenceNotification = {
6
+ user: Username;
7
+ join: boolean;
8
+ presenceId: PresenceId;
9
+ };
10
+
11
+ export const extractTimestampFromId = (
12
+ id: string,
13
+ ): number => {
14
+ const [timestampPart] = id.split('-');
15
+ return parseInt(timestampPart, 36);
16
+ };
17
+
18
+ export const shouldShowNotification = (
19
+ remotePresenceId: PresenceId,
20
+ localPresenceId: PresenceId,
21
+ ): boolean => {
22
+ const remotePresenceTimestamp = extractTimestampFromId(
23
+ remotePresenceId,
24
+ );
25
+ const localPresenceTimestamp =
26
+ extractTimestampFromId(localPresenceId);
27
+ return remotePresenceTimestamp > localPresenceTimestamp;
28
+ };
29
+
30
+ export const handleJoin = (
31
+ presenceId: PresenceId,
32
+ username: Username,
33
+ alreadyJoined: React.MutableRefObject<
34
+ Map<PresenceId, Username>
35
+ >,
36
+ setPresenceNotifications: any,
37
+ ) => {
38
+ if (!alreadyJoined.current.has(presenceId)) {
39
+ alreadyJoined.current.set(presenceId, username);
40
+ setPresenceNotifications(
41
+ (prev: PresenceNotification[]) => [
42
+ ...prev,
43
+ { user: username, join: true, presenceId },
44
+ ],
45
+ );
46
+ } else {
47
+ alreadyJoined.current.set(presenceId, username); // Update in case username changed
48
+ }
49
+ };
50
+
51
+ export const handleLeave = (
52
+ presenceId: PresenceId,
53
+ alreadyJoined: React.MutableRefObject<
54
+ Map<PresenceId, Username>
55
+ >,
56
+ setPresenceNotifications: any,
57
+ ) => {
58
+ const user = alreadyJoined.current.get(presenceId);
59
+ if (user) {
60
+ alreadyJoined.current.delete(presenceId);
61
+ setPresenceNotifications(
62
+ (prev: PresenceNotification[]) => [
63
+ ...prev,
64
+ { user, join: false, presenceId },
65
+ ],
66
+ );
67
+ }
68
+ };
69
+
70
+ export const PresenceNotifications = ({
71
+ docPresence,
72
+ localPresence,
73
+ }: {
74
+ docPresence?: any;
75
+ localPresence?: any;
76
+ }) => {
77
+ // `presenceNotifications`:
78
+ // - State variable containing a list of user presence notifications.
79
+ // - Each notification indicates if a user has joined or left a session.
80
+ const [presenceNotifications, setPresenceNotifications] =
81
+ useState<PresenceNotification[]>([]);
82
+
83
+ // `alreadyJoined`:
84
+ // - Ref (persistent across re-renders) containing a Map.
85
+ // - Map's key: `PresenceId` (unique identifier for user's presence).
86
+ // - Map's value: `Username` of the associated user.
87
+ // - Tracks users who have joined the session and their possible username changes.
88
+ const alreadyJoined = useRef<Map<PresenceId, Username>>(
89
+ new Map(),
90
+ );
91
+
92
+ useEffect(() => {
93
+ if (docPresence) {
94
+ const handleReceive = (
95
+ presenceId: PresenceId,
96
+ update: any,
97
+ ) => {
98
+ const join = update !== null;
99
+ if (
100
+ shouldShowNotification(
101
+ presenceId,
102
+ localPresence.presenceId,
103
+ )
104
+ ) {
105
+ if (join) {
106
+ const username =
107
+ docPresence.remotePresences[presenceId]
108
+ ?.username;
109
+ handleJoin(
110
+ presenceId,
111
+ username,
112
+ alreadyJoined,
113
+ setPresenceNotifications,
114
+ );
115
+ } else {
116
+ handleLeave(
117
+ presenceId,
118
+ alreadyJoined,
119
+ setPresenceNotifications,
120
+ );
121
+ }
122
+
123
+ const timeoutId = setTimeout(() => {
124
+ setPresenceNotifications((prev) =>
125
+ prev.slice(1),
126
+ );
127
+ }, 5000);
128
+
129
+ return () => {
130
+ clearTimeout(timeoutId);
131
+ };
132
+ }
133
+ };
134
+
135
+ docPresence.on('receive', handleReceive);
136
+ return () => {
137
+ docPresence.off('receive', handleReceive);
138
+ };
139
+ }
140
+ }, [docPresence, localPresence]);
141
+
142
+ return presenceNotifications.length > 0 ? (
143
+ <div className="vz-notification">
144
+ {presenceNotifications.map(
145
+ ({ user, join, presenceId }) => (
146
+ <div
147
+ key={presenceId}
148
+ className="vz-notification-item"
149
+ >
150
+ {join
151
+ ? `${user} has joined the session.`
152
+ : `${user} has left the session.`}
153
+ </div>
154
+ ),
155
+ )}
156
+ </div>
157
+ ) : null;
158
+ };
@@ -0,0 +1,17 @@
1
+ .vz-notification {
2
+ position: fixed;
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 10px;
6
+ bottom: 20px;
7
+ right: 20px;
8
+
9
+ color: rgb(154, 160, 172);
10
+ padding: 15px;
11
+
12
+ .vz-notification-item {
13
+ background-color: rgb(54, 65, 89);
14
+ padding: 5px;
15
+ border-radius: 5px;
16
+ }
17
+ }
@@ -0,0 +1,117 @@
1
+ import {
2
+ useCallback,
3
+ useContext,
4
+ useEffect,
5
+ useState,
6
+ } from 'react';
7
+ import { PlaySVG } from '../Icons';
8
+ import { SplitEditorSVG } from '../Icons/SplitEditorSVG';
9
+ import { VZCodeContext } from '../VZCodeContext';
10
+ import { OverlayTrigger, Tooltip } from '../bootstrap';
11
+
12
+ import './style.scss';
13
+
14
+ // Feature flag for split pane feature (WIP)
15
+ const enableSplitPane = false;
16
+
17
+ export const RunCodeWidget = ({
18
+ runCodeWidgetTooltipText = (
19
+ <>
20
+ <strong>Run Code</strong>
21
+ <div>(Shift + Enter or Ctrl + s)</div>
22
+ </>
23
+ ),
24
+ }: {
25
+ runCodeWidgetTooltipText?: JSX.Element;
26
+ }) => {
27
+ const { runCodeRef, runPrettierRef, splitCurrentPane } =
28
+ useContext(VZCodeContext);
29
+ const [isRunning, setIsRunning] = useState(false);
30
+
31
+ const handleClick = useCallback(
32
+ (event?: React.MouseEvent) => {
33
+ setIsRunning(true); // Set the running state to true
34
+
35
+ // Run Prettier
36
+ const runPrettier = runPrettierRef.current;
37
+ if (runPrettier !== null) {
38
+ runPrettier();
39
+ }
40
+
41
+ // Run the code
42
+ const runCode = runCodeRef.current;
43
+ if (runCode !== null) {
44
+ runCode();
45
+ }
46
+
47
+ // Optional: reset the icon state after animation completes (e.g., 1 second)
48
+ setTimeout(() => setIsRunning(false), 1000);
49
+ },
50
+ [runCodeRef, runPrettierRef],
51
+ );
52
+
53
+ const handleSplitEditor = useCallback(() => {
54
+ splitCurrentPane();
55
+ }, [splitCurrentPane]);
56
+
57
+ // Add the keyboard event listener for Ctrl+S and Shift+Enter
58
+ useEffect(() => {
59
+ const handleKeyDown = (event: KeyboardEvent) => {
60
+ if (
61
+ (event.ctrlKey && event.key === 's') ||
62
+ (event.shiftKey && event.key === 'Enter')
63
+ ) {
64
+ event.preventDefault();
65
+ handleClick();
66
+ }
67
+ };
68
+
69
+ window.addEventListener('keydown', handleKeyDown);
70
+
71
+ // Cleanup the event listener on component unmount
72
+ return () => {
73
+ window.removeEventListener('keydown', handleKeyDown);
74
+ };
75
+ }, [handleClick]);
76
+
77
+ return (
78
+ <div>
79
+ <div className="vz-code-split-view-widget">
80
+ {enableSplitPane && (
81
+ <OverlayTrigger
82
+ placement="bottom"
83
+ overlay={
84
+ <Tooltip id="fork-file-tooltip">
85
+ Split Editor
86
+ </Tooltip>
87
+ }
88
+ >
89
+ <i
90
+ onClick={handleSplitEditor}
91
+ className="icon-button icon-button-dark"
92
+ >
93
+ <SplitEditorSVG />
94
+ </i>
95
+ </OverlayTrigger>
96
+ )}
97
+ </div>
98
+ <div className="vz-code-run-code-widget">
99
+ <OverlayTrigger
100
+ placement="bottom"
101
+ overlay={
102
+ <Tooltip id="run-code-widget-tooltip">
103
+ {runCodeWidgetTooltipText}
104
+ </Tooltip>
105
+ }
106
+ >
107
+ <i
108
+ className={`icon-button icon-button-dark ${isRunning ? 'rotate-icon' : ''}`}
109
+ onClick={handleClick}
110
+ >
111
+ <PlaySVG />
112
+ </i>
113
+ </OverlayTrigger>
114
+ </div>
115
+ </div>
116
+ );
117
+ };
@@ -0,0 +1,24 @@
1
+ .vz-code-run-code-widget {
2
+ position: absolute;
3
+ right: 16px;
4
+ top: 9px;
5
+ color: var(--vh-color-neutral-04);
6
+ .rotate-icon {
7
+ animation: spin 1s linear;
8
+ }
9
+ }
10
+ .vz-code-split-view-widget {
11
+ position: absolute;
12
+ right: 51px;
13
+ top: 9px;
14
+ color: var(--vh-color-neutral-04);
15
+ }
16
+
17
+ @keyframes spin {
18
+ from {
19
+ transform: rotate(0deg);
20
+ }
21
+ to {
22
+ transform: rotate(360deg);
23
+ }
24
+ }
@@ -0,0 +1,159 @@
1
+ // From https://github.com/vizhub-core/vizhub/edit/main/vizhub-v2/packages/neoFrontend/src/pages/VizPage/SplitPaneResizeContext/index.js
2
+ import {
3
+ createContext,
4
+ useReducer,
5
+ useCallback,
6
+ useEffect,
7
+ } from 'react';
8
+
9
+ export type Side = 'left' | 'right';
10
+
11
+ export type SplitPaneResizeContextValue = {
12
+ codeEditorWidth: number;
13
+ sidebarWidth: number;
14
+ moveSplitPane: (a: number, side: Side) => void;
15
+ isDraggingRight: boolean;
16
+ isDraggingLeft: boolean;
17
+ setIsDragging: (a: boolean, side: Side) => void;
18
+ setSidebarView: (isAIChatOpen: boolean) => void;
19
+ };
20
+
21
+ // Default widths for different sidebar views
22
+ const filesViewWidth = 300;
23
+ const aiChatViewWidth = 800;
24
+ const initialCodeEditorWidthDefault = 800;
25
+
26
+ const initialValue: SplitPaneResizeContextValue = {
27
+ codeEditorWidth: initialCodeEditorWidthDefault,
28
+ sidebarWidth: filesViewWidth, // Default to files view
29
+ moveSplitPane: () => {},
30
+ isDraggingRight: false,
31
+ isDraggingLeft: false,
32
+ setIsDragging: () => {},
33
+ setSidebarView: () => {},
34
+ };
35
+
36
+ export const SplitPaneResizeContext =
37
+ createContext<SplitPaneResizeContextValue>(initialValue);
38
+
39
+ type SplitPaneState = {
40
+ // The width of the sidebar, in pixels.
41
+ sidebarWidth: number;
42
+ codeEditorWidth: number;
43
+ isDraggingLeft: boolean;
44
+ isDraggingRight: boolean;
45
+ };
46
+
47
+ const initialState: SplitPaneState = {
48
+ sidebarWidth: filesViewWidth,
49
+ codeEditorWidth: initialCodeEditorWidthDefault,
50
+ isDraggingLeft: false,
51
+ isDraggingRight: false,
52
+ };
53
+
54
+ type SplitPaneAction =
55
+ | {
56
+ type: 'move';
57
+ side: Side;
58
+ movementX: number;
59
+ }
60
+ | {
61
+ type: 'setIsDragging';
62
+ side: Side;
63
+ isDragging: boolean;
64
+ }
65
+ | {
66
+ type: 'setSidebarView';
67
+ isAIChatOpen: boolean;
68
+ };
69
+
70
+ const splitPaneReducer = (
71
+ state: SplitPaneState,
72
+ action: SplitPaneAction,
73
+ ) => {
74
+ switch (action.type) {
75
+ case 'move':
76
+ return action.side === 'left'
77
+ ? {
78
+ ...state,
79
+ sidebarWidth:
80
+ state.sidebarWidth + action.movementX,
81
+ }
82
+ : {
83
+ ...state,
84
+ codeEditorWidth:
85
+ state.codeEditorWidth + action.movementX,
86
+ };
87
+ // Handle setIsDragging
88
+ case 'setIsDragging':
89
+ return action.side === 'left'
90
+ ? {
91
+ ...state,
92
+ isDraggingLeft: action.isDragging,
93
+ }
94
+ : {
95
+ ...state,
96
+ isDraggingRight: action.isDragging,
97
+ };
98
+ case 'setSidebarView':
99
+ return {
100
+ ...state,
101
+ sidebarWidth: action.isAIChatOpen
102
+ ? aiChatViewWidth
103
+ : filesViewWidth,
104
+ };
105
+ default:
106
+ return state;
107
+ }
108
+ };
109
+
110
+ export const SplitPaneResizeProvider = ({ children }) => {
111
+ const [state, dispatch] = useReducer(
112
+ splitPaneReducer,
113
+ initialState,
114
+ );
115
+
116
+ const setIsDragging = useCallback(
117
+ (isDragging: boolean, side: Side) => {
118
+ dispatch({ type: 'setIsDragging', isDragging, side });
119
+ },
120
+ [dispatch],
121
+ );
122
+
123
+ const moveSplitPane = useCallback(
124
+ (movementX: number, side: Side) => {
125
+ dispatch({ type: 'move', movementX, side });
126
+ },
127
+ [dispatch],
128
+ );
129
+
130
+ const setSidebarView = useCallback(
131
+ (isAIChatOpen: boolean) => {
132
+ dispatch({ type: 'setSidebarView', isAIChatOpen });
133
+ },
134
+ [dispatch],
135
+ );
136
+
137
+ const {
138
+ sidebarWidth,
139
+ codeEditorWidth,
140
+ isDraggingLeft,
141
+ isDraggingRight,
142
+ } = state;
143
+
144
+ const value: SplitPaneResizeContextValue = {
145
+ codeEditorWidth,
146
+ sidebarWidth,
147
+ moveSplitPane,
148
+ isDraggingLeft,
149
+ isDraggingRight,
150
+ setIsDragging,
151
+ setSidebarView,
152
+ };
153
+
154
+ return (
155
+ <SplitPaneResizeContext.Provider value={value}>
156
+ {children}
157
+ </SplitPaneResizeContext.Provider>
158
+ );
159
+ };
@@ -0,0 +1,73 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import type { TabState } from '../../types';
3
+ import type { VizFileId } from '@vizhub/viz-types';
4
+ import { CloseSVG } from '../Icons';
5
+
6
+ // Supports adding the file's containing folder to the tab name
7
+ const fileNameSplit = (fileName: string) => {
8
+ const split = fileName.split('/');
9
+ if (split.length === 1) return split[split.length - 1];
10
+ return (
11
+ split[split.length - 2] + '/' + split[split.length - 1]
12
+ );
13
+ };
14
+
15
+ export const Tab = ({
16
+ fileId,
17
+ isTransient,
18
+ isActive,
19
+ setActiveFileId,
20
+ openTab,
21
+ closeTabs,
22
+ fileName,
23
+ }: {
24
+ fileId: VizFileId;
25
+ isTransient?: boolean;
26
+ isActive: boolean;
27
+ setActiveFileId: (fileId: VizFileId) => void;
28
+ openTab: (tabState: TabState) => void;
29
+ closeTabs: (fileIds: VizFileId[]) => void;
30
+ fileName: string;
31
+ }) => {
32
+ const handleCloseClick = useCallback(
33
+ (event: React.MouseEvent) => {
34
+ // Stop propagation so that the outer listener doesn't fire.
35
+ event.stopPropagation();
36
+
37
+ closeTabs([fileId]);
38
+ },
39
+ [closeTabs, fileId],
40
+ );
41
+
42
+ const tabName = useMemo(
43
+ () => fileNameSplit(fileName),
44
+ [fileName],
45
+ );
46
+
47
+ const handleClick = useCallback(() => {
48
+ setActiveFileId(fileId);
49
+ }, [fileId, setActiveFileId]);
50
+
51
+ // Double-clicking a transient tab makes it a persistent tab.
52
+ const handleDoubleClick = useCallback(() => {
53
+ openTab({ fileId });
54
+ }, [fileId, openTab]);
55
+
56
+ return (
57
+ <div
58
+ className={`tab user-select-none ${
59
+ isActive ? 'active' : ''
60
+ } ${isTransient ? 'transient' : ''}`}
61
+ onClick={handleClick}
62
+ onDoubleClick={handleDoubleClick}
63
+ >
64
+ {tabName}
65
+ <i
66
+ className="icon-button icon-button-dark"
67
+ onClick={handleCloseClick}
68
+ >
69
+ <CloseSVG />
70
+ </i>
71
+ </div>
72
+ );
73
+ };
@@ -0,0 +1,35 @@
1
+ import { useContext } from 'react';
2
+ import { TabState } from '../../types';
3
+ import { VZCodeContext } from '../VZCodeContext';
4
+ import { Tab } from './Tab';
5
+ import './style.scss';
6
+
7
+ // Displays the list of tabs above the code editor.
8
+ export const TabList = ({ activeFileId, tabList }) => {
9
+ const { files, setActiveFileId, openTab, closeTabs } =
10
+ useContext(VZCodeContext);
11
+
12
+ return (
13
+ <div className="vz-tab-list">
14
+ {files &&
15
+ tabList
16
+ // Handles the case that the tab list references a file that was deleted.
17
+ .filter(
18
+ (tabState: TabState) =>
19
+ tabState.fileId in files,
20
+ )
21
+ .map((tabState: TabState) => (
22
+ <Tab
23
+ key={tabState.fileId}
24
+ fileId={tabState.fileId}
25
+ isTransient={tabState.isTransient}
26
+ isActive={tabState.fileId === activeFileId}
27
+ setActiveFileId={setActiveFileId}
28
+ openTab={openTab}
29
+ closeTabs={closeTabs}
30
+ fileName={files[tabState.fileId].name}
31
+ />
32
+ ))}
33
+ </div>
34
+ );
35
+ };
@@ -0,0 +1,51 @@
1
+ .vz-tab-list {
2
+ display: flex;
3
+ background-color: #1f2022;
4
+ // When the tab list becomes too large, a horizontal scroll bar appears.
5
+ overflow-x: auto;
6
+
7
+ .tab {
8
+ // background: #47546b;
9
+ color: #fff;
10
+ font-family: var(--vzcode-font-family);
11
+ font-weight: 500;
12
+ align-items: center;
13
+ display: flex;
14
+ gap: 6px;
15
+ padding: 4px 4px 4px 14px;
16
+ border-top-left-radius: 8px;
17
+ border-top-right-radius: 8px;
18
+ margin-top: 6px;
19
+ margin-left: 2px;
20
+ margin-right: 2px;
21
+ cursor: pointer;
22
+ // Make sure text doesn't wrap within tabs.
23
+ white-space: nowrap;
24
+
25
+ // No left margin for first child
26
+ &:first-child {
27
+ margin-left: 0;
28
+ }
29
+
30
+ // on hover
31
+ &:hover {
32
+ // background-color: var(--vh-color-hover-dark);
33
+ background-color: rgba(255, 255, 255, 0.075);
34
+ }
35
+ }
36
+
37
+ .tab.active {
38
+ background-color: #202e46;
39
+ }
40
+
41
+ .tab.transient {
42
+ font-style: italic;
43
+ }
44
+
45
+ .icon-button {
46
+ color: var(--vh-color-neutral-03);
47
+ }
48
+ .icon-button:hover {
49
+ color: var(--vh-color-action-03);
50
+ }
51
+ }
@@ -0,0 +1,29 @@
1
+ import React, { Context, createContext } from 'react';
2
+ import {
3
+ VZCodeContextValue,
4
+ VZCodeProviderProps,
5
+ } from './types';
6
+ import { useVZCodeState } from './useVZCodeState';
7
+
8
+ // This context centralizes all the "smart" logic
9
+ // to do with the application state. This includes
10
+ // * Accessing and manipulating ShareDB data
11
+ // * Centralized application state
12
+ export const VZCodeContext: Context<VZCodeContextValue | null> =
13
+ createContext<VZCodeContextValue>(null);
14
+
15
+ export const VZCodeProvider: React.FC<
16
+ VZCodeProviderProps
17
+ > = ({ children, ...props }) => {
18
+ // Use the custom hook to get all the context value
19
+ const value = useVZCodeState(props);
20
+
21
+ return (
22
+ <VZCodeContext.Provider value={value}>
23
+ {children}
24
+ </VZCodeContext.Provider>
25
+ );
26
+ };
27
+
28
+ // Re-export types for backward compatibility
29
+ export type { VZCodeContextValue, VZCodeProviderProps };