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/assets/{index-FsyJOC4a.js → index-BKQfM_WG.js} +52 -52
- package/dist/assets/{worker-DrXASJTP.js → worker-1ri6mpan.js} +52 -52
- package/dist/index.html +1 -1
- package/package.json +4 -4
- package/src/client/AIAssist/AIAssistWidget/index.tsx +16 -17
- package/src/client/CodeEditor/getOrCreateEditor.ts +9 -1
- package/src/client/CodeEditor/index.tsx +10 -1
- package/src/client/CodeEditor/typeScriptLinter.ts +2 -0
- package/src/client/CodeEditor/widgets.ts +176 -168
- package/src/client/TabList/index.tsx +13 -19
- package/src/client/VZCodeContext.tsx +12 -0
- package/src/client/VZMiddle.tsx +10 -29
- package/src/client/VZSettings.tsx +43 -9
- 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 +0 -7
- package/src/client/useTypeScript/requestTypes.ts +3 -0
- package/src/client/useTypeScript/worker.ts +23 -3
- package/src/types.ts +5 -1
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-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
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,
|
|
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(
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
sel.addEventListener('input', updateHex);
|
|
70
|
+
sel.click();
|
|
68
71
|
},
|
|
72
|
+
},
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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})`,
|
|
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
|
-
|
|
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
|
-
};
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
//
|
|
264
|
-
from: colorInfo.index +
|
|
265
|
-
to: colorInfo.index +
|
|
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(
|
|
299
|
+
toDOM(): HTMLElement {
|
|
292
300
|
const parent = document.createElement('div');
|
|
293
301
|
const size = 20;
|
|
294
|
-
const innerSize =
|
|
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
|
|
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 &&
|