vzcode 0.21.0 → 0.24.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-9c8bbae0.js +111 -0
- package/dist/assets/index-cf19de3b.css +1 -0
- package/dist/assets/{worker-d5f0575a.js → worker-b775ddff.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +19 -12
- package/src/client/App.tsx +101 -13
- package/src/client/CodeEditor/getOrCreateEditor.ts +9 -1
- package/src/client/CodeEditor/index.tsx +9 -5
- package/src/client/CodeEditor/widgets.ts +238 -3
- package/src/client/PresenceNotifications/index.tsx +95 -78
- package/src/client/PrettierErrorOverlay/index.tsx +9 -0
- package/src/client/PrettierErrorOverlay/style.scss +9 -0
- package/src/client/Settings.tsx +42 -34
- package/src/client/Sidebar/DirectoryListing.tsx +7 -4
- package/src/client/Sidebar/FileListing.tsx +1 -1
- package/src/client/Sidebar/Item.tsx +17 -8
- package/src/client/TabList/style.scss +4 -0
- package/src/client/reducer.ts +122 -0
- package/src/client/style.scss +3 -0
- package/src/client/usePrettier/index.ts +32 -14
- package/src/client/usePrettier/worker.ts +1 -1
- package/src/client/useTabsState.ts +42 -5
- package/src/server/index.js +20 -3
- package/dist/assets/index-1547f7b8.css +0 -1
- package/dist/assets/index-37015623.js +0 -121
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import interact from '@replit/codemirror-interact';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
ViewPlugin,
|
|
5
|
+
Decoration,
|
|
6
|
+
WidgetType,
|
|
7
|
+
keymap,
|
|
8
|
+
} from '@codemirror/view';
|
|
9
|
+
import {
|
|
10
|
+
Annotation,
|
|
11
|
+
Extension,
|
|
12
|
+
RangeSet,
|
|
13
|
+
} from '@codemirror/state';
|
|
14
|
+
import { EditorView } from 'codemirror';
|
|
15
|
+
|
|
3
16
|
// Interactive code widgets.
|
|
4
17
|
// * Number dragger
|
|
5
18
|
// * Boolean toggler
|
|
@@ -15,10 +28,49 @@ export const widgets = ({
|
|
|
15
28
|
}) =>
|
|
16
29
|
interact({
|
|
17
30
|
rules: [
|
|
31
|
+
// hex color picker
|
|
32
|
+
// Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
|
|
33
|
+
{
|
|
34
|
+
regexp: /\"\#([0-9]|[A-F]|[a-f]){6}\"/g,
|
|
35
|
+
cursor: 'pointer',
|
|
36
|
+
onClick(text, setText, e) {
|
|
37
|
+
const res =
|
|
38
|
+
/\"(?<hex>\#([0-9]|[A-F]|[a-f]){6})\"/.exec(
|
|
39
|
+
text,
|
|
40
|
+
);
|
|
41
|
+
const startingColor = res.groups?.hex;
|
|
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
|
+
|
|
55
|
+
if (el.value) {
|
|
56
|
+
setText(
|
|
57
|
+
`"${
|
|
58
|
+
valueIsUpper
|
|
59
|
+
? el.value.toUpperCase()
|
|
60
|
+
: el.value
|
|
61
|
+
}"`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
sel.addEventListener('input', updateHex);
|
|
66
|
+
sel.click();
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
|
|
18
70
|
// a rule for a number dragger
|
|
19
71
|
{
|
|
20
72
|
// the regexp matching the value
|
|
21
|
-
regexp:
|
|
73
|
+
regexp: /(?<!\#)-?\b\d+\.?\d*\b/g,
|
|
22
74
|
// set cursor to "ew-resize" on hover
|
|
23
75
|
cursor: 'ew-resize',
|
|
24
76
|
// change number value based on mouse X movement on drag
|
|
@@ -43,7 +95,7 @@ export const widgets = ({
|
|
|
43
95
|
}
|
|
44
96
|
},
|
|
45
97
|
},
|
|
46
|
-
//vec2 slider
|
|
98
|
+
// vec2 slider
|
|
47
99
|
// Inspired by: https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L61
|
|
48
100
|
{
|
|
49
101
|
regexp:
|
|
@@ -64,7 +116,7 @@ export const widgets = ({
|
|
|
64
116
|
);
|
|
65
117
|
},
|
|
66
118
|
},
|
|
67
|
-
//color picker
|
|
119
|
+
// rgb color picker
|
|
68
120
|
// Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
|
|
69
121
|
//TODO: create color picker for hsl colors
|
|
70
122
|
{
|
|
@@ -82,6 +134,7 @@ export const widgets = ({
|
|
|
82
134
|
//sel will open the color picker when sel.click is called.
|
|
83
135
|
const sel = document.createElement('input');
|
|
84
136
|
sel.type = 'color';
|
|
137
|
+
|
|
85
138
|
if (!isNaN(r + g + b))
|
|
86
139
|
sel.value = rgb2Hex(r, g, b);
|
|
87
140
|
|
|
@@ -148,3 +201,185 @@ const hex2RGB = (hex: string): [number, number, number] => {
|
|
|
148
201
|
// Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L117
|
|
149
202
|
const rgb2Hex = (r: number, g: number, b: number): string =>
|
|
150
203
|
'#' + r.toString(16) + g.toString(16) + b.toString(16);
|
|
204
|
+
|
|
205
|
+
const colorCircleTheme = EditorView.baseTheme({
|
|
206
|
+
'.color-circle-parent': { display: 'inline-block' },
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const colorCircleAnnotation = Annotation.define();
|
|
210
|
+
|
|
211
|
+
export const colorsInTextPlugin: Extension = [
|
|
212
|
+
ViewPlugin.fromClass(
|
|
213
|
+
class {
|
|
214
|
+
decorations: any;
|
|
215
|
+
view: EditorView;
|
|
216
|
+
constructor(view: EditorView) {
|
|
217
|
+
this.decorations = RangeSet.of([]);
|
|
218
|
+
this.view = view;
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
decorations: (v) => {
|
|
223
|
+
const colorInfos = [];
|
|
224
|
+
|
|
225
|
+
const lines = v.view.state.doc.iter();
|
|
226
|
+
let line = lines.next();
|
|
227
|
+
|
|
228
|
+
//Offset is the number of characters before the hex so the circle can be placed properly.
|
|
229
|
+
let offset = 0;
|
|
230
|
+
while (!line.done) {
|
|
231
|
+
if (line.value === '\n') {
|
|
232
|
+
offset++;
|
|
233
|
+
line = lines.next();
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const hexColorOccurances = line.value.matchAll(
|
|
237
|
+
/\"\#([0-9]|[A-F]|[a-f]){6}\"/g,
|
|
238
|
+
);
|
|
239
|
+
let hexOccurance = hexColorOccurances.next();
|
|
240
|
+
while (!hexOccurance.done) {
|
|
241
|
+
const offsetColorInfo = hexOccurance.value;
|
|
242
|
+
offsetColorInfo.index += offset;
|
|
243
|
+
colorInfos.push(offsetColorInfo);
|
|
244
|
+
hexOccurance = hexColorOccurances.next();
|
|
245
|
+
}
|
|
246
|
+
offset += line.value.length;
|
|
247
|
+
line = lines.next();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return Decoration.set(
|
|
251
|
+
colorInfos.map((colorInfo) => {
|
|
252
|
+
return {
|
|
253
|
+
//9 is the length of the hex color string including quotation marks
|
|
254
|
+
from: colorInfo.index + 9,
|
|
255
|
+
to: colorInfo.index + 9,
|
|
256
|
+
value: Decoration.widget({
|
|
257
|
+
side: -1,
|
|
258
|
+
widget: new ColorWidget(colorInfo[0]),
|
|
259
|
+
}),
|
|
260
|
+
};
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
),
|
|
266
|
+
colorCircleTheme,
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
class ColorWidget extends WidgetType {
|
|
270
|
+
color: string;
|
|
271
|
+
constructor(color: string) {
|
|
272
|
+
super();
|
|
273
|
+
this.color = color;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
eq(widget: ColorWidget): boolean {
|
|
277
|
+
// TODO consider possibly adding a random ID to support multiple instances with the same color
|
|
278
|
+
return widget.color === this.color;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
toDOM(view: EditorView): HTMLElement {
|
|
282
|
+
const parent = document.createElement('div');
|
|
283
|
+
|
|
284
|
+
parent.setAttribute('style', 'width:10px;height:10px');
|
|
285
|
+
parent.className = 'color-circle-parent';
|
|
286
|
+
const svg = document.createElementNS(
|
|
287
|
+
'http://www.w3.org/2000/svg',
|
|
288
|
+
'svg',
|
|
289
|
+
);
|
|
290
|
+
const colorCircle = document.createElementNS(
|
|
291
|
+
'http://www.w3.org/2000/svg',
|
|
292
|
+
'circle',
|
|
293
|
+
);
|
|
294
|
+
colorCircle.setAttributeNS(
|
|
295
|
+
null,
|
|
296
|
+
'fill',
|
|
297
|
+
this.color.replace(/\"/g, ''),
|
|
298
|
+
);
|
|
299
|
+
colorCircle.setAttributeNS(null, 'r', '5');
|
|
300
|
+
colorCircle.setAttributeNS(null, 'cx', '5');
|
|
301
|
+
colorCircle.setAttributeNS(null, 'cy', '5');
|
|
302
|
+
|
|
303
|
+
svg.setAttributeNS(null, 'viewBox', '0 0 10 10');
|
|
304
|
+
svg.setAttributeNS(null, 'width', '10');
|
|
305
|
+
svg.setAttributeNS(null, 'height', '10');
|
|
306
|
+
|
|
307
|
+
svg.appendChild(colorCircle);
|
|
308
|
+
|
|
309
|
+
parent.appendChild(svg);
|
|
310
|
+
return parent;
|
|
311
|
+
}
|
|
312
|
+
ignoreEvent() {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export const highlightWidgets = ViewPlugin.fromClass(
|
|
318
|
+
class {
|
|
319
|
+
showHighlight: boolean;
|
|
320
|
+
view: EditorView;
|
|
321
|
+
constructor(view: EditorView) {
|
|
322
|
+
this.showHighlight = false;
|
|
323
|
+
this.view = view;
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
decorations: (v) => {
|
|
328
|
+
if (!v.showHighlight) {
|
|
329
|
+
return Decoration.none;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const interactOpportunities = [];
|
|
333
|
+
const lines = v.view.state.doc.iter();
|
|
334
|
+
let line = lines.next();
|
|
335
|
+
|
|
336
|
+
//Offset is the number of characters before the regex so the highlighting can be placed properly.
|
|
337
|
+
let offset = 0;
|
|
338
|
+
while (!line.done) {
|
|
339
|
+
if (line.value === '\n') {
|
|
340
|
+
offset++;
|
|
341
|
+
line = lines.next();
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const interactiveOccurances = line.value.matchAll(
|
|
345
|
+
//The below line contains all of the Regexes for all of the interactive widgets.
|
|
346
|
+
/\"\#([0-9]|[A-F]|[a-f]){6}\"|rotate\(-?\d*\.?\d*\)|https?:\/\/[^ "]+|vec2\(-?\b\d+\.?\d*\b\s*(,\s*-?\b\d+\.?\d*\b)?\)|true|false|(?<!\#)-?\b\d+\.?\d*\b|rgb\(.*\)/dg,
|
|
347
|
+
);
|
|
348
|
+
let interactOccurance =
|
|
349
|
+
interactiveOccurances.next();
|
|
350
|
+
while (!interactOccurance.done) {
|
|
351
|
+
const offsetInteract = interactOccurance.value;
|
|
352
|
+
offsetInteract.indices[0][0] += offset;
|
|
353
|
+
offsetInteract.indices[0][1] += offset;
|
|
354
|
+
interactOpportunities.push(offsetInteract);
|
|
355
|
+
interactOccurance = interactiveOccurances.next();
|
|
356
|
+
}
|
|
357
|
+
offset += line.value.length;
|
|
358
|
+
line = lines.next();
|
|
359
|
+
}
|
|
360
|
+
return Decoration.set(
|
|
361
|
+
interactOpportunities.map((opportunity) => {
|
|
362
|
+
return {
|
|
363
|
+
from: opportunity.indices[0][0],
|
|
364
|
+
to: opportunity.indices[0][1],
|
|
365
|
+
value: Decoration.mark({
|
|
366
|
+
class: 'cm-interact ',
|
|
367
|
+
}),
|
|
368
|
+
};
|
|
369
|
+
}),
|
|
370
|
+
);
|
|
371
|
+
},
|
|
372
|
+
eventHandlers: {
|
|
373
|
+
keydown(event, view) {
|
|
374
|
+
if (event.key == 'Alt') {
|
|
375
|
+
this.showHighlight = true;
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
keyup(event, view) {
|
|
379
|
+
if (event.key == 'Alt') {
|
|
380
|
+
this.showHighlight = false;
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import './style.scss';
|
|
3
3
|
|
|
4
|
+
type PresenceId = string;
|
|
5
|
+
|
|
4
6
|
// A presence notification is a message that is displayed when a user joins or leaves a session.
|
|
5
7
|
// It has the following properties:
|
|
6
8
|
// - `user` is the username of the user who joined or left the session.
|
|
@@ -10,7 +12,7 @@ import './style.scss';
|
|
|
10
12
|
type PresenceNotification = {
|
|
11
13
|
user: string;
|
|
12
14
|
join: boolean;
|
|
13
|
-
presenceId:
|
|
15
|
+
presenceId: PresenceId;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
export const PresenceNotifications = ({
|
|
@@ -21,86 +23,117 @@ export const PresenceNotifications = ({
|
|
|
21
23
|
const [presenceNotifications, setPresenceNotifications] =
|
|
22
24
|
useState<Array<PresenceNotification>>([]);
|
|
23
25
|
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// const [join, setJoin] = useState<boolean>(true);
|
|
26
|
+
// const [presenceIds, setPresenceIds] = useState<
|
|
27
|
+
// Array<any>
|
|
28
|
+
// >([]);
|
|
29
|
+
const alreadyJoinedPresenceIds = useRef<Set<PresenceId>>(
|
|
30
|
+
new Set(),
|
|
31
|
+
);
|
|
31
32
|
|
|
32
33
|
useEffect(() => {
|
|
33
34
|
// console.log('useEffect triggered.');
|
|
34
35
|
if (docPresence) {
|
|
35
36
|
// See https://share.github.io/sharedb/presence
|
|
36
37
|
docPresence.on('receive', (presenceId, update) => {
|
|
38
|
+
// Reason for notifications appearing on every cursor movement:
|
|
39
|
+
// code doesn't check if a notification about a user joining was previously sent
|
|
40
|
+
|
|
41
|
+
// Attempted solutions:
|
|
42
|
+
// - create an array called presenceIds that keeps track of the presences that join and leave
|
|
43
|
+
// - if presenceId is in presenceIds: local user was already alerted that a remote user joined
|
|
44
|
+
// - if presenceId is not in presenceIds: local user needs to be notified about a new user
|
|
45
|
+
// - problems:
|
|
46
|
+
// - the array is always empty inside useEffect (array works outside of useEffect)
|
|
47
|
+
// - causes check to fail
|
|
48
|
+
// - check docPresence.remotePresences, which keeps track of all the remote presences on the doc
|
|
49
|
+
// - see https://share.github.io/sharedb/api/presence
|
|
50
|
+
// - if presenceId is in docPresence.remotePresences: local user was already alerted that a remote user joined
|
|
51
|
+
// - if presenceId is not in docPresence.remotePresences: local user needs to be notified about a new user
|
|
52
|
+
// - problems:
|
|
53
|
+
// - useEffects is called when docPresence changes (in some cases, it's when the remote presence changes)
|
|
54
|
+
// - remotePresences already has the remote presence in the array, so check fails
|
|
55
|
+
// - could add another property to presence (true: already notified, false: need to be notified)
|
|
56
|
+
// - doesn't seem like an ideal solution
|
|
57
|
+
|
|
58
|
+
// console.log('docPresence:', docPresence);
|
|
59
|
+
// console.log('docPresence remotePresences:', docPresence.remotePresences);
|
|
60
|
+
// console.log('check for presence:', docPresence.remotePresences[presenceId]);
|
|
61
|
+
|
|
37
62
|
// TODO figure out how to get username from `presenceId`.
|
|
38
63
|
const user = 'someone';
|
|
39
64
|
|
|
40
65
|
// `true` means user has joined the session.
|
|
41
66
|
// `false` means user has left the session.
|
|
42
|
-
const join = update
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
setPresenceNotifications((prev) => [
|
|
46
|
-
...prev,
|
|
47
|
-
{ user, join, presenceId },
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
// Remove it after 5 seconds.
|
|
51
|
-
setTimeout(() => {
|
|
52
|
-
setPresenceNotifications((prev) =>
|
|
53
|
-
prev.filter(
|
|
54
|
-
(notification) =>
|
|
55
|
-
notification.presenceId !== presenceId,
|
|
56
|
-
),
|
|
57
|
-
);
|
|
58
|
-
}, 5000);
|
|
59
|
-
});
|
|
67
|
+
const join = update !== null;
|
|
68
|
+
|
|
69
|
+
console.log('join', join);
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
// Figure out if we have ever seen this user before.
|
|
72
|
+
|
|
73
|
+
// if (join && docPresence.remotePresences[presenceId] === undefined){
|
|
74
|
+
if (join) {
|
|
75
|
+
if (
|
|
76
|
+
!alreadyJoinedPresenceIds.current.has(
|
|
77
|
+
presenceId,
|
|
78
|
+
)
|
|
79
|
+
) {
|
|
80
|
+
alreadyJoinedPresenceIds.current.add(
|
|
81
|
+
presenceId,
|
|
82
|
+
);
|
|
83
|
+
setPresenceNotifications((prev) => [
|
|
84
|
+
...prev,
|
|
85
|
+
{ user, join, presenceId },
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// Remove it after 5 seconds.
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
setPresenceNotifications((prev) =>
|
|
91
|
+
prev.filter(
|
|
92
|
+
(notification) =>
|
|
93
|
+
notification.presenceId !==
|
|
94
|
+
presenceId &&
|
|
95
|
+
notification.join !== join,
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
}, 5000);
|
|
99
|
+
}
|
|
100
|
+
} else if (!join) {
|
|
101
|
+
setPresenceNotifications((prev) => [
|
|
102
|
+
...prev,
|
|
103
|
+
{ user, join, presenceId },
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
// Remove it after 5 seconds.
|
|
107
|
+
// TODO clean up this duplication.
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
setPresenceNotifications((prev) =>
|
|
110
|
+
prev.filter(
|
|
111
|
+
(notification) =>
|
|
112
|
+
notification.presenceId !== presenceId &&
|
|
113
|
+
notification.join !== join,
|
|
114
|
+
),
|
|
115
|
+
);
|
|
116
|
+
}, 5000);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
87
119
|
}
|
|
88
120
|
}, [docPresence]);
|
|
89
121
|
|
|
90
|
-
console.log(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
);
|
|
122
|
+
// console.log(
|
|
123
|
+
// 'presenceNotifications',
|
|
124
|
+
// presenceNotifications,
|
|
125
|
+
// );
|
|
126
|
+
// console.log(
|
|
127
|
+
// 'presenceIds',
|
|
128
|
+
// presenceIds,
|
|
129
|
+
// );
|
|
94
130
|
|
|
95
131
|
return presenceNotifications.length > 0 ? (
|
|
96
132
|
<div className="vz-notification">
|
|
97
|
-
{presenceNotifications.map((notification) => {
|
|
133
|
+
{presenceNotifications.map((notification, i) => {
|
|
98
134
|
const { user, join } = notification;
|
|
99
135
|
return (
|
|
100
|
-
<div
|
|
101
|
-
key={notification.presenceId}
|
|
102
|
-
className="vz-notification-item"
|
|
103
|
-
>
|
|
136
|
+
<div key={i} className="vz-notification-item">
|
|
104
137
|
{join
|
|
105
138
|
? `${user} has joined the session.`
|
|
106
139
|
: `${user} has left the session.`}
|
|
@@ -109,20 +142,4 @@ export const PresenceNotifications = ({
|
|
|
109
142
|
})}
|
|
110
143
|
</div>
|
|
111
144
|
) : null;
|
|
112
|
-
// return null;
|
|
113
|
-
|
|
114
|
-
// return (
|
|
115
|
-
// <Modal
|
|
116
|
-
// className="vz-notification"
|
|
117
|
-
// show={visible}
|
|
118
|
-
// onHide={() => setVisible(false)}
|
|
119
|
-
// animation={false}
|
|
120
|
-
// >
|
|
121
|
-
// <Modal.Body>
|
|
122
|
-
// {join
|
|
123
|
-
// ? `${user} has joined the session.`
|
|
124
|
-
// : `${user} has left the session.`}
|
|
125
|
-
// </Modal.Body>
|
|
126
|
-
// </Modal>
|
|
127
|
-
// );
|
|
128
145
|
};
|
package/src/client/Settings.tsx
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import { useCallback, useRef } from 'react';
|
|
2
2
|
import { Button, Modal, Form } from './bootstrap';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
ThemeLabel,
|
|
6
|
-
themeOptionsByLabel,
|
|
7
|
-
themes,
|
|
8
|
-
} from './themes';
|
|
3
|
+
import { ThemeLabel, themes } from './themes';
|
|
9
4
|
|
|
10
|
-
const saveTimes = [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
];
|
|
5
|
+
// const saveTimes = [
|
|
6
|
+
// { value: 1, label: '1 second' },
|
|
7
|
+
// { value: 5, label: '5 seconds' },
|
|
8
|
+
// { value: 10, label: '10 seconds' },
|
|
9
|
+
// { value: 30, label: '30 seconds' },
|
|
10
|
+
// ];
|
|
16
11
|
|
|
17
12
|
export let username = 'Anonymous';
|
|
18
13
|
|
|
@@ -27,23 +22,26 @@ export const Settings = ({
|
|
|
27
22
|
setTheme: (theme: ThemeLabel) => void;
|
|
28
23
|
theme: ThemeLabel;
|
|
29
24
|
}) => {
|
|
30
|
-
const
|
|
31
|
-
|
|
25
|
+
const handleThemeChange = useCallback((event) => {
|
|
26
|
+
const selectedValue = event.target.value;
|
|
27
|
+
console.log(selectedValue);
|
|
28
|
+
setTheme(selectedValue);
|
|
32
29
|
}, []);
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
// TODO https://github.com/vizhub-core/vzcode/issues/95
|
|
32
|
+
// const handleSaveTimeChange = useCallback(
|
|
33
|
+
// (selectedOption) => {
|
|
34
|
+
// const time = [{ value: selectedOption.value }];
|
|
35
|
+
// fetch('/saveTime', {
|
|
36
|
+
// method: 'POST',
|
|
37
|
+
// headers: {
|
|
38
|
+
// 'Content-Type': 'application/json',
|
|
39
|
+
// },
|
|
40
|
+
// body: JSON.stringify(time),
|
|
41
|
+
// });
|
|
42
|
+
// },
|
|
43
|
+
// [],
|
|
44
|
+
// );
|
|
47
45
|
|
|
48
46
|
const usernameRef = useRef<HTMLInputElement>(null);
|
|
49
47
|
const handleUsernameChange = () => {
|
|
@@ -76,16 +74,26 @@ export const Settings = ({
|
|
|
76
74
|
</Form.Group>
|
|
77
75
|
<Form.Group className="mb-3" controlId="formFork">
|
|
78
76
|
<Form.Label>Theme</Form.Label>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
value={
|
|
83
|
-
|
|
77
|
+
|
|
78
|
+
<select
|
|
79
|
+
className="form-select"
|
|
80
|
+
value={theme}
|
|
81
|
+
onChange={handleThemeChange}
|
|
82
|
+
>
|
|
83
|
+
{themes.map(({ label }) => (
|
|
84
|
+
<option key={label} value={label}>
|
|
85
|
+
{label}
|
|
86
|
+
</option>
|
|
87
|
+
))}
|
|
88
|
+
</select>
|
|
89
|
+
|
|
84
90
|
<Form.Text className="text-muted">
|
|
85
91
|
Select a color theme for the editor
|
|
86
92
|
</Form.Text>
|
|
87
93
|
</Form.Group>
|
|
88
|
-
|
|
94
|
+
{/*
|
|
95
|
+
TODO https://github.com/vizhub-core/vzcode/issues/95
|
|
96
|
+
<Form.Group className="mb-3" controlId="formFork">
|
|
89
97
|
<Form.Label>Auto-Save Time</Form.Label>
|
|
90
98
|
<Select
|
|
91
99
|
options={saveTimes}
|
|
@@ -94,7 +102,7 @@ export const Settings = ({
|
|
|
94
102
|
<Form.Text className="text-muted">
|
|
95
103
|
Select an auto save time
|
|
96
104
|
</Form.Text>
|
|
97
|
-
</Form.Group>
|
|
105
|
+
</Form.Group> */}
|
|
98
106
|
</Modal.Body>
|
|
99
107
|
<Modal.Footer>
|
|
100
108
|
<Button variant="primary" onClick={onClose}>
|
|
@@ -33,10 +33,13 @@ export const DirectoryListing = ({
|
|
|
33
33
|
toggleDirectory(path);
|
|
34
34
|
}, [toggleDirectory]);
|
|
35
35
|
|
|
36
|
-
const handleDeleteClick = useCallback(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
const handleDeleteClick = useCallback(
|
|
37
|
+
(event) => {
|
|
38
|
+
// https://github.com/vizhub-core/vzcode/issues/102
|
|
39
|
+
handleDeleteFileClick(path, event);
|
|
40
|
+
},
|
|
41
|
+
[handleDeleteFileClick, path],
|
|
42
|
+
);
|
|
40
43
|
|
|
41
44
|
const handleRenameClick = useCallback(() => {
|
|
42
45
|
// https://github.com/vizhub-core/vzcode/issues/103
|