cleaned up, removed Replaceable operation, changed Operation specific events method (put it into the Manager types), created Composition type

This commit is contained in:
DadaMonad 2015-04-06 21:35:04 +00:00
parent b24de43fe2
commit bb0bfcc5c8
13 changed files with 846 additions and 674 deletions

View File

@ -86,7 +86,8 @@ var y = new Y(connector);
* Delete a property
* .observe(observer)
* The `observer` is called whenever something on this object changes. Throws *add*, *update*, and *delete* events
* .unobserve(f)
* Delete an observer
# A note on intention preservation
When users create/update/delete the same property concurrently, only one change will prevail. Changes on different properties do not conflict with each other.
@ -115,7 +116,7 @@ But it would be really nice to get some feedback :)
* The History Buffer should be able to store operations in a database
## Get help
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/rwth-acis/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/y-js/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Please report _any_ issues to the [Github issue page](https://github.com/rwth-acis/yjs/issues)! I try to fix them very soon, if possible.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -299,7 +299,7 @@ module.exports = function() {
}
Insert.__super__.applyDelete.call(this, garbagecollect);
if (callLater) {
this.callOperationSpecificDeleteEvents(o);
this.parent.callOperationSpecificDeleteEvents(this, o);
}
if ((_ref = this.prev_cl) != null ? _ref.isDeleted() : void 0) {
return this.prev_cl.applyDelete();
@ -408,44 +408,11 @@ module.exports = function() {
}
this.setParent(this.prev_cl.getParent());
Insert.__super__.execute.apply(this, arguments);
this.callOperationSpecificInsertEvents();
this.parent.callOperationSpecificInsertEvents(this);
return this;
}
};
Insert.prototype.callOperationSpecificInsertEvents = function() {
var getContentType, _ref;
getContentType = function(content) {
if (content instanceof ops.Operation) {
return content.getCustomType();
} else {
return content;
}
};
return (_ref = this.parent) != null ? _ref.callEvent([
{
type: "insert",
position: this.getPosition(),
object: this.parent.getCustomType(),
changedBy: this.uid.creator,
value: getContentType(this.content)
}
]) : void 0;
};
Insert.prototype.callOperationSpecificDeleteEvents = function(o) {
return this.parent.callEvent([
{
type: "delete",
position: this.getPosition(),
object: this.parent.getCustomType(),
length: 1,
changedBy: o.uid.creator,
oldValue: this.val()
}
]);
};
Insert.prototype.getPosition = function() {
var position, prev;
position = 0;

View File

@ -308,6 +308,39 @@ module.exports = function() {
return this;
};
ListManager.prototype.callOperationSpecificInsertEvents = function(op) {
var getContentType;
getContentType = function(content) {
if (content instanceof ops.Operation) {
return content.getCustomType();
} else {
return content;
}
};
return this.callEvent([
{
type: "insert",
position: op.getPosition(),
object: this.getCustomType(),
changedBy: op.uid.creator,
value: getContentType(op.content)
}
]);
};
ListManager.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {
return this.callEvent([
{
type: "delete",
position: op.getPosition(),
object: this.getCustomType(),
length: 1,
changedBy: del_op.uid.creator,
oldValue: op.val()
}
]);
};
return ListManager;
})(ops.Operation);
@ -360,6 +393,45 @@ module.exports = function() {
return void 0;
};
ReplaceManager.prototype.callOperationSpecificInsertEvents = function(op) {
var old_value;
if (op.next_cl.type === "Delimiter" && op.prev_cl.type !== "Delimiter") {
if (!op.is_deleted) {
old_value = op.prev_cl.val();
this.callEventDecorator([
{
type: "update",
changedBy: op.uid.creator,
oldValue: old_value
}
]);
}
op.prev_cl.applyDelete();
} else if (op.next_cl.type !== "Delimiter") {
op.applyDelete();
} else {
this.callEventDecorator([
{
type: "add",
changedBy: op.uid.creator
}
]);
}
return void 0;
};
ReplaceManager.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {
if (op.next_cl.type === "Delimiter") {
return this.callEventDecorator([
{
type: "delete",
changedBy: del_op.uid.creator,
oldValue: op.val()
}
]);
}
};
ReplaceManager.prototype.replace = function(content, replaceable_uid) {
var o, relp;
o = this.getLastOperation();
@ -394,55 +466,35 @@ module.exports = function() {
return ReplaceManager;
})(ops.ListManager);
ops.Composition = (function(_super) {
__extends(Composition, _super);
function Composition(custom_type, _at_composition_value, uid, beginning, end) {
this.composition_value = _at_composition_value;
if (this.composition_value == null) {
throw new Error("You must instanziate ops.Composition with a composition_value!");
}
Composition.__super__.constructor.call(this, custom_type, null, null, uid, beginning, end);
}
Composition.prototype.type = "Composition";
Composition.prototype.val = function() {
return this.composition_value;
};
return Composition;
})(ops.ReplaceManager);
ops.Replaceable = (function(_super) {
__extends(Replaceable, _super);
function Replaceable(custom_type, content, parent, uid, prev, next, origin) {
this.saveOperation('parent', parent);
Replaceable.__super__.constructor.call(this, custom_type, content, uid, prev, next, origin);
Replaceable.__super__.constructor.call(this, custom_type, content, uid, prev, next, origin, parent);
}
Replaceable.prototype.type = "Replaceable";
Replaceable.prototype.callOperationSpecificInsertEvents = function() {
var old_value;
if (this.next_cl.type === "Delimiter" && this.prev_cl.type !== "Delimiter") {
if (!this.is_deleted) {
old_value = this.prev_cl.val();
this.parent.callEventDecorator([
{
type: "update",
changedBy: this.uid.creator,
oldValue: old_value
}
]);
}
this.prev_cl.applyDelete();
} else if (this.next_cl.type !== "Delimiter") {
this.applyDelete();
} else {
this.parent.callEventDecorator([
{
type: "add",
changedBy: this.uid.creator
}
]);
}
return void 0;
};
Replaceable.prototype.callOperationSpecificDeleteEvents = function(o) {
if (this.next_cl.type === "Delimiter") {
return this.parent.callEventDecorator([
{
type: "delete",
changedBy: o.uid.creator,
oldValue: this.val()
}
]);
}
};
Replaceable.prototype._encode = function(json) {
if (json == null) {
json = {};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -303,7 +303,7 @@ module.exports = ()->
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
# @param {Operation} next_cl The successor of this operation in the complete-list (cl)
#
constructor: (custom_type, content, uid, prev_cl, next_cl, origin, parent)->
constructor: (custom_type, content, parent, uid, prev_cl, next_cl, origin)->
# see encode to see, why we are doing it this way
if content is undefined
# nop
@ -345,7 +345,7 @@ module.exports = ()->
garbagecollect = true
super garbagecollect
if callLater
@callOperationSpecificDeleteEvents(o)
@parent.callOperationSpecificDeleteEvents(this, o)
if @prev_cl?.isDeleted()
# garbage collect prev_cl
@prev_cl.applyDelete()
@ -466,33 +466,9 @@ module.exports = ()->
@setParent @prev_cl.getParent() # do Insertions always have a parent?
super # notify the execution_listeners
@callOperationSpecificInsertEvents()
@parent.callOperationSpecificInsertEvents(this)
@
callOperationSpecificInsertEvents: ()->
getContentType = (content)->
if content instanceof ops.Operation
content.getCustomType()
else
content
@parent?.callEvent [
type: "insert"
position: @getPosition()
object: @parent.getCustomType()
changedBy: @uid.creator
value: getContentType @content
]
callOperationSpecificDeleteEvents: (o)->
@parent.callEvent [
type: "delete"
position: @getPosition()
object: @parent.getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
length: 1
changedBy: o.uid.creator
oldValue: @val()
]
#
# Compute the position of this operation.
#
@ -538,7 +514,7 @@ module.exports = ()->
} = json
if typeof content is "string"
content = JSON.parse(content)
new this null, content, uid, prev, next, origin, parent
new this null, content, parent, uid, prev, next, origin
#
# @nodoc

View File

@ -109,16 +109,18 @@ module.exports = ()->
type: "ListManager"
applyDelete: ()->
o = @end
o = @beginning
while o?
o.applyDelete()
o = o.prev_cl
o = o.next_cl
super()
cleanup: ()->
super()
toJson: (transform_to_value = false)->
val = @val()
for i, o in val
@ -226,12 +228,12 @@ module.exports = ()->
# TODO: always expect an array as content. Then you can combine this with the other option (else)
if contents instanceof ops.Operation
(new ops.Insert null, content, undefined, left, right).execute()
(new ops.Insert null, content, undefined, undefined, left, right).execute()
else
for c in contents
if c? and c._name? and c._getModel?
c = c._getModel(@custom_types, @operations)
tmp = (new ops.Insert null, c, undefined, left, right).execute()
tmp = (new ops.Insert null, c, undefined, undefined, left, right).execute()
left = tmp
@
@ -266,6 +268,31 @@ module.exports = ()->
delete_ops.push d._encode()
@
callOperationSpecificInsertEvents: (op)->
getContentType = (content)->
if content instanceof ops.Operation
content.getCustomType()
else
content
@callEvent [
type: "insert"
position: op.getPosition()
object: @getCustomType()
changedBy: op.uid.creator
value: getContentType op.content
]
callOperationSpecificDeleteEvents: (op, del_op)->
@callEvent [
type: "delete"
position: op.getPosition()
object: @getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
length: 1
changedBy: del_op.uid.creator
oldValue: op.val()
]
ops.ListManager.parse = (json)->
{
'uid' : uid
@ -273,6 +300,79 @@ module.exports = ()->
} = json
new this(custom_type, uid)
class ops.Composition extends ops.ListManager
constructor: (custom_type, @composition_value, uid, composition_ref)->
super custom_type, null, null, uid
if composition_ref
@saveOperation 'composition_ref', composition_ref
else
@composition_ref = @beginning
type: "Composition"
val: ()->
@composition_value
#
# This is called, when the Insert-operation was successfully executed.
#
callOperationSpecificInsertEvents: (op)->
if @composition_ref.next_cl is op
@custom_type._apply op.content
else
o = @end.prev_cl
while o isnt op
@custom_type._undo o.content
o = o.next_cl
while o isnt @end
@custom_type._apply o.content
o = o.next_cl
@composition_ref = @end.prev_cl
@callEvent [
type: "update"
changedBy: op.uid.creator
newValue: @val()
]
callOperationSpecificDeleteEvents: (op, del_op)->
return
#
# Create a new Delta
# - inserts new Content at the end of the list
# - updates the composition_value
# - updates the composition_ref
#
# @param delta The delta that is applied to the composition_value
#
applyDelta: (delta)->
(new ops.Insert null, content, @, null, @end.prev_cl, @end).execute()
undefined
#
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: (json = {})->
json.composition_value = JSON.stringify @composition_value
json.composition_ref = @composition_ref.getUid()
super json
ops.Composition.parse = (json)->
{
'uid' : uid
'custom_type': custom_type
'composition_value' : composition_value
'composition_ref' : composition_ref
} = json
new this(custom_type, JSON.parse(composition_value), uid, composition_ref)
#
# @nodoc
# Adds support for replace. The ReplaceManager manages Replaceable operations.
@ -289,23 +389,13 @@ module.exports = ()->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Delimiter} beginning Reference or Object.
# @param {Delimiter} end Reference or Object.
constructor: (custom_type, @event_properties, @event_this, uid, beginning, end)->
constructor: (custom_type, @event_properties, @event_this, uid)->
if not @event_properties['object']?
@event_properties['object'] = @event_this.getCustomType()
super custom_type, uid, beginning, end
super custom_type, uid
type: "ReplaceManager"
applyDelete: ()->
o = @beginning
while o?
o.applyDelete()
o = o.next_cl
super()
cleanup: ()->
super()
#
# This doesn't throw the same events as the ListManager. Therefore, the
# Replaceables also not throw the same events.
@ -321,6 +411,42 @@ module.exports = ()->
@event_this.callEvent events
undefined
#
# This is called, when the Insert-type was successfully executed.
# TODO: consider doing this in a more consistent manner. This could also be
# done with execute. But currently, there are no specital Insert-ops for ListManager.
#
callOperationSpecificInsertEvents: (op)->
if op.next_cl.type is "Delimiter" and op.prev_cl.type isnt "Delimiter"
# this replaces another Replaceable
if not op.is_deleted # When this is received from the HB, this could already be deleted!
old_value = op.prev_cl.val()
@callEventDecorator [
type: "update"
changedBy: op.uid.creator
oldValue: old_value
]
op.prev_cl.applyDelete()
else if op.next_cl.type isnt "Delimiter"
# This won't be recognized by the user, because another
# concurrent operation is set as the current value of the RM
op.applyDelete()
else # prev _and_ next are Delimiters. This is the first created Replaceable in the RM
@callEventDecorator [
type: "add"
changedBy: op.uid.creator
]
undefined
callOperationSpecificDeleteEvents: (op, del_op)->
if op.next_cl.type is "Delimiter"
@callEventDecorator [
type: "delete"
changedBy: del_op.uid.creator
oldValue: op.val()
]
#
# Replace the existing word with a new word.
#
@ -329,7 +455,7 @@ module.exports = ()->
#
replace: (content, replaceable_uid)->
o = @getLastOperation()
relp = (new ops.Replaceable null, content, @, replaceable_uid, o, o.next_cl).execute()
relp = (new ops.Insert null, content, @, replaceable_uid, o, o.next_cl).execute()
# TODO: delete repl (for debugging)
undefined
@ -350,86 +476,6 @@ module.exports = ()->
# throw new Error "Replace Manager doesn't contain anything."
o.val?() # ? - for the case that (currently) the RM does not contain anything (then o is a Delimiter)
#
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: (json = {})->
json.beginning = @beginning.getUid()
json.end = @end.getUid()
super json
#
# @nodoc
# The ReplaceManager manages Replaceables.
# @see ReplaceManager
#
class ops.Replaceable extends ops.Insert
#
# @param {Operation} content The value that this Replaceable holds.
# @param {ReplaceManager} parent Used to replace this Replaceable with another one.
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
#
constructor: (custom_type, content, parent, uid, prev, next, origin)->
@saveOperation 'parent', parent
super custom_type, content, uid, prev, next, origin # Parent is already saved by Replaceable
type: "Replaceable"
#
# This is called, when the Insert-type was successfully executed.
# TODO: consider doing this in a more consistent manner. This could also be
# done with execute. But currently, there are no specital Insert-ops for ListManager.
#
callOperationSpecificInsertEvents: ()->
if @next_cl.type is "Delimiter" and @prev_cl.type isnt "Delimiter"
# this replaces another Replaceable
if not @is_deleted # When this is received from the HB, this could already be deleted!
old_value = @prev_cl.val()
@parent.callEventDecorator [
type: "update"
changedBy: @uid.creator
oldValue: old_value
]
@prev_cl.applyDelete()
else if @next_cl.type isnt "Delimiter"
# This won't be recognized by the user, because another
# concurrent operation is set as the current value of the RM
@applyDelete()
else # prev _and_ next are Delimiters. This is the first created Replaceable in the RM
@parent.callEventDecorator [
type: "add"
changedBy: @uid.creator
]
undefined
callOperationSpecificDeleteEvents: (o)->
if @next_cl.type is "Delimiter"
@parent.callEventDecorator [
type: "delete"
changedBy: o.uid.creator
oldValue: @val()
]
#
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: (json = {})->
super json
ops.Replaceable.parse = (json)->
{
'content' : content
'parent' : parent
'uid' : uid
'prev': prev
'next': next
'origin' : origin
'custom_type' : custom_type
} = json
if typeof content is "string"
content = JSON.parse(content)
new this(custom_type, content, parent, uid, prev, next, origin)
basic_ops

File diff suppressed because one or more lines are too long

2
y.js

File diff suppressed because one or more lines are too long