vzcode 0.1.1 → 0.2.1

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/README.md CHANGED
@@ -1,18 +1,46 @@
1
1
  # VZCode
2
2
 
3
- Multiplayer code editor system
3
+ Multiplayer code editor system | [Kanban](https://github.com/orgs/vizhub-core/projects/2/views/1)
4
4
 
5
- ![image](https://user-images.githubusercontent.com/68416/219923570-75ef9f86-4a8d-47e9-8504-1e6c0290dc73.png)
5
+ ![image](https://user-images.githubusercontent.com/68416/224690259-293c75c5-5970-4066-80e4-b9dee568e10d.png)
6
6
 
7
- [Kanban](https://github.com/orgs/vizhub-core/projects/2/views/1)
7
+ ## Features
8
8
 
9
- ## Project Description
9
+ This project is nascent but has enough features to actually use, a minimum viable product (MVP):
10
10
 
11
- - Open source code editor for JavaScript, HTML, CSS, and Markdown
12
- - Support for real-time collaboration
13
- - Invite collaborators over LAN or HTTP Tunneing
14
- - Focus on instant feedback during development
15
- - Core component of [VizHub](https://vizhub.com/) next generation editor
11
+ - Browser-based code editing environment
12
+ - Sidebar listing files from the file system
13
+ - Note: directories are not yet supported
14
+ - Real-time collaboration (multiplayer mode)
15
+ - Invite collaborators over LAN, or
16
+ - Use a service like [NGrok](https://ngrok.com/) for remote collaboration
17
+ - Tabs on the top for managing open files
18
+ - Operatons on files (create file, rename file, delete file)
19
+ - Syntax highlighting for several Web languages
20
+ - Auto-saves changes back to the file system
21
+
22
+ ## Usage
23
+
24
+ One way to use this is as an editor on your system:
25
+
26
+ - Install with `npm install -g vzcode`
27
+ - Launch with `cd myProject; vzcode`
28
+
29
+ Another way to use this is to set up this editor within your project by introducing a new npm script like this:
30
+
31
+ ```json
32
+ {
33
+ "name": "example-project",
34
+ "scripts": {
35
+ "edit": "vzcode"
36
+ },
37
+ "dependencies": {
38
+ "vzcode": "^0.1.0"
39
+ }
40
+ }
41
+ ```
42
+
43
+ Then run `npm run edit` to start the editor.
16
44
 
17
45
  ## Stack
18
46
 
@@ -37,6 +65,7 @@ Multiplayer code editor system
37
65
  - Dogfood the product to identify ways it can be improved
38
66
  - Iterate based on feedback
39
67
  - Share the project with the world
68
+ - Core component of [VizHub](https://vizhub.com/) next generation editor
40
69
 
41
70
  ## Prior Work
42
71
 
package/cli.js CHANGED
@@ -2,13 +2,21 @@
2
2
  import http from 'http';
3
3
  import express from 'express';
4
4
  import ShareDB from 'sharedb';
5
- import json1 from 'ot-json1';
6
5
  import { WebSocketServer } from 'ws';
7
6
  import WebSocketJSONStream from '@teamwork/websocket-json-stream';
8
7
  import fs from 'fs';
9
8
  import path from 'path';
10
9
  import { fileURLToPath } from 'url';
10
+ import { json1Presence } from './src/ot.js';
11
+ import { randomId } from './src/randomId.js';
11
12
 
13
+ // The time in milliseconds by which auto-saving is debounced.
14
+ const autoSaveDebounceTimeMS = 800;
15
+
16
+ // Server port.
17
+ const port = 3030;
18
+
19
+ // Use the current working directory to look for files.
12
20
  const fullPath = process.cwd();
13
21
 
14
22
  // Isolate files, not directories.
@@ -18,22 +26,33 @@ const files = fs
18
26
  .filter((dirent) => dirent.isFile())
19
27
  .map((dirent) => dirent.name);
20
28
 
29
+ // Initialize the document using our data structure for representing files.
30
+ // * Keys are file ids, which are random numbers.
31
+ // * Values are objects with properties:
32
+ // * text - the text content of the file
33
+ // * name - the file name
21
34
  const initialDocument = {};
22
35
  files.forEach((file) => {
23
- const id = Math.floor(Math.random() * 10000000000);
36
+ const id = randomId();
24
37
  initialDocument[id] = {
25
38
  text: fs.readFileSync(file, 'utf-8'),
26
39
  name: file,
27
40
  };
28
41
  });
29
42
 
30
- ShareDB.types.register(json1.type);
43
+ // Register our custom OT type,
44
+ // because it does not ship with ShareDB.
45
+ ShareDB.types.register(json1Presence.type);
31
46
 
32
47
  const app = express();
33
- const port = 3030;
34
48
 
35
49
  // Use ShareDB over WebSocket
36
- const shareDBBackend = new ShareDB();
50
+ const shareDBBackend = new ShareDB({
51
+ // Enable presence
52
+ // See https://github.com/share/sharedb/blob/master/examples/rich-text-presence/server.js#L9
53
+ presence: true,
54
+ doNotForwardSendPresenceErrorsToClient: true,
55
+ });
37
56
  const server = http.createServer(app);
38
57
  const wss = new WebSocketServer({ server });
39
58
  wss.on('connection', (ws) => {
@@ -50,10 +69,7 @@ app.use(express.static(dir));
50
69
  // which is a representation of files on disk.
51
70
  const shareDBConnection = shareDBBackend.connect();
52
71
  const shareDBDoc = shareDBConnection.get('documents', '1');
53
- shareDBDoc.create(initialDocument, json1.type.uri);
54
-
55
- // The time in milliseconds by which auto-saving is debounced.
56
- const autoSaveDebounceTimeMS = 800;
72
+ shareDBDoc.create(initialDocument, json1Presence.type.uri);
57
73
 
58
74
  // The state of the document when files were last auto-saved.
59
75
  let previousDocument = initialDocument;
@@ -62,28 +78,41 @@ let previousDocument = initialDocument;
62
78
  const save = () => {
63
79
  const currentDocument = shareDBDoc.data;
64
80
 
65
- // Take a look at each file that we have currently.
66
- for (const key of Object.keys(currentDocument)) {
67
- // Handle changing of text content.
68
- if (previousDocument[key].text !== currentDocument[key].text) {
69
- const { name, text } = currentDocument[key];
70
- fs.writeFileSync(name, text);
81
+ // Take a look at each file (key) previously and currently.
82
+ const allKeys = Object.keys({ ...currentDocument, ...previousDocument });
83
+ for (const key of allKeys) {
84
+ const previous = previousDocument[key];
85
+ const current = currentDocument[key];
86
+
87
+ // If this file was neither created nor deleted...
88
+ if (previous && current) {
89
+ // Handle changing of text content.
90
+ if (previous.text !== current.text) {
91
+ fs.writeFileSync(current.name, current.text);
92
+ }
93
+
94
+ // Handle renaming files.
95
+ if (previous.name !== current.name) {
96
+ fs.renamesync(previous.name, current.name);
97
+ }
71
98
  }
72
99
 
73
- // Handle renaming files.
74
- const oldPath = previousDocument[key].name;
75
- const newPath = currentDocument[key].name;
76
- if (oldPath !== newPath) {
77
- fs.renameSync(oldPath, newPath);
100
+ // handle deleting files.
101
+ if (previous && !current) {
102
+ fs.unlinkSync(previous.name);
78
103
  }
79
104
 
80
- // TODO handle creating files
81
- // TODO handle deleting files
105
+ // Handle creating files.
106
+ if (!previous && current) {
107
+ fs.writeFileSync(current.name, current.text);
108
+ }
82
109
  }
83
110
  previousDocument = currentDocument;
84
111
  };
85
112
 
86
113
  // Listen for when users modify files.
114
+ // Files get written to disk after `autoSaveDebounceTimeMS`
115
+ // milliseconds of inactivity.
87
116
  let timeout;
88
117
  shareDBDoc.subscribe(() => {
89
118
  shareDBDoc.on('op', (op) => {
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap";@import"https://unpkg.com/boxicons@2.0.7/css/boxicons.min.css";@import"https://unpkg.com/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css";*{margin:0;padding:0;box-sizing:border-box}:root{--main-font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace}body{background:rgb(41,44,52);font-family:Poppins,sans-serif}.tab{width:max-content;height:30px;background:rgb(45,45,45);text-align:center;color:#fff;font-family:var(--main-font-family);justify-content:center;align-items:center;display:flex;padding-left:10px;padding-right:10px;border-top-left-radius:10px;border-top-right-radius:10px;margin-top:5px;margin-left:2px;margin-right:2px;cursor:pointer}.tab.active{background-color:#292c34}.tab-close{display:flex;cursor:pointer;margin-left:5px;justify-content:center;align-items:center}.tab-close:hover{border-radius:30%;background-color:#252526}.editor{position:fixed;margin-left:200px;width:100%;height:100%;top:30px;bottom:0;background-color:#292c34;color:#fff;font-family:var(--main-font-family)}.tab-list{position:fixed;display:flex;flex-direction:row;top:0;left:200px;height:35px;width:100%;background:rgb(37,37,38);z-index:100;transition:all .5s ease}.bottom-bar{position:absolute;bottom:0;left:0;height:20px;width:100%;background:rgb(45,45,45);z-index:100;transition:all .5s ease}.sidebar{position:fixed;top:0;left:0;height:100%;width:200px;background:rgb(54,65,89);z-index:100;transition:all .5s ease}.link-name{margin-top:30px;margin-left:10px;color:#9aa0ac;font-size:12px;text-decoration:none}.file{display:flex;flex-direction:row;align-items:center;justify-content:left;width:180px;cursor:pointer;margin-left:10px;color:#dbdde1;border-radius:5px}.full-Box{width:inherit;height:inherit;padding:7px;display:flex;flex-direction:row;align-items:center;justify-content:space-between}.utilities{justify-content:right;margin-right:3px}.noUtils{display:none}.settings{position:absolute;bottom:0;height:30px;width:180px;z-index:100;color:#dbdde1;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:14px 0;margin:10px;border-radius:5px}.file:hover,.settings:hover{background-color:#4a5469}.newBTN{justify-content:right;margin-right:10px;cursor:pointer}.themes{color:#252526}