vzcode 0.18.0 → 0.20.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/README.md +21 -0
- package/dist/assets/index-c31dfdc2.js +121 -0
- package/dist/index.html +1 -1
- package/package.json +13 -13
- package/src/client/App.tsx +12 -21
- package/src/client/CodeEditor/json1PresenceDisplay.js +26 -12
- package/src/client/CodeEditor/widgets.ts +95 -0
- package/src/client/Sidebar/ConfirmationBox.jsx +34 -0
- package/src/client/Sidebar/DirectoryListing.tsx +7 -1
- package/src/client/Sidebar/FileListing.tsx +13 -6
- package/src/client/Sidebar/Item.tsx +121 -0
- package/src/client/Sidebar/Listing.tsx +4 -1
- package/src/client/Sidebar/index.tsx +4 -1
- package/src/client/SplitPaneResizeContext.tsx +21 -7
- package/src/client/TabList/index.tsx +23 -1
- package/src/server/index.js +1 -1
- package/dist/assets/index-24635cd2.js +0 -115
- package/src/client/Sidebar/Item.jsx +0 -43
package/dist/index.html
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
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-
|
|
31
|
+
<script type="module" crossorigin src="/assets/index-c31dfdc2.js"></script>
|
|
32
32
|
<link rel="stylesheet" href="/assets/index-0053b846.css">
|
|
33
33
|
</head>
|
|
34
34
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vzcode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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.
|
|
52
|
+
"@codemirror/lang-markdown": "^6.2.1",
|
|
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.
|
|
57
|
+
"@uiw/codemirror-themes-all": "^4.21.18",
|
|
58
58
|
"codemirror": "^6.0.1",
|
|
59
59
|
"codemirror-ot": "^4.1.0",
|
|
60
60
|
"d3-array": "^3.2.4",
|
|
@@ -65,22 +65,22 @@
|
|
|
65
65
|
"ngrok": "^4.3.3",
|
|
66
66
|
"open": "^9.1.0",
|
|
67
67
|
"react": "^18.2.0",
|
|
68
|
-
"react-bootstrap": "^2.
|
|
68
|
+
"react-bootstrap": "^2.9.0",
|
|
69
69
|
"react-dom": "^18.2.0",
|
|
70
|
-
"react-select": "^5.7.
|
|
71
|
-
"sharedb": "^4.0.
|
|
72
|
-
"sharedb-client-browser": "^4.3.
|
|
70
|
+
"react-select": "^5.7.5",
|
|
71
|
+
"sharedb": "^4.0.1",
|
|
72
|
+
"sharedb-client-browser": "^4.3.1",
|
|
73
73
|
"ot-json1": "^1.0.2",
|
|
74
|
-
"ws": "^8.
|
|
74
|
+
"ws": "^8.14.2"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@types/react": "^18.2.
|
|
78
|
-
"@types/react-dom": "^18.2.
|
|
79
|
-
"@vitejs/plugin-react": "^4.0
|
|
77
|
+
"@types/react": "^18.2.23",
|
|
78
|
+
"@types/react-dom": "^18.2.8",
|
|
79
|
+
"@vitejs/plugin-react": "^4.1.0",
|
|
80
80
|
"prettier": "^3.0.3",
|
|
81
|
-
"sass": "^1.
|
|
81
|
+
"sass": "^1.68.0",
|
|
82
82
|
"typescript": "^5.2.2",
|
|
83
83
|
"vite": "^4.4.9",
|
|
84
|
-
"vitest": "^0.34.
|
|
84
|
+
"vitest": "^0.34.5"
|
|
85
85
|
}
|
|
86
86
|
}
|
package/src/client/App.tsx
CHANGED
|
@@ -198,24 +198,19 @@ function App() {
|
|
|
198
198
|
}
|
|
199
199
|
}, [submitOperation]);
|
|
200
200
|
|
|
201
|
-
// Called when a file in the sidebar is
|
|
201
|
+
// Called when a file in the sidebar is renamed.
|
|
202
202
|
const handleRenameFileClick = useCallback(
|
|
203
|
-
(fileId: FileId) => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
...document.files,
|
|
212
|
-
[fileId]: {
|
|
213
|
-
...document.files[fileId],
|
|
214
|
-
name: newName,
|
|
215
|
-
},
|
|
203
|
+
(fileId: FileId, newName: string) => {
|
|
204
|
+
submitOperation((document) => ({
|
|
205
|
+
...document,
|
|
206
|
+
files: {
|
|
207
|
+
...document.files,
|
|
208
|
+
[fileId]: {
|
|
209
|
+
...document.files[fileId],
|
|
210
|
+
name: newName,
|
|
216
211
|
},
|
|
217
|
-
}
|
|
218
|
-
}
|
|
212
|
+
},
|
|
213
|
+
}));
|
|
219
214
|
},
|
|
220
215
|
[submitOperation],
|
|
221
216
|
);
|
|
@@ -232,12 +227,8 @@ function App() {
|
|
|
232
227
|
[submitOperation, closeTab],
|
|
233
228
|
);
|
|
234
229
|
|
|
235
|
-
// TODO prompt the user "Are you sure?"
|
|
236
230
|
const handleDeleteFileClick = useCallback(
|
|
237
|
-
(fileId: FileId
|
|
238
|
-
// Stop propagation so that the outer listener doesn't fire,
|
|
239
|
-
// which would try to open this file in a tab.
|
|
240
|
-
event.stopPropagation();
|
|
231
|
+
(fileId: FileId) => {
|
|
241
232
|
deleteFile(fileId);
|
|
242
233
|
},
|
|
243
234
|
[deleteFile],
|
|
@@ -47,17 +47,32 @@ export const json1PresenceDisplay = ({
|
|
|
47
47
|
const { start, end } = presence;
|
|
48
48
|
const from = start[start.length - 1];
|
|
49
49
|
// TODO support selection ranges (first attempt introduced layout errors)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
const to = end[end.length - 1];
|
|
51
|
+
if (from === to) {
|
|
52
|
+
return {
|
|
53
|
+
from,
|
|
54
|
+
to,
|
|
55
|
+
value: Decoration.widget({
|
|
56
|
+
side: -1,
|
|
57
|
+
block: false,
|
|
58
|
+
widget: new PresenceWidget(id),
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
} else {
|
|
62
|
+
return {
|
|
63
|
+
from,
|
|
64
|
+
to,
|
|
65
|
+
value: Decoration.mark({
|
|
66
|
+
class: 'cm-json1-presence',
|
|
67
|
+
attributes: {
|
|
68
|
+
style: `
|
|
69
|
+
background-color: rgba(255, 255, 0, 0.5);
|
|
70
|
+
box-shadow: 1px 0 0 yellow;
|
|
71
|
+
`,
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
61
76
|
}),
|
|
62
77
|
// Without this argument, we get the following error:
|
|
63
78
|
// Uncaught Error: Ranges must be added sorted by `from` position and `startSide`
|
|
@@ -142,6 +157,5 @@ const presenceTheme = EditorView.baseTheme({
|
|
|
142
157
|
left: '0',
|
|
143
158
|
right: '0',
|
|
144
159
|
borderLeft: '1px solid yellow',
|
|
145
|
-
//borderRight: '1px solid black',
|
|
146
160
|
},
|
|
147
161
|
});
|
|
@@ -43,6 +43,61 @@ export const widgets = ({
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
},
|
|
46
|
+
//vec2 slider
|
|
47
|
+
// Inspired by: https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L61
|
|
48
|
+
{
|
|
49
|
+
regexp:
|
|
50
|
+
/vec2\(-?\b\d+\.?\d*\b\s*(,\s*-?\b\d+\.?\d*\b)?\)/g,
|
|
51
|
+
cursor: 'move',
|
|
52
|
+
|
|
53
|
+
onDrag: (text, setText, e) => {
|
|
54
|
+
const res =
|
|
55
|
+
/vec2\((?<x>-?\b\d+\.?\d*\b)\s*(,\s*(?<y>-?\b\d+\.?\d*\b))?\)/.exec(
|
|
56
|
+
text,
|
|
57
|
+
);
|
|
58
|
+
const x = Number(res?.groups?.x);
|
|
59
|
+
let y = Number(res?.groups?.y);
|
|
60
|
+
if (isNaN(x)) return;
|
|
61
|
+
if (isNaN(y)) y = x;
|
|
62
|
+
setText(
|
|
63
|
+
`vec2(${x + e.movementX}, ${y - e.movementY})`,
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
//color picker
|
|
68
|
+
// Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
|
|
69
|
+
//TODO: create color picker for hsl colors
|
|
70
|
+
{
|
|
71
|
+
regexp: /rgb\(.*\)/g,
|
|
72
|
+
cursor: 'pointer',
|
|
73
|
+
onClick: (text, setText, e) => {
|
|
74
|
+
const res =
|
|
75
|
+
/rgb\((?<r>\d+)\s*,\s*(?<g>\d+)\s*,\s*(?<b>\d+)\)/.exec(
|
|
76
|
+
text,
|
|
77
|
+
);
|
|
78
|
+
const r = Number(res?.groups?.r);
|
|
79
|
+
const g = Number(res?.groups?.g);
|
|
80
|
+
const b = Number(res?.groups?.b);
|
|
81
|
+
|
|
82
|
+
//sel will open the color picker when sel.click is called.
|
|
83
|
+
const sel = document.createElement('input');
|
|
84
|
+
sel.type = 'color';
|
|
85
|
+
if (!isNaN(r + g + b))
|
|
86
|
+
sel.value = rgb2Hex(r, g, b);
|
|
87
|
+
|
|
88
|
+
const updateRGB = (e: Event) => {
|
|
89
|
+
const el = e.target as HTMLInputElement;
|
|
90
|
+
if (el.value) {
|
|
91
|
+
const [r, g, b] = hex2RGB(el.value);
|
|
92
|
+
setText(`rgb(${r}, ${g}, ${b})`);
|
|
93
|
+
}
|
|
94
|
+
sel.removeEventListener('change', updateRGB);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
sel.addEventListener('change', updateRGB);
|
|
98
|
+
sel.click();
|
|
99
|
+
},
|
|
100
|
+
},
|
|
46
101
|
// url clicker
|
|
47
102
|
{
|
|
48
103
|
regexp: /https?:\/\/[^ "]+/g,
|
|
@@ -51,5 +106,45 @@ export const widgets = ({
|
|
|
51
106
|
window.open(text);
|
|
52
107
|
},
|
|
53
108
|
},
|
|
109
|
+
|
|
110
|
+
//Set rotation to the angle between the x-axis and a line from the word "rotate" to the mouse pointer (while dragging).
|
|
111
|
+
//The rotation is in range (-180,180]
|
|
112
|
+
{
|
|
113
|
+
regexp: /rotate\(-?\d*\.?\d*\)/g,
|
|
114
|
+
cursor: 'move',
|
|
115
|
+
onDragStart(text, setText, e) {
|
|
116
|
+
rotationOrigin = { x: e.screenX, y: e.screenY };
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
onDrag(text, setText, e) {
|
|
120
|
+
if (rotationOrigin == null) return;
|
|
121
|
+
//Calculate the angle between the x axis and a line from where the user first clicks to the current location of the mouse.
|
|
122
|
+
setText(
|
|
123
|
+
`rotate(${Math.round(
|
|
124
|
+
(Math.atan2(
|
|
125
|
+
rotationOrigin.y - e.screenY,
|
|
126
|
+
e.screenX - rotationOrigin.x,
|
|
127
|
+
) *
|
|
128
|
+
180) /
|
|
129
|
+
Math.PI,
|
|
130
|
+
)})`,
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
onDragEnd(text, setText) {
|
|
134
|
+
rotationOrigin = null;
|
|
135
|
+
},
|
|
136
|
+
},
|
|
54
137
|
],
|
|
55
138
|
});
|
|
139
|
+
|
|
140
|
+
let rotationOrigin: { x: number; y: number } = null;
|
|
141
|
+
|
|
142
|
+
// Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L108
|
|
143
|
+
const hex2RGB = (hex: string): [number, number, number] => {
|
|
144
|
+
const v = parseInt(hex.substring(1), 16);
|
|
145
|
+
return [(v >> 16) & 255, (v >> 8) & 255, v & 255];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L117
|
|
149
|
+
const rgb2Hex = (r: number, g: number, b: number): string =>
|
|
150
|
+
'#' + r.toString(16) + g.toString(16) + b.toString(16);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Button from 'react-bootstrap/Button';
|
|
3
|
+
import Modal from 'react-bootstrap/Modal';
|
|
4
|
+
|
|
5
|
+
// Inspired by https://react-bootstrap.netlify.app/docs/components/modal/#live-demo
|
|
6
|
+
export const ConfirmationBox = ({
|
|
7
|
+
show,
|
|
8
|
+
onClose,
|
|
9
|
+
onConfirm,
|
|
10
|
+
}) => (
|
|
11
|
+
<Modal
|
|
12
|
+
show={show}
|
|
13
|
+
onHide={onClose}
|
|
14
|
+
// confirm={onConfirm}
|
|
15
|
+
centered
|
|
16
|
+
>
|
|
17
|
+
<Modal.Header closeButton onClick={onClose}>
|
|
18
|
+
<Modal.Title>Delete File</Modal.Title>
|
|
19
|
+
</Modal.Header>
|
|
20
|
+
|
|
21
|
+
<Modal.Body>
|
|
22
|
+
<p>Are you sure you want to delete this file?</p>
|
|
23
|
+
</Modal.Body>
|
|
24
|
+
|
|
25
|
+
<Modal.Footer>
|
|
26
|
+
<Button variant="primary" onClick={onConfirm}>
|
|
27
|
+
Yes
|
|
28
|
+
</Button>
|
|
29
|
+
<Button variant="secondary" onClick={onClose}>
|
|
30
|
+
No
|
|
31
|
+
</Button>
|
|
32
|
+
</Modal.Footer>
|
|
33
|
+
</Modal>
|
|
34
|
+
);
|
|
@@ -17,7 +17,10 @@ export const DirectoryListing = ({
|
|
|
17
17
|
name: string;
|
|
18
18
|
path: string;
|
|
19
19
|
children: Array<FileTree | FileTreeFile>;
|
|
20
|
-
handleRenameFileClick: (
|
|
20
|
+
handleRenameFileClick: (
|
|
21
|
+
fileId: string,
|
|
22
|
+
newName: string,
|
|
23
|
+
) => void;
|
|
21
24
|
handleDeleteFileClick: (
|
|
22
25
|
fileId: string,
|
|
23
26
|
event: React.MouseEvent,
|
|
@@ -31,10 +34,12 @@ export const DirectoryListing = ({
|
|
|
31
34
|
}, [toggleDirectory]);
|
|
32
35
|
|
|
33
36
|
const handleDeleteClick = useCallback(() => {
|
|
37
|
+
// https://github.com/vizhub-core/vzcode/issues/102
|
|
34
38
|
console.log('TODO handleDeleteDirectoryClick');
|
|
35
39
|
}, []);
|
|
36
40
|
|
|
37
41
|
const handleRenameClick = useCallback(() => {
|
|
42
|
+
// https://github.com/vizhub-core/vzcode/issues/103
|
|
38
43
|
console.log('TODO handleRenameDirectoryClick');
|
|
39
44
|
}, []);
|
|
40
45
|
|
|
@@ -50,6 +55,7 @@ export const DirectoryListing = ({
|
|
|
50
55
|
handleClick={handleClick}
|
|
51
56
|
handleDeleteClick={handleDeleteClick}
|
|
52
57
|
handleRenameClick={handleRenameClick}
|
|
58
|
+
isDir={true}
|
|
53
59
|
>
|
|
54
60
|
<div
|
|
55
61
|
className="arrow-wrapper"
|
|
@@ -11,7 +11,10 @@ export const FileListing = ({
|
|
|
11
11
|
}: {
|
|
12
12
|
name: string;
|
|
13
13
|
fileId: FileId;
|
|
14
|
-
handleRenameFileClick: (
|
|
14
|
+
handleRenameFileClick: (
|
|
15
|
+
fileId: FileId,
|
|
16
|
+
newName: string,
|
|
17
|
+
) => void;
|
|
15
18
|
handleDeleteFileClick: (
|
|
16
19
|
fileId: FileId,
|
|
17
20
|
event: React.MouseEvent,
|
|
@@ -23,21 +26,25 @@ export const FileListing = ({
|
|
|
23
26
|
}, [fileId, handleFileClick]);
|
|
24
27
|
|
|
25
28
|
const handleDeleteClick = useCallback(
|
|
26
|
-
(event) => {
|
|
29
|
+
(event: React.MouseEvent) => {
|
|
27
30
|
handleDeleteFileClick(fileId, event);
|
|
28
31
|
},
|
|
29
|
-
[fileId],
|
|
32
|
+
[fileId, handleDeleteFileClick],
|
|
30
33
|
);
|
|
31
34
|
|
|
32
|
-
const handleRenameClick = useCallback(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
const handleRenameClick = useCallback(
|
|
36
|
+
(newName: string) => {
|
|
37
|
+
handleRenameFileClick(fileId, newName);
|
|
38
|
+
},
|
|
39
|
+
[fileId, handleRenameFileClick],
|
|
40
|
+
);
|
|
35
41
|
|
|
36
42
|
return (
|
|
37
43
|
<Item
|
|
38
44
|
handleClick={handleClick}
|
|
39
45
|
handleDeleteClick={handleDeleteClick}
|
|
40
46
|
handleRenameClick={handleRenameClick}
|
|
47
|
+
isDir={false}
|
|
41
48
|
>
|
|
42
49
|
{name}
|
|
43
50
|
</Item>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import { ConfirmationBox } from './ConfirmationBox';
|
|
3
|
+
|
|
4
|
+
export const Item = ({
|
|
5
|
+
name,
|
|
6
|
+
children,
|
|
7
|
+
handleClick,
|
|
8
|
+
handleRenameClick,
|
|
9
|
+
handleDeleteClick,
|
|
10
|
+
isDir,
|
|
11
|
+
}) => {
|
|
12
|
+
// Tracks whether the mouse is hovering over the file or directory
|
|
13
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
14
|
+
|
|
15
|
+
// Tracks whether the file is being renamed (inline text input)
|
|
16
|
+
const [isRenaming, setIsRenaming] = useState(false);
|
|
17
|
+
|
|
18
|
+
// Tracks the value of the input field when renaming
|
|
19
|
+
const [renameValue, setRenameValue] = useState('');
|
|
20
|
+
|
|
21
|
+
const onKeyDown = useCallback((event) => {
|
|
22
|
+
// TODO make 'Escape' cancel the changes - do not commit the rename
|
|
23
|
+
// https://github.com/vizhub-core/vzcode/issues/207
|
|
24
|
+
if (event.key === 'Enter' || event.key === 'Escape') {
|
|
25
|
+
event.target.blur();
|
|
26
|
+
}
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const onBlur = useCallback(
|
|
30
|
+
(event: React.FocusEvent<HTMLInputElement>) => {
|
|
31
|
+
if (event.target.value.trim() === '') {
|
|
32
|
+
// setEditingValue(value);
|
|
33
|
+
console.log('TODO: handle empty rename');
|
|
34
|
+
} else {
|
|
35
|
+
setIsRenaming(false);
|
|
36
|
+
handleRenameClick(event.target.value);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
[handleRenameClick],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const [showModal, setShowModal] = useState(false);
|
|
43
|
+
|
|
44
|
+
// Function to open the delete file confirmation modal
|
|
45
|
+
const handleModalOpen = useCallback(() => {
|
|
46
|
+
setShowModal(true);
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
// Function to close the modal
|
|
50
|
+
const handleModalClose = useCallback(() => {
|
|
51
|
+
setShowModal(false);
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
// Function to handle confirmation on modal
|
|
55
|
+
const handleConfirmModal = useCallback(() => {
|
|
56
|
+
setShowModal(false);
|
|
57
|
+
handleDeleteClick();
|
|
58
|
+
}, [handleDeleteClick]);
|
|
59
|
+
|
|
60
|
+
const handleMouseEnter = useCallback(() => {
|
|
61
|
+
setIsHovered(true);
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
const handleMouseLeave = useCallback(() => {
|
|
65
|
+
setIsHovered(false);
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
const onChange = useCallback(
|
|
69
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
70
|
+
setRenameValue(event.target.value);
|
|
71
|
+
},
|
|
72
|
+
[],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
className="full-box file-or-directory"
|
|
78
|
+
onClick={isRenaming ? null : handleClick}
|
|
79
|
+
onMouseEnter={handleMouseEnter}
|
|
80
|
+
onMouseLeave={handleMouseLeave}
|
|
81
|
+
>
|
|
82
|
+
<div className="name">
|
|
83
|
+
{isRenaming ? (
|
|
84
|
+
<input
|
|
85
|
+
type="text"
|
|
86
|
+
aria-label="Field name"
|
|
87
|
+
value={renameValue}
|
|
88
|
+
onKeyDown={onKeyDown}
|
|
89
|
+
onBlur={onBlur}
|
|
90
|
+
onChange={onChange}
|
|
91
|
+
/>
|
|
92
|
+
) : (
|
|
93
|
+
children
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
{isHovered ? (
|
|
97
|
+
<div className="utils">
|
|
98
|
+
<i
|
|
99
|
+
className="bx bxs-edit utilities"
|
|
100
|
+
style={{ color: '#abdafb' }}
|
|
101
|
+
onClick={() => {
|
|
102
|
+
setIsRenaming(!isRenaming);
|
|
103
|
+
setRenameValue(isDir ? name : children);
|
|
104
|
+
}}
|
|
105
|
+
></i>
|
|
106
|
+
<i
|
|
107
|
+
className="bx bx-trash"
|
|
108
|
+
style={{ color: '#eb336c' }}
|
|
109
|
+
onClick={handleModalOpen}
|
|
110
|
+
></i>
|
|
111
|
+
</div>
|
|
112
|
+
) : null}
|
|
113
|
+
|
|
114
|
+
<ConfirmationBox
|
|
115
|
+
show={showModal}
|
|
116
|
+
onClose={handleModalClose}
|
|
117
|
+
onConfirm={handleConfirmModal}
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
@@ -17,7 +17,10 @@ export const Listing = ({
|
|
|
17
17
|
toggleDirectory,
|
|
18
18
|
}: {
|
|
19
19
|
entity: FileTree | FileTreeFile;
|
|
20
|
-
handleRenameFileClick: (
|
|
20
|
+
handleRenameFileClick: (
|
|
21
|
+
fileId: FileId,
|
|
22
|
+
newName: string,
|
|
23
|
+
) => void;
|
|
21
24
|
handleDeleteFileClick: (
|
|
22
25
|
fileId: FileId,
|
|
23
26
|
event: React.MouseEvent,
|
|
@@ -24,7 +24,10 @@ export const Sidebar = ({
|
|
|
24
24
|
}: {
|
|
25
25
|
files: Files;
|
|
26
26
|
createFile?: () => void;
|
|
27
|
-
handleRenameFileClick?: (
|
|
27
|
+
handleRenameFileClick?: (
|
|
28
|
+
fileId: FileId,
|
|
29
|
+
newName: string,
|
|
30
|
+
) => void;
|
|
28
31
|
handleDeleteFileClick?: (
|
|
29
32
|
fileId: FileId,
|
|
30
33
|
event: React.MouseEvent,
|
|
@@ -13,16 +13,30 @@ export const SplitPaneResizeContext = createContext<{
|
|
|
13
13
|
setIsDragging: (a: boolean) => void;
|
|
14
14
|
}>(null);
|
|
15
15
|
|
|
16
|
+
// The amount of time to wait idle before writing to localStorage.
|
|
17
|
+
// This is to avoid writing to localStorage on every resize event.
|
|
18
|
+
// MS = milliseconds
|
|
16
19
|
const localStorageWriteDebounceMS = 800;
|
|
17
20
|
|
|
18
21
|
const localStoragePropertyName = 'vizHubCodeEditorWidth';
|
|
19
|
-
|
|
20
|
-
localStoragePropertyName,
|
|
21
|
-
);
|
|
22
|
-
// const initialWidthPercentage = window.innerWidth / 3;
|
|
22
|
+
|
|
23
23
|
const initialWidthDefault = 220;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
let initialWidth: number = initialWidthDefault;
|
|
25
|
+
|
|
26
|
+
// If we're in the browser,
|
|
27
|
+
if (typeof window !== 'undefined') {
|
|
28
|
+
//check localStorage for a previously stored width.
|
|
29
|
+
const initialWidthFromLocalStorage: string | null =
|
|
30
|
+
window.localStorage.getItem(localStoragePropertyName);
|
|
31
|
+
|
|
32
|
+
// If there is a previously stored width,
|
|
33
|
+
if (initialWidthFromLocalStorage !== null) {
|
|
34
|
+
// use it as the initial width.
|
|
35
|
+
initialWidth = +initialWidthFromLocalStorage;
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
// If we're not in the browser, use the default initial width.
|
|
39
|
+
}
|
|
26
40
|
|
|
27
41
|
const add = (a: number, b: number) => a + b;
|
|
28
42
|
|
|
@@ -36,7 +50,7 @@ export const SplitPaneResizeProvider = ({ children }) => {
|
|
|
36
50
|
useEffect(() => {
|
|
37
51
|
if (codeEditorWidth !== initialWidth) {
|
|
38
52
|
const timeout = setTimeout(() => {
|
|
39
|
-
localStorage.setItem(
|
|
53
|
+
window.localStorage.setItem(
|
|
40
54
|
localStoragePropertyName,
|
|
41
55
|
'' + codeEditorWidth,
|
|
42
56
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useMemo } from 'react';
|
|
1
|
+
import { useCallback, useMemo, useEffect } from 'react';
|
|
2
2
|
import { FileId, Files } from '../../types';
|
|
3
3
|
import './style.scss';
|
|
4
4
|
|
|
@@ -69,6 +69,28 @@ export const TabList = ({
|
|
|
69
69
|
setActiveFileId: (fileId: FileId) => void;
|
|
70
70
|
closeTab: (fileId: FileId) => void;
|
|
71
71
|
}) => {
|
|
72
|
+
//Create a function to perform the logic to delete the current tab
|
|
73
|
+
const handleKeyPress = useCallback(
|
|
74
|
+
(event: { altKey: boolean; key: string }) => {
|
|
75
|
+
if (event.altKey == true) {
|
|
76
|
+
if (event.key == 'w') {
|
|
77
|
+
closeTab(activeFileId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
[closeTab, activeFileId],
|
|
82
|
+
);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
// attach the event listener
|
|
85
|
+
document.addEventListener('keydown', handleKeyPress);
|
|
86
|
+
// remove the event listener
|
|
87
|
+
return () => {
|
|
88
|
+
document.removeEventListener(
|
|
89
|
+
'keydown',
|
|
90
|
+
handleKeyPress,
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
}, [handleKeyPress]);
|
|
72
94
|
return (
|
|
73
95
|
<div className="vz-tab-list">
|
|
74
96
|
{files &&
|