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,250 @@
1
+ import {
2
+ Pane,
3
+ PaneId,
4
+ PresenceIndicator,
5
+ SearchFileVisibility,
6
+ SearchResults,
7
+ ShareDBDoc,
8
+ Username,
9
+ } from '../../types';
10
+ import { VizFileId, VizContent } from '@vizhub/viz-types';
11
+ import { ThemeLabel } from '../themes';
12
+ import { closeTabsReducer } from './closeTabsReducer';
13
+ import { openTabReducer } from './openTabReducer';
14
+ import { setActiveFileIdReducer } from './setActiveFileIdReducer';
15
+ import {
16
+ setActiveFileLeftReducer,
17
+ setActiveFileRightReducer,
18
+ } from './setActiveFileLeftRightReducer';
19
+ import { setIsSettingsOpenReducer } from './setIsSettingsOpenReducer';
20
+ import { setIsDocOpenReducer } from './setIsDocOpenReducer';
21
+ import { setThemeReducer } from './setThemeReducer';
22
+ import { editorNoLongerWantsFocusReducer } from './editorNoLongerWantsFocusReducer';
23
+ import { setUsernameReducer } from './setUsernameReducer';
24
+ import {
25
+ setIsSearchOpenReducer,
26
+ setSearchReducer,
27
+ setSearchResultsReducer,
28
+ setSearchFileVisibilityReducer,
29
+ setSearchLineVisibilityReducer,
30
+ setSearchFocusedIndexReducer,
31
+ toggleSearchFocusedReducer,
32
+ } from './searchReducer';
33
+ import {
34
+ setIsAIChatOpenReducer,
35
+ toggleAIChatFocusedReducer,
36
+ setAIChatModeReducer,
37
+ } from './aiChatReducer';
38
+ import { toggleAutoFollowReducer } from './toggleAutoFollowReducer';
39
+ import { updatePresenceIndicatorReducer } from './updatePresenceIndicatorReducer';
40
+ import { splitCurrentPaneReducer } from './splitCurrentPaneReducer';
41
+ import { setIsVisualEditorOpenReducer } from './visualEditorReducer';
42
+ export { createInitialState } from './createInitialState';
43
+
44
+ // The shape of the state managed by the reducer.
45
+ export type VZState = {
46
+ // The state of the split pane (tree data structure).
47
+ pane: Pane;
48
+
49
+ // The id of the currently active pane.
50
+ activePaneId: PaneId;
51
+
52
+ // The theme that is currently active.
53
+ theme: ThemeLabel;
54
+
55
+ // Search pattern and most recent results based on the current pattern
56
+ search: SearchResults;
57
+
58
+ // True to show the search instead of files
59
+ isSearchOpen: boolean;
60
+
61
+ // True to show the visual editor instead of files
62
+ isVisualEditorOpen: boolean;
63
+
64
+ // True to show the AI chat instead of files
65
+ isAIChatOpen: boolean;
66
+
67
+ // True if the AI chat input should focus on the next render.
68
+ aiChatFocused: boolean;
69
+
70
+ // The current AI chat mode: 'ask' or 'edit'
71
+ aiChatMode: 'ask' | 'edit';
72
+
73
+ // True to show the settings modal.
74
+ isSettingsOpen: boolean;
75
+
76
+ isDocOpen: boolean;
77
+
78
+ // True if the editor should focus on the next render.
79
+ editorWantsFocus: boolean;
80
+
81
+ // The username of the current user.
82
+ username: Username;
83
+
84
+ // True if "auto-follow" is enabled.
85
+ // This is a feature that automatically scrolls the editor to the
86
+ // currently active remote user's cursor position.
87
+ enableAutoFollow: boolean;
88
+
89
+ // The list of remote users and which file they have open,
90
+ // for presence display in the sidebar.
91
+ sidebarPresenceIndicators: Array<PresenceIndicator>;
92
+ };
93
+
94
+ // The shape of the actions that can be dispatched to the reducer.
95
+ export type VZAction =
96
+ // `set_active_file_id`
97
+ // * Sets the active file ID.
98
+ | { type: 'set_active_file_id'; activeFileId: VizFileId }
99
+
100
+ // `set_active_file_left' 'set_active_file_right`
101
+ // * Sets the active file ID to be the tab directly to the left or right
102
+ // * of the currently active tab.
103
+ | { type: 'set_active_file_left' }
104
+ | { type: 'set_active_file_right' }
105
+
106
+ // `open_tab`
107
+ // * Opens a tab.
108
+ // * Also serves to change an already open transient tab to persistent.
109
+ | {
110
+ type: 'open_tab';
111
+ fileId: VizFileId;
112
+ isTransient?: boolean;
113
+ }
114
+
115
+ // `close_tabs`
116
+ // * Closes a set of tabs.
117
+ | {
118
+ type: 'close_tabs';
119
+ fileIdsToClose: Array<VizFileId>;
120
+ // The pane id to close tabs from.
121
+ }
122
+
123
+ // `set_theme`
124
+ // * Sets the theme.
125
+ | { type: 'set_theme'; themeLabel: ThemeLabel }
126
+
127
+ // `set_is_settings_open`
128
+ // * Sets whether the settings modal is open.
129
+ | { type: 'set_is_settings_open'; value: boolean }
130
+ | { type: 'set_is_doc_open'; value: boolean }
131
+
132
+ // `set_is_search_open`
133
+ // * Sets whether the search tab is open.
134
+ | { type: 'set_is_search_open'; value: boolean }
135
+
136
+ // `set_is_ai_chat_open`
137
+ // * Sets whether the AI chat tab is open.
138
+ | { type: 'set_is_ai_chat_open'; value: boolean }
139
+
140
+ // 'set_is_visual_editor_open'
141
+ // * Sets whether the Visual Editor is open.
142
+ | { type: 'set_is_visual_editor_open'; value: boolean }
143
+
144
+ // `toggle_ai_chat_focused`
145
+ // * Toggles focused variable to trigger AI chat input focus
146
+ | { type: 'toggle_ai_chat_focused' }
147
+
148
+ // `set_ai_chat_mode`
149
+ // * Sets the AI chat mode (ask or edit)
150
+ | { type: 'set_ai_chat_mode'; mode: 'ask' | 'edit' }
151
+
152
+ // `set_search`
153
+ // * Sets the current search pattern
154
+ | { type: 'set_search'; value: string }
155
+
156
+ // `set_search_results`
157
+ // * Sets the current search pattern
158
+ | {
159
+ type: 'set_search_results';
160
+ files: ShareDBDoc<VizContent>;
161
+ }
162
+
163
+ // `set_search_results_visibility`
164
+ // * Sets the visibility of a current search pattern file
165
+ | {
166
+ type: 'set_search_file_visibility';
167
+ files: ShareDBDoc<VizContent>;
168
+ id: string;
169
+ visibility: SearchFileVisibility;
170
+ }
171
+
172
+ // `hide_search_results_line`
173
+ // * Hides a current search pattern file's specific matching line
174
+ | {
175
+ type: 'hide_search_results_line';
176
+ files: ShareDBDoc<VizContent>;
177
+ id: string;
178
+ line: number;
179
+ }
180
+
181
+ // `set_active_search_index`
182
+ // * Sets focused (file) index and child focused (line) index for search results
183
+ | {
184
+ type: 'set_active_search_index';
185
+ focusedIndex: number;
186
+ childIndex: number;
187
+ }
188
+
189
+ // `toggle_search_focused`
190
+ // * Toggles focused variable to trigger search input focus
191
+ | { type: 'toggle_search_focused' }
192
+
193
+ // `editor_no_longer_wants_focus`
194
+ // * Sets `editorWantsFocus` to `false`.
195
+ | { type: 'editor_no_longer_wants_focus' }
196
+
197
+ // `set_username`
198
+ // * Sets the username.
199
+ | { type: 'set_username'; username: Username }
200
+
201
+ // `toggle_auto_follow`
202
+ // * Toggles the auto-follow feature.
203
+ | { type: 'toggle_auto_follow' }
204
+
205
+ // `update_presence_indicator
206
+ // * Updates a single presence indicator
207
+ | {
208
+ type: 'update_presence_indicator';
209
+ presenceIndicator: PresenceIndicator;
210
+ }
211
+
212
+ // `split_current_pane`
213
+ | { type: 'split_current_pane' };
214
+
215
+ const reducers = [
216
+ setActiveFileIdReducer,
217
+ setActiveFileLeftReducer,
218
+ setActiveFileRightReducer,
219
+ openTabReducer,
220
+ closeTabsReducer,
221
+ setThemeReducer,
222
+ setSearchReducer,
223
+ setSearchResultsReducer,
224
+ setSearchFileVisibilityReducer,
225
+ setSearchLineVisibilityReducer,
226
+ setSearchFocusedIndexReducer,
227
+ toggleSearchFocusedReducer,
228
+ setIsSearchOpenReducer,
229
+ setIsAIChatOpenReducer,
230
+ setIsVisualEditorOpenReducer,
231
+ toggleAIChatFocusedReducer,
232
+ setAIChatModeReducer,
233
+ setIsSettingsOpenReducer,
234
+ setIsDocOpenReducer,
235
+ editorNoLongerWantsFocusReducer,
236
+ setUsernameReducer,
237
+ toggleAutoFollowReducer,
238
+ updatePresenceIndicatorReducer,
239
+ splitCurrentPaneReducer,
240
+ ];
241
+
242
+ export const vzReducer = (
243
+ state: VZState,
244
+ action: VZAction,
245
+ ): VZState =>
246
+ reducers.reduce(
247
+ (currentState, currentReducer) =>
248
+ currentReducer(currentState, action),
249
+ state,
250
+ );
@@ -0,0 +1,127 @@
1
+ import { assert, describe, expect, it } from 'vitest';
2
+ import { openTabReducer } from './openTabReducer';
3
+ import { VZAction, VZState, createInitialState } from '.';
4
+ import { defaultTheme } from '../themes';
5
+
6
+ const initialState: VZState = createInitialState({
7
+ defaultTheme,
8
+ });
9
+
10
+ describe('openTabReducer', () => {
11
+ it('Opens a new tab', () => {
12
+ const action: VZAction = {
13
+ type: 'open_tab',
14
+ fileId: 'file1',
15
+ isTransient: false,
16
+ };
17
+ const newState = openTabReducer(initialState, action);
18
+
19
+ assert(newState.pane.type === 'leafPane');
20
+ expect(newState.pane.tabList.length).toBe(1);
21
+ expect(newState.pane.tabList[0].fileId).toBe('file1');
22
+ expect(newState.pane.tabList[0].isTransient).toBe(
23
+ false,
24
+ );
25
+ expect(newState.pane.activeFileId).toBe('file1');
26
+ });
27
+
28
+ it('Replaces a transient tab', () => {
29
+ const stateWithTransientTab = {
30
+ ...initialState,
31
+ pane: {
32
+ ...initialState.pane,
33
+ tabList: [{ fileId: 'file2', isTransient: true }],
34
+ },
35
+ };
36
+ const action: VZAction = {
37
+ type: 'open_tab',
38
+ fileId: 'file1',
39
+ isTransient: true,
40
+ };
41
+ const newState = openTabReducer(
42
+ stateWithTransientTab,
43
+ action,
44
+ );
45
+
46
+ assert(newState.pane.type === 'leafPane');
47
+ expect(newState.pane.tabList.length).toBe(1);
48
+ expect(newState.pane.tabList[0].fileId).toBe('file1');
49
+ expect(newState.pane.tabList[0].isTransient).toBe(true);
50
+ expect(newState.pane.activeFileId).toBe('file1');
51
+ });
52
+
53
+ it('Does not modify a non-transient tab', () => {
54
+ const stateWithNonTransientTab = {
55
+ ...initialState,
56
+ pane: {
57
+ ...initialState.pane,
58
+ tabList: [{ fileId: 'file1', isTransient: false }],
59
+ },
60
+ };
61
+ const action: VZAction = {
62
+ type: 'open_tab',
63
+ fileId: 'file1',
64
+ isTransient: true,
65
+ };
66
+ const newState = openTabReducer(
67
+ stateWithNonTransientTab,
68
+ action,
69
+ );
70
+
71
+ assert(newState.pane.type === 'leafPane');
72
+ expect(newState.pane.tabList.length).toBe(1);
73
+ expect(newState.pane.tabList[0].fileId).toBe('file1');
74
+ expect(newState.pane.tabList[0].isTransient).toBe(
75
+ false,
76
+ ); // should remain non-transient
77
+ expect(newState.pane.activeFileId).toBe('file1');
78
+ });
79
+
80
+ it('Activates an already open tab', () => {
81
+ const stateWithMultipleTabs = {
82
+ ...initialState,
83
+ pane: {
84
+ ...initialState.pane,
85
+ tabList: [
86
+ { fileId: 'file1', isTransient: false },
87
+ { fileId: 'file2', isTransient: false },
88
+ ],
89
+ },
90
+ };
91
+ const action: VZAction = {
92
+ type: 'open_tab',
93
+ fileId: 'file1',
94
+ isTransient: false,
95
+ };
96
+ const newState = openTabReducer(
97
+ stateWithMultipleTabs,
98
+ action,
99
+ );
100
+
101
+ assert(newState.pane.type === 'leafPane');
102
+ expect(newState.pane.tabList.length).toBe(2);
103
+ expect(newState.pane.activeFileId).toBe('file1');
104
+ });
105
+
106
+ it('Sets editorWantsFocus to true for non-transient tabs', () => {
107
+ const action: VZAction = {
108
+ type: 'open_tab',
109
+ fileId: 'file3',
110
+ isTransient: false, // This tab is persistent
111
+ };
112
+ const newState = openTabReducer(initialState, action);
113
+
114
+ expect(newState.editorWantsFocus).toBe(true);
115
+ });
116
+
117
+ it('Sets editorWantsFocus to false for transient tabs', () => {
118
+ const action: VZAction = {
119
+ type: 'open_tab',
120
+ fileId: 'file4',
121
+ isTransient: true, // This tab is transient
122
+ };
123
+ const newState = openTabReducer(initialState, action);
124
+
125
+ expect(newState.editorWantsFocus).toBe(false);
126
+ });
127
+ });
@@ -0,0 +1,74 @@
1
+ import { VZAction, VZState } from '.';
2
+ import { Pane, TabState } from '../../types';
3
+ import { findPane } from './findPane';
4
+ import { updatePane } from './updatePane';
5
+
6
+ export const openTabReducer = (
7
+ state: VZState,
8
+ action: VZAction,
9
+ ): VZState => {
10
+ if (action.type !== 'open_tab') return state;
11
+
12
+ const { pane, activePaneId } = state;
13
+ const activePane: Pane = findPane(pane, activePaneId);
14
+
15
+ if (activePane.type !== 'leafPane') {
16
+ throw new Error(
17
+ 'closeTabsReducer: activePane is not a leafPane',
18
+ );
19
+ }
20
+
21
+ // Check if the tab is already open and if it's transient.
22
+ const existingTabIndex = activePane.tabList.findIndex(
23
+ (tab) => tab.fileId === action.fileId,
24
+ );
25
+ const tabExists = existingTabIndex !== -1;
26
+ const existingTabIsTransient =
27
+ tabExists &&
28
+ activePane.tabList[existingTabIndex].isTransient;
29
+
30
+ // Find if there's any other transient tab.
31
+ const transientTabIndex = activePane.tabList.findIndex(
32
+ (tab) =>
33
+ tab.isTransient && tab.fileId !== action.fileId,
34
+ );
35
+
36
+ // The new tab state.
37
+ const newTabState: TabState = {
38
+ fileId: action.fileId,
39
+ isTransient: action.isTransient,
40
+ };
41
+
42
+ let newTabList: Array<TabState>;
43
+
44
+ if (tabExists) {
45
+ if (existingTabIsTransient) {
46
+ newTabList = activePane.tabList.map((tabState) =>
47
+ tabState.fileId === action.fileId
48
+ ? newTabState
49
+ : tabState,
50
+ );
51
+ } else {
52
+ newTabList = [...activePane.tabList];
53
+ }
54
+ } else if (transientTabIndex !== -1) {
55
+ newTabList = [...activePane.tabList];
56
+ newTabList[transientTabIndex] = newTabState;
57
+ } else {
58
+ newTabList = [...activePane.tabList, newTabState];
59
+ }
60
+
61
+ // If the tab is persistent, the editor should focus on the next render.
62
+ const editorWantsFocus = !action.isTransient;
63
+
64
+ return {
65
+ ...state,
66
+ pane: updatePane({
67
+ pane,
68
+ activePaneId,
69
+ newTabList,
70
+ newActiveFileId: action.fileId,
71
+ }),
72
+ editorWantsFocus,
73
+ };
74
+ };