vzcode 0.25.0 → 0.26.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-284b7339.js +111 -0
- package/dist/assets/index-e160f758.css +1 -0
- package/dist/index.html +2 -6
- package/package.json +5 -5
- package/src/client/App.tsx +45 -225
- package/src/client/CodeEditor/index.tsx +36 -6
- package/src/client/CodeEditor/{json1PresenceBroadcast.js → json1PresenceBroadcast.ts} +5 -3
- package/src/client/CodeEditor/{json1PresenceDisplay.js → json1PresenceDisplay.ts} +9 -3
- package/src/client/PresenceNotifications/index.tsx +0 -2
- package/src/client/TabList/Tab.tsx +56 -0
- package/src/client/TabList/index.tsx +11 -62
- package/src/client/TabList/style.scss +21 -9
- package/src/client/{Settings.tsx → VZSettings.tsx} +1 -1
- package/src/client/{Sidebar → VZSidebar}/FileListing.tsx +3 -0
- package/src/client/{Sidebar → VZSidebar}/Item.tsx +7 -4
- package/src/client/{Sidebar → VZSidebar}/Listing.tsx +3 -0
- package/src/client/{Sidebar → VZSidebar}/index.tsx +5 -6
- package/src/client/{Sidebar → VZSidebar}/styles.scss +17 -12
- package/src/client/bootstrap.ts +3 -0
- package/src/client/style.scss +5 -2
- package/src/client/useActions.ts +74 -0
- package/src/client/useFileCRUD.ts +101 -0
- package/src/client/useOpenDirectories.ts +25 -7
- package/src/client/useSubmitOperation.ts +25 -0
- package/src/client/{reducer.ts → vzReducer.ts} +42 -43
- package/src/index.ts +34 -0
- package/src/server/index.js +13 -2
- package/dist/assets/index-8628ebab.js +0 -111
- package/dist/assets/index-cf19de3b.css +0 -1
- /package/src/client/{Sidebar → VZSidebar}/ConfirmationBox.tsx +0 -0
- /package/src/client/{Sidebar → VZSidebar}/DirectoryArrowSVG.jsx +0 -0
- /package/src/client/{Sidebar → VZSidebar}/DirectoryListing.tsx +0 -0
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
.vz-tab-list {
|
|
2
2
|
display: flex;
|
|
3
|
-
background-color:
|
|
3
|
+
background-color: #33415b;
|
|
4
4
|
// When the tab list becomes too large, a horizontal scroll bar appears.
|
|
5
5
|
overflow-x: auto;
|
|
6
6
|
|
|
7
7
|
.tab {
|
|
8
|
-
background:
|
|
8
|
+
// background: #47546b;
|
|
9
9
|
color: #fff;
|
|
10
10
|
font-family: var(--vzcode-font-family);
|
|
11
11
|
align-items: center;
|
|
12
12
|
display: flex;
|
|
13
13
|
padding-top: 2px;
|
|
14
|
-
padding-left:
|
|
15
|
-
padding-right:
|
|
16
|
-
border-top-left-radius:
|
|
17
|
-
border-top-right-radius:
|
|
14
|
+
padding-left: 8px;
|
|
15
|
+
padding-right: 5px;
|
|
16
|
+
border-top-left-radius: 12px;
|
|
17
|
+
border-top-right-radius: 12px;
|
|
18
18
|
margin-top: 5px;
|
|
19
19
|
margin-left: 2px;
|
|
20
20
|
margin-right: 2px;
|
|
@@ -24,13 +24,25 @@
|
|
|
24
24
|
|
|
25
25
|
.tab-close {
|
|
26
26
|
margin-left: 5px;
|
|
27
|
+
margin-bottom: 2px;
|
|
27
28
|
}
|
|
28
29
|
.tab-close:hover {
|
|
29
|
-
border-radius:
|
|
30
|
-
background-color:
|
|
30
|
+
border-radius: 50%;
|
|
31
|
+
background-color: rgba(255, 255, 255, 0.2);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// No left margin for first child
|
|
35
|
+
&:first-child {
|
|
36
|
+
margin-left: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// on hover
|
|
40
|
+
&:hover {
|
|
41
|
+
background-color: #47546b;
|
|
31
42
|
}
|
|
32
43
|
}
|
|
44
|
+
|
|
33
45
|
.tab.active {
|
|
34
|
-
background-color:
|
|
46
|
+
background-color: #202e46;
|
|
35
47
|
}
|
|
36
48
|
}
|
|
@@ -8,6 +8,7 @@ export const FileListing = ({
|
|
|
8
8
|
handleRenameFileClick,
|
|
9
9
|
handleDeleteFileClick,
|
|
10
10
|
handleFileClick,
|
|
11
|
+
isActive,
|
|
11
12
|
}: {
|
|
12
13
|
name: string;
|
|
13
14
|
fileId: FileId;
|
|
@@ -20,6 +21,7 @@ export const FileListing = ({
|
|
|
20
21
|
event: React.MouseEvent,
|
|
21
22
|
) => void;
|
|
22
23
|
handleFileClick: (fileId: FileId) => void;
|
|
24
|
+
isActive: boolean;
|
|
23
25
|
}) => {
|
|
24
26
|
const handleClick = useCallback(() => {
|
|
25
27
|
handleFileClick(fileId);
|
|
@@ -46,6 +48,7 @@ export const FileListing = ({
|
|
|
46
48
|
handleDeleteClick={handleDeleteClick}
|
|
47
49
|
handleRenameClick={handleRenameClick}
|
|
48
50
|
isDir={false}
|
|
51
|
+
isActive={isActive}
|
|
49
52
|
>
|
|
50
53
|
{name}
|
|
51
54
|
</Item>
|
|
@@ -8,6 +8,7 @@ export const Item = ({
|
|
|
8
8
|
handleRenameClick,
|
|
9
9
|
handleDeleteClick,
|
|
10
10
|
isDir,
|
|
11
|
+
isActive = false,
|
|
11
12
|
}) => {
|
|
12
13
|
// Tracks whether the mouse is hovering over the file or directory
|
|
13
14
|
const [isHovered, setIsHovered] = useState(false);
|
|
@@ -82,7 +83,9 @@ export const Item = ({
|
|
|
82
83
|
|
|
83
84
|
return (
|
|
84
85
|
<div
|
|
85
|
-
className=
|
|
86
|
+
className={`full-box file-or-directory ${
|
|
87
|
+
isActive ? 'active-file' : ''
|
|
88
|
+
}`}
|
|
86
89
|
onClick={isRenaming ? null : handleClick}
|
|
87
90
|
onMouseEnter={handleMouseEnter}
|
|
88
91
|
onMouseLeave={handleMouseLeave}
|
|
@@ -105,7 +108,7 @@ export const Item = ({
|
|
|
105
108
|
<div className="utils">
|
|
106
109
|
<i
|
|
107
110
|
className="bx bxs-edit utilities"
|
|
108
|
-
style={{ color: '#
|
|
111
|
+
style={{ color: '#9edcff' }}
|
|
109
112
|
onClick={(event: React.MouseEvent) => {
|
|
110
113
|
event.stopPropagation();
|
|
111
114
|
setIsRenaming(!isRenaming);
|
|
@@ -113,8 +116,8 @@ export const Item = ({
|
|
|
113
116
|
}}
|
|
114
117
|
></i>
|
|
115
118
|
<i
|
|
116
|
-
className="bx bx-trash"
|
|
117
|
-
style={{ color: '#
|
|
119
|
+
className="bx bx-trash utilities"
|
|
120
|
+
style={{ color: '#ff006b' }}
|
|
118
121
|
onClick={handleModalOpen}
|
|
119
122
|
></i>
|
|
120
123
|
</div>
|
|
@@ -15,6 +15,7 @@ export const Listing = ({
|
|
|
15
15
|
handleFileClick,
|
|
16
16
|
isDirectoryOpen,
|
|
17
17
|
toggleDirectory,
|
|
18
|
+
activeFileId,
|
|
18
19
|
}: {
|
|
19
20
|
entity: FileTree | FileTreeFile;
|
|
20
21
|
handleRenameFileClick: (
|
|
@@ -28,6 +29,7 @@ export const Listing = ({
|
|
|
28
29
|
handleFileClick: (fileId: FileId) => void;
|
|
29
30
|
isDirectoryOpen: (path: string) => boolean;
|
|
30
31
|
toggleDirectory: (path: string) => void;
|
|
32
|
+
activeFileId: FileId;
|
|
31
33
|
}) => {
|
|
32
34
|
const { name, file, fileId } = entity as FileTreeFile;
|
|
33
35
|
const { path, children } = entity as FileTree;
|
|
@@ -38,6 +40,7 @@ export const Listing = ({
|
|
|
38
40
|
handleRenameFileClick={handleRenameFileClick}
|
|
39
41
|
handleDeleteFileClick={handleDeleteFileClick}
|
|
40
42
|
handleFileClick={handleFileClick}
|
|
43
|
+
isActive={fileId === activeFileId}
|
|
41
44
|
/>
|
|
42
45
|
) : (
|
|
43
46
|
<DirectoryListing
|
|
@@ -12,7 +12,7 @@ import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
|
|
|
12
12
|
import { Listing } from './Listing';
|
|
13
13
|
import './styles.scss';
|
|
14
14
|
|
|
15
|
-
export const
|
|
15
|
+
export const VZSidebar = ({
|
|
16
16
|
createFile,
|
|
17
17
|
files,
|
|
18
18
|
handleRenameFileClick,
|
|
@@ -21,6 +21,7 @@ export const Sidebar = ({
|
|
|
21
21
|
setIsSettingsOpen,
|
|
22
22
|
isDirectoryOpen,
|
|
23
23
|
toggleDirectory,
|
|
24
|
+
activeFileId,
|
|
24
25
|
}: {
|
|
25
26
|
files: Files;
|
|
26
27
|
createFile?: () => void;
|
|
@@ -36,6 +37,7 @@ export const Sidebar = ({
|
|
|
36
37
|
setIsSettingsOpen?: (isSettingsOpen: boolean) => void;
|
|
37
38
|
isDirectoryOpen?: (path: string) => boolean;
|
|
38
39
|
toggleDirectory?: (path: string) => void;
|
|
40
|
+
activeFileId?: FileId;
|
|
39
41
|
}) => {
|
|
40
42
|
const fileTree = useMemo(
|
|
41
43
|
() => (files ? sortFileTree(getFileTree(files)) : null),
|
|
@@ -57,11 +59,7 @@ export const Sidebar = ({
|
|
|
57
59
|
>
|
|
58
60
|
<div className="files">
|
|
59
61
|
<div className="full-box">
|
|
60
|
-
<div>
|
|
61
|
-
<a className="link-name" href="#">
|
|
62
|
-
Files
|
|
63
|
-
</a>
|
|
64
|
-
</div>
|
|
62
|
+
<div className="sidebar-section-hint">Files</div>
|
|
65
63
|
<div>
|
|
66
64
|
<i
|
|
67
65
|
className="bx bxs-file-plus new-btn"
|
|
@@ -88,6 +86,7 @@ export const Sidebar = ({
|
|
|
88
86
|
handleFileClick={handleFileClick}
|
|
89
87
|
isDirectoryOpen={isDirectoryOpen}
|
|
90
88
|
toggleDirectory={toggleDirectory}
|
|
89
|
+
activeFileId={activeFileId}
|
|
91
90
|
/>
|
|
92
91
|
);
|
|
93
92
|
})
|
|
@@ -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,7 @@
|
|
|
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
|
+
/* Variable for the background color */
|
|
4
5
|
--vz-bg-color: rgb(
|
|
5
6
|
41,
|
|
6
7
|
44,
|
|
@@ -36,7 +37,9 @@ body,
|
|
|
36
37
|
|
|
37
38
|
.app {
|
|
38
39
|
background-color: var(--vz-bg-color);
|
|
39
|
-
font-family: '
|
|
40
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
41
|
+
'Noto Sans', Helvetica, Arial, sans-serif,
|
|
42
|
+
'Apple Color Emoji', 'Segoe UI Emoji';
|
|
40
43
|
display: flex;
|
|
41
44
|
|
|
42
45
|
/* To support Resizer, which is position: absolute */
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { ThemeLabel } from './themes';
|
|
3
|
+
import { FileId } from '../types';
|
|
4
|
+
import { VZAction } from './vzReducer';
|
|
5
|
+
|
|
6
|
+
// This is a custom hook that returns a set of functions
|
|
7
|
+
// that can be used to dispatch actions to the reducer.
|
|
8
|
+
export const useActions = (
|
|
9
|
+
dispatch: (action: VZAction) => void,
|
|
10
|
+
) => {
|
|
11
|
+
const setActiveFileId = useCallback(
|
|
12
|
+
(activeFileId: FileId) => {
|
|
13
|
+
dispatch({
|
|
14
|
+
type: 'set_active_file_id',
|
|
15
|
+
activeFileId,
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
[dispatch],
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const openTab = useCallback(
|
|
22
|
+
(fileId: FileId) => {
|
|
23
|
+
dispatch({
|
|
24
|
+
type: 'open_tab',
|
|
25
|
+
fileId,
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
[dispatch],
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const closeTabs = useCallback(
|
|
32
|
+
(idsToDelete: Array<FileId>) => {
|
|
33
|
+
dispatch({
|
|
34
|
+
type: 'close_tabs',
|
|
35
|
+
idsToDelete,
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
[dispatch],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const setTheme = useCallback(
|
|
42
|
+
(themeLabel: ThemeLabel) => {
|
|
43
|
+
dispatch({
|
|
44
|
+
type: 'set_theme',
|
|
45
|
+
themeLabel: themeLabel,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
[dispatch],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// True to show the settings modal.
|
|
52
|
+
const setIsSettingsOpen = useCallback(
|
|
53
|
+
(value: boolean) => {
|
|
54
|
+
dispatch({
|
|
55
|
+
type: 'set_is_settings_open',
|
|
56
|
+
value: value,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
[dispatch],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const closeSettings = useCallback(() => {
|
|
63
|
+
setIsSettingsOpen(false);
|
|
64
|
+
}, [setIsSettingsOpen]);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
setActiveFileId,
|
|
68
|
+
openTab,
|
|
69
|
+
closeTabs,
|
|
70
|
+
setTheme,
|
|
71
|
+
setIsSettingsOpen,
|
|
72
|
+
closeSettings,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { FileId, VZCodeContent } from '../types';
|
|
3
|
+
import { randomId } from '../randomId';
|
|
4
|
+
|
|
5
|
+
// CRUD operations for files and directories
|
|
6
|
+
// CRUD = Create, Read, Update, Delete
|
|
7
|
+
export const useFileCRUD = ({
|
|
8
|
+
submitOperation,
|
|
9
|
+
closeTabs,
|
|
10
|
+
}) => {
|
|
11
|
+
// Create a new file
|
|
12
|
+
const createFile = useCallback(() => {
|
|
13
|
+
// TODO use Bootstrap modals not native prompts
|
|
14
|
+
// See https://github.com/vizhub-core/vzcode/issues/252
|
|
15
|
+
const name = prompt('Enter new file name');
|
|
16
|
+
if (name) {
|
|
17
|
+
submitOperation((document: VZCodeContent) => ({
|
|
18
|
+
...document,
|
|
19
|
+
files: {
|
|
20
|
+
...document.files,
|
|
21
|
+
[randomId()]: { name, text: '' },
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
}, [submitOperation]);
|
|
26
|
+
|
|
27
|
+
// Called when a file in the sidebar is renamed.
|
|
28
|
+
const handleRenameFileClick = useCallback(
|
|
29
|
+
(fileId: FileId, newName: string) => {
|
|
30
|
+
submitOperation((document) => ({
|
|
31
|
+
...document,
|
|
32
|
+
files: {
|
|
33
|
+
...document.files,
|
|
34
|
+
[fileId]: {
|
|
35
|
+
...document.files[fileId],
|
|
36
|
+
name: newName,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
},
|
|
41
|
+
[submitOperation],
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Deletes a file
|
|
45
|
+
const deleteFile = useCallback(
|
|
46
|
+
(fileId: FileId) => {
|
|
47
|
+
closeTabs([fileId]);
|
|
48
|
+
submitOperation((document) => {
|
|
49
|
+
const updatedFiles = { ...document.files };
|
|
50
|
+
delete updatedFiles[fileId];
|
|
51
|
+
return { ...document, files: updatedFiles };
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
[submitOperation, closeTabs],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Deletes a directory and all files within it
|
|
58
|
+
const deleteDirectory = useCallback(
|
|
59
|
+
(path: FileId) => {
|
|
60
|
+
const tabsToClose: Array<FileId> = [];
|
|
61
|
+
submitOperation((document: VZCodeContent) => {
|
|
62
|
+
const updatedFiles = { ...document.files };
|
|
63
|
+
for (const key in updatedFiles) {
|
|
64
|
+
if (updatedFiles[key].name.includes(path)) {
|
|
65
|
+
tabsToClose.push(key);
|
|
66
|
+
delete updatedFiles[key];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { ...document, files: updatedFiles };
|
|
70
|
+
});
|
|
71
|
+
closeTabs(tabsToClose);
|
|
72
|
+
},
|
|
73
|
+
[submitOperation, closeTabs],
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Generic delete function that calls either deleteFile or deleteDirectory
|
|
77
|
+
// TODO consider if there is a cleaner way to do this
|
|
78
|
+
// - Ideally we would have a separate function for deleting files and directories
|
|
79
|
+
// - When we click the delete button, we should be able to tell if it is a file or directory
|
|
80
|
+
const handleDeleteClick = useCallback(
|
|
81
|
+
(key: string) => {
|
|
82
|
+
// Regex to identify if the key is a file path or a file id.
|
|
83
|
+
if (/^[0-9]*$/.test(key)) {
|
|
84
|
+
if (key.length == 8) {
|
|
85
|
+
deleteFile(key);
|
|
86
|
+
} else {
|
|
87
|
+
deleteDirectory(key);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
deleteDirectory(key);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
[deleteFile, deleteDirectory],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
createFile,
|
|
98
|
+
handleRenameFileClick,
|
|
99
|
+
handleDeleteClick,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
@@ -17,6 +17,7 @@ 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
|
+
// TODO move this into reducer
|
|
20
21
|
export const useOpenDirectories = () => {
|
|
21
22
|
// The set of open directories by path/
|
|
22
23
|
const [openDirectories, setOpenDirectories] = useState<
|
|
@@ -30,14 +31,31 @@ export const useOpenDirectories = () => {
|
|
|
30
31
|
[openDirectories],
|
|
31
32
|
);
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Toggles the state of a directory (open/close).
|
|
36
|
+
* If the directory is currently open, it will close it.
|
|
37
|
+
* If it's closed, it will open it.
|
|
38
|
+
*/
|
|
34
39
|
const toggleDirectory = useCallback(
|
|
35
|
-
(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
(directoryPath: string) => {
|
|
41
|
+
// Create a copy of the currently open directories
|
|
42
|
+
const updatedOpenDirectories = new Set(
|
|
43
|
+
openDirectories,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Check if the directory is currently open
|
|
47
|
+
const directoryIsOpen =
|
|
48
|
+
isDirectoryOpen(directoryPath);
|
|
49
|
+
|
|
50
|
+
// Toggle the directory's state
|
|
51
|
+
if (directoryIsOpen) {
|
|
52
|
+
updatedOpenDirectories.delete(directoryPath);
|
|
53
|
+
} else {
|
|
54
|
+
updatedOpenDirectories.add(directoryPath);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Update the state with the modified set of open directories
|
|
58
|
+
setOpenDirectories(updatedOpenDirectories);
|
|
41
59
|
},
|
|
42
60
|
[openDirectories, isDirectoryOpen],
|
|
43
61
|
);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { ShareDBDoc, VZCodeContent } from '../types';
|
|
3
|
+
import { diff } from './diff';
|
|
4
|
+
|
|
5
|
+
// A helper function to submit diff-based operations to ShareDB
|
|
6
|
+
export const useSubmitOperation = (
|
|
7
|
+
shareDBDoc: ShareDBDoc<VZCodeContent>,
|
|
8
|
+
): ((
|
|
9
|
+
next: (content: VZCodeContent) => VZCodeContent,
|
|
10
|
+
) => void) => {
|
|
11
|
+
const submitOperation: (
|
|
12
|
+
next: (content: VZCodeContent) => VZCodeContent,
|
|
13
|
+
) => void = useCallback(
|
|
14
|
+
(next) => {
|
|
15
|
+
const content: VZCodeContent = shareDBDoc.data;
|
|
16
|
+
const op = diff(content, next(content));
|
|
17
|
+
if (op && shareDBDoc) {
|
|
18
|
+
shareDBDoc.submitOp(op);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
[shareDBDoc],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return submitOperation;
|
|
25
|
+
};
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { FileId } from '../types';
|
|
2
|
-
import {
|
|
2
|
+
import { ThemeLabel } from './themes';
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export type VZState = {
|
|
5
|
+
tabList: Array<FileId>;
|
|
6
|
+
activeFileId: FileId | null;
|
|
7
|
+
theme: ThemeLabel;
|
|
8
|
+
isSettingsOpen: boolean;
|
|
9
|
+
};
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export type VZAction =
|
|
12
|
+
| { type: 'set_active_file_id'; activeFileId: FileId }
|
|
13
|
+
| { type: 'open_tab'; fileId: FileId }
|
|
14
|
+
| { type: 'close_tab'; fileIdToRemove: FileId }
|
|
15
|
+
| { type: 'close_tabs'; idsToDelete: Array<FileId> }
|
|
16
|
+
| { type: 'set_theme'; themeLabel: ThemeLabel }
|
|
17
|
+
| { type: 'set_is_settings_open'; value: boolean };
|
|
18
|
+
|
|
19
|
+
export const vzReducer = (
|
|
20
|
+
state: VZState,
|
|
21
|
+
action: VZAction,
|
|
22
|
+
) => {
|
|
23
|
+
switch (action.type) {
|
|
24
|
+
case 'set_active_file_id': {
|
|
15
25
|
return {
|
|
16
26
|
...state,
|
|
17
27
|
activeFileId: action.activeFileId,
|
|
@@ -33,7 +43,8 @@ export const reducer = (state, action) => {
|
|
|
33
43
|
}
|
|
34
44
|
case 'close_tab': {
|
|
35
45
|
const i = state.tabList.findIndex(
|
|
36
|
-
(fileId) =>
|
|
46
|
+
(fileId: FileId) =>
|
|
47
|
+
fileId === action.fileIdToRemove,
|
|
37
48
|
);
|
|
38
49
|
if (i != -1) {
|
|
39
50
|
const newTabList = [
|
|
@@ -68,52 +79,40 @@ export const reducer = (state, action) => {
|
|
|
68
79
|
...state,
|
|
69
80
|
};
|
|
70
81
|
}
|
|
71
|
-
case '
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
newActiveFileId =
|
|
89
|
-
i === 0 ? newTabList[i] : newTabList[i - 1];
|
|
90
|
-
} else {
|
|
91
|
-
// or clear out the active file if we've closed the last tab.
|
|
92
|
-
newActiveFileId = null;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
});
|
|
82
|
+
case 'close_tabs': {
|
|
83
|
+
const newTabList = state.tabList.filter(
|
|
84
|
+
(fileId: FileId) =>
|
|
85
|
+
!action.idsToDelete.includes(fileId),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
let newActiveFileId: FileId | null =
|
|
89
|
+
state.activeFileId;
|
|
90
|
+
|
|
91
|
+
if (action.idsToDelete.includes(newActiveFileId)) {
|
|
92
|
+
const index =
|
|
93
|
+
state.tabList.indexOf(newActiveFileId);
|
|
94
|
+
newActiveFileId = newTabList[index]
|
|
95
|
+
? newTabList[index]
|
|
96
|
+
: newTabList[index - 1] || null;
|
|
97
|
+
}
|
|
98
|
+
|
|
97
99
|
return {
|
|
98
100
|
...state,
|
|
99
101
|
tabList: newTabList,
|
|
100
102
|
activeFileId: newActiveFileId,
|
|
101
103
|
};
|
|
102
104
|
}
|
|
103
|
-
|
|
104
|
-
// TODO persist this in local storage
|
|
105
|
-
case 'set_Theme': {
|
|
105
|
+
case 'set_theme': {
|
|
106
106
|
return {
|
|
107
107
|
...state,
|
|
108
108
|
theme: action.themeLabel,
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
|
-
case '
|
|
111
|
+
case 'set_is_settings_open': {
|
|
112
112
|
return {
|
|
113
113
|
...state,
|
|
114
114
|
isSettingsOpen: action.value,
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
throw Error('Unknown action: ' + action.type);
|
|
119
118
|
};
|