vzcode 1.10.0 → 1.12.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.
- package/README.md +1 -1
- package/dist/assets/{index-DXNuoOqQ.js → index-4cYZN5Uf.js} +57 -57
- package/dist/assets/{index--bvX81xc.js → index-CvSVuMne.js} +57 -57
- package/dist/assets/worker-CJWrLBN2.js +286 -0
- package/dist/index.html +1 -1
- package/package.json +13 -12
- package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +5 -2
- package/src/client/AIAssist/startAIAssist.ts +1 -1
- package/src/client/CodeEditor/getOrCreateEditor.ts +4 -0
- package/src/client/CodeEditor/index.tsx +2 -0
- package/src/client/CodeEditor/json1PresenceDisplay.ts +128 -90
- package/src/client/TabList/Tab.tsx +1 -2
- package/src/client/TabList/index.tsx +1 -1
- package/src/client/VZCodeContext.tsx +17 -14
- package/src/client/VZKeyboardShortcutsDoc.tsx +2 -1
- package/src/client/VZSidebar/Search.tsx +7 -2
- package/src/client/VZSidebar/index.tsx +87 -24
- package/src/client/tabsSearchParameters.ts +1 -2
- package/src/client/useActions.ts +10 -1
- package/src/client/useKeyboardShortcuts.ts +31 -1
- package/src/client/useURLSync.ts +1 -2
- package/src/client/vzReducer/closeTabReducer.test.ts +49 -33
- package/src/client/vzReducer/closeTabsReducer.ts +51 -19
- package/src/client/vzReducer/createInitialState.ts +8 -3
- package/src/client/vzReducer/findPane.ts +19 -0
- package/src/client/vzReducer/index.ts +17 -25
- package/src/client/vzReducer/openTabReducer.test.ts +38 -21
- package/src/client/vzReducer/openTabReducer.ts +26 -10
- package/src/client/vzReducer/searchReducer.ts +21 -1
- package/src/client/vzReducer/setActiveFileIdReducer.ts +23 -4
- package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +37 -8
- package/src/client/vzReducer/updatePane.ts +44 -0
- package/src/server/index.js +1 -0
- package/src/types.ts +69 -70
- package/dist/assets/worker-Cs0ZU3m1.js +0 -291
|
@@ -35,21 +35,74 @@ import './styles.scss';
|
|
|
35
35
|
const enableConnectionStatus = true;
|
|
36
36
|
|
|
37
37
|
export const VZSidebar = ({
|
|
38
|
-
createFileTooltipText =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
createFileTooltipText = (
|
|
39
|
+
<>
|
|
40
|
+
<strong>New file</strong>
|
|
41
|
+
{/* TODO verify that Ctrl + Shift + N works - does not work in Linux */}
|
|
42
|
+
<div>(Alt + N or Ctrl + Shift + N)</div>
|
|
43
|
+
</>
|
|
44
|
+
),
|
|
45
|
+
createDirTooltipText = (
|
|
46
|
+
<div>
|
|
47
|
+
<strong>New Directory</strong>
|
|
48
|
+
{/* TODO consider Alt+D, as Ctrl + Shift + D does not work in Linux */}
|
|
49
|
+
<div>(Ctrl + Shift + D)</div>
|
|
50
|
+
</div>
|
|
51
|
+
),
|
|
52
|
+
openSettingsTooltipText = (
|
|
53
|
+
<div>
|
|
54
|
+
<strong>Open Settings</strong>
|
|
55
|
+
<div>(Ctrl + Shift + S or Ctrl + ,)</div>
|
|
56
|
+
</div>
|
|
57
|
+
),
|
|
58
|
+
openKeyboardShortcuts = (
|
|
59
|
+
<div>
|
|
60
|
+
<strong>Keyboard Shortcuts</strong>
|
|
61
|
+
<div>(Ctrl + Shift + K)</div>
|
|
62
|
+
</div>
|
|
63
|
+
),
|
|
64
|
+
reportBugTooltipText = (
|
|
65
|
+
<div>
|
|
66
|
+
<strong>Report Bug</strong>
|
|
67
|
+
{/* TODO get this keyboard shortcut working? */}
|
|
68
|
+
{/* <div>(Ctrl + Shift + B)</div> */}
|
|
69
|
+
</div>
|
|
70
|
+
),
|
|
71
|
+
searchToolTipText = (
|
|
72
|
+
<div>
|
|
73
|
+
<strong>Search</strong>
|
|
74
|
+
<div>(Ctrl + Shift + F)</div>
|
|
75
|
+
</div>
|
|
76
|
+
),
|
|
77
|
+
filesToolTipText = (
|
|
78
|
+
<div>
|
|
79
|
+
<strong>Files</strong>
|
|
80
|
+
<div>(Ctrl + Shift + E)</div>
|
|
81
|
+
</div>
|
|
82
|
+
),
|
|
83
|
+
enableAutoFollowTooltipText = (
|
|
84
|
+
<div>
|
|
85
|
+
<strong>Enable Auto Follow</strong>
|
|
86
|
+
<div>(Ctrl + Shift + A)</div>
|
|
87
|
+
</div>
|
|
88
|
+
),
|
|
89
|
+
disableAutoFollowTooltipText = (
|
|
90
|
+
<div>
|
|
91
|
+
<strong>Disable Auto Follow</strong>
|
|
92
|
+
{/* TODO consider Alt+A, this breaks in Linux */}
|
|
93
|
+
<div>(Ctrl + Shift + A)</div>
|
|
94
|
+
</div>
|
|
95
|
+
),
|
|
45
96
|
}: {
|
|
46
|
-
createFileTooltipText?:
|
|
47
|
-
createDirTooltipText?:
|
|
48
|
-
openSettingsTooltipText?:
|
|
49
|
-
reportBugTooltipText?:
|
|
50
|
-
openKeyboardShortcuts?:
|
|
51
|
-
searchToolTipText?:
|
|
52
|
-
filesToolTipText?:
|
|
97
|
+
createFileTooltipText?: React.ReactNode;
|
|
98
|
+
createDirTooltipText?: React.ReactNode;
|
|
99
|
+
openSettingsTooltipText?: React.ReactNode;
|
|
100
|
+
reportBugTooltipText?: React.ReactNode;
|
|
101
|
+
openKeyboardShortcuts?: React.ReactNode;
|
|
102
|
+
searchToolTipText?: React.ReactNode;
|
|
103
|
+
filesToolTipText?: React.ReactNode;
|
|
104
|
+
enableAutoFollowTooltipText?: React.ReactNode;
|
|
105
|
+
disableAutoFollowTooltipText?: React.ReactNode;
|
|
53
106
|
}) => {
|
|
54
107
|
const {
|
|
55
108
|
files,
|
|
@@ -135,8 +188,9 @@ export const VZSidebar = ({
|
|
|
135
188
|
}
|
|
136
189
|
>
|
|
137
190
|
<i
|
|
138
|
-
|
|
191
|
+
id="files-icon"
|
|
139
192
|
className="icon-button icon-button-dark"
|
|
193
|
+
onClick={() => setIsSearchOpen(false)}
|
|
140
194
|
>
|
|
141
195
|
<FolderSVG />
|
|
142
196
|
</i>
|
|
@@ -151,8 +205,9 @@ export const VZSidebar = ({
|
|
|
151
205
|
}
|
|
152
206
|
>
|
|
153
207
|
<i
|
|
154
|
-
|
|
208
|
+
id="search-icon"
|
|
155
209
|
className="icon-button icon-button-dark"
|
|
210
|
+
onClick={() => setIsSearchOpen(true)}
|
|
156
211
|
>
|
|
157
212
|
<SearchSVG />
|
|
158
213
|
</i>
|
|
@@ -167,8 +222,9 @@ export const VZSidebar = ({
|
|
|
167
222
|
}
|
|
168
223
|
>
|
|
169
224
|
<i
|
|
170
|
-
|
|
225
|
+
id="shortcut-icon"
|
|
171
226
|
className="icon-button icon-button-dark"
|
|
227
|
+
onClick={handleQuestionMarkClick}
|
|
172
228
|
>
|
|
173
229
|
<QuestionMarkSVG />
|
|
174
230
|
</i>
|
|
@@ -187,7 +243,10 @@ export const VZSidebar = ({
|
|
|
187
243
|
target="_blank"
|
|
188
244
|
rel="noopener noreferrer"
|
|
189
245
|
>
|
|
190
|
-
<i
|
|
246
|
+
<i
|
|
247
|
+
id="bug-icon"
|
|
248
|
+
className="icon-button icon-button-dark"
|
|
249
|
+
>
|
|
191
250
|
<BugSVG />
|
|
192
251
|
</i>
|
|
193
252
|
</a>
|
|
@@ -202,8 +261,9 @@ export const VZSidebar = ({
|
|
|
202
261
|
}
|
|
203
262
|
>
|
|
204
263
|
<i
|
|
205
|
-
|
|
264
|
+
id="settings-icon"
|
|
206
265
|
className="icon-button icon-button-dark"
|
|
266
|
+
onClick={handleSettingsClick}
|
|
207
267
|
>
|
|
208
268
|
<GearSVG />
|
|
209
269
|
</i>
|
|
@@ -218,8 +278,9 @@ export const VZSidebar = ({
|
|
|
218
278
|
}
|
|
219
279
|
>
|
|
220
280
|
<i
|
|
221
|
-
|
|
281
|
+
id="new-file-icon"
|
|
222
282
|
className="icon-button icon-button-dark"
|
|
283
|
+
onClick={handleOpenCreateFileModal}
|
|
223
284
|
>
|
|
224
285
|
<NewSVG />
|
|
225
286
|
</i>
|
|
@@ -235,8 +296,9 @@ export const VZSidebar = ({
|
|
|
235
296
|
}
|
|
236
297
|
>
|
|
237
298
|
<i
|
|
238
|
-
|
|
299
|
+
id="new-directory-icon"
|
|
239
300
|
className="icon-button icon-button-dark"
|
|
301
|
+
onClick={handleOpenCreateDirModal}
|
|
240
302
|
>
|
|
241
303
|
<FileSVG />
|
|
242
304
|
</i>
|
|
@@ -248,18 +310,19 @@ export const VZSidebar = ({
|
|
|
248
310
|
overlay={
|
|
249
311
|
<Tooltip id="toggle-auto-follow">
|
|
250
312
|
{enableAutoFollow
|
|
251
|
-
?
|
|
252
|
-
:
|
|
313
|
+
? disableAutoFollowTooltipText
|
|
314
|
+
: enableAutoFollowTooltipText}
|
|
253
315
|
</Tooltip>
|
|
254
316
|
}
|
|
255
317
|
>
|
|
256
318
|
<i
|
|
257
|
-
|
|
319
|
+
id="auto-focus-icon"
|
|
258
320
|
className={`icon-button icon-button-dark${
|
|
259
321
|
enableAutoFollow
|
|
260
322
|
? ' vh-color-success-01'
|
|
261
323
|
: ''
|
|
262
324
|
}`}
|
|
325
|
+
onClick={toggleAutoFollow}
|
|
263
326
|
>
|
|
264
327
|
<PinSVG />
|
|
265
328
|
</i>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { FileId, VZCodeContent } from '../types';
|
|
2
|
-
import { TabState } from './vzReducer';
|
|
1
|
+
import { FileId, TabState, VZCodeContent } from '../types';
|
|
3
2
|
|
|
4
3
|
// The delimiter used to separate file names in the `tabs` parameter.
|
|
5
4
|
// We need a character that is both URL-safe (does not get escaped in URLs)
|
package/src/client/useActions.ts
CHANGED
|
@@ -4,10 +4,11 @@ import {
|
|
|
4
4
|
FileId,
|
|
5
5
|
SearchFileVisibility,
|
|
6
6
|
ShareDBDoc,
|
|
7
|
+
TabState,
|
|
7
8
|
Username,
|
|
8
9
|
VZCodeContent,
|
|
9
10
|
} from '../types';
|
|
10
|
-
import {
|
|
11
|
+
import { VZAction } from './vzReducer';
|
|
11
12
|
|
|
12
13
|
// This is a custom hook that returns a set of functions
|
|
13
14
|
// that can be used to dispatch actions to the reducer.
|
|
@@ -113,6 +114,13 @@ export const useActions = (
|
|
|
113
114
|
[dispatch],
|
|
114
115
|
);
|
|
115
116
|
|
|
117
|
+
// Toggle the focused variable, which should focus the search input
|
|
118
|
+
const toggleSearchFocused = useCallback(() => {
|
|
119
|
+
dispatch({
|
|
120
|
+
type: 'toggle_search_focused',
|
|
121
|
+
});
|
|
122
|
+
}, [dispatch]);
|
|
123
|
+
|
|
116
124
|
// True to show the settings modal.
|
|
117
125
|
const setIsSettingsOpen = useCallback(
|
|
118
126
|
(value: boolean) => {
|
|
@@ -175,6 +183,7 @@ export const useActions = (
|
|
|
175
183
|
setSearch,
|
|
176
184
|
setSearchResults,
|
|
177
185
|
setSearchFileVisibility,
|
|
186
|
+
toggleSearchFocused,
|
|
178
187
|
setIsSettingsOpen,
|
|
179
188
|
setIsDocOpen,
|
|
180
189
|
closeSettings,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useContext, useEffect } from 'react';
|
|
2
2
|
import { shouldTriggerRun } from './shouldTriggerRun';
|
|
3
3
|
import { syntaxTree } from '@codemirror/language';
|
|
4
4
|
import { SyntaxNode, SyntaxNodeRef } from '@lezer/common';
|
|
@@ -151,6 +151,18 @@ function jumpToDefinition(
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
// Sidebar keyboard shortcuts in form Ctrl + Shift + <key>
|
|
155
|
+
const sideBarKeyBoardMap = {
|
|
156
|
+
E: 'files-icon',
|
|
157
|
+
F: 'search-icon',
|
|
158
|
+
K: 'shortcut-icon',
|
|
159
|
+
B: 'bug-icon',
|
|
160
|
+
S: 'settings-icon',
|
|
161
|
+
N: 'new-file-icon',
|
|
162
|
+
D: 'new-directory-icon',
|
|
163
|
+
A: 'auto-focus-icon',
|
|
164
|
+
};
|
|
165
|
+
|
|
154
166
|
// This module implements the keyboard shortcuts
|
|
155
167
|
// for the VZCode editor.
|
|
156
168
|
// These include:
|
|
@@ -166,6 +178,7 @@ export const useKeyboardShortcuts = ({
|
|
|
166
178
|
handleOpenCreateFileModal,
|
|
167
179
|
setActiveFileLeft,
|
|
168
180
|
setActiveFileRight,
|
|
181
|
+
toggleSearchFocused,
|
|
169
182
|
runPrettierRef,
|
|
170
183
|
runCodeRef,
|
|
171
184
|
sidebarRef,
|
|
@@ -177,6 +190,7 @@ export const useKeyboardShortcuts = ({
|
|
|
177
190
|
handleOpenCreateFileModal: () => void;
|
|
178
191
|
setActiveFileLeft: () => void;
|
|
179
192
|
setActiveFileRight: () => void;
|
|
193
|
+
toggleSearchFocused: () => void;
|
|
180
194
|
runPrettierRef: React.MutableRefObject<() => void>;
|
|
181
195
|
runCodeRef: React.MutableRefObject<() => void>;
|
|
182
196
|
sidebarRef: React.RefObject<HTMLDivElement>;
|
|
@@ -203,6 +217,22 @@ export const useKeyboardShortcuts = ({
|
|
|
203
217
|
return;
|
|
204
218
|
}
|
|
205
219
|
|
|
220
|
+
if (event.ctrlKey && event.shiftKey) {
|
|
221
|
+
// Handle keyboard shortcuts related to the side bar icons
|
|
222
|
+
document
|
|
223
|
+
.getElementById(sideBarKeyBoardMap[event.key])
|
|
224
|
+
?.click();
|
|
225
|
+
|
|
226
|
+
// Ensure the search input is always focused
|
|
227
|
+
if (event.key === 'F') {
|
|
228
|
+
toggleSearchFocused();
|
|
229
|
+
}
|
|
230
|
+
} else if (event.ctrlKey && event.key === ',') {
|
|
231
|
+
document
|
|
232
|
+
.getElementById(sideBarKeyBoardMap['S'])
|
|
233
|
+
?.click();
|
|
234
|
+
}
|
|
235
|
+
|
|
206
236
|
if (event.ctrlKey === true) {
|
|
207
237
|
// On holding CTRL key, search for a potential definition jump using mouse location
|
|
208
238
|
document.addEventListener(
|
package/src/client/useURLSync.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useSearchParams } from 'react-router-dom';
|
|
2
|
-
import { FileId, VZCodeContent } from '../types';
|
|
3
|
-
import { TabState } from './vzReducer';
|
|
2
|
+
import { FileId, TabState, VZCodeContent } from '../types';
|
|
4
3
|
import { useEffect, useMemo, useRef } from 'react';
|
|
5
4
|
import {
|
|
6
5
|
TabStateParams,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { assert, describe, expect, it } from 'vitest';
|
|
2
2
|
import { closeTabsReducer } from './closeTabsReducer';
|
|
3
3
|
import { VZAction, VZState, createInitialState } from '.';
|
|
4
4
|
import { defaultTheme } from '../themes';
|
|
@@ -25,8 +25,9 @@ describe('closeTabsReducer', () => {
|
|
|
25
25
|
action,
|
|
26
26
|
);
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
expect(newState.
|
|
28
|
+
assert(newState.pane.type === 'leafPane');
|
|
29
|
+
expect(newState.pane.tabList.length).toBe(0);
|
|
30
|
+
expect(newState.pane.activeFileId).toBeNull();
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
it('Keeps other tabs open', () => {
|
|
@@ -36,11 +37,14 @@ describe('closeTabsReducer', () => {
|
|
|
36
37
|
};
|
|
37
38
|
const stateWithTabs = {
|
|
38
39
|
...initialState,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
pane: {
|
|
41
|
+
...initialState.pane,
|
|
42
|
+
tabList: [
|
|
43
|
+
{ fileId: 'file1', isTransient: false },
|
|
44
|
+
{ fileId: 'file2', isTransient: false },
|
|
45
|
+
],
|
|
46
|
+
activeFileId: 'file1',
|
|
47
|
+
},
|
|
44
48
|
};
|
|
45
49
|
|
|
46
50
|
const newState = closeTabsReducer(
|
|
@@ -48,24 +52,29 @@ describe('closeTabsReducer', () => {
|
|
|
48
52
|
action,
|
|
49
53
|
);
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
expect(newState.tabList
|
|
53
|
-
expect(newState.
|
|
55
|
+
assert(newState.pane.type === 'leafPane');
|
|
56
|
+
expect(newState.pane.tabList.length).toBe(1);
|
|
57
|
+
expect(newState.pane.tabList[0].fileId).toBe('file1');
|
|
58
|
+
expect(newState.pane.activeFileId).toBe('file1');
|
|
54
59
|
});
|
|
55
60
|
|
|
56
|
-
it('Activates previous tab after closing an active tab', () => {
|
|
61
|
+
it.only('Activates previous tab after closing an active tab', () => {
|
|
57
62
|
const action: VZAction = {
|
|
58
63
|
type: 'close_tabs',
|
|
59
64
|
fileIdsToClose: ['file2'],
|
|
60
65
|
};
|
|
61
|
-
const stateWithTabs = {
|
|
66
|
+
const stateWithTabs: VZState = {
|
|
62
67
|
...initialState,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
pane: {
|
|
69
|
+
...initialState.pane,
|
|
70
|
+
type: 'leafPane',
|
|
71
|
+
tabList: [
|
|
72
|
+
{ fileId: 'file1', isTransient: false },
|
|
73
|
+
{ fileId: 'file2', isTransient: false },
|
|
74
|
+
{ fileId: 'file3', isTransient: false },
|
|
75
|
+
],
|
|
76
|
+
activeFileId: 'file2',
|
|
77
|
+
},
|
|
69
78
|
};
|
|
70
79
|
|
|
71
80
|
const newState = closeTabsReducer(
|
|
@@ -73,8 +82,9 @@ describe('closeTabsReducer', () => {
|
|
|
73
82
|
action,
|
|
74
83
|
);
|
|
75
84
|
|
|
76
|
-
|
|
77
|
-
expect(newState.
|
|
85
|
+
assert(newState.pane.type === 'leafPane');
|
|
86
|
+
expect(newState.pane.tabList.length).toBe(2);
|
|
87
|
+
expect(newState.pane.activeFileId).toBe('file1'); // should switch to the previous tab
|
|
78
88
|
});
|
|
79
89
|
|
|
80
90
|
it('Does not modify the state if closing non-existing tabs', () => {
|
|
@@ -84,12 +94,15 @@ describe('closeTabsReducer', () => {
|
|
|
84
94
|
};
|
|
85
95
|
const stateWithTabs = {
|
|
86
96
|
...initialState,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
pane: {
|
|
98
|
+
...initialState.pane,
|
|
99
|
+
tabList: [
|
|
100
|
+
{ fileId: 'file1', isTransient: false },
|
|
101
|
+
{ fileId: 'file2', isTransient: false },
|
|
102
|
+
{ fileId: 'file3', isTransient: false },
|
|
103
|
+
],
|
|
104
|
+
activeFileId: 'file2',
|
|
105
|
+
},
|
|
93
106
|
};
|
|
94
107
|
|
|
95
108
|
const newState = closeTabsReducer(
|
|
@@ -121,8 +134,9 @@ describe('closeTabsReducer', () => {
|
|
|
121
134
|
action,
|
|
122
135
|
);
|
|
123
136
|
|
|
124
|
-
|
|
125
|
-
expect(newState.
|
|
137
|
+
assert(newState.pane.type === 'leafPane');
|
|
138
|
+
expect(newState.pane.tabList.length).toBe(2);
|
|
139
|
+
expect(newState.pane.activeFileId).toBe('file3'); // remains unchanged
|
|
126
140
|
});
|
|
127
141
|
|
|
128
142
|
it('Closes the first tab out of many, when it is active', () => {
|
|
@@ -146,8 +160,9 @@ describe('closeTabsReducer', () => {
|
|
|
146
160
|
action,
|
|
147
161
|
);
|
|
148
162
|
|
|
149
|
-
|
|
150
|
-
expect(newState.
|
|
163
|
+
assert(newState.pane.type === 'leafPane');
|
|
164
|
+
expect(newState.pane.tabList.length).toBe(2);
|
|
165
|
+
expect(newState.pane.activeFileId).toBe('file2'); // switches to the next available tab
|
|
151
166
|
});
|
|
152
167
|
|
|
153
168
|
it('Closes multiple tabs at once', () => {
|
|
@@ -171,7 +186,8 @@ describe('closeTabsReducer', () => {
|
|
|
171
186
|
action,
|
|
172
187
|
);
|
|
173
188
|
|
|
174
|
-
|
|
175
|
-
expect(newState.
|
|
189
|
+
assert(newState.pane.type === 'leafPane');
|
|
190
|
+
expect(newState.pane.tabList.length).toBe(1);
|
|
191
|
+
expect(newState.pane.activeFileId).toBe('file3'); // switches to the next available tab
|
|
176
192
|
});
|
|
177
193
|
});
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { VZAction, VZState } from '.';
|
|
2
|
+
import {
|
|
3
|
+
FileId,
|
|
4
|
+
Pane,
|
|
5
|
+
PaneId,
|
|
6
|
+
TabState,
|
|
7
|
+
} from '../../types';
|
|
8
|
+
import { findPane } from './findPane';
|
|
9
|
+
import { updatePane } from './updatePane';
|
|
3
10
|
|
|
4
11
|
export const closeTabsReducer = (
|
|
5
12
|
state: VZState,
|
|
@@ -9,35 +16,56 @@ export const closeTabsReducer = (
|
|
|
9
16
|
return state;
|
|
10
17
|
}
|
|
11
18
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
19
|
+
const { pane, activePaneId } = state;
|
|
20
|
+
const activePane: Pane = findPane(pane, activePaneId);
|
|
21
|
+
|
|
22
|
+
if (activePane.type !== 'leafPane') {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'closeTabsReducer: activePane is not a leafPane',
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Derive the new tab list by filtering out the tabs to close.
|
|
29
|
+
const newTabList: Array<TabState> =
|
|
30
|
+
activePane.tabList.filter(
|
|
31
|
+
(tabState) =>
|
|
32
|
+
!action.fileIdsToClose.includes(tabState.fileId),
|
|
33
|
+
);
|
|
16
34
|
|
|
17
35
|
// If the active tab wasn't closed, keep it active.
|
|
18
36
|
if (
|
|
19
|
-
!action.fileIdsToClose.includes(
|
|
37
|
+
!action.fileIdsToClose.includes(
|
|
38
|
+
activePane.activeFileId!,
|
|
39
|
+
)
|
|
20
40
|
) {
|
|
21
41
|
return {
|
|
22
42
|
...state,
|
|
23
|
-
|
|
43
|
+
pane: updatePane({
|
|
44
|
+
pane,
|
|
45
|
+
activePaneId,
|
|
46
|
+
newTabList,
|
|
47
|
+
}),
|
|
24
48
|
};
|
|
25
49
|
}
|
|
26
50
|
|
|
27
|
-
|
|
28
|
-
(tabState) => tabState.fileId === state.activeFileId,
|
|
29
|
-
);
|
|
30
|
-
|
|
51
|
+
// Otherwise, we'll need to also find a new active tab.
|
|
31
52
|
let newActiveFileId: FileId | null = null;
|
|
32
53
|
|
|
54
|
+
// The question becomes: which tab should be active now?
|
|
55
|
+
// - Either the tab to the left or right of the closed tab.
|
|
56
|
+
|
|
33
57
|
// Try getting the previous available tab first.
|
|
58
|
+
const originalIndex = activePane.tabList.findIndex(
|
|
59
|
+
(tabState) =>
|
|
60
|
+
tabState.fileId === activePane.activeFileId,
|
|
61
|
+
);
|
|
34
62
|
for (let i = originalIndex - 1; i >= 0; i--) {
|
|
35
63
|
if (
|
|
36
64
|
!action.fileIdsToClose.includes(
|
|
37
|
-
|
|
65
|
+
activePane.tabList[i].fileId,
|
|
38
66
|
)
|
|
39
67
|
) {
|
|
40
|
-
newActiveFileId =
|
|
68
|
+
newActiveFileId = activePane.tabList[i].fileId;
|
|
41
69
|
break;
|
|
42
70
|
}
|
|
43
71
|
}
|
|
@@ -46,15 +74,15 @@ export const closeTabsReducer = (
|
|
|
46
74
|
if (!newActiveFileId) {
|
|
47
75
|
for (
|
|
48
76
|
let i = originalIndex + 1;
|
|
49
|
-
i <
|
|
77
|
+
i < activePane.tabList.length;
|
|
50
78
|
i++
|
|
51
79
|
) {
|
|
52
80
|
if (
|
|
53
81
|
!action.fileIdsToClose.includes(
|
|
54
|
-
|
|
82
|
+
activePane.tabList[i].fileId,
|
|
55
83
|
)
|
|
56
84
|
) {
|
|
57
|
-
newActiveFileId =
|
|
85
|
+
newActiveFileId = activePane.tabList[i].fileId;
|
|
58
86
|
break;
|
|
59
87
|
}
|
|
60
88
|
}
|
|
@@ -62,7 +90,11 @@ export const closeTabsReducer = (
|
|
|
62
90
|
|
|
63
91
|
return {
|
|
64
92
|
...state,
|
|
65
|
-
|
|
66
|
-
|
|
93
|
+
pane: updatePane({
|
|
94
|
+
pane,
|
|
95
|
+
activePaneId,
|
|
96
|
+
newTabList,
|
|
97
|
+
newActiveFileId,
|
|
98
|
+
}),
|
|
67
99
|
};
|
|
68
100
|
};
|
|
@@ -9,10 +9,15 @@ export const createInitialState = ({
|
|
|
9
9
|
defaultTheme: ThemeLabel;
|
|
10
10
|
initialUsername?: Username;
|
|
11
11
|
}): VZState => ({
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
pane: {
|
|
13
|
+
id: 'root',
|
|
14
|
+
type: 'leafPane',
|
|
15
|
+
tabList: [],
|
|
16
|
+
activeFileId: null,
|
|
17
|
+
},
|
|
18
|
+
activePaneId: 'root',
|
|
14
19
|
theme: defaultTheme,
|
|
15
|
-
search: { pattern: '', results: {} },
|
|
20
|
+
search: { pattern: '', results: {}, focused: false },
|
|
16
21
|
isSearchOpen: false,
|
|
17
22
|
isSettingsOpen: false,
|
|
18
23
|
isDocOpen: false,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Pane, PaneId } from '../../types';
|
|
2
|
+
|
|
3
|
+
// Recursively finds a pane by id, given a root pane node.
|
|
4
|
+
export const findPane = (
|
|
5
|
+
pane: Pane,
|
|
6
|
+
paneId: PaneId,
|
|
7
|
+
): Pane => {
|
|
8
|
+
if (pane.id === paneId) {
|
|
9
|
+
return pane;
|
|
10
|
+
}
|
|
11
|
+
if (pane.type === 'splitPane') {
|
|
12
|
+
for (const child of pane.children) {
|
|
13
|
+
const foundPane = findPane(child, paneId);
|
|
14
|
+
if (foundPane) {
|
|
15
|
+
return foundPane;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|