vzcode 0.26.0 → 0.27.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.
Files changed (48) hide show
  1. package/dist/assets/index-5f9f376e.css +1 -0
  2. package/dist/assets/index-70b6d947.js +111 -0
  3. package/dist/index.html +2 -2
  4. package/package.json +4 -4
  5. package/src/client/App.tsx +86 -43
  6. package/src/client/CodeEditor/getOrCreateEditor.ts +6 -1
  7. package/src/client/CodeEditor/index.tsx +60 -12
  8. package/src/client/CodeEditor/json1PresenceBroadcast.ts +6 -2
  9. package/src/client/CodeEditor/json1PresenceDisplay.ts +0 -45
  10. package/src/client/CodeEditor/style.scss +5 -1
  11. package/src/client/CodeErrorOverlay/index.tsx +27 -0
  12. package/src/client/CodeErrorOverlay/style.scss +30 -0
  13. package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
  14. package/src/client/PresenceNotifications/index.tsx +118 -103
  15. package/src/client/SplitPaneResizeContext.tsx +12 -7
  16. package/src/client/TabList/Tab.tsx +20 -6
  17. package/src/client/TabList/index.tsx +18 -8
  18. package/src/client/TabList/style.scss +5 -0
  19. package/src/client/VZSettings.tsx +33 -20
  20. package/src/client/VZSidebar/{ConfirmationBox.tsx → DeleteConfirmationModal.tsx} +17 -14
  21. package/src/client/VZSidebar/DirectoryListing.tsx +27 -26
  22. package/src/client/VZSidebar/FileListing.tsx +16 -19
  23. package/src/client/VZSidebar/Item.tsx +83 -37
  24. package/src/client/VZSidebar/Listing.tsx +17 -14
  25. package/src/client/VZSidebar/index.tsx +43 -24
  26. package/src/client/style.scss +3 -0
  27. package/src/client/useActions.ts +25 -6
  28. package/src/client/useEditorCache.ts +2 -1
  29. package/src/client/useFileCRUD.ts +29 -27
  30. package/src/client/usernameLocalStorage.ts +43 -0
  31. package/src/client/vzReducer/closeTabReducer.test.ts +177 -0
  32. package/src/client/vzReducer/closeTabsReducer.ts +68 -0
  33. package/src/client/vzReducer/createInitialState.ts +19 -0
  34. package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
  35. package/src/client/vzReducer/index.ts +107 -0
  36. package/src/client/vzReducer/openTabReducer.test.ts +110 -0
  37. package/src/client/vzReducer/openTabReducer.ts +57 -0
  38. package/src/client/vzReducer/setActiveFileIdReducer.ts +9 -0
  39. package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
  40. package/src/client/vzReducer/setThemeReducer.ts +9 -0
  41. package/src/client/vzReducer/setUsernameReducer.ts +9 -0
  42. package/src/index.ts +9 -3
  43. package/src/types.ts +12 -5
  44. package/dist/assets/index-284b7339.js +0 -111
  45. package/dist/assets/index-e160f758.css +0 -1
  46. package/src/client/PrettierErrorOverlay/index.tsx +0 -9
  47. package/src/client/PrettierErrorOverlay/style.scss +0 -9
  48. package/src/client/vzReducer.ts +0 -118
package/dist/index.html CHANGED
@@ -24,8 +24,8 @@
24
24
  href="https://unpkg.com/boxicons@2.0.7/css/boxicons.min.css"
25
25
  rel="stylesheet"
26
26
  />
27
- <script type="module" crossorigin src="/assets/index-284b7339.js"></script>
28
- <link rel="stylesheet" href="/assets/index-e160f758.css">
27
+ <script type="module" crossorigin src="/assets/index-70b6d947.js"></script>
28
+ <link rel="stylesheet" href="/assets/index-5f9f376e.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -51,7 +51,7 @@
51
51
  "@codemirror/lang-markdown": "^6.2.2",
52
52
  "@codemirror/state": "^6.3.1",
53
53
  "@codemirror/theme-one-dark": "^6.1.2",
54
- "@codemirror/view": "^6.21.3",
54
+ "@codemirror/view": "^6.21.4",
55
55
  "@lezer/highlight": "^1.1.6",
56
56
  "@replit/codemirror-interact": "^6.3.0",
57
57
  "@teamwork/websocket-json-stream": "^2.0.0",
@@ -83,11 +83,11 @@
83
83
  "ws": "^8.14.2"
84
84
  },
85
85
  "devDependencies": {
86
- "@types/react": "^18.2.31",
86
+ "@types/react": "^18.2.33",
87
87
  "@types/react-dom": "^18.2.14",
88
88
  "@vitejs/plugin-react": "^4.1.0",
89
89
  "prettier": "^3.0.3",
90
- "sass": "^1.69.4",
90
+ "sass": "^1.69.5",
91
91
  "typescript": "^5.2.2",
92
92
  "vite": "^4.5.0",
93
93
  "vitest": "^0.34.6"
@@ -1,33 +1,39 @@
1
- import {
2
- useState,
3
- useEffect,
4
- useCallback,
5
- useRef,
6
- useReducer,
7
- } from 'react';
1
+ import { useState, useEffect, useReducer } from 'react';
8
2
  import ShareDBClient from 'sharedb-client-browser/dist/sharedb-client-umd.cjs';
9
3
  import { json1Presence } from '../ot';
10
4
  import { randomId } from '../randomId';
11
5
  import { CodeEditor } from './CodeEditor';
12
6
  import { VZSettings } from './VZSettings';
13
7
  import { VZSidebar } from './VZSidebar';
14
- import { Files, ShareDBDoc, VZCodeContent } from '../types';
8
+ import {
9
+ Files,
10
+ ShareDBDoc,
11
+ Username,
12
+ VZCodeContent,
13
+ } from '../types';
15
14
  import { TabList } from './TabList';
16
15
  import { useOpenDirectories } from './useOpenDirectories';
17
16
  import { defaultTheme, useDynamicTheme } from './themes';
18
- import { useEditorCache } from './useEditorCache';
17
+ import {
18
+ EditorCache,
19
+ useEditorCache,
20
+ } from './useEditorCache';
19
21
  import { usePrettier } from './usePrettier';
20
22
  // @ts-ignore
21
23
  import PrettierWorker from './usePrettier/worker?worker';
22
24
  import { SplitPaneResizeProvider } from './SplitPaneResizeContext';
23
25
  import { Resizer } from './Resizer';
24
26
  import { PresenceNotifications } from './PresenceNotifications';
25
- import { PrettierErrorOverlay } from './PrettierErrorOverlay';
26
- import { vzReducer } from './vzReducer';
27
+ import { CodeErrorOverlay } from './CodeErrorOverlay';
28
+ import { vzReducer, createInitialState } from './vzReducer';
27
29
  import { useActions } from './useActions';
28
30
  import { useFileCRUD } from './useFileCRUD';
29
- import './style.scss';
30
31
  import { useSubmitOperation } from './useSubmitOperation';
32
+ import {
33
+ useInitialUsername,
34
+ usePersistUsername,
35
+ } from './usernameLocalStorage';
36
+ import './style.scss';
31
37
 
32
38
  // Instantiate the Prettier worker.
33
39
  const prettierWorker = new PrettierWorker();
@@ -53,17 +59,6 @@ function App() {
53
59
  const [shareDBDoc, setShareDBDoc] =
54
60
  useState<ShareDBDoc<VZCodeContent> | null>(null);
55
61
 
56
- const submitOperation: (
57
- next: (content: VZCodeContent) => VZCodeContent,
58
- ) => void = useSubmitOperation(shareDBDoc);
59
-
60
- // Auto-run Pretter after local changes.
61
- const { prettierError } = usePrettier(
62
- shareDBDoc,
63
- submitOperation,
64
- prettierWorker,
65
- );
66
-
67
62
  // Local ShareDB presence, for broadcasting our cursor position
68
63
  // so other clients can see it.
69
64
  // See https://share.github.io/sharedb/api/local-presence
@@ -127,7 +122,14 @@ function App() {
127
122
  });
128
123
 
129
124
  // Set up our local presence for broadcasting this client's presence.
130
- setLocalPresence(docPresence.create(randomId()));
125
+ const generateTimestampedId = () => {
126
+ const timestamp = Date.now().toString(36);
127
+ const randomPart = randomId();
128
+ return `${timestamp}-${randomPart}`;
129
+ };
130
+ setLocalPresence(
131
+ docPresence.create(generateTimestampedId()),
132
+ );
131
133
 
132
134
  // Store docPresence so child components can listen for changes.
133
135
  setDocPresence(docPresence);
@@ -141,17 +143,40 @@ function App() {
141
143
  };
142
144
  }, []);
143
145
 
146
+ // Get the initial username from localStorage.
147
+ const initialUsername: Username = useInitialUsername();
148
+
149
+ const submitOperation: (
150
+ next: (content: VZCodeContent) => VZCodeContent,
151
+ ) => void = useSubmitOperation(shareDBDoc);
152
+
153
+ // Auto-run Pretter after local changes.
154
+ const {
155
+ prettierError,
156
+ }: {
157
+ prettierError: string | null;
158
+ } = usePrettier(
159
+ shareDBDoc,
160
+ submitOperation,
161
+ prettierWorker,
162
+ );
163
+
144
164
  // https://react.dev/reference/react/useReducer
145
- const [state, dispatch] = useReducer(vzReducer, {
146
- tabList: [],
147
- activeFileId: null,
148
- theme: defaultTheme,
149
- isSettingsOpen: false,
150
- });
165
+ const [state, dispatch] = useReducer(
166
+ vzReducer,
167
+ { defaultTheme, initialUsername },
168
+ createInitialState,
169
+ );
151
170
 
152
171
  // Unpack state.
153
- const { tabList, activeFileId, theme, isSettingsOpen } =
154
- state;
172
+ const {
173
+ tabList,
174
+ activeFileId,
175
+ theme,
176
+ isSettingsOpen,
177
+ editorWantsFocus,
178
+ username,
179
+ } = state;
155
180
 
156
181
  // Functions for dispatching actions to the reducer.
157
182
  const {
@@ -161,6 +186,8 @@ function App() {
161
186
  setTheme,
162
187
  setIsSettingsOpen,
163
188
  closeSettings,
189
+ editorNoLongerWantsFocus,
190
+ setUsername,
164
191
  } = useActions(dispatch);
165
192
 
166
193
  // The set of open directories.
@@ -168,8 +195,10 @@ function App() {
168
195
  const { isDirectoryOpen, toggleDirectory } =
169
196
  useOpenDirectories();
170
197
 
198
+ usePersistUsername(username);
199
+
171
200
  // Cache of CodeMirror editors by file id.
172
- const editorCache = useEditorCache();
201
+ const editorCache: EditorCache = useEditorCache();
173
202
 
174
203
  // Handle dynamic theme changes.
175
204
  useDynamicTheme(editorCache, theme);
@@ -177,9 +206,14 @@ function App() {
177
206
  // Handle file CRUD operations.
178
207
  const {
179
208
  createFile,
180
- handleRenameFileClick,
181
- handleDeleteClick,
182
- } = useFileCRUD({ submitOperation, closeTabs });
209
+ renameFile,
210
+ deleteFile,
211
+ deleteDirectory,
212
+ } = useFileCRUD({
213
+ submitOperation,
214
+ closeTabs,
215
+ openTab,
216
+ });
183
217
 
184
218
  // Isolate the files object from the document.
185
219
  const files: Files | null = content
@@ -191,11 +225,12 @@ function App() {
191
225
  <div className="app">
192
226
  <div className="left">
193
227
  <VZSidebar
194
- createFile={createFile}
195
228
  files={files}
196
- handleRenameFileClick={handleRenameFileClick}
197
- handleDeleteFileClick={handleDeleteClick}
198
- handleFileClick={openTab}
229
+ createFile={createFile}
230
+ renameFile={renameFile}
231
+ deleteFile={deleteFile}
232
+ deleteDirectory={deleteDirectory}
233
+ openTab={openTab}
199
234
  setIsSettingsOpen={setIsSettingsOpen}
200
235
  isDirectoryOpen={isDirectoryOpen}
201
236
  toggleDirectory={toggleDirectory}
@@ -206,6 +241,8 @@ function App() {
206
241
  onClose={closeSettings}
207
242
  theme={theme}
208
243
  setTheme={setTheme}
244
+ username={username}
245
+ setUsername={setUsername}
209
246
  />
210
247
  </div>
211
248
  <div className="right">
@@ -214,7 +251,9 @@ function App() {
214
251
  tabList={tabList}
215
252
  activeFileId={activeFileId}
216
253
  setActiveFileId={setActiveFileId}
254
+ openTab={openTab}
217
255
  closeTabs={closeTabs}
256
+ createFile={createFile}
218
257
  />
219
258
  {content && activeFileId ? (
220
259
  <CodeEditor
@@ -225,13 +264,17 @@ function App() {
225
264
  activeFileId={activeFileId}
226
265
  theme={theme}
227
266
  editorCache={editorCache}
267
+ editorWantsFocus={editorWantsFocus}
268
+ editorNoLongerWantsFocus={
269
+ editorNoLongerWantsFocus
270
+ }
271
+ username={username}
228
272
  />
229
273
  ) : null}
230
- <PrettierErrorOverlay
231
- prettierError={prettierError}
232
- />
274
+ <CodeErrorOverlay errorMessage={prettierError} />
233
275
  <PresenceNotifications
234
276
  docPresence={docPresence}
277
+ localPresence={localPresence}
235
278
  />
236
279
  </div>
237
280
  <Resizer />
@@ -12,6 +12,7 @@ import { json1Presence, textUnicode } from '../../ot';
12
12
  import {
13
13
  FileId,
14
14
  ShareDBDoc,
15
+ Username,
15
16
  VZCodeContent,
16
17
  } from '../../types';
17
18
  import { json1PresenceBroadcast } from './json1PresenceBroadcast';
@@ -64,6 +65,7 @@ export const getOrCreateEditor = ({
64
65
  theme,
65
66
  onInteract,
66
67
  editorCache,
68
+ usernameRef,
67
69
  }: {
68
70
  fileId: FileId;
69
71
 
@@ -82,6 +84,7 @@ export const getOrCreateEditor = ({
82
84
  theme: ThemeLabel;
83
85
  onInteract?: () => void;
84
86
  editorCache: EditorCache;
87
+ usernameRef: React.MutableRefObject<Username>;
85
88
  }): EditorCacheValue => {
86
89
  // Cache hit
87
90
  if (editorCache.has(fileId)) {
@@ -118,13 +121,15 @@ export const getOrCreateEditor = ({
118
121
  );
119
122
 
120
123
  // Deals with broadcasting changes in cursor location and selection.
121
- if (localPresence)
124
+ if (localPresence) {
122
125
  extensions.push(
123
126
  json1PresenceBroadcast({
124
127
  path: textPath,
125
128
  localPresence,
129
+ usernameRef,
126
130
  }),
127
131
  );
132
+ }
128
133
 
129
134
  // Deals with receiving the broadcas from other clients and displaying them.
130
135
  if (docPresence)
@@ -2,10 +2,12 @@ import {
2
2
  useRef,
3
3
  useLayoutEffect,
4
4
  useCallback,
5
+ useMemo,
5
6
  } from 'react';
6
7
  import {
7
8
  FileId,
8
9
  ShareDBDoc,
10
+ Username,
9
11
  VZCodeContent,
10
12
  } from '../../types';
11
13
  import { ThemeLabel, defaultTheme } from '../themes';
@@ -25,6 +27,9 @@ export const CodeEditor = ({
25
27
  theme = defaultTheme,
26
28
  filesPath = ['files'],
27
29
  editorCache,
30
+ editorWantsFocus,
31
+ editorNoLongerWantsFocus,
32
+ username,
28
33
  }: {
29
34
  activeFileId: FileId;
30
35
  shareDBDoc: ShareDBDoc<VZCodeContent> | null;
@@ -39,6 +44,13 @@ export const CodeEditor = ({
39
44
  // Defaults to `files` if not provided.
40
45
  filesPath?: string[];
41
46
  editorCache: EditorCache;
47
+
48
+ // Whether the editor should be focused.
49
+ editorWantsFocus: boolean;
50
+
51
+ // Signals that the editor no longer wants focus.
52
+ editorNoLongerWantsFocus: () => void;
53
+ username: Username;
42
54
  }) => {
43
55
  const ref = useRef<HTMLDivElement>(null);
44
56
 
@@ -67,17 +79,15 @@ export const CodeEditor = ({
67
79
  }, 800);
68
80
  }, [submitOperation]);
69
81
 
70
- // Every time the active file switches from one file to another,
71
- // the editor corresponding to the old file is removed from the DOM,
72
- // and the editor corresponding to the new file is added to the DOM.
73
- useLayoutEffect(() => {
74
- // Guard against cases where page is still loading.
75
- if (!ref.current) return;
76
- if (!shareDBDoc) return;
82
+ // Track username on a ref, so that we can use it in the
83
+ // presence object's `username` property.
84
+ const usernameRef = useRef<Username>(username);
85
+ usernameRef.current = username;
77
86
 
78
- // Get the editor corresponding to the active file.
79
- // Looks in `editorCache` first, and if not found, creates a new editor.
80
- const editorCacheValue: EditorCacheValue =
87
+ // Get the editor corresponding to the active file.
88
+ // Looks in `editorCache` first, and if not found, creates a new editor.
89
+ const editorCacheValue: EditorCacheValue = useMemo(
90
+ () =>
81
91
  getOrCreateEditor({
82
92
  fileId: activeFileId,
83
93
  shareDBDoc,
@@ -87,7 +97,28 @@ export const CodeEditor = ({
87
97
  theme,
88
98
  onInteract,
89
99
  editorCache,
90
- });
100
+ usernameRef,
101
+ }),
102
+ [
103
+ activeFileId,
104
+ shareDBDoc,
105
+ filesPath,
106
+ localPresence,
107
+ docPresence,
108
+ theme,
109
+ onInteract,
110
+ editorCache,
111
+ usernameRef,
112
+ ],
113
+ );
114
+
115
+ // Every time the active file switches from one file to another,
116
+ // the editor corresponding to the old file is removed from the DOM,
117
+ // and the editor corresponding to the new file is added to the DOM.
118
+ useLayoutEffect(() => {
119
+ // Guard against cases where page is still loading.
120
+ if (!ref.current) return;
121
+ if (!shareDBDoc) return;
91
122
 
92
123
  // Add the editor to the DOM.
93
124
  ref.current.appendChild(editorCacheValue.editor.dom);
@@ -97,7 +128,24 @@ export const CodeEditor = ({
97
128
  // This happens every time `activeFileId` changes.
98
129
  ref.current.removeChild(editorCacheValue.editor.dom);
99
130
  };
100
- }, [shareDBDoc, activeFileId, onInteract]);
131
+ }, [shareDBDoc, editorCacheValue]);
132
+
133
+ // useEffect(() => {
134
+ // if (editorWantsFocus) {
135
+ // editorCacheValue.editor.focus();
136
+ // }
137
+
138
+ // Focus the editor
139
+ useLayoutEffect(() => {
140
+ if (editorWantsFocus) {
141
+ editorCacheValue.editor.focus();
142
+ editorNoLongerWantsFocus();
143
+ }
144
+ }, [
145
+ editorWantsFocus,
146
+ editorCacheValue,
147
+ editorNoLongerWantsFocus,
148
+ ]);
101
149
 
102
150
  return <div className="vz-code-editor" ref={ref}></div>;
103
151
  };
@@ -5,11 +5,13 @@ import { EditorView } from 'codemirror';
5
5
  export const json1PresenceBroadcast = ({
6
6
  path,
7
7
  localPresence,
8
+ usernameRef,
8
9
  }) =>
9
10
  // See https://discuss.codemirror.net/t/codemirror-6-proper-way-to-listen-for-changes/2395/10
10
11
  EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
11
12
  // If this update modified the cursor / selection,
12
13
  // we broadcast the selection update via ShareDB presence.
14
+
13
15
  if (viewUpdate.selectionSet) {
14
16
  // Isolate the single selection to use for presence.
15
17
  // Unfortunately JSON1 with presence does not yet
@@ -24,8 +26,10 @@ export const json1PresenceBroadcast = ({
24
26
  start: [...path, from],
25
27
  end: [...path, to],
26
28
 
27
- // TODO fill this in with the user's actual username.
28
- username: 'anonymous',
29
+ // We use a ref here so that the presence always broadcasts
30
+ // the latest username, in the case that it has been changed
31
+ // in the Settings mid-session.
32
+ username: usernameRef.current,
29
33
  };
30
34
 
31
35
  // Broadcast presence to remote clients!
@@ -37,10 +37,6 @@ export const json1PresenceDisplay = ({
37
37
  // Receive remote presence changes.
38
38
  docPresence.on('receive', (id, presence) => {
39
39
  // If presence === null, the user has disconnected / exited
40
- // notification(presence);
41
- // console.log('docPresence in presenceDisplay:',docPresence);
42
- // console.log('presence in presenceDisplay:', presence);
43
-
44
40
  // We also check if the presence is for the current file or not.
45
41
  if (presence && pathMatches(path, presence)) {
46
42
  presenceState[id] = presence;
@@ -107,47 +103,6 @@ export const json1PresenceDisplay = ({
107
103
  presenceTheme,
108
104
  ];
109
105
 
110
- // const notification = (presence) => {
111
- // const notif = document.createElement('div');
112
- // notif.className = 'notification-presence';
113
- // if (presence === null){
114
- // notif.textContent = 'User asdfgasdfgasdfg left the collaboration session.';
115
- // notif.style.display = 'block';
116
- // }
117
- // // The following causes the popup to show every time the remote cursor moves
118
- // // to a different location on the file and not when the user first joins
119
- // // else {
120
- // // notif.textContent = presence + 'joined the collaboration session.';
121
- // // notif.style.display = 'block';
122
- // // }
123
- // document.body.appendChild(notif);
124
-
125
- // const notifStyle = document.createElement('style');
126
- // notifStyle.innerText = `
127
- // .notification-presence {
128
- // position: fixed;
129
- // display: none;
130
- // bottom: 20px;
131
- // right: 20px;
132
- // max-width: 300px;
133
- // overflow-x: hidden;
134
- // white-space: nowrap;
135
- // background-color: rgb(54, 65, 89);
136
- // color: rgb(154, 160, 172);
137
- // padding: 15px;
138
- // }
139
- // .notification-presence:hover {
140
- // overflow-x: auto;
141
- // }`;
142
- // document.head.appendChild(notifStyle);
143
-
144
- // setTimeout(() => {
145
- // notif.style.display = 'none';
146
- // }, 10000);
147
-
148
- // return notif;
149
- // };
150
-
151
106
  const presenceAnnotation = Annotation.define();
152
107
 
153
108
  // Checks that the path of this file
@@ -4,8 +4,12 @@
4
4
  font-family: var(--vzcode-font-family);
5
5
  overflow-x: auto;
6
6
 
7
- /* TODO QA this */
8
7
  .cm-editor {
9
8
  flex: 1;
10
9
  }
10
+
11
+ .cm-scroller {
12
+ font-family: var(--vzcode-font-family);
13
+ font-size: var(--vzcode-font-size);
14
+ }
11
15
  }
@@ -0,0 +1,27 @@
1
+ import { useCallback } from 'react';
2
+ import './style.scss';
3
+
4
+ // Feature flag for the button that dismisses the error.
5
+ const enableErrorDismiss = false;
6
+
7
+ export const CodeErrorOverlay = ({
8
+ errorMessage,
9
+ }: {
10
+ errorMessage: string | null;
11
+ }) => {
12
+ const handleCloseClick = useCallback(() => {
13
+ console.log('TODO dismiss the error');
14
+ }, []);
15
+
16
+ return errorMessage !== null ? (
17
+ <pre className="vz-code-error-overlay">
18
+ {errorMessage}
19
+ {enableErrorDismiss ? (
20
+ <div
21
+ className={'bx bx-x error-dismiss-button'}
22
+ onClick={handleCloseClick}
23
+ ></div>
24
+ ) : null}
25
+ </pre>
26
+ ) : null;
27
+ };
@@ -0,0 +1,30 @@
1
+ .vz-code-error-overlay {
2
+ color: rgb(255, 35, 35);
3
+ font-family: var(--vzcode-font-family);
4
+ font-size: var(--vzcode-font-size);
5
+ background-color: rgba(0, 0, 0, 0.6);
6
+ position: absolute;
7
+ bottom: 0;
8
+ left: 0;
9
+ right: 0;
10
+ // pointer-events: none;
11
+ margin: 0;
12
+ padding: 8px;
13
+
14
+ border-top-left-radius: 12px;
15
+ border-top-right-radius: 12px;
16
+
17
+ // TODO unify with styles in tab close button
18
+ .error-dismiss-button {
19
+ position: absolute;
20
+ top: 0;
21
+ right: 0;
22
+ margin: 5px;
23
+ cursor: pointer;
24
+ color: white;
25
+ }
26
+ .error-dismiss-button:hover {
27
+ border-radius: 50%;
28
+ background-color: rgba(255, 255, 255, 0.2);
29
+ }
30
+ }