vzcode 0.26.0 → 0.27.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/dist/assets/index-5f9f376e.css +1 -0
- package/dist/assets/index-70b6d947.js +111 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/client/App.tsx +86 -43
- package/src/client/CodeEditor/getOrCreateEditor.ts +6 -1
- package/src/client/CodeEditor/index.tsx +60 -12
- package/src/client/CodeEditor/json1PresenceBroadcast.ts +6 -2
- package/src/client/CodeEditor/json1PresenceDisplay.ts +0 -45
- package/src/client/CodeEditor/style.scss +5 -1
- package/src/client/CodeErrorOverlay/index.tsx +27 -0
- package/src/client/CodeErrorOverlay/style.scss +30 -0
- package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
- package/src/client/PresenceNotifications/index.tsx +118 -103
- package/src/client/SplitPaneResizeContext.tsx +12 -7
- package/src/client/TabList/Tab.tsx +20 -6
- package/src/client/TabList/index.tsx +18 -8
- package/src/client/TabList/style.scss +5 -0
- package/src/client/VZSettings.tsx +33 -20
- package/src/client/VZSidebar/{ConfirmationBox.tsx → DeleteConfirmationModal.tsx} +17 -14
- package/src/client/VZSidebar/DirectoryListing.tsx +27 -26
- package/src/client/VZSidebar/FileListing.tsx +16 -19
- package/src/client/VZSidebar/Item.tsx +83 -37
- package/src/client/VZSidebar/Listing.tsx +17 -14
- package/src/client/VZSidebar/index.tsx +43 -24
- package/src/client/style.scss +3 -0
- package/src/client/useActions.ts +25 -6
- package/src/client/useEditorCache.ts +2 -1
- package/src/client/useFileCRUD.ts +29 -27
- package/src/client/usernameLocalStorage.ts +43 -0
- package/src/client/vzReducer/closeTabReducer.test.ts +177 -0
- package/src/client/vzReducer/closeTabsReducer.ts +68 -0
- package/src/client/vzReducer/createInitialState.ts +19 -0
- package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
- package/src/client/vzReducer/index.ts +107 -0
- package/src/client/vzReducer/openTabReducer.test.ts +110 -0
- package/src/client/vzReducer/openTabReducer.ts +57 -0
- package/src/client/vzReducer/setActiveFileIdReducer.ts +9 -0
- package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
- package/src/client/vzReducer/setThemeReducer.ts +9 -0
- package/src/client/vzReducer/setUsernameReducer.ts +9 -0
- package/src/index.ts +9 -3
- package/src/types.ts +12 -5
- package/dist/assets/index-284b7339.js +0 -111
- package/dist/assets/index-e160f758.css +0 -1
- package/src/client/PrettierErrorOverlay/index.tsx +0 -9
- package/src/client/PrettierErrorOverlay/style.scss +0 -9
- package/src/client/vzReducer.ts +0 -118
|
@@ -1,32 +1,35 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import { Button, Modal } from '../bootstrap';
|
|
3
2
|
|
|
3
|
+
// Confirmation modal for deleting a file or directory.
|
|
4
4
|
// Inspired by https://react-bootstrap.netlify.app/docs/components/modal/#live-demo
|
|
5
|
-
export const
|
|
5
|
+
export const DeleteConfirmationModal = ({
|
|
6
6
|
show,
|
|
7
7
|
onClose,
|
|
8
8
|
onConfirm,
|
|
9
|
+
isDirectory,
|
|
10
|
+
name,
|
|
9
11
|
}) => (
|
|
10
|
-
<Modal
|
|
11
|
-
show={show}
|
|
12
|
-
onHide={onClose}
|
|
13
|
-
// confirm={onConfirm}
|
|
14
|
-
centered
|
|
15
|
-
>
|
|
12
|
+
<Modal show={show} onHide={onClose} centered>
|
|
16
13
|
<Modal.Header closeButton onClick={onClose}>
|
|
17
|
-
<Modal.Title>
|
|
14
|
+
<Modal.Title>
|
|
15
|
+
Delete {isDirectory ? 'Directory' : 'File'}
|
|
16
|
+
</Modal.Title>
|
|
18
17
|
</Modal.Header>
|
|
19
18
|
|
|
20
19
|
<Modal.Body>
|
|
21
|
-
<p>
|
|
20
|
+
<p>
|
|
21
|
+
This will delete the{' '}
|
|
22
|
+
{isDirectory ? 'directory' : 'file'}{' '}
|
|
23
|
+
<strong>{name}</strong>.
|
|
24
|
+
</p>
|
|
22
25
|
</Modal.Body>
|
|
23
26
|
|
|
24
27
|
<Modal.Footer>
|
|
25
|
-
<Button variant="primary" onClick={onConfirm}>
|
|
26
|
-
Yes
|
|
27
|
-
</Button>
|
|
28
28
|
<Button variant="secondary" onClick={onClose}>
|
|
29
|
-
|
|
29
|
+
Cancel
|
|
30
|
+
</Button>
|
|
31
|
+
<Button variant="primary" onClick={onConfirm}>
|
|
32
|
+
Delete
|
|
30
33
|
</Button>
|
|
31
34
|
</Modal.Footer>
|
|
32
35
|
</Modal>
|
|
@@ -2,44 +2,44 @@ import { useCallback, useMemo } from 'react';
|
|
|
2
2
|
import { Item } from './Item';
|
|
3
3
|
import { Listing } from './Listing';
|
|
4
4
|
import { DirectoryArrowSVG } from './DirectoryArrowSVG';
|
|
5
|
-
import {
|
|
5
|
+
import type {
|
|
6
|
+
FileTree,
|
|
7
|
+
FileTreeFile,
|
|
8
|
+
FileTreePath,
|
|
9
|
+
} from '../../types';
|
|
6
10
|
|
|
7
11
|
export const DirectoryListing = ({
|
|
8
12
|
name,
|
|
9
13
|
path,
|
|
10
14
|
children,
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
renameFile,
|
|
16
|
+
deleteFile,
|
|
17
|
+
deleteDirectory,
|
|
13
18
|
handleFileClick,
|
|
19
|
+
handleFileDoubleClick,
|
|
14
20
|
isDirectoryOpen,
|
|
15
21
|
toggleDirectory,
|
|
22
|
+
activeFileId,
|
|
16
23
|
}: {
|
|
17
24
|
name: string;
|
|
18
25
|
path: string;
|
|
19
26
|
children: Array<FileTree | FileTreeFile>;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
) => void;
|
|
24
|
-
handleDeleteFileClick: (
|
|
25
|
-
fileId: string,
|
|
26
|
-
event: React.MouseEvent,
|
|
27
|
-
) => void;
|
|
27
|
+
renameFile: (fileId: string, newName: string) => void;
|
|
28
|
+
deleteFile: (fileId: string) => void;
|
|
29
|
+
deleteDirectory: (path: FileTreePath) => void;
|
|
28
30
|
handleFileClick: (fileId: string) => void;
|
|
31
|
+
handleFileDoubleClick: (fileId: string) => void;
|
|
29
32
|
isDirectoryOpen: (path: string) => boolean;
|
|
30
33
|
toggleDirectory: (path: string) => void;
|
|
34
|
+
activeFileId: string;
|
|
31
35
|
}) => {
|
|
32
36
|
const handleClick = useCallback(() => {
|
|
33
37
|
toggleDirectory(path);
|
|
34
|
-
}, [toggleDirectory]);
|
|
38
|
+
}, [toggleDirectory, path]);
|
|
35
39
|
|
|
36
|
-
const handleDeleteClick = useCallback(
|
|
37
|
-
(
|
|
38
|
-
|
|
39
|
-
handleDeleteFileClick(path, event);
|
|
40
|
-
},
|
|
41
|
-
[handleDeleteFileClick, path],
|
|
42
|
-
);
|
|
40
|
+
const handleDeleteClick = useCallback(() => {
|
|
41
|
+
deleteDirectory(path);
|
|
42
|
+
}, [deleteDirectory, path]);
|
|
43
43
|
|
|
44
44
|
const handleRenameClick = useCallback(() => {
|
|
45
45
|
// https://github.com/vizhub-core/vzcode/issues/103
|
|
@@ -58,7 +58,7 @@ export const DirectoryListing = ({
|
|
|
58
58
|
handleClick={handleClick}
|
|
59
59
|
handleDeleteClick={handleDeleteClick}
|
|
60
60
|
handleRenameClick={handleRenameClick}
|
|
61
|
-
|
|
61
|
+
isDirectory={true}
|
|
62
62
|
>
|
|
63
63
|
<div
|
|
64
64
|
className="arrow-wrapper"
|
|
@@ -79,15 +79,16 @@ export const DirectoryListing = ({
|
|
|
79
79
|
<Listing
|
|
80
80
|
entity={entity}
|
|
81
81
|
key={fileId || path}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
handleDeleteFileClick={
|
|
86
|
-
handleDeleteFileClick
|
|
87
|
-
}
|
|
82
|
+
renameFile={renameFile}
|
|
83
|
+
deleteFile={deleteFile}
|
|
84
|
+
deleteDirectory={deleteDirectory}
|
|
88
85
|
handleFileClick={handleFileClick}
|
|
86
|
+
handleFileDoubleClick={
|
|
87
|
+
handleFileDoubleClick
|
|
88
|
+
}
|
|
89
89
|
isDirectoryOpen={isDirectoryOpen}
|
|
90
90
|
toggleDirectory={toggleDirectory}
|
|
91
|
+
activeFileId={activeFileId}
|
|
91
92
|
/>
|
|
92
93
|
);
|
|
93
94
|
})}
|
|
@@ -5,49 +5,46 @@ import { FileId } from '../../types';
|
|
|
5
5
|
export const FileListing = ({
|
|
6
6
|
name,
|
|
7
7
|
fileId,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
renameFile,
|
|
9
|
+
deleteFile,
|
|
10
10
|
handleFileClick,
|
|
11
|
+
handleFileDoubleClick,
|
|
11
12
|
isActive,
|
|
12
13
|
}: {
|
|
13
14
|
name: string;
|
|
14
15
|
fileId: FileId;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
newName: string,
|
|
18
|
-
) => void;
|
|
19
|
-
handleDeleteFileClick: (
|
|
20
|
-
fileId: FileId,
|
|
21
|
-
event: React.MouseEvent,
|
|
22
|
-
) => void;
|
|
16
|
+
renameFile: (fileId: FileId, newName: string) => void;
|
|
17
|
+
deleteFile: (fileId: FileId) => void;
|
|
23
18
|
handleFileClick: (fileId: FileId) => void;
|
|
19
|
+
handleFileDoubleClick: (fileId: FileId) => void;
|
|
24
20
|
isActive: boolean;
|
|
25
21
|
}) => {
|
|
26
22
|
const handleClick = useCallback(() => {
|
|
27
23
|
handleFileClick(fileId);
|
|
28
24
|
}, [fileId, handleFileClick]);
|
|
29
25
|
|
|
30
|
-
const
|
|
31
|
-
(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
const handleDoubleClick = useCallback(() => {
|
|
27
|
+
handleFileDoubleClick(fileId);
|
|
28
|
+
}, [fileId, handleFileDoubleClick]);
|
|
29
|
+
|
|
30
|
+
const handleDeleteClick = useCallback(() => {
|
|
31
|
+
deleteFile(fileId);
|
|
32
|
+
}, [fileId, deleteFile]);
|
|
36
33
|
|
|
37
34
|
const handleRenameClick = useCallback(
|
|
38
35
|
(newName: string) => {
|
|
39
|
-
|
|
36
|
+
renameFile(fileId, newName);
|
|
40
37
|
},
|
|
41
|
-
[fileId,
|
|
38
|
+
[fileId, renameFile],
|
|
42
39
|
);
|
|
43
40
|
|
|
44
41
|
return (
|
|
45
42
|
<Item
|
|
46
43
|
name={name}
|
|
47
44
|
handleClick={handleClick}
|
|
45
|
+
handleDoubleClick={handleDoubleClick}
|
|
48
46
|
handleDeleteClick={handleDeleteClick}
|
|
49
47
|
handleRenameClick={handleRenameClick}
|
|
50
|
-
isDir={false}
|
|
51
48
|
isActive={isActive}
|
|
52
49
|
>
|
|
53
50
|
{name}
|
|
@@ -1,14 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useCallback,
|
|
4
|
+
useRef,
|
|
5
|
+
useEffect,
|
|
6
|
+
} from 'react';
|
|
3
7
|
|
|
8
|
+
// TODO consider moving this up to a higher level of the component tree
|
|
9
|
+
import { DeleteConfirmationModal } from './DeleteConfirmationModal';
|
|
10
|
+
|
|
11
|
+
// TODO support renaming directories
|
|
12
|
+
// See https://github.com/vizhub-core/vzcode/issues/103
|
|
13
|
+
const enableRenameDirectory = false;
|
|
14
|
+
|
|
15
|
+
// A file or directory in the sidebar.
|
|
4
16
|
export const Item = ({
|
|
5
17
|
name,
|
|
6
18
|
children,
|
|
7
19
|
handleClick,
|
|
20
|
+
handleDoubleClick,
|
|
8
21
|
handleRenameClick,
|
|
9
22
|
handleDeleteClick,
|
|
10
|
-
|
|
23
|
+
isDirectory = false,
|
|
11
24
|
isActive = false,
|
|
25
|
+
}: {
|
|
26
|
+
name: string;
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
handleClick: (event: React.MouseEvent) => void;
|
|
29
|
+
handleDoubleClick?: (event: React.MouseEvent) => void;
|
|
30
|
+
handleRenameClick: (newName: string) => void;
|
|
31
|
+
handleDeleteClick: () => void;
|
|
32
|
+
isDirectory?: boolean;
|
|
33
|
+
isActive?: boolean;
|
|
12
34
|
}) => {
|
|
13
35
|
// Tracks whether the mouse is hovering over the file or directory
|
|
14
36
|
const [isHovered, setIsHovered] = useState(false);
|
|
@@ -19,28 +41,48 @@ export const Item = ({
|
|
|
19
41
|
// Tracks the value of the input field when renaming
|
|
20
42
|
const [renameValue, setRenameValue] = useState('');
|
|
21
43
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
}, []);
|
|
44
|
+
// Tracks whether the delete confirmation modal is open
|
|
45
|
+
const [showModal, setShowModal] = useState(false);
|
|
46
|
+
|
|
47
|
+
// Ref to track the input DOM, so that we can focus and blur it
|
|
48
|
+
const renameInputRef = useRef(null);
|
|
29
49
|
|
|
30
|
-
const
|
|
31
|
-
(event: React.
|
|
32
|
-
if (event.
|
|
33
|
-
// setEditingValue(value);
|
|
34
|
-
console.log('TODO: handle empty rename');
|
|
35
|
-
} else {
|
|
50
|
+
const onKeyDown = useCallback(
|
|
51
|
+
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
52
|
+
if (event.key === 'Escape') {
|
|
36
53
|
setIsRenaming(false);
|
|
37
|
-
|
|
54
|
+
} else if (event.key === 'Enter') {
|
|
55
|
+
renameInputRef.current.blur();
|
|
38
56
|
}
|
|
39
57
|
},
|
|
40
|
-
[
|
|
58
|
+
[],
|
|
41
59
|
);
|
|
42
60
|
|
|
43
|
-
const
|
|
61
|
+
const onBlur = useCallback(() => {
|
|
62
|
+
setIsRenaming(false);
|
|
63
|
+
if (renameInputRef.current.value.trim() === '') {
|
|
64
|
+
// Renaming to empty signals deletion
|
|
65
|
+
setShowModal(true);
|
|
66
|
+
} else {
|
|
67
|
+
handleRenameClick(renameInputRef.current.value);
|
|
68
|
+
}
|
|
69
|
+
}, [handleRenameClick]);
|
|
70
|
+
|
|
71
|
+
const handleRenameIconClick = useCallback(
|
|
72
|
+
(event: React.MouseEvent) => {
|
|
73
|
+
event.stopPropagation();
|
|
74
|
+
setIsRenaming(!isRenaming);
|
|
75
|
+
setRenameValue(name);
|
|
76
|
+
},
|
|
77
|
+
[name, isRenaming],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Focus the input field when renaming
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (isRenaming) {
|
|
83
|
+
renameInputRef.current.focus();
|
|
84
|
+
}
|
|
85
|
+
}, [isRenaming]);
|
|
44
86
|
|
|
45
87
|
// Function to open the delete file confirmation modal
|
|
46
88
|
const handleModalOpen = useCallback(
|
|
@@ -74,25 +116,28 @@ export const Item = ({
|
|
|
74
116
|
setIsHovered(false);
|
|
75
117
|
}, []);
|
|
76
118
|
|
|
77
|
-
const onChange = useCallback(
|
|
78
|
-
(
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
[],
|
|
82
|
-
);
|
|
119
|
+
const onChange = useCallback(() => {
|
|
120
|
+
setRenameValue(renameInputRef.current.value);
|
|
121
|
+
}, []);
|
|
83
122
|
|
|
84
123
|
return (
|
|
85
124
|
<div
|
|
86
|
-
className={`full-box file-or-directory ${
|
|
125
|
+
className={`full-box file-or-directory user-select-none ${
|
|
87
126
|
isActive ? 'active-file' : ''
|
|
88
127
|
}`}
|
|
89
128
|
onClick={isRenaming ? null : handleClick}
|
|
129
|
+
onDoubleClick={
|
|
130
|
+
isRenaming || !handleDoubleClick
|
|
131
|
+
? null
|
|
132
|
+
: handleDoubleClick
|
|
133
|
+
}
|
|
90
134
|
onMouseEnter={handleMouseEnter}
|
|
91
135
|
onMouseLeave={handleMouseLeave}
|
|
92
136
|
>
|
|
93
137
|
<div className="name">
|
|
94
138
|
{isRenaming ? (
|
|
95
139
|
<input
|
|
140
|
+
ref={renameInputRef}
|
|
96
141
|
type="text"
|
|
97
142
|
aria-label="Field name"
|
|
98
143
|
value={renameValue}
|
|
@@ -104,17 +149,16 @@ export const Item = ({
|
|
|
104
149
|
children
|
|
105
150
|
)}
|
|
106
151
|
</div>
|
|
107
|
-
{isHovered ? (
|
|
152
|
+
{isHovered && !isRenaming ? (
|
|
108
153
|
<div className="utils">
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
></i>
|
|
154
|
+
{(isDirectory ? enableRenameDirectory : true) ? (
|
|
155
|
+
<i
|
|
156
|
+
className="bx bxs-edit utilities"
|
|
157
|
+
style={{ color: '#9edcff' }}
|
|
158
|
+
onClick={handleRenameIconClick}
|
|
159
|
+
></i>
|
|
160
|
+
) : null}
|
|
161
|
+
|
|
118
162
|
<i
|
|
119
163
|
className="bx bx-trash utilities"
|
|
120
164
|
style={{ color: '#ff006b' }}
|
|
@@ -123,10 +167,12 @@ export const Item = ({
|
|
|
123
167
|
</div>
|
|
124
168
|
) : null}
|
|
125
169
|
|
|
126
|
-
<
|
|
170
|
+
<DeleteConfirmationModal
|
|
127
171
|
show={showModal}
|
|
128
172
|
onClose={handleModalClose}
|
|
129
173
|
onConfirm={handleConfirmModal}
|
|
174
|
+
isDirectory={isDirectory}
|
|
175
|
+
name={name}
|
|
130
176
|
/>
|
|
131
177
|
</div>
|
|
132
178
|
);
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
FileId,
|
|
4
4
|
FileTree,
|
|
5
5
|
FileTreeFile,
|
|
6
|
+
FileTreePath,
|
|
6
7
|
} from '../../types';
|
|
7
8
|
import { FileListing } from './FileListing';
|
|
8
9
|
|
|
@@ -10,23 +11,21 @@ import { FileListing } from './FileListing';
|
|
|
10
11
|
// that appears in the Sidebar.
|
|
11
12
|
export const Listing = ({
|
|
12
13
|
entity,
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
renameFile,
|
|
15
|
+
deleteFile,
|
|
16
|
+
deleteDirectory,
|
|
15
17
|
handleFileClick,
|
|
18
|
+
handleFileDoubleClick,
|
|
16
19
|
isDirectoryOpen,
|
|
17
20
|
toggleDirectory,
|
|
18
21
|
activeFileId,
|
|
19
22
|
}: {
|
|
20
23
|
entity: FileTree | FileTreeFile;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
) => void;
|
|
25
|
-
handleDeleteFileClick: (
|
|
26
|
-
fileId: FileId,
|
|
27
|
-
event: React.MouseEvent,
|
|
28
|
-
) => void;
|
|
24
|
+
renameFile: (fileId: FileId, newName: string) => void;
|
|
25
|
+
deleteFile: (fileId: FileId) => void;
|
|
26
|
+
deleteDirectory: (path: FileTreePath) => void;
|
|
29
27
|
handleFileClick: (fileId: FileId) => void;
|
|
28
|
+
handleFileDoubleClick: (fileId: FileId) => void;
|
|
30
29
|
isDirectoryOpen: (path: string) => boolean;
|
|
31
30
|
toggleDirectory: (path: string) => void;
|
|
32
31
|
activeFileId: FileId;
|
|
@@ -37,9 +36,10 @@ export const Listing = ({
|
|
|
37
36
|
<FileListing
|
|
38
37
|
fileId={fileId}
|
|
39
38
|
name={name}
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
renameFile={renameFile}
|
|
40
|
+
deleteFile={deleteFile}
|
|
42
41
|
handleFileClick={handleFileClick}
|
|
42
|
+
handleFileDoubleClick={handleFileDoubleClick}
|
|
43
43
|
isActive={fileId === activeFileId}
|
|
44
44
|
/>
|
|
45
45
|
) : (
|
|
@@ -47,11 +47,14 @@ export const Listing = ({
|
|
|
47
47
|
name={name}
|
|
48
48
|
path={path}
|
|
49
49
|
children={children}
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
renameFile={renameFile}
|
|
51
|
+
deleteFile={deleteFile}
|
|
52
|
+
deleteDirectory={deleteDirectory}
|
|
52
53
|
handleFileClick={handleFileClick}
|
|
54
|
+
handleFileDoubleClick={handleFileDoubleClick}
|
|
53
55
|
isDirectoryOpen={isDirectoryOpen}
|
|
54
56
|
toggleDirectory={toggleDirectory}
|
|
57
|
+
activeFileId={activeFileId}
|
|
55
58
|
/>
|
|
56
59
|
);
|
|
57
60
|
};
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
FileId,
|
|
4
4
|
FileTree,
|
|
5
5
|
FileTreeFile,
|
|
6
|
+
FileTreePath,
|
|
6
7
|
Files,
|
|
7
8
|
} from '../../types';
|
|
8
9
|
import { getFileTree } from '../getFileTree';
|
|
@@ -13,30 +14,32 @@ import { Listing } from './Listing';
|
|
|
13
14
|
import './styles.scss';
|
|
14
15
|
|
|
15
16
|
export const VZSidebar = ({
|
|
16
|
-
createFile,
|
|
17
17
|
files,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
createFile,
|
|
19
|
+
renameFile,
|
|
20
|
+
deleteFile,
|
|
21
|
+
deleteDirectory,
|
|
22
|
+
openTab,
|
|
21
23
|
setIsSettingsOpen,
|
|
22
24
|
isDirectoryOpen,
|
|
23
25
|
toggleDirectory,
|
|
24
26
|
activeFileId,
|
|
25
27
|
}: {
|
|
26
28
|
files: Files;
|
|
27
|
-
createFile
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
createFile: () => void;
|
|
30
|
+
renameFile: (fileId: FileId, newName: string) => void;
|
|
31
|
+
deleteFile: (fileId: FileId) => void;
|
|
32
|
+
deleteDirectory: (path: FileTreePath) => void;
|
|
33
|
+
openTab: ({
|
|
34
|
+
fileId,
|
|
35
|
+
isTransient,
|
|
36
|
+
}: {
|
|
37
|
+
fileId: FileId;
|
|
38
|
+
isTransient?: boolean;
|
|
39
|
+
}) => void;
|
|
40
|
+
setIsSettingsOpen: (isSettingsOpen: boolean) => void;
|
|
41
|
+
isDirectoryOpen: (path: string) => boolean;
|
|
42
|
+
toggleDirectory: (path: string) => void;
|
|
40
43
|
activeFileId?: FileId;
|
|
41
44
|
}) => {
|
|
42
45
|
const fileTree = useMemo(
|
|
@@ -52,6 +55,22 @@ export const VZSidebar = ({
|
|
|
52
55
|
SplitPaneResizeContext,
|
|
53
56
|
);
|
|
54
57
|
|
|
58
|
+
// On single-click, open the file in a transient tab.
|
|
59
|
+
const handleFileClick = useCallback(
|
|
60
|
+
(fileId: FileId) => {
|
|
61
|
+
openTab({ fileId, isTransient: true });
|
|
62
|
+
},
|
|
63
|
+
[openTab],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// On double-click, open the file in a persistent tab.
|
|
67
|
+
const handleFileDoubleClick = useCallback(
|
|
68
|
+
(fileId: FileId) => {
|
|
69
|
+
openTab({ fileId, isTransient: false });
|
|
70
|
+
},
|
|
71
|
+
[openTab],
|
|
72
|
+
);
|
|
73
|
+
|
|
55
74
|
return (
|
|
56
75
|
<div
|
|
57
76
|
className="vz-sidebar"
|
|
@@ -75,15 +94,15 @@ export const VZSidebar = ({
|
|
|
75
94
|
const key = fileId ? fileId : path;
|
|
76
95
|
return (
|
|
77
96
|
<Listing
|
|
78
|
-
entity={entity}
|
|
79
97
|
key={key}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
handleDeleteFileClick
|
|
85
|
-
}
|
|
98
|
+
entity={entity}
|
|
99
|
+
renameFile={renameFile}
|
|
100
|
+
deleteFile={deleteFile}
|
|
101
|
+
deleteDirectory={deleteDirectory}
|
|
86
102
|
handleFileClick={handleFileClick}
|
|
103
|
+
handleFileDoubleClick={
|
|
104
|
+
handleFileDoubleClick
|
|
105
|
+
}
|
|
87
106
|
isDirectoryOpen={isDirectoryOpen}
|
|
88
107
|
toggleDirectory={toggleDirectory}
|
|
89
108
|
activeFileId={activeFileId}
|
package/src/client/style.scss
CHANGED
package/src/client/useActions.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { ThemeLabel } from './themes';
|
|
3
|
-
import { FileId } from '../types';
|
|
4
|
-
import { VZAction } from './vzReducer';
|
|
3
|
+
import { FileId, Username } from '../types';
|
|
4
|
+
import { TabState, VZAction } from './vzReducer';
|
|
5
5
|
|
|
6
6
|
// This is a custom hook that returns a set of functions
|
|
7
7
|
// that can be used to dispatch actions to the reducer.
|
|
@@ -19,20 +19,21 @@ export const useActions = (
|
|
|
19
19
|
);
|
|
20
20
|
|
|
21
21
|
const openTab = useCallback(
|
|
22
|
-
(
|
|
22
|
+
(tabState: TabState): void => {
|
|
23
23
|
dispatch({
|
|
24
24
|
type: 'open_tab',
|
|
25
|
-
fileId,
|
|
25
|
+
fileId: tabState.fileId,
|
|
26
|
+
isTransient: tabState.isTransient,
|
|
26
27
|
});
|
|
27
28
|
},
|
|
28
29
|
[dispatch],
|
|
29
30
|
);
|
|
30
31
|
|
|
31
32
|
const closeTabs = useCallback(
|
|
32
|
-
(
|
|
33
|
+
(fileIdsToClose: Array<FileId>) => {
|
|
33
34
|
dispatch({
|
|
34
35
|
type: 'close_tabs',
|
|
35
|
-
|
|
36
|
+
fileIdsToClose,
|
|
36
37
|
});
|
|
37
38
|
},
|
|
38
39
|
[dispatch],
|
|
@@ -63,6 +64,22 @@ export const useActions = (
|
|
|
63
64
|
setIsSettingsOpen(false);
|
|
64
65
|
}, [setIsSettingsOpen]);
|
|
65
66
|
|
|
67
|
+
const editorNoLongerWantsFocus = useCallback(() => {
|
|
68
|
+
dispatch({
|
|
69
|
+
type: 'editor_no_longer_wants_focus',
|
|
70
|
+
});
|
|
71
|
+
}, [dispatch]);
|
|
72
|
+
|
|
73
|
+
const setUsername = useCallback(
|
|
74
|
+
(username: Username) => {
|
|
75
|
+
dispatch({
|
|
76
|
+
type: 'set_username',
|
|
77
|
+
username,
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
[dispatch],
|
|
81
|
+
);
|
|
82
|
+
|
|
66
83
|
return {
|
|
67
84
|
setActiveFileId,
|
|
68
85
|
openTab,
|
|
@@ -70,5 +87,7 @@ export const useActions = (
|
|
|
70
87
|
setTheme,
|
|
71
88
|
setIsSettingsOpen,
|
|
72
89
|
closeSettings,
|
|
90
|
+
editorNoLongerWantsFocus,
|
|
91
|
+
setUsername,
|
|
73
92
|
};
|
|
74
93
|
};
|