vzcode 0.80.0 → 0.82.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.
@@ -5,7 +5,9 @@ import {
5
5
  useState,
6
6
  } from 'react';
7
7
  import {
8
+ FileId,
8
9
  Files,
10
+ ItemId,
9
11
  ShareDBDoc,
10
12
  SubmitOperation,
11
13
  Username,
@@ -111,6 +113,9 @@ export type VZCodeContextValue = {
111
113
  runCodeRef: React.MutableRefObject<null | (() => void)>;
112
114
 
113
115
  connected: boolean;
116
+
117
+ hoveredItemId: ItemId | null;
118
+ setHoveredItemId: (itemId: ItemId | null) => void;
114
119
  };
115
120
 
116
121
  export const VZCodeProvider = ({
@@ -275,6 +280,10 @@ export const VZCodeProvider = ({
275
280
  runCodeRef,
276
281
  });
277
282
 
283
+ // Track the currently hovered file id.
284
+ const [hoveredItemId, setHoveredItemId] =
285
+ useState<ItemId | null>(null);
286
+
278
287
  // The value provided by this context.
279
288
  const value: VZCodeContextValue = {
280
289
  content,
@@ -328,6 +337,9 @@ export const VZCodeProvider = ({
328
337
  runCodeRef,
329
338
 
330
339
  connected,
340
+
341
+ hoveredItemId,
342
+ setHoveredItemId,
331
343
  };
332
344
 
333
345
  return (
@@ -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,27 @@ export const VZMiddle = ({
20
21
  aiAssistOptions,
21
22
  aiAssistTooltipText,
22
23
  aiAssistClickOverride,
24
+ customInteractRules,
25
+ allowGlobals = false,
23
26
  }: {
24
27
  enableAIAssist?: boolean;
25
28
  aiAssistEndpoint?: string;
26
29
  aiAssistOptions?: { [key: string]: string };
27
30
  aiAssistTooltipText?: string;
28
31
  aiAssistClickOverride?: () => void;
32
+ customInteractRules?: Array<InteractRule>;
33
+ allowGlobals?: boolean;
29
34
  }) => {
30
35
  const { codeEditorWidth } = useContext(
31
36
  SplitPaneResizeContext,
32
37
  );
33
38
 
34
- // TODO leverage this context in deeper levels of the component tree.
35
39
  const {
36
40
  content,
37
- shareDBDoc,
38
- submitOperation,
39
41
  localPresence,
40
42
  docPresence,
41
- files,
42
- createFile,
43
43
  activeFileId,
44
- setActiveFileId,
45
- tabList,
46
- openTab,
47
- closeTabs,
48
- theme,
49
- username,
50
- editorCache,
51
- editorWantsFocus,
52
- editorNoLongerWantsFocus,
53
44
  errorMessage,
54
- typeScriptWorker,
55
45
  } = useContext(VZCodeContext);
56
46
 
57
47
  // This prevents the CodeEditor from rendering
@@ -66,27 +56,18 @@ export const VZMiddle = ({
66
56
  className="middle"
67
57
  style={{ width: codeEditorWidth + 'px' }}
68
58
  >
69
- <TabList
70
- files={files}
71
- tabList={tabList}
72
- activeFileId={activeFileId}
73
- setActiveFileId={setActiveFileId}
74
- openTab={openTab}
75
- closeTabs={closeTabs}
76
- createFile={createFile}
77
- />
59
+ <TabList />
78
60
  {isClient && content && activeFileId && (
79
- <CodeEditor />
61
+ <CodeEditor
62
+ customInteractRules={customInteractRules}
63
+ allowGlobals={allowGlobals}
64
+ />
80
65
  )}
81
66
  {isClient &&
82
67
  enableAIAssist &&
83
68
  content &&
84
69
  activeFileId ? (
85
70
  <AIAssistWidget
86
- activeFileId={activeFileId}
87
- shareDBDoc={shareDBDoc}
88
- editorCache={editorCache}
89
- tabList={tabList}
90
71
  aiAssistEndpoint={aiAssistEndpoint}
91
72
  aiAssistOptions={aiAssistOptions}
92
73
  aiAssistTooltipText={aiAssistTooltipText}
@@ -64,9 +64,6 @@ export const VZSettings = ({
64
64
  }, [selectedFont]);
65
65
 
66
66
  useEffect(() => {
67
- // const fontSize = event.target.value;
68
- // const root = document.documentElement;
69
- // root.style.setProperty('--vzcode-font-size', fontSize);
70
67
  document.body.style.setProperty(
71
68
  '--vzcode-font-size',
72
69
  selectedFontSize,
@@ -109,7 +106,6 @@ export const VZSettings = ({
109
106
 
110
107
  <Form.Group className="mb-3" controlId="formFork">
111
108
  <Form.Label>Theme</Form.Label>
112
-
113
109
  <select
114
110
  className="form-select"
115
111
  value={theme}
@@ -121,7 +117,6 @@ export const VZSettings = ({
121
117
  </option>
122
118
  ))}
123
119
  </select>
124
-
125
120
  <Form.Text className="text-muted">
126
121
  Select a color theme for the editor
127
122
  </Form.Text>
@@ -129,7 +124,6 @@ export const VZSettings = ({
129
124
 
130
125
  <Form.Group className="mb-3" controlId="formFork">
131
126
  <Form.Label>Font</Form.Label>
132
-
133
127
  <select
134
128
  className="form-select"
135
129
  onChange={handleFontChange}
@@ -141,7 +135,6 @@ export const VZSettings = ({
141
135
  </option>
142
136
  ))}
143
137
  </select>
144
-
145
138
  <Form.Text className="text-muted">
146
139
  Select font for the editor
147
140
  </Form.Text>
@@ -149,7 +142,6 @@ export const VZSettings = ({
149
142
 
150
143
  <Form.Group className="mb-3" controlId="formFork">
151
144
  <Form.Label>Font Size</Form.Label>
152
-
153
145
  <select
154
146
  className="form-select"
155
147
  onChange={handleFontSizeChange}
@@ -161,11 +153,53 @@ export const VZSettings = ({
161
153
  </option>
162
154
  ))}
163
155
  </select>
164
-
165
156
  <Form.Text className="text-muted">
166
157
  Select a font size for the editor
167
158
  </Form.Text>
168
159
  </Form.Group>
160
+
161
+ {/* Keyboard Shortcuts Documentation */}
162
+ <Form.Group className="mb-3" controlId="formFork">
163
+ <Form.Label>Keyboard Shortcuts</Form.Label>
164
+ <ul>
165
+ <li>
166
+ <strong>Alt-w</strong> <span>⌥W</span>
167
+ <br />
168
+ <span>Close the current tab</span>
169
+ </li>
170
+ <li>
171
+ <strong>Alt-n</strong> <span>⌥N</span>
172
+ <br />
173
+ <span>Open the create file modal</span>
174
+ </li>
175
+ <li>
176
+ <strong>Alt-PageUp</strong> <span>⌥⇞</span>
177
+ <br />
178
+ <span>
179
+ Change the active tab to the previous one
180
+ </span>
181
+ </li>
182
+ <li>
183
+ <strong>Alt-PageDown</strong> <span>⌥⇟</span>
184
+ <br />
185
+ <span>
186
+ Change the active tab to the next one
187
+ </span>
188
+ </li>
189
+ <li>
190
+ <strong>Ctrl-s</strong> or{' '}
191
+ <strong>Shift-Enter</strong> <span>⌘S</span>{' '}
192
+ or <span>⇧↩</span>
193
+ <br />
194
+ <span>
195
+ Run the code and format it with Prettier
196
+ </span>
197
+ </li>
198
+ </ul>
199
+ <Form.Text className="text-muted">
200
+ Keyboard shortcuts for quick actions
201
+ </Form.Text>
202
+ </Form.Group>
169
203
  </Modal.Body>
170
204
  <Modal.Footer>
171
205
  <Button variant="primary" onClick={closeSettings}>
@@ -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
  };
@@ -174,17 +174,10 @@ export const VZSidebar = ({
174
174
  <Listing
175
175
  key={key}
176
176
  entity={entity}
177
- renameFile={renameFile}
178
- deleteFile={deleteFile}
179
- renameDirectory={renameDirectory}
180
- deleteDirectory={deleteDirectory}
181
177
  handleFileClick={handleFileClick}
182
178
  handleFileDoubleClick={
183
179
  handleFileDoubleClick
184
180
  }
185
- isDirectoryOpen={isDirectoryOpen}
186
- toggleDirectory={toggleDirectory}
187
- activeFileId={activeFileId}
188
181
  />
189
182
  );
190
183
  })
@@ -20,6 +20,9 @@ export type LinterRequest = {
20
20
  fileName: string;
21
21
  fileContent: string;
22
22
  requestId: string;
23
+
24
+ // If true, linting will not show errors for global variables.
25
+ allowGlobals?: boolean;
23
26
  };
24
27
 
25
28
  export type LinterResponse = {
@@ -217,8 +217,12 @@ onmessage = async ({ data }) => {
217
217
  console.log('Lint Request');
218
218
  }
219
219
  const linterRequest: LinterRequest = data;
220
- const { fileName, fileContent, requestId } =
221
- linterRequest;
220
+ const {
221
+ fileName,
222
+ fileContent,
223
+ requestId,
224
+ allowGlobals,
225
+ } = linterRequest;
222
226
 
223
227
  const tsFileName = getTSFileName(fileName);
224
228
  let tsErrors = [];
@@ -267,7 +271,15 @@ onmessage = async ({ data }) => {
267
271
  // flag or with a '--target' of 'es2015' or higher."
268
272
  const LINT_ERROR_CODE_ITERATED_THROUGH = 2802;
269
273
 
274
+ // Argument of type '{ Month: string; High: number; Temp: number; Low: number; }'
275
+ // is not assignable to parameter of type 'never'.
276
+ const LINT_ERROR_CODE_ASSIGNABLE_TO_NEVER = 2345;
277
+
278
+ // Cannot find name 'd3'.
279
+ const LINT_ERROR_CODE_CANNOT_FIND_NAME = 2304;
280
+
270
281
  if (debug) {
282
+ console.log('tsErrors');
271
283
  console.log(tsErrors);
272
284
  }
273
285
  tsErrors = tsErrors.filter(
@@ -279,7 +291,15 @@ onmessage = async ({ data }) => {
279
291
  error.code !== LINT_ERROR_CODE_ANY_TYPE_KEYS &&
280
292
  error.code !==
281
293
  LINT_ERROR_CODE_NON_EXISTENT_PROPERTY &&
282
- error.code !== LINT_ERROR_CODE_ITERATED_THROUGH,
294
+ error.code !== LINT_ERROR_CODE_ITERATED_THROUGH &&
295
+ error.code !==
296
+ LINT_ERROR_CODE_ASSIGNABLE_TO_NEVER &&
297
+ // If allowGlobals is true, we will allow
298
+ // global variables to be used without error.
299
+ allowGlobals
300
+ ? error.code !==
301
+ LINT_ERROR_CODE_CANNOT_FIND_NAME
302
+ : true,
283
303
  );
284
304
  // }
285
305
  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,