Skip to content

Commit 254e9a3

Browse files
committed
basic working sharedb + CRA example
1 parent 94d08a7 commit 254e9a3

File tree

11 files changed

+743
-128
lines changed

11 files changed

+743
-128
lines changed

package.json

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,30 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6+
"@nx-js/observer-util": "^4.2.2",
7+
"@teamwork/websocket-json-stream": "^2.0.0",
68
"@testing-library/jest-dom": "^4.2.4",
79
"@testing-library/react": "^9.3.2",
810
"@testing-library/user-event": "^7.1.2",
911
"@types/jest": "^24.0.0",
1012
"@types/node": "^12.0.0",
1113
"@types/react": "^16.9.0",
1214
"@types/react-dom": "^16.9.0",
15+
"comlink": "^4.3.0",
16+
"express": "^4.17.1",
17+
"nanoid": "^3.1.10",
18+
"random-words": "^1.1.1",
1319
"react": "^16.13.1",
1420
"react-dom": "^16.13.1",
1521
"react-scripts": "3.4.1",
16-
"typescript": "~3.7.2"
22+
"react-use-comlink": "^2.0.1",
23+
"reconnecting-websocket": "^4.4.0",
24+
"sharedb": "^1.4.0",
25+
"typescript": "~3.7.2",
26+
"ws": "^7.3.1"
1727
},
1828
"scripts": {
19-
"start": "react-scripts start",
29+
"start": "concurrently 'node server' 'react-scripts start'",
2030
"build": "react-scripts build",
2131
"test": "react-scripts test",
2232
"eject": "react-scripts eject"
@@ -35,5 +45,10 @@
3545
"last 1 firefox version",
3646
"last 1 safari version"
3747
]
48+
},
49+
"devDependencies": {
50+
"concurrently": "^5.2.0",
51+
"ts-node": "^8.10.2",
52+
"ts-node-dev": "^1.0.0-pre.52"
3853
}
3954
}

server.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const WebSocketJSONStream = require("@teamwork/websocket-json-stream");
2+
const express = require("express");
3+
const http = require("http");
4+
const ShareDB = require("sharedb");
5+
const { Server } = require("ws");
6+
7+
var backend = new ShareDB();
8+
9+
// createDoc(startServer);
10+
11+
// (now doing this in the client instead, so that the ID can be dynamic)
12+
// function createDoc(callback) {
13+
// var connection = backend.connect();
14+
// var doc = connection.get("examples", "test");
15+
// doc.fetch(function (err) {
16+
// if (err) throw err;
17+
// if (doc.type === null) {
18+
// doc.create({ nodes: {}, edges: [] }, callback);
19+
// return;
20+
// }
21+
// callback();
22+
// });
23+
// }
24+
25+
function startServer() {
26+
// Create a web server to serve files and listen to WebSocket connections
27+
var app = express();
28+
app.use(express.static("static"));
29+
var server = http.createServer(app);
30+
31+
// Connect any incoming WebSocket connection to ShareDB
32+
var wss = new Server({ server });
33+
wss.on("connection", function (ws) {
34+
var stream = new WebSocketJSONStream(ws);
35+
backend.listen(stream);
36+
});
37+
38+
server.listen(8080);
39+
console.log("Listening on http://localhost:8080");
40+
}
41+
42+
startServer();

src/App.css

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/App.test.tsx

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/App.tsx

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,72 @@
1-
import React from 'react';
2-
import logo from './logo.svg';
3-
import './App.css';
1+
import randomWords from "random-words";
2+
import React, { useEffect, useRef } from "react";
3+
import { connectToDB, getConnection } from "./sharedb";
4+
5+
const loadNewFlow = () => {
6+
window.location.href = [window.location.origin, randomWords()].join("#");
7+
// should react to url change rather than full reload
8+
window.location.reload();
9+
};
10+
11+
const Flow: React.FC<{ id: string }> = ({ id }) => {
12+
const [state, setState] = React.useState<any>();
13+
const docRef = useRef(null);
14+
15+
useEffect(() => {
16+
// should probably useContext or something rather than
17+
// this useRef stuff
18+
docRef.current = getConnection(id);
19+
const doc = docRef.current;
20+
21+
const cloneStateFromShareDB = () =>
22+
setState(JSON.parse(JSON.stringify(doc.data)));
23+
24+
connectToDB(doc).then(() => {
25+
cloneStateFromShareDB();
26+
doc.on("op", cloneStateFromShareDB);
27+
});
28+
29+
return () => {
30+
docRef.current.destroy();
31+
};
32+
}, []);
433

5-
function App() {
634
return (
7-
<div className="App">
8-
<header className="App-header">
9-
<img src={logo} className="App-logo" alt="logo" />
10-
<p>
11-
Edit <code>src/App.tsx</code> and save to reload.
12-
</p>
13-
<a
14-
className="App-link"
15-
href="https://reactjs.org"
16-
target="_blank"
17-
rel="noopener noreferrer"
18-
>
19-
Learn React
20-
</a>
21-
</header>
35+
<div>
36+
{state?.nodes &&
37+
Object.keys(state.nodes).map((k) => (
38+
<Node key={k} doc={docRef.current} id={k} />
39+
))}
40+
<button
41+
onClick={() => {
42+
// add a node
43+
docRef.current.submitOp([{ p: ["nodes", randomWords()], oi: {} }]);
44+
}}
45+
>
46+
ADD
47+
</button>
48+
49+
<button onClick={loadNewFlow}>New flow</button>
2250
</div>
2351
);
24-
}
52+
};
53+
54+
const Node = React.memo(({ id, doc }: any) => (
55+
<h1
56+
onClick={() => {
57+
// remove the node
58+
doc.submitOp([{ p: ["nodes", id], od: doc.data.nodes[id] }]);
59+
}}
60+
>
61+
{id} {Math.round(Math.random() * 1000)}
62+
</h1>
63+
));
64+
65+
const App = () => {
66+
let [, id] = window.location.href.split("#");
67+
if (!id) loadNewFlow();
68+
69+
return <Flow id={id} />;
70+
};
2571

2672
export default App;

src/index.css

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/index.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import React from 'react';
2-
import ReactDOM from 'react-dom';
3-
import './index.css';
4-
import App from './App';
5-
import * as serviceWorker from './serviceWorker';
1+
import React from "react";
2+
import ReactDOM from "react-dom";
3+
import App from "./App";
4+
import * as serviceWorker from "./serviceWorker";
65

76
ReactDOM.render(
87
<React.StrictMode>
98
<App />
109
</React.StrictMode>,
11-
document.getElementById('root')
10+
document.getElementById("root")
1211
);
1312

1413
// If you want your app to work offline and load faster, you can change

src/logo.svg

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/sharedb.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import ReconnectingWebSocket from "reconnecting-websocket";
2+
import sharedb from "sharedb/lib/client";
3+
4+
const socket = new ReconnectingWebSocket("ws://localhost:8080");
5+
const connection = new sharedb.Connection(socket);
6+
7+
export const getConnection = (id) => connection.get("examples", id);
8+
9+
export const createDoc = async (
10+
doc,
11+
initialData = { nodes: {}, edges: [] }
12+
) => {
13+
return new Promise(async (res, rej) => {
14+
doc.create(initialData, (err) => {
15+
if (err) rej(err);
16+
res();
17+
});
18+
});
19+
};
20+
21+
export const connectToDB = (doc) =>
22+
new Promise((res, rej) => {
23+
doc.subscribe(async (err) => {
24+
console.log("subscribing to doc");
25+
if (err) return rej(err);
26+
if (doc.type === null) {
27+
console.log("creating doc");
28+
await createDoc(doc);
29+
}
30+
return res();
31+
});
32+
});

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"skipLibCheck": true,
1111
"esModuleInterop": true,
1212
"allowSyntheticDefaultImports": true,
13-
"strict": true,
13+
"strict": false,
1414
"forceConsistentCasingInFileNames": true,
1515
"module": "esnext",
1616
"moduleResolution": "node",

0 commit comments

Comments
 (0)