vzcode 0.25.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 -6
- package/package.json +7 -7
- package/src/client/App.tsx +110 -247
- package/src/client/CodeEditor/getOrCreateEditor.ts +6 -1
- package/src/client/CodeEditor/index.tsx +94 -16
- package/src/client/CodeEditor/{json1PresenceBroadcast.js → json1PresenceBroadcast.ts} +9 -3
- package/src/client/CodeEditor/{json1PresenceDisplay.js → json1PresenceDisplay.ts} +9 -48
- 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 +119 -106
- package/src/client/SplitPaneResizeContext.tsx +12 -7
- package/src/client/TabList/Tab.tsx +70 -0
- package/src/client/TabList/index.tsx +28 -69
- package/src/client/TabList/style.scss +26 -9
- package/src/client/{Settings.tsx → VZSettings.tsx} +34 -21
- package/src/client/{Sidebar/ConfirmationBox.tsx → VZSidebar/DeleteConfirmationModal.tsx} +17 -14
- package/src/client/{Sidebar → VZSidebar}/DirectoryListing.tsx +27 -26
- package/src/client/{Sidebar → VZSidebar}/FileListing.tsx +19 -19
- package/src/client/VZSidebar/Item.tsx +179 -0
- package/src/client/{Sidebar → VZSidebar}/Listing.tsx +20 -14
- package/src/client/{Sidebar → VZSidebar}/index.tsx +48 -30
- package/src/client/{Sidebar → VZSidebar}/styles.scss +17 -12
- package/src/client/bootstrap.ts +3 -0
- package/src/client/style.scss +8 -2
- package/src/client/useActions.ts +93 -0
- package/src/client/useEditorCache.ts +2 -1
- package/src/client/useFileCRUD.ts +103 -0
- package/src/client/useOpenDirectories.ts +25 -7
- package/src/client/useSubmitOperation.ts +25 -0
- 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 +40 -0
- package/src/server/index.js +13 -2
- package/src/types.ts +12 -5
- package/dist/assets/index-8628ebab.js +0 -111
- package/dist/assets/index-cf19de3b.css +0 -1
- package/src/client/PrettierErrorOverlay/index.tsx +0 -9
- package/src/client/PrettierErrorOverlay/style.scss +0 -9
- package/src/client/Sidebar/Item.tsx +0 -130
- package/src/client/reducer.ts +0 -119
- /package/src/client/{Sidebar → VZSidebar}/DirectoryArrowSVG.jsx +0 -0
|
@@ -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,47 +5,47 @@ 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,
|
|
12
|
+
isActive,
|
|
11
13
|
}: {
|
|
12
14
|
name: string;
|
|
13
15
|
fileId: FileId;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
newName: string,
|
|
17
|
-
) => void;
|
|
18
|
-
handleDeleteFileClick: (
|
|
19
|
-
fileId: FileId,
|
|
20
|
-
event: React.MouseEvent,
|
|
21
|
-
) => void;
|
|
16
|
+
renameFile: (fileId: FileId, newName: string) => void;
|
|
17
|
+
deleteFile: (fileId: FileId) => void;
|
|
22
18
|
handleFileClick: (fileId: FileId) => void;
|
|
19
|
+
handleFileDoubleClick: (fileId: FileId) => void;
|
|
20
|
+
isActive: boolean;
|
|
23
21
|
}) => {
|
|
24
22
|
const handleClick = useCallback(() => {
|
|
25
23
|
handleFileClick(fileId);
|
|
26
24
|
}, [fileId, handleFileClick]);
|
|
27
25
|
|
|
28
|
-
const
|
|
29
|
-
(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
const handleDoubleClick = useCallback(() => {
|
|
27
|
+
handleFileDoubleClick(fileId);
|
|
28
|
+
}, [fileId, handleFileDoubleClick]);
|
|
29
|
+
|
|
30
|
+
const handleDeleteClick = useCallback(() => {
|
|
31
|
+
deleteFile(fileId);
|
|
32
|
+
}, [fileId, deleteFile]);
|
|
34
33
|
|
|
35
34
|
const handleRenameClick = useCallback(
|
|
36
35
|
(newName: string) => {
|
|
37
|
-
|
|
36
|
+
renameFile(fileId, newName);
|
|
38
37
|
},
|
|
39
|
-
[fileId,
|
|
38
|
+
[fileId, renameFile],
|
|
40
39
|
);
|
|
41
40
|
|
|
42
41
|
return (
|
|
43
42
|
<Item
|
|
44
43
|
name={name}
|
|
45
44
|
handleClick={handleClick}
|
|
45
|
+
handleDoubleClick={handleDoubleClick}
|
|
46
46
|
handleDeleteClick={handleDeleteClick}
|
|
47
47
|
handleRenameClick={handleRenameClick}
|
|
48
|
-
|
|
48
|
+
isActive={isActive}
|
|
49
49
|
>
|
|
50
50
|
{name}
|
|
51
51
|
</Item>
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useCallback,
|
|
4
|
+
useRef,
|
|
5
|
+
useEffect,
|
|
6
|
+
} from 'react';
|
|
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.
|
|
16
|
+
export const Item = ({
|
|
17
|
+
name,
|
|
18
|
+
children,
|
|
19
|
+
handleClick,
|
|
20
|
+
handleDoubleClick,
|
|
21
|
+
handleRenameClick,
|
|
22
|
+
handleDeleteClick,
|
|
23
|
+
isDirectory = false,
|
|
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;
|
|
34
|
+
}) => {
|
|
35
|
+
// Tracks whether the mouse is hovering over the file or directory
|
|
36
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
37
|
+
|
|
38
|
+
// Tracks whether the file is being renamed (inline text input)
|
|
39
|
+
const [isRenaming, setIsRenaming] = useState(false);
|
|
40
|
+
|
|
41
|
+
// Tracks the value of the input field when renaming
|
|
42
|
+
const [renameValue, setRenameValue] = useState('');
|
|
43
|
+
|
|
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);
|
|
49
|
+
|
|
50
|
+
const onKeyDown = useCallback(
|
|
51
|
+
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
52
|
+
if (event.key === 'Escape') {
|
|
53
|
+
setIsRenaming(false);
|
|
54
|
+
} else if (event.key === 'Enter') {
|
|
55
|
+
renameInputRef.current.blur();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
[],
|
|
59
|
+
);
|
|
60
|
+
|
|
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]);
|
|
86
|
+
|
|
87
|
+
// Function to open the delete file confirmation modal
|
|
88
|
+
const handleModalOpen = useCallback(
|
|
89
|
+
(event: React.MouseEvent) => {
|
|
90
|
+
event.stopPropagation();
|
|
91
|
+
setShowModal(true);
|
|
92
|
+
},
|
|
93
|
+
[],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Function to close the modal
|
|
97
|
+
const handleModalClose = useCallback(() => {
|
|
98
|
+
setShowModal(false);
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
// Function to handle confirmation on modal
|
|
102
|
+
const handleConfirmModal = useCallback(
|
|
103
|
+
(event: React.MouseEvent) => {
|
|
104
|
+
event.stopPropagation();
|
|
105
|
+
setShowModal(false);
|
|
106
|
+
handleDeleteClick();
|
|
107
|
+
},
|
|
108
|
+
[handleDeleteClick],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const handleMouseEnter = useCallback(() => {
|
|
112
|
+
setIsHovered(true);
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
const handleMouseLeave = useCallback(() => {
|
|
116
|
+
setIsHovered(false);
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
const onChange = useCallback(() => {
|
|
120
|
+
setRenameValue(renameInputRef.current.value);
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
className={`full-box file-or-directory user-select-none ${
|
|
126
|
+
isActive ? 'active-file' : ''
|
|
127
|
+
}`}
|
|
128
|
+
onClick={isRenaming ? null : handleClick}
|
|
129
|
+
onDoubleClick={
|
|
130
|
+
isRenaming || !handleDoubleClick
|
|
131
|
+
? null
|
|
132
|
+
: handleDoubleClick
|
|
133
|
+
}
|
|
134
|
+
onMouseEnter={handleMouseEnter}
|
|
135
|
+
onMouseLeave={handleMouseLeave}
|
|
136
|
+
>
|
|
137
|
+
<div className="name">
|
|
138
|
+
{isRenaming ? (
|
|
139
|
+
<input
|
|
140
|
+
ref={renameInputRef}
|
|
141
|
+
type="text"
|
|
142
|
+
aria-label="Field name"
|
|
143
|
+
value={renameValue}
|
|
144
|
+
onKeyDown={onKeyDown}
|
|
145
|
+
onBlur={onBlur}
|
|
146
|
+
onChange={onChange}
|
|
147
|
+
/>
|
|
148
|
+
) : (
|
|
149
|
+
children
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
{isHovered && !isRenaming ? (
|
|
153
|
+
<div className="utils">
|
|
154
|
+
{(isDirectory ? enableRenameDirectory : true) ? (
|
|
155
|
+
<i
|
|
156
|
+
className="bx bxs-edit utilities"
|
|
157
|
+
style={{ color: '#9edcff' }}
|
|
158
|
+
onClick={handleRenameIconClick}
|
|
159
|
+
></i>
|
|
160
|
+
) : null}
|
|
161
|
+
|
|
162
|
+
<i
|
|
163
|
+
className="bx bx-trash utilities"
|
|
164
|
+
style={{ color: '#ff006b' }}
|
|
165
|
+
onClick={handleModalOpen}
|
|
166
|
+
></i>
|
|
167
|
+
</div>
|
|
168
|
+
) : null}
|
|
169
|
+
|
|
170
|
+
<DeleteConfirmationModal
|
|
171
|
+
show={showModal}
|
|
172
|
+
onClose={handleModalClose}
|
|
173
|
+
onConfirm={handleConfirmModal}
|
|
174
|
+
isDirectory={isDirectory}
|
|
175
|
+
name={name}
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
@@ -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,24 +11,24 @@ 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,
|
|
21
|
+
activeFileId,
|
|
18
22
|
}: {
|
|
19
23
|
entity: FileTree | FileTreeFile;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
) => void;
|
|
24
|
-
handleDeleteFileClick: (
|
|
25
|
-
fileId: FileId,
|
|
26
|
-
event: React.MouseEvent,
|
|
27
|
-
) => void;
|
|
24
|
+
renameFile: (fileId: FileId, newName: string) => void;
|
|
25
|
+
deleteFile: (fileId: FileId) => void;
|
|
26
|
+
deleteDirectory: (path: FileTreePath) => void;
|
|
28
27
|
handleFileClick: (fileId: FileId) => void;
|
|
28
|
+
handleFileDoubleClick: (fileId: FileId) => void;
|
|
29
29
|
isDirectoryOpen: (path: string) => boolean;
|
|
30
30
|
toggleDirectory: (path: string) => void;
|
|
31
|
+
activeFileId: FileId;
|
|
31
32
|
}) => {
|
|
32
33
|
const { name, file, fileId } = entity as FileTreeFile;
|
|
33
34
|
const { path, children } = entity as FileTree;
|
|
@@ -35,20 +36,25 @@ export const Listing = ({
|
|
|
35
36
|
<FileListing
|
|
36
37
|
fileId={fileId}
|
|
37
38
|
name={name}
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
renameFile={renameFile}
|
|
40
|
+
deleteFile={deleteFile}
|
|
40
41
|
handleFileClick={handleFileClick}
|
|
42
|
+
handleFileDoubleClick={handleFileDoubleClick}
|
|
43
|
+
isActive={fileId === activeFileId}
|
|
41
44
|
/>
|
|
42
45
|
) : (
|
|
43
46
|
<DirectoryListing
|
|
44
47
|
name={name}
|
|
45
48
|
path={path}
|
|
46
49
|
children={children}
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
renameFile={renameFile}
|
|
51
|
+
deleteFile={deleteFile}
|
|
52
|
+
deleteDirectory={deleteDirectory}
|
|
49
53
|
handleFileClick={handleFileClick}
|
|
54
|
+
handleFileDoubleClick={handleFileDoubleClick}
|
|
50
55
|
isDirectoryOpen={isDirectoryOpen}
|
|
51
56
|
toggleDirectory={toggleDirectory}
|
|
57
|
+
activeFileId={activeFileId}
|
|
52
58
|
/>
|
|
53
59
|
);
|
|
54
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,30 +13,34 @@ import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
|
|
|
12
13
|
import { Listing } from './Listing';
|
|
13
14
|
import './styles.scss';
|
|
14
15
|
|
|
15
|
-
export const
|
|
16
|
-
createFile,
|
|
16
|
+
export const VZSidebar = ({
|
|
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,
|
|
26
|
+
activeFileId,
|
|
24
27
|
}: {
|
|
25
28
|
files: Files;
|
|
26
|
-
createFile
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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;
|
|
43
|
+
activeFileId?: FileId;
|
|
39
44
|
}) => {
|
|
40
45
|
const fileTree = useMemo(
|
|
41
46
|
() => (files ? sortFileTree(getFileTree(files)) : null),
|
|
@@ -50,6 +55,22 @@ export const Sidebar = ({
|
|
|
50
55
|
SplitPaneResizeContext,
|
|
51
56
|
);
|
|
52
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
|
+
|
|
53
74
|
return (
|
|
54
75
|
<div
|
|
55
76
|
className="vz-sidebar"
|
|
@@ -57,11 +78,7 @@ export const Sidebar = ({
|
|
|
57
78
|
>
|
|
58
79
|
<div className="files">
|
|
59
80
|
<div className="full-box">
|
|
60
|
-
<div>
|
|
61
|
-
<a className="link-name" href="#">
|
|
62
|
-
Files
|
|
63
|
-
</a>
|
|
64
|
-
</div>
|
|
81
|
+
<div className="sidebar-section-hint">Files</div>
|
|
65
82
|
<div>
|
|
66
83
|
<i
|
|
67
84
|
className="bx bxs-file-plus new-btn"
|
|
@@ -77,17 +94,18 @@ export const Sidebar = ({
|
|
|
77
94
|
const key = fileId ? fileId : path;
|
|
78
95
|
return (
|
|
79
96
|
<Listing
|
|
80
|
-
entity={entity}
|
|
81
97
|
key={key}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
handleDeleteFileClick
|
|
87
|
-
}
|
|
98
|
+
entity={entity}
|
|
99
|
+
renameFile={renameFile}
|
|
100
|
+
deleteFile={deleteFile}
|
|
101
|
+
deleteDirectory={deleteDirectory}
|
|
88
102
|
handleFileClick={handleFileClick}
|
|
103
|
+
handleFileDoubleClick={
|
|
104
|
+
handleFileDoubleClick
|
|
105
|
+
}
|
|
89
106
|
isDirectoryOpen={isDirectoryOpen}
|
|
90
107
|
toggleDirectory={toggleDirectory}
|
|
108
|
+
activeFileId={activeFileId}
|
|
91
109
|
/>
|
|
92
110
|
);
|
|
93
111
|
})
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
.vz-sidebar {
|
|
2
2
|
height: 100%;
|
|
3
|
-
|
|
3
|
+
color: #d6d9de;
|
|
4
|
+
background: #33415b;
|
|
4
5
|
display: flex;
|
|
5
6
|
flex-direction: column;
|
|
6
7
|
justify-content: space-between;
|
|
7
8
|
|
|
8
9
|
.full-box {
|
|
9
|
-
padding:
|
|
10
|
+
padding: 3px 6px;
|
|
10
11
|
display: flex;
|
|
11
12
|
flex-direction: row;
|
|
12
13
|
align-items: center;
|
|
@@ -24,16 +25,14 @@
|
|
|
24
25
|
justify-content: space-between;
|
|
25
26
|
cursor: pointer;
|
|
26
27
|
margin: 0 10px;
|
|
27
|
-
|
|
28
|
-
border-radius: 5px;
|
|
28
|
+
border-radius: 4px;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
.
|
|
32
|
-
margin-top: 30px;
|
|
31
|
+
.sidebar-section-hint {
|
|
32
|
+
// margin-top: 30px;
|
|
33
33
|
margin-left: 10px;
|
|
34
|
-
color: rgb(154, 160, 172);
|
|
35
34
|
font-size: 12px;
|
|
36
|
-
|
|
35
|
+
color: #99a0ad;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
.new-btn {
|
|
@@ -44,14 +43,21 @@
|
|
|
44
43
|
|
|
45
44
|
.utils {
|
|
46
45
|
display: flex;
|
|
46
|
+
gap: 8px;
|
|
47
47
|
}
|
|
48
48
|
.utilities {
|
|
49
|
-
margin-right: 3px;
|
|
49
|
+
// margin-right: 3px;
|
|
50
|
+
transform: scale(1.2);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
.file-or-directory:hover,
|
|
53
54
|
.settings:hover {
|
|
54
|
-
background-color:
|
|
55
|
+
background-color: #47546b;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.active-file,
|
|
59
|
+
.active-file:hover {
|
|
60
|
+
background-color: #202e46;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
.name {
|
|
@@ -71,13 +77,12 @@
|
|
|
71
77
|
|
|
72
78
|
.settings {
|
|
73
79
|
height: 30px;
|
|
74
|
-
color: rgb(219, 221, 225);
|
|
75
80
|
cursor: pointer;
|
|
76
81
|
display: flex;
|
|
77
82
|
align-items: center;
|
|
78
83
|
justify-content: center;
|
|
79
84
|
padding: 14px 0;
|
|
80
85
|
margin: 10px;
|
|
81
|
-
border-radius:
|
|
86
|
+
border-radius: 4px;
|
|
82
87
|
}
|
|
83
88
|
}
|
package/src/client/bootstrap.ts
CHANGED
|
@@ -5,4 +5,7 @@ import Form from 'react-bootstrap/cjs/Form.js';
|
|
|
5
5
|
import Button from 'react-bootstrap/cjs/Button.js';
|
|
6
6
|
import Modal from 'react-bootstrap/cjs/Modal.js';
|
|
7
7
|
|
|
8
|
+
// Pull in custom CSS from vizhub-ui.
|
|
9
|
+
import 'vizhub-ui/dist/vizhub-ui.css';
|
|
10
|
+
|
|
8
11
|
export { Form, Button, Modal };
|
package/src/client/style.scss
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
:root {
|
|
2
2
|
--vzcode-font-family: ui-monospace, SFMono-Regular,
|
|
3
|
-
SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
|
3
|
+
'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace !important;
|
|
4
|
+
|
|
5
|
+
--vzcode-font-size: 16px;
|
|
6
|
+
|
|
7
|
+
/* Variable for the background color */
|
|
4
8
|
--vz-bg-color: rgb(
|
|
5
9
|
41,
|
|
6
10
|
44,
|
|
@@ -36,7 +40,9 @@ body,
|
|
|
36
40
|
|
|
37
41
|
.app {
|
|
38
42
|
background-color: var(--vz-bg-color);
|
|
39
|
-
font-family: '
|
|
43
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
44
|
+
'Noto Sans', Helvetica, Arial, sans-serif,
|
|
45
|
+
'Apple Color Emoji', 'Segoe UI Emoji';
|
|
40
46
|
display: flex;
|
|
41
47
|
|
|
42
48
|
/* To support Resizer, which is position: absolute */
|