vzcode 0.15.0 → 0.17.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
@@ -18,7 +18,7 @@
18
18
  href="https://unpkg.com/bootstrap@5.3.0/dist/css/bootstrap.min.css"
19
19
  rel="stylesheet"
20
20
  />
21
- <script type="module" crossorigin src="/assets/index-176d73ed.js"></script>
21
+ <script type="module" crossorigin src="/assets/index-ab3ae13e.js"></script>
22
22
  <link rel="stylesheet" href="/assets/index-ccd52a89.css">
23
23
  </head>
24
24
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -47,14 +47,14 @@
47
47
  "homepage": "https://github.com/vizhub-core/vzcode#readme",
48
48
  "dependencies": {
49
49
  "@codemirror/lang-css": "^6.2.1",
50
- "@codemirror/lang-html": "^6.4.5",
51
- "@codemirror/lang-javascript": "^6.2.0",
50
+ "@codemirror/lang-html": "^6.4.6",
51
+ "@codemirror/lang-javascript": "^6.2.1",
52
52
  "@codemirror/lang-markdown": "^6.2.0",
53
53
  "@codemirror/state": "^6.2.1",
54
54
  "@codemirror/theme-one-dark": "^6.1.2",
55
55
  "@replit/codemirror-interact": "^6.3.0",
56
56
  "@teamwork/websocket-json-stream": "^2.0.0",
57
- "@uiw/codemirror-themes-all": "^4.21.11",
57
+ "@uiw/codemirror-themes-all": "^4.21.12",
58
58
  "codemirror": "^6.0.1",
59
59
  "codemirror-ot": "^4.1.0",
60
60
  "d3-array": "^3.2.4",
@@ -77,7 +77,7 @@
77
77
  "@types/react": "^18.2.21",
78
78
  "@types/react-dom": "^18.2.7",
79
79
  "@vitejs/plugin-react": "^4.0.4",
80
- "prettier": "^3.0.2",
80
+ "prettier": "^3.0.3",
81
81
  "sass": "^1.66.1",
82
82
  "typescript": "^5.2.2",
83
83
  "vite": "^4.4.9",
@@ -10,9 +10,16 @@ import { FileId, Files, ShareDBDoc, VZCodeContent } from '../types';
10
10
  import { TabList } from './TabList';
11
11
  import { useOpenDirectories } from './useOpenDirectories';
12
12
  import { useTabsState } from './useTabsState';
13
- import { defaultTheme } from './themes';
13
+ import { ThemeLabel, defaultTheme } from './themes';
14
14
  import './style.scss';
15
+ import { useEditorCache } from './useEditorCache';
16
+ import { useDynamicTheme } from './useDynamicTheme';
15
17
  import { usePrettier } from './usePrettier';
18
+ // @ts-ignore
19
+ import PrettierWorker from './usePrettier/worker?worker';
20
+
21
+ // Instantiate the Prettier worker.
22
+ const prettierWorker = new PrettierWorker();
16
23
 
17
24
  // Register our custom JSON1 OT type that supports presence.
18
25
  // See https://github.com/vizhub-core/json1-presence
@@ -45,7 +52,7 @@ function App() {
45
52
  );
46
53
 
47
54
  // Auto-run Pretter after local changes.
48
- usePrettier(shareDBDoc, submitOperation);
55
+ usePrettier(shareDBDoc, submitOperation, prettierWorker);
49
56
 
50
57
  // Local ShareDB presence, for broadcasting our cursor position
51
58
  // so other clients can see it.
@@ -61,30 +68,8 @@ function App() {
61
68
  // Starts out as `null` until the document is loaded.
62
69
  const [content, setContent] = useState<VZCodeContent | null>(null);
63
70
 
64
- // The id of the currently open file tab.
65
- const [activeFileId, setActiveFileId] = useState<FileId>(null);
66
-
67
- // The ordered list of tabs.
68
- const [tabList, setTabList] = useState<Array<FileId>>([]);
69
-
70
- // Logic for opening and closing tabs.
71
- const { closeTab, openTab } = useTabsState(
72
- activeFileId,
73
- setActiveFileId,
74
- tabList,
75
- setTabList,
76
- );
77
-
78
- // The current theme.
79
- const [theme, setTheme] = useState<string>(defaultTheme);
80
-
81
- // True to show the settings modal.
82
- const [isSettingsOpen, setIsSettingsOpen] = useState(false);
83
-
84
- // The set of open directories.
85
- const { isDirectoryOpen, toggleDirectory } = useOpenDirectories();
86
-
87
71
  // Set up the connection to ShareDB.
72
+ // TODO move this logic to a hook called `useShareDB`
88
73
  useEffect(() => {
89
74
  // Since there is only ever a single document,
90
75
  // these things are pretty arbitrary.
@@ -136,6 +121,54 @@ function App() {
136
121
  };
137
122
  }, []);
138
123
 
124
+ // The id of the currently open file tab.
125
+ // TODO make this a URL param
126
+ const [activeFileId, setActiveFileId] = useState<FileId>(null);
127
+
128
+ // The ordered list of tabs.
129
+ // TODO make this a URL param
130
+ const [tabList, setTabList] = useState<Array<FileId>>([]);
131
+
132
+ // Logic for opening and closing tabs.
133
+ const { closeTab, openTab } = useTabsState(
134
+ activeFileId,
135
+ setActiveFileId,
136
+ tabList,
137
+ setTabList,
138
+ );
139
+
140
+ // The current theme.
141
+ // TODO persist this in local storage
142
+ const [theme, setTheme] = useState<ThemeLabel>(defaultTheme);
143
+
144
+ // Cache of CodeMirror editors by file id.
145
+ const editorCache = useEditorCache();
146
+
147
+ // Handle dynamic theme changes.
148
+ useDynamicTheme(editorCache, theme);
149
+
150
+ // True to show the settings modal.
151
+ const [isSettingsOpen, setIsSettingsOpen] = useState(false);
152
+
153
+ // The set of open directories.
154
+ const { isDirectoryOpen, toggleDirectory } = useOpenDirectories();
155
+
156
+ // TODO move this logic to a hook called `useFileCRUD`
157
+ // Include
158
+ // - `createFile`
159
+ // - `handleRenameFileClick`
160
+ // - `deleteFile`
161
+ // - `handleDeleteFileClick`
162
+ const createFile = useCallback(() => {
163
+ const name = prompt('Enter new file name');
164
+ if (name) {
165
+ submitOperation((document) => ({
166
+ ...document,
167
+ files: { ...document.files, [randomId()]: { name, text: '' } },
168
+ }));
169
+ }
170
+ }, [submitOperation]);
171
+
139
172
  // Called when a file in the sidebar is double-clicked.
140
173
  const handleRenameFileClick = useCallback(
141
174
  (fileId: FileId) => {
@@ -170,16 +203,6 @@ function App() {
170
203
  [submitOperation, closeTab],
171
204
  );
172
205
 
173
- const createFile = useCallback(() => {
174
- const name = prompt('Enter new file name');
175
- if (name) {
176
- submitOperation((document) => ({
177
- ...document,
178
- files: { ...document.files, [randomId()]: { name, text: '' } },
179
- }));
180
- }
181
- }, [submitOperation]);
182
-
183
206
  // TODO prompt the user "Are you sure?"
184
207
  const handleDeleteFileClick = useCallback(
185
208
  (fileId: FileId, event: React.MouseEvent) => {
@@ -213,6 +236,7 @@ function App() {
213
236
  }, 800);
214
237
  }, [submitOperation]);
215
238
 
239
+ // Isolate the files object from the document.
216
240
  const files: Files | null = content ? content.files : null;
217
241
 
218
242
  return (
@@ -231,6 +255,7 @@ function App() {
231
255
  <Settings
232
256
  show={isSettingsOpen}
233
257
  onClose={handleSettingsClose}
258
+ theme={theme}
234
259
  setTheme={setTheme}
235
260
  />
236
261
  </div>
@@ -250,6 +275,7 @@ function App() {
250
275
  activeFileId={activeFileId}
251
276
  theme={theme}
252
277
  onInteract={handleInteract}
278
+ editorCache={editorCache}
253
279
  />
254
280
  ) : null}
255
281
  </div>
@@ -6,17 +6,26 @@ import { html } from '@codemirror/lang-html';
6
6
  import { css } from '@codemirror/lang-css';
7
7
  import { json1Sync } from 'codemirror-ot';
8
8
  import { json1Presence, textUnicode } from '../../ot';
9
- import { FileId, ShareDBDoc } from '../../types';
9
+ import { FileId, ShareDBDoc, VZCodeContent } from '../../types';
10
10
  import { json1PresenceBroadcast } from './json1PresenceBroadcast';
11
11
  import { json1PresenceDisplay } from './json1PresenceDisplay';
12
12
  import { widgets } from './widgets';
13
+ import { EditorCache, EditorCacheValue } from '../useEditorCache';
14
+ import { ThemeLabel, themeOptionsByLabel } from '../themes';
13
15
 
14
- // Singleton cache of CodeMirror instances
15
- // These are created, but never destroyed.
16
- // TODO consider invalidating this cache at some point.
17
- // * Keys are file ids
18
- // * Values are CodeMirror instances (TODO add CodeMirror type)
19
- const editorCache = new Map<FileId, any>();
16
+ // Language extensions for CodeMirror.
17
+ // Keys are file extensions.
18
+ // Values are CodeMirror extensions.
19
+ // TODO consider moving this to a separate file.
20
+ const languageExtensions = {
21
+ js: javascript,
22
+ json: javascript,
23
+ jsx: javascript,
24
+ ts: javascript,
25
+ html: html,
26
+ css: css,
27
+ md: markdown,
28
+ };
20
29
 
21
30
  // Gets a value at a path in an object.
22
31
  // e.g. getAtPath({a: {b: 1}}, ['a', 'b']) === 1
@@ -29,7 +38,8 @@ const getAtPath = (obj, path) => {
29
38
  return current;
30
39
  };
31
40
 
32
- // Gets or creates a CodeMirror editor for the given file id.
41
+ // Gets or creates an `editorCache` entry for the given file id.
42
+ // Looks in `editorCache` first, and if not found, creates a new editor.
33
43
  export const getOrCreateEditor = ({
34
44
  fileId,
35
45
  shareDBDoc,
@@ -38,27 +48,46 @@ export const getOrCreateEditor = ({
38
48
  docPresence,
39
49
  theme,
40
50
  onInteract,
51
+ editorCache,
41
52
  }: {
42
53
  fileId: FileId;
43
- shareDBDoc: ShareDBDoc<any>;
54
+
55
+ // The ShareDB document that contains the file.
56
+ // Used when the editor is created for:
57
+ // * Figuring out the initial text
58
+ // * Figuring out the initial language extension
59
+ // based on the file extension.
60
+ // It's also passed into the `json1Sync` extension,
61
+ // which is used for multiplayer editing.
62
+ shareDBDoc: ShareDBDoc<VZCodeContent>;
63
+
44
64
  filesPath: string[];
45
65
  localPresence: any;
46
66
  docPresence: any;
47
- theme: any;
67
+ theme: ThemeLabel;
48
68
  onInteract?: () => void;
49
- }) => {
69
+ editorCache: EditorCache;
70
+ }): EditorCacheValue => {
71
+ // Cache hit
72
+ if (editorCache.has(fileId)) {
73
+ return editorCache.get(fileId);
74
+ }
75
+
76
+ // Cache miss
77
+
78
+ // Compute `text` and `fileExtension` from the ShareDB document.
50
79
  const data = shareDBDoc.data;
51
80
  const textPath = [...filesPath, fileId, 'text'];
52
81
  const namePath = [...filesPath, fileId, 'name'];
53
-
54
82
  const text = getAtPath(data, textPath);
55
83
  const name = getAtPath(data, namePath);
56
-
57
84
  const fileExtension = name.split('.').pop();
58
85
 
59
- // TODO revisit if this is the right approach for dynamic themes
60
- let themeSet = new Compartment();
86
+ // Create a compartment for the theme so that it can be changed dynamically.
87
+ // Inspired by: https://github.com/craftzdog/cm6-themes/blob/main/example/index.ts
88
+ let themeCompartment = new Compartment();
61
89
 
90
+ // The CodeMirror extensions to use.
62
91
  const extensions = [];
63
92
 
64
93
  // This plugin implements multiplayer editing,
@@ -81,38 +110,67 @@ export const getOrCreateEditor = ({
81
110
  if (docPresence)
82
111
  extensions.push(json1PresenceDisplay({ path: textPath, docPresence }));
83
112
 
113
+ // This is the "basic setup" for CodeMirror,
114
+ // which actually adds a ton on functionality.
115
+ // TODO vet this functionality, and determine how much
116
+ // we want to replace with
117
+ // https://github.com/vizhub-core/vzcode/issues/134
84
118
  extensions.push(basicSetup);
85
- extensions.push(themeSet.of(theme));
119
+
120
+ // This supports dynamic changing of the theme.
121
+ extensions.push(themeCompartment.of(themeOptionsByLabel[theme].value));
86
122
 
87
123
  // TODO handle dynamic changing of the file extension.
88
- if (
89
- fileExtension === 'js' ||
90
- fileExtension === 'json' ||
91
- fileExtension === 'jsx' ||
92
- fileExtension === 'ts'
93
- ) {
94
- extensions.push(javascript());
95
- } else if (fileExtension === 'html') {
96
- extensions.push(html());
97
- } else if (fileExtension === 'css') {
98
- extensions.push(css());
99
- } else if (fileExtension === 'md') {
100
- extensions.push(markdown());
124
+ // TODO handle dynamic file extensions by making
125
+ // the CodeMirror language extension dynamic
126
+ // using a Compartment.
127
+ const languageCompartment = new Compartment();
128
+ // See https://github.com/vizhub-core/vzcode/issues/55
129
+ const getLanguageExtension = (fileExtension) => {
130
+ const languageFunc = languageExtensions[fileExtension];
131
+ return languageFunc ? languageFunc() : undefined;
132
+ };
133
+
134
+ const languageExtension = getLanguageExtension(fileExtension);
135
+ if (languageExtension) {
136
+ extensions.push(languageCompartment.of(languageExtension));
137
+ } else {
138
+ // Not sure if this case even works.
139
+ // TODO manually test this case by creating a file
140
+ // that has no extension, opening it up,
141
+ // and then adding an extension.
142
+ console.warn(`No language extension for file extension: ${fileExtension}`);
143
+ // We still need to push the compartment,
144
+ // otherwise the compartment won't work when
145
+ // a file extension _is_ added later on.
146
+ extensions.push(languageCompartment.of([]));
101
147
  }
102
148
 
149
+ // Add interactive widgets.
150
+ // Includes the Alt+drag functionality for numbers.
151
+ // Calls `onInteract` when one of those widgets is interacted with.
152
+ // This can be used to trigger a transition to throttled mode
153
+ // for hot reloading.
154
+ // TODO consider leveraging the new `dragEnd` event handler.
155
+ // and removing the `onInteract` callback, replacing it with
156
+ // `onInteractStart` and `onInteractEnd`.
157
+ // That may be tricky for one-off interactions though, like
158
+ // the boolean checkboxes. The color pickers are also tricky,
159
+ // as they would also need to be able to handle `onInteractEnd`.
160
+ // See https://github.com/replit/codemirror-interact/issues/14
103
161
  extensions.push(widgets({ onInteract }));
104
162
 
105
- let editor = editorCache.get(fileId);
106
- if (!editor) {
107
- editor = new EditorView({
108
- state: EditorState.create({
109
- doc: text,
110
- extensions,
111
- }),
112
- });
113
-
114
- // Populate the cache.
115
- editorCache.set(fileId, editor);
116
- }
117
- return editor;
163
+ const editor = new EditorView({
164
+ state: EditorState.create({
165
+ doc: text,
166
+ extensions,
167
+ }),
168
+ });
169
+
170
+ const editorCacheValue = { editor, themeCompartment };
171
+
172
+ // Populate the cache.
173
+ editorCache.set(fileId, editorCacheValue);
174
+
175
+ return editorCacheValue;
118
176
  };
@@ -1,6 +1,7 @@
1
1
  import { useRef, useLayoutEffect } from 'react';
2
2
  import { FileId } from '../../types';
3
- import { defaultTheme, themesByLabel } from '../themes';
3
+ import { ThemeLabel, defaultTheme } from '../themes';
4
+ import { EditorCache, EditorCacheValue } from '../useEditorCache';
4
5
  import { getOrCreateEditor } from './getOrCreateEditor';
5
6
  import './style.scss';
6
7
 
@@ -12,40 +13,51 @@ export const CodeEditor = ({
12
13
  theme = defaultTheme,
13
14
  filesPath = ['files'],
14
15
  onInteract,
16
+ editorCache,
15
17
  }: {
16
18
  activeFileId: FileId;
17
19
  shareDBDoc: any;
18
20
  localPresence?: any;
19
21
  docPresence?: any;
20
- theme?: string;
22
+ theme?: ThemeLabel;
21
23
 
22
24
  // The path of the files object in the ShareDB document.
23
- // Defaults to the root of the document.
25
+ // Defaults to `files` if not provided.
24
26
  filesPath?: string[];
25
27
  onInteract?: () => void;
28
+ editorCache: EditorCache;
26
29
  }) => {
27
30
  const ref = useRef<HTMLDivElement>(null);
28
31
 
32
+ // Every time the active file switches from one file to another,
33
+ // the editor corresponding to the old file is removed from the DOM,
34
+ // and the editor corresponding to the new file is added to the DOM.
35
+
29
36
  useLayoutEffect(() => {
37
+ // Guard against cases where page is still loading.
30
38
  if (!ref.current) return;
31
39
  if (!shareDBDoc) return;
32
40
 
33
- // `getOrCreateEditor` gets called
34
- // every time the active file changes.
35
- const editor = getOrCreateEditor({
41
+ // Get the editor corresponding to the active file.
42
+ // Looks in `editorCache` first, and if not found, creates a new editor.
43
+ const editorCacheValue: EditorCacheValue = getOrCreateEditor({
36
44
  fileId: activeFileId,
37
45
  shareDBDoc,
38
46
  filesPath,
39
47
  localPresence,
40
48
  docPresence,
41
- // TODO refactor this, make dynamic themes work
42
- theme: themesByLabel[theme],
49
+ theme,
43
50
  onInteract,
51
+ editorCache,
44
52
  });
45
- ref.current.appendChild(editor.dom);
53
+
54
+ // Add the editor to the DOM.
55
+ ref.current.appendChild(editorCacheValue.editor.dom);
46
56
 
47
57
  return () => {
48
- ref.current.removeChild(editor.dom);
58
+ // Remove the old editor from the DOM.
59
+ // This happens every time `activeFileId` changes.
60
+ ref.current.removeChild(editorCacheValue.editor.dom);
49
61
  };
50
62
  }, [shareDBDoc, activeFileId]);
51
63
 
@@ -3,7 +3,7 @@ import Modal from 'react-bootstrap/Modal';
3
3
  import Form from 'react-bootstrap/Form';
4
4
  import Button from 'react-bootstrap/Button';
5
5
  import Select from 'react-select';
6
- import { themes } from './themes';
6
+ import { ThemeLabel, themeOptionsByLabel, themes } from './themes';
7
7
 
8
8
  const saveTimes = [
9
9
  { value: 1, label: '1 second' },
@@ -12,7 +12,17 @@ const saveTimes = [
12
12
  { value: 30, label: '30 seconds' },
13
13
  ];
14
14
 
15
- export const Settings = ({ show, onClose, setTheme }) => {
15
+ export const Settings = ({
16
+ show,
17
+ onClose,
18
+ setTheme,
19
+ theme,
20
+ }: {
21
+ show: boolean;
22
+ onClose: () => void;
23
+ setTheme: (theme: ThemeLabel) => void;
24
+ theme: ThemeLabel;
25
+ }) => {
16
26
  const handleChange = useCallback((selectedOption) => {
17
27
  setTheme(selectedOption.label);
18
28
  // TODO make this work.
@@ -55,7 +65,11 @@ export const Settings = ({ show, onClose, setTheme }) => {
55
65
  </Form.Group>
56
66
  <Form.Group className="mb-3" controlId="formFork">
57
67
  <Form.Label>Theme</Form.Label>
58
- <Select options={themes} onChange={handleChange} />
68
+ <Select
69
+ options={themes}
70
+ onChange={handleChange}
71
+ value={themeOptionsByLabel[theme]}
72
+ />
59
73
  <Form.Text className="text-muted">
60
74
  Select a color theme for the editor
61
75
  </Form.Text>
@@ -3,7 +3,7 @@ import * as ReactDOM from 'react-dom/client';
3
3
  import App from './App';
4
4
 
5
5
  ReactDOM.createRoot(document.getElementById('root')).render(
6
- <React.StrictMode>
7
- <App />
8
- </React.StrictMode>,
6
+ // <React.StrictMode>
7
+ <App />,
8
+ // </React.StrictMode>,
9
9
  );
@@ -7,11 +7,29 @@ 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
11
 
11
12
  // CodeMirror themes
12
- export const themes = [
13
+ export type ThemeLabel =
14
+ | 'abcdef'
15
+ | 'dracula'
16
+ | 'eclipse'
17
+ | 'material'
18
+ | 'nord'
19
+ | 'oneDark'
20
+ | 'okaidia'
21
+ | 'github'
22
+ | 'xcode';
23
+ // | 'vizhub'
24
+
25
+ export type ThemeOption = {
26
+ value: any;
27
+ label: ThemeLabel;
28
+ };
29
+
30
+ export const themes: Array<ThemeOption> = [
13
31
  { value: abcdef, label: 'abcdef' },
14
- { value: dracula, label: 'darcula' },
32
+ { value: dracula, label: 'dracula' },
15
33
  { value: eclipse, label: 'eclipse' },
16
34
  { value: material, label: 'material' },
17
35
  { value: nord, label: 'nord' },
@@ -19,13 +37,18 @@ export const themes = [
19
37
  { value: okaidia, label: 'okaidia' },
20
38
  { value: githubDark, label: 'github' },
21
39
  { value: xcodeLight, label: 'xcode' },
40
+ // { value: vizhub, label: 'vizhub' },
22
41
  ];
23
42
 
24
43
  // Map theme labels to theme values
25
- export const themesByLabel = themes.reduce((acc, theme) => {
26
- acc[theme.label] = theme.value;
27
- return acc;
28
- }, {});
44
+ export const themeOptionsByLabel: Record<ThemeLabel, ThemeOption> =
45
+ themes.reduce(
46
+ (acc, themeOption: ThemeOption) => {
47
+ acc[themeOption.label] = themeOption;
48
+ return acc;
49
+ },
50
+ {} as Record<ThemeLabel, any>,
51
+ );
29
52
 
30
53
  // The default theme
31
- export const defaultTheme = 'oneDark';
54
+ export const defaultTheme: ThemeLabel = 'oneDark';
@@ -0,0 +1,26 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { EditorCache } from './useEditorCache';
3
+ import { ThemeLabel, themeOptionsByLabel } from './themes';
4
+
5
+ export const useDynamicTheme = (
6
+ editorCache: EditorCache,
7
+ theme: ThemeLabel,
8
+ ) => {
9
+ const isFirstRun = useRef(true);
10
+ useEffect(() => {
11
+ // Skip the first run, since the theme is already initialized.
12
+ if (isFirstRun.current) {
13
+ isFirstRun.current = false;
14
+ } else {
15
+ // When the user changes the theme, update the theme of all editors.
16
+ for (const editorCacheValue of Array.from(editorCache.values())) {
17
+ const { editor, themeCompartment } = editorCacheValue;
18
+ editor.dispatch({
19
+ effects: themeCompartment.reconfigure([
20
+ themeOptionsByLabel[theme].value,
21
+ ]),
22
+ });
23
+ }
24
+ }
25
+ }, [theme, editorCache]);
26
+ };
@@ -0,0 +1,21 @@
1
+ import { useRef } from 'react';
2
+ import { FileId } from '../types';
3
+
4
+ export type EditorCacheValue = {
5
+ editor: any;
6
+ themeCompartment: any;
7
+ };
8
+
9
+ export type EditorCache = Map<FileId, EditorCacheValue>;
10
+
11
+ export const useEditorCache = () => {
12
+ // Singleton cache of CodeMirror instances
13
+ // These are created, but never destroyed.
14
+ // TODO consider invalidating this cache at some point.
15
+ // Right now these editor instances just keep accumulating,
16
+ // and they all retain their connections to ShareDB.
17
+ // This has not been an issue so far, but it could become one.
18
+ const editorCacheRef = useRef<EditorCache>(new Map());
19
+
20
+ return editorCacheRef.current;
21
+ };
@@ -1,8 +1,6 @@
1
1
  import { JSONOp } from 'ot-json1';
2
2
  import { FileId, ShareDBDoc, VZCodeContent } from '../../types';
3
3
  import { useEffect, useRef } from 'react';
4
- // @ts-ignore
5
- import PrettierWorker from './worker?worker';
6
4
 
7
5
  // The time in milliseconds by which auto-saving is debounced.
8
6
  const autoPrettierDebounceTimeMS = 1200;
@@ -20,7 +18,8 @@ const extension = (fileName: string) => {
20
18
 
21
19
  export const usePrettier = (
22
20
  shareDBDoc: ShareDBDoc<VZCodeContent> | null,
23
- submitOperation,
21
+ submitOperation: (next: (content: VZCodeContent) => VZCodeContent) => void,
22
+ prettierWorker: Worker,
24
23
  ) => {
25
24
  // The set of files that have been modified
26
25
  // since the last Prettier run.
@@ -36,8 +35,6 @@ export const usePrettier = (
36
35
  return;
37
36
  }
38
37
 
39
- const prettierWorker = new PrettierWorker();
40
-
41
38
  // Get the message that comes back from the worker.
42
39
  // This is the prettified text for a specific file.
43
40
  const handleMessage = (event) => {
@@ -0,0 +1 @@
1
+ // TODO