vzcode 0.84.0 → 0.86.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.
@@ -57,6 +57,10 @@ export type VZCodeContextValue = {
57
57
  createFile: (fileName: string, text?: string) => void;
58
58
  renameFile: (fileId: string, fileName: string) => void;
59
59
  deleteFile: (fileId: string) => void;
60
+ createDirectory: (
61
+ fileName: string,
62
+ text?: string,
63
+ ) => void;
60
64
  renameDirectory: (
61
65
  directoryId: string,
62
66
  directoryOldName: string,
@@ -107,6 +111,12 @@ export type VZCodeContextValue = {
107
111
  handleOpenCreateFileModal: () => void;
108
112
  handleCloseCreateFileModal: () => void;
109
113
  handleCreateFileClick: (newFileName: string) => void;
114
+
115
+ isCreateDirModalOpen: boolean;
116
+ handleOpenCreateDirModal: () => void;
117
+ handleCloseCreateDirModal: () => void;
118
+ handleCreateDirClick: (newFileName: string) => void;
119
+
110
120
  runPrettierRef: React.MutableRefObject<
111
121
  null | (() => void)
112
122
  >;
@@ -237,6 +247,7 @@ export const VZCodeProvider = ({
237
247
  createFile,
238
248
  renameFile,
239
249
  deleteFile,
250
+ createDirectory,
240
251
  renameDirectory,
241
252
  deleteDirectory,
242
253
  } = useFileCRUD({
@@ -265,6 +276,26 @@ export const VZCodeProvider = ({
265
276
  [createFile, setIsCreateFileModalOpen],
266
277
  );
267
278
 
279
+ // State to control the create directory modal's visibility
280
+ const [isCreateDirModalOpen, setIsCreateDirModalOpen] =
281
+ useState(false);
282
+
283
+ const handleOpenCreateDirModal = useCallback(() => {
284
+ setIsCreateDirModalOpen(true);
285
+ }, []);
286
+
287
+ const handleCloseCreateDirModal = useCallback(() => {
288
+ setIsCreateDirModalOpen(false);
289
+ }, []);
290
+
291
+ const handleCreateDirClick = useCallback(
292
+ (newDirName: string) => {
293
+ createDirectory(newDirName);
294
+ setIsCreateDirModalOpen(false);
295
+ },
296
+ [createDirectory, setIsCreateDirModalOpen],
297
+ );
298
+
268
299
  // Isolate the files object from the document.
269
300
  const files: Files | null = content
270
301
  ? content.files
@@ -296,6 +327,7 @@ export const VZCodeProvider = ({
296
327
  createFile,
297
328
  renameFile,
298
329
  deleteFile,
330
+ createDirectory,
299
331
  renameDirectory,
300
332
  deleteDirectory,
301
333
 
@@ -333,6 +365,10 @@ export const VZCodeProvider = ({
333
365
  handleOpenCreateFileModal,
334
366
  handleCloseCreateFileModal,
335
367
  handleCreateFileClick,
368
+ isCreateDirModalOpen,
369
+ handleOpenCreateDirModal,
370
+ handleCloseCreateDirModal,
371
+ handleCreateDirClick,
336
372
  runPrettierRef,
337
373
  runCodeRef,
338
374
 
@@ -1,6 +1,7 @@
1
1
  import { VZSettings } from './VZSettings';
2
2
  import { VZSidebar } from './VZSidebar';
3
3
  import { CreateFileModal } from './VZSidebar/CreateFileModal';
4
+ import { CreateDirModal } from './VZSidebar/CreateDirModal';
4
5
 
5
6
  // The middle portion of the VZCode environment, containing:
6
7
  // * The sidebar
@@ -14,6 +15,7 @@ export const VZLeft = ({ enableUsernameField = true }) => {
14
15
  enableUsernameField={enableUsernameField}
15
16
  />
16
17
  <CreateFileModal />
18
+ <CreateDirModal />
17
19
  </div>
18
20
  );
19
21
  };
@@ -34,27 +34,24 @@ export const VZSettings = ({
34
34
  [],
35
35
  );
36
36
 
37
- // Track selected font and font size.
38
- // TODO store these in local storage
39
- const [selectedFont, setSelectedFont] =
40
- useState('Roboto Mono');
41
- const [selectedFontSize, setSelectedFontSize] =
42
- useState('16px');
37
+ // Initialize font and size from localStorage or defaults
38
+ const [selectedFont, setSelectedFont] = useState(localStorage.getItem('vzcodeSelectedFont') || 'Roboto Mono');
39
+ const [selectedFontSize, setSelectedFontSize] = useState(localStorage.getItem('vzcodeSelectedFontSize') || '16px');
43
40
 
44
41
  // Called when the user selects a different font
45
- const handleFontChange = useCallback(
46
- (event: React.ChangeEvent<HTMLSelectElement>) => {
47
- setSelectedFont(event.target.value);
48
- },
49
- [],
50
- );
42
+ const handleFontChange = useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
43
+ const newFont = event.target.value;
44
+ localStorage.setItem('vzcodeSelectedFont', newFont);
45
+ setSelectedFont(newFont);
46
+ }, []);
51
47
 
52
48
  // Called when the user selects a different font size
53
- const handleFontSizeChange = (
54
- event: React.ChangeEvent<HTMLSelectElement>,
55
- ) => {
56
- setSelectedFontSize(event.target.value);
57
- };
49
+ const handleFontSizeChange = useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
50
+ const newSize = event.target.value;
51
+ localStorage.setItem('vzcodeSelectedFontSize', newSize);
52
+ setSelectedFontSize(newSize);
53
+ }, []);
54
+
58
55
 
59
56
  useEffect(() => {
60
57
  document.body.style.setProperty(
@@ -0,0 +1,99 @@
1
+ import {
2
+ useState,
3
+ useCallback,
4
+ useRef,
5
+ useEffect,
6
+ useContext,
7
+ } from 'react';
8
+ import { Modal, Form, Button } from '../bootstrap';
9
+ import { VZCodeContext } from '../VZCodeContext';
10
+
11
+ export const CreateDirModal = ({
12
+ initialFileName = '',
13
+ }) => {
14
+ const [newName, setNewName] = useState(initialFileName);
15
+ const inputRef = useRef(null);
16
+
17
+ const {
18
+ isCreateDirModalOpen,
19
+ handleCloseCreateDirModal,
20
+ handleCreateDirClick,
21
+ } = useContext(VZCodeContext);
22
+
23
+ useEffect(() => {
24
+ if (isCreateDirModalOpen && inputRef.current) {
25
+ inputRef.current.focus();
26
+ }
27
+ }, [isCreateDirModalOpen]);
28
+
29
+ const handleNameChange = useCallback((event) => {
30
+ setNewName(event.target.value);
31
+ }, []);
32
+
33
+ const onCreateClick = useCallback(() => {
34
+ handleCreateDirClick(newName);
35
+ setNewName('');
36
+ }, [newName, handleCreateDirClick]);
37
+
38
+ // Returns true if file name is valid, false otherwise.
39
+ const validateFileName = useCallback(
40
+ (fileName: string) => {
41
+ const regex =
42
+ /^[a-zA-Z0-9](?:[a-zA-Z0-9 ./+=_-]*[a-zA-Z0-9])?$/;
43
+ return regex.test(fileName);
44
+ },
45
+ [],
46
+ );
47
+
48
+ const handleKeyDown = useCallback(
49
+ (e) => {
50
+ if (
51
+ e.key === 'Enter' &&
52
+ validateFileName(newName) &&
53
+ (e.ctrlKey ||
54
+ e.shiftKey ||
55
+ (!e.altKey && !e.metaKey))
56
+ ) {
57
+ onCreateClick();
58
+ }
59
+ },
60
+ [onCreateClick, validateFileName, newName],
61
+ );
62
+
63
+ return isCreateDirModalOpen ? (
64
+ <Modal
65
+ show={isCreateDirModalOpen}
66
+ onHide={handleCloseCreateDirModal}
67
+ animation={false}
68
+ onKeyDown={handleKeyDown}
69
+ >
70
+ <Modal.Header closeButton>
71
+ <Modal.Title>Create Directory</Modal.Title>
72
+ </Modal.Header>
73
+ <Modal.Body>
74
+ <Form.Group className="mb-3" controlId="fileName">
75
+ <Form.Label>Directory Name</Form.Label>
76
+ <Form.Control
77
+ type="text"
78
+ value={newName}
79
+ onChange={handleNameChange}
80
+ ref={inputRef}
81
+ spellCheck="false"
82
+ />
83
+ <Form.Text className="text-muted">
84
+ Enter the name for your file
85
+ </Form.Text>
86
+ </Form.Group>
87
+ </Modal.Body>
88
+ <Modal.Footer>
89
+ <Button
90
+ variant="primary"
91
+ onClick={onCreateClick}
92
+ disabled={!validateFileName(newName)}
93
+ >
94
+ Create Directory
95
+ </Button>
96
+ </Modal.Footer>
97
+ </Modal>
98
+ ) : null;
99
+ };
@@ -14,7 +14,7 @@ import { getFileTree } from '../getFileTree';
14
14
  import { sortFileTree } from '../sortFileTree';
15
15
  import { disableSettings } from '../featureFlags';
16
16
  import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
17
- import { BugSVG, GearSVG, NewSVG } from '../Icons';
17
+ import { BugSVG, GearSVG, NewSVG, FileSVG } from '../Icons';
18
18
  import { Listing } from './Listing';
19
19
  import { VZCodeContext } from '../VZCodeContext';
20
20
  import { useDragAndDrop } from './useDragAndDrop';
@@ -27,10 +27,12 @@ const enableConnectionStatus = true;
27
27
 
28
28
  export const VZSidebar = ({
29
29
  createFileTooltipText = 'New File',
30
+ createDirTooltipText = 'New Directory',
30
31
  openSettingsTooltipText = 'Open Settings',
31
32
  reportBugTooltipText = 'Report Bug',
32
33
  }: {
33
34
  createFileTooltipText?: string;
35
+ createDirTooltipText?: string;
34
36
  openSettingsTooltipText?: string;
35
37
  reportBugTooltipText?: string;
36
38
  }) => {
@@ -46,6 +48,7 @@ export const VZSidebar = ({
46
48
  isDirectoryOpen,
47
49
  toggleDirectory,
48
50
  handleOpenCreateFileModal,
51
+ handleOpenCreateDirModal,
49
52
  connected,
50
53
  } = useContext(VZCodeContext);
51
54
 
@@ -157,6 +160,23 @@ export const VZSidebar = ({
157
160
  <NewSVG />
158
161
  </i>
159
162
  </OverlayTrigger>
163
+
164
+ {/*Directory Rename*/}
165
+ <OverlayTrigger
166
+ placement="left"
167
+ overlay={
168
+ <Tooltip id="create-dir-tooltip">
169
+ {createDirTooltipText}
170
+ </Tooltip>
171
+ }
172
+ >
173
+ <i
174
+ onClick={handleOpenCreateDirModal}
175
+ className="icon-button icon-button-dark"
176
+ >
177
+ <FileSVG />
178
+ </i>
179
+ </OverlayTrigger>
160
180
  </div>
161
181
  </div>
162
182
  {isDragOver ? (
@@ -37,24 +37,35 @@ export const useDragAndDrop = () => {
37
37
  event.preventDefault();
38
38
  event.stopPropagation();
39
39
  setIsDragOver(false);
40
- const files = event.dataTransfer.files;
41
-
42
- if (debug) {
43
- console.log(files);
44
- }
45
-
46
- Array.from(files).forEach((file) => {
47
- const reader = new FileReader();
48
- reader.onload = (readEvent) => {
49
- createFile(
50
- file.name,
51
- readEvent.target.result as string,
52
- );
53
- };
54
- reader.onerror = (error) => {
55
- console.error('Error reading file:', error);
56
- };
57
- reader.readAsText(file);
40
+
41
+ const items = event.dataTransfer.items;
42
+
43
+ const processEntry = (entry, path) => {
44
+ if (entry.isFile) {
45
+ entry.file((file) => {
46
+ const reader = new FileReader();
47
+ reader.onload = (e) => {
48
+ const content = e.target.result as string;
49
+ createFile(`${path}${file.name}`, content);
50
+ };
51
+ reader.readAsText(file);
52
+ });
53
+ } else if (entry.isDirectory) {
54
+ const dirReader = entry.createReader();
55
+ dirReader.readEntries((entries) => {
56
+ const newPath = `${path}${entry.name}/`;
57
+ for (const entry of entries) {
58
+ processEntry(entry, newPath);
59
+ }
60
+ });
61
+ }
62
+ };
63
+ const itemsArray = Array.from(items);
64
+ itemsArray.forEach((item) => {
65
+ const entry = item.webkitGetAsEntry();
66
+ if (entry) {
67
+ processEntry(entry, '');
68
+ }
58
69
  });
59
70
  },
60
71
  [createFile],
@@ -45,6 +45,23 @@ export const useFileCRUD = ({
45
45
  [submitOperation],
46
46
  );
47
47
 
48
+ const createDirectory = useCallback(
49
+ (name: string, text = null) => {
50
+ if (name) {
51
+ name += '/';
52
+ const fileId: FileId = randomId();
53
+ submitOperation((document: VZCodeContent) => ({
54
+ ...document,
55
+ files: {
56
+ ...document.files,
57
+ [fileId]: { name, text },
58
+ },
59
+ }));
60
+ }
61
+ },
62
+ [submitOperation],
63
+ );
64
+
48
65
  // Called when a file in the sidebar is renamed.
49
66
  const renameFile = useCallback(
50
67
  (fileId: FileId, newName: string) => {
@@ -148,6 +165,7 @@ export const useFileCRUD = ({
148
165
  createFile,
149
166
  renameFile,
150
167
  deleteFile,
168
+ createDirectory,
151
169
  renameDirectory,
152
170
  deleteDirectory,
153
171
  };
@@ -217,10 +217,16 @@ const save = () => {
217
217
  }
218
218
  }
219
219
 
220
- // Handle creating files.
220
+ // Handle creating files and Directories.
221
221
  if (!previous && current) {
222
- fs.writeFileSync(current.name, current.text);
222
+ //File Creation
223
+ if(!isDirectory(current.name)){
224
+ fs.writeFileSync(current.name, current.text);
225
+ }else{
226
+ fs.mkdirSync(current.name, { recursive: true });
227
+ }
223
228
  }
229
+
224
230
  }
225
231
  // TODO deleted all directories under directoriesToDelete
226
232