vzcode 0.24.0 → 0.25.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/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-9c8bbae0.js"></script>
31
+ <script type="module" crossorigin src="/assets/index-8628ebab.js"></script>
32
32
  <link rel="stylesheet" href="/assets/index-cf19de3b.css">
33
33
  </head>
34
34
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -53,6 +53,7 @@
53
53
  "@codemirror/state": "^6.3.1",
54
54
  "@codemirror/theme-one-dark": "^6.1.2",
55
55
  "@codemirror/view": "^6.21.3",
56
+ "@lezer/highlight": "^1.1.6",
56
57
  "@replit/codemirror-interact": "^6.3.0",
57
58
  "@teamwork/websocket-json-stream": "^2.0.0",
58
59
  "@uiw/codemirror-theme-abcdef": "^4.21.20",
@@ -63,6 +64,7 @@
63
64
  "@uiw/codemirror-theme-nord": "^4.21.20",
64
65
  "@uiw/codemirror-theme-okaidia": "^4.21.20",
65
66
  "@uiw/codemirror-theme-xcode": "^4.21.20",
67
+ "@uiw/codemirror-themes": "^4.21.20",
66
68
  "codemirror": "^6.0.1",
67
69
  "codemirror-ot": "^4.1.0",
68
70
  "d3-array": "^3.2.4",
@@ -20,10 +20,12 @@ import {
20
20
  } from '../types';
21
21
  import { TabList } from './TabList';
22
22
  import { useOpenDirectories } from './useOpenDirectories';
23
- import { useTabsState } from './useTabsState';
24
- import { ThemeLabel, defaultTheme } from './themes';
23
+ import {
24
+ ThemeLabel,
25
+ defaultTheme,
26
+ useDynamicTheme,
27
+ } from './themes';
25
28
  import { useEditorCache } from './useEditorCache';
26
- import { useDynamicTheme } from './useDynamicTheme';
27
29
  import { usePrettier } from './usePrettier';
28
30
  // @ts-ignore
29
31
  import PrettierWorker from './usePrettier/worker?worker';
@@ -153,76 +155,92 @@ function App() {
153
155
  };
154
156
  }, []);
155
157
 
156
- // The id of the currently open file tab.
157
- // TODO make this a URL param
158
- // Migrated to useReducer
159
- //const [activeFileId, setActiveFileId] =
160
- //useState<FileId | null>(null);
161
-
162
- // The ordered list of tabs.
163
- // TODO make this a URL param
164
158
  // https://react.dev/reference/react/useReducer
165
- // const [tabList, setTabList] = useState<Array<FileId>>([]);
166
159
  const [state, dispatch] = useReducer(reducer, {
167
- submitOperation,
168
160
  tabList: [],
169
- // activeFileId: null,
170
- // theme: defaultTheme,
171
- // isSettingsOpen:false
161
+ activeFileId: null,
162
+ theme: defaultTheme,
163
+ isSettingsOpen: false,
172
164
  });
173
165
 
174
166
  //Reducer states
175
167
  const tabList = state.tabList;
176
168
  const activeFileId = state.activeFileId;
169
+ const theme = state.theme;
170
+ const isSettingsOpen = state.isSettingsOpen;
177
171
  // TODO phase this out as we complete the refactoring
178
172
  // It's here now for backwards compatibility
179
- const setTabList = (tabList) => {
180
- dispatch({
181
- type: 'set_tab_list',
182
- tabList,
183
- });
184
- };
185
173
 
186
- const setActiveFileId = (activeFileId) => {
187
- dispatch({
188
- type: 'set_active_fileId',
189
- activeFileId,
190
- });
191
- };
192
- const openTab = (fileId: FileId) => {
193
- dispatch({
194
- type: 'open_tab',
195
- fileId,
196
- });
197
- };
174
+ const setTabList = useCallback(
175
+ (tabList) => {
176
+ dispatch({
177
+ type: 'set_tab_list',
178
+ tabList,
179
+ });
180
+ },
181
+ [dispatch],
182
+ );
198
183
 
199
- const closeTab = (fileIdToRemove: FileId) => {
200
- dispatch({
201
- type: 'close_tab',
202
- fileIdToRemove,
203
- });
204
- };
184
+ const setActiveFileId = useCallback(
185
+ (activeFileId) => {
186
+ dispatch({
187
+ type: 'set_active_fileId',
188
+ activeFileId,
189
+ });
190
+ },
191
+ [dispatch],
192
+ );
205
193
 
206
- const multiCloseTab = (idsToDelete: Array<FileId>) => {
207
- dispatch({
208
- type: 'multi_close_tab',
209
- idsToDelete,
210
- });
211
- };
212
-
213
- // Logic for opening and closing tabs. Migrated to useReducer
214
- /*
215
- const {} = useTabsState(
216
- activeFileId,
217
- setActiveFileId,
218
- tabList,
219
- setTabList,
220
- ); */
221
-
222
- // The current theme.
223
- // TODO persist this in local storage
224
- const [theme, setTheme] =
225
- useState<ThemeLabel>(defaultTheme);
194
+ const openTab = useCallback(
195
+ (fileId: FileId) => {
196
+ dispatch({
197
+ type: 'open_tab',
198
+ fileId,
199
+ });
200
+ },
201
+ [dispatch],
202
+ );
203
+
204
+ const closeTab = useCallback(
205
+ (fileIdToRemove: FileId) => {
206
+ dispatch({
207
+ type: 'close_tab',
208
+ fileIdToRemove,
209
+ });
210
+ },
211
+ [dispatch],
212
+ );
213
+
214
+ const multiCloseTab = useCallback(
215
+ (idsToDelete: Array<FileId>) => {
216
+ dispatch({
217
+ type: 'multi_close_tab',
218
+ idsToDelete,
219
+ });
220
+ },
221
+ [dispatch],
222
+ );
223
+
224
+ const setTheme = useCallback(
225
+ (themeLabel: ThemeLabel) => {
226
+ dispatch({
227
+ type: 'set_Theme',
228
+ themeLabel: themeLabel,
229
+ });
230
+ },
231
+ [dispatch],
232
+ );
233
+
234
+ // True to show the settings modal.
235
+ const setIsSettingsOpen = useCallback(
236
+ (value: boolean) => {
237
+ dispatch({
238
+ type: 'set_Is_Settings_Open',
239
+ value: value,
240
+ });
241
+ },
242
+ [dispatch],
243
+ );
226
244
 
227
245
  // Cache of CodeMirror editors by file id.
228
246
  const editorCache = useEditorCache();
@@ -230,10 +248,6 @@ function App() {
230
248
  // Handle dynamic theme changes.
231
249
  useDynamicTheme(editorCache, theme);
232
250
 
233
- // True to show the settings modal.
234
- const [isSettingsOpen, setIsSettingsOpen] =
235
- useState(false);
236
-
237
251
  // The set of open directories.
238
252
  const { isDirectoryOpen, toggleDirectory } =
239
253
  useOpenDirectories();
@@ -19,6 +19,7 @@ import { json1PresenceDisplay } from './json1PresenceDisplay';
19
19
  import {
20
20
  colorsInTextPlugin,
21
21
  highlightWidgets,
22
+ rotationIndicator,
22
23
  widgets,
23
24
  } from './widgets';
24
25
  import {
@@ -192,6 +193,8 @@ export const getOrCreateEditor = ({
192
193
 
193
194
  extensions.push(highlightWidgets);
194
195
 
196
+ extensions.push(rotationIndicator);
197
+
195
198
  const editor = new EditorView({
196
199
  state: EditorState.create({
197
200
  doc: text,
@@ -166,22 +166,27 @@ export const widgets = ({
166
166
  regexp: /rotate\(-?\d*\.?\d*\)/g,
167
167
  cursor: 'move',
168
168
  onDragStart(text, setText, e) {
169
- rotationOrigin = { x: e.screenX, y: e.screenY };
169
+ rotationOrigin = { x: e.clientX, y: e.clientY };
170
170
  },
171
171
 
172
172
  onDrag(text, setText, e) {
173
173
  if (rotationOrigin == null) return;
174
+ const rotationDegree = Math.round(
175
+ (Math.atan2(
176
+ rotationOrigin.y - e.clientY,
177
+ e.clientX - rotationOrigin.x,
178
+ ) *
179
+ 180) /
180
+ Math.PI,
181
+ );
174
182
  //Calculate the angle between the x axis and a line from where the user first clicks to the current location of the mouse.
175
- setText(
176
- `rotate(${Math.round(
177
- (Math.atan2(
178
- rotationOrigin.y - e.screenY,
179
- e.screenX - rotationOrigin.x,
180
- ) *
181
- 180) /
182
- Math.PI,
183
- )})`,
183
+ setText(`rotate(${rotationDegree})`);
184
+ const updateDragEvent = new CustomEvent(
185
+ 'updateRotateDrag',
186
+ { detail: rotationDegree },
184
187
  );
188
+
189
+ document.dispatchEvent(updateDragEvent);
185
190
  },
186
191
  onDragEnd(text, setText) {
187
192
  rotationOrigin = null;
@@ -383,3 +388,100 @@ export const highlightWidgets = ViewPlugin.fromClass(
383
388
  },
384
389
  },
385
390
  );
391
+
392
+ export const rotationIndicator = ViewPlugin.fromClass(
393
+ class {
394
+ view: EditorView;
395
+ textPosition?: number;
396
+ rotation: number;
397
+ constructor(view) {
398
+ this.view = view;
399
+ this.textPosition = null;
400
+ this.rotation = 0;
401
+
402
+ document.addEventListener(
403
+ 'updateRotateDrag',
404
+ (e: CustomEvent) => {
405
+ this.textPosition = this.view.posAtCoords(
406
+ rotationOrigin,
407
+ false,
408
+ );
409
+
410
+ this.rotation = e.detail;
411
+ },
412
+ );
413
+ }
414
+ },
415
+ {
416
+ decorations: (v) => {
417
+ if (rotationOrigin === null) {
418
+ return Decoration.none;
419
+ }
420
+ return Decoration.set([
421
+ {
422
+ from: v.textPosition,
423
+ to: v.textPosition,
424
+ value: Decoration.widget({
425
+ side: -1,
426
+ widget: new RotationCircle(v.rotation),
427
+ }),
428
+ },
429
+ ]);
430
+ },
431
+ },
432
+ );
433
+
434
+ class RotationCircle extends WidgetType {
435
+ angle: number;
436
+ constructor(angle: number) {
437
+ super();
438
+ this.angle = angle;
439
+ }
440
+
441
+ toDOM(view: EditorView): HTMLElement {
442
+ const parent = document.createElement('div');
443
+
444
+ parent.setAttribute('style', 'width:20px;height:20px');
445
+ parent.className = 'color-circle-parent';
446
+ const svg = document.createElementNS(
447
+ 'http://www.w3.org/2000/svg',
448
+ 'svg',
449
+ );
450
+ const colorCircle = document.createElementNS(
451
+ 'http://www.w3.org/2000/svg',
452
+ 'circle',
453
+ );
454
+ colorCircle.setAttributeNS(null, 'fill', '#808080');
455
+ colorCircle.setAttributeNS(null, 'r', '5');
456
+ colorCircle.setAttributeNS(null, 'cx', '5');
457
+ colorCircle.setAttributeNS(null, 'cy', '5');
458
+
459
+ const indicatorLine = document.createElementNS(
460
+ 'http://www.w3.org/2000/svg',
461
+ 'line',
462
+ );
463
+ indicatorLine.setAttributeNS(null, 'x1', '10');
464
+ indicatorLine.setAttributeNS(null, 'x2', '5');
465
+ indicatorLine.setAttributeNS(null, 'y1', '5');
466
+ indicatorLine.setAttributeNS(null, 'y2', '5');
467
+ indicatorLine.setAttributeNS(null, 'stroke', 'black');
468
+ indicatorLine.setAttributeNS(
469
+ null,
470
+ 'transform',
471
+ `rotate(${360 - this.angle},5,5)`,
472
+ );
473
+
474
+ svg.setAttributeNS(null, 'viewBox', '0 0 10 10');
475
+ svg.setAttributeNS(null, 'width', '20');
476
+ svg.setAttributeNS(null, 'height', '20');
477
+
478
+ svg.appendChild(colorCircle);
479
+ svg.appendChild(indicatorLine);
480
+
481
+ parent.appendChild(svg);
482
+ return parent;
483
+ }
484
+ ignoreEvent() {
485
+ return false;
486
+ }
487
+ }
@@ -19,9 +19,9 @@ export const Item = ({
19
19
  const [renameValue, setRenameValue] = useState('');
20
20
 
21
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') {
22
+ if (event.key === 'Escape') {
23
+ setIsRenaming(false);
24
+ } else if (event.key === 'Enter') {
25
25
  event.target.blur();
26
26
  }
27
27
  }, []);
@@ -1,11 +1,16 @@
1
1
  import { FileId } from '../types';
2
+ import { act } from 'react-dom/test-utils';
2
3
 
3
4
  export const reducer = (state, action) => {
4
5
  switch (action.type) {
5
6
  // TODO phase this out
7
+
8
+ // The ordered list of tabs.
9
+ // TODO make this a URL param
6
10
  case 'set_tab_list': {
7
11
  return { ...state, tabList: action.tabList };
8
12
  }
13
+ // TODO make this a URL param
9
14
  case 'set_active_fileId': {
10
15
  return {
11
16
  ...state,
@@ -95,28 +100,20 @@ export const reducer = (state, action) => {
95
100
  activeFileId: newActiveFileId,
96
101
  };
97
102
  }
98
- // TODO eventually
99
- // case 'init_sharedb': {
100
- // return {
101
- // ...state,
102
- // submitOperation: action.submitOperation
103
- // }
104
- // }
105
- // case 'delete_file': {
106
- // case 'delete_directory': {
107
- // case 'create_file': {
108
- // state.submitOperation((document) => ({
109
- // ...document,
110
- // files: {
111
- // ...document.files,
112
- // [randomId()]: { name:action.name, text: '' },
113
- // },
114
- // }))
115
- // return state;
116
-
117
- // }
118
- // case 'rename_file': {
119
- // ...
103
+ // The current theme.
104
+ // TODO persist this in local storage
105
+ case 'set_Theme': {
106
+ return {
107
+ ...state,
108
+ theme: action.themeLabel,
109
+ };
110
+ }
111
+ case 'set_Is_Settings_Open': {
112
+ return {
113
+ ...state,
114
+ isSettingsOpen: action.value,
115
+ };
116
+ }
120
117
  }
121
118
  throw Error('Unknown action: ' + action.type);
122
119
  };
@@ -0,0 +1,7 @@
1
+ export {
2
+ themes,
3
+ defaultTheme,
4
+ themeOptionsByLabel,
5
+ } from './themes';
6
+ export type { ThemeLabel, ThemeOption } from './themes';
7
+ export { useDynamicTheme } from './useDynamicTheme';
@@ -7,7 +7,7 @@ import { githubDark } from '@uiw/codemirror-theme-github';
7
7
  import { material } from '@uiw/codemirror-theme-material';
8
8
  import { nord } from '@uiw/codemirror-theme-nord';
9
9
  import { xcodeLight } from '@uiw/codemirror-theme-xcode';
10
- // import { vizhub } from './vizhub-theme';
10
+ import { vizhubTheme } from './vizhubTheme';
11
11
 
12
12
  // CodeMirror themes
13
13
  export type ThemeLabel =
@@ -19,8 +19,8 @@ export type ThemeLabel =
19
19
  | 'oneDark'
20
20
  | 'okaidia'
21
21
  | 'github'
22
- | 'xcode';
23
- // | 'vizhub'
22
+ | 'xcode'
23
+ | 'vizhub';
24
24
 
25
25
  export type ThemeOption = {
26
26
  value: any;
@@ -37,7 +37,7 @@ export const themes: Array<ThemeOption> = [
37
37
  { value: okaidia, label: 'okaidia' },
38
38
  { value: githubDark, label: 'github' },
39
39
  { value: xcodeLight, label: 'xcode' },
40
- // { value: vizhub, label: 'vizhub' },
40
+ { value: vizhubTheme, label: 'vizhub' },
41
41
  ];
42
42
 
43
43
  // Map theme labels to theme values
@@ -53,4 +53,4 @@ export const themeOptionsByLabel: Record<
53
53
  );
54
54
 
55
55
  // The default theme
56
- export const defaultTheme: ThemeLabel = 'oneDark';
56
+ export const defaultTheme: ThemeLabel = 'vizhub';
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef } from 'react';
2
- import { EditorCache } from './useEditorCache';
2
+ import { EditorCache } from '../useEditorCache';
3
3
  import { ThemeLabel, themeOptionsByLabel } from './themes';
4
4
 
5
5
  export const useDynamicTheme = (
@@ -0,0 +1,80 @@
1
+ // import { hcl } from 'd3-color';
2
+
3
+ // This module defines all the colors for the VizHub theme.
4
+ // Ported from the CodeMirror 5 theme defined here:
5
+ // https://github.com/vizhub-core/vizhub-legacy/blob/01cadfb78a2611df32f981b1fd8136b70447de9e/vizhub-v2/packages/neoFrontend/src/pages/VizPage/Body/Editor/themes/vizHub.js
6
+
7
+ // This is how the colors were generated:
8
+ // const sidebarDark = hcl('#3d4b65');
9
+ // const sidebarLight = hcl('#5b677d');
10
+ // const luminaceDifference = sidebarLight.l - sidebarDark.l;
11
+ // const backgroundLuminance =
12
+ // sidebarDark.l - luminaceDifference * 1.1;
13
+ // const luminance = 90;
14
+ // export const backgroundColor = hcl(
15
+ // sidebarDark.h,
16
+ // sidebarDark.c,
17
+ // backgroundLuminance,
18
+ // ).formatHex();
19
+ // export const light = hcl(0, 0, luminance).formatHex();
20
+ // export const dark = hcl(
21
+ // sidebarDark.h,
22
+ // sidebarDark.c,
23
+ // 80,
24
+ // ).formatHex();
25
+ // console.log(
26
+ // JSON.stringify(
27
+ // {
28
+ // backgroundColor,
29
+ // light,
30
+ // dark,
31
+ // },
32
+ // null,
33
+ // 2,
34
+ // ),
35
+ // );
36
+
37
+ // These are inlined from the above code, but the code is kept around for future reference.
38
+ export const backgroundColor = '#202e46';
39
+ export const light = '#e2e2e2';
40
+ export const dark = '#b9c7e6';
41
+
42
+ export const lineNumbers = 'rgba(255,255,255,0.2)';
43
+ export const lineNumbersActive = 'rgba(255,255,255,0.5)';
44
+ export const selectionBackground = '#000';
45
+ export const selectionBackgroundMatch = 'rgba(0,0,0,0.4)';
46
+ export const lineHighlight = 'rgba(0,0,0,0.1)';
47
+ export const caretColor = 'white';
48
+
49
+ // Compute 6 colors that are evenly spaced in terms of hue
50
+ // Keeping this code around for future reference
51
+ // const rotation = 0.397;
52
+ // const numColors = 6;
53
+ // const highlightColors = Array.from(
54
+ // { length: numColors },
55
+ // (_, i) => {
56
+ // let t = (i / numColors + rotation) % 1;
57
+ // return hcl(t * 360, saturation, luminance).formatHex();
58
+ // },
59
+ // );
60
+ // console.log(JSON.stringify(highlightColors));
61
+
62
+ // We inline the colors here, but the code above is how they were generated.
63
+ const highlightColors = [
64
+ '#77fd8c',
65
+ '#00ffff',
66
+ '#66ecff',
67
+ '#ffb9ff',
68
+ '#ffabb3',
69
+ '#ffdb56',
70
+ ];
71
+
72
+ export const MINT = highlightColors[0];
73
+ export const AQUA = highlightColors[1];
74
+ export const SKY = highlightColors[2];
75
+ export const LAVENDER = highlightColors[3];
76
+ export const SALMON = highlightColors[4];
77
+ export const GOLDENROD = highlightColors[5];
78
+
79
+ // Red that's not too bright, for errors
80
+ export const PANIC = '#ff2222';