yjs/src/Types/Array.js
2015-07-25 23:58:57 +00:00

188 lines
5.6 KiB
JavaScript

/* global EventHandler, Y, CustomType, Struct */
;(function () {
class YArray {
constructor (os, _model, idArray, valArray) {
this.os = os
this._model = _model
// Array of all the operation id's
this.idArray = idArray
// Array of all the values
this.valArray = valArray
this.eventHandler = new EventHandler(ops => {
var userEvents = []
for (var i in ops) {
var op = ops[i]
if (op.struct === 'Insert') {
let pos
// we check op.left only!,
// because op.right might not be defined when this is called
if (op.left === null) {
pos = 0
} else {
var sid = JSON.stringify(op.left)
pos = this.idArray.indexOf(sid) + 1
if (pos <= 0) {
throw new Error('Unexpected operation!')
}
}
this.idArray.splice(pos, 0, JSON.stringify(op.id))
this.valArray.splice(pos, 0, op.content)
userEvents.push({
type: 'insert',
object: this,
index: pos,
length: 1
})
} else if (op.struct === 'Delete') {
let pos = this.idArray.indexOf(JSON.stringify(op.target))
this.idArray.splice(pos, 1)
this.valArray.splice(pos, 1)
userEvents.push({
type: 'delete',
object: this,
index: pos,
length: 1
})
} else {
throw new Error('Unexpected struct!')
}
}
this.eventHandler.callUserEventListeners(userEvents)
})
}
get length () {
return this.idArray.length
}
get (pos) {
if (pos == null || typeof pos !== 'number') {
throw new Error('pos must be a number!')
}
return this.valArray[pos]
}
toArray () {
return this.valArray.slice()
}
insert (pos, contents) {
if (typeof pos !== 'number') {
throw new Error('pos must be a number!')
}
if (!(contents instanceof Array)) {
throw new Error('contents must be an Array of objects!')
}
if (contents.length === 0) {
return
}
if (pos > this.idArray.length || pos < 0) {
throw new Error('This position exceeds the range of the array!')
}
var mostLeft = pos === 0 ? null : JSON.parse(this.idArray[pos - 1])
var ops = []
var prevId = mostLeft
for (var i = 0; i < contents.length; i++) {
var op = {
left: prevId,
origin: prevId,
// right: mostRight,
// NOTE: I intentionally do not define right here, because it could be deleted
// at the time of creating this operation, and is therefore not defined in idArray
parent: this._model,
content: contents[i],
struct: 'Insert',
id: this.os.getNextOpId()
}
ops.push(op)
prevId = op.id
}
var eventHandler = this.eventHandler
eventHandler.awaitAndPrematurelyCall(ops)
this.os.requestTransaction(function *() {
// now we can set the right reference.
var mostRight
if (mostLeft != null) {
mostRight = (yield* this.getOperation(mostLeft)).right
} else {
mostRight = (yield* this.getOperation(ops[0].parent)).start
}
for (var j in ops) {
ops[j].right = mostRight
}
yield* this.applyCreatedOperations(ops)
eventHandler.awaitedLastInserts(ops.length)
})
}
delete (pos, length = 1) {
if (typeof length !== 'number') {
throw new Error('pos must be a number!')
}
if (typeof pos !== 'number') {
throw new Error('pos must be a number!')
}
if (pos + length > this.idArray.length || pos < 0 || length < 0) {
throw new Error('The deletion range exceeds the range of the array!')
}
if (length === 0) {
return
}
var eventHandler = this.eventHandler
var newLeft = pos > 0 ? JSON.parse(this.idArray[pos - 1]) : null
var dels = []
for (var i = 0; i < length; i++) {
dels.push({
target: JSON.parse(this.idArray[pos + i]),
struct: 'Delete'
})
}
eventHandler.awaitAndPrematurelyCall(dels)
this.os.requestTransaction(function *() {
yield* this.applyCreatedOperations(dels)
eventHandler.awaitedLastDeletes(dels.length, newLeft)
})
}
observe (f) {
this.eventHandler.addUserEventListener(f)
}
* _changed (transaction, op) {
if (!op.deleted) {
if (op.struct === 'Insert') {
var l = op.left
var left
while (l != null) {
left = yield* transaction.getOperation(l)
if (!left.deleted) {
break
}
l = left.left
}
op.left = l
}
this.eventHandler.receivedOp(op)
}
}
}
Y.Array = new CustomType({
class: YArray,
createType: function * YArrayCreator () {
var model = {
start: null,
end: null,
struct: 'List',
type: 'Array',
id: this.store.getNextOpId()
}
yield* this.applyCreatedOperations([model])
return yield* this.createType(model)
},
initType: function * YArrayInitializer (os, model) {
var valArray = []
var idArray = yield* Struct.List.map.call(this, model, function (c) {
valArray.push(c.content)
return JSON.stringify(c.id)
})
return new YArray(os, model.id, idArray, valArray)
}
})
})()