vzcode 1.15.0 → 1.17.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/assets/index-BQgRA0SD.js +181 -0
- package/dist/assets/index-CSSpCKM0.js +447 -0
- package/dist/assets/index-zzK6rRiK.css +1 -0
- package/dist/assets/worker-8af7bTHW.js +293 -0
- package/dist/index.html +2 -2
- package/package.json +40 -34
- package/src/client/AIAssist/AIAssistWidget/index.tsx +10 -6
- package/src/client/App/index.tsx +52 -20
- package/src/client/App/usePending.ts +50 -0
- package/src/client/App/useShareDB.ts +40 -1
- package/src/client/CodeEditor/Copilot/index.ts +28 -0
- package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +129 -33
- package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +36 -1
- package/src/client/CodeEditor/getOrCreateEditor.ts +43 -6
- package/src/client/CodeEditor/index.tsx +7 -4
- package/src/client/CodeEditor/json1PresenceDisplay.ts +5 -5
- package/src/client/CodeEditor/rainbowBrackets.ts +14 -2
- package/src/client/CodeEditor/style.scss +106 -1
- package/src/client/Icons/MicSVG.tsx +25 -0
- package/src/client/RunCodeWidget/index.tsx +20 -3
- package/src/client/TabList/index.tsx +8 -11
- package/src/client/VZCodeContext.tsx +66 -24
- package/src/client/VZKeyboardShortcutsDoc.tsx +8 -8
- package/src/client/VZLeft.tsx +4 -2
- package/src/client/VZMiddle.tsx +86 -27
- package/src/client/VZResizer/index.tsx +3 -3
- package/src/client/VZSettings.tsx +54 -3
- package/src/client/VZSidebar/FileListing.tsx +20 -6
- package/src/client/VZSidebar/Listing.tsx +2 -2
- package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
- package/src/client/VZSidebar/index.tsx +80 -15
- package/src/client/VZSidebar/styles.scss +6 -2
- package/src/client/presenceColor.ts +8 -0
- package/src/client/useTypeScript/worker/constants.ts +6 -0
- package/src/server/generateAIResponse.js +79 -13
- package/src/server/handleAICopilot.js +77 -0
- package/src/server/index.js +23 -8
- package/src/server/livekit.js +24 -0
- package/src/server/setupEnv.js +1 -1
- package/src/types.ts +1 -0
- package/dist/assets/index-Bm65AzMP.js +0 -425
- package/dist/assets/index-DbFdL1A1.js +0 -159
- package/dist/assets/index-Yz-ZHc0O.css +0 -1
- package/dist/assets/worker-CJWrLBN2.js +0 -286
|
@@ -32,7 +32,8 @@ class ColorWidget extends WidgetType {
|
|
|
32
32
|
|
|
33
33
|
parent.setAttribute(
|
|
34
34
|
'style',
|
|
35
|
-
`width:${size}px;height:${size}px
|
|
35
|
+
`width:${size}px;height:${size}px;position:relative;`,
|
|
36
|
+
// Position relative for the hovering tool tip to work
|
|
36
37
|
);
|
|
37
38
|
parent.className = 'color-circle-parent';
|
|
38
39
|
const svg = document.createElementNS(
|
|
@@ -72,8 +73,42 @@ class ColorWidget extends WidgetType {
|
|
|
72
73
|
svg.appendChild(colorCircle);
|
|
73
74
|
|
|
74
75
|
parent.appendChild(svg);
|
|
76
|
+
|
|
77
|
+
// Hovering tool tip shortcut for when users hover over color circle
|
|
78
|
+
const hoveringTooltip = document.createElement('div');
|
|
79
|
+
|
|
80
|
+
// Set class for tooltip to use CSS styles
|
|
81
|
+
hoveringTooltip.className = 'tooltip_hover_circle';
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
hoveringTooltip.innerHTML =
|
|
85
|
+
`<strong>Alt + Click on a hex color</strong><br />
|
|
86
|
+
<div>Open a color picker to modify the color</div>`;
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
parent.appendChild(hoveringTooltip);
|
|
90
|
+
|
|
91
|
+
// Timer to prevent constant pop-ups when scrolling through code
|
|
92
|
+
let hoverTimeout: number;
|
|
93
|
+
|
|
94
|
+
// Fade in and fade out effect for when user hovers over the circle
|
|
95
|
+
// Mouse Event to set y and x of the tool tip pop up to prevent overlap onto circle
|
|
96
|
+
parent.addEventListener('mouseenter', (event: MouseEvent) => {
|
|
97
|
+
hoverTimeout = window.setTimeout(() => {
|
|
98
|
+
hoveringTooltip.style.top = `${event.clientY - 60}px`;
|
|
99
|
+
hoveringTooltip.style.left = `${event.clientX}px`;
|
|
100
|
+
hoveringTooltip.style.opacity = '1';
|
|
101
|
+
}, 700);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
parent.addEventListener('mouseleave', () => {
|
|
105
|
+
clearTimeout(hoverTimeout);
|
|
106
|
+
hoveringTooltip.style.opacity = '0';
|
|
107
|
+
});
|
|
108
|
+
|
|
75
109
|
return parent;
|
|
76
110
|
}
|
|
111
|
+
|
|
77
112
|
ignoreEvent() {
|
|
78
113
|
return false;
|
|
79
114
|
}
|
|
@@ -10,11 +10,13 @@ import { markdown } from '@codemirror/lang-markdown';
|
|
|
10
10
|
import { html } from '@codemirror/lang-html';
|
|
11
11
|
import { css } from '@codemirror/lang-css';
|
|
12
12
|
import { json1Sync } from 'codemirror-ot';
|
|
13
|
+
|
|
13
14
|
import { autocompletion } from '@codemirror/autocomplete';
|
|
14
15
|
import { indentationMarkers } from '@replit/codemirror-indentation-markers';
|
|
15
16
|
// import { showMinimap } from '@replit/codemirror-minimap';
|
|
16
17
|
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
|
17
18
|
import { Diagnostic, linter } from '@codemirror/lint';
|
|
19
|
+
|
|
18
20
|
import { json1Presence, textUnicode } from '../../ot';
|
|
19
21
|
import {
|
|
20
22
|
FileId,
|
|
@@ -45,11 +47,14 @@ import { InteractRule } from '@replit/codemirror-interact';
|
|
|
45
47
|
import rainbowBrackets from '../CodeEditor/rainbowBrackets';
|
|
46
48
|
import { cssLanguage } from '@codemirror/lang-css';
|
|
47
49
|
import { javascriptLanguage } from '@codemirror/lang-javascript';
|
|
50
|
+
import { copilot } from './Copilot';
|
|
48
51
|
|
|
49
52
|
// Feature flag to enable TypeScript completions & TypeScript Linter.
|
|
50
|
-
const enableTypeScriptCompletions = true;
|
|
51
53
|
const enableTypeScriptLinter = true;
|
|
52
54
|
|
|
55
|
+
// Feature flag to enable AI copilot
|
|
56
|
+
const enableCopilot = true;
|
|
57
|
+
|
|
53
58
|
// Enables TypeScript +JSX support in CodeMirror.
|
|
54
59
|
const tsx = () =>
|
|
55
60
|
javascript({ jsx: true, typescript: true });
|
|
@@ -74,6 +79,7 @@ const htmlConfig = {
|
|
|
74
79
|
],
|
|
75
80
|
nestedAttributes: [],
|
|
76
81
|
};
|
|
82
|
+
|
|
77
83
|
// Language extensions for CodeMirror.
|
|
78
84
|
// Keys are file extensions.
|
|
79
85
|
// Values are CodeMirror extensions.
|
|
@@ -101,6 +107,13 @@ const getAtPath = (obj, path) => {
|
|
|
101
107
|
return current;
|
|
102
108
|
};
|
|
103
109
|
|
|
110
|
+
// Extend the EditorCacheValue type to include the compartments and the updateRainbowBrackets method
|
|
111
|
+
interface ExtendedEditorCacheValue extends EditorCacheValue {
|
|
112
|
+
themeCompartment: Compartment;
|
|
113
|
+
rainbowBracketsCompartment: Compartment;
|
|
114
|
+
updateRainbowBrackets: (enabled: boolean) => void;
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
// Gets or creates an `editorCache` entry for the given file id.
|
|
105
118
|
// Looks in `editorCache` first, and if not found, creates a new editor.
|
|
106
119
|
export const getOrCreateEditor = ({
|
|
@@ -120,6 +133,8 @@ export const getOrCreateEditor = ({
|
|
|
120
133
|
allowGlobals,
|
|
121
134
|
enableAutoFollowRef,
|
|
122
135
|
openTab,
|
|
136
|
+
aiCopilotEndpoint,
|
|
137
|
+
rainbowBracketsEnabled = true, // New parameter to enable or disable Rainbow Brackets
|
|
123
138
|
}: {
|
|
124
139
|
// TODO pass this in from the outside
|
|
125
140
|
paneId?: PaneId;
|
|
@@ -156,13 +171,15 @@ export const getOrCreateEditor = ({
|
|
|
156
171
|
// enable auto-following the cursors of remote users.
|
|
157
172
|
enableAutoFollowRef: React.MutableRefObject<boolean>;
|
|
158
173
|
openTab: (tabState: TabState) => void;
|
|
159
|
-
|
|
174
|
+
aiCopilotEndpoint: string;
|
|
175
|
+
rainbowBracketsEnabled?: boolean; // New parameter type
|
|
176
|
+
}): ExtendedEditorCacheValue => {
|
|
160
177
|
// Cache hit
|
|
161
178
|
|
|
162
179
|
const cacheKey = editorCacheKey(fileId, paneId);
|
|
163
180
|
|
|
164
181
|
if (editorCache.has(cacheKey)) {
|
|
165
|
-
return editorCache.get(cacheKey);
|
|
182
|
+
return editorCache.get(cacheKey) as ExtendedEditorCacheValue;
|
|
166
183
|
}
|
|
167
184
|
|
|
168
185
|
// Cache miss
|
|
@@ -177,6 +194,9 @@ export const getOrCreateEditor = ({
|
|
|
177
194
|
// Create a compartment for the theme so that it can be changed dynamically.
|
|
178
195
|
// Inspired by: https://github.com/craftzdog/cm6-themes/blob/main/example/index.ts
|
|
179
196
|
let themeCompartment = new Compartment();
|
|
197
|
+
|
|
198
|
+
// Create a compartment for rainbow brackets so that it can be enabled/disabled dynamically.
|
|
199
|
+
let rainbowBracketsCompartment = new Compartment();
|
|
180
200
|
|
|
181
201
|
// The CodeMirror extensions to use.
|
|
182
202
|
// const extensions = [autocompletion(), html(htmlConfig)]
|
|
@@ -237,6 +257,13 @@ export const getOrCreateEditor = ({
|
|
|
237
257
|
themeCompartment.of(themeOptionsByLabel[theme].value),
|
|
238
258
|
);
|
|
239
259
|
|
|
260
|
+
// Adds compartment for rainbow brackets with initial toggle state.
|
|
261
|
+
extensions.push(
|
|
262
|
+
rainbowBracketsCompartment.of(
|
|
263
|
+
rainbowBracketsEnabled ? rainbowBrackets() : []
|
|
264
|
+
),
|
|
265
|
+
);
|
|
266
|
+
|
|
240
267
|
// TODO handle dynamic changing of the file extension.
|
|
241
268
|
// TODO handle dynamic file extensions by making
|
|
242
269
|
// the CodeMirror language extension dynamic
|
|
@@ -380,8 +407,11 @@ export const getOrCreateEditor = ({
|
|
|
380
407
|
}),
|
|
381
408
|
),
|
|
382
409
|
);
|
|
383
|
-
|
|
384
|
-
|
|
410
|
+
|
|
411
|
+
// adds copilot
|
|
412
|
+
if (enableCopilot) {
|
|
413
|
+
extensions.push(copilot({ aiCopilotEndpoint }));
|
|
414
|
+
}
|
|
385
415
|
|
|
386
416
|
const editor = new EditorView({
|
|
387
417
|
state: EditorState.create({
|
|
@@ -390,10 +420,17 @@ export const getOrCreateEditor = ({
|
|
|
390
420
|
}),
|
|
391
421
|
});
|
|
392
422
|
|
|
393
|
-
const editorCacheValue = { editor, themeCompartment
|
|
423
|
+
const editorCacheValue: ExtendedEditorCacheValue = { editor, themeCompartment, rainbowBracketsCompartment, updateRainbowBrackets: (enabled) => {
|
|
424
|
+
editor.dispatch({
|
|
425
|
+
effects: rainbowBracketsCompartment.reconfigure(enabled ? rainbowBrackets() : []),
|
|
426
|
+
});
|
|
427
|
+
}};
|
|
394
428
|
|
|
395
429
|
// Populate the cache.
|
|
396
430
|
editorCache.set(cacheKey, editorCacheValue);
|
|
397
431
|
|
|
432
|
+
// Initialize rainbow brackets based on the toggle state
|
|
433
|
+
editorCacheValue.updateRainbowBrackets(rainbowBracketsEnabled);
|
|
434
|
+
|
|
398
435
|
return editorCacheValue;
|
|
399
436
|
};
|
|
@@ -19,12 +19,14 @@ const filesPath = ['files'];
|
|
|
19
19
|
export const CodeEditor = ({
|
|
20
20
|
customInteractRules,
|
|
21
21
|
allowGlobals,
|
|
22
|
+
aiCopilotEndpoint,
|
|
22
23
|
}: {
|
|
23
24
|
customInteractRules?: Array<InteractRule>;
|
|
24
25
|
allowGlobals: boolean;
|
|
26
|
+
aiCopilotEndpoint: string;
|
|
25
27
|
}) => {
|
|
26
28
|
const {
|
|
27
|
-
|
|
29
|
+
activePane,
|
|
28
30
|
shareDBDoc,
|
|
29
31
|
content,
|
|
30
32
|
submitOperation,
|
|
@@ -84,7 +86,7 @@ export const CodeEditor = ({
|
|
|
84
86
|
const editorCacheValue: EditorCacheValue = useMemo(
|
|
85
87
|
() =>
|
|
86
88
|
getOrCreateEditor({
|
|
87
|
-
fileId: activeFileId,
|
|
89
|
+
fileId: activePane.activeFileId,
|
|
88
90
|
shareDBDoc,
|
|
89
91
|
content,
|
|
90
92
|
filesPath,
|
|
@@ -99,10 +101,10 @@ export const CodeEditor = ({
|
|
|
99
101
|
allowGlobals,
|
|
100
102
|
enableAutoFollowRef,
|
|
101
103
|
openTab,
|
|
102
|
-
|
|
104
|
+
aiCopilotEndpoint,
|
|
103
105
|
}),
|
|
104
106
|
[
|
|
105
|
-
|
|
107
|
+
activePane,
|
|
106
108
|
shareDBDoc,
|
|
107
109
|
filesPath,
|
|
108
110
|
localPresence,
|
|
@@ -112,6 +114,7 @@ export const CodeEditor = ({
|
|
|
112
114
|
editorCache,
|
|
113
115
|
usernameRef,
|
|
114
116
|
typeScriptWorker,
|
|
117
|
+
aiCopilotEndpoint,
|
|
115
118
|
],
|
|
116
119
|
);
|
|
117
120
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
Decoration,
|
|
6
6
|
} from '@codemirror/view';
|
|
7
7
|
import { Annotation, RangeSet } from '@codemirror/state';
|
|
8
|
-
import
|
|
8
|
+
import { assignUserColor } from '../presenceColor';
|
|
9
9
|
import {
|
|
10
10
|
FileId,
|
|
11
11
|
Presence,
|
|
@@ -114,11 +114,11 @@ export const json1PresenceDisplay = ({
|
|
|
114
114
|
const { start, end } = presence;
|
|
115
115
|
const from = +start[start.length - 1];
|
|
116
116
|
const to = +end[end.length - 1];
|
|
117
|
-
const userColor =
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
const userColor = assignUserColor(
|
|
118
|
+
presence.username,
|
|
119
|
+
);
|
|
120
120
|
const { username } = presence;
|
|
121
|
-
|
|
121
|
+
//console.log("File User Color:" + assignUserColor(presence.username));
|
|
122
122
|
presenceDecorations.push({
|
|
123
123
|
from,
|
|
124
124
|
to: from,
|
|
@@ -8,6 +8,12 @@ function generateColors() {
|
|
|
8
8
|
return ['red', 'orange', 'yellow', 'green'];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
let isRainbowEnabled = true;
|
|
12
|
+
|
|
13
|
+
export const toggleRainbowBrackets = (enabled: boolean) => {
|
|
14
|
+
isRainbowEnabled = enabled;
|
|
15
|
+
};
|
|
16
|
+
|
|
11
17
|
const rainbowBracketsPlugin = ViewPlugin.fromClass(
|
|
12
18
|
class {
|
|
13
19
|
decorations;
|
|
@@ -50,12 +56,16 @@ const rainbowBracketsPlugin = ViewPlugin.fromClass(
|
|
|
50
56
|
) {
|
|
51
57
|
const color =
|
|
52
58
|
colors[stack.length % colors.length];
|
|
59
|
+
const bracketClass = isRainbowEnabled
|
|
60
|
+
? `rainbow-bracket-${color}`
|
|
61
|
+
: `default-bracket`;
|
|
62
|
+
|
|
53
63
|
decorations.push(
|
|
54
64
|
Decoration.mark({
|
|
55
|
-
class:
|
|
65
|
+
class: bracketClass,
|
|
56
66
|
}).range(open.from, open.from + 1),
|
|
57
67
|
Decoration.mark({
|
|
58
|
-
class:
|
|
68
|
+
class: bracketClass,
|
|
59
69
|
}).range(pos, pos + 1),
|
|
60
70
|
);
|
|
61
71
|
}
|
|
@@ -105,6 +115,8 @@ export default function rainbowBrackets() {
|
|
|
105
115
|
},
|
|
106
116
|
'.rainbow-bracket-green': { color: '#B2FFB2' },
|
|
107
117
|
'.rainbow-bracket-green > span': { color: '#B2FFB2' },
|
|
118
|
+
'.default-bracket': { color: 'currentColor' },
|
|
119
|
+
'.default-bracket > span': { color: 'currentColor' },
|
|
108
120
|
}),
|
|
109
121
|
];
|
|
110
122
|
}
|
|
@@ -48,4 +48,109 @@
|
|
|
48
48
|
.cm-line {
|
|
49
49
|
font-family: var(--vzcode-font-family);
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
|
|
52
|
+
.tooltip_hover_circle {
|
|
53
|
+
color: white;
|
|
54
|
+
text-align: center;
|
|
55
|
+
position: fixed;
|
|
56
|
+
background-color: black;
|
|
57
|
+
padding: 5px;
|
|
58
|
+
border-radius: 5px;
|
|
59
|
+
white-space: nowrap;
|
|
60
|
+
opacity: 0;
|
|
61
|
+
transition: opacity 0.3s ease;
|
|
62
|
+
z-index: 1000;
|
|
63
|
+
pointer-events: none;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.tooltip_hover_circle strong {
|
|
67
|
+
font-size: 14px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.tooltip_hover_circle div {
|
|
71
|
+
font-size: 12px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.cm-panel.cm-search {
|
|
75
|
+
padding: 5px;
|
|
76
|
+
position: relative;
|
|
77
|
+
background-color: var(--vh-color-neutral-01);
|
|
78
|
+
border-radius: 6px;
|
|
79
|
+
color: var(--vh-color-neutral-04);
|
|
80
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.cm-panels {
|
|
84
|
+
background-color: #333338;
|
|
85
|
+
color: var(--vh-color-neutral-04);
|
|
86
|
+
border: 2px solid var(--vh-color-neutral-02);
|
|
87
|
+
border-radius: 6px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.cm-textfield {
|
|
91
|
+
background-color: var(--vh-color-neutral-01);
|
|
92
|
+
color: #efe7dc;
|
|
93
|
+
border: 1px solid var(--vh-color-neutral-04);
|
|
94
|
+
padding: 8px;
|
|
95
|
+
border-radius: 6px;
|
|
96
|
+
font-size: 14px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.cm-button {
|
|
100
|
+
background-color: var(--vh-color-neutral-01);
|
|
101
|
+
border: 1px solid #193350;
|
|
102
|
+
color: var(--vh-color-neutral-04);
|
|
103
|
+
border: 1px solid var(--vh-color-neutral-04);
|
|
104
|
+
border-radius: 5px;
|
|
105
|
+
padding: 6px 12px;
|
|
106
|
+
font-weight: bold;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
transition: all 0.3s ease-in-out;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.cm-button:hover {
|
|
112
|
+
background-color: #50c878;
|
|
113
|
+
color: #ffffff;
|
|
114
|
+
transform: scale(1.1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.cm-button:active {
|
|
118
|
+
background-color: #202e46;
|
|
119
|
+
transform: scale(0.95);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.cm-searchMatch {
|
|
123
|
+
background-color: rgba(255, 255, 0, 0.5);
|
|
124
|
+
border-radius: 2px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.cm-searchMatch-selected {
|
|
128
|
+
background-color: rgba(255, 140, 0, 0.5);
|
|
129
|
+
border: 2px solid #ff8800;
|
|
130
|
+
border-radius: 2px;
|
|
131
|
+
}
|
|
132
|
+
.cm-panel.cm-search [name="close"] {
|
|
133
|
+
position: absolute;
|
|
134
|
+
top: 0;
|
|
135
|
+
right: 4px;
|
|
136
|
+
background-color: var(--vh-color-neutral-01);
|
|
137
|
+
color: var(--vh-color-neutral-04);
|
|
138
|
+
border: none;
|
|
139
|
+
font: inherit;
|
|
140
|
+
padding: 4px 8px;
|
|
141
|
+
margin: 4px;
|
|
142
|
+
border-radius: 4px;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
transition: all 0.3s ease-in-out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.cm-panel.cm-search [name="close"]:hover {
|
|
148
|
+
background-color: var(--vh-color-caution-01);
|
|
149
|
+
color: #ffffff;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.cm-panel.cm-search [name="close"]:active {
|
|
153
|
+
background-color: var(--vh-color-caution-02);
|
|
154
|
+
transform: scale(0.95);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const MicSVG = () => (
|
|
2
|
+
<svg
|
|
3
|
+
fill="#000000"
|
|
4
|
+
height="24"
|
|
5
|
+
width="24"
|
|
6
|
+
stroke="currentColor"
|
|
7
|
+
version="1.1"
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 512 512"
|
|
10
|
+
enable-background="new 0 0 512 512"
|
|
11
|
+
>
|
|
12
|
+
<g>
|
|
13
|
+
<g>
|
|
14
|
+
<path
|
|
15
|
+
fill="currentColor"
|
|
16
|
+
d="m439.5,236c0-11.3-9.1-20.4-20.4-20.4s-20.4,9.1-20.4,20.4c0,70-64,126.9-142.7,126.9-78.7,0-142.7-56.9-142.7-126.9 0-11.3-9.1-20.4-20.4-20.4s-20.4,9.1-20.4,20.4c0,86.2 71.5,157.4 163.1,166.7v57.5h-23.6c-11.3,0-20.4,9.1-20.4,20.4 0,11.3 9.1,20.4 20.4,20.4h88c11.3,0 20.4-9.1 20.4-20.4 0-11.3-9.1-20.4-20.4-20.4h-23.6v-57.5c91.6-9.3 163.1-80.5 163.1-166.7z"
|
|
17
|
+
/>
|
|
18
|
+
<path
|
|
19
|
+
fill="currentColor"
|
|
20
|
+
d="m256,323.5c51,0 92.3-41.3 92.3-92.3v-127.9c0-51-41.3-92.3-92.3-92.3s-92.3,41.3-92.3,92.3v127.9c0,51 41.3,92.3 92.3,92.3zm-52.3-220.2c0-28.8 23.5-52.3 52.3-52.3s52.3,23.5 52.3,52.3v127.9c0,28.8-23.5,52.3-52.3,52.3s-52.3-23.5-52.3-52.3v-127.9z"
|
|
21
|
+
/>
|
|
22
|
+
</g>
|
|
23
|
+
</g>
|
|
24
|
+
</svg>
|
|
25
|
+
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useContext, useState } from 'react';
|
|
1
|
+
import { useCallback, useContext, useEffect, useState } from 'react';
|
|
2
2
|
import { PlaySVG } from '../Icons';
|
|
3
3
|
import { SplitEditorSVG } from '../Icons/SplitEditorSVG';
|
|
4
4
|
import { VZCodeContext } from '../VZCodeContext';
|
|
@@ -17,7 +17,7 @@ export const RunCodeWidget = ({
|
|
|
17
17
|
</>
|
|
18
18
|
),
|
|
19
19
|
}: {
|
|
20
|
-
|
|
20
|
+
runCodeWidgetTooltipText?: JSX.Element;
|
|
21
21
|
}) => {
|
|
22
22
|
const { runCodeRef, runPrettierRef, splitCurrentPane } =
|
|
23
23
|
useContext(VZCodeContext);
|
|
@@ -40,7 +40,7 @@ export const RunCodeWidget = ({
|
|
|
40
40
|
|
|
41
41
|
// Optional: reset the icon state after animation completes (e.g., 1 second)
|
|
42
42
|
setTimeout(() => setIsRunning(false), 1000);
|
|
43
|
-
}, []);
|
|
43
|
+
}, [runCodeRef, runPrettierRef]);
|
|
44
44
|
|
|
45
45
|
const handleSplitEditor = useCallback(() => {
|
|
46
46
|
console.log('Split Editor');
|
|
@@ -48,6 +48,23 @@ export const RunCodeWidget = ({
|
|
|
48
48
|
splitCurrentPane();
|
|
49
49
|
}, [splitCurrentPane]);
|
|
50
50
|
|
|
51
|
+
// Add the keyboard event listener for Ctrl+S and Shift+Enter
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
54
|
+
if ((event.ctrlKey && event.key === 's') || (event.shiftKey && event.key === 'Enter')) {
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
handleClick();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
61
|
+
|
|
62
|
+
// Cleanup the event listener on component unmount
|
|
63
|
+
return () => {
|
|
64
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
65
|
+
};
|
|
66
|
+
}, [handleClick]);
|
|
67
|
+
|
|
51
68
|
return (
|
|
52
69
|
<div>
|
|
53
70
|
<div className="vz-code-split-view-widget">
|
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
import { useContext } from 'react';
|
|
2
|
+
import { TabState } from '../../types';
|
|
2
3
|
import { VZCodeContext } from '../VZCodeContext';
|
|
3
4
|
import { Tab } from './Tab';
|
|
4
5
|
import './style.scss';
|
|
5
|
-
import { TabState } from '../../types';
|
|
6
6
|
|
|
7
7
|
// Displays the list of tabs above the code editor.
|
|
8
|
-
export const TabList = () => {
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
activeFileId,
|
|
12
|
-
setActiveFileId,
|
|
13
|
-
tabList,
|
|
14
|
-
openTab,
|
|
15
|
-
closeTabs,
|
|
16
|
-
} = useContext(VZCodeContext);
|
|
8
|
+
export const TabList = ({ activeFileId, tabList }) => {
|
|
9
|
+
const { files, setActiveFileId, openTab, closeTabs } =
|
|
10
|
+
useContext(VZCodeContext);
|
|
17
11
|
|
|
18
12
|
return (
|
|
19
13
|
<div className="vz-tab-list">
|
|
20
14
|
{files &&
|
|
21
15
|
tabList
|
|
22
16
|
// Handles the case that the tab list references a file that was deleted.
|
|
23
|
-
.filter(
|
|
17
|
+
.filter(
|
|
18
|
+
(tabState: TabState) =>
|
|
19
|
+
tabState.fileId in files,
|
|
20
|
+
)
|
|
24
21
|
.map((tabState: TabState) => (
|
|
25
22
|
<Tab
|
|
26
23
|
key={tabState.fileId}
|