vzcode 0.13.0 → 0.14.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/assets/index-176d73ed.js +115 -0
- package/dist/assets/worker-aa634fce.js +237 -0
- package/dist/index.html +1 -1
- package/package.json +12 -11
- package/src/client/App.tsx +32 -25
- package/src/client/usePrettier/index.ts +161 -0
- package/src/client/usePrettier/worker.ts +93 -0
- package/src/server/index.js +17 -1
- package/src/types.ts +20 -1
- package/dist/assets/index-8a130b74.js +0 -89
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-
|
|
21
|
+
<script type="module" crossorigin src="/assets/index-176d73ed.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.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Multiplayer code editor system",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@codemirror/lang-css": "^6.2.1",
|
|
50
50
|
"@codemirror/lang-html": "^6.4.5",
|
|
51
|
-
"@codemirror/lang-javascript": "^6.
|
|
51
|
+
"@codemirror/lang-javascript": "^6.2.0",
|
|
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
|
-
"@replit/codemirror-interact": "^6.
|
|
55
|
+
"@replit/codemirror-interact": "^6.3.0",
|
|
56
56
|
"@teamwork/websocket-json-stream": "^2.0.0",
|
|
57
|
-
"@uiw/codemirror-themes-all": "^4.21.
|
|
57
|
+
"@uiw/codemirror-themes-all": "^4.21.11",
|
|
58
58
|
"codemirror": "^6.0.1",
|
|
59
|
-
"codemirror-ot": "^4.
|
|
59
|
+
"codemirror-ot": "^4.1.0",
|
|
60
60
|
"d3-array": "^3.2.4",
|
|
61
61
|
"diff-match-patch": "^1.0.5",
|
|
62
62
|
"dotenv": "^16.3.1",
|
|
@@ -69,17 +69,18 @@
|
|
|
69
69
|
"react-dom": "^18.2.0",
|
|
70
70
|
"react-select": "^5.7.4",
|
|
71
71
|
"sharedb": "^4.0.0",
|
|
72
|
-
"sharedb-client-browser": "^4.
|
|
72
|
+
"sharedb-client-browser": "^4.3.0",
|
|
73
73
|
"ws": "^8.13.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@types/react": "^18.2.
|
|
76
|
+
"@types/react": "^18.2.21",
|
|
77
77
|
"@types/react-dom": "^18.2.7",
|
|
78
78
|
"@vitejs/plugin-react": "^4.0.4",
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
79
|
+
"ot-json1": "^1.0.2",
|
|
80
|
+
"prettier": "^3.0.2",
|
|
81
|
+
"sass": "^1.66.1",
|
|
82
|
+
"typescript": "^5.2.2",
|
|
82
83
|
"vite": "^4.4.9",
|
|
83
|
-
"vitest": "^0.34.
|
|
84
|
+
"vitest": "^0.34.3"
|
|
84
85
|
}
|
|
85
86
|
}
|
package/src/client/App.tsx
CHANGED
|
@@ -6,12 +6,13 @@ import { CodeEditor } from './CodeEditor';
|
|
|
6
6
|
import { diff } from './diff';
|
|
7
7
|
import { Settings } from './Settings';
|
|
8
8
|
import { Sidebar } from './Sidebar';
|
|
9
|
-
import { FileId, Files } from '../types';
|
|
9
|
+
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
13
|
import { defaultTheme } from './themes';
|
|
14
14
|
import './style.scss';
|
|
15
|
+
import { usePrettier } from './usePrettier';
|
|
15
16
|
|
|
16
17
|
// Register our custom JSON1 OT type that supports presence.
|
|
17
18
|
// See https://github.com/vizhub-core/json1-presence
|
|
@@ -24,15 +25,27 @@ const wsProtocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
|
|
24
25
|
const socket = new WebSocket(wsProtocol + window.location.host + '/ws');
|
|
25
26
|
const connection = new Connection(socket);
|
|
26
27
|
|
|
27
|
-
// The ShareDB document type.
|
|
28
|
-
type Document = {
|
|
29
|
-
files: Files;
|
|
30
|
-
isInteracting: boolean;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
28
|
function App() {
|
|
34
29
|
// The ShareDB document.
|
|
35
|
-
const [shareDBDoc, setShareDBDoc] =
|
|
30
|
+
const [shareDBDoc, setShareDBDoc] =
|
|
31
|
+
useState<ShareDBDoc<VZCodeContent> | null>(null);
|
|
32
|
+
|
|
33
|
+
// A helper function to submit operations to the ShareDB document
|
|
34
|
+
const submitOperation: (
|
|
35
|
+
next: (content: VZCodeContent) => VZCodeContent,
|
|
36
|
+
) => void = useCallback(
|
|
37
|
+
(next) => {
|
|
38
|
+
const content: VZCodeContent = shareDBDoc.data;
|
|
39
|
+
const op = diff(content, next(content));
|
|
40
|
+
if (op && shareDBDoc) {
|
|
41
|
+
shareDBDoc.submitOp(op);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
[shareDBDoc],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Auto-run Pretter after local changes.
|
|
48
|
+
usePrettier(shareDBDoc, submitOperation);
|
|
36
49
|
|
|
37
50
|
// Local ShareDB presence, for broadcasting our cursor position
|
|
38
51
|
// so other clients can see it.
|
|
@@ -46,7 +59,7 @@ function App() {
|
|
|
46
59
|
// The `doc.data` part of the ShareDB document,
|
|
47
60
|
// updated on each change to decouple rendering from ShareDB.
|
|
48
61
|
// Starts out as `null` until the document is loaded.
|
|
49
|
-
const [
|
|
62
|
+
const [content, setContent] = useState<VZCodeContent | null>(null);
|
|
50
63
|
|
|
51
64
|
// The id of the currently open file tab.
|
|
52
65
|
const [activeFileId, setActiveFileId] = useState<FileId>(null);
|
|
@@ -90,13 +103,13 @@ function App() {
|
|
|
90
103
|
setShareDBDoc(shareDBDoc);
|
|
91
104
|
|
|
92
105
|
// Set initial data.
|
|
93
|
-
|
|
106
|
+
setContent(shareDBDoc.data);
|
|
94
107
|
|
|
95
108
|
// Listen for all changes and update `data`.
|
|
96
109
|
// This decouples rendering logic from ShareDB.
|
|
97
110
|
// This callback gets called on each change.
|
|
98
111
|
shareDBDoc.on('op', () => {
|
|
99
|
-
|
|
112
|
+
setContent(shareDBDoc.data);
|
|
100
113
|
});
|
|
101
114
|
|
|
102
115
|
// Set up presence.
|
|
@@ -123,16 +136,6 @@ function App() {
|
|
|
123
136
|
};
|
|
124
137
|
}, []);
|
|
125
138
|
|
|
126
|
-
// A helper function to submit operations to the ShareDB document
|
|
127
|
-
const submitOperation = useCallback(
|
|
128
|
-
(updateFunction: (document: Document) => Document) => {
|
|
129
|
-
const currentDocument = shareDBDoc.data;
|
|
130
|
-
const nextDocument = updateFunction(currentDocument);
|
|
131
|
-
shareDBDoc.submitOp(diff(currentDocument, nextDocument));
|
|
132
|
-
},
|
|
133
|
-
[shareDBDoc],
|
|
134
|
-
);
|
|
135
|
-
|
|
136
139
|
// Called when a file in the sidebar is double-clicked.
|
|
137
140
|
const handleRenameFileClick = useCallback(
|
|
138
141
|
(fileId: FileId) => {
|
|
@@ -193,27 +196,31 @@ function App() {
|
|
|
193
196
|
}, []);
|
|
194
197
|
|
|
195
198
|
// Set `doc.data.isInteracting` to `true` when the user is interacting
|
|
196
|
-
// via interactive code widgets, and `false` when they are not.
|
|
199
|
+
// via interactive code widgets (e.g. Alt+drag), and `false` when they are not.
|
|
197
200
|
const interactTimeoutRef = useRef(null);
|
|
198
201
|
const handleInteract = useCallback(() => {
|
|
202
|
+
// Set `isInteracting: true` if not already set.
|
|
199
203
|
if (!interactTimeoutRef.current) {
|
|
200
204
|
submitOperation((document) => ({ ...document, isInteracting: true }));
|
|
201
205
|
} else {
|
|
202
206
|
clearTimeout(interactTimeoutRef.current);
|
|
203
207
|
}
|
|
204
208
|
|
|
209
|
+
// Set `isInteracting: false` after a delay.
|
|
205
210
|
interactTimeoutRef.current = setTimeout(() => {
|
|
206
211
|
interactTimeoutRef.current = null;
|
|
207
212
|
submitOperation((document) => ({ ...document, isInteracting: false }));
|
|
208
213
|
}, 800);
|
|
209
214
|
}, [submitOperation]);
|
|
210
215
|
|
|
216
|
+
const files: Files | null = content ? content.files : null;
|
|
217
|
+
|
|
211
218
|
return (
|
|
212
219
|
<div className="app">
|
|
213
220
|
<div className="left">
|
|
214
221
|
<Sidebar
|
|
215
222
|
createFile={createFile}
|
|
216
|
-
files={
|
|
223
|
+
files={files}
|
|
217
224
|
handleRenameFileClick={handleRenameFileClick}
|
|
218
225
|
handleDeleteFileClick={handleDeleteFileClick}
|
|
219
226
|
handleFileClick={openTab}
|
|
@@ -229,13 +236,13 @@ function App() {
|
|
|
229
236
|
</div>
|
|
230
237
|
<div className="right">
|
|
231
238
|
<TabList
|
|
232
|
-
files={
|
|
239
|
+
files={files}
|
|
233
240
|
tabList={tabList}
|
|
234
241
|
activeFileId={activeFileId}
|
|
235
242
|
setActiveFileId={setActiveFileId}
|
|
236
243
|
closeTab={closeTab}
|
|
237
244
|
/>
|
|
238
|
-
{
|
|
245
|
+
{content && activeFileId ? (
|
|
239
246
|
<CodeEditor
|
|
240
247
|
shareDBDoc={shareDBDoc}
|
|
241
248
|
localPresence={localPresence}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { JSONOp } from 'ot-json1';
|
|
2
|
+
import { FileId, ShareDBDoc, VZCodeContent } from '../../types';
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import PrettierWorker from './worker?worker';
|
|
6
|
+
|
|
7
|
+
// The time in milliseconds by which auto-saving is debounced.
|
|
8
|
+
const autoPrettierDebounceTimeMS = 1200;
|
|
9
|
+
|
|
10
|
+
// Computes a file extension from a file name.
|
|
11
|
+
// Example: 'foo.js' => '.js'
|
|
12
|
+
const extension = (fileName: string) => {
|
|
13
|
+
const match = fileName.match(/\.([^.]+)$/);
|
|
14
|
+
if (match) {
|
|
15
|
+
return match[1];
|
|
16
|
+
} else {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const usePrettier = (
|
|
22
|
+
shareDBDoc: ShareDBDoc<VZCodeContent> | null,
|
|
23
|
+
submitOperation,
|
|
24
|
+
) => {
|
|
25
|
+
// The set of files that have been modified
|
|
26
|
+
// since the last Prettier run.
|
|
27
|
+
const dirtyFiles: Set<FileId> = new Set<FileId>();
|
|
28
|
+
|
|
29
|
+
// A ref to keep track of whether an op is being applied.
|
|
30
|
+
// This is used to prevent Prettier from running
|
|
31
|
+
// when the op is coming from Prettier itself.
|
|
32
|
+
const isApplyingOpRef = useRef(false);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!shareDBDoc) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const prettierWorker = new PrettierWorker();
|
|
40
|
+
|
|
41
|
+
// Get the message that comes back from the worker.
|
|
42
|
+
// This is the prettified text for a specific file.
|
|
43
|
+
const handleMessage = (event) => {
|
|
44
|
+
const { fileId, error, fileTextPrettified } = event.data;
|
|
45
|
+
if (error) {
|
|
46
|
+
console.log(error);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isApplyingOpRef.current = true;
|
|
51
|
+
submitOperation((document) => ({
|
|
52
|
+
...document,
|
|
53
|
+
files: {
|
|
54
|
+
...document.files,
|
|
55
|
+
[fileId]: {
|
|
56
|
+
...document.files[fileId],
|
|
57
|
+
text: fileTextPrettified,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}));
|
|
61
|
+
isApplyingOpRef.current = false;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Listen for messages from the worker.
|
|
65
|
+
prettierWorker.addEventListener('message', handleMessage);
|
|
66
|
+
|
|
67
|
+
const runPrettier = async () => {
|
|
68
|
+
// Get the content of the document
|
|
69
|
+
const content = shareDBDoc.data;
|
|
70
|
+
|
|
71
|
+
// Get the files
|
|
72
|
+
const files = content.files;
|
|
73
|
+
|
|
74
|
+
// Get the dirty files
|
|
75
|
+
const dirtyFileIds = Array.from(dirtyFiles);
|
|
76
|
+
|
|
77
|
+
// Clear the set of dirty files
|
|
78
|
+
dirtyFiles.clear();
|
|
79
|
+
|
|
80
|
+
// Run Prettier on each dirty file
|
|
81
|
+
for (const fileId of dirtyFileIds) {
|
|
82
|
+
// Get the file
|
|
83
|
+
const file = files[fileId];
|
|
84
|
+
|
|
85
|
+
// Craft the data to send to the worker
|
|
86
|
+
const data = {
|
|
87
|
+
fileText: file.text,
|
|
88
|
+
fileExtension: extension(file.name),
|
|
89
|
+
fileId,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Run Prettier for this file
|
|
93
|
+
prettierWorker.postMessage(data);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Function to debounce running Prettier
|
|
98
|
+
let debounceTimeout;
|
|
99
|
+
function debouncePrettier() {
|
|
100
|
+
clearTimeout(debounceTimeout);
|
|
101
|
+
debounceTimeout = setTimeout(runPrettier, autoPrettierDebounceTimeMS);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Listen for changes
|
|
105
|
+
shareDBDoc.on('op batch', (op: JSONOp, isLocal: boolean) => {
|
|
106
|
+
// Only act on changes coming from the client
|
|
107
|
+
if (isLocal) {
|
|
108
|
+
// If the op is coming from Prettier itself,
|
|
109
|
+
// then do nothing.
|
|
110
|
+
if (isApplyingOpRef.current) {
|
|
111
|
+
// console.log('ignoring op from Prettier');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Example op that we want to act on:
|
|
116
|
+
// op [
|
|
117
|
+
// "files",
|
|
118
|
+
// "2514857669",
|
|
119
|
+
// "text",
|
|
120
|
+
// {
|
|
121
|
+
// "es": [
|
|
122
|
+
// 18,
|
|
123
|
+
// "d"
|
|
124
|
+
// ]
|
|
125
|
+
// }
|
|
126
|
+
// ]
|
|
127
|
+
|
|
128
|
+
// Check if the path of this op is the text content of a file
|
|
129
|
+
if (
|
|
130
|
+
op &&
|
|
131
|
+
op.length === 4 &&
|
|
132
|
+
op[0] === 'files' &&
|
|
133
|
+
typeof op[1] === 'string' &&
|
|
134
|
+
op[2] === 'text'
|
|
135
|
+
) {
|
|
136
|
+
// Get the file id
|
|
137
|
+
const fileId: FileId = op[1];
|
|
138
|
+
|
|
139
|
+
// Add the file id to the set of dirty files
|
|
140
|
+
dirtyFiles.add(fileId);
|
|
141
|
+
|
|
142
|
+
// Debounce running Prettier
|
|
143
|
+
debouncePrettier();
|
|
144
|
+
|
|
145
|
+
// console.log('Op is for file', fileId);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
// console.log('ignoring op from remote', op, isLocal);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// In the event that the component unmounts,
|
|
152
|
+
return () => {
|
|
153
|
+
// Remove the event listener
|
|
154
|
+
prettierWorker.removeEventListener('message', handleMessage);
|
|
155
|
+
|
|
156
|
+
// Clear the timeout
|
|
157
|
+
clearTimeout(debounceTimeout);
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
}, [shareDBDoc]);
|
|
161
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { format } from 'prettier/standalone';
|
|
2
|
+
import * as prettierPluginBabel from 'prettier/plugins/babel';
|
|
3
|
+
import * as prettierPluginEstree from 'prettier/plugins/estree';
|
|
4
|
+
import * as prettierPluginHtml from 'prettier/plugins/html';
|
|
5
|
+
import * as prettierPluginMarkdown from 'prettier/plugins/markdown';
|
|
6
|
+
import * as prettierPluginCSS from 'prettier/plugins/postcss';
|
|
7
|
+
// import * as prettierPluginYaml from 'prettier/plugins/yaml';
|
|
8
|
+
// import * as prettierPluginGraphql from 'prettier/plugins/graphql';
|
|
9
|
+
// import * as prettierPluginAngular from 'prettier/plugins/angular';
|
|
10
|
+
import * as prettierPluginTypescript from 'prettier/plugins/typescript';
|
|
11
|
+
|
|
12
|
+
import { FileId } from '../../types';
|
|
13
|
+
|
|
14
|
+
const parsers = {
|
|
15
|
+
js: 'babel',
|
|
16
|
+
jsx: 'babel',
|
|
17
|
+
mjs: 'babel',
|
|
18
|
+
cjs: 'babel',
|
|
19
|
+
ts: 'typescript',
|
|
20
|
+
tsx: 'typescript',
|
|
21
|
+
css: 'css',
|
|
22
|
+
// '.less': 'less',
|
|
23
|
+
// '.scss': 'scss',
|
|
24
|
+
html: 'html',
|
|
25
|
+
json: 'json',
|
|
26
|
+
json5: 'json5',
|
|
27
|
+
// '.graphql': 'graphql',
|
|
28
|
+
// '.gql': 'graphql',
|
|
29
|
+
md: 'markdown',
|
|
30
|
+
markdown: 'markdown',
|
|
31
|
+
// '.yaml': 'yaml',
|
|
32
|
+
// '.yml': 'yaml',
|
|
33
|
+
// '.vue': 'vue',
|
|
34
|
+
// '.component.html': 'angular',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const plugins = [
|
|
38
|
+
prettierPluginBabel,
|
|
39
|
+
prettierPluginEstree,
|
|
40
|
+
prettierPluginHtml,
|
|
41
|
+
prettierPluginMarkdown,
|
|
42
|
+
prettierPluginTypescript,
|
|
43
|
+
prettierPluginCSS,
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
onmessage = async ({
|
|
47
|
+
data,
|
|
48
|
+
}: {
|
|
49
|
+
data: {
|
|
50
|
+
// The text content of the file
|
|
51
|
+
fileText: string;
|
|
52
|
+
|
|
53
|
+
// The file extension
|
|
54
|
+
// Supported extensions: https://prettier.io/docs/en/options.html#parser
|
|
55
|
+
// VizHub only supports
|
|
56
|
+
// - JavaScript
|
|
57
|
+
// - TypeScript
|
|
58
|
+
// - HTML
|
|
59
|
+
// - CSS
|
|
60
|
+
// - JSON
|
|
61
|
+
// - Markdown
|
|
62
|
+
|
|
63
|
+
fileExtension: string;
|
|
64
|
+
|
|
65
|
+
// The file id
|
|
66
|
+
fileId: FileId;
|
|
67
|
+
};
|
|
68
|
+
}) => {
|
|
69
|
+
const { fileExtension, fileText, fileId } = data;
|
|
70
|
+
const parser = parsers[fileExtension];
|
|
71
|
+
|
|
72
|
+
if (!parser) {
|
|
73
|
+
postMessage({
|
|
74
|
+
fileId,
|
|
75
|
+
error: `Unsupported file extension for Prettier: ${fileExtension}`,
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const fileTextPrettified = await format(fileText, { parser, plugins });
|
|
82
|
+
|
|
83
|
+
postMessage({
|
|
84
|
+
fileId,
|
|
85
|
+
fileTextPrettified,
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
postMessage({
|
|
89
|
+
fileId,
|
|
90
|
+
error,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
package/src/server/index.js
CHANGED
|
@@ -53,7 +53,23 @@ const shareDBBackend = new ShareDB({
|
|
|
53
53
|
const server = http.createServer(app);
|
|
54
54
|
const wss = new WebSocketServer({ server });
|
|
55
55
|
wss.on('connection', (ws) => {
|
|
56
|
-
|
|
56
|
+
const clientStream = new WebSocketJSONStream(ws);
|
|
57
|
+
shareDBBackend.listen(clientStream);
|
|
58
|
+
|
|
59
|
+
// Prevent server crashes on errors.
|
|
60
|
+
clientStream.on('error', (error) => {
|
|
61
|
+
console.log('clientStream error: ' + error.message);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Handle errors
|
|
65
|
+
ws.on('error', (error) => {
|
|
66
|
+
console.log('ws error: ' + error.message);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Handle disconnections
|
|
70
|
+
ws.on('close', (code) => {
|
|
71
|
+
clientStream.end();
|
|
72
|
+
});
|
|
57
73
|
});
|
|
58
74
|
|
|
59
75
|
// Serve static files
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// FileId
|
|
2
2
|
// * A unique ID for a file.
|
|
3
|
+
|
|
4
|
+
import { JSONOp } from 'ot-json1';
|
|
5
|
+
|
|
3
6
|
// * This is a random string.
|
|
4
7
|
export type FileId = string;
|
|
5
8
|
|
|
@@ -64,6 +67,22 @@ export type ShareDBDoc<T> = {
|
|
|
64
67
|
data: T;
|
|
65
68
|
ingestSnapshot: (snapshot: any, callback) => void;
|
|
66
69
|
subscribe: (callback) => void;
|
|
67
|
-
on: (event: string, callback: () => void) => void;
|
|
70
|
+
on: (event: string, callback: (op: JSONOp, source: boolean) => void) => void;
|
|
68
71
|
off: (event: string, callback: () => void) => void;
|
|
72
|
+
submitOp: (op: JSONOp, options?: any, callback?: () => void) => void;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// The ShareDB document type for VZCode.
|
|
76
|
+
export type VZCodeContent = {
|
|
77
|
+
// `files`
|
|
78
|
+
// * The files in the VZCode instance.
|
|
79
|
+
files: Files;
|
|
80
|
+
|
|
81
|
+
// `isInteracting`
|
|
82
|
+
// * `true` when the user is interacting
|
|
83
|
+
// via interactive code widgets (e.g. Alt+drag)
|
|
84
|
+
// * Hot reloading is throttled when this is `true`.
|
|
85
|
+
// * `false` when they are not (e.g. normal typing)
|
|
86
|
+
// * Hot reloading is debounced when this is `false`.
|
|
87
|
+
isInteracting: boolean;
|
|
69
88
|
};
|