implemented disconnect/reconnect in webrtc connector. adapted the example gc also collects child elements (needs improvements)

This commit is contained in:
Kevin Jahns 2015-10-13 14:50:54 +02:00
parent 51e20fb9c7
commit d6e1cd42a2
7 changed files with 124 additions and 57 deletions

View File

@ -1,20 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <body>
<meta charset=utf-8 /> <button id="button">Disconnect</button>
<title>Y Example</title> <h1 id="contenteditable" contentEditable></h1>
<textarea style="width:80%;" rows=40 id="textfield"></textarea>
<script src="../../node_modules/simplewebrtc/simplewebrtc.bundle.js"></script> <script src="../../node_modules/simplewebrtc/simplewebrtc.bundle.js"></script>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</head>
<body>
<h1 id="contenteditable" contentEditable> yjs Tutorial</h1>
<p> Collaborative Json editing with <a href="https://github.com/rwth-acis/yjs/">yjs</a>
and XMPP Connector. </p>
<textarea style="width:80%;" rows=40 id="textfield"></textarea>
<p> <a href="https://github.com/y-js/yjs/">yjs</a> is a Framework for Real-Time collaboration on arbitrary data types.
</p>
</body> </body>
</html> </html>

View File

@ -1,25 +1,47 @@
/* global Y */ /* global Y */
// create a shared object. This function call will return a promise!
Y({ Y({
db: { db: {
name: 'Memory' name: 'Memory'
}, },
connector: { connector: {
name: 'WebRTC', name: 'WebRTC',
room: 'mineeeeeee', room: 'TextBindDemo',
debug: true debug: true
} }
}).then(function (yconfig) { }).then(function (yconfig) {
window.y = yconfig.root // yconfig holds all the information about the shared object
window.yconfig = yconfig window.yconfig = yconfig
// yconfig.root holds the shared element
window.y = yconfig.root
// now we bind the textarea and the contenteditable h1 element
// to a shared element
var textarea = document.getElementById('textfield') var textarea = document.getElementById('textfield')
var contenteditable = document.getElementById('contenteditable') var contenteditable = document.getElementById('contenteditable')
yconfig.root.observePath(['text'], function (text) { yconfig.root.observePath(['text'], function (text) {
// every time the 'text' property of the yconfig.root changes,
// this function is called. Then we bind it to the html elements
if (text != null) { if (text != null) {
// when the text property is deleted, text may be undefined!
// This is why we have to check if text exists..
text.bind(textarea) text.bind(textarea)
text.bind(contenteditable) text.bind(contenteditable)
window.ytext = text
} }
}) })
// create a shared TextBind
yconfig.root.set('text', Y.TextBind) yconfig.root.set('text', Y.TextBind)
// We also provide a button for disconnecting/reconnecting the shared element
var button = document.querySelector('#button')
button.onclick = function () {
if (button.innerText === 'Disconnect') {
yconfig.disconnect()
button.innerText = 'Reconnect'
} else {
yconfig.reconnect()
button.innerText = 'Disconnect'
}
}
}) })

View File

@ -97,7 +97,7 @@ if (options.regenerator) {
files.test = polyfills.concat(files.test) files.test = polyfills.concat(files.test)
} }
gulp.task('build:deploy', function () { gulp.task('deploy', function () {
gulp.src(files.src) gulp.src(files.src)
.pipe(sourcemaps.init()) .pipe(sourcemaps.init())
.pipe(concat('y.js')) .pipe(concat('y.js'))
@ -120,6 +120,13 @@ gulp.task('build:test', function () {
if (!options.regenerator) { if (!options.regenerator) {
babelOptions.blacklist = 'regenerator' babelOptions.blacklist = 'regenerator'
} }
gulp.src(files.src)
.pipe(sourcemaps.init())
.pipe(concat('y.js'))
.pipe(babel(babelOptions))
.pipe(sourcemaps.write())
.pipe(gulp.dest('.'))
return gulp.src('src/**/*.js') return gulp.src('src/**/*.js')
.pipe(sourcemaps.init()) .pipe(sourcemaps.init())
.pipe(babel(babelOptions)) .pipe(babel(babelOptions))
@ -140,10 +147,6 @@ gulp.task('dev:browser', ['build:test'], function () {
.pipe(jasmineBrowser.server({port: options.testport})) .pipe(jasmineBrowser.server({port: options.testport}))
}) })
gulp.task('dev:deploy', ['build:deploy'], function () {
gulp.watch('src/**/*.js', ['build:deploy'])
})
gulp.task('dev', ['build:test'], function () { gulp.task('dev', ['build:test'], function () {
gulp.start('dev:browser') gulp.start('dev:browser')
gulp.start('dev:node') gulp.start('dev:node')

View File

@ -1,4 +1,4 @@
/* global Y */ /* global Y, SimpleWebRTC */
'use strict' 'use strict'
class WebRTC extends Y.AbstractConnector { class WebRTC extends Y.AbstractConnector {
@ -11,21 +11,16 @@ class WebRTC extends Y.AbstractConnector {
} }
options.role = 'slave' options.role = 'slave'
super(y, options) super(y, options)
this.webrtcOptions = {
var room = options.room
var webrtcOptions = {
url: options.url || 'https://yatta.ninja:8888', url: options.url || 'https://yatta.ninja:8888',
room: options.room room: options.room
} }
var swr = new SimpleWebRTC(this.webrtcOptions)
var swr = new SimpleWebRTC(webrtcOptions) // eslint-disable-line no-undef
this.swr = swr this.swr = swr
var self = this var self = this
swr.once('connectionReady', function (userId) { swr.once('connectionReady', function (userId) {
// SimpleWebRTC (swr) is initialized // SimpleWebRTC (swr) is initialized
swr.joinRoom(room) swr.joinRoom(self.webrtcOptions.room)
swr.once('joinedRoom', function () { swr.once('joinedRoom', function () {
self.setUserId(userId) self.setUserId(userId)
@ -61,6 +56,14 @@ class WebRTC extends Y.AbstractConnector {
}) })
}) })
} }
disconnect () {
this.swr.leaveRoom()
super.disconnect()
}
reconnect () {
this.swr.joinRoom(this.webrtcOptions.room)
super.reconnect()
}
send (uid, message) { send (uid, message) {
var self = this var self = this
// we have to make sure that the message is sent under all circumstances // we have to make sure that the message is sent under all circumstances

View File

@ -115,10 +115,26 @@ class AbstractTransaction {
}) })
} }
} }
* deleteList (start) {
if (this.store.y.connector.isSynced) {
while (start != null && this.store.y.connector.isSynced) {
start = (yield* this.getOperation(start))
start.gc = true
yield* this.setOperation(start)
// TODO: will always reset the parent..
this.store.gc1.push(start.id)
start = start.right
}
} else {
// TODO: when not possible??? do later in (gcWhenSynced)
}
}
/* /*
Mark an operation as deleted, and add it to the GC, if possible. Mark an operation as deleted, and add it to the GC, if possible.
*/ */
* deleteOperation (targetId) { * deleteOperation (targetId, preventCallType) {
var target = yield* this.getOperation(targetId) var target = yield* this.getOperation(targetId)
if (target == null || !target.deleted) { if (target == null || !target.deleted) {
@ -129,6 +145,7 @@ class AbstractTransaction {
if (!target.deleted) { if (!target.deleted) {
// set deleted & notify type // set deleted & notify type
target.deleted = true target.deleted = true
if (!preventCallType) {
var type = this.store.initializedTypes[JSON.stringify(target.parent)] var type = this.store.initializedTypes[JSON.stringify(target.parent)]
if (type != null) { if (type != null) {
yield* type._changed(this, { yield* type._changed(this, {
@ -137,6 +154,24 @@ class AbstractTransaction {
}) })
} }
} }
// delete containing lists
if (target.start != null) {
// TODO: don't do it like this .. -.-
yield* this.deleteList(target.start)
yield* this.deleteList(target.id)
}
if (target.map != null) {
for (var name in target.map) {
yield* this.deleteList(target.map[name])
}
// TODO: here to.. (see above)
yield* this.deleteList(target.id)
}
if (target.opContent != null) {
yield* this.deleteOperation(target.opContent)
target.opContent = null
}
}
var left = target.left != null ? yield* this.getOperation(target.left) : null var left = target.left != null ? yield* this.getOperation(target.left) : null
this.store.addToGarbageCollector(target, left) this.store.addToGarbageCollector(target, left)
@ -182,10 +217,12 @@ class AbstractTransaction {
// if op exists, then clean that mess up.. // if op exists, then clean that mess up..
var o = yield* this.getOperation(id) var o = yield* this.getOperation(id)
if (o != null) { if (o != null) {
/*
if (!o.deleted) { if (!o.deleted) {
yield* this.deleteOperation(id) yield* this.deleteOperation(id)
o = yield* this.getOperation(id) o = yield* this.getOperation(id)
} }
*/
// remove gc'd op from the left op, if it exists // remove gc'd op from the left op, if it exists
if (o.left != null) { if (o.left != null) {
@ -233,6 +270,7 @@ class AbstractTransaction {
yield* this.setOperation(right) yield* this.setOperation(right)
} }
if (o.parent != null) {
// remove gc'd op from parent, if it exists // remove gc'd op from parent, if it exists
var parent = yield* this.getOperation(o.parent) var parent = yield* this.getOperation(o.parent)
var setParent = false // whether to save parent to the os var setParent = false // whether to save parent to the os
@ -249,7 +287,9 @@ class AbstractTransaction {
if (setParent) { if (setParent) {
yield* this.setOperation(parent) yield* this.setOperation(parent)
} }
yield* this.removeOperation(o.id) // actually remove it from the os }
// finally remove it from the os
yield* this.removeOperation(o.id)
} }
} }
} }

View File

@ -203,12 +203,11 @@ Y.Memory = (function () {
for (var i in deletions) { for (var i in deletions) {
var del = deletions[i] var del = deletions[i]
var id = [del[0], del[1]] var id = [del[0], del[1]]
// always try to delete..
yield* this.deleteOperation(id)
if (del[2]) { if (del[2]) {
// gc // gc
yield* this.garbageCollectOperation(id) yield* this.garbageCollectOperation(id)
} else {
// delete
yield* this.deleteOperation(id)
} }
} }
} }

View File

@ -194,12 +194,20 @@ var Struct = {
yield* this.setOperation(right) yield* this.setOperation(right)
} }
// notify parent // update parents .map/start/end properties
if (op.parentSub != null) { if (op.parentSub != null) {
if (left == null) { if (left == null) {
parent.map[op.parentSub] = op.id parent.map[op.parentSub] = op.id
yield* this.setOperation(parent) yield* this.setOperation(parent)
} }
// is a child of a map struct.
// Then also make sure that only the most left element is not deleted
if (op.right != null) {
yield* this.deleteOperation(op.right, true)
}
if (op.left != null) {
yield* this.deleteOperation(op.id, true)
}
} else { } else {
if (right == null || left == null) { if (right == null || left == null) {
if (right == null) { if (right == null) {