vzcode 0.78.0 → 0.80.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
@@ -20,8 +20,8 @@
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-Cv26Z85N.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-WtPPom-d.css">
23
+ <script type="module" crossorigin src="/assets/index-FsyJOC4a.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-J8-kRnnE.css">
25
25
  </head>
26
26
  <body>
27
27
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "0.78.0",
3
+ "version": "0.80.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -79,15 +79,15 @@
79
79
  "@replit/codemirror-vscode-keymap": "^6.0.2",
80
80
  "@teamwork/websocket-json-stream": "^2.0.0",
81
81
  "@typescript/vfs": "^1.5.0",
82
- "@uiw/codemirror-theme-abcdef": "^4.21.24",
83
- "@uiw/codemirror-theme-dracula": "^4.21.24",
84
- "@uiw/codemirror-theme-eclipse": "^4.21.24",
85
- "@uiw/codemirror-theme-github": "^4.21.24",
86
- "@uiw/codemirror-theme-material": "^4.21.24",
87
- "@uiw/codemirror-theme-nord": "^4.21.24",
88
- "@uiw/codemirror-theme-okaidia": "^4.21.24",
89
- "@uiw/codemirror-theme-xcode": "^4.21.24",
90
- "@uiw/codemirror-themes": "^4.21.24",
82
+ "@uiw/codemirror-theme-abcdef": "^4.21.25",
83
+ "@uiw/codemirror-theme-dracula": "^4.21.25",
84
+ "@uiw/codemirror-theme-eclipse": "^4.21.25",
85
+ "@uiw/codemirror-theme-github": "^4.21.25",
86
+ "@uiw/codemirror-theme-material": "^4.21.25",
87
+ "@uiw/codemirror-theme-nord": "^4.21.25",
88
+ "@uiw/codemirror-theme-okaidia": "^4.21.25",
89
+ "@uiw/codemirror-theme-xcode": "^4.21.25",
90
+ "@uiw/codemirror-themes": "^4.21.25",
91
91
  "body-parser": "^1.20.2",
92
92
  "codemirror": "^6.0.1",
93
93
  "codemirror-ot": "^4.4.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.0",
98
+ "express": "^4.19.1",
99
99
  "ignore": "^5.3.1",
100
100
  "json0-ot-diff": "^1.1.2",
101
101
  "ngrok": "^4.3.3",
@@ -112,14 +112,14 @@
112
112
  "ws": "^8.16.0"
113
113
  },
114
114
  "devDependencies": {
115
- "@types/react": "^18.2.67",
115
+ "@types/react": "^18.2.69",
116
116
  "@types/react-dom": "^18.2.22",
117
117
  "@vitejs/plugin-react": "^4.2.1",
118
118
  "concurrently": "^8.2.2",
119
119
  "prettier": "^3.2.5",
120
120
  "sass": "^1.72.0",
121
- "typescript": "^5.4.2",
122
- "vite": "^5.2.2",
121
+ "typescript": "^5.4.3",
122
+ "vite": "^5.2.6",
123
123
  "vitest": "^1.4.0"
124
124
  }
125
125
  }
@@ -80,6 +80,7 @@ const getAtPath = (obj, path) => {
80
80
  export const getOrCreateEditor = ({
81
81
  fileId,
82
82
  shareDBDoc,
83
+ content,
83
84
  filesPath,
84
85
  localPresence,
85
86
  docPresence,
@@ -98,7 +99,13 @@ export const getOrCreateEditor = ({
98
99
  // based on the file extension.
99
100
  // It's also passed into the `json1Sync` extension,
100
101
  // which is used for multiplayer editing.
101
- shareDBDoc: ShareDBDoc<VZCodeContent>;
102
+ // This can be `undefined` in the case where we are
103
+ // viewing files read-only, in which case multiplayer
104
+ // editing is not enabled.
105
+ shareDBDoc: ShareDBDoc<VZCodeContent> | undefined;
106
+
107
+ // The initial content to present.
108
+ content: VZCodeContent;
102
109
 
103
110
  filesPath: string[];
104
111
  localPresence: any;
@@ -118,11 +125,10 @@ export const getOrCreateEditor = ({
118
125
  // Cache miss
119
126
 
120
127
  // Compute `text` and `fileExtension` from the ShareDB document.
121
- const data = shareDBDoc.data;
122
128
  const textPath = [...filesPath, fileId, 'text'];
123
129
  const namePath = [...filesPath, fileId, 'name'];
124
- const text = getAtPath(data, textPath);
125
- const name = getAtPath(data, namePath);
130
+ const text = getAtPath(content, textPath);
131
+ const name = getAtPath(content, namePath);
126
132
  const fileExtension = name.split('.').pop();
127
133
 
128
134
  // Create a compartment for the theme so that it can be changed dynamically.
@@ -135,31 +141,41 @@ export const getOrCreateEditor = ({
135
141
  // This plugin implements multiplayer editing,
136
142
  // real-time synchronozation of changes across clients.
137
143
  // Does not deal with showing others' cursors.
138
- extensions.push(
139
- json1Sync({
140
- shareDBDoc,
141
- path: textPath,
142
- json1: json1Presence,
143
- textUnicode,
144
- }),
145
- );
146
-
147
- // Deals with broadcasting changes in cursor location and selection.
148
- if (localPresence) {
144
+ if (shareDBDoc) {
149
145
  extensions.push(
150
- json1PresenceBroadcast({
146
+ json1Sync({
147
+ shareDBDoc,
151
148
  path: textPath,
152
- localPresence,
153
- usernameRef,
149
+ json1: json1Presence,
150
+ textUnicode,
154
151
  }),
155
152
  );
156
- }
157
153
 
158
- // Deals with receiving the broadcas from other clients and displaying them.
159
- if (docPresence)
160
- extensions.push(
161
- json1PresenceDisplay({ path: textPath, docPresence }),
162
- );
154
+ // Deals with broadcasting changes in cursor location and selection.
155
+ if (localPresence) {
156
+ extensions.push(
157
+ json1PresenceBroadcast({
158
+ path: textPath,
159
+ localPresence,
160
+ usernameRef,
161
+ }),
162
+ );
163
+ }
164
+
165
+ // Deals with receiving the broadcas from other clients and displaying them.
166
+ if (docPresence) {
167
+ extensions.push(
168
+ json1PresenceDisplay({
169
+ path: textPath,
170
+ docPresence,
171
+ }),
172
+ );
173
+ }
174
+ } else {
175
+ // If the ShareDB document is not provided,
176
+ // then we do not allow editing.
177
+ extensions.push(EditorView.editable.of(false));
178
+ }
163
179
 
164
180
  extensions.push(colorsInTextPlugin);
165
181
 
@@ -247,7 +263,7 @@ export const getOrCreateEditor = ({
247
263
  }),
248
264
  );
249
265
  }
250
- if (enableTypeScriptLinter) {
266
+ if (shareDBDoc && enableTypeScriptLinter) {
251
267
  extensions.push(
252
268
  linter(
253
269
  typeScriptLinter({
@@ -3,66 +3,33 @@ import {
3
3
  useLayoutEffect,
4
4
  useCallback,
5
5
  useMemo,
6
+ useContext,
6
7
  } from 'react';
7
- import {
8
- FileId,
9
- ShareDBDoc,
10
- Username,
11
- VZCodeContent,
12
- } from '../../types';
13
- import { ThemeLabel, defaultTheme } from '../themes';
14
- import {
15
- EditorCache,
16
- EditorCacheValue,
17
- } from '../useEditorCache';
8
+ import { Username } from '../../types';
9
+ import { EditorCacheValue } from '../useEditorCache';
18
10
  import { getOrCreateEditor } from './getOrCreateEditor';
11
+ import { VZCodeContext } from '../VZCodeContext';
19
12
  import './style.scss';
20
13
 
21
- export const CodeEditor = ({
22
- activeFileId,
23
- shareDBDoc,
24
- submitOperation,
25
- localPresence,
26
- docPresence,
27
- theme = defaultTheme,
28
- filesPath = ['files'],
29
- editorCache,
30
- editorWantsFocus,
31
- editorNoLongerWantsFocus,
32
- username,
33
- typeScriptWorker,
34
- }: {
35
- activeFileId: FileId;
36
- shareDBDoc: ShareDBDoc<VZCodeContent> | null;
37
- submitOperation: (
38
- next: (content: VZCodeContent) => VZCodeContent,
39
- ) => void;
40
- localPresence?: any;
41
- docPresence?: any;
42
- theme?: ThemeLabel;
43
-
44
- // The path of the files object in the ShareDB document.
45
- // Defaults to `files` if not provided.
46
- filesPath?: string[];
47
- editorCache: EditorCache;
48
-
49
- // Whether the editor should be focused.
50
- editorWantsFocus: boolean;
51
-
52
- // Signals that the editor no longer wants focus.
53
- editorNoLongerWantsFocus: () => void;
54
- username: Username;
55
-
56
- // The server endpoint for the AI Assist service.
57
- aiAssistEndpoint?: string;
14
+ // The path in the ShareDB document where the files live.
15
+ const filesPath = ['files'];
16
+
17
+ export const CodeEditor = () => {
18
+ const {
19
+ activeFileId,
20
+ shareDBDoc,
21
+ content,
22
+ submitOperation,
23
+ localPresence,
24
+ docPresence,
25
+ username,
26
+ editorCache,
27
+ editorWantsFocus,
28
+ editorNoLongerWantsFocus,
29
+ typeScriptWorker,
30
+ theme,
31
+ } = useContext(VZCodeContext);
58
32
 
59
- // Additional options to pass to the AI Assist service,
60
- // an object with string values.
61
- aiAssistOptions?: {
62
- [key: string]: string;
63
- };
64
- typeScriptWorker: Worker;
65
- }) => {
66
33
  const ref = useRef<HTMLDivElement>(null);
67
34
 
68
35
  // Set `doc.data.isInteracting` to `true` when the user is interacting
@@ -103,6 +70,7 @@ export const CodeEditor = ({
103
70
  getOrCreateEditor({
104
71
  fileId: activeFileId,
105
72
  shareDBDoc,
73
+ content,
106
74
  filesPath,
107
75
  localPresence,
108
76
  docPresence,
@@ -132,7 +100,7 @@ export const CodeEditor = ({
132
100
  useLayoutEffect(() => {
133
101
  // Guard against cases where page is still loading.
134
102
  if (!ref.current) return;
135
- if (!shareDBDoc) return;
103
+ if (!content) return;
136
104
 
137
105
  // Add the editor to the DOM.
138
106
  ref.current.appendChild(editorCacheValue.editor.dom);
@@ -91,7 +91,10 @@ export type VZCodeContextValue = {
91
91
  toggleDirectory: (path: string) => void;
92
92
 
93
93
  editorCache: EditorCache;
94
+
95
+ // Whether the editor should be focused.
94
96
  editorWantsFocus: boolean;
97
+ // Signals that the editor no longer wants focus.
95
98
  editorNoLongerWantsFocus: () => void;
96
99
 
97
100
  errorMessage: string | null;
@@ -76,21 +76,7 @@ export const VZMiddle = ({
76
76
  createFile={createFile}
77
77
  />
78
78
  {isClient && content && activeFileId && (
79
- <CodeEditor
80
- shareDBDoc={shareDBDoc}
81
- submitOperation={submitOperation}
82
- localPresence={localPresence}
83
- docPresence={docPresence}
84
- activeFileId={activeFileId}
85
- theme={theme}
86
- editorCache={editorCache}
87
- editorWantsFocus={editorWantsFocus}
88
- editorNoLongerWantsFocus={
89
- editorNoLongerWantsFocus
90
- }
91
- username={username}
92
- typeScriptWorker={typeScriptWorker}
93
- />
79
+ <CodeEditor />
94
80
  )}
95
81
  {isClient &&
96
82
  enableAIAssist &&
@@ -94,121 +94,123 @@ export const VZSidebar = ({
94
94
 
95
95
  return (
96
96
  <div
97
- className={`drop-zone ${isDragOver ? 'dragover' : ''}`}
97
+ className="vz-sidebar"
98
+ style={{ width: sidebarWidth + 'px' }}
98
99
  onDragEnter={handleDragEnter}
99
100
  onDragOver={handleDragOver}
100
101
  onDragLeave={handleDragLeave}
101
102
  onDrop={handleDrop}
102
103
  >
103
- <div
104
- className="vz-sidebar"
105
- style={{ width: sidebarWidth + 'px' }}
106
- >
107
- <div className="files">
108
- <div className="full-box">
109
- <div className="sidebar-section-hint">
110
- Project Files
111
- </div>
112
- <div className="sidebar-section-buttons">
113
- <OverlayTrigger
114
- placement="left"
115
- overlay={
116
- <Tooltip id="report-bug-tooltip">
117
- {reportBugTooltipText}
118
- </Tooltip>
119
- }
104
+ <div className="files">
105
+ <div className="full-box">
106
+ <div className="sidebar-section-hint">
107
+ Project Files
108
+ </div>
109
+ <div className="sidebar-section-buttons">
110
+ <OverlayTrigger
111
+ placement="left"
112
+ overlay={
113
+ <Tooltip id="report-bug-tooltip">
114
+ {reportBugTooltipText}
115
+ </Tooltip>
116
+ }
117
+ >
118
+ <a
119
+ href="https://github.com/vizhub-core/vzcode/issues/new"
120
+ target="_blank"
121
+ rel="noopener noreferrer"
120
122
  >
121
- <a
122
- href="https://github.com/vizhub-core/vzcode/issues/new"
123
- target="_blank"
124
- rel="noopener noreferrer"
125
- >
126
- <i className="icon-button icon-button-dark">
127
- <BugSVG />
128
- </i>
129
- </a>
130
- </OverlayTrigger>
131
- {disableSettings ? null : (
132
- <OverlayTrigger
133
- placement="left"
134
- overlay={
135
- <Tooltip id="open-settings-tooltip">
136
- {openSettingsTooltipText}
137
- </Tooltip>
138
- }
139
- >
140
- <i
141
- onClick={handleSettingsClick}
142
- className="icon-button icon-button-dark"
143
- >
144
- <GearSVG />
145
- </i>
146
- </OverlayTrigger>
147
- )}
123
+ <i className="icon-button icon-button-dark">
124
+ <BugSVG />
125
+ </i>
126
+ </a>
127
+ </OverlayTrigger>
128
+ {disableSettings ? null : (
148
129
  <OverlayTrigger
149
130
  placement="left"
150
131
  overlay={
151
- <Tooltip id="create-file-tooltip">
152
- {createFileTooltipText}
132
+ <Tooltip id="open-settings-tooltip">
133
+ {openSettingsTooltipText}
153
134
  </Tooltip>
154
135
  }
155
136
  >
156
137
  <i
157
- onClick={handleOpenCreateFileModal}
138
+ onClick={handleSettingsClick}
158
139
  className="icon-button icon-button-dark"
159
140
  >
160
- <NewSVG />
141
+ <GearSVG />
161
142
  </i>
162
143
  </OverlayTrigger>
163
- </div>
144
+ )}
145
+ <OverlayTrigger
146
+ placement="left"
147
+ overlay={
148
+ <Tooltip id="create-file-tooltip">
149
+ {createFileTooltipText}
150
+ </Tooltip>
151
+ }
152
+ >
153
+ <i
154
+ onClick={handleOpenCreateFileModal}
155
+ className="icon-button icon-button-dark"
156
+ >
157
+ <NewSVG />
158
+ </i>
159
+ </OverlayTrigger>
164
160
  </div>
165
- {filesExist ? (
166
- fileTree.children.map((entity) => {
167
- const { fileId } = entity as FileTreeFile;
168
- const { path } = entity as FileTree;
169
- const key = fileId ? fileId : path;
170
- return (
171
- <Listing
172
- key={key}
173
- entity={entity}
174
- renameFile={renameFile}
175
- deleteFile={deleteFile}
176
- renameDirectory={renameDirectory}
177
- deleteDirectory={deleteDirectory}
178
- handleFileClick={handleFileClick}
179
- handleFileDoubleClick={
180
- handleFileDoubleClick
181
- }
182
- isDirectoryOpen={isDirectoryOpen}
183
- toggleDirectory={toggleDirectory}
184
- activeFileId={activeFileId}
185
- />
186
- );
187
- })
188
- ) : (
189
- <div className="empty">
190
- <div className="empty-text">
191
- It looks like you don't have any files yet!
192
- Click the "Create file" button above to
193
- create your first file.
194
- </div>
195
- </div>
196
- )}
197
161
  </div>
198
-
199
- {enableConnectionStatus && (
200
- <div className="connection-status">
201
- {connected ? 'Connected' : 'Connection Lost'}
202
- <div className="connection">
203
- <div
204
- className={`connection-status-indicator ${
205
- connected ? 'connected' : 'disconnected'
206
- }`}
162
+ {isDragOver ? (
163
+ <div className="empty">
164
+ <div className="empty-text">
165
+ Drop files here!
166
+ </div>
167
+ </div>
168
+ ) : filesExist ? (
169
+ fileTree.children.map((entity) => {
170
+ const { fileId } = entity as FileTreeFile;
171
+ const { path } = entity as FileTree;
172
+ const key = fileId ? fileId : path;
173
+ return (
174
+ <Listing
175
+ key={key}
176
+ entity={entity}
177
+ renameFile={renameFile}
178
+ deleteFile={deleteFile}
179
+ renameDirectory={renameDirectory}
180
+ deleteDirectory={deleteDirectory}
181
+ handleFileClick={handleFileClick}
182
+ handleFileDoubleClick={
183
+ handleFileDoubleClick
184
+ }
185
+ isDirectoryOpen={isDirectoryOpen}
186
+ toggleDirectory={toggleDirectory}
187
+ activeFileId={activeFileId}
207
188
  />
189
+ );
190
+ })
191
+ ) : (
192
+ <div className="empty">
193
+ <div className="empty-text">
194
+ It looks like you don't have any files yet!
195
+ Click the "Create file" button above to create
196
+ your first file.
208
197
  </div>
209
198
  </div>
210
199
  )}
211
200
  </div>
201
+
202
+ {enableConnectionStatus && (
203
+ <div className="connection-status">
204
+ {connected ? 'Connected' : 'Connection Lost'}
205
+ <div className="connection">
206
+ <div
207
+ className={`connection-status-indicator ${
208
+ connected ? 'connected' : 'disconnected'
209
+ }`}
210
+ />
211
+ </div>
212
+ </div>
213
+ )}
212
214
  </div>
213
215
  );
214
216
  };
@@ -127,11 +127,17 @@
127
127
  padding: 20px;
128
128
  border: 1px dashed rgba(255, 255, 255, 0.5); /* White dashed outline */
129
129
  border-radius: 10px;
130
- }
131
130
 
132
- .empty-text {
133
- margin-bottom: 10px;
134
- font-size: 14px;
131
+ /*
132
+ * This fixes some issues where the drop zone for files
133
+ * was flickering when dragging files over the sidebar.
134
+ */
135
+ pointer-events: none;
136
+
137
+ .empty-text {
138
+ font-size: 16px;
139
+ font-weight: 500;
140
+ }
135
141
  }
136
142
 
137
143
  .connection-status {
@@ -155,17 +161,3 @@
155
161
  }
156
162
  }
157
163
  }
158
-
159
- .drop-zone {
160
- height: 100%;
161
- border: 3px solid #0a1731;
162
- width: auto;
163
- color: #bdc7dc;
164
- text-align: center;
165
- font-family: var(--vzcode-font-family);
166
- font-weight: 500;
167
- }
168
-
169
- .drop-zone.dragover {
170
- border-color: #009639;
171
- }
@@ -132,7 +132,7 @@ export const useFileCRUD = ({
132
132
  submitOperation((document: VZCodeContent) => {
133
133
  const updatedFiles = { ...document.files };
134
134
  for (const key in updatedFiles) {
135
- if (updatedFiles[key].name.includes(path)) {
135
+ if (updatedFiles[key].name.includes(path + '/')) {
136
136
  tabsToClose.push(key);
137
137
  delete updatedFiles[key];
138
138
  }
@@ -53,6 +53,9 @@ const initializeFileSystem = async () => {
53
53
 
54
54
  // Skip type checking of declaration files
55
55
  skipLibCheck: true,
56
+
57
+ // Support React JSX
58
+ jsx: ts.JsxEmit.React,
56
59
  };
57
60
 
58
61
  // `true` breaks in a Web Worker because