implemented disconnect/reconnect in webrtc connector. adapted the example gc also collects child elements (needs improvements)
This commit is contained in:
parent
51e20fb9c7
commit
d6e1cd42a2
@ -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>
|
||||||
|
@ -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'
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
13
gulpfile.js
13
gulpfile.js
@ -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')
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user