Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8bca15d72 | ||
|
|
a64730e651 | ||
|
|
409a9414f1 | ||
|
|
24facaab09 | ||
|
|
060549f2cb | ||
|
|
dfe3b0b1d1 | ||
|
|
e23154bec2 | ||
|
|
1682d43c26 | ||
|
|
68c417fe6f | ||
|
|
2ea163a5cf | ||
|
|
020dacdad4 | ||
|
|
42abcc897c | ||
|
|
0a321610aa |
12
.babelrc
Normal file
12
.babelrc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
["latest", {
|
||||||
|
"es2015": {
|
||||||
|
"modules": false
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"external-helpers"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/* @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
|
|
||||||
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/* @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>;
|
|
||||||
}
|
|
||||||
32
examples/ace/index.html
Normal file
32
examples/ace/index.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!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>
|
||||||
24
examples/ace/index.js
Normal file
24
examples/ace/index.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/* 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)
|
||||||
|
})
|
||||||
29
examples/bower.json
Normal file
29
examples/bower.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
examples/chat/index.html
Normal file
18
examples/chat/index.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!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>
|
||||||
75
examples/chat/index.js
Normal file
75
examples/chat/index.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/* @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
|
||||||
|
}
|
||||||
|
})
|
||||||
23
examples/codemirror/index.html
Normal file
23
examples/codemirror/index.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!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>
|
||||||
24
examples/codemirror/index.js
Normal file
24
examples/codemirror/index.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/* 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)
|
||||||
|
})
|
||||||
19
examples/drawing/index.html
Normal file
19
examples/drawing/index.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!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>
|
||||||
84
examples/drawing/index.js
Normal file
84
examples/drawing/index.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/* 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
|
||||||
|
}
|
||||||
|
})
|
||||||
23
examples/jigsaw/index.html
Normal file
23
examples/jigsaw/index.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!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>
|
||||||
69
examples/jigsaw/index.js
Normal file
69
examples/jigsaw/index.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/* @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 + ")"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
24
examples/monaco/index.html
Normal file
24
examples/monaco/index.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!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>
|
||||||
31
examples/monaco/index.js
Normal file
31
examples/monaco/index.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/* 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
10
examples/package.json
Normal file
10
examples/package.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "examples",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "",
|
||||||
|
"author": "Kevin Jahns",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"monaco-editor": "^0.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
31
examples/quill/index.html
Normal file
31
examples/quill/index.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!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>
|
||||||
39
examples/quill/index.js
Normal file
39
examples/quill/index.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/* 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)
|
||||||
|
})
|
||||||
31
examples/serviceworker/index.html
Normal file
31
examples/serviceworker/index.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!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>
|
||||||
49
examples/serviceworker/index.js
Normal file
49
examples/serviceworker/index.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/* 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)
|
||||||
|
})
|
||||||
22
examples/serviceworker/yjs-sw-template.js
Normal file
22
examples/serviceworker/yjs-sw-template.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/* 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'
|
||||||
|
)
|
||||||
8
examples/textarea/index.html
Normal file
8
examples/textarea/index.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!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>
|
||||||
23
examples/textarea/index.js
Normal file
23
examples/textarea/index.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/* 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..
|
||||||
|
})
|
||||||
39
examples/xml/index.html
Normal file
39
examples/xml/index.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!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>
|
||||||
21
examples/xml/index.js
Normal file
21
examples/xml/index.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/* 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)
|
||||||
|
})
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
|
|
||||||
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
104
gulpfile.js
@@ -1,104 +0,0 @@
|
|||||||
/* 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
Normal file
3119
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@@ -1,22 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "12.3.1",
|
"version": "13.0.0-0",
|
||||||
"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": {
|
||||||
"test": "node --harmony ./node_modules/.bin/gulp test",
|
"lint": "standard",
|
||||||
"lint": "./node_modules/.bin/standard"
|
"dist": "rollup -c rollup.dist.js",
|
||||||
|
"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"
|
||||||
]
|
]
|
||||||
@@ -42,41 +39,24 @@
|
|||||||
},
|
},
|
||||||
"homepage": "http://y-js.org",
|
"homepage": "http://y-js.org",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^5.0.0-beta6",
|
"babel-cli": "^6.24.1",
|
||||||
"babel-plugin-transform-runtime": "^6.1.18",
|
"babel-preset-latest": "^6.24.1",
|
||||||
"babel-preset-es2015": "^6.1.18",
|
"babel-plugin-external-helpers": "^6.22.0",
|
||||||
"babelify": "^7.2.0",
|
"babel-plugin-transform-regenerator": "^6.24.1",
|
||||||
"browserify": "^12.0.1",
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"eslint": "^1.10.2",
|
"chance": "^1.0.9",
|
||||||
"gulp": "^3.9.0",
|
"concurrently": "^3.4.0",
|
||||||
"gulp-bump": "^1.0.0",
|
"rollup-plugin-babel": "^2.7.1",
|
||||||
"gulp-concat": "^2.6.0",
|
"rollup-plugin-commonjs": "^8.0.2",
|
||||||
"gulp-filter": "^3.0.1",
|
"rollup-plugin-inject": "^2.0.0",
|
||||||
"gulp-git": "^1.6.0",
|
"rollup-plugin-multi-entry": "^2.0.1",
|
||||||
"gulp-header": "^1.8.8",
|
"rollup-plugin-node-resolve": "^3.0.0",
|
||||||
"gulp-if": "^2.0.0",
|
"rollup-plugin-uglify": "^1.0.2",
|
||||||
"gulp-jasmine": "^2.0.1",
|
"rollup-regenerator-runtime": "^6.23.1",
|
||||||
"gulp-jasmine-browser": "^0.2.3",
|
"rollup-watch": "^3.2.2",
|
||||||
"gulp-load-plugins": "^1.0.0",
|
"standard": "^10.0.2"
|
||||||
"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.3"
|
"debug": "^2.6.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
rollup.dist.js
Normal file
47
rollup.dist.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
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}
|
||||||
|
*/
|
||||||
|
`
|
||||||
|
}
|
||||||
20
rollup.test.js
Normal file
20
rollup.test.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
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' }
|
||||||
|
|
||||||
module.exports = function (Y/* :any */) {
|
export default function extendConnector (Y/* :any */) {
|
||||||
class AbstractConnector {
|
class AbstractConnector {
|
||||||
/* ::
|
/* ::
|
||||||
y: YConfig;
|
y: YConfig;
|
||||||
@@ -59,7 +59,6 @@ module.exports = function (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
|
||||||
@@ -113,7 +112,7 @@ module.exports = function (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) {
|
||||||
@@ -146,6 +145,9 @@ module.exports = function (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',
|
||||||
@@ -187,7 +189,7 @@ module.exports = function (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
|
||||||
}
|
}
|
||||||
@@ -202,7 +204,8 @@ module.exports = function (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
|
||||||
yield* this.garbageCollectAfterSync()
|
// It is safer to remove this!
|
||||||
|
// 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()
|
||||||
@@ -240,11 +243,7 @@ module.exports = function (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)
|
||||||
}
|
}
|
||||||
@@ -268,7 +267,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
type: 'sync stop',
|
type: 'sync stop',
|
||||||
protocolVersion: this.protocolVersion
|
protocolVersion: this.protocolVersion
|
||||||
})
|
})
|
||||||
return Promise.reject('Incompatible protocol version')
|
return Promise.reject(new Error('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
|
||||||
@@ -292,12 +291,23 @@ module.exports = function (Y/* :any */) {
|
|||||||
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()
|
||||||
if (canWrite(auth)) {
|
// TODO: remove
|
||||||
yield* this.applyDeleteSet(m.deleteSet)
|
// if (canWrite(auth)) {
|
||||||
}
|
// yield * this.applyDeleteSet(m.deleteSet)
|
||||||
|
// }
|
||||||
|
|
||||||
var ds = yield * this.getDeleteSet()
|
var ds = yield * this.getDeleteSet()
|
||||||
var answer = {
|
var answer = {
|
||||||
@@ -329,14 +339,12 @@ module.exports = function (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
|
||||||
var defer = {}
|
let defer = this.connections[sender].syncStep2
|
||||||
defer.promise = new Promise(function (resolve) {
|
let m = message
|
||||||
defer.resolve = resolve
|
// apply operations first
|
||||||
})
|
|
||||||
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) {
|
||||||
@@ -344,30 +352,19 @@ module.exports = function (Y/* :any */) {
|
|||||||
} else {
|
} else {
|
||||||
this.store.apply(m.os)
|
this.store.apply(m.os)
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* This just sends the complete hb after some time
|
|
||||||
* Mostly for debugging..
|
|
||||||
*
|
|
||||||
db.requestTransaction(function * () {
|
|
||||||
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()
|
||||||
})
|
})
|
||||||
|
/*/ then apply ds
|
||||||
|
db.whenTransactionsFinished().then(() => {
|
||||||
|
db.requestTransaction(function * () {
|
||||||
|
yield * this.applyDeleteSet(m.deleteSet)
|
||||||
|
})
|
||||||
|
defer.resolve()
|
||||||
|
})*/
|
||||||
|
return defer.promise
|
||||||
} else if (message.type === 'sync done') {
|
} else if (message.type === 'sync done') {
|
||||||
var self = this
|
var self = this
|
||||||
this.syncStep2.then(function () {
|
this.connections[sender].syncStep2.promise.then(function () {
|
||||||
self._setSyncedWith(sender)
|
self._setSyncedWith(sender)
|
||||||
})
|
})
|
||||||
} else if (message.type === 'update' && canWrite(auth)) {
|
} else if (message.type === 'update' && canWrite(auth)) {
|
||||||
@@ -388,7 +385,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject('Unable to deliver message')
|
return Promise.reject(new Error('Unable to deliver message'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_setSyncedWith (user) {
|
_setSyncedWith (user) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
module.exports = function (Y /* :any */) {
|
export default function extendDatabase (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,14 +39,15 @@ module.exports = function (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 (r) {
|
this.userIdPromise = new Promise(function (resolve) {
|
||||||
resolve = r
|
resolve_ = resolve
|
||||||
})
|
})
|
||||||
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>
|
||||||
@@ -71,7 +72,7 @@ module.exports = function (Y /* :any */) {
|
|||||||
this.waitingTransactions = []
|
this.waitingTransactions = []
|
||||||
this.transactionInProgress = false
|
this.transactionInProgress = false
|
||||||
this.transactionIsFlushed = false
|
this.transactionIsFlushed = false
|
||||||
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
if (typeof YConcurrencyTestingMode !== 'undefined') {
|
||||||
this.executeOrder = []
|
this.executeOrder = []
|
||||||
}
|
}
|
||||||
this.gc1 = [] // first stage
|
this.gc1 = [] // first stage
|
||||||
@@ -79,7 +80,11 @@ module.exports = function (Y /* :any */) {
|
|||||||
|
|
||||||
function garbageCollect () {
|
function garbageCollect () {
|
||||||
return os.whenTransactionsFinished().then(function () {
|
return os.whenTransactionsFinished().then(function () {
|
||||||
if (os.gc1.length > 0 || os.gc2.length > 0) {
|
if (os.gcTimeout > 0 && (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!')
|
||||||
}
|
}
|
||||||
@@ -117,7 +122,7 @@ module.exports = function (Y /* :any */) {
|
|||||||
this.startRepairCheck()
|
this.startRepairCheck()
|
||||||
}
|
}
|
||||||
startGarbageCollector () {
|
startGarbageCollector () {
|
||||||
this.gc = this.dbOpts.gc == null || this.dbOpts.gc
|
this.gc = 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 {
|
||||||
@@ -177,7 +182,7 @@ module.exports = function (Y /* :any */) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
addToDebug () {
|
addToDebug () {
|
||||||
if (typeof YConcurrency_TestingMode !== 'undefined') {
|
if (typeof YConcurrencyTestingMode !== '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
|
||||||
@@ -265,7 +270,7 @@ module.exports = function (Y /* :any */) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
* destroy () {
|
* destroy () {
|
||||||
clearInterval(this.gcInterval)
|
clearTimeout(this.gcInterval)
|
||||||
this.gcInterval = null
|
this.gcInterval = null
|
||||||
this.stopRepairCheck()
|
this.stopRepairCheck()
|
||||||
}
|
}
|
||||||
@@ -511,12 +516,12 @@ module.exports = function (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 (r) {
|
var promise = new Promise(function (resolve) {
|
||||||
resolve = r
|
resolve_ = resolve
|
||||||
})
|
})
|
||||||
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.YConcurrency_TestingMode = true
|
g.YConcurrencyTestingMode = 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.
|
||||||
*/
|
*/
|
||||||
module.exports = function (Y/* :any */) {
|
export default function extendStruct (Y) {
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
module.exports = function (Y/* :any */) {
|
export default function extendTransaction (Y) {
|
||||||
class TransactionInterface {
|
class TransactionInterface {
|
||||||
/* ::
|
/* ::
|
||||||
store: Y.AbstractDatabase;
|
store: Y.AbstractDatabase;
|
||||||
@@ -966,7 +966,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
// 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 missing_origins = [op]
|
var missingOrigins = [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 @@ module.exports = function (Y/* :any */) {
|
|||||||
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 = missing_origins[missing_origins.length - 1].id
|
o.right = missingOrigins[missingOrigins.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 $missing_origins
|
// we set another o, check if we can reduce $missingOrigins
|
||||||
while (missing_origins.length > 0 && Y.utils.matchesId(o, missing_origins[missing_origins.length - 1].origin)) {
|
while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) {
|
||||||
missing_origins.pop()
|
missingOrigins.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,17 +995,18 @@ module.exports = function (Y/* :any */) {
|
|||||||
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 (missing_origins.length > 0) {
|
if (missingOrigins.length > 0) {
|
||||||
|
debugger
|
||||||
console.log('This should not happen .. :( please report this')
|
console.log('This should not happen .. :( please report this')
|
||||||
}
|
}
|
||||||
missing_origins = [op]
|
missingOrigins = [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 = missing_origins[missing_origins.length - 1].id
|
s.right = missingOrigins[missingOrigins.length - 1].id
|
||||||
s.left = s.origin
|
s.left = s.origin
|
||||||
send.push(s)
|
send.push(s)
|
||||||
missing_origins.push(o)
|
missingOrigins.push(o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
/* @flow */
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
EventHandler is an helper class for constructing custom types.
|
EventHandler is an helper class for constructing custom types.
|
||||||
|
|
||||||
@@ -23,7 +20,8 @@
|
|||||||
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,18 +1,20 @@
|
|||||||
/* @flow */
|
import debug from 'debug'
|
||||||
'use strict'
|
import extendConnector from './Connector.js'
|
||||||
|
import extendDatabase from './Database.js'
|
||||||
|
import extendTransaction from './Transaction.js'
|
||||||
|
import extendStruct from './Struct.js'
|
||||||
|
import extendUtils from './Utils.js'
|
||||||
|
|
||||||
require('./Connector.js')(Y)
|
extendConnector(Y)
|
||||||
require('./Database.js')(Y)
|
extendDatabase(Y)
|
||||||
require('./Transaction.js')(Y)
|
extendTransaction(Y)
|
||||||
require('./Struct.js')(Y)
|
extendStruct(Y)
|
||||||
require('./Utils.js')(Y)
|
extendUtils(Y)
|
||||||
require('./Connectors/Test.js')(Y)
|
|
||||||
|
|
||||||
Y.debug = require('debug')
|
Y.debug = debug
|
||||||
|
|
||||||
var requiringModules = {}
|
var requiringModules = {}
|
||||||
|
|
||||||
module.exports = Y
|
|
||||||
Y.requiringModules = requiringModules
|
Y.requiringModules = requiringModules
|
||||||
|
|
||||||
Y.extend = function (name, value) {
|
Y.extend = function (name, value) {
|
||||||
@@ -110,7 +112,7 @@ type YOptions = {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
export default function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
||||||
if (opts.hasOwnProperty('sourceDir')) {
|
if (opts.hasOwnProperty('sourceDir')) {
|
||||||
Y.sourceDir = opts.sourceDir
|
Y.sourceDir = opts.sourceDir
|
||||||
}
|
}
|
||||||
@@ -120,17 +122,16 @@ 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('An options object is expected! ')
|
if (opts == null) reject(new Error('An options object is expected!'))
|
||||||
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)')
|
else if (opts.connector == null) reject(new Error('You must specify a connector! (missing connector property)'))
|
||||||
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
else if (opts.connector.name == null) reject(new Error('You must specify connector name! (missing connector.name property)'))
|
||||||
else if (opts.db == null) reject('You must specify a database! (missing db property)')
|
else if (opts.db == null) reject(new Error('You must specify a database! (missing db property)'))
|
||||||
else if (opts.connector.name == null) reject('You must specify db name! (missing db.name property)')
|
else if (opts.connector.name == null) reject(new Error('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 () {
|
||||||
@@ -139,7 +140,6 @@ 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(function () {
|
return this.db.whenTransactionsFinished().then(function () {
|
||||||
this.db.destroyTypes()
|
self.db.destroyTypes()
|
||||||
// make sure to wait for all transactions before destroying the db
|
// make sure to wait for all transactions before destroying the db
|
||||||
this.db.requestTransaction(function * () {
|
self.db.requestTransaction(function * () {
|
||||||
yield * self.db.destroy()
|
yield * self.db.destroy()
|
||||||
})
|
})
|
||||||
return this.db.whenTransactionsFinished()
|
return self.db.whenTransactionsFinished()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
178
tests-lib/helper.js
Normal file
178
tests-lib/helper.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
151
tests-lib/test-connector.js
Normal file
151
tests-lib/test-connector.js
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
/* 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