vzcode 0.20.1 → 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-12dbf516.js"></script>
32
- <link rel="stylesheet" href="/assets/index-0053b846.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.20.1",
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
  }
@@ -227,11 +227,36 @@ function App() {
227
227
  [submitOperation, closeTab],
228
228
  );
229
229
 
230
- const handleDeleteFileClick = useCallback(
231
- (fileId: FileId) => {
232
- 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
+ });
242
+ },
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
+ }
233
258
  },
234
- [deleteFile],
259
+ [deleteFile, deleteDirectory],
235
260
  );
236
261
 
237
262
  const handleSettingsClose = useCallback(() => {
@@ -275,7 +300,7 @@ function App() {
275
300
  createFile={createFile}
276
301
  files={files}
277
302
  handleRenameFileClick={handleRenameFileClick}
278
- handleDeleteFileClick={handleDeleteFileClick}
303
+ handleDeleteFileClick={handleDeleteClick}
279
304
  handleFileClick={openTab}
280
305
  setIsSettingsOpen={setIsSettingsOpen}
281
306
  isDirectoryOpen={isDirectoryOpen}
@@ -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,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,18 +1,13 @@
1
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
- ];
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
+ // ];
16
11
 
17
12
  export const Settings = ({
18
13
  show,
@@ -25,23 +20,26 @@ export const Settings = ({
25
20
  setTheme: (theme: ThemeLabel) => void;
26
21
  theme: ThemeLabel;
27
22
  }) => {
28
- const handleChange = useCallback((selectedOption) => {
29
- setTheme(selectedOption.label);
23
+ const handleThemeChange = useCallback((event) => {
24
+ const selectedValue = event.target.value;
25
+ console.log(selectedValue);
26
+ setTheme(selectedValue);
30
27
  }, []);
31
28
 
32
- const handleSaveTimeChange = useCallback(
33
- (selectedOption) => {
34
- const time = [{ value: selectedOption.value }];
35
- fetch('/saveTime', {
36
- method: 'POST',
37
- headers: {
38
- 'Content-Type': 'application/json',
39
- },
40
- body: JSON.stringify(time),
41
- });
42
- },
43
- [],
44
- );
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
+ // );
45
43
 
46
44
  return show ? (
47
45
  <Modal
@@ -66,16 +64,26 @@ export const Settings = ({
66
64
  </Form.Group>
67
65
  <Form.Group className="mb-3" controlId="formFork">
68
66
  <Form.Label>Theme</Form.Label>
69
- <Select
70
- options={themes}
71
- onChange={handleChange}
72
- value={themeOptionsByLabel[theme]}
73
- />
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
+
74
80
  <Form.Text className="text-muted">
75
81
  Select a color theme for the editor
76
82
  </Form.Text>
77
83
  </Form.Group>
78
- <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">
79
87
  <Form.Label>Auto-Save Time</Form.Label>
80
88
  <Select
81
89
  options={saveTimes}
@@ -84,7 +92,7 @@ export const Settings = ({
84
92
  <Form.Text className="text-muted">
85
93
  Select an auto save time
86
94
  </Form.Text>
87
- </Form.Group>
95
+ </Form.Group> */}
88
96
  </Modal.Body>
89
97
  <Modal.Footer>
90
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,10 +41,12 @@ 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}
47
48
  isDir={false}
49
+ name={name}
48
50
  >
49
51
  {name}
50
52
  </Item>
@@ -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}: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}