vzcode 0.79.0 → 0.81.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-DZXcjW4O.js → index-CnwdGSLW.js} +55 -55
- package/dist/assets/{index-WtPPom-d.css → index-J8-kRnnE.css} +1 -1
- package/dist/assets/{worker-Bp-D5zJz.js → worker-tyOiPqgH.js} +53 -53
- package/dist/index.html +2 -2
- package/package.json +13 -13
- package/src/client/AIAssist/AIAssistWidget/index.tsx +4 -16
- package/src/client/CodeEditor/getOrCreateEditor.ts +6 -1
- package/src/client/CodeEditor/index.tsx +7 -1
- package/src/client/CodeEditor/widgets.ts +173 -167
- package/src/client/TabList/index.tsx +13 -19
- package/src/client/VZCodeContext.tsx +12 -0
- package/src/client/VZMiddle.tsx +7 -29
- package/src/client/VZSidebar/DirectoryListing.tsx +12 -32
- package/src/client/VZSidebar/FileListing.tsx +6 -5
- package/src/client/VZSidebar/Item.tsx +30 -10
- package/src/client/VZSidebar/Listing.tsx +6 -30
- package/src/client/VZSidebar/index.tsx +86 -91
- package/src/client/VZSidebar/styles.scss +10 -18
- package/src/client/useFileCRUD.ts +1 -1
- package/src/client/useTypeScript/worker.ts +11 -1
- package/src/types.ts +5 -1
package/dist/index.html
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
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-
|
|
24
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
23
|
+
<script type="module" crossorigin src="/assets/index-CnwdGSLW.js"></script>
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-J8-kRnnE.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
|
27
27
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vzcode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.81.0",
|
|
4
4
|
"description": "Multiplayer code editor system",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -79,15 +79,15 @@
|
|
|
79
79
|
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
|
80
80
|
"@teamwork/websocket-json-stream": "^2.0.0",
|
|
81
81
|
"@typescript/vfs": "^1.5.0",
|
|
82
|
-
"@uiw/codemirror-theme-abcdef": "^4.21.
|
|
83
|
-
"@uiw/codemirror-theme-dracula": "^4.21.
|
|
84
|
-
"@uiw/codemirror-theme-eclipse": "^4.21.
|
|
85
|
-
"@uiw/codemirror-theme-github": "^4.21.
|
|
86
|
-
"@uiw/codemirror-theme-material": "^4.21.
|
|
87
|
-
"@uiw/codemirror-theme-nord": "^4.21.
|
|
88
|
-
"@uiw/codemirror-theme-okaidia": "^4.21.
|
|
89
|
-
"@uiw/codemirror-theme-xcode": "^4.21.
|
|
90
|
-
"@uiw/codemirror-themes": "^4.21.
|
|
82
|
+
"@uiw/codemirror-theme-abcdef": "^4.21.25",
|
|
83
|
+
"@uiw/codemirror-theme-dracula": "^4.21.25",
|
|
84
|
+
"@uiw/codemirror-theme-eclipse": "^4.21.25",
|
|
85
|
+
"@uiw/codemirror-theme-github": "^4.21.25",
|
|
86
|
+
"@uiw/codemirror-theme-material": "^4.21.25",
|
|
87
|
+
"@uiw/codemirror-theme-nord": "^4.21.25",
|
|
88
|
+
"@uiw/codemirror-theme-okaidia": "^4.21.25",
|
|
89
|
+
"@uiw/codemirror-theme-xcode": "^4.21.25",
|
|
90
|
+
"@uiw/codemirror-themes": "^4.21.25",
|
|
91
91
|
"body-parser": "^1.20.2",
|
|
92
92
|
"codemirror": "^6.0.1",
|
|
93
93
|
"codemirror-ot": "^4.4.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.
|
|
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,14 +112,14 @@
|
|
|
112
112
|
"ws": "^8.16.0"
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
115
|
-
"@types/react": "^18.2.
|
|
115
|
+
"@types/react": "^18.2.71",
|
|
116
116
|
"@types/react-dom": "^18.2.22",
|
|
117
117
|
"@vitejs/plugin-react": "^4.2.1",
|
|
118
118
|
"concurrently": "^8.2.2",
|
|
119
119
|
"prettier": "^3.2.5",
|
|
120
120
|
"sass": "^1.72.0",
|
|
121
121
|
"typescript": "^5.4.3",
|
|
122
|
-
"vite": "^5.2.
|
|
122
|
+
"vite": "^5.2.6",
|
|
123
123
|
"vitest": "^1.4.0"
|
|
124
124
|
}
|
|
125
125
|
}
|
|
@@ -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 {
|
|
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
|
|
@@ -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,7 @@ export const getOrCreateEditor = ({
|
|
|
89
90
|
editorCache,
|
|
90
91
|
usernameRef,
|
|
91
92
|
typeScriptWorker,
|
|
93
|
+
customInteractRules,
|
|
92
94
|
}: {
|
|
93
95
|
fileId: FileId;
|
|
94
96
|
|
|
@@ -116,6 +118,7 @@ export const getOrCreateEditor = ({
|
|
|
116
118
|
usernameRef: React.MutableRefObject<Username>;
|
|
117
119
|
aiAssistEndpoint?: string;
|
|
118
120
|
typeScriptWorker: Worker;
|
|
121
|
+
customInteractRules?: Array<InteractRule>;
|
|
119
122
|
}): EditorCacheValue => {
|
|
120
123
|
// Cache hit
|
|
121
124
|
if (editorCache.has(fileId)) {
|
|
@@ -234,7 +237,9 @@ export const getOrCreateEditor = ({
|
|
|
234
237
|
// the boolean checkboxes. The color pickers are also tricky,
|
|
235
238
|
// as they would also need to be able to handle `onInteractEnd`.
|
|
236
239
|
// See https://github.com/replit/codemirror-interact/issues/14
|
|
237
|
-
extensions.push(
|
|
240
|
+
extensions.push(
|
|
241
|
+
widgets({ onInteract, customInteractRules }),
|
|
242
|
+
);
|
|
238
243
|
|
|
239
244
|
extensions.push(highlightWidgets);
|
|
240
245
|
|
|
@@ -10,11 +10,16 @@ import { EditorCacheValue } from '../useEditorCache';
|
|
|
10
10
|
import { getOrCreateEditor } from './getOrCreateEditor';
|
|
11
11
|
import { VZCodeContext } from '../VZCodeContext';
|
|
12
12
|
import './style.scss';
|
|
13
|
+
import { InteractRule } from '@replit/codemirror-interact';
|
|
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
|
+
}: {
|
|
21
|
+
customInteractRules?: Array<InteractRule>;
|
|
22
|
+
}) => {
|
|
18
23
|
const {
|
|
19
24
|
activeFileId,
|
|
20
25
|
shareDBDoc,
|
|
@@ -79,6 +84,7 @@ export const CodeEditor = () => {
|
|
|
79
84
|
editorCache,
|
|
80
85
|
usernameRef,
|
|
81
86
|
typeScriptWorker,
|
|
87
|
+
customInteractRules,
|
|
82
88
|
}),
|
|
83
89
|
[
|
|
84
90
|
activeFileId,
|
|
@@ -1,7 +1,9 @@
|
|
|
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
3
|
// import interact from '@replit/codemirror-interact';
|
|
4
|
-
import interact
|
|
4
|
+
import interact, {
|
|
5
|
+
InteractRule,
|
|
6
|
+
} from './codemirror-interact';
|
|
5
7
|
|
|
6
8
|
import {
|
|
7
9
|
ViewPlugin,
|
|
@@ -11,6 +13,9 @@ import {
|
|
|
11
13
|
import { Extension, RangeSet } from '@codemirror/state';
|
|
12
14
|
import { EditorView } from 'codemirror';
|
|
13
15
|
|
|
16
|
+
// Regular expression for hex colors.
|
|
17
|
+
const colorRegex = /#[0-9A-Fa-f]{6}/g;
|
|
18
|
+
|
|
14
19
|
// Interactive code widgets.
|
|
15
20
|
// * Number dragger
|
|
16
21
|
// * Boolean toggler
|
|
@@ -21,181 +26,181 @@ import { EditorView } from 'codemirror';
|
|
|
21
26
|
// `onInteract` is called when the user interacts with a widget.
|
|
22
27
|
export const widgets = ({
|
|
23
28
|
onInteract,
|
|
29
|
+
customInteractRules,
|
|
24
30
|
}: {
|
|
25
31
|
onInteract?: () => void;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
customInteractRules?: Array<InteractRule>;
|
|
33
|
+
}) => {
|
|
34
|
+
const rules: Array<InteractRule> = [
|
|
35
|
+
// hex color picker
|
|
36
|
+
// Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
|
|
37
|
+
// Works without quotes to support CSS.
|
|
38
|
+
{
|
|
39
|
+
regexp: colorRegex,
|
|
40
|
+
cursor: 'pointer',
|
|
41
|
+
onClick(
|
|
42
|
+
text: string,
|
|
43
|
+
setText: (newText: string) => void,
|
|
44
|
+
) {
|
|
45
|
+
const startingColor: string = text;
|
|
46
|
+
|
|
47
|
+
const sel: HTMLInputElement =
|
|
48
|
+
document.createElement('input');
|
|
49
|
+
sel.type = 'color';
|
|
50
|
+
sel.value = startingColor.toLowerCase();
|
|
51
|
+
|
|
52
|
+
// valueIsUpper maintains the style of the user's code. It keeps the case of a-f the same case as the original.
|
|
53
|
+
const valueIsUpper: boolean =
|
|
54
|
+
startingColor.toUpperCase() === startingColor;
|
|
55
|
+
|
|
56
|
+
const updateHex = (e: Event) => {
|
|
57
|
+
const el: HTMLInputElement =
|
|
58
|
+
e.target as HTMLInputElement;
|
|
59
|
+
if (el.value) {
|
|
60
|
+
setText(
|
|
61
|
+
valueIsUpper
|
|
62
|
+
? el.value.toUpperCase()
|
|
63
|
+
: el.value,
|
|
39
64
|
);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
},
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
sel.addEventListener('input', updateHex);
|
|
68
|
+
sel.click();
|
|
68
69
|
},
|
|
70
|
+
},
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
},
|
|
72
|
+
// a rule for a number dragger
|
|
73
|
+
{
|
|
74
|
+
// the regexp matching the value
|
|
75
|
+
regexp: /(?<!\#)-?\b\d+\.?\d*\b/g,
|
|
76
|
+
// set cursor to "ew-resize" on hover
|
|
77
|
+
cursor: 'ew-resize',
|
|
78
|
+
// change number value based on mouse X movement on drag
|
|
79
|
+
onDrag: (text, setText, e) => {
|
|
80
|
+
if (onInteract) onInteract();
|
|
81
|
+
const newVal = Number(text) + e.movementX;
|
|
82
|
+
if (isNaN(newVal)) return;
|
|
83
|
+
setText(newVal.toString());
|
|
83
84
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
85
|
+
},
|
|
86
|
+
// bool toggler
|
|
87
|
+
{
|
|
88
|
+
regexp: /true|false/g,
|
|
89
|
+
cursor: 'pointer',
|
|
90
|
+
onClick: (text, setText) => {
|
|
91
|
+
if (onInteract) onInteract();
|
|
92
|
+
switch (text) {
|
|
93
|
+
case 'true':
|
|
94
|
+
return setText('false');
|
|
95
|
+
case 'false':
|
|
96
|
+
return setText('true');
|
|
97
|
+
}
|
|
97
98
|
},
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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})`,
|
|
99
|
+
},
|
|
100
|
+
// vec2 slider
|
|
101
|
+
// Inspired by: https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L61
|
|
102
|
+
{
|
|
103
|
+
regexp:
|
|
104
|
+
/vec2\(-?\b\d+\.?\d*\b\s*(,\s*-?\b\d+\.?\d*\b)?\)/g,
|
|
105
|
+
cursor: 'move',
|
|
106
|
+
|
|
107
|
+
onDrag: (text, setText, e) => {
|
|
108
|
+
const res =
|
|
109
|
+
/vec2\((?<x>-?\b\d+\.?\d*\b)\s*(,\s*(?<y>-?\b\d+\.?\d*\b))?\)/.exec(
|
|
110
|
+
text,
|
|
116
111
|
);
|
|
117
|
-
|
|
112
|
+
const x = Number(res?.groups?.x);
|
|
113
|
+
let y = Number(res?.groups?.y);
|
|
114
|
+
if (isNaN(x)) return;
|
|
115
|
+
if (isNaN(y)) y = x;
|
|
116
|
+
setText(
|
|
117
|
+
`vec2(${x + e.movementX}, ${y - e.movementY})`,
|
|
118
|
+
);
|
|
118
119
|
},
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
};
|
|
120
|
+
},
|
|
121
|
+
// rgb color picker
|
|
122
|
+
// Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
|
|
123
|
+
//TODO: create color picker for hsl colors
|
|
124
|
+
{
|
|
125
|
+
regexp: /rgb\(.*\)/g,
|
|
126
|
+
cursor: 'pointer',
|
|
127
|
+
onClick: (text, setText, e) => {
|
|
128
|
+
const res =
|
|
129
|
+
/rgb\((?<r>\d+)\s*,\s*(?<g>\d+)\s*,\s*(?<b>\d+)\)/.exec(
|
|
130
|
+
text,
|
|
131
|
+
);
|
|
132
|
+
const r = Number(res?.groups?.r);
|
|
133
|
+
const g = Number(res?.groups?.g);
|
|
134
|
+
const b = Number(res?.groups?.b);
|
|
151
135
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
136
|
+
//sel will open the color picker when sel.click is called.
|
|
137
|
+
const sel = document.createElement('input');
|
|
138
|
+
sel.type = 'color';
|
|
139
|
+
|
|
140
|
+
if (!isNaN(r + g + b)) sel.value = rgb2Hex(r, g, b);
|
|
141
|
+
|
|
142
|
+
const updateRGB = (e: Event) => {
|
|
143
|
+
const el = e.target as HTMLInputElement;
|
|
144
|
+
if (onInteract) onInteract();
|
|
145
|
+
|
|
146
|
+
if (el.value) {
|
|
147
|
+
const [r, g, b] = hex2RGB(el.value);
|
|
148
|
+
setText(`rgb(${r}, ${g}, ${b})`);
|
|
149
|
+
}
|
|
150
|
+
sel.removeEventListener('change', updateRGB);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
sel.addEventListener('change', updateRGB);
|
|
154
|
+
sel.click();
|
|
155
155
|
},
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
156
|
+
},
|
|
157
|
+
// url clicker
|
|
158
|
+
{
|
|
159
|
+
regexp: /https?:\/\/[^ ")]+/g,
|
|
160
|
+
cursor: 'pointer',
|
|
161
|
+
onClick: (text) => {
|
|
162
|
+
window.open(text);
|
|
163
163
|
},
|
|
164
|
+
},
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
166
|
+
//Set rotation to the angle between the x-axis and a line from the word "rotate" to the mouse pointer (while dragging).
|
|
167
|
+
//The rotation is in range (-180,180]
|
|
168
|
+
{
|
|
169
|
+
regexp: /rotate\(-?\d*\.?\d*\)/g,
|
|
170
|
+
cursor: 'move',
|
|
171
|
+
onDragStart(text, setText, e) {
|
|
172
|
+
rotationOrigin = { x: e.clientX, y: e.clientY };
|
|
173
|
+
},
|
|
173
174
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
175
|
+
onDrag(text, setText, e) {
|
|
176
|
+
if (rotationOrigin == null) return;
|
|
177
|
+
const rotationDegree = Math.round(
|
|
178
|
+
(Math.atan2(
|
|
179
|
+
rotationOrigin.y - e.clientY,
|
|
180
|
+
e.clientX - rotationOrigin.x,
|
|
181
|
+
) *
|
|
182
|
+
180) /
|
|
183
|
+
Math.PI,
|
|
184
|
+
);
|
|
185
|
+
//Calculate the angle between the x axis and a line from where the user first clicks to the current location of the mouse.
|
|
186
|
+
setText(`rotate(${rotationDegree})`);
|
|
187
|
+
const updateDragEvent = new CustomEvent(
|
|
188
|
+
'updateRotateDrag',
|
|
189
|
+
{ detail: rotationDegree },
|
|
190
|
+
);
|
|
190
191
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
},
|
|
192
|
+
document.dispatchEvent(updateDragEvent);
|
|
193
|
+
},
|
|
194
|
+
onDragEnd() {
|
|
195
|
+
rotationOrigin = null;
|
|
196
196
|
},
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
if (customInteractRules) {
|
|
200
|
+
rules.push(...customInteractRules);
|
|
201
|
+
}
|
|
202
|
+
return interact({ rules });
|
|
203
|
+
};
|
|
199
204
|
|
|
200
205
|
let rotationOrigin: { x: number; y: number } = null;
|
|
201
206
|
|
|
@@ -244,7 +249,8 @@ export const colorsInTextPlugin: Extension = [
|
|
|
244
249
|
}
|
|
245
250
|
const hexColorOccurences = line.value.matchAll(
|
|
246
251
|
// /\"\#([0-9]|[A-F]|[a-f]){6}\"/g,
|
|
247
|
-
/["']\#([0-9]|[A-F]|[a-f]){6}["']/g,
|
|
252
|
+
// /["']\#([0-9]|[A-F]|[a-f]){6}["']/g,
|
|
253
|
+
colorRegex,
|
|
248
254
|
);
|
|
249
255
|
let hexOccurance = hexColorOccurences.next();
|
|
250
256
|
while (!hexOccurance.done) {
|
|
@@ -260,9 +266,9 @@ export const colorsInTextPlugin: Extension = [
|
|
|
260
266
|
return Decoration.set(
|
|
261
267
|
colorInfos.map((colorInfo) => {
|
|
262
268
|
return {
|
|
263
|
-
//
|
|
264
|
-
from: colorInfo.index +
|
|
265
|
-
to: colorInfo.index +
|
|
269
|
+
// 7 is the length of the hex color string
|
|
270
|
+
from: colorInfo.index + 7,
|
|
271
|
+
to: colorInfo.index + 7,
|
|
266
272
|
value: Decoration.widget({
|
|
267
273
|
side: -1,
|
|
268
274
|
widget: new ColorWidget(colorInfo[0]),
|
|
@@ -288,10 +294,10 @@ class ColorWidget extends WidgetType {
|
|
|
288
294
|
return widget.color === this.color;
|
|
289
295
|
}
|
|
290
296
|
|
|
291
|
-
toDOM(
|
|
297
|
+
toDOM(): HTMLElement {
|
|
292
298
|
const parent = document.createElement('div');
|
|
293
299
|
const size = 20;
|
|
294
|
-
const innerSize =
|
|
300
|
+
const innerSize = 14;
|
|
295
301
|
|
|
296
302
|
parent.setAttribute(
|
|
297
303
|
'style',
|
|
@@ -311,7 +317,7 @@ class ColorWidget extends WidgetType {
|
|
|
311
317
|
'fill',
|
|
312
318
|
this.color.replace(/["']/g, ''),
|
|
313
319
|
);
|
|
314
|
-
colorCircle.setAttributeNS(null, 'stroke', 'black');
|
|
320
|
+
// colorCircle.setAttributeNS(null, 'stroke', 'black');
|
|
315
321
|
colorCircle.setAttributeNS(
|
|
316
322
|
null,
|
|
317
323
|
'r',
|
|
@@ -1,26 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
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 &&
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
useState,
|
|
6
6
|
} from 'react';
|
|
7
7
|
import {
|
|
8
|
+
FileId,
|
|
8
9
|
Files,
|
|
10
|
+
ItemId,
|
|
9
11
|
ShareDBDoc,
|
|
10
12
|
SubmitOperation,
|
|
11
13
|
Username,
|
|
@@ -111,6 +113,9 @@ export type VZCodeContextValue = {
|
|
|
111
113
|
runCodeRef: React.MutableRefObject<null | (() => void)>;
|
|
112
114
|
|
|
113
115
|
connected: boolean;
|
|
116
|
+
|
|
117
|
+
hoveredItemId: ItemId | null;
|
|
118
|
+
setHoveredItemId: (itemId: ItemId | null) => void;
|
|
114
119
|
};
|
|
115
120
|
|
|
116
121
|
export const VZCodeProvider = ({
|
|
@@ -275,6 +280,10 @@ export const VZCodeProvider = ({
|
|
|
275
280
|
runCodeRef,
|
|
276
281
|
});
|
|
277
282
|
|
|
283
|
+
// Track the currently hovered file id.
|
|
284
|
+
const [hoveredItemId, setHoveredItemId] =
|
|
285
|
+
useState<ItemId | null>(null);
|
|
286
|
+
|
|
278
287
|
// The value provided by this context.
|
|
279
288
|
const value: VZCodeContextValue = {
|
|
280
289
|
content,
|
|
@@ -328,6 +337,9 @@ export const VZCodeProvider = ({
|
|
|
328
337
|
runCodeRef,
|
|
329
338
|
|
|
330
339
|
connected,
|
|
340
|
+
|
|
341
|
+
hoveredItemId,
|
|
342
|
+
setHoveredItemId,
|
|
331
343
|
};
|
|
332
344
|
|
|
333
345
|
return (
|