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.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/index-a9585cbc.js +111 -0
  3. package/dist/assets/index-d30c9f1b.css +1 -0
  4. package/dist/index.html +2 -2
  5. package/package.json +6 -4
  6. package/src/client/AIAssist.ts +46 -0
  7. package/src/client/App.tsx +86 -43
  8. package/src/client/CodeEditor/getOrCreateEditor.ts +22 -2
  9. package/src/client/CodeEditor/index.tsx +75 -12
  10. package/src/client/CodeEditor/json1PresenceBroadcast.ts +6 -2
  11. package/src/client/CodeEditor/json1PresenceDisplay.ts +0 -45
  12. package/src/client/CodeEditor/style.scss +5 -1
  13. package/src/client/CodeErrorOverlay/index.tsx +37 -0
  14. package/src/client/CodeErrorOverlay/style.scss +41 -0
  15. package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
  16. package/src/client/PresenceNotifications/index.tsx +118 -103
  17. package/src/client/SplitPaneResizeContext.tsx +12 -7
  18. package/src/client/TabList/Tab.tsx +20 -6
  19. package/src/client/TabList/index.tsx +18 -8
  20. package/src/client/TabList/style.scss +5 -0
  21. package/src/client/VZSettings.tsx +33 -20
  22. package/src/client/VZSidebar/{ConfirmationBox.tsx → DeleteConfirmationModal.tsx} +17 -14
  23. package/src/client/VZSidebar/DirectoryListing.tsx +27 -26
  24. package/src/client/VZSidebar/FileListing.tsx +16 -19
  25. package/src/client/VZSidebar/Item.tsx +83 -37
  26. package/src/client/VZSidebar/Listing.tsx +17 -14
  27. package/src/client/VZSidebar/index.tsx +87 -45
  28. package/src/client/VZSidebar/styles.scss +21 -1
  29. package/src/client/style.scss +3 -0
  30. package/src/client/useActions.ts +25 -6
  31. package/src/client/useEditorCache.ts +2 -1
  32. package/src/client/useFileCRUD.ts +29 -27
  33. package/src/client/usernameLocalStorage.ts +43 -0
  34. package/src/client/vzReducer/closeTabReducer.test.ts +177 -0
  35. package/src/client/vzReducer/closeTabsReducer.ts +68 -0
  36. package/src/client/vzReducer/createInitialState.ts +19 -0
  37. package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
  38. package/src/client/vzReducer/index.ts +107 -0
  39. package/src/client/vzReducer/openTabReducer.test.ts +110 -0
  40. package/src/client/vzReducer/openTabReducer.ts +57 -0
  41. package/src/client/vzReducer/setActiveFileIdReducer.ts +9 -0
  42. package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
  43. package/src/client/vzReducer/setThemeReducer.ts +9 -0
  44. package/src/client/vzReducer/setUsernameReducer.ts +9 -0
  45. package/src/index.ts +9 -3
  46. package/src/server/handleAIAssist.js +69 -0
  47. package/src/server/index.js +9 -0
  48. package/src/types.ts +12 -5
  49. package/dist/assets/index-284b7339.js +0 -111
  50. package/dist/assets/index-e160f758.css +0 -1
  51. package/src/client/PrettierErrorOverlay/index.tsx +0 -9
  52. package/src/client/PrettierErrorOverlay/style.scss +0 -9
  53. 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 ConfirmationBox = ({
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>Delete File</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>Are you sure you want to delete this file?</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
- No
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 { FileTree, FileTreeFile } from '../../types';
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
- handleRenameFileClick,
12
- handleDeleteFileClick,
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
- handleRenameFileClick: (
21
- fileId: string,
22
- newName: string,
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
- (event) => {
38
- // https://github.com/vizhub-core/vzcode/issues/102
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
- isDir={true}
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
- handleRenameFileClick={
83
- handleRenameFileClick
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
- handleRenameFileClick,
9
- handleDeleteFileClick,
8
+ renameFile,
9
+ deleteFile,
10
10
  handleFileClick,
11
+ handleFileDoubleClick,
11
12
  isActive,
12
13
  }: {
13
14
  name: string;
14
15
  fileId: FileId;
15
- handleRenameFileClick: (
16
- fileId: FileId,
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 handleDeleteClick = useCallback(
31
- (event: React.MouseEvent) => {
32
- handleDeleteFileClick(fileId, event);
33
- },
34
- [fileId, handleDeleteFileClick],
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
- handleRenameFileClick(fileId, newName);
36
+ renameFile(fileId, newName);
40
37
  },
41
- [fileId, handleRenameFileClick],
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 { useState, useCallback, useMemo } from 'react';
2
- import { ConfirmationBox } from './ConfirmationBox';
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
- isDir,
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
- const onKeyDown = useCallback((event) => {
23
- if (event.key === 'Escape') {
24
- setIsRenaming(false);
25
- } else if (event.key === 'Enter') {
26
- event.target.blur();
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 onBlur = useCallback(
31
- (event: React.FocusEvent<HTMLInputElement>) => {
32
- if (event.target.value.trim() === '') {
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
- handleRenameClick(event.target.value);
54
+ } else if (event.key === 'Enter') {
55
+ renameInputRef.current.blur();
38
56
  }
39
57
  },
40
- [handleRenameClick],
58
+ [],
41
59
  );
42
60
 
43
- const [showModal, setShowModal] = useState(false);
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
- (event: React.ChangeEvent<HTMLInputElement>) => {
79
- setRenameValue(event.target.value);
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
- <i
110
- className="bx bxs-edit utilities"
111
- style={{ color: '#9edcff' }}
112
- onClick={(event: React.MouseEvent) => {
113
- event.stopPropagation();
114
- setIsRenaming(!isRenaming);
115
- setRenameValue(isDir ? name : children);
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
- <ConfirmationBox
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
- handleRenameFileClick,
14
- handleDeleteFileClick,
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
- handleRenameFileClick: (
22
- fileId: FileId,
23
- newName: string,
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
- handleRenameFileClick={handleRenameFileClick}
41
- handleDeleteFileClick={handleDeleteFileClick}
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
- handleRenameFileClick={handleRenameFileClick}
51
- handleDeleteFileClick={handleDeleteFileClick}
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
- handleRenameFileClick,
19
- handleDeleteFileClick,
20
- handleFileClick,
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?: () => void;
28
- handleRenameFileClick?: (
29
- fileId: FileId,
30
- newName: string,
31
- ) => void;
32
- handleDeleteFileClick?: (
33
- fileId: FileId,
34
- event: React.MouseEvent,
35
- ) => void;
36
- handleFileClick?: (fileId: FileId) => void;
37
- setIsSettingsOpen?: (isSettingsOpen: boolean) => void;
38
- isDirectoryOpen?: (path: string) => boolean;
39
- toggleDirectory?: (path: string) => void;
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
- <i
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
- {fileTree
72
- ? fileTree.children.map((entity) => {
73
- const { fileId } = entity as FileTreeFile;
74
- const { path } = entity as FileTree;
75
- const key = fileId ? fileId : path;
76
- return (
77
- <Listing
78
- entity={entity}
79
- key={key}
80
- handleRenameFileClick={
81
- handleRenameFileClick
82
- }
83
- handleDeleteFileClick={
84
- handleDeleteFileClick
85
- }
86
- handleFileClick={handleFileClick}
87
- isDirectoryOpen={isDirectoryOpen}
88
- toggleDirectory={toggleDirectory}
89
- activeFileId={activeFileId}
90
- />
91
- );
92
- })
93
- : null}
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
  }
@@ -1,6 +1,9 @@
1
1
  :root {
2
2
  --vzcode-font-family: ui-monospace, SFMono-Regular,
3
3
  'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace !important;
4
+
5
+ --vzcode-font-size: 16px;
6
+
4
7
  /* Variable for the background color */
5
8
  --vz-bg-color: rgb(
6
9
  41,