relu-core
Version:
512 lines (463 loc) • 13.7 kB
JavaScript
var slice = Array.prototype.slice;
function ActionsList() {
this.x = undefined;
this.onChange = function() {};
}
module.exports = ActionsList;
ActionsList.prototype.update = function(path, newValue) {
this.x = ActionsList._makeNested(this.x, path, ActionsList._makeUpdate, newValue);
this.onChange();
};
ActionsList.prototype.modify = function(path, fn) {
this.x = ActionsList._makeNested(this.x, path, ActionsList._makeModify, fn);
this.onChange();
};
ActionsList.prototype.add = function(path, index, newItem) {
this.x = ActionsList._makeNested(this.x, path, ActionsList._makeAdd, index, newItem);
this.onChange();
};
ActionsList.prototype.addBefore = function(path, index, newItem) {
this.x = ActionsList._makeNested(this.x, path, ActionsList._makeAddBefore, index, newItem);
this.onChange();
};
ActionsList.prototype.remove = function(path, index) {
this.x = ActionsList._makeNested(this.x, path, ActionsList._makeRemove, index);
this.onChange();
};
ActionsList.prototype.removeBefore = function(path, index) {
this.x = ActionsList._makeNested(this.x, path, ActionsList._makeRemoveBefore, index);
this.onChange();
};
ActionsList.prototype.splice = function(path, index, count) {
var args = slice.call(arguments, 3);
this.x = ActionsList._makeNested(this.x, path, ActionsList._makeSplice, [index, count], args);
this.onChange();
};
ActionsList.prototype.spliceBefore = function(path, index, count) {
var args = slice.call(arguments, 3);
this.x = ActionsList._makeNested(this.x, path, ActionsList._makeSpliceBefore, [index, count], args);
this.onChange();
};
ActionsList.prototype.apply = function(def, initialState) {
var x = this.x;
this.x = undefined;
ActionsList._apply(x, def, initialState);
};
ActionsList.prototype.reset = function() {
this.x = undefined;
};
ActionsList.prototype.atPath = function(path) {
return new DelegatedActionsList(this, path);
};
ActionsList.prototype.atPaths = function(paths) {
return new MultiActionsList(paths.map(function(p) {
return new DelegatedActionsList(this, p);
}, this));
};
/****************************************************************/
function DelegatedActionsList(parent, path) {
this.parent = parent;
this.path = path;
}
DelegatedActionsList.prototype.update = function(path, newValue) {
this.parent.update(this.path.concat(path), newValue);
};
DelegatedActionsList.prototype.modify = function(path, fn) {
this.parent.modify(this.path.concat(path), fn);
};
DelegatedActionsList.prototype.add = function(path, index, newItem) {
this.parent.add(this.path.concat(path), index, newItem);
};
DelegatedActionsList.prototype.addBefore = function(path, index, newItem) {
this.parent.addBefore(this.path.concat(path), index, newItem);
};
DelegatedActionsList.prototype.remove = function(path, index) {
this.parent.remove(this.path.concat(path), index);
};
DelegatedActionsList.prototype.removeBefore = function(path, index) {
this.parent.removeBefore(this.path.concat(path), index);
};
DelegatedActionsList.prototype.splice = function(path, index, count) {
this.parent.splice.apply(this.parent, [this.path.concat(path)].concat(slice.call(arguments, 1)));
};
DelegatedActionsList.prototype.spliceBefore = function(path, index, count) {
this.parent.spliceBefore.apply(this.parent, [this.path.concat(path)].concat(slice.call(arguments, 1)));
};
DelegatedActionsList.prototype.atPath = function(path) {
return this.parent.atPath(this.path.concat(path));
};
DelegatedActionsList.prototype.atPaths = function(paths) {
return new MultiActionsList(paths.map(function(p) {
return this.atPath(p);
}, this));
};
/****************************************************************/
function MultiActionsList(parents) {
this.parents = parents;
};
MultiActionsList.prototype.update = function(path, newValue) {
this.parents.forEach(function(p) {
p.update(path, newValue);
});
};
MultiActionsList.prototype.modify = function(path, fn) {
this.parents.forEach(function(p) {
p.modify(path, fn);
});
};
MultiActionsList.prototype.add = function(path, index, newItem) {
this.parents.forEach(function(p) {
p.add(path, index, newItem);
});
};
MultiActionsList.prototype.addBefore = function(path, index, newItem) {
this.parents.forEach(function(p) {
p.addBefore(path, index, newItem);
});
};
MultiActionsList.prototype.remove = function(path, index) {
this.parents.forEach(function(p) {
p.remove(path, index);
});
};
MultiActionsList.prototype.removeBefore = function(path, index) {
this.parents.forEach(function(p) {
p.removeBefore(path, index);
});
};
MultiActionsList.prototype.splice = function(path, index, count) {
var args = slice.call(arguments, 1);
this.parents.forEach(function(p) {
p.splice.apply(p, [path].concat(args));
});
};
MultiActionsList.prototype.spliceBefore = function(path, index, count) {
var args = slice.call(arguments, 1);
this.parents.forEach(function(p) {
p.spliceBefore.apply(p, [path].concat(args));
});
};
MultiActionsList.prototype.atPath = function(path) {
return new MultiActionsList(this.parents.map(function(p) {
return p.atPath(path);
}));
};
MultiActionsList.prototype.atPaths = function(paths) {
return new MultiActionsList(paths.map(function(p) {
return this.atPath(p);
}, this));
};
/****************************************************************/
ActionsList._makeUpdate = function _makeUpdate(x, newValue) {
return {t: 2, v: newValue};
};
ActionsList._makeModify = function _makeModify(x, fn) {
if(!x) return {t: 4, m: [fn]};
if(Array.isArray(x) || x.t === 3) throw new Error("Cannot modify non-primitive value:" + x);
if(x.t === 4) {
x.m.push(fn);
return x;
} else {
x.v = fn(x.v);
return x;
}
};
ActionsList._makeAdd = function _makeAdd(x, idx, item) {
if(!x) return [{t: 1, i: idx, v: item}];
if(!Array.isArray(x)) {
if(x.t !== 2) throw new Error("Cannot add items to primitive value: " + x);
if(!Array.isArray(x.v)) throw new Error("Cannot add items to non-array value: " + x);
if(idx === Infinity)
x.v.push(item);
else
x.v.splice(idx, 0, item);
return x;
}
if(idx === Infinity) {
x.push({t: 1, i: Infinity, v: item});
return x;
}
for(var i = x.length-1; i >= 0; i--) {
var action = x[i];
var actionIdx = action.i;
// Switch action with this action
if(action.t !== 1) {
x.splice(i+1, 0, {t: 1, i: idx, v: item});
return x;
}
if(actionIdx < idx) {
x.splice(i+1, 0, {t: 1, i: idx, v: item});
return x;
}
action.i++;
}
x.unshift({t: 1, i: idx, v: item});
return x;
};
ActionsList._makeAddBefore = function _makeAddBefore(x, idx, item) {
if(!x) return [{t: 1, i: idx, v: item}];
if(!Array.isArray(x)) {
if(x.t !== 2) throw new Error("Cannot add items to primitive value: " + x);
return x;
}
if(idx === Infinity) {
x.push({t: 1, i: Infinity, v: item});
return x;
}
for(var i = 0; i < x.length; i++) {
var action = x[i];
var actionIdx = action.i;
if(action.t === 1) {
if(actionIdx >= idx) {
action.i++
} else {
idx++;
}
} else if(action.t === -1) {
if(actionIdx < idx) {
idx--;
}
}
}
for(var i = 0; i < x.length; i++) {
var action = x[i];
var actionIdx = action.i;
if(action.t === 1 && actionIdx > idx) {
x.splice(i, 0, {t: 1, i: idx, v: item});
return x;
}
}
x.push({t: 1, i: idx, v: item});
return x;
};
ActionsList._makeRemove = function _makeRemove(x, idx) {
if(!x) return [{t: -1, i: idx}];
if(!Array.isArray(x)) {
if(x.t !== 2) throw new Error("Cannot remove items from primitive value: " + x);
if(!Array.isArray(x.v)) throw new Error("Cannot remove items from non-array value: " + x);
if(idx === Infinity)
x.v.pop();
else
x.v.splice(idx, 1);
return x;
}
if(idx === Infinity) {
x.unshift({t:-1, i:Infinity});
return x;
}
// fix all actions to represent the new action
for(var i = x.length-1; i >= 0; i--) {
var action = x[i];
var actionIdx = action.i;
// Switch action with this action
if(action.t === 1) { // switching with add action
if(actionIdx === idx) { // if the idx is the same
// add + remove = nothing
x.splice(i, 1);
return x;
}
if(actionIdx > idx) { // if the remove is before the add
// "add" move one left
action.i--;
} else {
// "remove" move one left
idx--;
}
} else if(action.t === 0) { // switching with nested action
if(actionIdx === idx) { // if the idx is the same
// nested + remove = remove
x.splice(i, 1);
} else if(actionIdx > idx) {
// "nested" move one left
action.i--;
}
} else { // switching with remove action
// store the action sorted: left is highest index
if(actionIdx > idx) { // if in correct order
// insert it here
x.splice(i+1, 0, {t:-1, i:idx});
return x;
}
if(actionIdx <= idx) {
idx++;
}
}
}
x.unshift({t:-1, i:idx});
return x;
};
ActionsList._makeRemoveBefore = function _makeRemoveBefore(x, idx) {
if(!x) return [{t: -1, i: idx}];
if(!Array.isArray(x)) {
if(x.t !== 2) throw new Error("Cannot remove items from primitive value: " + x);
return x;
}
if(idx === Infinity) {
x.unshift({t:-1, i:Infinity});
return x;
}
// fix all actions to represent the new action
for(var i = x.length-1; i >= 0; i--) {
var action = x[i];
var actionIdx = action.i;
// Switch action with this action
if(action.t === -1) { // switching with remove action
// store the action sorted: left is highest index
if(actionIdx === idx) {
// already removed...
return x;
} else if(actionIdx > idx) { // if in correct order
// insert it here
x.splice(i+1, 0, {t: -1, i: idx});
for(i++; i < x.length; i++) {
var action = x[i];
var actionIdx = action.i;
if(action.t === -1) {
if(actionIdx < idx) idx--;
} else {
if(actionIdx > idx) action.i--;
}
}
return x;
}
}
}
x.unshift({t: -1, i: idx});
for(i = 1; i < x.length; i++) {
var action = x[i];
var actionIdx = action.i;
if(action.t === -1) {
if(actionIdx < idx) idx--;
} else {
if(actionIdx > idx) action.i--;
}
}
return x;
};
ActionsList._makeSplice = function _makeSplice(x, args, items) {
var idx = args[0];
var count = args[1];
for(var i = count - 1; i >= 0; i--)
x = ActionsList._makeRemove(x, i + idx);
for(i = 0; i < items.length; i++)
x = ActionsList._makeAdd(x, i + idx, items[i]);
return x;
};
ActionsList._makeSpliceBefore = function _makeSplice(x, args, items) {
var idx = args[0];
var count = args[1];
for(var i = 0; i < count; i++)
x = ActionsList._makeRemoveBefore(x, i + idx);
for(i = items.length-1; i >= 0; i--)
x = ActionsList._makeAddBefore(x, idx, items[i]);
return x;
};
ActionsList._makeNested = function _makeNested(x, path, fn, arg1, arg2) {
if(!path || path.length === 0) {
return fn(x, arg1, arg2);
}
if(typeof path[0] === "number") {
if(!x) return [{t: 0, i: path[0], a: applyNext()}];
if(!Array.isArray(x)) {
if(x.t !== 2 || !Array.isArray(x.v)) throw new Error("Cannot access item of non-array value");
var value = x.v[path[0]];
x.v[path[0]] = applyNext({t: 2, v: value}).v;
return x;
}
for(var i = x.length - 1; i >= 0; i--) {
var action = x[i];
if(action.t === -1) {
x.splice(i + 1, 0, {t: 0, i: path[0], a: applyNext()});
return x;
} else if(action.t === 0) {
if(action.i === path[0]) {
action.a = applyNext(action.a);
return x;
} else if(action.i < path[0]) {
x.splice(i + 1, 0, {t: 0, i: path[0], a: applyNext()});
return x;
}
} else if(action.t === 1) {
if(action.i === path[0]) {
action.v = applyNext({t: 2, v: action.v}).v;
return x;
}
if(action.i < path[0]) {
path[0]--;
}
}
}
x.unshift({t: 0, i: path[0], a: applyNext()});
return x;
} else {
var prop = path[0];
if(!x) {
var p = {};
p[prop] = applyNext();
return {t: 3, p: p};
}
if(Array.isArray(x)) {
throw new Error("Cannot access property of array value");
}
if(x.t === 2) {
var value = x.v[prop];
x.v[prop] = applyNext({t: 2, v: value}).v;
return x;
} else if(x.t === 3) {
x.p[prop] = applyNext(x.p[prop]);
return x;
} else throw new Error("Cannot access property of primitive value");
}
function applyNext(x) {
path.shift();
if(path.length > 0) {
return _makeNested(x, path, fn, arg1, arg2);
} else {
return fn(x, arg1, arg2);
}
}
};
ActionsList._apply = function _apply(x, def) {
if(x) {
if(Array.isArray(x)) {
for(var i = 0; i < x.length; i++) {
var action = x[i];
switch(action.t) {
case -1:
def.remove(action.i);
break;
case 0:
def.enter(action.i, function(def) {
_apply(action.a, def);
});
break;
case 1:
def.add(action.i, action.v);
break;
default:
throw new Error("Unexpected action type " + action.t);
}
}
def.done();
return;
}
switch(x.t) {
case 2:
def.update(x.v);
break;
case 3:
Object.keys(x.p).forEach(function(p) {
def.enter(p, function(def) {
_apply(x.p[p], def);
});
});
break;
case 4:
def.modify(x.m);
break;
default:
throw new Error("Unexpected action type " + x.t);
}
}
def.done();
};