vzcode 0.79.0 → 0.81.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.
@@ -7,6 +7,7 @@ import { PresenceNotifications } from './PresenceNotifications';
7
7
  import { AIAssistWidget } from './AIAssist/AIAssistWidget';
8
8
  import { VZCodeContext } from './VZCodeContext';
9
9
  import { RunCodeWidget } from './RunCodeWidget';
10
+ import { InteractRule } from '@replit/codemirror-interact';
10
11
 
11
12
  // The middle portion of the VZCode environment, containing:
12
13
  // * The list of tabs at the top
@@ -20,38 +21,25 @@ export const VZMiddle = ({
20
21
  aiAssistOptions,
21
22
  aiAssistTooltipText,
22
23
  aiAssistClickOverride,
24
+ customInteractRules,
23
25
  }: {
24
26
  enableAIAssist?: boolean;
25
27
  aiAssistEndpoint?: string;
26
28
  aiAssistOptions?: { [key: string]: string };
27
29
  aiAssistTooltipText?: string;
28
30
  aiAssistClickOverride?: () => void;
31
+ customInteractRules?: Array<InteractRule>;
29
32
  }) => {
30
33
  const { codeEditorWidth } = useContext(
31
34
  SplitPaneResizeContext,
32
35
  );
33
36
 
34
- // TODO leverage this context in deeper levels of the component tree.
35
37
  const {
36
38
  content,
37
- shareDBDoc,
38
- submitOperation,
39
39
  localPresence,
40
40
  docPresence,
41
- files,
42
- createFile,
43
41
  activeFileId,
44
- setActiveFileId,
45
- tabList,
46
- openTab,
47
- closeTabs,
48
- theme,
49
- username,
50
- editorCache,
51
- editorWantsFocus,
52
- editorNoLongerWantsFocus,
53
42
  errorMessage,
54
- typeScriptWorker,
55
43
  } = useContext(VZCodeContext);
56
44
 
57
45
  // This prevents the CodeEditor from rendering
@@ -66,27 +54,17 @@ export const VZMiddle = ({
66
54
  className="middle"
67
55
  style={{ width: codeEditorWidth + 'px' }}
68
56
  >
69
- <TabList
70
- files={files}
71
- tabList={tabList}
72
- activeFileId={activeFileId}
73
- setActiveFileId={setActiveFileId}
74
- openTab={openTab}
75
- closeTabs={closeTabs}
76
- createFile={createFile}
77
- />
57
+ <TabList />
78
58
  {isClient && content && activeFileId && (
79
- <CodeEditor />
59
+ <CodeEditor
60
+ customInteractRules={customInteractRules}
61
+ />
80
62
  )}
81
63
  {isClient &&
82
64
  enableAIAssist &&
83
65
  content &&
84
66
  activeFileId ? (
85
67
  <AIAssistWidget
86
- activeFileId={activeFileId}
87
- shareDBDoc={shareDBDoc}
88
- editorCache={editorCache}
89
- tabList={tabList}
90
68
  aiAssistEndpoint={aiAssistEndpoint}
91
69
  aiAssistOptions={aiAssistOptions}
92
70
  aiAssistTooltipText={aiAssistTooltipText}
@@ -1,44 +1,30 @@
1
- import { useCallback, useMemo } from 'react';
1
+ import { useCallback, useContext, useMemo } from 'react';
2
+ import type { FileTree, FileTreeFile } from '../../types';
3
+ import { DirectoryArrowSVG } from '../Icons';
4
+ import { VZCodeContext } from '../VZCodeContext';
2
5
  import { Item } from './Item';
3
6
  import { Listing } from './Listing';
4
- import { DirectoryArrowSVG } from '../Icons';
5
- import type {
6
- FileTree,
7
- FileTreeFile,
8
- FileTreePath,
9
- } from '../../types';
10
7
 
11
8
  export const DirectoryListing = ({
12
9
  name,
13
10
  path,
14
11
  children,
15
- renameFile,
16
- deleteFile,
17
- renameDirectory,
18
- deleteDirectory,
19
12
  handleFileClick,
20
13
  handleFileDoubleClick,
21
- isDirectoryOpen,
22
- toggleDirectory,
23
- activeFileId,
24
14
  }: {
25
15
  name: string;
26
16
  path: string;
27
17
  children: Array<FileTree | FileTreeFile>;
28
- renameFile: (fileId: string, newName: string) => void;
29
- deleteFile: (fileId: string) => void;
30
- renameDirectory: (
31
- path: FileTreePath,
32
- oldName: string,
33
- newName: string,
34
- ) => void;
35
- deleteDirectory: (path: FileTreePath) => void;
36
18
  handleFileClick: (fileId: string) => void;
37
19
  handleFileDoubleClick: (fileId: string) => void;
38
- isDirectoryOpen: (path: string) => boolean;
39
- toggleDirectory: (path: string) => void;
40
- activeFileId: string;
41
20
  }) => {
21
+ const {
22
+ renameDirectory,
23
+ deleteDirectory,
24
+ isDirectoryOpen,
25
+ toggleDirectory,
26
+ } = useContext(VZCodeContext);
27
+
42
28
  const handleClick = useCallback(() => {
43
29
  toggleDirectory(path);
44
30
  }, [toggleDirectory, path]);
@@ -62,6 +48,7 @@ export const DirectoryListing = ({
62
48
  return (
63
49
  <>
64
50
  <Item
51
+ id={path}
65
52
  name={name}
66
53
  handleClick={handleClick}
67
54
  handleDeleteClick={handleDeleteClick}
@@ -87,17 +74,10 @@ export const DirectoryListing = ({
87
74
  <Listing
88
75
  entity={entity}
89
76
  key={fileId || path}
90
- renameFile={renameFile}
91
- deleteFile={deleteFile}
92
- renameDirectory={renameDirectory}
93
- deleteDirectory={deleteDirectory}
94
77
  handleFileClick={handleFileClick}
95
78
  handleFileDoubleClick={
96
79
  handleFileDoubleClick
97
80
  }
98
- isDirectoryOpen={isDirectoryOpen}
99
- toggleDirectory={toggleDirectory}
100
- activeFileId={activeFileId}
101
81
  />
102
82
  );
103
83
  })}
@@ -1,25 +1,25 @@
1
- import { useCallback } from 'react';
1
+ import { useCallback, useContext } from 'react';
2
2
  import { Item } from './Item';
3
3
  import { FileId } from '../../types';
4
4
  import { FileSVG } from '../Icons';
5
+ import { VZCodeContext } from '../VZCodeContext';
5
6
 
6
7
  export const FileListing = ({
7
8
  name,
8
9
  fileId,
9
- renameFile,
10
- deleteFile,
11
10
  handleFileClick,
12
11
  handleFileDoubleClick,
13
12
  isActive,
14
13
  }: {
15
14
  name: string;
16
15
  fileId: FileId;
17
- renameFile: (fileId: FileId, newName: string) => void;
18
- deleteFile: (fileId: FileId) => void;
19
16
  handleFileClick: (fileId: FileId) => void;
20
17
  handleFileDoubleClick: (fileId: FileId) => void;
21
18
  isActive: boolean;
22
19
  }) => {
20
+ const { renameFile, deleteFile } =
21
+ useContext(VZCodeContext);
22
+
23
23
  const handleClick = useCallback(() => {
24
24
  handleFileClick(fileId);
25
25
  }, [fileId, handleFileClick]);
@@ -41,6 +41,7 @@ export const FileListing = ({
41
41
 
42
42
  return (
43
43
  <Item
44
+ id={fileId}
44
45
  name={name}
45
46
  handleClick={handleClick}
46
47
  handleDoubleClick={handleDoubleClick}
@@ -3,12 +3,15 @@ import {
3
3
  useCallback,
4
4
  useRef,
5
5
  useEffect,
6
+ useContext,
6
7
  } from 'react';
7
8
  import { EditSVG, FileSVG, TrashSVG } from '../Icons';
8
9
  import { Tooltip, OverlayTrigger } from '../bootstrap';
9
10
 
10
11
  // TODO consider moving this up to a higher level of the component tree
11
12
  import { DeleteConfirmationModal } from './DeleteConfirmationModal';
13
+ import { VZCodeContext } from '../VZCodeContext';
14
+ import { ItemId } from '../../types';
12
15
 
13
16
  // TODO support renaming directories
14
17
  // See https://github.com/vizhub-core/vzcode/issues/103
@@ -16,6 +19,7 @@ const enableRenameDirectory = true;
16
19
 
17
20
  // A file or directory in the sidebar.
18
21
  export const Item = ({
22
+ id,
19
23
  name,
20
24
  children,
21
25
  handleClick,
@@ -31,6 +35,7 @@ export const Item = ({
31
35
  ? 'Delete Directory'
32
36
  : 'Delete File',
33
37
  }: {
38
+ id: ItemId;
34
39
  name: string;
35
40
  children: React.ReactNode;
36
41
  handleClick: (event: React.MouseEvent) => void;
@@ -42,8 +47,21 @@ export const Item = ({
42
47
  renameFileTooltipText?: string;
43
48
  deleteFileTooltipText?: string;
44
49
  }) => {
45
- // Tracks whether the mouse is hovering over the file or directory
46
- const [isHovered, setIsHovered] = useState(false);
50
+ const { hoveredItemId, setHoveredItemId } =
51
+ useContext(VZCodeContext);
52
+ // // Tracks whether the mouse is hovering over the file or directory
53
+ // const [isHovered, setIsHovered] = useState(false);
54
+ const isHovered = hoveredItemId === id;
55
+ const setIsHovered = useCallback(
56
+ (isHovered: boolean) => {
57
+ if (isHovered) {
58
+ setHoveredItemId(id);
59
+ } else {
60
+ setHoveredItemId(null);
61
+ }
62
+ },
63
+ [id, setHoveredItemId],
64
+ );
47
65
 
48
66
  // Tracks whether the file is being renamed (inline text input)
49
67
  const [isRenaming, setIsRenaming] = useState(false);
@@ -207,14 +225,16 @@ export const Item = ({
207
225
  </OverlayTrigger>
208
226
  </div>
209
227
  ) : null}
210
-
211
- <DeleteConfirmationModal
212
- show={showModal}
213
- onClose={handleModalClose}
214
- onConfirm={handleConfirmModal}
215
- isDirectory={isDirectory}
216
- name={name}
217
- />
228
+ {/* TODO Move this up to a higher level of the tree */}
229
+ {showModal && (
230
+ <DeleteConfirmationModal
231
+ show={showModal}
232
+ onClose={handleModalClose}
233
+ onConfirm={handleConfirmModal}
234
+ isDirectory={isDirectory}
235
+ name={name}
236
+ />
237
+ )}
218
238
  </div>
219
239
  );
220
240
  };
@@ -1,49 +1,32 @@
1
- import { DirectoryListing } from './DirectoryListing';
2
- import {
1
+ import { useContext } from 'react';
2
+ import type {
3
3
  FileId,
4
4
  FileTree,
5
5
  FileTreeFile,
6
- FileTreePath,
7
6
  } from '../../types';
7
+ import { VZCodeContext } from '../VZCodeContext';
8
+ import { DirectoryListing } from './DirectoryListing';
8
9
  import { FileListing } from './FileListing';
9
10
 
10
11
  // A "Listing" is a "FileListing" or a "DirectoryListing"
11
12
  // that appears in the Sidebar.
12
13
  export const Listing = ({
13
14
  entity,
14
- renameFile,
15
- deleteFile,
16
- renameDirectory,
17
- deleteDirectory,
18
15
  handleFileClick,
19
16
  handleFileDoubleClick,
20
- isDirectoryOpen,
21
- toggleDirectory,
22
- activeFileId,
23
17
  }: {
24
18
  entity: FileTree | FileTreeFile;
25
- renameFile: (fileId: FileId, newName: string) => void;
26
- deleteFile: (fileId: FileId) => void;
27
- renameDirectory: (
28
- path: FileTreePath,
29
- oldName: string,
30
- newName: string,
31
- ) => void;
32
- deleteDirectory: (path: FileTreePath) => void;
33
19
  handleFileClick: (fileId: FileId) => void;
34
20
  handleFileDoubleClick: (fileId: FileId) => void;
35
- isDirectoryOpen: (path: string) => boolean;
36
- toggleDirectory: (path: string) => void;
37
- activeFileId: FileId;
38
21
  }) => {
22
+ const { activeFileId } = useContext(VZCodeContext);
39
23
  const { name, file, fileId } = entity as FileTreeFile;
40
24
  const { path, children } = entity as FileTree;
25
+
41
26
  return file ? (
42
27
  <FileListing
43
28
  fileId={fileId}
44
29
  name={name}
45
- renameFile={renameFile}
46
- deleteFile={deleteFile}
47
30
  handleFileClick={handleFileClick}
48
31
  handleFileDoubleClick={handleFileDoubleClick}
49
32
  isActive={fileId === activeFileId}
@@ -53,15 +36,8 @@ export const Listing = ({
53
36
  name={name}
54
37
  path={path}
55
38
  children={children}
56
- renameFile={renameFile}
57
- deleteFile={deleteFile}
58
- renameDirectory={renameDirectory}
59
- deleteDirectory={deleteDirectory}
60
39
  handleFileClick={handleFileClick}
61
40
  handleFileDoubleClick={handleFileDoubleClick}
62
- isDirectoryOpen={isDirectoryOpen}
63
- toggleDirectory={toggleDirectory}
64
- activeFileId={activeFileId}
65
41
  />
66
42
  );
67
43
  };
@@ -94,121 +94,116 @@ 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
+ handleFileClick={handleFileClick}
178
+ handleFileDoubleClick={
179
+ handleFileDoubleClick
180
+ }
207
181
  />
182
+ );
183
+ })
184
+ ) : (
185
+ <div className="empty">
186
+ <div className="empty-text">
187
+ It looks like you don't have any files yet!
188
+ Click the "Create file" button above to create
189
+ your first file.
208
190
  </div>
209
191
  </div>
210
192
  )}
211
193
  </div>
194
+
195
+ {enableConnectionStatus && (
196
+ <div className="connection-status">
197
+ {connected ? 'Connected' : 'Connection Lost'}
198
+ <div className="connection">
199
+ <div
200
+ className={`connection-status-indicator ${
201
+ connected ? 'connected' : 'disconnected'
202
+ }`}
203
+ />
204
+ </div>
205
+ </div>
206
+ )}
212
207
  </div>
213
208
  );
214
209
  };
@@ -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
@@ -264,7 +267,12 @@ onmessage = async ({ data }) => {
264
267
  // flag or with a '--target' of 'es2015' or higher."
265
268
  const LINT_ERROR_CODE_ITERATED_THROUGH = 2802;
266
269
 
270
+ // Argument of type '{ Month: string; High: number; Temp: number; Low: number; }'
271
+ // is not assignable to parameter of type 'never'.
272
+ const LINT_ERROR_CODE_ASSIGNABLE_TO_NEVER = 2345;
273
+
267
274
  if (debug) {
275
+ console.log('tsErrors');
268
276
  console.log(tsErrors);
269
277
  }
270
278
  tsErrors = tsErrors.filter(
@@ -276,7 +284,9 @@ onmessage = async ({ data }) => {
276
284
  error.code !== LINT_ERROR_CODE_ANY_TYPE_KEYS &&
277
285
  error.code !==
278
286
  LINT_ERROR_CODE_NON_EXISTENT_PROPERTY &&
279
- error.code !== LINT_ERROR_CODE_ITERATED_THROUGH,
287
+ error.code !== LINT_ERROR_CODE_ITERATED_THROUGH &&
288
+ error.code !==
289
+ LINT_ERROR_CODE_ASSIGNABLE_TO_NEVER,
280
290
  );
281
291
  // }
282
292
  tsErrors = convertToCodeMirrorDiagnostic(tsErrors);
package/src/types.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  // FileId
2
2
  // * A unique ID for a file.
3
-
4
3
  // * This is a random string.
5
4
  export type FileId = string;
6
5
 
6
+ // ItemId
7
+ // * A unique ID for an item in the sidebar.
8
+ // * This could be either a file ID or a directory path.
9
+ export type ItemId = FileId | FileTreePath;
10
+
7
11
  // Files
8
12
  // * A collection of files.
9
13
  // * Keys are _not_ file names or array indices,