vzcode 0.5.0 → 0.7.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 +20 -5
- package/dist/assets/index-ccd52a89.css +1 -0
- package/dist/assets/index-d2d6b59b.js +89 -0
- package/dist/index.html +17 -3
- package/package.json +26 -21
- package/src/client/{App.jsx → App.tsx} +75 -120
- package/src/client/CodeEditor/getOrCreateEditor.ts +113 -0
- package/src/client/CodeEditor/index.tsx +50 -0
- package/src/client/{json1PresenceDisplay.js → CodeEditor/json1PresenceDisplay.js} +2 -2
- package/src/client/CodeEditor/style.scss +11 -0
- package/src/client/Settings.tsx +76 -0
- package/src/client/Sidebar/DirectoryListing.tsx +77 -0
- package/src/client/Sidebar/{File.jsx → FileListing.tsx} +14 -4
- package/src/client/Sidebar/Listing.tsx +44 -0
- package/src/client/Sidebar/index.tsx +81 -0
- package/src/client/Sidebar/styles.scss +84 -0
- package/src/client/TabList/index.tsx +82 -0
- package/src/client/TabList/style.scss +32 -0
- package/src/client/{getFileTree.js → getFileTree.ts} +6 -3
- package/src/client/main.jsx +1 -1
- package/src/client/{sortFileTree.js → sortFileTree.ts} +7 -3
- package/src/client/style.scss +49 -0
- package/src/client/themes.ts +31 -0
- package/src/client/{Sidebar/useOpenDirectories.js → useOpenDirectories.ts} +18 -14
- package/src/client/useTabsState.ts +51 -0
- package/src/server/index.js +0 -0
- package/src/types.ts +69 -0
- package/dist/assets/index-29b6a606.css +0 -1
- package/dist/assets/index-523ecad7.js +0 -89
- package/src/client/CodeEditor.jsx +0 -31
- package/src/client/Sidebar/Directory.jsx +0 -62
- package/src/client/Sidebar/FileOrDirectory.jsx +0 -33
- package/src/client/Sidebar/index.jsx +0 -64
- package/src/client/Sidebar/styles.css +0 -88
- package/src/client/getOrCreateEditor.js +0 -84
- package/src/client/settings.jsx +0 -141
- package/src/client/style.css +0 -120
- /package/src/client/{json1PresenceBroadcast.js → CodeEditor/json1PresenceBroadcast.js} +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
import { Item } from './Item';
|
|
3
|
+
import { Listing } from './Listing';
|
|
4
|
+
import { DirectoryArrowSVG } from './DirectoryArrowSVG';
|
|
5
|
+
import { FileTree, FileTreeFile } from '../../types';
|
|
6
|
+
|
|
7
|
+
export const DirectoryListing = ({
|
|
8
|
+
name,
|
|
9
|
+
path,
|
|
10
|
+
children,
|
|
11
|
+
handleRenameFileClick,
|
|
12
|
+
handleDeleteFileClick,
|
|
13
|
+
handleFileClick,
|
|
14
|
+
isDirectoryOpen,
|
|
15
|
+
toggleDirectory,
|
|
16
|
+
}: {
|
|
17
|
+
name: string;
|
|
18
|
+
path: string;
|
|
19
|
+
children: Array<FileTree | FileTreeFile>;
|
|
20
|
+
handleRenameFileClick: (fileId: string) => void;
|
|
21
|
+
handleDeleteFileClick: (fileId: string, event: React.MouseEvent) => void;
|
|
22
|
+
handleFileClick: (fileId: string) => void;
|
|
23
|
+
isDirectoryOpen: (path: string) => boolean;
|
|
24
|
+
toggleDirectory: (path: string) => void;
|
|
25
|
+
}) => {
|
|
26
|
+
const handleClick = useCallback(() => {
|
|
27
|
+
toggleDirectory(path);
|
|
28
|
+
}, [toggleDirectory]);
|
|
29
|
+
|
|
30
|
+
const handleDeleteClick = useCallback(() => {
|
|
31
|
+
console.log('TODO handleDeleteDirectoryClick');
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const handleRenameClick = useCallback(() => {
|
|
35
|
+
console.log('TODO handleRenameDirectoryClick');
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const isOpen = useMemo(() => isDirectoryOpen(path), [isDirectoryOpen, path]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
<Item
|
|
43
|
+
name={name}
|
|
44
|
+
handleClick={handleClick}
|
|
45
|
+
handleDeleteClick={handleDeleteClick}
|
|
46
|
+
handleRenameClick={handleRenameClick}
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
className="arrow-wrapper"
|
|
50
|
+
style={{ transform: `rotate(${isOpen ? 90 : 0}deg)` }}
|
|
51
|
+
>
|
|
52
|
+
<DirectoryArrowSVG />
|
|
53
|
+
</div>
|
|
54
|
+
{name}
|
|
55
|
+
</Item>
|
|
56
|
+
{children && isOpen ? (
|
|
57
|
+
<div className="indentation">
|
|
58
|
+
{children.map((entity) => {
|
|
59
|
+
const { fileId } = entity as FileTreeFile;
|
|
60
|
+
const { path } = entity as FileTree;
|
|
61
|
+
return (
|
|
62
|
+
<Listing
|
|
63
|
+
entity={entity}
|
|
64
|
+
key={fileId || path}
|
|
65
|
+
handleRenameFileClick={handleRenameFileClick}
|
|
66
|
+
handleDeleteFileClick={handleDeleteFileClick}
|
|
67
|
+
handleFileClick={handleFileClick}
|
|
68
|
+
isDirectoryOpen={isDirectoryOpen}
|
|
69
|
+
toggleDirectory={toggleDirectory}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
})}
|
|
73
|
+
</div>
|
|
74
|
+
) : null}
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { Item } from './Item';
|
|
3
|
+
import { FileId } from '../../types';
|
|
3
4
|
|
|
4
|
-
export const
|
|
5
|
+
export const FileListing = ({
|
|
5
6
|
name,
|
|
6
7
|
fileId,
|
|
7
8
|
handleRenameFileClick,
|
|
8
9
|
handleDeleteFileClick,
|
|
9
10
|
handleFileClick,
|
|
11
|
+
}: {
|
|
12
|
+
name: string;
|
|
13
|
+
fileId: FileId;
|
|
14
|
+
handleRenameFileClick: (fileId: FileId) => void;
|
|
15
|
+
handleDeleteFileClick: (fileId: FileId, event: React.MouseEvent) => void;
|
|
16
|
+
handleFileClick: (fileId: FileId) => void;
|
|
10
17
|
}) => {
|
|
11
18
|
const handleClick = useCallback(() => {
|
|
12
19
|
handleFileClick(fileId);
|
|
13
20
|
}, [fileId, handleFileClick]);
|
|
14
21
|
|
|
15
|
-
const handleDeleteClick = useCallback(
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
const handleDeleteClick = useCallback(
|
|
23
|
+
(event) => {
|
|
24
|
+
handleDeleteFileClick(fileId, event);
|
|
25
|
+
},
|
|
26
|
+
[fileId],
|
|
27
|
+
);
|
|
18
28
|
|
|
19
29
|
const handleRenameClick = useCallback(() => {
|
|
20
30
|
handleRenameFileClick(fileId);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DirectoryListing } from './DirectoryListing';
|
|
2
|
+
import { FileId, FileTree, FileTreeFile } from '../../types';
|
|
3
|
+
import { FileListing } from './FileListing';
|
|
4
|
+
|
|
5
|
+
// A "Listing" is a "FileListing" or a "DirectoryListing"
|
|
6
|
+
// that appears in the Sidebar.
|
|
7
|
+
export const Listing = ({
|
|
8
|
+
entity,
|
|
9
|
+
handleRenameFileClick,
|
|
10
|
+
handleDeleteFileClick,
|
|
11
|
+
handleFileClick,
|
|
12
|
+
isDirectoryOpen,
|
|
13
|
+
toggleDirectory,
|
|
14
|
+
}: {
|
|
15
|
+
entity: FileTree | FileTreeFile;
|
|
16
|
+
handleRenameFileClick: (fileId: FileId) => void;
|
|
17
|
+
handleDeleteFileClick: (fileId: FileId, event: React.MouseEvent) => void;
|
|
18
|
+
handleFileClick: (fileId: FileId) => void;
|
|
19
|
+
isDirectoryOpen: (path: string) => boolean;
|
|
20
|
+
toggleDirectory: (path: string) => void;
|
|
21
|
+
}) => {
|
|
22
|
+
const { name, file, fileId } = entity as FileTreeFile;
|
|
23
|
+
const { path, children } = entity as FileTree;
|
|
24
|
+
return file ? (
|
|
25
|
+
<FileListing
|
|
26
|
+
fileId={fileId}
|
|
27
|
+
name={name}
|
|
28
|
+
handleRenameFileClick={handleRenameFileClick}
|
|
29
|
+
handleDeleteFileClick={handleDeleteFileClick}
|
|
30
|
+
handleFileClick={handleFileClick}
|
|
31
|
+
/>
|
|
32
|
+
) : (
|
|
33
|
+
<DirectoryListing
|
|
34
|
+
name={name}
|
|
35
|
+
path={path}
|
|
36
|
+
children={children}
|
|
37
|
+
handleRenameFileClick={handleRenameFileClick}
|
|
38
|
+
handleDeleteFileClick={handleDeleteFileClick}
|
|
39
|
+
handleFileClick={handleFileClick}
|
|
40
|
+
isDirectoryOpen={isDirectoryOpen}
|
|
41
|
+
toggleDirectory={toggleDirectory}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
import { getFileTree } from '../getFileTree';
|
|
3
|
+
import { sortFileTree } from '../sortFileTree';
|
|
4
|
+
import { disableSettings } from '../featureFlags';
|
|
5
|
+
import { Listing } from './Listing';
|
|
6
|
+
|
|
7
|
+
import './styles.scss';
|
|
8
|
+
import { FileId, FileTree, FileTreeFile, Files } from '../../types';
|
|
9
|
+
|
|
10
|
+
export const Sidebar = ({
|
|
11
|
+
createFile,
|
|
12
|
+
files,
|
|
13
|
+
handleRenameFileClick,
|
|
14
|
+
handleDeleteFileClick,
|
|
15
|
+
handleFileClick,
|
|
16
|
+
setIsSettingsOpen,
|
|
17
|
+
isDirectoryOpen,
|
|
18
|
+
toggleDirectory,
|
|
19
|
+
}: {
|
|
20
|
+
files: Files;
|
|
21
|
+
createFile?: () => void;
|
|
22
|
+
handleRenameFileClick?: (fileId: FileId) => void;
|
|
23
|
+
handleDeleteFileClick?: (fileId: FileId, event: React.MouseEvent) => void;
|
|
24
|
+
handleFileClick?: (fileId: FileId) => void;
|
|
25
|
+
setIsSettingsOpen?: (isSettingsOpen: boolean) => void;
|
|
26
|
+
isDirectoryOpen?: (path: string) => boolean;
|
|
27
|
+
toggleDirectory?: (path: string) => void;
|
|
28
|
+
}) => {
|
|
29
|
+
const fileTree = useMemo(
|
|
30
|
+
() => (files ? sortFileTree(getFileTree(files)) : null),
|
|
31
|
+
[files],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const handleSettingsClick = useCallback(() => {
|
|
35
|
+
setIsSettingsOpen(true);
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="vz-sidebar">
|
|
40
|
+
<div className="files">
|
|
41
|
+
<div className="full-box">
|
|
42
|
+
<div>
|
|
43
|
+
<a className="link-name" href="#">
|
|
44
|
+
Files
|
|
45
|
+
</a>
|
|
46
|
+
</div>
|
|
47
|
+
<div>
|
|
48
|
+
<i
|
|
49
|
+
className="bx bxs-file-plus new-btn"
|
|
50
|
+
style={{ color: '#dbdde1' }}
|
|
51
|
+
onClick={createFile}
|
|
52
|
+
></i>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
{fileTree
|
|
56
|
+
? fileTree.children.map((entity) => {
|
|
57
|
+
const { fileId } = entity as FileTreeFile;
|
|
58
|
+
const { path } = entity as FileTree;
|
|
59
|
+
const key = fileId ? fileId : path;
|
|
60
|
+
return (
|
|
61
|
+
<Listing
|
|
62
|
+
entity={entity}
|
|
63
|
+
key={key}
|
|
64
|
+
handleRenameFileClick={handleRenameFileClick}
|
|
65
|
+
handleDeleteFileClick={handleDeleteFileClick}
|
|
66
|
+
handleFileClick={handleFileClick}
|
|
67
|
+
isDirectoryOpen={isDirectoryOpen}
|
|
68
|
+
toggleDirectory={toggleDirectory}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
})
|
|
72
|
+
: null}
|
|
73
|
+
</div>
|
|
74
|
+
{disableSettings ? null : (
|
|
75
|
+
<div className="settings" onClick={handleSettingsClick}>
|
|
76
|
+
Settings
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
.vz-sidebar {
|
|
2
|
+
height: 100%;
|
|
3
|
+
width: 200px;
|
|
4
|
+
background: rgb(54, 65, 89);
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
|
|
9
|
+
.full-box {
|
|
10
|
+
padding: 7px;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: row;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: space-between;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.files {
|
|
18
|
+
overflow: auto;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.file-or-directory {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: row;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: space-between;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
margin: 0 10px;
|
|
28
|
+
color: rgb(219, 221, 225);
|
|
29
|
+
border-radius: 5px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.link-name {
|
|
33
|
+
margin-top: 30px;
|
|
34
|
+
margin-left: 10px;
|
|
35
|
+
color: rgb(154, 160, 172);
|
|
36
|
+
font-size: 12px;
|
|
37
|
+
text-decoration: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.new-btn {
|
|
41
|
+
justify-content: right;
|
|
42
|
+
margin-right: 10px;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.utils {
|
|
47
|
+
display: flex;
|
|
48
|
+
}
|
|
49
|
+
.utilities {
|
|
50
|
+
margin-right: 3px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.file-or-directory:hover,
|
|
54
|
+
.settings:hover {
|
|
55
|
+
background-color: rgb(74, 84, 105);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.name {
|
|
59
|
+
display: flex;
|
|
60
|
+
text-overflow: ellipsis;
|
|
61
|
+
white-space: nowrap;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.indentation {
|
|
66
|
+
margin-left: 20px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.arrow-wrapper {
|
|
70
|
+
margin-right: 4px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.settings {
|
|
74
|
+
height: 30px;
|
|
75
|
+
color: rgb(219, 221, 225);
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
padding: 14px 0;
|
|
81
|
+
margin: 10px;
|
|
82
|
+
border-radius: 5px;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
import { FileId, Files } from '../../types';
|
|
3
|
+
import './style.scss';
|
|
4
|
+
|
|
5
|
+
// Supports adding the file's containing folder to the tab name
|
|
6
|
+
const fileNameSplit = (fileName) => {
|
|
7
|
+
const split = fileName.split('/');
|
|
8
|
+
if (split.length === 1) return split[split.length - 1];
|
|
9
|
+
return split[split.length - 2] + '/' + split[split.length - 1];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const Tab = ({
|
|
13
|
+
fileId,
|
|
14
|
+
isActive,
|
|
15
|
+
setActiveFileId,
|
|
16
|
+
closeTab,
|
|
17
|
+
fileName,
|
|
18
|
+
}: {
|
|
19
|
+
fileId: FileId;
|
|
20
|
+
isActive: boolean;
|
|
21
|
+
setActiveFileId: (fileId: FileId) => void;
|
|
22
|
+
closeTab: (fileId: FileId) => void;
|
|
23
|
+
fileName: string;
|
|
24
|
+
}) => {
|
|
25
|
+
const handleCloseClick = useCallback(
|
|
26
|
+
(event: React.MouseEvent) => {
|
|
27
|
+
// Stop propagation so that the outer listener doesn't fire.
|
|
28
|
+
event.stopPropagation();
|
|
29
|
+
|
|
30
|
+
closeTab(fileId);
|
|
31
|
+
},
|
|
32
|
+
[closeTab, fileId],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const tabName = useMemo(() => fileNameSplit(fileName), [fileName]);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
className={isActive ? 'tab active' : 'tab'}
|
|
40
|
+
onClick={() => {
|
|
41
|
+
setActiveFileId(fileId);
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
{tabName}
|
|
45
|
+
<div
|
|
46
|
+
className={isActive ? 'bx bx-x tab-close' : ''}
|
|
47
|
+
onClick={handleCloseClick}
|
|
48
|
+
></div>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Displays the list of tabs above the code editor.
|
|
54
|
+
export const TabList = ({
|
|
55
|
+
files,
|
|
56
|
+
tabList,
|
|
57
|
+
activeFileId,
|
|
58
|
+
setActiveFileId,
|
|
59
|
+
closeTab,
|
|
60
|
+
}: {
|
|
61
|
+
files: Files;
|
|
62
|
+
tabList: FileId[];
|
|
63
|
+
activeFileId: FileId;
|
|
64
|
+
setActiveFileId: (fileId: FileId) => void;
|
|
65
|
+
closeTab: (fileId: FileId) => void;
|
|
66
|
+
}) => {
|
|
67
|
+
return (
|
|
68
|
+
<div className="vz-tab-list">
|
|
69
|
+
{files &&
|
|
70
|
+
tabList.map((fileId: FileId) => (
|
|
71
|
+
<Tab
|
|
72
|
+
key={fileId}
|
|
73
|
+
fileId={fileId}
|
|
74
|
+
isActive={fileId === activeFileId}
|
|
75
|
+
setActiveFileId={setActiveFileId}
|
|
76
|
+
closeTab={closeTab}
|
|
77
|
+
fileName={files[fileId].name}
|
|
78
|
+
/>
|
|
79
|
+
))}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
.vz-tab-list {
|
|
2
|
+
display: flex;
|
|
3
|
+
background-color: rgb(37, 37, 38);
|
|
4
|
+
|
|
5
|
+
.tab {
|
|
6
|
+
background: rgb(45, 45, 45);
|
|
7
|
+
color: #fff;
|
|
8
|
+
font-family: var(--vzcode-font-family);
|
|
9
|
+
align-items: center;
|
|
10
|
+
display: flex;
|
|
11
|
+
padding-top: 2px;
|
|
12
|
+
padding-left: 10px;
|
|
13
|
+
padding-right: 10px;
|
|
14
|
+
border-top-left-radius: 10px;
|
|
15
|
+
border-top-right-radius: 10px;
|
|
16
|
+
margin-top: 5px;
|
|
17
|
+
margin-left: 2px;
|
|
18
|
+
margin-right: 2px;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
|
|
21
|
+
.tab-close {
|
|
22
|
+
margin-left: 5px;
|
|
23
|
+
}
|
|
24
|
+
.tab-close:hover {
|
|
25
|
+
border-radius: 30%;
|
|
26
|
+
background-color: rgb(37, 37, 38);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
.tab.active {
|
|
30
|
+
background-color: rgb(41, 44, 52);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// Inspired by
|
|
2
|
+
|
|
3
|
+
import { Files, FileTree } from '../types';
|
|
4
|
+
|
|
2
5
|
// https://github.com/vizhub-core/vizhub/blob/main/vizhub-v2/packages/neoFrontend/src/pages/VizPage/Body/Editor/FilesSection/FileTree/getFileTree.js
|
|
3
|
-
export const getFileTree = (files) => {
|
|
4
|
-
const tree = { name: 'files' };
|
|
6
|
+
export const getFileTree = (files: Files): FileTree => {
|
|
7
|
+
const tree: FileTree = { name: 'files' };
|
|
5
8
|
for (const fileId of Object.keys(files)) {
|
|
6
9
|
const file = files[fileId];
|
|
7
10
|
const path = file.name.split('/');
|
|
@@ -11,7 +14,7 @@ export const getFileTree = (files) => {
|
|
|
11
14
|
// Walk the path.
|
|
12
15
|
for (let i = 0; i < n - 1; i++) {
|
|
13
16
|
// Search for an existing child.
|
|
14
|
-
let child;
|
|
17
|
+
let child: FileTree | undefined;
|
|
15
18
|
const name = path[i];
|
|
16
19
|
if (node.children) {
|
|
17
20
|
for (let j = 0; j < node.children.length; j++) {
|
package/src/client/main.jsx
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// Recursively sorts the file tree in-place.
|
|
2
2
|
// Sorts first to group directories before files.
|
|
3
3
|
// Sorts second alphabetically.
|
|
4
|
+
|
|
5
|
+
import { FileTree, FileTreeFile } from '../types';
|
|
6
|
+
|
|
4
7
|
// From https://github.com/vizhub-core/vizhub/blob/main/vizhub-v2/packages/neoFrontend/src/pages/VizPage/Body/Editor/FilesSection/FileTree/sortFileTree.js
|
|
5
|
-
export const sortFileTree = (fileTree) => {
|
|
8
|
+
export const sortFileTree = (fileTree: FileTree) => {
|
|
6
9
|
if (fileTree.children) {
|
|
7
10
|
fileTree.children.sort((a, b) => {
|
|
8
|
-
const aIsFile = a.file ? 1 : 0;
|
|
9
|
-
const bIsFile = b.file ? 1 : 0;
|
|
11
|
+
const aIsFile = (a as FileTreeFile).file ? 1 : 0;
|
|
12
|
+
const bIsFile = (b as FileTreeFile).file ? 1 : 0;
|
|
13
|
+
// Directories before files.
|
|
10
14
|
return aIsFile - bIsFile || a.name.localeCompare(b.name);
|
|
11
15
|
});
|
|
12
16
|
fileTree.children.forEach(sortFileTree);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--vzcode-font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
|
|
3
|
+
Liberation Mono, monospace;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* width */
|
|
7
|
+
::-webkit-scrollbar {
|
|
8
|
+
width: 10px;
|
|
9
|
+
height: 10px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Track */
|
|
13
|
+
::-webkit-scrollbar-track {
|
|
14
|
+
background: rgb(41, 44, 52);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Handle */
|
|
18
|
+
::-webkit-scrollbar-thumb {
|
|
19
|
+
background: rgb(37, 37, 38);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Handle on hover */
|
|
23
|
+
::-webkit-scrollbar-thumb:hover {
|
|
24
|
+
background: #555;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
html,
|
|
28
|
+
body,
|
|
29
|
+
#root,
|
|
30
|
+
.app {
|
|
31
|
+
height: 100%;
|
|
32
|
+
}
|
|
33
|
+
.app {
|
|
34
|
+
/* TODO use a variable for this */
|
|
35
|
+
background-color: rgb(41, 44, 52);
|
|
36
|
+
|
|
37
|
+
font-family: 'Poppins', sans-serif;
|
|
38
|
+
display: flex;
|
|
39
|
+
|
|
40
|
+
.left {
|
|
41
|
+
display: flex;
|
|
42
|
+
}
|
|
43
|
+
.right {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex: 1;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
min-width: 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { oneDark } from '@codemirror/theme-one-dark';
|
|
2
|
+
import { okaidia } from '@uiw/codemirror-theme-okaidia';
|
|
3
|
+
import { abcdef } from '@uiw/codemirror-theme-abcdef';
|
|
4
|
+
import { dracula } from '@uiw/codemirror-theme-dracula';
|
|
5
|
+
import { eclipse } from '@uiw/codemirror-theme-eclipse';
|
|
6
|
+
import { githubDark } from '@uiw/codemirror-theme-github';
|
|
7
|
+
import { material } from '@uiw/codemirror-theme-material';
|
|
8
|
+
import { nord } from '@uiw/codemirror-theme-nord';
|
|
9
|
+
import { xcodeLight } from '@uiw/codemirror-theme-xcode';
|
|
10
|
+
|
|
11
|
+
// CodeMirror themes
|
|
12
|
+
export const themes = [
|
|
13
|
+
{ value: abcdef, label: 'abcdef' },
|
|
14
|
+
{ value: dracula, label: 'darcula' },
|
|
15
|
+
{ value: eclipse, label: 'eclipse' },
|
|
16
|
+
{ value: material, label: 'material' },
|
|
17
|
+
{ value: nord, label: 'nord' },
|
|
18
|
+
{ value: oneDark, label: 'oneDark' },
|
|
19
|
+
{ value: okaidia, label: 'okaidia' },
|
|
20
|
+
{ value: githubDark, label: 'github' },
|
|
21
|
+
{ value: xcodeLight, label: 'xcode' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// Map theme labels to theme values
|
|
25
|
+
export const themesByLabel = themes.reduce((acc, theme) => {
|
|
26
|
+
acc[theme.label] = theme.value;
|
|
27
|
+
return acc;
|
|
28
|
+
}, {});
|
|
29
|
+
|
|
30
|
+
// The default theme
|
|
31
|
+
export const defaultTheme = 'oneDark';
|
|
@@ -17,23 +17,27 @@ import { useState, useCallback } from 'react';
|
|
|
17
17
|
|
|
18
18
|
// Inspired by
|
|
19
19
|
// https://github.com/vizhub-core/vizhub/blob/main/vizhub-v2/packages/neoFrontend/src/pages/VizPage/Body/Editor/FilesSection/useOpenDirectories.js
|
|
20
|
-
export const useOpenDirectories = (
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
export const useOpenDirectories = () => {
|
|
21
|
+
// The set of open directories by path/
|
|
22
|
+
const [openDirectories, setOpenDirectories] = useState<Set<string>>(
|
|
23
|
+
new Set(),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Whether a directory is open.
|
|
27
|
+
const isDirectoryOpen: (path: string) => boolean = useCallback(
|
|
28
|
+
(path) => openDirectories.has(path),
|
|
29
|
+
[openDirectories],
|
|
30
|
+
);
|
|
25
31
|
|
|
26
|
-
//
|
|
32
|
+
// Toggle whether a directory is open.
|
|
27
33
|
const toggleDirectory = useCallback(
|
|
28
34
|
(path) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
[path]: !openDirectories[path],
|
|
33
|
-
})
|
|
34
|
-
);
|
|
35
|
+
const newOpenDirectories = new Set(openDirectories);
|
|
36
|
+
newOpenDirectories[isDirectoryOpen(path) ? 'delete' : 'add'](path);
|
|
37
|
+
setOpenDirectories(newOpenDirectories);
|
|
35
38
|
},
|
|
36
|
-
[openDirectories]
|
|
39
|
+
[openDirectories, isDirectoryOpen],
|
|
37
40
|
);
|
|
38
|
-
|
|
41
|
+
|
|
42
|
+
return { isDirectoryOpen, toggleDirectory };
|
|
39
43
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { FileId } from '../types';
|
|
3
|
+
|
|
4
|
+
// Encapsulates the state and logic for managing the list of open tabs.
|
|
5
|
+
export const useTabsState = (
|
|
6
|
+
activeFileId: FileId,
|
|
7
|
+
setActiveFileId: (fileId: FileId | null) => void,
|
|
8
|
+
tabList: Array<FileId>,
|
|
9
|
+
setTabList: (tabList: Array<FileId>) => void,
|
|
10
|
+
) => {
|
|
11
|
+
// Called when a tab is closed.
|
|
12
|
+
const closeTab = useCallback(
|
|
13
|
+
(fileIdToRemove: FileId) => {
|
|
14
|
+
const i = tabList.findIndex((fileId) => fileId === fileIdToRemove);
|
|
15
|
+
|
|
16
|
+
// Support calling `closeTab` on a fileId that is not open,
|
|
17
|
+
// such as when a file is deleted..
|
|
18
|
+
if (i !== -1) {
|
|
19
|
+
// Remove the tab from the tab list.
|
|
20
|
+
const newTabList = [...tabList.slice(0, i), ...tabList.slice(i + 1)];
|
|
21
|
+
setTabList(newTabList);
|
|
22
|
+
|
|
23
|
+
// If we are closing the active file,
|
|
24
|
+
if (activeFileId === fileIdToRemove) {
|
|
25
|
+
// set the new active file to the next tab over,
|
|
26
|
+
if (newTabList.length > 0) {
|
|
27
|
+
setActiveFileId(i === 0 ? newTabList[i] : newTabList[i - 1]);
|
|
28
|
+
} else {
|
|
29
|
+
// or clear out the active file
|
|
30
|
+
// if we've closed the last tab.
|
|
31
|
+
setActiveFileId(null);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
[tabList, activeFileId],
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Called when a tab is opened.
|
|
40
|
+
const openTab = useCallback(
|
|
41
|
+
(fileId: FileId) => {
|
|
42
|
+
setActiveFileId(fileId);
|
|
43
|
+
if (!tabList.includes(fileId)) {
|
|
44
|
+
setTabList([...tabList, fileId]);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
[tabList],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return { tabList, closeTab, openTab };
|
|
51
|
+
};
|
package/src/server/index.js
CHANGED
|
File without changes
|