188 lines
5.6 KiB
JavaScript
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)
|
|
}
|
|
})
|
|
})()
|