vzcode 0.21.0 → 0.24.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.
@@ -42,9 +42,13 @@ export const Item = ({
42
42
  const [showModal, setShowModal] = useState(false);
43
43
 
44
44
  // Function to open the delete file confirmation modal
45
- const handleModalOpen = useCallback(() => {
46
- setShowModal(true);
47
- }, []);
45
+ const handleModalOpen = useCallback(
46
+ (event: React.MouseEvent) => {
47
+ event.stopPropagation();
48
+ setShowModal(true);
49
+ },
50
+ [],
51
+ );
48
52
 
49
53
  // Function to close the modal
50
54
  const handleModalClose = useCallback(() => {
@@ -52,10 +56,14 @@ export const Item = ({
52
56
  }, []);
53
57
 
54
58
  // Function to handle confirmation on modal
55
- const handleConfirmModal = useCallback(() => {
56
- setShowModal(false);
57
- handleDeleteClick();
58
- }, [handleDeleteClick]);
59
+ const handleConfirmModal = useCallback(
60
+ (event: React.MouseEvent) => {
61
+ event.stopPropagation();
62
+ setShowModal(false);
63
+ handleDeleteClick();
64
+ },
65
+ [handleDeleteClick],
66
+ );
59
67
 
60
68
  const handleMouseEnter = useCallback(() => {
61
69
  setIsHovered(true);
@@ -98,7 +106,8 @@ export const Item = ({
98
106
  <i
99
107
  className="bx bxs-edit utilities"
100
108
  style={{ color: '#abdafb' }}
101
- onClick={() => {
109
+ onClick={(event: React.MouseEvent) => {
110
+ event.stopPropagation();
102
111
  setIsRenaming(!isRenaming);
103
112
  setRenameValue(isDir ? name : children);
104
113
  }}
@@ -1,6 +1,8 @@
1
1
  .vz-tab-list {
2
2
  display: flex;
3
3
  background-color: rgb(37, 37, 38);
4
+ // When the tab list becomes too large, a horizontal scroll bar appears.
5
+ overflow-x: auto;
4
6
 
5
7
  .tab {
6
8
  background: rgb(45, 45, 45);
@@ -17,6 +19,8 @@
17
19
  margin-left: 2px;
18
20
  margin-right: 2px;
19
21
  cursor: pointer;
22
+ // Make sure text doesn't wrap within tabs.
23
+ white-space: nowrap;
20
24
 
21
25
  .tab-close {
22
26
  margin-left: 5px;
@@ -0,0 +1,122 @@
1
+ import { FileId } from '../types';
2
+
3
+ export const reducer = (state, action) => {
4
+ switch (action.type) {
5
+ // TODO phase this out
6
+ case 'set_tab_list': {
7
+ return { ...state, tabList: action.tabList };
8
+ }
9
+ case 'set_active_fileId': {
10
+ return {
11
+ ...state,
12
+ activeFileId: action.activeFileId,
13
+ };
14
+ }
15
+ case 'open_tab': {
16
+ if (!state.tabList.includes(action.fileId)) {
17
+ return {
18
+ ...state,
19
+ activeFileId: action.fileId,
20
+ tabList: [...state.tabList, action.fileId],
21
+ };
22
+ } else {
23
+ return {
24
+ ...state,
25
+ activeFileId: action.fileId,
26
+ };
27
+ }
28
+ }
29
+ case 'close_tab': {
30
+ const i = state.tabList.findIndex(
31
+ (fileId) => fileId === action.fileIdToRemove,
32
+ );
33
+ if (i != -1) {
34
+ const newTabList = [
35
+ ...state.tabList.slice(0, i),
36
+ ...state.tabList.slice(i + 1),
37
+ ];
38
+ if (state.activeFileId === action.fileIdToRemove) {
39
+ // set the new active file to the next tab over,
40
+ if (newTabList.length > 0) {
41
+ return {
42
+ ...state,
43
+ activeFileId:
44
+ i === 0 ? newTabList[i] : newTabList[i - 1],
45
+ tabList: newTabList,
46
+ };
47
+ } else {
48
+ // or clear out the active file
49
+ // if we've closed the last tab.
50
+ return {
51
+ ...state,
52
+ activeFileId: null,
53
+ tabList: newTabList,
54
+ };
55
+ }
56
+ }
57
+ return {
58
+ ...state,
59
+ tabList: newTabList,
60
+ };
61
+ }
62
+ return {
63
+ ...state,
64
+ };
65
+ }
66
+ case 'multi_close_tab': {
67
+ let newTabList = [...state.tabList]; // Create a copy of the tabList array.
68
+ let newActiveFileId: FileId = state.activeFileId;
69
+ action.idsToDelete.forEach((id) => {
70
+ const i = newTabList.findIndex(
71
+ (fileId) => fileId === id,
72
+ );
73
+ if (i !== -1) {
74
+ // Remove the tab from the tab list.
75
+ newTabList = [
76
+ ...newTabList.slice(0, i),
77
+ ...newTabList.slice(i + 1),
78
+ ];
79
+ // If we are closing the active file,
80
+ if (newActiveFileId === id) {
81
+ // set the new active file to the next tab over,
82
+ if (newTabList.length > 0) {
83
+ newActiveFileId =
84
+ i === 0 ? newTabList[i] : newTabList[i - 1];
85
+ } else {
86
+ // or clear out the active file if we've closed the last tab.
87
+ newActiveFileId = null;
88
+ }
89
+ }
90
+ }
91
+ });
92
+ return {
93
+ ...state,
94
+ tabList: newTabList,
95
+ activeFileId: newActiveFileId,
96
+ };
97
+ }
98
+ // TODO eventually
99
+ // case 'init_sharedb': {
100
+ // return {
101
+ // ...state,
102
+ // submitOperation: action.submitOperation
103
+ // }
104
+ // }
105
+ // case 'delete_file': {
106
+ // case 'delete_directory': {
107
+ // case 'create_file': {
108
+ // state.submitOperation((document) => ({
109
+ // ...document,
110
+ // files: {
111
+ // ...document.files,
112
+ // [randomId()]: { name:action.name, text: '' },
113
+ // },
114
+ // }))
115
+ // return state;
116
+
117
+ // }
118
+ // case 'rename_file': {
119
+ // ...
120
+ }
121
+ throw Error('Unknown action: ' + action.type);
122
+ };
@@ -45,6 +45,9 @@ body,
45
45
  .left,
46
46
  .right {
47
47
  display: flex;
48
+
49
+ /* To suport PrettierErrorOverlay, which is position: absolute */
50
+ position: relative;
48
51
  }
49
52
 
50
53
  .right {
@@ -4,7 +4,7 @@ import {
4
4
  ShareDBDoc,
5
5
  VZCodeContent,
6
6
  } from '../../types';
7
- import { useEffect, useRef } from 'react';
7
+ import { useEffect, useRef, useState } from 'react';
8
8
 
9
9
  // The time in milliseconds by which auto-saving is debounced.
10
10
  const autoPrettierDebounceTimeMS = 1200;
@@ -31,6 +31,14 @@ export const usePrettier = (
31
31
  // since the last Prettier run.
32
32
  const dirtyFiles: Set<FileId> = new Set<FileId>();
33
33
 
34
+ // State to hold the error from Prettier
35
+ // `null` means no errors
36
+ // If this is a string, it's the
37
+ // error message from Prettier.
38
+ const [prettierError, setPrettierError] = useState<
39
+ string | null
40
+ >(null);
41
+
34
42
  // A ref to keep track of whether an op is being applied.
35
43
  // This is used to prevent Prettier from running
36
44
  // when the op is coming from Prettier itself.
@@ -46,9 +54,18 @@ export const usePrettier = (
46
54
  const handleMessage = (event) => {
47
55
  const { fileId, error, fileTextPrettified } =
48
56
  event.data;
57
+
58
+ // Handle syntax errors
49
59
  if (error) {
50
- console.log(error);
60
+ // If there's an error, set the error state
61
+ setPrettierError(error);
62
+
63
+ // Return early as there is no text change to apply.
51
64
  return;
65
+ } else {
66
+ // Make sure the error overlay goes away
67
+ // when Prettier runs successfully with no errors.
68
+ setPrettierError(null);
52
69
  }
53
70
 
54
71
  isApplyingOpRef.current = true;
@@ -159,19 +176,20 @@ export const usePrettier = (
159
176
  } else {
160
177
  // console.log('ignoring op from remote', op, isLocal);
161
178
  }
162
-
163
- // In the event that the component unmounts,
164
- return () => {
165
- // Remove the event listener
166
- prettierWorker.removeEventListener(
167
- 'message',
168
- handleMessage,
169
- );
170
-
171
- // Clear the timeout
172
- clearTimeout(debounceTimeout);
173
- };
174
179
  },
175
180
  );
181
+
182
+ // In the event that the component unmounts,
183
+ return () => {
184
+ // Remove the event listener
185
+ prettierWorker.removeEventListener(
186
+ 'message',
187
+ handleMessage,
188
+ );
189
+
190
+ // Clear the timeout
191
+ clearTimeout(debounceTimeout);
192
+ };
176
193
  }, [shareDBDoc]);
194
+ return { prettierError }; // Return the errors for use in other files
177
195
  };
@@ -90,7 +90,7 @@ onmessage = async ({
90
90
  } catch (error) {
91
91
  postMessage({
92
92
  fileId,
93
- error,
93
+ error: error.toString(),
94
94
  });
95
95
  }
96
96
  };
@@ -11,6 +11,7 @@ export const useTabsState = (
11
11
  // Called when a tab is closed.
12
12
  const closeTab = useCallback(
13
13
  (fileIdToRemove: FileId) => {
14
+ let newActiveFileId: FileId = activeFileId;
14
15
  const i = tabList.findIndex(
15
16
  (fileId) => fileId === fileIdToRemove,
16
17
  );
@@ -29,16 +30,52 @@ export const useTabsState = (
29
30
  if (activeFileId === fileIdToRemove) {
30
31
  // set the new active file to the next tab over,
31
32
  if (newTabList.length > 0) {
32
- setActiveFileId(
33
- i === 0 ? newTabList[i] : newTabList[i - 1],
34
- );
33
+ newActiveFileId =
34
+ i === 0 ? newTabList[i] : newTabList[i - 1];
35
35
  } else {
36
36
  // or clear out the active file
37
37
  // if we've closed the last tab.
38
- setActiveFileId(null);
38
+ newActiveFileId = null;
39
39
  }
40
40
  }
41
41
  }
42
+ setActiveFileId(newActiveFileId);
43
+ },
44
+ [tabList, activeFileId],
45
+ );
46
+
47
+ //When closing multiple tabs use this function over CloseTab()
48
+ const multiCloseTab = useCallback(
49
+ (idsToDelete: Array<FileId>) => {
50
+ let newTabList = [...tabList]; // Create a copy of the tabList array.
51
+ let newActiveFileId: FileId = activeFileId;
52
+ idsToDelete.forEach((id) => {
53
+ const i = newTabList.findIndex(
54
+ (fileId) => fileId === id,
55
+ );
56
+ if (i !== -1) {
57
+ // Remove the tab from the tab list.
58
+ newTabList = [
59
+ ...newTabList.slice(0, i),
60
+ ...newTabList.slice(i + 1),
61
+ ];
62
+
63
+ // If we are closing the active file,
64
+ if (newActiveFileId === id) {
65
+ // set the new active file to the next tab over,
66
+ if (newTabList.length > 0) {
67
+ newActiveFileId =
68
+ i === 0 ? newTabList[i] : newTabList[i - 1];
69
+ } else {
70
+ // or clear out the active file if we've closed the last tab.
71
+ newActiveFileId = null;
72
+ }
73
+ }
74
+ }
75
+ });
76
+ //Run setStates after doing multiple changes to the two variables. Will properly render tabList and activeFileID.
77
+ setActiveFileId(newActiveFileId);
78
+ setTabList(newTabList);
42
79
  },
43
80
  [tabList, activeFileId],
44
81
  );
@@ -54,5 +91,5 @@ export const useTabsState = (
54
91
  [tabList],
55
92
  );
56
93
 
57
- return { tabList, closeTab, openTab };
94
+ return { tabList, closeTab, openTab, multiCloseTab };
58
95
  };
@@ -109,13 +109,30 @@ const save = () => {
109
109
 
110
110
  // Handle renaming files.
111
111
  if (previous.name !== current.name) {
112
- fs.renameSync(previous.name, current.name);
112
+ fs.renamesync(previous.name, current.name);
113
113
  }
114
114
  }
115
115
 
116
- // handle deleting files.
116
+ // handle deleting files and directories.
117
117
  if (previous && !current) {
118
- fs.unlinkSync(previous.name);
118
+ let stats = fs.statSync(previous.name);
119
+ //Check if the file path we are trying to delete is a directory
120
+ if (!stats.isDirectory()) {
121
+ fs.unlinkSync(previous.name);
122
+ } else {
123
+ //Performs directory deletion.
124
+ fs.rm(
125
+ previous.name,
126
+ {
127
+ recursive: true, //Makes sure that all files in directory are deleted.
128
+ },
129
+ (error) => {
130
+ if (error) {
131
+ console.log(error);
132
+ }
133
+ },
134
+ );
135
+ }
119
136
  }
120
137
 
121
138
  // Handle creating files.
@@ -1 +0,0 @@
1
- .vz-code-editor{flex:1;display:flex;font-family:var(--vzcode-font-family);overflow-x:auto}.vz-code-editor .cm-editor{flex:1}.vz-sidebar{height:100%;background:rgb(54,65,89);display:flex;flex-direction:column;justify-content:space-between}.vz-sidebar .full-box{padding:7px;display:flex;flex-direction:row;align-items:center;justify-content:space-between}.vz-sidebar .files{overflow:auto}.vz-sidebar .file-or-directory{display:flex;flex-direction:row;align-items:center;justify-content:space-between;cursor:pointer;margin:0 10px;color:#dbdde1;border-radius:5px}.vz-sidebar .link-name{margin-top:30px;margin-left:10px;color:#9aa0ac;font-size:12px;text-decoration:none}.vz-sidebar .new-btn{justify-content:right;margin-right:10px;cursor:pointer}.vz-sidebar .utils{display:flex}.vz-sidebar .utilities{margin-right:3px}.vz-sidebar .file-or-directory:hover,.vz-sidebar .settings:hover{background-color:#4a5469}.vz-sidebar .name{display:flex;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.vz-sidebar .indentation{margin-left:20px}.vz-sidebar .arrow-wrapper{margin-right:4px}.vz-sidebar .settings{height:30px;color:#dbdde1;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:14px 0;margin:10px;border-radius:5px}.vz-tab-list{display:flex;background-color:#252526}.vz-tab-list .tab{background:rgb(45,45,45);color:#fff;font-family:var(--vzcode-font-family);align-items:center;display:flex;padding-top:2px;padding-left:10px;padding-right:10px;border-top-left-radius:10px;border-top-right-radius:10px;margin-top:5px;margin-left:2px;margin-right:2px;cursor:pointer}.vz-tab-list .tab .tab-close{margin-left:5px}.vz-tab-list .tab .tab-close:hover{border-radius:30%;background-color:#252526}.vz-tab-list .tab.active{background-color:#292c34}.vz-resizer{position:absolute;top:0;left:0;bottom:0}.vz-resizer:hover{cursor:col-resize}.vz-resizer .vz-resizer-thumb{position:absolute;top:0;bottom:0;transition:background-color .15s .25s}.vz-resizer:hover .vz-resizer-thumb{background-color:#0078d4}.vz-notification{position:fixed;display:flex;flex-direction:column;gap:10px;bottom:20px;right:20px;color:#9aa0ac;padding:15px}.vz-notification .vz-notification-item{background-color:#364159;padding:5px;border-radius:5px}:root{--vzcode-font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;--vz-bg-color: rgb( 41, 44, 52 )}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:var(--app-bg-color)}::-webkit-scrollbar-thumb{background:rgb(37,37,38)}::-webkit-scrollbar-thumb:hover{background:#555}html,body,#root,.app{height:100%}.app{background-color:var(--vz-bg-color);font-family:Poppins,sans-serif;display:flex;position:relative}.app .left,.app .right{display:flex}.app .right{flex:1;flex-direction:column;min-width:0}