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 * Delete a property
* .observe(observer) * .observe(observer)
* The `observer` is called whenever something on this object changes. Throws *add*, *update*, and *delete* events * 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 # 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. 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 * The History Buffer should be able to store operations in a database
## Get help ## 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. 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); Insert.__super__.applyDelete.call(this, garbagecollect);
if (callLater) { if (callLater) {
this.callOperationSpecificDeleteEvents(o); this.parent.callOperationSpecificDeleteEvents(this, o);
} }
if ((_ref = this.prev_cl) != null ? _ref.isDeleted() : void 0) { if ((_ref = this.prev_cl) != null ? _ref.isDeleted() : void 0) {
return this.prev_cl.applyDelete(); return this.prev_cl.applyDelete();
@ -408,44 +408,11 @@ module.exports = function() {
} }
this.setParent(this.prev_cl.getParent()); this.setParent(this.prev_cl.getParent());
Insert.__super__.execute.apply(this, arguments); Insert.__super__.execute.apply(this, arguments);
this.callOperationSpecificInsertEvents(); this.parent.callOperationSpecificInsertEvents(this);
return 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() { Insert.prototype.getPosition = function() {
var position, prev; var position, prev;
position = 0; position = 0;

View File

@ -308,6 +308,39 @@ module.exports = function() {
return this; 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; return ListManager;
})(ops.Operation); })(ops.Operation);
@ -360,6 +393,45 @@ module.exports = function() {
return void 0; 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) { ReplaceManager.prototype.replace = function(content, replaceable_uid) {
var o, relp; var o, relp;
o = this.getLastOperation(); o = this.getLastOperation();
@ -394,55 +466,35 @@ module.exports = function() {
return ReplaceManager; return ReplaceManager;
})(ops.ListManager); })(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) { ops.Replaceable = (function(_super) {
__extends(Replaceable, _super); __extends(Replaceable, _super);
function Replaceable(custom_type, content, parent, uid, prev, next, origin) { 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, parent);
Replaceable.__super__.constructor.call(this, custom_type, content, uid, prev, next, origin);
} }
Replaceable.prototype.type = "Replaceable"; 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) { Replaceable.prototype._encode = function(json) {
if (json == null) { if (json == null) {
json = {}; 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} 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) # @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 # see encode to see, why we are doing it this way
if content is undefined if content is undefined
# nop # nop
@ -345,7 +345,7 @@ module.exports = ()->
garbagecollect = true garbagecollect = true
super garbagecollect super garbagecollect
if callLater if callLater
@callOperationSpecificDeleteEvents(o) @parent.callOperationSpecificDeleteEvents(this, o)
if @prev_cl?.isDeleted() if @prev_cl?.isDeleted()
# garbage collect prev_cl # garbage collect prev_cl
@prev_cl.applyDelete() @prev_cl.applyDelete()
@ -466,33 +466,9 @@ module.exports = ()->
@setParent @prev_cl.getParent() # do Insertions always have a parent? @setParent @prev_cl.getParent() # do Insertions always have a parent?
super # notify the execution_listeners 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. # Compute the position of this operation.
# #
@ -538,7 +514,7 @@ module.exports = ()->
} = json } = json
if typeof content is "string" if typeof content is "string"
content = JSON.parse(content) content = JSON.parse(content)
new this null, content, uid, prev, next, origin, parent new this null, content, parent, uid, prev, next, origin
# #
# @nodoc # @nodoc

View File

@ -109,16 +109,18 @@ module.exports = ()->
type: "ListManager" type: "ListManager"
applyDelete: ()-> applyDelete: ()->
o = @end o = @beginning
while o? while o?
o.applyDelete() o.applyDelete()
o = o.prev_cl o = o.next_cl
super() super()
cleanup: ()-> cleanup: ()->
super() super()
toJson: (transform_to_value = false)-> toJson: (transform_to_value = false)->
val = @val() val = @val()
for i, o in 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) # TODO: always expect an array as content. Then you can combine this with the other option (else)
if contents instanceof ops.Operation 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 else
for c in contents for c in contents
if c? and c._name? and c._getModel? if c? and c._name? and c._getModel?
c = c._getModel(@custom_types, @operations) 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 left = tmp
@ @
@ -266,6 +268,31 @@ module.exports = ()->
delete_ops.push d._encode() 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)-> ops.ListManager.parse = (json)->
{ {
'uid' : uid 'uid' : uid
@ -273,6 +300,79 @@ module.exports = ()->
} = json } = json
new this(custom_type, uid) 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 # @nodoc
# Adds support for replace. The ReplaceManager manages Replaceable operations. # 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 {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Delimiter} beginning Reference or Object. # @param {Delimiter} beginning Reference or Object.
# @param {Delimiter} end 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']? if not @event_properties['object']?
@event_properties['object'] = @event_this.getCustomType() @event_properties['object'] = @event_this.getCustomType()
super custom_type, uid, beginning, end super custom_type, uid
type: "ReplaceManager" 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 # This doesn't throw the same events as the ListManager. Therefore, the
# Replaceables also not throw the same events. # Replaceables also not throw the same events.
@ -321,6 +411,42 @@ module.exports = ()->
@event_this.callEvent events @event_this.callEvent events
undefined 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. # Replace the existing word with a new word.
# #
@ -329,7 +455,7 @@ module.exports = ()->
# #
replace: (content, replaceable_uid)-> replace: (content, replaceable_uid)->
o = @getLastOperation() 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) # TODO: delete repl (for debugging)
undefined undefined
@ -350,86 +476,6 @@ module.exports = ()->
# throw new Error "Replace Manager doesn't contain anything." # 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) 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 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