vzcode 0.80.0 → 0.82.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.
package/dist/index.html CHANGED
@@ -20,7 +20,7 @@
20
20
  href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
21
21
  rel="stylesheet"
22
22
  />
23
- <script type="module" crossorigin src="/assets/index-FsyJOC4a.js"></script>
23
+ <script type="module" crossorigin src="/assets/index-BKQfM_WG.js"></script>
24
24
  <link rel="stylesheet" crossorigin href="/assets/index-J8-kRnnE.css">
25
25
  </head>
26
26
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "0.80.0",
3
+ "version": "0.82.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -74,7 +74,7 @@
74
74
  "@codemirror/view": "^6.26.0",
75
75
  "@lezer/highlight": "^1.2.0",
76
76
  "@replit/codemirror-indentation-markers": "^6.5.1",
77
- "@replit/codemirror-interact": "^6.3.0",
77
+ "@replit/codemirror-interact": "^6.3.1",
78
78
  "@replit/codemirror-lang-svelte": "^6.0.0",
79
79
  "@replit/codemirror-vscode-keymap": "^6.0.2",
80
80
  "@teamwork/websocket-json-stream": "^2.0.0",
@@ -95,7 +95,7 @@
95
95
  "d3-array": "^3.2.4",
96
96
  "diff-match-patch": "^1.0.5",
97
97
  "dotenv": "^16.4.5",
98
- "express": "^4.19.1",
98
+ "express": "^4.19.2",
99
99
  "ignore": "^5.3.1",
100
100
  "json0-ot-diff": "^1.1.2",
101
101
  "ngrok": "^4.3.3",
@@ -112,7 +112,7 @@
112
112
  "ws": "^8.16.0"
113
113
  },
114
114
  "devDependencies": {
115
- "@types/react": "^18.2.69",
115
+ "@types/react": "^18.2.72",
116
116
  "@types/react-dom": "^18.2.22",
117
117
  "@vitejs/plugin-react": "^4.2.1",
118
118
  "concurrently": "^8.2.2",
@@ -1,12 +1,6 @@
1
1
  import { useCallback, useContext, useState } from 'react';
2
- import {
3
- FileId,
4
- ShareDBDoc,
5
- VZCodeContent,
6
- } from '../../../types';
7
2
  import { OverlayTrigger, Tooltip } from '../../bootstrap';
8
- import { EditorCache } from '../../useEditorCache';
9
- import { TabState } from '../../vzReducer';
3
+ import { VZCodeContext } from '../../VZCodeContext';
10
4
  import {
11
5
  RequestId,
12
6
  generateRequestId,
@@ -14,30 +8,24 @@ import {
14
8
  import { SparklesSVG } from '../../Icons';
15
9
  import { startAIAssist } from '../startAIAssist';
16
10
  import { Spinner } from '../Spinner';
17
- import { VZCodeContext } from '../../VZCodeContext';
18
11
  import './style.scss';
19
12
 
20
13
  const enableStopGeneration = false;
21
14
 
22
15
  export const AIAssistWidget = ({
23
- activeFileId,
24
- shareDBDoc,
25
- editorCache,
26
- tabList,
27
16
  aiAssistEndpoint,
28
17
  aiAssistOptions,
29
18
  aiAssistTooltipText = 'Start AI Assist',
30
19
  aiAssistClickOverride,
31
20
  }: {
32
- activeFileId: FileId;
33
- shareDBDoc: ShareDBDoc<VZCodeContent>;
34
- editorCache: EditorCache;
35
- tabList: Array<TabState>;
36
21
  aiAssistEndpoint: string;
37
22
  aiAssistOptions: { [key: string]: string };
38
23
  aiAssistTooltipText?: string;
39
24
  aiAssistClickOverride?: () => void;
40
25
  }) => {
26
+ const { shareDBDoc, activeFileId, tabList, editorCache } =
27
+ useContext(VZCodeContext);
28
+
41
29
  // The stream ID of the most recent request.
42
30
  // * If `null`, no request has been made yet.
43
31
  // * If non-null, a request has been made, and the
@@ -67,6 +55,17 @@ export const AIAssistWidget = ({
67
55
  aiAssistOptions,
68
56
  });
69
57
 
58
+ // Introduce a delay before running Prettier
59
+ // and the code, to give the ShareDB ops a chance
60
+ // to be applied and to propagate via WebSockets
61
+ // to the client. This is necessary because sometimes
62
+ // the ShareDB ops from the AI assist actually don't
63
+ // reach the client until AFTER the response comes back
64
+ // from `await startAIAssist`.
65
+ await new Promise((resolve) =>
66
+ setTimeout(resolve, 1000),
67
+ );
68
+
70
69
  // Trigger a Prettier run after the AI Assist.
71
70
  const runPrettier = runPrettierRef.current;
72
71
  if (runPrettier !== null) {
@@ -81,7 +80,7 @@ export const AIAssistWidget = ({
81
80
 
82
81
  // Handles the case that the user has started,
83
82
  // stopped, and started again before the first request
84
- // has finished, bu comparing the current stream ID
83
+ // has finished, by comparing the current stream ID
85
84
  // with the stream ID that was active when the request
86
85
  // was started.
87
86
  setAiStreamId((currentAIStreamId) =>
@@ -39,6 +39,7 @@ import { typeScriptCompletions } from './typeScriptCompletions';
39
39
  import { typeScriptLinter } from './typeScriptLinter';
40
40
  import { keymap } from '@codemirror/view';
41
41
  import { basicSetup } from './basicSetup';
42
+ import { InteractRule } from '@replit/codemirror-interact';
42
43
 
43
44
  // Feature flag to enable TypeScript completions & TypeScript Linter.
44
45
  const enableTypeScriptCompletions = true;
@@ -89,6 +90,8 @@ export const getOrCreateEditor = ({
89
90
  editorCache,
90
91
  usernameRef,
91
92
  typeScriptWorker,
93
+ customInteractRules,
94
+ allowGlobals,
92
95
  }: {
93
96
  fileId: FileId;
94
97
 
@@ -116,6 +119,8 @@ export const getOrCreateEditor = ({
116
119
  usernameRef: React.MutableRefObject<Username>;
117
120
  aiAssistEndpoint?: string;
118
121
  typeScriptWorker: Worker;
122
+ customInteractRules?: Array<InteractRule>;
123
+ allowGlobals: boolean;
119
124
  }): EditorCacheValue => {
120
125
  // Cache hit
121
126
  if (editorCache.has(fileId)) {
@@ -234,7 +239,9 @@ export const getOrCreateEditor = ({
234
239
  // the boolean checkboxes. The color pickers are also tricky,
235
240
  // as they would also need to be able to handle `onInteractEnd`.
236
241
  // See https://github.com/replit/codemirror-interact/issues/14
237
- extensions.push(widgets({ onInteract }));
242
+ extensions.push(
243
+ widgets({ onInteract, customInteractRules }),
244
+ );
238
245
 
239
246
  extensions.push(highlightWidgets);
240
247
 
@@ -271,6 +278,7 @@ export const getOrCreateEditor = ({
271
278
  fileName: name,
272
279
  shareDBDoc,
273
280
  fileId,
281
+ allowGlobals,
274
282
  }) as unknown as () => Diagnostic[],
275
283
  //Needs the unknown because we are returning a Promise<Diagnostic>
276
284
  ),
@@ -9,12 +9,19 @@ import { Username } from '../../types';
9
9
  import { EditorCacheValue } from '../useEditorCache';
10
10
  import { getOrCreateEditor } from './getOrCreateEditor';
11
11
  import { VZCodeContext } from '../VZCodeContext';
12
+ import { InteractRule } from '@replit/codemirror-interact';
12
13
  import './style.scss';
13
14
 
14
15
  // The path in the ShareDB document where the files live.
15
16
  const filesPath = ['files'];
16
17
 
17
- export const CodeEditor = () => {
18
+ export const CodeEditor = ({
19
+ customInteractRules,
20
+ allowGlobals,
21
+ }: {
22
+ customInteractRules?: Array<InteractRule>;
23
+ allowGlobals: boolean;
24
+ }) => {
18
25
  const {
19
26
  activeFileId,
20
27
  shareDBDoc,
@@ -79,6 +86,8 @@ export const CodeEditor = () => {
79
86
  editorCache,
80
87
  usernameRef,
81
88
  typeScriptWorker,
89
+ customInteractRules,
90
+ allowGlobals,
82
91
  }),
83
92
  [
84
93
  activeFileId,
@@ -10,6 +10,7 @@ export const typeScriptLinter = ({
10
10
  fileName,
11
11
  shareDBDoc,
12
12
  fileId,
13
+ allowGlobals,
13
14
  }) => {
14
15
  return async () => {
15
16
  const requestId = generateRequestId();
@@ -20,6 +21,7 @@ export const typeScriptLinter = ({
20
21
  fileName,
21
22
  fileContent,
22
23
  requestId,
24
+ allowGlobals,
23
25
  };
24
26
  typeScriptWorker.postMessage(linterRequest);
25
27
 
@@ -1,7 +1,11 @@
1
1
  // TODO move back to this way of importing when this PR is merged:
2
2
  // https://github.com/replit/codemirror-interact/pull/19/files
3
- // import interact from '@replit/codemirror-interact';
4
- import interact from './codemirror-interact';
3
+ import interact, {
4
+ InteractRule,
5
+ } from '@replit/codemirror-interact';
6
+ // import interact, {
7
+ // InteractRule,
8
+ // } from './codemirror-interact';
5
9
 
6
10
  import {
7
11
  ViewPlugin,
@@ -11,6 +15,9 @@ import {
11
15
  import { Extension, RangeSet } from '@codemirror/state';
12
16
  import { EditorView } from 'codemirror';
13
17
 
18
+ // Regular expression for hex colors.
19
+ const colorRegex = /#[0-9A-Fa-f]{6}/g;
20
+
14
21
  // Interactive code widgets.
15
22
  // * Number dragger
16
23
  // * Boolean toggler
@@ -21,181 +28,181 @@ import { EditorView } from 'codemirror';
21
28
  // `onInteract` is called when the user interacts with a widget.
22
29
  export const widgets = ({
23
30
  onInteract,
31
+ customInteractRules,
24
32
  }: {
25
33
  onInteract?: () => void;
26
- }) =>
27
- interact({
28
- rules: [
29
- // hex color picker
30
- // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
31
- // Extended to support single and double quotes
32
- {
33
- regexp: /["']\#([0-9]|[A-F]|[a-f]){6}["']/g,
34
- cursor: 'pointer',
35
- onClick(text, setText, e) {
36
- const res =
37
- /(?<quote>["'])(?<hex>\#([0-9]|[A-F]|[a-f]){6})\k<quote>/.exec(
38
- text,
34
+ customInteractRules?: Array<InteractRule>;
35
+ }) => {
36
+ const rules: Array<InteractRule> = [
37
+ // hex color picker
38
+ // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
39
+ // Works without quotes to support CSS.
40
+ {
41
+ regexp: colorRegex,
42
+ cursor: 'pointer',
43
+ onClick(
44
+ text: string,
45
+ setText: (newText: string) => void,
46
+ ) {
47
+ const startingColor: string = text;
48
+
49
+ const sel: HTMLInputElement =
50
+ document.createElement('input');
51
+ sel.type = 'color';
52
+ sel.value = startingColor.toLowerCase();
53
+
54
+ // valueIsUpper maintains the style of the user's code. It keeps the case of a-f the same case as the original.
55
+ const valueIsUpper: boolean =
56
+ startingColor.toUpperCase() === startingColor;
57
+
58
+ const updateHex = (e: Event) => {
59
+ const el: HTMLInputElement =
60
+ e.target as HTMLInputElement;
61
+ if (el.value) {
62
+ setText(
63
+ valueIsUpper
64
+ ? el.value.toUpperCase()
65
+ : el.value,
39
66
  );
40
- const startingColor = res.groups?.hex;
41
- const quoteType = res.groups?.quote;
42
-
43
- const sel = document.createElement('input');
44
- sel.type = 'color';
45
-
46
- sel.value = startingColor.toLowerCase();
47
-
48
- //valueIsUpper maintains style of user's code. It keeps the case of a-f the same case as the original."
49
- const valueIsUpper =
50
- startingColor.toUpperCase() === startingColor;
51
-
52
- const updateHex = (e: Event) => {
53
- const el = e.target as HTMLInputElement;
54
- if (onInteract) onInteract();
55
- if (el.value) {
56
- setText(
57
- `${quoteType}${
58
- valueIsUpper
59
- ? el.value.toUpperCase()
60
- : el.value
61
- }${quoteType}`,
62
- );
63
- }
64
- };
65
- sel.addEventListener('input', updateHex);
66
- sel.click();
67
- },
67
+ }
68
+ };
69
+ sel.addEventListener('input', updateHex);
70
+ sel.click();
68
71
  },
72
+ },
69
73
 
70
- // a rule for a number dragger
71
- {
72
- // the regexp matching the value
73
- regexp: /(?<!\#)-?\b\d+\.?\d*\b/g,
74
- // set cursor to "ew-resize" on hover
75
- cursor: 'ew-resize',
76
- // change number value based on mouse X movement on drag
77
- onDrag: (text, setText, e) => {
78
- if (onInteract) onInteract();
79
- const newVal = Number(text) + e.movementX;
80
- if (isNaN(newVal)) return;
81
- setText(newVal.toString());
82
- },
74
+ // a rule for a number dragger
75
+ {
76
+ // the regexp matching the value
77
+ regexp: /(?<!\#)-?\b\d+\.?\d*\b/g,
78
+ // set cursor to "ew-resize" on hover
79
+ cursor: 'ew-resize',
80
+ // change number value based on mouse X movement on drag
81
+ onDrag: (text, setText, e) => {
82
+ if (onInteract) onInteract();
83
+ const newVal = Number(text) + e.movementX;
84
+ if (isNaN(newVal)) return;
85
+ setText(newVal.toString());
83
86
  },
84
- // bool toggler
85
- {
86
- regexp: /true|false/g,
87
- cursor: 'pointer',
88
- onClick: (text, setText) => {
89
- if (onInteract) onInteract();
90
- switch (text) {
91
- case 'true':
92
- return setText('false');
93
- case 'false':
94
- return setText('true');
95
- }
96
- },
87
+ },
88
+ // bool toggler
89
+ {
90
+ regexp: /true|false/g,
91
+ cursor: 'pointer',
92
+ onClick: (text, setText) => {
93
+ if (onInteract) onInteract();
94
+ switch (text) {
95
+ case 'true':
96
+ return setText('false');
97
+ case 'false':
98
+ return setText('true');
99
+ }
97
100
  },
98
- // vec2 slider
99
- // Inspired by: https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L61
100
- {
101
- regexp:
102
- /vec2\(-?\b\d+\.?\d*\b\s*(,\s*-?\b\d+\.?\d*\b)?\)/g,
103
- cursor: 'move',
104
-
105
- onDrag: (text, setText, e) => {
106
- const res =
107
- /vec2\((?<x>-?\b\d+\.?\d*\b)\s*(,\s*(?<y>-?\b\d+\.?\d*\b))?\)/.exec(
108
- text,
109
- );
110
- const x = Number(res?.groups?.x);
111
- let y = Number(res?.groups?.y);
112
- if (isNaN(x)) return;
113
- if (isNaN(y)) y = x;
114
- setText(
115
- `vec2(${x + e.movementX}, ${y - e.movementY})`,
101
+ },
102
+ // vec2 slider
103
+ // Inspired by: https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L61
104
+ {
105
+ regexp:
106
+ /vec2\(-?\b\d+\.?\d*\b\s*(,\s*-?\b\d+\.?\d*\b)?\)/g,
107
+ cursor: 'move',
108
+
109
+ onDrag: (text, setText, e) => {
110
+ const res =
111
+ /vec2\((?<x>-?\b\d+\.?\d*\b)\s*(,\s*(?<y>-?\b\d+\.?\d*\b))?\)/.exec(
112
+ text,
116
113
  );
117
- },
114
+ const x = Number(res?.groups?.x);
115
+ let y = Number(res?.groups?.y);
116
+ if (isNaN(x)) return;
117
+ if (isNaN(y)) y = x;
118
+ setText(
119
+ `vec2(${x + e.movementX}, ${y - e.movementY})`,
120
+ );
118
121
  },
119
- // rgb color picker
120
- // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
121
- //TODO: create color picker for hsl colors
122
- {
123
- regexp: /rgb\(.*\)/g,
124
- cursor: 'pointer',
125
- onClick: (text, setText, e) => {
126
- const res =
127
- /rgb\((?<r>\d+)\s*,\s*(?<g>\d+)\s*,\s*(?<b>\d+)\)/.exec(
128
- text,
129
- );
130
- const r = Number(res?.groups?.r);
131
- const g = Number(res?.groups?.g);
132
- const b = Number(res?.groups?.b);
133
-
134
- //sel will open the color picker when sel.click is called.
135
- const sel = document.createElement('input');
136
- sel.type = 'color';
137
-
138
- if (!isNaN(r + g + b))
139
- sel.value = rgb2Hex(r, g, b);
140
-
141
- const updateRGB = (e: Event) => {
142
- const el = e.target as HTMLInputElement;
143
- if (onInteract) onInteract();
144
-
145
- if (el.value) {
146
- const [r, g, b] = hex2RGB(el.value);
147
- setText(`rgb(${r}, ${g}, ${b})`);
148
- }
149
- sel.removeEventListener('change', updateRGB);
150
- };
122
+ },
123
+ // rgb color picker
124
+ // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
125
+ //TODO: create color picker for hsl colors
126
+ {
127
+ regexp: /rgb\(.*\)/g,
128
+ cursor: 'pointer',
129
+ onClick: (text, setText, e) => {
130
+ const res =
131
+ /rgb\((?<r>\d+)\s*,\s*(?<g>\d+)\s*,\s*(?<b>\d+)\)/.exec(
132
+ text,
133
+ );
134
+ const r = Number(res?.groups?.r);
135
+ const g = Number(res?.groups?.g);
136
+ const b = Number(res?.groups?.b);
151
137
 
152
- sel.addEventListener('change', updateRGB);
153
- sel.click();
154
- },
138
+ //sel will open the color picker when sel.click is called.
139
+ const sel = document.createElement('input');
140
+ sel.type = 'color';
141
+
142
+ if (!isNaN(r + g + b)) sel.value = rgb2Hex(r, g, b);
143
+
144
+ const updateRGB = (e: Event) => {
145
+ const el = e.target as HTMLInputElement;
146
+ if (onInteract) onInteract();
147
+
148
+ if (el.value) {
149
+ const [r, g, b] = hex2RGB(el.value);
150
+ setText(`rgb(${r}, ${g}, ${b})`);
151
+ }
152
+ sel.removeEventListener('change', updateRGB);
153
+ };
154
+
155
+ sel.addEventListener('change', updateRGB);
156
+ sel.click();
155
157
  },
156
- // url clicker
157
- {
158
- regexp: /https?:\/\/[^ "]+/g,
159
- cursor: 'pointer',
160
- onClick: (text) => {
161
- window.open(text);
162
- },
158
+ },
159
+ // url clicker
160
+ {
161
+ regexp: /https?:\/\/[^ ")]+/g,
162
+ cursor: 'pointer',
163
+ onClick: (text) => {
164
+ window.open(text);
163
165
  },
166
+ },
164
167
 
165
- //Set rotation to the angle between the x-axis and a line from the word "rotate" to the mouse pointer (while dragging).
166
- //The rotation is in range (-180,180]
167
- {
168
- regexp: /rotate\(-?\d*\.?\d*\)/g,
169
- cursor: 'move',
170
- onDragStart(text, setText, e) {
171
- rotationOrigin = { x: e.clientX, y: e.clientY };
172
- },
168
+ //Set rotation to the angle between the x-axis and a line from the word "rotate" to the mouse pointer (while dragging).
169
+ //The rotation is in range (-180,180]
170
+ {
171
+ regexp: /rotate\(-?\d*\.?\d*\)/g,
172
+ cursor: 'move',
173
+ onDragStart(text, setText, e) {
174
+ rotationOrigin = { x: e.clientX, y: e.clientY };
175
+ },
173
176
 
174
- onDrag(text, setText, e) {
175
- if (rotationOrigin == null) return;
176
- const rotationDegree = Math.round(
177
- (Math.atan2(
178
- rotationOrigin.y - e.clientY,
179
- e.clientX - rotationOrigin.x,
180
- ) *
181
- 180) /
182
- Math.PI,
183
- );
184
- //Calculate the angle between the x axis and a line from where the user first clicks to the current location of the mouse.
185
- setText(`rotate(${rotationDegree})`);
186
- const updateDragEvent = new CustomEvent(
187
- 'updateRotateDrag',
188
- { detail: rotationDegree },
189
- );
177
+ onDrag(text, setText, e) {
178
+ if (rotationOrigin == null) return;
179
+ const rotationDegree = Math.round(
180
+ (Math.atan2(
181
+ rotationOrigin.y - e.clientY,
182
+ e.clientX - rotationOrigin.x,
183
+ ) *
184
+ 180) /
185
+ Math.PI,
186
+ );
187
+ //Calculate the angle between the x axis and a line from where the user first clicks to the current location of the mouse.
188
+ setText(`rotate(${rotationDegree})`);
189
+ const updateDragEvent = new CustomEvent(
190
+ 'updateRotateDrag',
191
+ { detail: rotationDegree },
192
+ );
190
193
 
191
- document.dispatchEvent(updateDragEvent);
192
- },
193
- onDragEnd(text, setText) {
194
- rotationOrigin = null;
195
- },
194
+ document.dispatchEvent(updateDragEvent);
195
+ },
196
+ onDragEnd() {
197
+ rotationOrigin = null;
196
198
  },
197
- ],
198
- });
199
+ },
200
+ ];
201
+ if (customInteractRules) {
202
+ rules.push(...customInteractRules);
203
+ }
204
+ return interact({ rules });
205
+ };
199
206
 
200
207
  let rotationOrigin: { x: number; y: number } = null;
201
208
 
@@ -244,7 +251,8 @@ export const colorsInTextPlugin: Extension = [
244
251
  }
245
252
  const hexColorOccurences = line.value.matchAll(
246
253
  // /\"\#([0-9]|[A-F]|[a-f]){6}\"/g,
247
- /["']\#([0-9]|[A-F]|[a-f]){6}["']/g,
254
+ // /["']\#([0-9]|[A-F]|[a-f]){6}["']/g,
255
+ colorRegex,
248
256
  );
249
257
  let hexOccurance = hexColorOccurences.next();
250
258
  while (!hexOccurance.done) {
@@ -260,9 +268,9 @@ export const colorsInTextPlugin: Extension = [
260
268
  return Decoration.set(
261
269
  colorInfos.map((colorInfo) => {
262
270
  return {
263
- // 9 is the length of the hex color string including quotation marks
264
- from: colorInfo.index + 9,
265
- to: colorInfo.index + 9,
271
+ // 7 is the length of the hex color string
272
+ from: colorInfo.index + 7,
273
+ to: colorInfo.index + 7,
266
274
  value: Decoration.widget({
267
275
  side: -1,
268
276
  widget: new ColorWidget(colorInfo[0]),
@@ -288,10 +296,10 @@ class ColorWidget extends WidgetType {
288
296
  return widget.color === this.color;
289
297
  }
290
298
 
291
- toDOM(view: EditorView): HTMLElement {
299
+ toDOM(): HTMLElement {
292
300
  const parent = document.createElement('div');
293
301
  const size = 20;
294
- const innerSize = 16;
302
+ const innerSize = 14;
295
303
 
296
304
  parent.setAttribute(
297
305
  'style',
@@ -311,7 +319,7 @@ class ColorWidget extends WidgetType {
311
319
  'fill',
312
320
  this.color.replace(/["']/g, ''),
313
321
  );
314
- colorCircle.setAttributeNS(null, 'stroke', 'black');
322
+ // colorCircle.setAttributeNS(null, 'stroke', 'black');
315
323
  colorCircle.setAttributeNS(
316
324
  null,
317
325
  'r',
@@ -1,26 +1,20 @@
1
- import type { TabState } from '../vzReducer';
2
- import type { FileId, Files } from '../../types';
1
+ import { useContext } from 'react';
2
+ import { VZCodeContext } from '../VZCodeContext';
3
+ import { TabState } from '../vzReducer';
3
4
  import { Tab } from './Tab';
4
5
  import './style.scss';
5
6
 
6
7
  // Displays the list of tabs above the code editor.
7
- export const TabList = ({
8
- files,
9
- tabList,
10
- activeFileId,
11
- setActiveFileId,
12
- openTab,
13
- closeTabs,
14
- createFile,
15
- }: {
16
- files: Files;
17
- tabList: Array<TabState>;
18
- activeFileId: FileId;
19
- setActiveFileId: (fileId: FileId) => void;
20
- openTab: (tabState: TabState) => void;
21
- closeTabs: (fileIds: FileId[]) => void;
22
- createFile: (fileName: string) => void;
23
- }) => {
8
+ export const TabList = () => {
9
+ const {
10
+ files,
11
+ activeFileId,
12
+ setActiveFileId,
13
+ tabList,
14
+ openTab,
15
+ closeTabs,
16
+ } = useContext(VZCodeContext);
17
+
24
18
  return (
25
19
  <div className="vz-tab-list">
26
20
  {files &&