Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03685ac84a | ||
|
|
6fa79320a5 | ||
|
|
4e3a297efc | ||
|
|
ef2cbf210b | ||
|
|
3a1c7a71d4 | ||
|
|
dd6c196135 |
12
.babelrc
12
.babelrc
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
["latest", {
|
|
||||||
"es2015": {
|
|
||||||
"modules": false
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"external-helpers"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
34
README.md
34
README.md
@@ -22,6 +22,7 @@ is a list of the modules we know of:
|
|||||||
|[webrtc](https://github.com/y-js/y-webrtc) | Propagate updates Browser2Browser via WebRTC|
|
|[webrtc](https://github.com/y-js/y-webrtc) | Propagate updates Browser2Browser via WebRTC|
|
||||||
|[websockets](https://github.com/y-js/y-websockets-client) | Set up [a central server](https://github.com/y-js/y-websockets-client), and connect to it via websockets |
|
|[websockets](https://github.com/y-js/y-websockets-client) | Set up [a central server](https://github.com/y-js/y-websockets-client), and connect to it via websockets |
|
||||||
|[xmpp](https://github.com/y-js/y-xmpp) | Propagate updates in a XMPP multi-user-chat room ([XEP-0045](http://xmpp.org/extensions/xep-0045.html))|
|
|[xmpp](https://github.com/y-js/y-xmpp) | Propagate updates in a XMPP multi-user-chat room ([XEP-0045](http://xmpp.org/extensions/xep-0045.html))|
|
||||||
|
|[ipfs](https://github.com/ipfs-labs/y-ipfs-connector) | Connector for the [Interplanetary File System](https://ipfs.io/)!|
|
||||||
|[test](https://github.com/y-js/y-test) | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios|
|
|[test](https://github.com/y-js/y-test) | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios|
|
||||||
|
|
||||||
##### Database adapters
|
##### Database adapters
|
||||||
@@ -54,23 +55,23 @@ Install Yjs, and its modules with [bower](http://bower.io/), or
|
|||||||
[npm](https://www.npmjs.org/package/yjs).
|
[npm](https://www.npmjs.org/package/yjs).
|
||||||
|
|
||||||
### Bower
|
### Bower
|
||||||
```
|
```sh
|
||||||
bower install --save yjs y-array % add all y-* modules you want to use
|
bower install --save yjs y-array % add all y-* modules you want to use
|
||||||
```
|
```
|
||||||
You only need to include the `y.js` file. Yjs is able to automatically require
|
You only need to include the `y.js` file. Yjs is able to automatically require
|
||||||
missing modules.
|
missing modules.
|
||||||
```
|
```html
|
||||||
<script src="./bower_components/yjs/y.js"></script>
|
<script src="./bower_components/yjs/y.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Npm
|
### Npm
|
||||||
```
|
```sh
|
||||||
npm install --save yjs % add all y-* modules you want to use
|
npm install --save yjs % add all y-* modules you want to use
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't include via script tag, you have to explicitly include all modules!
|
If you don't include via script tag, you have to explicitly include all modules!
|
||||||
(Same goes for other module systems)
|
(Same goes for other module systems)
|
||||||
```
|
```js
|
||||||
var Y = require('yjs')
|
var Y = require('yjs')
|
||||||
require('y-array')(Y) // add the y-array type to Yjs
|
require('y-array')(Y) // add the y-array type to Yjs
|
||||||
require('y-websockets-client')(Y)
|
require('y-websockets-client')(Y)
|
||||||
@@ -83,7 +84,7 @@ require('y-text')(Y)
|
|||||||
```
|
```
|
||||||
|
|
||||||
### ES6 Syntax
|
### ES6 Syntax
|
||||||
```
|
```js
|
||||||
import Y from 'yjs'
|
import Y from 'yjs'
|
||||||
import yArray from 'y-array'
|
import yArray from 'y-array'
|
||||||
import yWebsocketsClient from 'y-webrtc'
|
import yWebsocketsClient from 'y-webrtc'
|
||||||
@@ -97,7 +98,7 @@ Y.extend(yArray, yWebsocketsClient, yMemory, yArray, yMap, yText /*, .. */)
|
|||||||
|
|
||||||
# Text editing example
|
# Text editing example
|
||||||
Install dependencies
|
Install dependencies
|
||||||
```
|
```sh
|
||||||
bower i yjs y-memory y-webrtc y-array y-text
|
bower i yjs y-memory y-webrtc y-array y-text
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -165,6 +166,25 @@ soon, if possible.
|
|||||||
endpoint of the used connector.
|
endpoint of the used connector.
|
||||||
* All of our connectors also have a default connection endpoint that you can
|
* All of our connectors also have a default connection endpoint that you can
|
||||||
use for development.
|
use for development.
|
||||||
|
* We provide basic authentification for all connectors. The value of
|
||||||
|
`options.connector.auth` (this can be a passphase) is sent to all connected
|
||||||
|
Yjs instances. `options.connector.checkAuth` may grant read or write access
|
||||||
|
depending on the `auth` information.
|
||||||
|
Example: A client specifies `options.connector.auth = 'superSecretPassword`.
|
||||||
|
A server specifies
|
||||||
|
```js
|
||||||
|
options.connector.checkAuth = function (auth, yjsInstance, sender) {
|
||||||
|
return new Promise(function (resolve, reject){
|
||||||
|
if (auth === 'superSecretPassword') {
|
||||||
|
resolve('write') // grant read-write access
|
||||||
|
} else if (auth === 'different password') {
|
||||||
|
resolve('read') // grant read-only access
|
||||||
|
} else {
|
||||||
|
reject('wrong password!') // reject connection
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
* Set `options.connector.generateUserId = true` in order to genenerate a
|
* Set `options.connector.generateUserId = true` in order to genenerate a
|
||||||
userid, instead of receiving one from the server. This way the `Y(..)` is
|
userid, instead of receiving one from the server. This way the `Y(..)` is
|
||||||
immediately going to be resolved, without waiting for any confirmation from
|
immediately going to be resolved, without waiting for any confirmation from
|
||||||
@@ -180,7 +200,7 @@ soon, if possible.
|
|||||||
* Defaults to `/bower_components`
|
* Defaults to `/bower_components`
|
||||||
* Not required when running on `nodejs` / `iojs`
|
* Not required when running on `nodejs` / `iojs`
|
||||||
* When using nodejs you need to manually extend Yjs:
|
* When using nodejs you need to manually extend Yjs:
|
||||||
```
|
```js
|
||||||
var Y = require('yjs')
|
var Y = require('yjs')
|
||||||
// you have to require a db, connector, and *all* types you use!
|
// you have to require a db, connector, and *all* types you use!
|
||||||
require('y-memory')(Y)
|
require('y-memory')(Y)
|
||||||
|
|||||||
72
declarations/Structs.js
Normal file
72
declarations/Structs.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
type UserId = string
|
||||||
|
type Id = [UserId, number|string]
|
||||||
|
|
||||||
|
/*
|
||||||
|
type Struct = {
|
||||||
|
id: Id,
|
||||||
|
left?: Id,
|
||||||
|
right?: Id,
|
||||||
|
target?: Id,
|
||||||
|
struct: 'Insert' | 'Delete'
|
||||||
|
}*/
|
||||||
|
|
||||||
|
type Struct = Insertion | Deletion
|
||||||
|
type Operation = Struct
|
||||||
|
|
||||||
|
type Insertion = {
|
||||||
|
id: Id,
|
||||||
|
left: ?Id,
|
||||||
|
origin: ?Id,
|
||||||
|
right: ?Id,
|
||||||
|
parent: Id,
|
||||||
|
parentSub: ?Id,
|
||||||
|
opContent: ?Id,
|
||||||
|
content: ?any,
|
||||||
|
struct: 'Insert'
|
||||||
|
}
|
||||||
|
|
||||||
|
type Deletion = {
|
||||||
|
target: Id,
|
||||||
|
struct: 'Delete'
|
||||||
|
}
|
||||||
|
|
||||||
|
type MapStruct = {
|
||||||
|
id: Id,
|
||||||
|
type: TypeNames,
|
||||||
|
map: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListStruct = {
|
||||||
|
id: Id,
|
||||||
|
type: TypeNames,
|
||||||
|
start: Id,
|
||||||
|
end: Id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type MessageSyncStep1 = {
|
||||||
|
type: 'sync step 1',
|
||||||
|
deleteSet: any,
|
||||||
|
stateSet: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageSyncStep2 = {
|
||||||
|
type: 'sync step 2',
|
||||||
|
os: Array<Operation>,
|
||||||
|
deleteSet: any,
|
||||||
|
stateSet: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageUpdate = {
|
||||||
|
type: 'update',
|
||||||
|
ops: Array<Operation>
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageSyncDone = {
|
||||||
|
type: 'sync done'
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message = MessageSyncStep1 | MessageSyncStep2 | MessageUpdate | MessageSyncDone
|
||||||
|
|
||||||
0
declarations/Type.js
Normal file
0
declarations/Type.js
Normal file
34
declarations/Y.js
Normal file
34
declarations/Y.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
type YGlobal = {
|
||||||
|
utils: Object,
|
||||||
|
Struct: any,
|
||||||
|
AbstractDatabase: any,
|
||||||
|
AbstractConnector: any,
|
||||||
|
Transaction: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type YConfig = {
|
||||||
|
db: Object,
|
||||||
|
connector: Object,
|
||||||
|
root: Object
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeName = 'array' | 'map' | 'text'
|
||||||
|
|
||||||
|
declare var YConcurrency_TestingMode : boolean
|
||||||
|
|
||||||
|
type Transaction<A> = Generator<any, A, any>
|
||||||
|
|
||||||
|
type SyncRole = 'master' | 'slave'
|
||||||
|
|
||||||
|
declare class Store {
|
||||||
|
find: (id:Id) => Transaction<any>;
|
||||||
|
put: (n:any) => Transaction<void>;
|
||||||
|
delete: (id:Id) => Transaction<void>;
|
||||||
|
findWithLowerBound: (start:Id) => Transaction<any>;
|
||||||
|
findWithUpperBound: (end:Id) => Transaction<any>;
|
||||||
|
findNext: (id:Id) => Transaction<any>;
|
||||||
|
findPrev: (id:Id) => Transaction<any>;
|
||||||
|
iterate: (t:any,start:?Id,end:?Id,gen:any) => Transaction<any>;
|
||||||
|
}
|
||||||
2
dist
2
dist
Submodule dist updated: 42aa7ec5c9...8739fd3a9c
@@ -1,32 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style type="text/css" media="screen">
|
|
||||||
#aceContainer {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.inserted {
|
|
||||||
position:absolute;
|
|
||||||
z-index:20;
|
|
||||||
background-color: #FFC107;
|
|
||||||
}
|
|
||||||
.deleted {
|
|
||||||
position:absolute;
|
|
||||||
z-index:20;
|
|
||||||
background-color: #FFC107;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="aceContainer"></div>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/ace-builds/src/ace.js"></script>
|
|
||||||
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/* global Y, ace */
|
|
||||||
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'ace-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
ace: 'Text' // y.share.textarea is of type Y.Text
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yAce = y
|
|
||||||
|
|
||||||
// bind the textarea to a shared text element
|
|
||||||
var editor = ace.edit('aceContainer')
|
|
||||||
editor.setTheme('ace/theme/chrome')
|
|
||||||
editor.getSession().setMode('ace/mode/javascript')
|
|
||||||
|
|
||||||
y.share.ace.bindAce(editor)
|
|
||||||
})
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "yjs-examples",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"homepage": "y-js.org",
|
|
||||||
"authors": [
|
|
||||||
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
|
||||||
],
|
|
||||||
"description": "Examples for Yjs",
|
|
||||||
"license": "MIT",
|
|
||||||
"ignore": [],
|
|
||||||
"dependencies": {
|
|
||||||
"yjs": "latest",
|
|
||||||
"y-array": "latest",
|
|
||||||
"y-map": "latest",
|
|
||||||
"y-memory": "latest",
|
|
||||||
"y-richtext": "latest",
|
|
||||||
"y-webrtc": "latest",
|
|
||||||
"y-websockets-client": "latest",
|
|
||||||
"y-text": "latest",
|
|
||||||
"y-indexeddb": "latest",
|
|
||||||
"y-xml": "latest",
|
|
||||||
"quill": "^1.0.0-rc.2",
|
|
||||||
"ace": "~1.2.3",
|
|
||||||
"ace-builds": "~1.2.3",
|
|
||||||
"jquery": "~2.2.2",
|
|
||||||
"d3": "^3.5.16",
|
|
||||||
"codemirror": "^5.25.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<style>
|
|
||||||
#chat p span {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div id="chat"></div>
|
|
||||||
<form id="chatform">
|
|
||||||
<input name="username" type="text" style="width:15%;">
|
|
||||||
<input name="message" type="text" style="width:60%;">
|
|
||||||
<input type="submit" value="Send">
|
|
||||||
</form>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
/* global Y */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'chat-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
chat: 'Array'
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yChat = y
|
|
||||||
// This functions inserts a message at the specified position in the DOM
|
|
||||||
function appendMessage(message, position) {
|
|
||||||
var p = document.createElement('p')
|
|
||||||
var uname = document.createElement('span')
|
|
||||||
uname.appendChild(document.createTextNode(message.username + ": "))
|
|
||||||
p.appendChild(uname)
|
|
||||||
p.appendChild(document.createTextNode(message.message))
|
|
||||||
document.querySelector('#chat').insertBefore(p, chat.children[position] || null)
|
|
||||||
}
|
|
||||||
// This function makes sure that only 7 messages exist in the chat history.
|
|
||||||
// The rest is deleted
|
|
||||||
function cleanupChat () {
|
|
||||||
var len
|
|
||||||
while ((len = y.share.chat.length) > 7) {
|
|
||||||
y.share.chat.delete(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Insert the initial content
|
|
||||||
y.share.chat.toArray().forEach(appendMessage)
|
|
||||||
cleanupChat()
|
|
||||||
|
|
||||||
// whenever content changes, make sure to reflect the changes in the DOM
|
|
||||||
y.share.chat.observe(function (event) {
|
|
||||||
if (event.type === 'insert') {
|
|
||||||
for (var i = 0; i < event.length; i++) {
|
|
||||||
appendMessage(event.values[i], event.index + i)
|
|
||||||
}
|
|
||||||
} else if (event.type === 'delete') {
|
|
||||||
for (var i = 0; i < event.length; i++) {
|
|
||||||
chat.children[event.index].remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// concurrent insertions may result in a history > 7, so cleanup here
|
|
||||||
cleanupChat()
|
|
||||||
})
|
|
||||||
document.querySelector('#chatform').onsubmit = function (event) {
|
|
||||||
// the form is submitted
|
|
||||||
var message = {
|
|
||||||
username: this.querySelector("[name=username]").value,
|
|
||||||
message: this.querySelector("[name=message]").value
|
|
||||||
}
|
|
||||||
if (message.username.length > 0 && message.message.length > 0) {
|
|
||||||
if (y.share.chat.length > 6) {
|
|
||||||
// If we are goint to insert the 8th element, make sure to delete first.
|
|
||||||
y.share.chat.delete(0)
|
|
||||||
}
|
|
||||||
// Here we insert a message in the shared chat type.
|
|
||||||
// This will call the observe function (see line 40)
|
|
||||||
// and reflect the change in the DOM
|
|
||||||
y.share.chat.push([message])
|
|
||||||
this.querySelector("[name=message]").value = ""
|
|
||||||
}
|
|
||||||
// Do not send this form!
|
|
||||||
event.preventDefault()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="codeMirrorContainer"></div>
|
|
||||||
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/codemirror/lib/codemirror.js"></script>
|
|
||||||
<script src="../bower_components/codemirror/mode/javascript/javascript.js"></script>
|
|
||||||
<link rel="stylesheet" href="../bower_components/codemirror/lib/codemirror.css">
|
|
||||||
<style>
|
|
||||||
.CodeMirror {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/* global Y, CodeMirror */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'codemirror-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
codemirror: 'Text' // y.share.codemirror is of type Y.Text
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yCodeMirror = y
|
|
||||||
|
|
||||||
var editor = CodeMirror(document.querySelector('#codeMirrorContainer'), {
|
|
||||||
mode: 'javascript',
|
|
||||||
lineNumbers: true
|
|
||||||
})
|
|
||||||
y.share.codemirror.bindCodeMirror(editor)
|
|
||||||
})
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<style>
|
|
||||||
path {
|
|
||||||
fill: none;
|
|
||||||
stroke: blue;
|
|
||||||
stroke-width: 1px;
|
|
||||||
stroke-linejoin: round;
|
|
||||||
stroke-linecap: round;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<button type="button" id="clearDrawingCanvas">Clear Drawing</button>
|
|
||||||
<svg id="drawingCanvas" viewbox="0 0 100 100" width="100%"></svg>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/d3/d3.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/* globals Y, d3 */
|
|
||||||
'strict mode'
|
|
||||||
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'drawing-example'
|
|
||||||
// url: 'localhost:1234'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
drawing: 'Array'
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yDrawing = y
|
|
||||||
var drawing = y.share.drawing
|
|
||||||
var renderPath = d3.svg.line()
|
|
||||||
.x(function (d) { return d[0] })
|
|
||||||
.y(function (d) { return d[1] })
|
|
||||||
.interpolate('basis')
|
|
||||||
|
|
||||||
var svg = d3.select('#drawingCanvas')
|
|
||||||
.call(d3.behavior.drag()
|
|
||||||
.on('dragstart', dragstart)
|
|
||||||
.on('drag', drag)
|
|
||||||
.on('dragend', dragend))
|
|
||||||
|
|
||||||
// create line from a shared array object and update the line when the array changes
|
|
||||||
function drawLine (yarray) {
|
|
||||||
var line = svg.append('path').datum(yarray.toArray())
|
|
||||||
line.attr('d', renderPath)
|
|
||||||
yarray.observe(function (event) {
|
|
||||||
// we only implement insert events that are appended to the end of the array
|
|
||||||
event.values.forEach(function (value) {
|
|
||||||
line.datum().push(value)
|
|
||||||
})
|
|
||||||
line.attr('d', renderPath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// call drawLine every time an array is appended
|
|
||||||
y.share.drawing.observe(function (event) {
|
|
||||||
if (event.type === 'insert') {
|
|
||||||
event.values.forEach(drawLine)
|
|
||||||
} else {
|
|
||||||
// just remove all elements (thats what we do anyway)
|
|
||||||
svg.selectAll('path').remove()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// draw all existing content
|
|
||||||
for (var i = 0; i < drawing.length; i++) {
|
|
||||||
drawLine(drawing.get(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear canvas on request
|
|
||||||
document.querySelector('#clearDrawingCanvas').onclick = function () {
|
|
||||||
drawing.delete(0, drawing.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sharedLine = null
|
|
||||||
function dragstart () {
|
|
||||||
drawing.insert(drawing.length, [Y.Array])
|
|
||||||
sharedLine = drawing.get(drawing.length - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// After one dragged event is recognized, we ignore them for 33ms.
|
|
||||||
var ignoreDrag = null
|
|
||||||
function drag () {
|
|
||||||
if (sharedLine != null && ignoreDrag == null) {
|
|
||||||
ignoreDrag = window.setTimeout(function () {
|
|
||||||
ignoreDrag = null
|
|
||||||
}, 33)
|
|
||||||
sharedLine.push([d3.mouse(this)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragend () {
|
|
||||||
sharedLine = null
|
|
||||||
window.clearTimeout(ignoreDrag)
|
|
||||||
ignoreDrag = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style type="text/css">
|
|
||||||
.draggable {
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<svg id="puzzle-example" width="100%" viewBox="0 0 800 800">
|
|
||||||
<g>
|
|
||||||
<path d="M 311.76636,154.23389 C 312.14136,171.85693 318.14087,184.97998 336.13843,184.23047 C 354.13647,183.48047 351.88647,180.48096 354.88599,178.98096 C 357.8855,177.48096 368.38452,170.35693 380.00806,169.98193 C 424.61841,168.54297 419.78296,223.6001 382.25757,223.6001 C 377.75806,223.6001 363.51001,219.10107 356.38599,211.97656 C 349.26196,204.85254 310.64185,207.10254 314.76636,236.34863 C 316.34888,247.5708 324.08374,267.90723 324.84595,286.23486 C 325.29321,296.99414 323.17603,307.00635 321.58911,315.6377 C 360.11353,305.4585 367.73462,304.30518 404.00513,312.83936 C 410.37915,314.33887 436.62573,310.21436 421.25269,290.3418 C 405.87964,270.46924 406.25464,248.34717 417.12817,240.84814 C 428.00171,233.34912 446.74976,228.84961 457.99829,234.09912 C 469.24683,239.34814 484.61987,255.84619 475.24585,271.59424 C 465.87231,287.34229 452.74878,290.7168 456.49829,303.84033 C 460.2478,316.96387 479.74536,320.33838 500.74292,321.83789 C 509.70142,322.47803 527.97192,323.28467 542.10864,320.12939 C 549.91821,318.38672 556.92212,315.89502 562.46753,313.56396 C 561.40796,277.80664 560.84888,245.71729 560.3606,241.97314 C 558.85278,230.41455 542.49536,217.28564 525.86499,223.2251 C 520.61548,225.1001 519.86548,231.84912 505.24243,232.59912 C 444.92798,235.69238 462.06958,143.26709 525.86499,180.48096 C 539.52759,188.45068 575.19409,190.7583 570.10913,156.85889 C 567.85962,141.86035 553.98608,102.86523 553.98608,102.86523 C 553.98608,102.86523 477.23755,111.82227 451.99878,91.991699 C 441.50024,83.74292 444.87476,69.494629 449.37427,61.245605 C 453.87378,52.996582 465.12231,46.622559 464.74731,36.123779 C 463.02563,-12.086426 392.96704,-10.902832 396.5061,36.873535 C 397.25562,46.997314 406.62964,52.621582 410.75415,60.495605 C 420.00757,78.161377 405.50024,96.073486 384.50757,99.490723 C 377.36206,100.65381 349.17505,102.65332 320.39429,102.23486 C 319.677,102.22461 318.95923,102.21143 318.24194,102.19775 C 315.08423,120.9751 311.55688,144.39697 311.76636,154.23389 z " style="fill:#f2c569;stroke:#000000" id="path2502"/>
|
|
||||||
<path d="M 500.74292,321.83789 C 479.74536,320.33838 460.2478,316.96387 456.49829,303.84033 C 452.74878,290.7168 465.87231,287.34229 475.24585,271.59424 C 484.61987,255.84619 469.24683,239.34814 457.99829,234.09912 C 446.74976,228.84961 428.00171,233.34912 417.12817,240.84814 C 406.25464,248.34717 405.87964,270.46924 421.25269,290.3418 C 436.62573,310.21436 410.37915,314.33887 404.00513,312.83936 C 367.73462,304.30518 360.11353,305.4585 321.58911,315.6377 C 320.56372,321.21484 319.75854,326.2207 320.01538,330.46191 C 320.76538,342.83545 329.3894,385.95508 327.8894,392.7041 C 326.3894,399.45312 313.64136,418.20117 297.89331,407.32715 C 282.14526,396.45361 276.52075,393.4541 265.27222,394.5791 C 254.02368,395.70361 239.77563,402.07812 239.77563,419.32568 C 239.77563,436.57373 250.27417,449.69727 268.64673,447.82227 C 287.36353,445.9126 317.92163,423.11035 325.63989,452.69678 C 330.1394,469.94434 330.51392,487.19238 330.1394,498.44092 C 329.95825,503.87646 326.09985,518.06592 322.16089,531.28125 C 353.2854,532.73682 386.47095,531.26611 394.2561,529.93701 C 430.30933,523.78174 429.31909,496.09766 412.62866,477.44385 C 406.25464,470.31934 401.75513,455.32129 405.87964,444.82275 C 414.07056,423.97314 458.8064,422.17773 473.37134,438.82324 C 483.86987,450.82178 475.99585,477.44385 468.49683,482.69287 C 453.52222,493.17529 457.22485,516.83008 473.37134,528.06201 C 504.79126,549.91943 572.35913,535.56152 572.35913,535.56152 C 572.35913,535.56152 567.85962,498.06592 567.48462,471.81934 C 567.10962,445.57275 589.60669,450.07227 593.3562,450.07227 C 597.10571,450.07227 604.22974,455.32129 609.47925,459.4458 C 614.72876,463.57031 618.85327,469.94434 630.85181,470.69434 C 677.43726,473.60596 674.58813,420.7373 631.97632,413.32666 C 623.35229,411.82666 614.72876,416.32617 603.10522,424.57519 C 591.48169,432.82422 577.23315,425.32519 570.10913,417.45117 C 566.07788,412.99561 563.8479,360.16406 562.46753,313.56396 C 556.92212,315.89502 549.91821,318.38672 542.10864,320.12939 C 527.97192,323.28467 509.70142,322.47803 500.74292,321.83789 z " style="fill:#f3f3d6;stroke:#000000" id="path2504"/>
|
|
||||||
<path d="M 240.52563,141.86035 C 257.60327,159.6499 243.94507,188.68799 214.65356,190.22949 C 185.09448,191.78516 164.66675,157.17822 190.28589,136.61621 C 200.49585,128.42139 198.05786,114.12158 179.78296,106.98975 C 154.4187,97.091553 90.54419,107.73975 90.54419,107.73975 C 90.54419,107.73975 100.88794,135.11328 101.41772,168.48242 C 101.79272,192.104 68.796875,189.47949 63.172607,186.85498 C 57.54834,184.23047 45.924805,173.73145 37.675781,173.73145 C -14.411865,173.73145 -10.013184,245.84375 39.925537,232.22412 C 48.174316,229.97461 56.42334,220.97559 68.796875,222.47559 C 81.17041,223.9751 87.544434,232.59912 87.544434,246.09766 C 87.544434,252.51709 87.0354,281.24268 86.340576,312.87012 C 119.15894,313.67676 160.60962,314.46582 170.03442,313.58887 C 186.15698,312.08936 195.90601,301.59033 188.40698,293.3418 C 180.90796,285.09277 156.16089,256.59619 179.03296,239.34814 C 201.90503,222.10059 235.65112,231.84912 239.77563,247.22217 C 243.90015,262.59521 240.52563,273.46924 234.90112,279.09326 C 229.27661,284.71777 210.52905,298.96582 221.40259,308.71484 C 232.27661,318.46338 263.77222,330.83691 302.39282,320.71338 C 309.58862,318.82715 315.92114,317.13525 321.58911,315.6377 C 323.17603,307.00635 325.29321,296.99414 324.84595,286.23486 C 324.08374,267.90723 316.34888,247.5708 314.76636,236.34863 C 310.64185,207.10254 349.26196,204.85254 356.38599,211.97656 C 363.51001,219.10107 377.75806,223.6001 382.25757,223.6001 C 419.78296,223.6001 424.61841,168.54297 380.00806,169.98193 C 368.38452,170.35693 357.8855,177.48096 354.88599,178.98096 C 351.88647,180.48096 354.13647,183.48047 336.13843,184.23047 C 318.14087,184.97998 312.14136,171.85693 311.76636,154.23389 C 311.55688,144.39697 315.08423,120.9751 318.24194,102.19775 C 290.37524,101.67725 262.46069,98.968262 254.39868,97.991211 C 233.38013,95.443359 217.17456,117.53662 240.52563,141.86035 z " style="fill:#bebcdb;stroke:#000000" id="path2506"/>
|
|
||||||
<path d="M 325.63989,452.69678 C 317.92163,423.11035 287.36353,445.9126 268.64673,447.82227 C 250.27417,449.69727 239.77563,436.57373 239.77563,419.32568 C 239.77563,402.07812 254.02368,395.70361 265.27222,394.5791 C 276.52075,393.4541 282.14526,396.45361 297.89331,407.32715 C 313.64136,418.20117 326.3894,399.45313 327.8894,392.7041 C 329.3894,385.95508 320.76538,342.83545 320.01538,330.46191 C 319.75855,326.2207 320.56372,321.21484 321.58911,315.6377 C 315.92114,317.13525 309.58862,318.82715 302.39282,320.71338 C 263.77222,330.83691 232.27661,318.46338 221.40259,308.71484 C 210.52905,298.96582 229.27661,284.71777 234.90112,279.09326 C 240.52563,273.46924 243.90015,262.59521 239.77563,247.22217 C 235.65112,231.84912 201.90503,222.10059 179.03296,239.34814 C 156.16089,256.59619 180.90796,285.09277 188.40698,293.3418 C 195.90601,301.59033 186.15698,312.08936 170.03442,313.58887 C 160.60962,314.46582 119.15894,313.67676 86.340576,312.87012 C 85.573975,347.74561 84.581299,386.15088 83.794922,402.07812 C 82.295166,432.44922 109.29175,422.32568 115.66577,420.82568 C 122.04028,419.32568 126.16479,409.57715 143.03735,408.45215 C 185.9231,405.59326 186.09985,466.69629 144.16235,467.69482 C 128.41431,468.06982 113.79126,451.19678 108.16675,447.44727 C 102.54272,443.69775 87.919433,442.94775 83.794922,457.9458 C 82.01709,464.41113 78.118652,481.65137 78.098144,496.18994 C 78.071045,515.38037 82.295166,531.81201 82.295166,531.81201 C 82.295166,531.81201 105.54224,526.5625 149.41187,526.5625 C 193.28149,526.5625 199.65552,547.93506 194.78101,558.80859 C 189.90649,569.68213 181.28296,568.93213 179.40796,583.18066 C 172.7063,634.11133 253.34106,631.08203 249.14917,584.68018 C 247.96948,571.62354 237.16528,571.66699 232.27661,557.68359 C 222.17944,528.80273 244.64966,523.56299 257.39819,524.68799 C 263.59351,525.23437 290.95679,529.73389 320.75757,531.21582 C 321.22437,531.23877 321.69312,531.25928 322.16089,531.28125 C 326.09985,518.06592 329.95825,503.87646 330.1394,498.44092 C 330.51392,487.19238 330.1394,469.94434 325.63989,452.69678 z " style="fill:#d3ea9d;stroke:#000000" id="path2508"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/d3/d3.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
/* global Y, d3 */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'Puzzle-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
piece1: 'Map',
|
|
||||||
piece2: 'Map',
|
|
||||||
piece3: 'Map',
|
|
||||||
piece4: 'Map'
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yJigsaw = y
|
|
||||||
var origin // mouse start position - translation of piece
|
|
||||||
var drag = d3.behavior.drag()
|
|
||||||
.on('dragstart', function (params) {
|
|
||||||
// get the translation of the element
|
|
||||||
var translation = d3.select(this).attr('transform').slice(10,-1).split(',').map(Number)
|
|
||||||
// mouse coordinates
|
|
||||||
var mouse = d3.mouse(this.parentNode)
|
|
||||||
origin = {
|
|
||||||
x: mouse[0] - translation[0],
|
|
||||||
y: mouse[1] - translation[1]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on("drag", function(){
|
|
||||||
var mouse = d3.mouse(this.parentNode)
|
|
||||||
var x = mouse[0] - origin.x // =^= mouse - mouse at dragstart + translation at dragstart
|
|
||||||
var y = mouse[1] - origin.y
|
|
||||||
d3.select(this).attr("transform", "translate(" + x + "," + y + ")")
|
|
||||||
})
|
|
||||||
.on('dragend', function (piece, i) {
|
|
||||||
// save the current translation of the puzzle piece
|
|
||||||
var mouse = d3.mouse(this.parentNode)
|
|
||||||
var x = mouse[0] - origin.x
|
|
||||||
var y = mouse[1] - origin.y
|
|
||||||
piece.set('translation', {x: x, y: y})
|
|
||||||
})
|
|
||||||
|
|
||||||
var data = [y.share.piece1, y.share.piece2, y.share.piece3, y.share.piece4]
|
|
||||||
var pieces = d3.select(document.querySelector("#puzzle-example")).selectAll("path").data(data)
|
|
||||||
|
|
||||||
pieces
|
|
||||||
.classed('draggable', true)
|
|
||||||
.attr("transform", function (piece) {
|
|
||||||
var translation = piece.get('translation') || {x: 0, y: 0}
|
|
||||||
return "translate(" + translation.x + "," + translation.y + ")"
|
|
||||||
}).call(drag)
|
|
||||||
|
|
||||||
data.forEach(function(piece){
|
|
||||||
piece.observe(function () {
|
|
||||||
// whenever a property of a piece changes, update the translation of the pieces
|
|
||||||
pieces
|
|
||||||
.transition()
|
|
||||||
.attr("transform", function (piece) {
|
|
||||||
var translation = piece.get('translation') || {x: 0, y: 0}
|
|
||||||
return "translate(" + translation.x + "," + translation.y + ")"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="monacoContainer"></div>
|
|
||||||
<style>
|
|
||||||
#monacoContainer {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/y-array/y-array.js"></script>
|
|
||||||
<script src="../bower_components/y-text/y-text.js"></script>
|
|
||||||
<script src="../bower_components/y-websockets-client/y-websockets-client.js"></script>
|
|
||||||
<script src="../bower_components/y-memory/y-memory.js"></script>
|
|
||||||
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/* global Y */
|
|
||||||
|
|
||||||
require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' }})
|
|
||||||
require(['vs/editor/editor.main'], function() {
|
|
||||||
|
|
||||||
// Initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'monaco-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
monaco: 'Text' // y.share.monaco is of type Y.Text
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yMonaco = y
|
|
||||||
|
|
||||||
// Create Monaco editor
|
|
||||||
var editor = monaco.editor.create(document.getElementById('monacoContainer'), {
|
|
||||||
language: 'javascript'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Bind to y.share.monaco
|
|
||||||
y.share.monaco.bindMonaco(editor)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "examples",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"description": "",
|
|
||||||
"author": "Kevin Jahns",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"monaco-editor": "^0.8.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<!-- quill does not include dist files! We are using the hosted version instead -->
|
|
||||||
<!--link rel="stylesheet" href="../bower_components/quill/dist/quill.snow.css" /-->
|
|
||||||
<link href="https://cdn.quilljs.com/1.0.4/quill.snow.css" rel="stylesheet">
|
|
||||||
<link href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css" rel="stylesheet">
|
|
||||||
<link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/styles/monokai-sublime.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
#quill-container {
|
|
||||||
border: 1px solid gray;
|
|
||||||
box-shadow: 0px 0px 10px gray;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="quill-container">
|
|
||||||
<div id="quill">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.js" type="text/javascript"></script>
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/highlight.min.js" type="text/javascript"></script>
|
|
||||||
<script src="https://cdn.quilljs.com/1.0.4/quill.js"></script>
|
|
||||||
<!-- quill does not include dist files! We are using the hosted version instead (see above)
|
|
||||||
<script src="../bower_components/quill/dist/quill.js"></script>
|
|
||||||
-->
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/* global Y, Quill */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'richtext-example-quill-1.0-test'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
richtext: 'Richtext' // y.share.richtext is of type Y.Richtext
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yQuill = y
|
|
||||||
|
|
||||||
// create quill element
|
|
||||||
window.quill = new Quill('#quill', {
|
|
||||||
modules: {
|
|
||||||
formula: true,
|
|
||||||
syntax: true,
|
|
||||||
toolbar: [
|
|
||||||
[{ size: ['small', false, 'large', 'huge'] }],
|
|
||||||
['bold', 'italic', 'underline'],
|
|
||||||
[{ color: [] }, { background: [] }], // Snow theme fills in values
|
|
||||||
[{ script: 'sub' }, { script: 'super' }],
|
|
||||||
['link', 'image'],
|
|
||||||
['link', 'code-block'],
|
|
||||||
[{list: 'ordered' }]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
theme: 'snow'
|
|
||||||
});
|
|
||||||
// bind quill to richtext type
|
|
||||||
y.share.richtext.bind(window.quill)
|
|
||||||
})
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<!-- quill does not include dist files! We are using the hosted version instead -->
|
|
||||||
<!--link rel="stylesheet" href="../bower_components/quill/dist/quill.snow.css" /-->
|
|
||||||
<link href="https://cdn.quilljs.com/1.0.4/quill.snow.css" rel="stylesheet">
|
|
||||||
<link href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css" rel="stylesheet">
|
|
||||||
<link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/styles/monokai-sublime.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
#quill-container {
|
|
||||||
border: 1px solid gray;
|
|
||||||
box-shadow: 0px 0px 10px gray;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="quill-container">
|
|
||||||
<div id="quill">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.js" type="text/javascript"></script>
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/highlight.min.js" type="text/javascript"></script>
|
|
||||||
<script src="https://cdn.quilljs.com/1.0.4/quill.js"></script>
|
|
||||||
<!-- quill does not include dist files! We are using the hosted version instead (see above)
|
|
||||||
<script src="../bower_components/quill/dist/quill.js"></script>
|
|
||||||
-->
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/* global Y, Quill */
|
|
||||||
|
|
||||||
// register yjs service worker
|
|
||||||
if('serviceWorker' in navigator){
|
|
||||||
// Register service worker
|
|
||||||
// it is important to copy yjs-sw-template to the root directory!
|
|
||||||
navigator.serviceWorker.register('./yjs-sw-template.js').then(function(reg){
|
|
||||||
console.log("Yjs service worker registration succeeded. Scope is " + reg.scope);
|
|
||||||
}).catch(function(err){
|
|
||||||
console.error("Yjs service worker registration failed with error " + err);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'serviceworker',
|
|
||||||
room: 'ServiceWorkerExample2'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
richtext: 'Richtext' // y.share.richtext is of type Y.Richtext
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yServiceWorker = y
|
|
||||||
|
|
||||||
// create quill element
|
|
||||||
window.quill = new Quill('#quill', {
|
|
||||||
modules: {
|
|
||||||
formula: true,
|
|
||||||
syntax: true,
|
|
||||||
toolbar: [
|
|
||||||
[{ size: ['small', false, 'large', 'huge'] }],
|
|
||||||
['bold', 'italic', 'underline'],
|
|
||||||
[{ color: [] }, { background: [] }], // Snow theme fills in values
|
|
||||||
[{ script: 'sub' }, { script: 'super' }],
|
|
||||||
['link', 'image'],
|
|
||||||
['link', 'code-block'],
|
|
||||||
[{list: 'ordered' }]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
theme: 'snow'
|
|
||||||
})
|
|
||||||
// bind quill to richtext type
|
|
||||||
y.share.richtext.bind(window.quill)
|
|
||||||
})
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/* eslint-env worker */
|
|
||||||
|
|
||||||
// copy and modify this file
|
|
||||||
|
|
||||||
self.DBConfig = {
|
|
||||||
name: 'indexeddb'
|
|
||||||
}
|
|
||||||
self.ConnectorConfig = {
|
|
||||||
name: 'websockets-client',
|
|
||||||
// url: '..',
|
|
||||||
options: {
|
|
||||||
jsonp: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
importScripts(
|
|
||||||
'/bower_components/yjs/y.js',
|
|
||||||
'/bower_components/y-memory/y-memory.js',
|
|
||||||
'/bower_components/y-indexeddb/y-indexeddb.js',
|
|
||||||
'/bower_components/y-websockets-client/y-websockets-client.js',
|
|
||||||
'/bower_components/y-serviceworker/yjs-sw-include.js'
|
|
||||||
)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<textarea style="width:80%;" rows=40 id="textfield" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* global Y */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'Textarea-example'
|
|
||||||
// url: '127.0.0.1:1234'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
textarea: 'Text' // y.share.textarea is of type Y.Text
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yTextarea = y
|
|
||||||
|
|
||||||
// bind the textarea to a shared text element
|
|
||||||
y.share.textarea.bind(document.getElementById('textfield'))
|
|
||||||
// thats it..
|
|
||||||
})
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
</head>
|
|
||||||
<script src="../bower_components/yjs/y.js"></script>
|
|
||||||
<script src="../bower_components/jquery/dist/jquery.min.js"></script>
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1> Shared DOM Example </h1>
|
|
||||||
<p> Use native DOM function or jQuery to manipulate the shared DOM (window.sharedDom). </p>
|
|
||||||
<div class="command">
|
|
||||||
<button type="button">Execute</button>
|
|
||||||
<input type="text" value='$(sharedDom).append("<h3>Appended headline</h3>")' size="40"/>
|
|
||||||
</div>
|
|
||||||
<div class="command">
|
|
||||||
<button type="button">Execute</button>
|
|
||||||
<input type="text" value='$(sharedDom).attr("align","right")' size="40"/>
|
|
||||||
</div>
|
|
||||||
<div class="command">
|
|
||||||
<button type="button">Execute</button>
|
|
||||||
<input type="text" value='$(sharedDom).attr("style","color:blue;")' size="40"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var commands = document.querySelectorAll(".command");
|
|
||||||
Array.prototype.forEach.call(document.querySelectorAll('.command'), function (command) {
|
|
||||||
var execute = function(){
|
|
||||||
eval(command.querySelector("input").value);
|
|
||||||
}
|
|
||||||
command.querySelector("button").onclick = execute
|
|
||||||
$(command.querySelector("input")).keyup(function (e) {
|
|
||||||
if (e.keyCode == 13) {
|
|
||||||
execute()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/* global Y */
|
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client',
|
|
||||||
room: 'Xml-example'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
xml: 'Xml("p")' // y.share.xml is of type Y.Xml with tagname "p"
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yXml = y
|
|
||||||
// bind xml type to a dom, and put it in body
|
|
||||||
window.sharedDom = y.share.xml.getDom()
|
|
||||||
document.body.appendChild(window.sharedDom)
|
|
||||||
})
|
|
||||||
214
gulpfile.helper.js
Normal file
214
gulpfile.helper.js
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
|
||||||
|
var $ = require('gulp-load-plugins')()
|
||||||
|
var minimist = require('minimist')
|
||||||
|
var browserify = require('browserify')
|
||||||
|
var source = require('vinyl-source-stream')
|
||||||
|
var buffer = require('vinyl-buffer')
|
||||||
|
|
||||||
|
module.exports = function (gulp, helperOptions) {
|
||||||
|
var runSequence = require('run-sequence').use(gulp)
|
||||||
|
var options = minimist(process.argv.slice(2), {
|
||||||
|
string: ['modulename', 'export', 'name', 'port', 'testfiles', 'es6'],
|
||||||
|
default: {
|
||||||
|
moduleName: helperOptions.moduleName,
|
||||||
|
targetName: helperOptions.targetName,
|
||||||
|
export: 'ignore',
|
||||||
|
port: '8888',
|
||||||
|
testfiles: '**/*.spec.js',
|
||||||
|
es6: false,
|
||||||
|
browserify: helperOptions.browserify != null ? helperOptions.browserify : false,
|
||||||
|
includeRuntime: helperOptions.includeRuntime || false,
|
||||||
|
debug: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (options.es6 !== false) {
|
||||||
|
options.es6 = true
|
||||||
|
}
|
||||||
|
var files = {
|
||||||
|
dist: helperOptions.entry,
|
||||||
|
specs: helperOptions.specs,
|
||||||
|
src: './src/**/*.js'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.includeRuntime) {
|
||||||
|
files.distEs5 = ['node_modules/regenerator/runtime.js', files.dist]
|
||||||
|
} else {
|
||||||
|
files.distEs5 = [files.dist]
|
||||||
|
}
|
||||||
|
|
||||||
|
var header = require('gulp-header')
|
||||||
|
var banner = ['/**',
|
||||||
|
' * <%= pkg.name %> - <%= pkg.description %>',
|
||||||
|
' * @version v<%= pkg.version %>',
|
||||||
|
' * @link <%= pkg.homepage %>',
|
||||||
|
' * @license <%= pkg.license %>',
|
||||||
|
' */',
|
||||||
|
''].join('\n')
|
||||||
|
|
||||||
|
gulp.task('dist:es5', function () {
|
||||||
|
var babelOptions = {
|
||||||
|
presets: ['es2015']
|
||||||
|
}
|
||||||
|
return (browserify({
|
||||||
|
entries: files.distEs5,
|
||||||
|
debug: true,
|
||||||
|
standalone: options.moduleName
|
||||||
|
}).transform('babelify', babelOptions)
|
||||||
|
.bundle()
|
||||||
|
.pipe(source(options.targetName))
|
||||||
|
.pipe(buffer())
|
||||||
|
.pipe($.sourcemaps.init({loadMaps: true}))
|
||||||
|
.pipe($.if(!options.debug, $.uglify().on('error', function (e) {
|
||||||
|
console.log('\x07', e.message, JSON.stringify(e)); return this.end()
|
||||||
|
})))
|
||||||
|
.pipe(header(banner, { pkg: require('./package.json') }))
|
||||||
|
.pipe($.sourcemaps.write('.'))
|
||||||
|
.pipe(gulp.dest('./dist/')))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('dist:es6', function () {
|
||||||
|
return (browserify({
|
||||||
|
entries: files.dist,
|
||||||
|
debug: true,
|
||||||
|
standalone: options.moduleName
|
||||||
|
}).bundle()
|
||||||
|
.pipe(source(options.targetName))
|
||||||
|
.pipe(buffer())
|
||||||
|
.pipe($.sourcemaps.init({loadMaps: true}))
|
||||||
|
// .pipe($.uglify()) -- generators not yet supported see #448
|
||||||
|
.pipe($.rename({
|
||||||
|
extname: '.es6'
|
||||||
|
}))
|
||||||
|
.pipe(header(banner, { pkg: require('./package.json') }))
|
||||||
|
.pipe($.sourcemaps.write('.'))
|
||||||
|
.pipe(gulp.dest('./dist/')))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('dist', ['dist:es6', 'dist:es5'])
|
||||||
|
|
||||||
|
gulp.task('watch:dist', function (cb) {
|
||||||
|
options.debug = true
|
||||||
|
gulp.src(['./README.md'])
|
||||||
|
.pipe($.watch('./README.md'))
|
||||||
|
.pipe(gulp.dest('./dist/'))
|
||||||
|
runSequence('dist', function () {
|
||||||
|
gulp.watch(files.src.concat('./README.md'), ['dist'])
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('dev:node', ['test'], function () {
|
||||||
|
gulp.watch(files.src, ['test'])
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('spec-build', function () {
|
||||||
|
var browserify = require('browserify')
|
||||||
|
var source = require('vinyl-source-stream')
|
||||||
|
var buffer = require('vinyl-buffer')
|
||||||
|
|
||||||
|
return browserify({
|
||||||
|
entries: files.specs, // .concat(files.distEs5),
|
||||||
|
debug: true
|
||||||
|
})// .transform('babelify', { presets: ['es2015'] })
|
||||||
|
.bundle()
|
||||||
|
.pipe(source('specs.js'))
|
||||||
|
.pipe(buffer())
|
||||||
|
// .pipe($.sourcemaps.init({loadMaps: true}))
|
||||||
|
// .pipe($.sourcemaps.write('.'))
|
||||||
|
.pipe(gulp.dest('./build/'))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('dev:browser', ['spec-build'], function () {
|
||||||
|
gulp.watch(files.src, ['spec-build'])
|
||||||
|
return gulp.src('./build/specs.js')
|
||||||
|
.pipe($.jasmineBrowser.specRunner())
|
||||||
|
.pipe($.jasmineBrowser.server({port: options.port}))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('test', function () {
|
||||||
|
return gulp.src(files.specs)
|
||||||
|
.pipe($.jasmine({
|
||||||
|
verbose: true,
|
||||||
|
includeStuckTrace: true
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('updateSubmodule', function () {
|
||||||
|
return gulp.src('./package.json', {read: false})
|
||||||
|
.pipe($.shell([
|
||||||
|
'git submodule update --init',
|
||||||
|
'cd dist && git pull origin dist'
|
||||||
|
]))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('bump', function (cb) {
|
||||||
|
gulp.src(['./package.json', './bower.json', './dist/bower.json'], {base: '.'})
|
||||||
|
.pipe($.prompt.prompt({
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'bump',
|
||||||
|
message: 'What type of bump would you like to do?',
|
||||||
|
choices: ['patch', 'minor', 'major']
|
||||||
|
}, function (res) {
|
||||||
|
if (res.bump.length === 0) {
|
||||||
|
console.info('You have to select a bump type. Now I\'m going to use "patch" as bump type..')
|
||||||
|
}
|
||||||
|
var bumptype = res.bump[0]
|
||||||
|
if (bumptype === 'major') {
|
||||||
|
runSequence('bump_major', cb)
|
||||||
|
} else if (bumptype === 'minor') {
|
||||||
|
runSequence('bump_minor', cb)
|
||||||
|
} else {
|
||||||
|
runSequence('bump_patch', cb)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
gulp.task('bump_patch', function () {
|
||||||
|
return gulp.src(['./package.json', './bower.json', './dist/bower.json'], {base: '.'})
|
||||||
|
.pipe($.bump({type: 'patch'}))
|
||||||
|
.pipe(gulp.dest('./'))
|
||||||
|
})
|
||||||
|
gulp.task('bump_minor', function () {
|
||||||
|
return gulp.src(['./package.json', './bower.json', './dist/bower.json'], {base: '.'})
|
||||||
|
.pipe($.bump({type: 'minor'}))
|
||||||
|
.pipe(gulp.dest('./'))
|
||||||
|
})
|
||||||
|
gulp.task('bump_major', function () {
|
||||||
|
return gulp.src(['./package.json', './bower.json', './dist/bower.json'], {base: '.'})
|
||||||
|
.pipe($.bump({type: 'major'}))
|
||||||
|
.pipe(gulp.dest('./'))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('publish_commits', function () {
|
||||||
|
return gulp.src('./package.json')
|
||||||
|
.pipe($.prompt.confirm({
|
||||||
|
message: 'Are you sure you want to publish this release?',
|
||||||
|
default: false
|
||||||
|
}))
|
||||||
|
.pipe($.shell([
|
||||||
|
'cp README.md dist',
|
||||||
|
'standard',
|
||||||
|
'echo "Deploying version <%= getVersion(file.path) %>"',
|
||||||
|
'git pull',
|
||||||
|
'cd ./dist/ && git add -A',
|
||||||
|
'cd ./dist/ && git commit -am "Deploy <%= getVersion(file.path) %>" -n',
|
||||||
|
'cd ./dist/ && git push origin HEAD:dist',
|
||||||
|
'cd ./dist/ && git tag -a v<%= getVersion(file.path) %> -m "Release <%= getVersion(file.path) %>"',
|
||||||
|
'cd ./dist/ && git push origin --tags',
|
||||||
|
'git commit -am "Release <%= getVersion(file.path) %>" -n',
|
||||||
|
'git push',
|
||||||
|
'npm publish',
|
||||||
|
'echo Finished'
|
||||||
|
], {
|
||||||
|
templateData: {
|
||||||
|
getVersion: function () {
|
||||||
|
return JSON.parse(String.fromCharCode.apply(null, this.file._contents)).version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('publish', function (cb) {
|
||||||
|
/* TODO: include 'test',*/
|
||||||
|
runSequence('updateSubmodule', 'bump', 'dist', 'publish_commits', cb)
|
||||||
|
})
|
||||||
|
}
|
||||||
104
gulpfile.js
Normal file
104
gulpfile.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
|
||||||
|
/** Gulp Commands
|
||||||
|
|
||||||
|
gulp command*
|
||||||
|
[--export ModuleType]
|
||||||
|
[--name ModuleName]
|
||||||
|
[--testport TestPort]
|
||||||
|
[--testfiles TestFiles]
|
||||||
|
|
||||||
|
Module name (ModuleName):
|
||||||
|
Compile this to "y.js" (default)
|
||||||
|
|
||||||
|
Supported module types (ModuleType):
|
||||||
|
- amd
|
||||||
|
- amdStrict
|
||||||
|
- common
|
||||||
|
- commonStrict
|
||||||
|
- ignore (default)
|
||||||
|
- system
|
||||||
|
- umd
|
||||||
|
- umdStrict
|
||||||
|
|
||||||
|
Test port (TestPort):
|
||||||
|
Serve the specs on port 8888 (default)
|
||||||
|
|
||||||
|
Test files (TestFiles):
|
||||||
|
Specify which specs to use!
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
- build:deploy
|
||||||
|
Build this library for deployment (es6->es5, minified)
|
||||||
|
- dev:browser
|
||||||
|
Watch the ./src directory.
|
||||||
|
Builds the library on changes.
|
||||||
|
Starts an http-server and serves the test suite on http://127.0.0.1:8888.
|
||||||
|
- dev:node
|
||||||
|
Watch the ./src directory.
|
||||||
|
Builds and specs the library on changes.
|
||||||
|
Usefull to run with node-inspector.
|
||||||
|
`node-debug $(which gulp) dev:node
|
||||||
|
- test:
|
||||||
|
Test this library
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gulp = require('gulp')
|
||||||
|
var $ = require('gulp-load-plugins')()
|
||||||
|
var runSequence = require('run-sequence').use(gulp)
|
||||||
|
|
||||||
|
require('./gulpfile.helper.js')(gulp, {
|
||||||
|
polyfills: [],
|
||||||
|
entry: './src/y.js',
|
||||||
|
targetName: 'y.js',
|
||||||
|
moduleName: 'Y',
|
||||||
|
includeRuntime: true,
|
||||||
|
specs: [
|
||||||
|
'./src/Database.spec.js',
|
||||||
|
'../y-array/src/Array.spec.js',
|
||||||
|
'../y-map/src/Map.spec.js'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('dev:examples', ['watch:dist'], function () {
|
||||||
|
// watch all distfiles and copy them to bower_components
|
||||||
|
var distfiles = ['./dist/*.{js,es6}', './dist/*.{js,es6}.map', '../y-*/dist/*.{js,es6}', '../y-*/dist/*.{js,es6}.map']
|
||||||
|
gulp.src(distfiles)
|
||||||
|
.pipe($.watch(distfiles))
|
||||||
|
.pipe($.rename(function (path) {
|
||||||
|
var dir = path.dirname.split(/[\\\/]/)[0]
|
||||||
|
console.log(JSON.stringify(path))
|
||||||
|
path.dirname = dir === '.' ? 'yjs' : dir
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest('./dist/Examples/bower_components/'))
|
||||||
|
|
||||||
|
return $.serve('dist/Examples/')()
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('default', ['updateSubmodule'], function (cb) {
|
||||||
|
gulp.src('package.json')
|
||||||
|
.pipe($.prompt.prompt({
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'tasks',
|
||||||
|
message: 'Which tasks would you like to run?',
|
||||||
|
choices: [
|
||||||
|
'test Test this project',
|
||||||
|
'dev:examples Serve the examples directory in ./dist/',
|
||||||
|
'dev:browser Watch files & serve the testsuite for the browser',
|
||||||
|
'dev:nodejs Watch filse & test this project with nodejs',
|
||||||
|
'bump Bump the current state of the project',
|
||||||
|
'publish Publish this project. Creates a github tag',
|
||||||
|
'dist Build the distribution files'
|
||||||
|
]
|
||||||
|
}, function (res) {
|
||||||
|
var tasks = res.tasks.map(function (task) {
|
||||||
|
return task.split(' ')[0]
|
||||||
|
})
|
||||||
|
if (tasks.length > 0) {
|
||||||
|
console.info('gulp ' + tasks.join(' '))
|
||||||
|
runSequence(tasks, cb)
|
||||||
|
} else {
|
||||||
|
console.info('Ok, .. goodbye')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
3119
package-lock.json
generated
3119
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@@ -1,19 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.0.0-0",
|
"version": "12.3.3",
|
||||||
"description": "A framework for real-time p2p shared editing on any data",
|
"description": "A framework for real-time p2p shared editing on any data",
|
||||||
"main": "./src/y.js",
|
"main": "./src/y.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "standard",
|
"test": "node --harmony ./node_modules/.bin/gulp test",
|
||||||
"dist": "rollup -c rollup.dist.js",
|
"lint": "./node_modules/.bin/standard"
|
||||||
"serve": "concurrently 'serve examples' 'rollup -wc rollup.dist.js -o examples/bower_components/yjs/y.js'"
|
|
||||||
},
|
},
|
||||||
"pre-commit": [
|
"pre-commit": [
|
||||||
"lint",
|
"lint",
|
||||||
"test"
|
"test"
|
||||||
],
|
],
|
||||||
"standard": {
|
"standard": {
|
||||||
|
"parser": "babel-eslint",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
|
"build/**",
|
||||||
|
"dist/**",
|
||||||
|
"declarations/**",
|
||||||
"./y.js",
|
"./y.js",
|
||||||
"./y.js.map"
|
"./y.js.map"
|
||||||
]
|
]
|
||||||
@@ -39,24 +42,41 @@
|
|||||||
},
|
},
|
||||||
"homepage": "http://y-js.org",
|
"homepage": "http://y-js.org",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.24.1",
|
"babel-eslint": "^5.0.0-beta6",
|
||||||
"babel-preset-latest": "^6.24.1",
|
"babel-plugin-transform-runtime": "^6.1.18",
|
||||||
"babel-plugin-external-helpers": "^6.22.0",
|
"babel-preset-es2015": "^6.1.18",
|
||||||
"babel-plugin-transform-regenerator": "^6.24.1",
|
"babelify": "^7.2.0",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"browserify": "^12.0.1",
|
||||||
"chance": "^1.0.9",
|
"eslint": "^1.10.2",
|
||||||
"concurrently": "^3.4.0",
|
"gulp": "^3.9.0",
|
||||||
"rollup-plugin-babel": "^2.7.1",
|
"gulp-bump": "^1.0.0",
|
||||||
"rollup-plugin-commonjs": "^8.0.2",
|
"gulp-concat": "^2.6.0",
|
||||||
"rollup-plugin-inject": "^2.0.0",
|
"gulp-filter": "^3.0.1",
|
||||||
"rollup-plugin-multi-entry": "^2.0.1",
|
"gulp-git": "^1.6.0",
|
||||||
"rollup-plugin-node-resolve": "^3.0.0",
|
"gulp-header": "^1.8.8",
|
||||||
"rollup-plugin-uglify": "^1.0.2",
|
"gulp-if": "^2.0.0",
|
||||||
"rollup-regenerator-runtime": "^6.23.1",
|
"gulp-jasmine": "^2.0.1",
|
||||||
"rollup-watch": "^3.2.2",
|
"gulp-jasmine-browser": "^0.2.3",
|
||||||
"standard": "^10.0.2"
|
"gulp-load-plugins": "^1.0.0",
|
||||||
|
"gulp-prompt": "^0.1.2",
|
||||||
|
"gulp-rename": "^1.2.2",
|
||||||
|
"gulp-serve": "^1.2.0",
|
||||||
|
"gulp-shell": "^0.5.1",
|
||||||
|
"gulp-sourcemaps": "^1.5.2",
|
||||||
|
"gulp-tag-version": "^1.3.0",
|
||||||
|
"gulp-uglify": "^2.0.0",
|
||||||
|
"gulp-util": "^3.0.6",
|
||||||
|
"gulp-watch": "^4.3.5",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"pre-commit": "^1.1.1",
|
||||||
|
"regenerator": "^0.8.42",
|
||||||
|
"run-sequence": "^1.1.4",
|
||||||
|
"seedrandom": "^2.4.2",
|
||||||
|
"standard": "^5.2.2",
|
||||||
|
"vinyl-buffer": "^1.0.0",
|
||||||
|
"vinyl-source-stream": "^1.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^2.6.8"
|
"debug": "^2.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import inject from 'rollup-plugin-inject'
|
|
||||||
import babel from 'rollup-plugin-babel'
|
|
||||||
import uglify from 'rollup-plugin-uglify'
|
|
||||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
|
||||||
import commonjs from 'rollup-plugin-commonjs'
|
|
||||||
var pkg = require('./package.json')
|
|
||||||
|
|
||||||
export default {
|
|
||||||
entry: 'src/y.js',
|
|
||||||
moduleName: 'Y',
|
|
||||||
format: 'umd',
|
|
||||||
plugins: [
|
|
||||||
nodeResolve({
|
|
||||||
main: true,
|
|
||||||
module: true,
|
|
||||||
browser: true
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
babel({
|
|
||||||
runtimeHelpers: true
|
|
||||||
}),
|
|
||||||
inject({
|
|
||||||
regeneratorRuntime: 'regenerator-runtime'
|
|
||||||
}),
|
|
||||||
uglify({
|
|
||||||
output: {
|
|
||||||
comments: function (node, comment) {
|
|
||||||
var text = comment.value
|
|
||||||
var type = comment.type
|
|
||||||
if (type === 'comment2') {
|
|
||||||
// multiline comment
|
|
||||||
return /@license/i.test(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
],
|
|
||||||
dest: 'y.js',
|
|
||||||
sourceMap: true,
|
|
||||||
banner: `
|
|
||||||
/**
|
|
||||||
* ${pkg.name} - ${pkg.description}
|
|
||||||
* @version v${pkg.version}
|
|
||||||
* @license ${pkg.license}
|
|
||||||
*/
|
|
||||||
`
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
|
||||||
import commonjs from 'rollup-plugin-commonjs'
|
|
||||||
import multiEntry from 'rollup-plugin-multi-entry'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
entry: 'tests/*.js',
|
|
||||||
moduleName: 'y-array-tests',
|
|
||||||
format: 'umd',
|
|
||||||
plugins: [
|
|
||||||
nodeResolve({
|
|
||||||
main: true,
|
|
||||||
module: true,
|
|
||||||
browser: true
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
multiEntry()
|
|
||||||
],
|
|
||||||
dest: 'y-array.test.js',
|
|
||||||
sourceMap: true
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
/* @flow */
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
function canRead (auth) { return auth === 'read' || auth === 'write' }
|
function canRead (auth) { return auth === 'read' || auth === 'write' }
|
||||||
function canWrite (auth) { return auth === 'write' }
|
function canWrite (auth) { return auth === 'write' }
|
||||||
|
|
||||||
export default function extendConnector (Y/* :any */) {
|
module.exports = function (Y/* :any */) {
|
||||||
class AbstractConnector {
|
class AbstractConnector {
|
||||||
/* ::
|
/* ::
|
||||||
y: YConfig;
|
y: YConfig;
|
||||||
@@ -39,7 +36,6 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
// this client receives operations from only one other client.
|
// this client receives operations from only one other client.
|
||||||
// In particular, this does not work with y-webrtc.
|
// In particular, this does not work with y-webrtc.
|
||||||
// It will work with y-websockets-client
|
// It will work with y-websockets-client
|
||||||
this.preferUntransformed = opts.preferUntransformed || false
|
|
||||||
if (opts.role == null || opts.role === 'master') {
|
if (opts.role == null || opts.role === 'master') {
|
||||||
this.role = 'master'
|
this.role = 'master'
|
||||||
} else if (opts.role === 'slave') {
|
} else if (opts.role === 'slave') {
|
||||||
@@ -59,6 +55,7 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
this.syncingClients = []
|
this.syncingClients = []
|
||||||
this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
|
this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
|
||||||
this.debug = opts.debug === true
|
this.debug = opts.debug === true
|
||||||
|
this.syncStep2 = Promise.resolve()
|
||||||
this.broadcastOpBuffer = []
|
this.broadcastOpBuffer = []
|
||||||
this.protocolVersion = 11
|
this.protocolVersion = 11
|
||||||
this.authInfo = opts.auth || null
|
this.authInfo = opts.auth || null
|
||||||
@@ -112,7 +109,7 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
this.userEventListeners.push(f)
|
this.userEventListeners.push(f)
|
||||||
}
|
}
|
||||||
removeUserEventListener (f) {
|
removeUserEventListener (f) {
|
||||||
this.userEventListeners = this.userEventListeners.filter(g => f !== g)
|
this.userEventListeners = this.userEventListeners.filter(g => { f !== g })
|
||||||
}
|
}
|
||||||
userLeft (user) {
|
userLeft (user) {
|
||||||
if (this.connections[user] != null) {
|
if (this.connections[user] != null) {
|
||||||
@@ -145,9 +142,6 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
isSynced: false,
|
isSynced: false,
|
||||||
role: role
|
role: role
|
||||||
}
|
}
|
||||||
let defer = {}
|
|
||||||
defer.promise = new Promise(function (resolve) { defer.resolve = resolve })
|
|
||||||
this.connections[user].syncStep2 = defer
|
|
||||||
for (var f of this.userEventListeners) {
|
for (var f of this.userEventListeners) {
|
||||||
f({
|
f({
|
||||||
action: 'userJoined',
|
action: 'userJoined',
|
||||||
@@ -189,13 +183,10 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
var answer = {
|
var answer = {
|
||||||
type: 'sync step 1',
|
type: 'sync step 1',
|
||||||
stateSet: stateSet,
|
stateSet: stateSet,
|
||||||
// deleteSet: deleteSet,
|
deleteSet: deleteSet,
|
||||||
protocolVersion: conn.protocolVersion,
|
protocolVersion: conn.protocolVersion,
|
||||||
auth: conn.authInfo
|
auth: conn.authInfo
|
||||||
}
|
}
|
||||||
if (conn.preferUntransformed && Object.keys(stateSet).length === 0) {
|
|
||||||
answer.preferUntransformed = true
|
|
||||||
}
|
|
||||||
conn.send(syncUser, answer)
|
conn.send(syncUser, answer)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -204,8 +195,7 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
if (!conn.isSynced) {
|
if (!conn.isSynced) {
|
||||||
// it is crucial that isSynced is set at the time garbageCollectAfterSync is called
|
// it is crucial that isSynced is set at the time garbageCollectAfterSync is called
|
||||||
conn.isSynced = true
|
conn.isSynced = true
|
||||||
// It is safer to remove this!
|
yield* this.garbageCollectAfterSync()
|
||||||
// TODO: remove: yield * this.garbageCollectAfterSync()
|
|
||||||
// call whensynced listeners
|
// call whensynced listeners
|
||||||
for (var f of conn.whenSyncedListeners) {
|
for (var f of conn.whenSyncedListeners) {
|
||||||
f()
|
f()
|
||||||
@@ -243,7 +233,11 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
if (this.broadcastOpBuffer.length === 0) {
|
if (this.broadcastOpBuffer.length === 0) {
|
||||||
this.broadcastOpBuffer = ops
|
this.broadcastOpBuffer = ops
|
||||||
|
if (this.y.db.transactionInProgress) {
|
||||||
this.y.db.whenTransactionsFinished().then(broadcastOperations)
|
this.y.db.whenTransactionsFinished().then(broadcastOperations)
|
||||||
|
} else {
|
||||||
|
setTimeout(broadcastOperations, 0)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops)
|
this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops)
|
||||||
}
|
}
|
||||||
@@ -267,11 +261,11 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
type: 'sync stop',
|
type: 'sync stop',
|
||||||
protocolVersion: this.protocolVersion
|
protocolVersion: this.protocolVersion
|
||||||
})
|
})
|
||||||
return Promise.reject(new Error('Incompatible protocol version'))
|
return Promise.reject('Incompatible protocol version')
|
||||||
}
|
}
|
||||||
if (message.auth != null && this.connections[sender] != null) {
|
if (message.auth != null && this.connections[sender] != null) {
|
||||||
// authenticate using auth in message
|
// authenticate using auth in message
|
||||||
var auth = this.checkAuth(message.auth, this.y)
|
var auth = this.checkAuth(message.auth, this.y, sender)
|
||||||
this.connections[sender].auth = auth
|
this.connections[sender].auth = auth
|
||||||
auth.then(auth => {
|
auth.then(auth => {
|
||||||
for (var f of this.userEventListeners) {
|
for (var f of this.userEventListeners) {
|
||||||
@@ -284,30 +278,19 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
})
|
})
|
||||||
} else if (this.connections[sender] != null && this.connections[sender].auth == null) {
|
} else if (this.connections[sender] != null && this.connections[sender].auth == null) {
|
||||||
// authenticate without otherwise
|
// authenticate without otherwise
|
||||||
this.connections[sender].auth = this.checkAuth(null, this.y)
|
this.connections[sender].auth = this.checkAuth(null, this.y, sender)
|
||||||
}
|
}
|
||||||
if (this.connections[sender] != null && this.connections[sender].auth != null) {
|
if (this.connections[sender] != null && this.connections[sender].auth != null) {
|
||||||
return this.connections[sender].auth.then((auth) => {
|
return this.connections[sender].auth.then((auth) => {
|
||||||
if (message.type === 'sync step 1' && canRead(auth)) {
|
if (message.type === 'sync step 1' && canRead(auth)) {
|
||||||
let conn = this
|
let conn = this
|
||||||
let m = message
|
let m = message
|
||||||
let wait // wait for sync step 2 to complete
|
|
||||||
if (this.role === 'slave') {
|
|
||||||
wait = Promise.all(Object.keys(this.connections)
|
|
||||||
.map(uid => this.connections[uid])
|
|
||||||
.filter(conn => conn.role === 'master')
|
|
||||||
.map(conn => conn.syncStep2.promise)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
wait = Promise.resolve()
|
|
||||||
}
|
|
||||||
wait.then(() => {
|
|
||||||
this.y.db.requestTransaction(function *() {
|
this.y.db.requestTransaction(function *() {
|
||||||
var currentStateSet = yield* this.getStateSet()
|
var currentStateSet = yield* this.getStateSet()
|
||||||
// TODO: remove
|
if (canWrite(auth)) {
|
||||||
// if (canWrite(auth)) {
|
yield* this.applyDeleteSet(m.deleteSet)
|
||||||
// yield * this.applyDeleteSet(m.deleteSet)
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
var ds = yield* this.getDeleteSet()
|
var ds = yield* this.getDeleteSet()
|
||||||
var answer = {
|
var answer = {
|
||||||
@@ -317,11 +300,7 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
protocolVersion: this.protocolVersion,
|
protocolVersion: this.protocolVersion,
|
||||||
auth: this.authInfo
|
auth: this.authInfo
|
||||||
}
|
}
|
||||||
if (message.preferUntransformed === true && Object.keys(m.stateSet).length === 0) {
|
|
||||||
answer.osUntransformed = yield * this.getOperationsUntransformed()
|
|
||||||
} else {
|
|
||||||
answer.os = yield* this.getOperations(m.stateSet)
|
answer.os = yield* this.getOperations(m.stateSet)
|
||||||
}
|
|
||||||
conn.send(sender, answer)
|
conn.send(sender, answer)
|
||||||
if (this.forwardToSyncingClients) {
|
if (this.forwardToSyncingClients) {
|
||||||
conn.syncingClients.push(sender)
|
conn.syncingClients.push(sender)
|
||||||
@@ -339,12 +318,14 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
} else if (message.type === 'sync step 2' && canWrite(auth)) {
|
} else if (message.type === 'sync step 2' && canWrite(auth)) {
|
||||||
var db = this.y.db
|
var db = this.y.db
|
||||||
let defer = this.connections[sender].syncStep2
|
var defer = {}
|
||||||
let m = message
|
defer.promise = new Promise(function (resolve) {
|
||||||
// apply operations first
|
defer.resolve = resolve
|
||||||
|
})
|
||||||
|
this.syncStep2 = defer.promise
|
||||||
|
let m /* :MessageSyncStep2 */ = message
|
||||||
db.requestTransaction(function * () {
|
db.requestTransaction(function * () {
|
||||||
yield* this.applyDeleteSet(m.deleteSet)
|
yield* this.applyDeleteSet(m.deleteSet)
|
||||||
if (m.osUntransformed != null) {
|
if (m.osUntransformed != null) {
|
||||||
@@ -352,19 +333,30 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
} else {
|
} else {
|
||||||
this.store.apply(m.os)
|
this.store.apply(m.os)
|
||||||
}
|
}
|
||||||
defer.resolve()
|
/*
|
||||||
})
|
* This just sends the complete hb after some time
|
||||||
/*/ then apply ds
|
* Mostly for debugging..
|
||||||
db.whenTransactionsFinished().then(() => {
|
*
|
||||||
db.requestTransaction(function * () {
|
db.requestTransaction(function * () {
|
||||||
yield * this.applyDeleteSet(m.deleteSet)
|
var ops = yield* this.getOperations(m.stateSet)
|
||||||
|
if (ops.length > 0) {
|
||||||
|
if (!broadcastHB) { // TODO: consider to broadcast here..
|
||||||
|
conn.send(sender, {
|
||||||
|
type: 'update',
|
||||||
|
ops: ops
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// broadcast only once!
|
||||||
|
conn.broadcastOps(ops)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
*/
|
||||||
defer.resolve()
|
defer.resolve()
|
||||||
})*/
|
})
|
||||||
return defer.promise
|
|
||||||
} else if (message.type === 'sync done') {
|
} else if (message.type === 'sync done') {
|
||||||
var self = this
|
var self = this
|
||||||
this.connections[sender].syncStep2.promise.then(function () {
|
this.syncStep2.then(function () {
|
||||||
self._setSyncedWith(sender)
|
self._setSyncedWith(sender)
|
||||||
})
|
})
|
||||||
} else if (message.type === 'update' && canWrite(auth)) {
|
} else if (message.type === 'update' && canWrite(auth)) {
|
||||||
@@ -385,7 +377,7 @@ export default function extendConnector (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error('Unable to deliver message'))
|
return Promise.reject('Unable to deliver message')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_setSyncedWith (user) {
|
_setSyncedWith (user) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
export default function extendDatabase (Y /* :any */) {
|
module.exports = function (Y /* :any */) {
|
||||||
/*
|
/*
|
||||||
Partial definition of an OperationStore.
|
Partial definition of an OperationStore.
|
||||||
TODO: name it Database, operation store only holds operations.
|
TODO: name it Database, operation store only holds operations.
|
||||||
@@ -39,15 +39,14 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
*/
|
*/
|
||||||
constructor (y, opts) {
|
constructor (y, opts) {
|
||||||
this.y = y
|
this.y = y
|
||||||
opts.gc = opts.gc === true
|
|
||||||
this.dbOpts = opts
|
this.dbOpts = opts
|
||||||
var os = this
|
var os = this
|
||||||
this.userId = null
|
this.userId = null
|
||||||
var resolve_
|
var resolve
|
||||||
this.userIdPromise = new Promise(function (resolve) {
|
this.userIdPromise = new Promise(function (r) {
|
||||||
resolve_ = resolve
|
resolve = r
|
||||||
})
|
})
|
||||||
this.userIdPromise.resolve = resolve_
|
this.userIdPromise.resolve = resolve
|
||||||
// whether to broadcast all applied operations (insert & delete hook)
|
// whether to broadcast all applied operations (insert & delete hook)
|
||||||
this.forwardAppliedOperations = false
|
this.forwardAppliedOperations = false
|
||||||
// E.g. this.listenersById[id] : Array<Listener>
|
// E.g. this.listenersById[id] : Array<Listener>
|
||||||
@@ -72,7 +71,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
this.waitingTransactions = []
|
this.waitingTransactions = []
|
||||||
this.transactionInProgress = false
|
this.transactionInProgress = false
|
||||||
this.transactionIsFlushed = false
|
this.transactionIsFlushed = false
|
||||||
if (typeof YConcurrencyTestingMode !== 'undefined') {
|
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
||||||
this.executeOrder = []
|
this.executeOrder = []
|
||||||
}
|
}
|
||||||
this.gc1 = [] // first stage
|
this.gc1 = [] // first stage
|
||||||
@@ -80,11 +79,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
|
|
||||||
function garbageCollect () {
|
function garbageCollect () {
|
||||||
return os.whenTransactionsFinished().then(function () {
|
return os.whenTransactionsFinished().then(function () {
|
||||||
if (os.gcTimeout > 0 && (os.gc1.length > 0 || os.gc2.length > 0)) {
|
if (os.gc1.length > 0 || os.gc2.length > 0) {
|
||||||
// debug
|
|
||||||
if (os.y.connector.isSynced === false) {
|
|
||||||
debugger
|
|
||||||
}
|
|
||||||
if (!os.y.connector.isSynced) {
|
if (!os.y.connector.isSynced) {
|
||||||
console.warn('gc should be empty when not synced!')
|
console.warn('gc should be empty when not synced!')
|
||||||
}
|
}
|
||||||
@@ -122,7 +117,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
this.startRepairCheck()
|
this.startRepairCheck()
|
||||||
}
|
}
|
||||||
startGarbageCollector () {
|
startGarbageCollector () {
|
||||||
this.gc = this.dbOpts.gc
|
this.gc = this.dbOpts.gc == null || this.dbOpts.gc
|
||||||
if (this.gc) {
|
if (this.gc) {
|
||||||
this.gcTimeout = !this.dbOpts.gcTimeout ? 50000 : this.dbOpts.gcTimeout
|
this.gcTimeout = !this.dbOpts.gcTimeout ? 50000 : this.dbOpts.gcTimeout
|
||||||
} else {
|
} else {
|
||||||
@@ -182,7 +177,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
addToDebug () {
|
addToDebug () {
|
||||||
if (typeof YConcurrencyTestingMode !== 'undefined') {
|
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
||||||
var command /* :string */ = Array.prototype.map.call(arguments, function (s) {
|
var command /* :string */ = Array.prototype.map.call(arguments, function (s) {
|
||||||
if (typeof s === 'string') {
|
if (typeof s === 'string') {
|
||||||
return s
|
return s
|
||||||
@@ -270,7 +265,7 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
* destroy () {
|
* destroy () {
|
||||||
clearTimeout(this.gcInterval)
|
clearInterval(this.gcInterval)
|
||||||
this.gcInterval = null
|
this.gcInterval = null
|
||||||
this.stopRepairCheck()
|
this.stopRepairCheck()
|
||||||
}
|
}
|
||||||
@@ -516,12 +511,12 @@ export default function extendDatabase (Y /* :any */) {
|
|||||||
whenTransactionsFinished () {
|
whenTransactionsFinished () {
|
||||||
if (this.transactionInProgress) {
|
if (this.transactionInProgress) {
|
||||||
if (this.transactionsFinished == null) {
|
if (this.transactionsFinished == null) {
|
||||||
var resolve_
|
var resolve
|
||||||
var promise = new Promise(function (resolve) {
|
var promise = new Promise(function (r) {
|
||||||
resolve_ = resolve
|
resolve = r
|
||||||
})
|
})
|
||||||
this.transactionsFinished = {
|
this.transactionsFinished = {
|
||||||
resolve: resolve_,
|
resolve: resolve,
|
||||||
promise: promise
|
promise: promise
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ g.setRandomSeed = function setRandomSeed (seed) {
|
|||||||
|
|
||||||
g.generateRandomSeed()
|
g.generateRandomSeed()
|
||||||
|
|
||||||
g.YConcurrencyTestingMode = true
|
g.YConcurrency_TestingMode = true
|
||||||
|
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
* requiredOps
|
* requiredOps
|
||||||
- Operations that are required to execute this operation.
|
- Operations that are required to execute this operation.
|
||||||
*/
|
*/
|
||||||
export default function extendStruct (Y) {
|
module.exports = function (Y/* :any */) {
|
||||||
var Struct = {
|
var Struct = {
|
||||||
/* This is the only operation that is actually not a structure, because
|
/* This is the only operation that is actually not a structure, because
|
||||||
it is not stored in the OS. This is why it _does not_ have an id
|
it is not stored in the OS. This is why it _does not_ have an id
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
- this is called only by `getOperations(startSS)`. It makes an operation
|
- this is called only by `getOperations(startSS)`. It makes an operation
|
||||||
applyable on a given SS.
|
applyable on a given SS.
|
||||||
*/
|
*/
|
||||||
export default function extendTransaction (Y) {
|
module.exports = function (Y/* :any */) {
|
||||||
class TransactionInterface {
|
class TransactionInterface {
|
||||||
/* ::
|
/* ::
|
||||||
store: Y.AbstractDatabase;
|
store: Y.AbstractDatabase;
|
||||||
@@ -96,7 +96,7 @@ export default function extendTransaction (Y) {
|
|||||||
send.push(Y.Struct[op.struct].encode(op))
|
send.push(Y.Struct[op.struct].encode(op))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.store.y.connector.isSynced && send.length > 0) { // TODO: && !this.store.forwardAppliedOperations (but then i don't send delete ops)
|
if (send.length > 0) { // TODO: && !this.store.forwardAppliedOperations (but then i don't send delete ops)
|
||||||
// is connected, and this is not going to be send in addOperation
|
// is connected, and this is not going to be send in addOperation
|
||||||
this.store.y.connector.broadcastOps(send)
|
this.store.y.connector.broadcastOps(send)
|
||||||
}
|
}
|
||||||
@@ -717,7 +717,7 @@ export default function extendTransaction (Y) {
|
|||||||
}
|
}
|
||||||
* addOperation (op) {
|
* addOperation (op) {
|
||||||
yield* this.os.put(op)
|
yield* this.os.put(op)
|
||||||
if (this.store.y.connector.isSynced && this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
|
if (this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
|
||||||
// is connected, and this is not going to be send in addOperation
|
// is connected, and this is not going to be send in addOperation
|
||||||
this.store.y.connector.broadcastOps([op])
|
this.store.y.connector.broadcastOps([op])
|
||||||
}
|
}
|
||||||
@@ -966,7 +966,7 @@ export default function extendTransaction (Y) {
|
|||||||
// 2. or to the first operation that has an origin that is not to the
|
// 2. or to the first operation that has an origin that is not to the
|
||||||
// right of op.
|
// right of op.
|
||||||
// For this we maintain a list of ops which origins are not found yet.
|
// For this we maintain a list of ops which origins are not found yet.
|
||||||
var missingOrigins = [op]
|
var missing_origins = [op]
|
||||||
var newright = op.right
|
var newright = op.right
|
||||||
while (true) {
|
while (true) {
|
||||||
if (o.left == null) {
|
if (o.left == null) {
|
||||||
@@ -974,15 +974,15 @@ export default function extendTransaction (Y) {
|
|||||||
send.push(op)
|
send.push(op)
|
||||||
if (!Y.utils.compareIds(o.id, op.id)) {
|
if (!Y.utils.compareIds(o.id, op.id)) {
|
||||||
o = Y.Struct[op.struct].encode(o)
|
o = Y.Struct[op.struct].encode(o)
|
||||||
o.right = missingOrigins[missingOrigins.length - 1].id
|
o.right = missing_origins[missing_origins.length - 1].id
|
||||||
send.push(o)
|
send.push(o)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
o = yield* this.getInsertion(o.left)
|
o = yield* this.getInsertion(o.left)
|
||||||
// we set another o, check if we can reduce $missingOrigins
|
// we set another o, check if we can reduce $missing_origins
|
||||||
while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) {
|
while (missing_origins.length > 0 && Y.utils.matchesId(o, missing_origins[missing_origins.length - 1].origin)) {
|
||||||
missingOrigins.pop()
|
missing_origins.pop()
|
||||||
}
|
}
|
||||||
if (o.id[1] < (startSS[o.id[0]] || 0)) {
|
if (o.id[1] < (startSS[o.id[0]] || 0)) {
|
||||||
// case 2. o is known
|
// case 2. o is known
|
||||||
@@ -995,18 +995,17 @@ export default function extendTransaction (Y) {
|
|||||||
send.push(op)
|
send.push(op)
|
||||||
op = Y.Struct[op.struct].encode(o)
|
op = Y.Struct[op.struct].encode(o)
|
||||||
op.right = newright
|
op.right = newright
|
||||||
if (missingOrigins.length > 0) {
|
if (missing_origins.length > 0) {
|
||||||
debugger
|
|
||||||
console.log('This should not happen .. :( please report this')
|
console.log('This should not happen .. :( please report this')
|
||||||
}
|
}
|
||||||
missingOrigins = [op]
|
missing_origins = [op]
|
||||||
} else {
|
} else {
|
||||||
// case 4. send o, continue to find op.origin
|
// case 4. send o, continue to find op.origin
|
||||||
var s = Y.Struct[op.struct].encode(o)
|
var s = Y.Struct[op.struct].encode(o)
|
||||||
s.right = missingOrigins[missingOrigins.length - 1].id
|
s.right = missing_origins[missing_origins.length - 1].id
|
||||||
s.left = s.origin
|
s.left = s.origin
|
||||||
send.push(s)
|
send.push(s)
|
||||||
missingOrigins.push(o)
|
missing_origins.push(o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
/* @flow */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
EventHandler is an helper class for constructing custom types.
|
EventHandler is an helper class for constructing custom types.
|
||||||
|
|
||||||
@@ -20,8 +23,7 @@
|
|||||||
database request to finish). EventHandler helps you to make your type
|
database request to finish). EventHandler helps you to make your type
|
||||||
synchronous.
|
synchronous.
|
||||||
*/
|
*/
|
||||||
|
module.exports = function (Y /* : any*/) {
|
||||||
export default function Utils (Y) {
|
|
||||||
Y.utils = {}
|
Y.utils = {}
|
||||||
|
|
||||||
Y.utils.bubbleEvent = function (type, event) {
|
Y.utils.bubbleEvent = function (type, event) {
|
||||||
|
|||||||
44
src/y.js
44
src/y.js
@@ -1,20 +1,18 @@
|
|||||||
import debug from 'debug'
|
/* @flow */
|
||||||
import extendConnector from './Connector.js'
|
'use strict'
|
||||||
import extendDatabase from './Database.js'
|
|
||||||
import extendTransaction from './Transaction.js'
|
|
||||||
import extendStruct from './Struct.js'
|
|
||||||
import extendUtils from './Utils.js'
|
|
||||||
|
|
||||||
extendConnector(Y)
|
require('./Connector.js')(Y)
|
||||||
extendDatabase(Y)
|
require('./Database.js')(Y)
|
||||||
extendTransaction(Y)
|
require('./Transaction.js')(Y)
|
||||||
extendStruct(Y)
|
require('./Struct.js')(Y)
|
||||||
extendUtils(Y)
|
require('./Utils.js')(Y)
|
||||||
|
require('./Connectors/Test.js')(Y)
|
||||||
|
|
||||||
Y.debug = debug
|
Y.debug = require('debug')
|
||||||
|
|
||||||
var requiringModules = {}
|
var requiringModules = {}
|
||||||
|
|
||||||
|
module.exports = Y
|
||||||
Y.requiringModules = requiringModules
|
Y.requiringModules = requiringModules
|
||||||
|
|
||||||
Y.extend = function (name, value) {
|
Y.extend = function (name, value) {
|
||||||
@@ -112,7 +110,7 @@ type YOptions = {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
||||||
if (opts.hasOwnProperty('sourceDir')) {
|
if (opts.hasOwnProperty('sourceDir')) {
|
||||||
Y.sourceDir = opts.sourceDir
|
Y.sourceDir = opts.sourceDir
|
||||||
}
|
}
|
||||||
@@ -122,16 +120,17 @@ export default function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
|||||||
modules.push(opts.share[name])
|
modules.push(opts.share[name])
|
||||||
}
|
}
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
if (opts == null) reject(new Error('An options object is expected!'))
|
if (opts == null) reject('An options object is expected! ')
|
||||||
else if (opts.connector == null) reject(new Error('You must specify a connector! (missing connector property)'))
|
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)')
|
||||||
else if (opts.connector.name == null) reject(new Error('You must specify connector name! (missing connector.name property)'))
|
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
||||||
else if (opts.db == null) reject(new Error('You must specify a database! (missing db property)'))
|
else if (opts.db == null) reject('You must specify a database! (missing db property)')
|
||||||
else if (opts.connector.name == null) reject(new Error('You must specify db name! (missing db.name property)'))
|
else if (opts.connector.name == null) reject('You must specify db name! (missing db.name property)')
|
||||||
else {
|
else {
|
||||||
opts = Y.utils.copyObject(opts)
|
opts = Y.utils.copyObject(opts)
|
||||||
opts.connector = Y.utils.copyObject(opts.connector)
|
opts.connector = Y.utils.copyObject(opts.connector)
|
||||||
opts.db = Y.utils.copyObject(opts.db)
|
opts.db = Y.utils.copyObject(opts.db)
|
||||||
opts.share = Y.utils.copyObject(opts.share)
|
opts.share = Y.utils.copyObject(opts.share)
|
||||||
|
setTimeout(function () {
|
||||||
Y.requestModules(modules).then(function () {
|
Y.requestModules(modules).then(function () {
|
||||||
var yconfig = new YConfig(opts)
|
var yconfig = new YConfig(opts)
|
||||||
yconfig.db.whenUserIdSet(function () {
|
yconfig.db.whenUserIdSet(function () {
|
||||||
@@ -140,6 +139,7 @@ export default function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}).catch(reject)
|
}).catch(reject)
|
||||||
|
}, 0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -225,13 +225,13 @@ class YConfig {
|
|||||||
} else {
|
} else {
|
||||||
this.connector.disconnect()
|
this.connector.disconnect()
|
||||||
}
|
}
|
||||||
return this.db.whenTransactionsFinished().then(function () {
|
return this.db.whenTransactionsFinished(function () {
|
||||||
self.db.destroyTypes()
|
this.db.destroyTypes()
|
||||||
// make sure to wait for all transactions before destroying the db
|
// make sure to wait for all transactions before destroying the db
|
||||||
self.db.requestTransaction(function * () {
|
this.db.requestTransaction(function * () {
|
||||||
yield* self.db.destroy()
|
yield* self.db.destroy()
|
||||||
})
|
})
|
||||||
return self.db.whenTransactionsFinished()
|
return this.db.whenTransactionsFinished()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,178 +0,0 @@
|
|||||||
|
|
||||||
import _Y from '../../yjs/src/y.js'
|
|
||||||
|
|
||||||
import yMemory from '../../y-memory/src/Memory.js'
|
|
||||||
import yArray from '../../y-array/src/y-array.js'
|
|
||||||
import yMap from '../../y-map/src/Map.js'
|
|
||||||
import yTest from './test-connector.js'
|
|
||||||
|
|
||||||
import Chance from 'chance'
|
|
||||||
|
|
||||||
export let Y = _Y
|
|
||||||
|
|
||||||
Y.extend(yMemory, yArray, yMap, yTest)
|
|
||||||
|
|
||||||
export async function garbageCollectUsers (t, users) {
|
|
||||||
await flushAll(t, users)
|
|
||||||
await Promise.all(users.map(u => u.db.emptyGarbageCollector()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 1. reconnect and flush all
|
|
||||||
* 2. user 0 gc
|
|
||||||
* 3. get type content
|
|
||||||
* 4. disconnect & reconnect all (so gc is propagated)
|
|
||||||
* 5. compare os, ds, ss
|
|
||||||
*/
|
|
||||||
export async function compareUsers (t, users) {
|
|
||||||
await Promise.all(users.map(u => u.reconnect()))
|
|
||||||
if (users[0].connector.testRoom == null) {
|
|
||||||
await wait(100)
|
|
||||||
}
|
|
||||||
await flushAll(t, users)
|
|
||||||
|
|
||||||
await users[0].db.garbageCollect()
|
|
||||||
await users[0].db.garbageCollect()
|
|
||||||
|
|
||||||
var userTypeContents = users.map(u => u.share.array._content.map(c => c.val || JSON.stringify(c.type)))
|
|
||||||
|
|
||||||
// disconnect all except user 0
|
|
||||||
await Promise.all(users.slice(1).map(async u =>
|
|
||||||
u.disconnect()
|
|
||||||
))
|
|
||||||
if (users[0].connector.testRoom == null) {
|
|
||||||
await wait(100)
|
|
||||||
}
|
|
||||||
// reconnect all
|
|
||||||
await Promise.all(users.map(u => u.reconnect()))
|
|
||||||
if (users[0].connector.testRoom == null) {
|
|
||||||
await wait(100)
|
|
||||||
}
|
|
||||||
await users[0].connector.testRoom.flushAll(users)
|
|
||||||
await Promise.all(users.map(u =>
|
|
||||||
new Promise(function (resolve) {
|
|
||||||
u.connector.whenSynced(resolve)
|
|
||||||
})
|
|
||||||
))
|
|
||||||
|
|
||||||
var data = await Promise.all(users.map(async (u) => {
|
|
||||||
var data = {}
|
|
||||||
u.db.requestTransaction(function * () {
|
|
||||||
var os = yield * this.getOperationsUntransformed()
|
|
||||||
data.os = {}
|
|
||||||
os.untransformed.forEach((op) => {
|
|
||||||
op = Y.Struct[op.struct].encode(op)
|
|
||||||
delete op.origin
|
|
||||||
data.os[JSON.stringify(op.id)] = op
|
|
||||||
return op
|
|
||||||
})
|
|
||||||
data.ds = yield * this.getDeleteSet()
|
|
||||||
data.ss = yield * this.getStateSet()
|
|
||||||
})
|
|
||||||
await u.db.whenTransactionsFinished()
|
|
||||||
return data
|
|
||||||
}))
|
|
||||||
for (var i = 0; i < data.length - 1; i++) {
|
|
||||||
await t.asyncGroup(async () => {
|
|
||||||
t.compare(userTypeContents[i], userTypeContents[i + 1], 'types')
|
|
||||||
t.compare(data[i].os, data[i + 1].os, 'os')
|
|
||||||
t.compare(data[i].ds, data[i + 1].ds, 'ds')
|
|
||||||
t.compare(data[i].ss, data[i + 1].ss, 'ss')
|
|
||||||
}, `Compare user${i} with user${i + 1}`)
|
|
||||||
}
|
|
||||||
await Promise.all(users.map(async (u) => {
|
|
||||||
await u.close()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initArrays (t, opts) {
|
|
||||||
var result = {
|
|
||||||
users: []
|
|
||||||
}
|
|
||||||
var share = Object.assign({ flushHelper: 'Map', array: 'Array' }, opts.share)
|
|
||||||
var chance = opts.chance || new Chance(t.getSeed() * 1000000000)
|
|
||||||
var connector = Object.assign({ room: 'debugging_' + t.name, testContext: t, chance }, opts.connector)
|
|
||||||
for (let i = 0; i < opts.users; i++) {
|
|
||||||
let dbOpts
|
|
||||||
let connOpts
|
|
||||||
if (i === 0) {
|
|
||||||
// Only one instance can gc!
|
|
||||||
dbOpts = Object.assign({ gc: true }, opts.db)
|
|
||||||
connOpts = Object.assign({ role: 'master' }, connector)
|
|
||||||
} else {
|
|
||||||
dbOpts = Object.assign({ gc: false }, opts.db)
|
|
||||||
connOpts = Object.assign({ role: 'slave' }, connector)
|
|
||||||
}
|
|
||||||
let y = await Y({
|
|
||||||
connector: connOpts,
|
|
||||||
db: dbOpts,
|
|
||||||
share: share
|
|
||||||
})
|
|
||||||
result.users.push(y)
|
|
||||||
for (let name in share) {
|
|
||||||
result[name + i] = y.share[name]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.array0.delete(0, result.array0.length)
|
|
||||||
if (result.users[0].connector.testRoom != null) {
|
|
||||||
// flush for sync if test-connector
|
|
||||||
await result.users[0].connector.testRoom.flushAll(result.users)
|
|
||||||
}
|
|
||||||
await Promise.all(result.users.map(u => {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
u.connector.whenSynced(resolve)
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
await flushAll(t, result.users)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function flushAll (t, users) {
|
|
||||||
// users = users.filter(u => u.connector.isSynced)
|
|
||||||
if (users.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await wait(0)
|
|
||||||
if (users[0].connector.testRoom != null) {
|
|
||||||
// use flushAll method specified in Test Connector
|
|
||||||
await users[0].connector.testRoom.flushAll(users)
|
|
||||||
} else {
|
|
||||||
// flush for any connector
|
|
||||||
await Promise.all(users.map(u => { return u.db.whenTransactionsFinished() }))
|
|
||||||
|
|
||||||
var flushCounter = users[0].share.flushHelper.get('0') || 0
|
|
||||||
flushCounter++
|
|
||||||
await Promise.all(users.map(async (u, i) => {
|
|
||||||
// wait for all users to set the flush counter to the same value
|
|
||||||
await new Promise(resolve => {
|
|
||||||
function observer () {
|
|
||||||
var allUsersReceivedUpdate = true
|
|
||||||
for (var i = 0; i < users.length; i++) {
|
|
||||||
if (u.share.flushHelper.get(i + '') !== flushCounter) {
|
|
||||||
allUsersReceivedUpdate = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (allUsersReceivedUpdate) {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
u.share.flushHelper.observe(observer)
|
|
||||||
u.share.flushHelper.set(i + '', flushCounter)
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function flushSome (t, users) {
|
|
||||||
if (users[0].connector.testRoom == null) {
|
|
||||||
// if not test-connector, wait for some time for operations to arrive
|
|
||||||
await wait(100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function wait (t) {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
setTimeout(resolve, t != null ? t : 100)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/* global Y */
|
|
||||||
import { wait } from './helper.js'
|
|
||||||
|
|
||||||
var rooms = {}
|
|
||||||
|
|
||||||
export class TestRoom {
|
|
||||||
constructor (roomname) {
|
|
||||||
this.room = roomname
|
|
||||||
this.users = {}
|
|
||||||
this.nextUserId = 0
|
|
||||||
}
|
|
||||||
join (connector) {
|
|
||||||
if (connector.userId == null) {
|
|
||||||
connector.setUserId('' + (this.nextUserId++))
|
|
||||||
}
|
|
||||||
Object.keys(this.users).forEach(uid => {
|
|
||||||
this.users[uid].userJoined(connector.userId, connector.role)
|
|
||||||
connector.userJoined(uid, this.users[uid].role)
|
|
||||||
})
|
|
||||||
this.users[connector.userId] = connector
|
|
||||||
}
|
|
||||||
leave (connector) {
|
|
||||||
delete this.users[connector.userId]
|
|
||||||
Object.keys(this.users).forEach(uid => {
|
|
||||||
this.users[uid].userLeft(connector.userId)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
send (sender, receiver, m) {
|
|
||||||
m = JSON.parse(JSON.stringify(m))
|
|
||||||
var user = this.users[receiver]
|
|
||||||
if (user != null) {
|
|
||||||
user.receiveMessage(sender, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
broadcast (sender, m) {
|
|
||||||
Object.keys(this.users).forEach(receiver => {
|
|
||||||
this.send(sender, receiver, m)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
async flushAll (users) {
|
|
||||||
let flushing = true
|
|
||||||
let allUserIds = Object.keys(this.users)
|
|
||||||
if (users == null) {
|
|
||||||
users = allUserIds.map(id => this.users[id].y)
|
|
||||||
}
|
|
||||||
while (flushing) {
|
|
||||||
await wait(10)
|
|
||||||
let res = await Promise.all(allUserIds.map(id => this.users[id]._flushAll(users)))
|
|
||||||
flushing = res.some(status => status === 'flushing')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTestRoom (roomname) {
|
|
||||||
if (rooms[roomname] == null) {
|
|
||||||
rooms[roomname] = new TestRoom(roomname)
|
|
||||||
}
|
|
||||||
return rooms[roomname]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function extendTestConnector (Y) {
|
|
||||||
class TestConnector extends Y.AbstractConnector {
|
|
||||||
constructor (y, options) {
|
|
||||||
if (options === undefined) {
|
|
||||||
throw new Error('Options must not be undefined!')
|
|
||||||
}
|
|
||||||
if (options.room == null) {
|
|
||||||
throw new Error('You must define a room name!')
|
|
||||||
}
|
|
||||||
super(y, options)
|
|
||||||
this.options = options
|
|
||||||
this.room = options.room
|
|
||||||
this.chance = options.chance
|
|
||||||
this.testRoom = getTestRoom(this.room)
|
|
||||||
this.testRoom.join(this)
|
|
||||||
}
|
|
||||||
disconnect () {
|
|
||||||
this.testRoom.leave(this)
|
|
||||||
return super.disconnect()
|
|
||||||
}
|
|
||||||
reconnect () {
|
|
||||||
this.testRoom.join(this)
|
|
||||||
return super.reconnect()
|
|
||||||
}
|
|
||||||
send (uid, message) {
|
|
||||||
this.testRoom.send(this.userId, uid, message)
|
|
||||||
}
|
|
||||||
broadcast (message) {
|
|
||||||
this.testRoom.broadcast(this.userId, message)
|
|
||||||
}
|
|
||||||
async whenSynced (f) {
|
|
||||||
var synced = false
|
|
||||||
var periodicFlushTillSync = () => {
|
|
||||||
if (synced) {
|
|
||||||
f()
|
|
||||||
} else {
|
|
||||||
this.testRoom.flushAll([this.y]).then(function () {
|
|
||||||
setTimeout(periodicFlushTillSync, 10)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
periodicFlushTillSync()
|
|
||||||
return super.whenSynced(function () {
|
|
||||||
synced = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
receiveMessage (sender, m) {
|
|
||||||
if (this.userId !== sender && this.connections[sender] != null) {
|
|
||||||
var buffer = this.connections[sender].buffer
|
|
||||||
if (buffer == null) {
|
|
||||||
buffer = this.connections[sender].buffer = []
|
|
||||||
}
|
|
||||||
buffer.push(m)
|
|
||||||
if (this.chance.bool({likelihood: 30})) {
|
|
||||||
// flush 1/2 with 30% chance
|
|
||||||
var flushLength = Math.round(buffer.length / 2)
|
|
||||||
buffer.splice(0, flushLength).forEach(m => {
|
|
||||||
super.receiveMessage(sender, m)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async _flushAll (flushUsers) {
|
|
||||||
if (flushUsers.some(u => u.connector.userId === this.userId)) {
|
|
||||||
// this one needs to sync with every other user
|
|
||||||
flushUsers = Object.keys(this.connections).map(id => this.testRoom.users[id].y)
|
|
||||||
}
|
|
||||||
var finished = []
|
|
||||||
for (let i = 0; i < flushUsers.length; i++) {
|
|
||||||
let userId = flushUsers[i].connector.userId
|
|
||||||
if (userId === this.userId) continue
|
|
||||||
let buffer = this.connections[userId].buffer
|
|
||||||
if (buffer != null) {
|
|
||||||
var messages = buffer.splice(0)
|
|
||||||
for (let j = 0; j < messages.length; j++) {
|
|
||||||
let p = super.receiveMessage(userId, messages[j])
|
|
||||||
finished.push(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Promise.all(finished)
|
|
||||||
await this.y.db.whenTransactionsFinished()
|
|
||||||
return finished.length > 0 ? 'flushing' : 'done'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Y.extend('test', TestConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof Y !== 'undefined') {
|
|
||||||
extendTestConnector(Y)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user