vzcode 0.21.0 → 0.23.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.
@@ -0,0 +1 @@
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;overflow-x:auto}.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;white-space:nowrap}.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}: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}
package/dist/index.html CHANGED
@@ -28,8 +28,8 @@
28
28
  href="https://unpkg.com/bootstrap@5.3.0/dist/css/bootstrap.min.css"
29
29
  rel="stylesheet"
30
30
  />
31
- <script type="module" crossorigin src="/assets/index-37015623.js"></script>
32
- <link rel="stylesheet" href="/assets/index-1547f7b8.css">
31
+ <script type="module" crossorigin src="/assets/index-49f71876.js"></script>
32
+ <link rel="stylesheet" href="/assets/index-c63b4be7.css">
33
33
  </head>
34
34
  <body>
35
35
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -49,12 +49,12 @@
49
49
  "@codemirror/lang-css": "^6.2.1",
50
50
  "@codemirror/lang-html": "^6.4.6",
51
51
  "@codemirror/lang-javascript": "^6.2.1",
52
- "@codemirror/lang-markdown": "^6.2.1",
52
+ "@codemirror/lang-markdown": "^6.2.2",
53
53
  "@codemirror/state": "^6.2.1",
54
54
  "@codemirror/theme-one-dark": "^6.1.2",
55
55
  "@replit/codemirror-interact": "^6.3.0",
56
56
  "@teamwork/websocket-json-stream": "^2.0.0",
57
- "@uiw/codemirror-themes-all": "^4.21.18",
57
+ "@uiw/codemirror-themes-all": "^4.21.19",
58
58
  "codemirror": "^6.0.1",
59
59
  "codemirror-ot": "^4.1.0",
60
60
  "d3-array": "^3.2.4",
@@ -67,20 +67,19 @@
67
67
  "react": "^18.2.0",
68
68
  "react-bootstrap": "^2.9.0",
69
69
  "react-dom": "^18.2.0",
70
- "react-select": "^5.7.5",
71
- "sharedb": "^4.0.1",
70
+ "sharedb": "^4.1.0",
72
71
  "sharedb-client-browser": "^4.3.1",
73
72
  "ot-json1": "^1.0.2",
74
73
  "ws": "^8.14.2"
75
74
  },
76
75
  "devDependencies": {
77
- "@types/react": "^18.2.23",
78
- "@types/react-dom": "^18.2.8",
76
+ "@types/react": "^18.2.28",
77
+ "@types/react-dom": "^18.2.13",
79
78
  "@vitejs/plugin-react": "^4.1.0",
80
79
  "prettier": "^3.0.3",
81
- "sass": "^1.68.0",
80
+ "sass": "^1.69.2",
82
81
  "typescript": "^5.2.2",
83
- "vite": "^4.4.9",
84
- "vitest": "^0.34.5"
82
+ "vite": "^4.4.11",
83
+ "vitest": "^0.34.6"
85
84
  }
86
85
  }
@@ -28,7 +28,6 @@ import { usePrettier } from './usePrettier';
28
28
  import PrettierWorker from './usePrettier/worker?worker';
29
29
  import { SplitPaneResizeProvider } from './SplitPaneResizeContext';
30
30
  import { Resizer } from './Resizer';
31
- import { PresenceNotifications } from './PresenceNotifications';
32
31
  import './style.scss';
33
32
 
34
33
  // Instantiate the Prettier worker.
@@ -228,11 +227,36 @@ function App() {
228
227
  [submitOperation, closeTab],
229
228
  );
230
229
 
231
- const handleDeleteFileClick = useCallback(
232
- (fileId: FileId) => {
233
- deleteFile(fileId);
230
+ const deleteDirectory = useCallback(
231
+ (path: FileId) => {
232
+ submitOperation((document) => {
233
+ const updatedFiles = { ...document.files };
234
+ for (const key in updatedFiles) {
235
+ if (updatedFiles[key].name.includes(path)) {
236
+ closeTab(key);
237
+ delete updatedFiles[key];
238
+ }
239
+ }
240
+ return { ...document, files: updatedFiles };
241
+ });
234
242
  },
235
- [deleteFile],
243
+ [submitOperation, closeTab],
244
+ );
245
+
246
+ const handleDeleteClick = useCallback(
247
+ (key: string) => {
248
+ //Regex to identify if the key is a file path or a file id.
249
+ if (/^[0-9]*$/.test(key)) {
250
+ if (key.length == 8) {
251
+ deleteFile(key);
252
+ } else {
253
+ deleteDirectory(key);
254
+ }
255
+ } else {
256
+ deleteDirectory(key);
257
+ }
258
+ },
259
+ [deleteFile, deleteDirectory],
236
260
  );
237
261
 
238
262
  const handleSettingsClose = useCallback(() => {
@@ -276,7 +300,7 @@ function App() {
276
300
  createFile={createFile}
277
301
  files={files}
278
302
  handleRenameFileClick={handleRenameFileClick}
279
- handleDeleteFileClick={handleDeleteFileClick}
303
+ handleDeleteFileClick={handleDeleteClick}
280
304
  handleFileClick={openTab}
281
305
  setIsSettingsOpen={setIsSettingsOpen}
282
306
  isDirectoryOpen={isDirectoryOpen}
@@ -308,9 +332,6 @@ function App() {
308
332
  editorCache={editorCache}
309
333
  />
310
334
  ) : null}
311
- <PresenceNotifications
312
- docPresence={docPresence}
313
- />
314
335
  </div>
315
336
  <Resizer />
316
337
  </div>
@@ -16,7 +16,7 @@ import {
16
16
  } from '../../types';
17
17
  import { json1PresenceBroadcast } from './json1PresenceBroadcast';
18
18
  import { json1PresenceDisplay } from './json1PresenceDisplay';
19
- import { widgets } from './widgets';
19
+ import { colorsInTextPlugin, widgets } from './widgets';
20
20
  import {
21
21
  EditorCache,
22
22
  EditorCacheValue,
@@ -127,6 +127,8 @@ export const getOrCreateEditor = ({
127
127
  json1PresenceDisplay({ path: textPath, docPresence }),
128
128
  );
129
129
 
130
+ extensions.push(colorsInTextPlugin);
131
+
130
132
  // This is the "basic setup" for CodeMirror,
131
133
  // which actually adds a ton on functionality.
132
134
  // TODO vet this functionality, and determine how much
@@ -1,5 +1,4 @@
1
1
  import { EditorView } from 'codemirror';
2
- import { username } from '../Settings.tsx';
3
2
 
4
3
  // Deals with broadcasting changes in cursor location and selection.
5
4
  export const json1PresenceBroadcast = ({
@@ -23,7 +22,6 @@ export const json1PresenceBroadcast = ({
23
22
  const presence = {
24
23
  start: [...path, from],
25
24
  end: [...path, to],
26
- username: username,
27
25
  };
28
26
 
29
27
  // Broadcast presence to remote clients!
@@ -32,16 +32,13 @@ export const json1PresenceDisplay = ({
32
32
  // Receive remote presence changes.
33
33
  docPresence.on('receive', (id, presence) => {
34
34
  // If presence === null, the user has disconnected / exited
35
- // notification(presence);
36
- // console.log('docPresence in presenceDisplay:',docPresence);
37
- // console.log('presence in presenceDisplay:', presence);
38
-
39
35
  // We also check if the presence is for the current file or not.
40
36
  if (presence && pathMatches(path, presence)) {
41
37
  presenceState[id] = presence;
42
38
  } else {
43
39
  delete presenceState[id];
44
40
  }
41
+
45
42
  // Update decorations to reflect new presence state.
46
43
  // TODO consider mutating this rather than recomputing it on each change.
47
44
  this.decorations = Decoration.set(
@@ -102,47 +99,6 @@ export const json1PresenceDisplay = ({
102
99
  presenceTheme,
103
100
  ];
104
101
 
105
- // const notification = (presence) => {
106
- // const notif = document.createElement('div');
107
- // notif.className = 'notification-presence';
108
- // if (presence === null){
109
- // notif.textContent = 'User asdfgasdfgasdfg left the collaboration session.';
110
- // notif.style.display = 'block';
111
- // }
112
- // // The following causes the popup to show every time the remote cursor moves
113
- // // to a different location on the file and not when the user first joins
114
- // // else {
115
- // // notif.textContent = presence + 'joined the collaboration session.';
116
- // // notif.style.display = 'block';
117
- // // }
118
- // document.body.appendChild(notif);
119
-
120
- // const notifStyle = document.createElement('style');
121
- // notifStyle.innerText = `
122
- // .notification-presence {
123
- // position: fixed;
124
- // display: none;
125
- // bottom: 20px;
126
- // right: 20px;
127
- // max-width: 300px;
128
- // overflow-x: hidden;
129
- // white-space: nowrap;
130
- // background-color: rgb(54, 65, 89);
131
- // color: rgb(154, 160, 172);
132
- // padding: 15px;
133
- // }
134
- // .notification-presence:hover {
135
- // overflow-x: auto;
136
- // }`;
137
- // document.head.appendChild(notifStyle);
138
-
139
- // setTimeout(() => {
140
- // notif.style.display = 'none';
141
- // }, 10000);
142
-
143
- // return notif;
144
- // };
145
-
146
102
  const presenceAnnotation = Annotation.define();
147
103
 
148
104
  // Checks that the path of this file
@@ -1,5 +1,17 @@
1
1
  import interact from '@replit/codemirror-interact';
2
2
 
3
+ import {
4
+ ViewPlugin,
5
+ Decoration,
6
+ WidgetType,
7
+ } from '@codemirror/view';
8
+ import {
9
+ Annotation,
10
+ Extension,
11
+ RangeSet,
12
+ } from '@codemirror/state';
13
+ import { EditorView } from 'codemirror';
14
+
3
15
  // Interactive code widgets.
4
16
  // * Number dragger
5
17
  // * Boolean toggler
@@ -15,10 +27,49 @@ export const widgets = ({
15
27
  }) =>
16
28
  interact({
17
29
  rules: [
30
+ // hex color picker
31
+ // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
32
+ {
33
+ regexp: /\"\#([0-9]|[A-F]|[a-f]){6}\"/g,
34
+ cursor: 'pointer',
35
+ onClick(text, setText, e) {
36
+ const res =
37
+ /\"(?<hex>\#([0-9]|[A-F]|[a-f]){6})\"/.exec(
38
+ text,
39
+ );
40
+ const startingColor = res.groups?.hex;
41
+
42
+ const sel = document.createElement('input');
43
+ sel.type = 'color';
44
+
45
+ sel.value = startingColor.toLowerCase();
46
+
47
+ //valueIsUpper maintains style of user's code. It keeps the case of a-f the same case as the original."
48
+ const valueIsUpper =
49
+ startingColor.toUpperCase() === startingColor;
50
+
51
+ const updateHex = (e: Event) => {
52
+ const el = e.target as HTMLInputElement;
53
+
54
+ if (el.value) {
55
+ setText(
56
+ `"${
57
+ valueIsUpper
58
+ ? el.value.toUpperCase()
59
+ : el.value
60
+ }"`,
61
+ );
62
+ }
63
+ };
64
+ sel.addEventListener('input', updateHex);
65
+ sel.click();
66
+ },
67
+ },
68
+
18
69
  // a rule for a number dragger
19
70
  {
20
71
  // the regexp matching the value
21
- regexp: /-?\b\d+\.?\d*\b/g,
72
+ regexp: /(?<!\#)-?\b\d+\.?\d*\b/g,
22
73
  // set cursor to "ew-resize" on hover
23
74
  cursor: 'ew-resize',
24
75
  // change number value based on mouse X movement on drag
@@ -43,7 +94,7 @@ export const widgets = ({
43
94
  }
44
95
  },
45
96
  },
46
- //vec2 slider
97
+ // vec2 slider
47
98
  // Inspired by: https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L61
48
99
  {
49
100
  regexp:
@@ -64,7 +115,7 @@ export const widgets = ({
64
115
  );
65
116
  },
66
117
  },
67
- //color picker
118
+ // rgb color picker
68
119
  // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
69
120
  //TODO: create color picker for hsl colors
70
121
  {
@@ -82,6 +133,7 @@ export const widgets = ({
82
133
  //sel will open the color picker when sel.click is called.
83
134
  const sel = document.createElement('input');
84
135
  sel.type = 'color';
136
+
85
137
  if (!isNaN(r + g + b))
86
138
  sel.value = rgb2Hex(r, g, b);
87
139
 
@@ -148,3 +200,115 @@ const hex2RGB = (hex: string): [number, number, number] => {
148
200
  // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L117
149
201
  const rgb2Hex = (r: number, g: number, b: number): string =>
150
202
  '#' + r.toString(16) + g.toString(16) + b.toString(16);
203
+
204
+ const colorCircleTheme = EditorView.baseTheme({
205
+ '.color-circle-parent': { display: 'inline-block' },
206
+ });
207
+
208
+ const colorCircleAnnotation = Annotation.define();
209
+
210
+ export const colorsInTextPlugin: Extension = [
211
+ ViewPlugin.fromClass(
212
+ class {
213
+ decorations: any;
214
+ view: EditorView;
215
+ constructor(view: EditorView) {
216
+ this.decorations = RangeSet.of([]);
217
+ this.view = view;
218
+ }
219
+ },
220
+ {
221
+ decorations: (v) => {
222
+ const colorInfos = [];
223
+
224
+ const lines = v.view.state.doc.iter();
225
+ let line = lines.next();
226
+
227
+ //Offset is the number of characters before the hex so the circle can be placed properly.
228
+ let offset = 0;
229
+ while (!line.done) {
230
+ if (line.value === '\n') {
231
+ offset++;
232
+ line = lines.next();
233
+ continue;
234
+ }
235
+ const hexColorOccurances = line.value.matchAll(
236
+ /\"\#([0-9]|[A-F]|[a-f]){6}\"/g,
237
+ );
238
+ let hexOccurance = hexColorOccurances.next();
239
+ while (!hexOccurance.done) {
240
+ const offsetColorInfo = hexOccurance.value;
241
+ offsetColorInfo.index += offset;
242
+ colorInfos.push(offsetColorInfo);
243
+ hexOccurance = hexColorOccurances.next();
244
+ }
245
+ offset += line.value.length;
246
+ line = lines.next();
247
+ }
248
+
249
+ return Decoration.set(
250
+ colorInfos.map((colorInfo) => {
251
+ return {
252
+ //9 is the length of the hex color string including quotation marks
253
+ from: colorInfo.index + 9,
254
+ to: colorInfo.index + 9,
255
+ value: Decoration.widget({
256
+ side: -1,
257
+ widget: new ColorWidget(colorInfo[0]),
258
+ }),
259
+ };
260
+ }),
261
+ );
262
+ },
263
+ },
264
+ ),
265
+ colorCircleTheme,
266
+ ];
267
+
268
+ class ColorWidget extends WidgetType {
269
+ color: string;
270
+ constructor(color: string) {
271
+ super();
272
+ this.color = color;
273
+ }
274
+
275
+ eq(widget: ColorWidget): boolean {
276
+ // TODO consider possibly adding a random ID to support multiple instances with the same color
277
+ return widget.color === this.color;
278
+ }
279
+
280
+ toDOM(view: EditorView): HTMLElement {
281
+ const parent = document.createElement('div');
282
+
283
+ parent.setAttribute('style', 'width:10px;height:10px');
284
+ parent.className = 'color-circle-parent';
285
+ const svg = document.createElementNS(
286
+ 'http://www.w3.org/2000/svg',
287
+ 'svg',
288
+ );
289
+ const colorCircle = document.createElementNS(
290
+ 'http://www.w3.org/2000/svg',
291
+ 'circle',
292
+ );
293
+ colorCircle.setAttributeNS(
294
+ null,
295
+ 'fill',
296
+ this.color.replace(/\"/g, ''),
297
+ );
298
+ colorCircle.setAttributeNS(null, 'r', '5');
299
+ colorCircle.setAttributeNS(null, 'cx', '5');
300
+ colorCircle.setAttributeNS(null, 'cy', '5');
301
+
302
+ svg.setAttributeNS(null, 'viewBox', '0 0 10 10');
303
+ svg.setAttributeNS(null, 'width', '10');
304
+ svg.setAttributeNS(null, 'height', '10');
305
+
306
+ svg.appendChild(colorCircle);
307
+
308
+ parent.appendChild(svg);
309
+ return parent;
310
+ }
311
+ ignoreEvent() {
312
+ return false;
313
+ }
314
+ }
@@ -1,20 +1,13 @@
1
- import { useCallback, useRef } from 'react';
1
+ import { useCallback } from 'react';
2
2
  import { Button, Modal, Form } from './bootstrap';
3
- import Select from 'react-select';
4
- import {
5
- ThemeLabel,
6
- themeOptionsByLabel,
7
- themes,
8
- } from './themes';
3
+ import { ThemeLabel, themes } from './themes';
9
4
 
10
- const saveTimes = [
11
- { value: 1, label: '1 second' },
12
- { value: 5, label: '5 seconds' },
13
- { value: 10, label: '10 seconds' },
14
- { value: 30, label: '30 seconds' },
15
- ];
16
-
17
- export let username = 'Anonymous';
5
+ // const saveTimes = [
6
+ // { value: 1, label: '1 second' },
7
+ // { value: 5, label: '5 seconds' },
8
+ // { value: 10, label: '10 seconds' },
9
+ // { value: 30, label: '30 seconds' },
10
+ // ];
18
11
 
19
12
  export const Settings = ({
20
13
  show,
@@ -27,29 +20,26 @@ export const Settings = ({
27
20
  setTheme: (theme: ThemeLabel) => void;
28
21
  theme: ThemeLabel;
29
22
  }) => {
30
- const handleChange = useCallback((selectedOption) => {
31
- setTheme(selectedOption.label);
23
+ const handleThemeChange = useCallback((event) => {
24
+ const selectedValue = event.target.value;
25
+ console.log(selectedValue);
26
+ setTheme(selectedValue);
32
27
  }, []);
33
28
 
34
- const handleSaveTimeChange = useCallback(
35
- (selectedOption) => {
36
- const time = [{ value: selectedOption.value }];
37
- fetch('/saveTime', {
38
- method: 'POST',
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- },
42
- body: JSON.stringify(time),
43
- });
44
- },
45
- [],
46
- );
47
-
48
- const usernameRef = useRef<HTMLInputElement>(null);
49
- const handleUsernameChange = () => {
50
- const usernameValue = usernameRef.current?.value;
51
- username = usernameValue;
52
- };
29
+ // TODO https://github.com/vizhub-core/vzcode/issues/95
30
+ // const handleSaveTimeChange = useCallback(
31
+ // (selectedOption) => {
32
+ // const time = [{ value: selectedOption.value }];
33
+ // fetch('/saveTime', {
34
+ // method: 'POST',
35
+ // headers: {
36
+ // 'Content-Type': 'application/json',
37
+ // },
38
+ // body: JSON.stringify(time),
39
+ // });
40
+ // },
41
+ // [],
42
+ // );
53
43
 
54
44
  return show ? (
55
45
  <Modal
@@ -66,8 +56,6 @@ export const Settings = ({
66
56
  <Form.Label>Username</Form.Label>
67
57
  <Form.Control
68
58
  type="text"
69
- ref={usernameRef}
70
- onChange={handleUsernameChange}
71
59
  placeholder="Enter username"
72
60
  />
73
61
  <Form.Text className="text-muted">
@@ -76,16 +64,26 @@ export const Settings = ({
76
64
  </Form.Group>
77
65
  <Form.Group className="mb-3" controlId="formFork">
78
66
  <Form.Label>Theme</Form.Label>
79
- <Select
80
- options={themes}
81
- onChange={handleChange}
82
- value={themeOptionsByLabel[theme]}
83
- />
67
+
68
+ <select
69
+ className="form-select"
70
+ value={theme}
71
+ onChange={handleThemeChange}
72
+ >
73
+ {themes.map(({ label }) => (
74
+ <option key={label} value={label}>
75
+ {label}
76
+ </option>
77
+ ))}
78
+ </select>
79
+
84
80
  <Form.Text className="text-muted">
85
81
  Select a color theme for the editor
86
82
  </Form.Text>
87
83
  </Form.Group>
88
- <Form.Group className="mb-3" controlId="formFork">
84
+ {/*
85
+ TODO https://github.com/vizhub-core/vzcode/issues/95
86
+ <Form.Group className="mb-3" controlId="formFork">
89
87
  <Form.Label>Auto-Save Time</Form.Label>
90
88
  <Select
91
89
  options={saveTimes}
@@ -94,7 +92,7 @@ export const Settings = ({
94
92
  <Form.Text className="text-muted">
95
93
  Select an auto save time
96
94
  </Form.Text>
97
- </Form.Group>
95
+ </Form.Group> */}
98
96
  </Modal.Body>
99
97
  <Modal.Footer>
100
98
  <Button variant="primary" onClick={onClose}>
@@ -33,10 +33,13 @@ export const DirectoryListing = ({
33
33
  toggleDirectory(path);
34
34
  }, [toggleDirectory]);
35
35
 
36
- const handleDeleteClick = useCallback(() => {
37
- // https://github.com/vizhub-core/vzcode/issues/102
38
- console.log('TODO handleDeleteDirectoryClick');
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
43
 
41
44
  const handleRenameClick = useCallback(() => {
42
45
  // https://github.com/vizhub-core/vzcode/issues/103
@@ -41,6 +41,7 @@ export const FileListing = ({
41
41
 
42
42
  return (
43
43
  <Item
44
+ name={name}
44
45
  handleClick={handleClick}
45
46
  handleDeleteClick={handleDeleteClick}
46
47
  handleRenameClick={handleRenameClick}
@@ -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;
@@ -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}