vzcode 0.26.0 → 0.28.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-a9585cbc.js +111 -0
- package/dist/assets/index-d30c9f1b.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +6 -4
- package/src/client/AIAssist.ts +46 -0
- package/src/client/App.tsx +86 -43
- package/src/client/CodeEditor/getOrCreateEditor.ts +22 -2
- package/src/client/CodeEditor/index.tsx +75 -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 +37 -0
- package/src/client/CodeErrorOverlay/style.scss +41 -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 +87 -45
- package/src/client/VZSidebar/styles.scss +21 -1
- 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/server/handleAIAssist.js +69 -0
- package/src/server/index.js +9 -0
- 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';
|
|
@@ -12,31 +13,44 @@ import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
|
|
|
12
13
|
import { Listing } from './Listing';
|
|
13
14
|
import './styles.scss';
|
|
14
15
|
|
|
16
|
+
const CreateFileButton = ({ createFile }) => {
|
|
17
|
+
return (
|
|
18
|
+
<i
|
|
19
|
+
className="bx bxs-file-plus new-btn"
|
|
20
|
+
style={{ color: '#dbdde1' }}
|
|
21
|
+
onClick={createFile}
|
|
22
|
+
// TODO better tooltip
|
|
23
|
+
title="Create file"
|
|
24
|
+
></i>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
15
27
|
export const VZSidebar = ({
|
|
16
|
-
createFile,
|
|
17
28
|
files,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
createFile,
|
|
30
|
+
renameFile,
|
|
31
|
+
deleteFile,
|
|
32
|
+
deleteDirectory,
|
|
33
|
+
openTab,
|
|
21
34
|
setIsSettingsOpen,
|
|
22
35
|
isDirectoryOpen,
|
|
23
36
|
toggleDirectory,
|
|
24
37
|
activeFileId,
|
|
25
38
|
}: {
|
|
26
39
|
files: Files;
|
|
27
|
-
createFile
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
createFile: () => void;
|
|
41
|
+
renameFile: (fileId: FileId, newName: string) => void;
|
|
42
|
+
deleteFile: (fileId: FileId) => void;
|
|
43
|
+
deleteDirectory: (path: FileTreePath) => void;
|
|
44
|
+
openTab: ({
|
|
45
|
+
fileId,
|
|
46
|
+
isTransient,
|
|
47
|
+
}: {
|
|
48
|
+
fileId: FileId;
|
|
49
|
+
isTransient?: boolean;
|
|
50
|
+
}) => void;
|
|
51
|
+
setIsSettingsOpen: (isSettingsOpen: boolean) => void;
|
|
52
|
+
isDirectoryOpen: (path: string) => boolean;
|
|
53
|
+
toggleDirectory: (path: string) => void;
|
|
40
54
|
activeFileId?: FileId;
|
|
41
55
|
}) => {
|
|
42
56
|
const fileTree = useMemo(
|
|
@@ -52,6 +66,28 @@ export const VZSidebar = ({
|
|
|
52
66
|
SplitPaneResizeContext,
|
|
53
67
|
);
|
|
54
68
|
|
|
69
|
+
// On single-click, open the file in a transient tab.
|
|
70
|
+
const handleFileClick = useCallback(
|
|
71
|
+
(fileId: FileId) => {
|
|
72
|
+
openTab({ fileId, isTransient: true });
|
|
73
|
+
},
|
|
74
|
+
[openTab],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// On double-click, open the file in a persistent tab.
|
|
78
|
+
const handleFileDoubleClick = useCallback(
|
|
79
|
+
(fileId: FileId) => {
|
|
80
|
+
openTab({ fileId, isTransient: false });
|
|
81
|
+
},
|
|
82
|
+
[openTab],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// True if files exist.
|
|
86
|
+
const filesExist =
|
|
87
|
+
fileTree &&
|
|
88
|
+
fileTree.children &&
|
|
89
|
+
fileTree.children.length > 0;
|
|
90
|
+
|
|
55
91
|
return (
|
|
56
92
|
<div
|
|
57
93
|
className="vz-sidebar"
|
|
@@ -61,36 +97,42 @@ export const VZSidebar = ({
|
|
|
61
97
|
<div className="full-box">
|
|
62
98
|
<div className="sidebar-section-hint">Files</div>
|
|
63
99
|
<div>
|
|
64
|
-
<
|
|
65
|
-
className="bx bxs-file-plus new-btn"
|
|
66
|
-
style={{ color: '#dbdde1' }}
|
|
67
|
-
onClick={createFile}
|
|
68
|
-
></i>
|
|
100
|
+
<CreateFileButton createFile={createFile} />
|
|
69
101
|
</div>
|
|
70
102
|
</div>
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
{filesExist ? (
|
|
104
|
+
fileTree.children.map((entity) => {
|
|
105
|
+
const { fileId } = entity as FileTreeFile;
|
|
106
|
+
const { path } = entity as FileTree;
|
|
107
|
+
const key = fileId ? fileId : path;
|
|
108
|
+
return (
|
|
109
|
+
<Listing
|
|
110
|
+
key={key}
|
|
111
|
+
entity={entity}
|
|
112
|
+
renameFile={renameFile}
|
|
113
|
+
deleteFile={deleteFile}
|
|
114
|
+
deleteDirectory={deleteDirectory}
|
|
115
|
+
handleFileClick={handleFileClick}
|
|
116
|
+
handleFileDoubleClick={
|
|
117
|
+
handleFileDoubleClick
|
|
118
|
+
}
|
|
119
|
+
isDirectoryOpen={isDirectoryOpen}
|
|
120
|
+
toggleDirectory={toggleDirectory}
|
|
121
|
+
activeFileId={activeFileId}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
})
|
|
125
|
+
) : (
|
|
126
|
+
<div className="empty">
|
|
127
|
+
<div className="empty-text">
|
|
128
|
+
It looks like you don't have any files yet!
|
|
129
|
+
Click the "Create file" button below to create
|
|
130
|
+
your first file.
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<CreateFileButton createFile={createFile} />
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
94
136
|
</div>
|
|
95
137
|
{disableSettings ? null : (
|
|
96
138
|
<div
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
.files {
|
|
18
18
|
overflow: auto;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
flex: 1;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
.file-or-directory {
|
|
@@ -46,7 +49,6 @@
|
|
|
46
49
|
gap: 8px;
|
|
47
50
|
}
|
|
48
51
|
.utilities {
|
|
49
|
-
// margin-right: 3px;
|
|
50
52
|
transform: scale(1.2);
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -85,4 +87,22 @@
|
|
|
85
87
|
margin: 10px;
|
|
86
88
|
border-radius: 4px;
|
|
87
89
|
}
|
|
90
|
+
|
|
91
|
+
/* Empty state for when there are no files */
|
|
92
|
+
.empty {
|
|
93
|
+
display: flex;
|
|
94
|
+
flex: 1;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
align-items: center;
|
|
97
|
+
justify-content: space-evenly;
|
|
98
|
+
margin: 10px;
|
|
99
|
+
padding: 20px;
|
|
100
|
+
border: 1px dashed rgba(255, 255, 255, 0.5); /* White dashed outline */
|
|
101
|
+
border-radius: 10px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.empty-text {
|
|
105
|
+
margin-bottom: 10px;
|
|
106
|
+
font-size: 14px;
|
|
107
|
+
}
|
|
88
108
|
}
|
package/src/client/style.scss
CHANGED