Reorder api (#2316)

* Added 'reorder' function to composition API

* Re-implemented reordering in Elements

* Make LAD table editable

* Remove test spec focus

* Fixing bugs with event listeners

* Clean up listeners properly in Elements pool

* Fixed race condition on drag-and-drop to initiate edit

* Implement reordering in LAD tables
This commit is contained in:
Andrew Henry 2019-03-19 10:31:56 -07:00 committed by Deep Tailor
parent 23efef4469
commit 6116351dad
9 changed files with 194 additions and 53 deletions

View File

@ -21,7 +21,11 @@ define([
topicService.and.returnValue(mutationTopic); topicService.and.returnValue(mutationTopic);
publicAPI = {}; publicAPI = {};
publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [ publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [
'get' 'get',
'mutate'
]);
publicAPI.objects.eventEmitter = jasmine.createSpyObj('eventemitter', [
'on'
]); ]);
publicAPI.objects.get.and.callFake(function (identifier) { publicAPI.objects.get.and.callFake(function (identifier) {
return Promise.resolve({identifier: identifier}); return Promise.resolve({identifier: identifier});
@ -52,6 +56,14 @@ define([
{ {
namespace: 'test', namespace: 'test',
key: 'a' key: 'a'
},
{
namespace: 'test',
key: 'b'
},
{
namespace: 'test',
key: 'c'
} }
] ]
}; };
@ -68,12 +80,39 @@ define([
composition.on('add', listener); composition.on('add', listener);
return composition.load().then(function () { return composition.load().then(function () {
expect(listener.calls.count()).toBe(1); expect(listener.calls.count()).toBe(3);
expect(listener).toHaveBeenCalledWith({ expect(listener).toHaveBeenCalledWith({
identifier: {namespace: 'test', key: 'a'} identifier: {namespace: 'test', key: 'a'}
}); });
}); });
}); });
describe('supports reordering of composition', function () {
var listener;
beforeEach(function () {
listener = jasmine.createSpy('reorderListener');
composition.on('reorder', listener);
return composition.load();
});
it('', function () {
composition.reorder(1, 0);
let newComposition =
publicAPI.objects.mutate.calls.mostRecent().args[2];
expect(listener).toHaveBeenCalledWith(1, 0);
expect(newComposition[0].key).toEqual('b');
expect(newComposition[1].key).toEqual('a');
});
it('', function () {
composition.reorder(0, 2);
let newComposition =
publicAPI.objects.mutate.calls.mostRecent().args[2];
expect(listener).toHaveBeenCalledWith(0, 2);
expect(newComposition[0].key).toEqual('c');
expect(newComposition[2].key).toEqual('a');
})
});
// TODO: Implement add/removal in new default provider. // TODO: Implement add/removal in new default provider.
xit('synchronizes changes between instances', function () { xit('synchronizes changes between instances', function () {

View File

@ -56,7 +56,8 @@ define([
this.listeners = { this.listeners = {
add: [], add: [],
remove: [], remove: [],
load: [] load: [],
reorder: []
}; };
this.onProviderAdd = this.onProviderAdd.bind(this); this.onProviderAdd = this.onProviderAdd.bind(this);
this.onProviderRemove = this.onProviderRemove.bind(this); this.onProviderRemove = this.onProviderRemove.bind(this);
@ -91,6 +92,13 @@ define([
this.onProviderRemove, this.onProviderRemove,
this this
); );
} if (event === 'reorder') {
this.provider.on(
this.domainObject,
'reorder',
this.onProviderReorder,
this
)
} }
} }
@ -141,6 +149,13 @@ define([
this.onProviderRemove, this.onProviderRemove,
this this
); );
} else if (event === 'reorder') {
this.provider.off(
this.domainObject,
'reorder',
this.onProviderReorder,
this
);
} }
} }
} }
@ -209,6 +224,33 @@ define([
} }
}; };
/**
* Reorder the domain objects in this composition.
*
* A call to [load]{@link module:openmct.CompositionCollection#load}
* must have resolved before using this method.
*
* @param {number} oldIndex
* @param {number} newIndex
* @memberof module:openmct.CompositionCollection#
* @name remove
*/
CompositionCollection.prototype.reorder = function (oldIndex, newIndex, skipMutate) {
if (!skipMutate) {
this.provider.reorder(this.domainObject, oldIndex, newIndex);
} else {
this.emit('reorder', oldIndex, newIndex);
}
};
/**
* Handle reorder from provider.
* @private
*/
CompositionCollection.prototype.onProviderReorder = function (oldIndex, newIndex) {
this.reorder(oldIndex, newIndex, true);
};
/** /**
* Handle adds from provider. * Handle adds from provider.
* @private * @private
@ -232,12 +274,12 @@ define([
* Emit events. * Emit events.
* @private * @private
*/ */
CompositionCollection.prototype.emit = function (event, payload) { CompositionCollection.prototype.emit = function (event, ...payload) {
this.listeners[event].forEach(function (l) { this.listeners[event].forEach(function (l) {
if (l.context) { if (l.context) {
l.callback.call(l.context, payload); l.callback.apply(l.context, payload);
} else { } else {
l.callback(payload); l.callback(...payload);
} }
}); });
}; };

View File

@ -126,6 +126,7 @@ define([
objectListeners = this.listeningTo[keyString] = { objectListeners = this.listeningTo[keyString] = {
add: [], add: [],
remove: [], remove: [],
reorder: [],
composition: [].slice.apply(domainObject.composition) composition: [].slice.apply(domainObject.composition)
}; };
} }
@ -160,7 +161,7 @@ define([
}); });
objectListeners[event].splice(index, 1); objectListeners[event].splice(index, 1);
if (!objectListeners.add.length && !objectListeners.remove.length) { if (!objectListeners.add.length && !objectListeners.remove.length && !objectListeners.reorder.length) {
delete this.listeningTo[keyString]; delete this.listeningTo[keyString];
} }
}; };
@ -203,6 +204,30 @@ define([
// TODO: this needs to be synchronized via mutation // TODO: this needs to be synchronized via mutation
}; };
DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {
let newComposition = domainObject.composition.slice();
newComposition[newIndex] = domainObject.composition[oldIndex];
newComposition[oldIndex] = domainObject.composition[newIndex];
this.publicAPI.objects.mutate(domainObject, 'composition', newComposition);
let id = objectUtils.makeKeyString(domainObject.identifier);
var listeners = this.listeningTo[id];
if (!listeners) {
return;
}
listeners.reorder.forEach(notify);
function notify(listener) {
if (listener.context) {
listener.callback.call(listener.context, oldIndex, newIndex);
} else {
listener.callback(oldIndex, newIndex);
}
}
};
/** /**
* Listens on general mutation topic, using injector to fetch to avoid * Listens on general mutation topic, using injector to fetch to avoid
* circular dependencies. * circular dependencies.

View File

@ -35,6 +35,9 @@ define([
canView: function (domainObject) { canView: function (domainObject) {
return domainObject.type === 'LadTableSet'; return domainObject.type === 'LadTableSet';
}, },
canEdit: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
view: function (domainObject) { view: function (domainObject) {
let component; let component;

View File

@ -35,6 +35,9 @@ define([
canView: function (domainObject) { canView: function (domainObject) {
return domainObject.type === 'LadTable'; return domainObject.type === 'LadTable';
}, },
canEdit: function (domainObject) {
return domainObject.type === 'LadTable';
},
view: function (domainObject) { view: function (domainObject) {
let component; let component;

View File

@ -65,17 +65,24 @@ export default {
let index = _.findIndex(this.items, (item) => this.openmct.objects.makeKeyString(identifier) === item.key); let index = _.findIndex(this.items, (item) => this.openmct.objects.makeKeyString(identifier) === item.key);
this.items.splice(index, 1); this.items.splice(index, 1);
},
reorder(oldIndex, newIndex) {
let objectAtOldIndex = this.items[oldIndex];
this.$set(this.items, oldIndex, this.items[newIndex]);
this.$set(this.items, newIndex, objectAtOldIndex);
} }
}, },
mounted() { mounted() {
this.composition = this.openmct.composition.get(this.domainObject); this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addItem); this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem); this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.reorder);
this.composition.load(); this.composition.load();
}, },
destroyed() { destroyed() {
this.composition.off('add', this.addItem); this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem); this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.reorder);
} }
} }
</script> </script>

View File

@ -93,6 +93,12 @@
this.primaryTelemetryObjects.splice(index,1); this.primaryTelemetryObjects.splice(index,1);
primary = undefined; primary = undefined;
}, },
reorderPrimary(oldIndex, newIndex) {
let objectAtOldIndex = this.primaryTelemetryObjects[oldIndex];
this.$set(this.primaryTelemetryObjects, oldIndex, this.primaryTelemetryObjects[newIndex]);
this.$set(this.primaryTelemetryObjects, newIndex, objectAtOldIndex);
},
addSecondary(primary) { addSecondary(primary) {
return (domainObject) => { return (domainObject) => {
let secondary = {}; let secondary = {};
@ -120,11 +126,13 @@
this.composition = this.openmct.composition.get(this.domainObject); this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addPrimary); this.composition.on('add', this.addPrimary);
this.composition.on('remove', this.removePrimary); this.composition.on('remove', this.removePrimary);
this.composition.on('reorder', this.reorderPrimary);
this.composition.load(); this.composition.load();
}, },
destroyed() { destroyed() {
this.composition.off('add', this.addPrimary); this.composition.off('add', this.addPrimary);
this.composition.off('remove', this.removePrimary); this.composition.off('remove', this.removePrimary);
this.composition.off('reorder', this.reorderPrimary);
this.compositions.forEach(c => { this.compositions.forEach(c => {
c.composition.off('add', c.addCallback); c.composition.off('add', c.addCallback);
c.composition.off('remove', c.removeCallback); c.composition.off('remove', c.removeCallback);

View File

@ -83,85 +83,89 @@ export default {
this.showSelection(selection); this.showSelection(selection);
} }
this.openmct.selection.on('change', this.showSelection); this.openmct.selection.on('change', this.showSelection);
this.openmct.editor.on('isEditing', (isEditing)=>{ this.openmct.editor.on('isEditing', this.setEditState);
this.isEditing = isEditing;
this.showSelection(this.openmct.selection.get());
});
}, },
methods: { methods: {
setEditState(isEditing) {
this.isEditing = isEditing;
this.showSelection(this.openmct.selection.get());
},
showSelection(selection) { showSelection(selection) {
this.elements = []; this.elements = [];
this.elementsCache = []; this.elementsCache = {};
this.listeners = [];
this.parentObject = selection[0].context.item; this.parentObject = selection[0].context.item;
if (this.mutationUnobserver) { if (this.mutationUnobserver) {
this.mutationUnobserver(); this.mutationUnobserver();
} }
if (this.compositionUnlistener) {
this.compositionUnlistener();
}
if (this.parentObject) { if (this.parentObject) {
this.mutationUnobserver = this.openmct.objects.observe(this.parentObject, '*', (updatedModel) => { this.mutationUnobserver = this.openmct.objects.observe(this.parentObject, '*', (updatedModel) => {
this.parentObject = updatedModel; this.parentObject = updatedModel;
this.refreshComposition();
}); });
this.refreshComposition(); this.composition = this.openmct.composition.get(this.parentObject);
if (this.composition) {
this.composition.load();
this.composition.on('add', this.addElement);
this.composition.on('remove', this.removeElement);
this.composition.on('reorder', this.reorderElements);
this.compositionUnlistener = () => {
this.composition.off('add', this.addElement);
this.composition.off('remove', this.removeElement);
this.composition.off('reorder', this.reorderElements);
delete this.compositionUnlistener;
}
}
} }
}, },
refreshComposition() { addElement(element) {
let composition = this.openmct.composition.get(this.parentObject); let keyString = this.openmct.objects.makeKeyString(element.identifier);
this.elementsCache[keyString] =
if (composition){ JSON.parse(JSON.stringify(element));
composition.load().then(this.setElements); this.applySearch(this.currentSearch);
}
}, },
setElements(elements) { reorderElements() {
this.elementsCache = elements.map((element)=>JSON.parse(JSON.stringify(element))) this.applySearch(this.currentSearch);
},
removeElement(identifier) {
let keyString = this.openmct.objects.makeKeyString(element.identifier);
delete this.elementsCache[keyString];
this.applySearch(this.currentSearch); this.applySearch(this.currentSearch);
}, },
applySearch(input) { applySearch(input) {
this.currentSearch = input; this.currentSearch = input;
this.elements = this.elementsCache.filter((element) => { this.elements = this.parentObject.composition.map((id) =>
return element.name.toLowerCase().search( this.elementsCache[this.openmct.objects.makeKeyString(id)]
this.currentSearch) !== -1; ).filter((element) => {
return element !== undefined &&
element.name.toLowerCase().search(this.currentSearch) !== -1;
}); });
}, },
addObject(child){
this.elementsCache.push(child);
this.applySearch(this.currentSearch);
},
removeObject(childId){
this.elementsCache = this.elementsCache.filter((element) => !matches(element, childId));
this.applySearch(this.currentSearch);
function matches(elementA, elementBId) {
return elementA.identifier.namespace === elementBId.namespace &&
elementA.identifier.key === elementBId.key;
}
},
allowDrop(event) { allowDrop(event) {
event.preventDefault(); event.preventDefault();
}, },
moveTo(moveToIndex) { moveTo(moveToIndex) {
console.log('dropped'); this.composition.reorder(this.moveFromIndex, moveToIndex);
let composition = this.parentObject.composition;
let moveFromId = composition[this.moveFromIndex];
let deleteIndex = this.moveFromIndex;
if (moveToIndex < this.moveFromIndex) {
composition.splice(deleteIndex, 1);
composition.splice(moveToIndex, 0, moveFromId);
} else {
composition.splice(deleteIndex, 1);
composition.splice(moveToIndex, 0, moveFromId);
}
this.openmct.objects.mutate(this.parentObject, 'composition', composition);
}, },
moveFrom(index){ moveFrom(index){
this.moveFromIndex = index; this.moveFromIndex = index;
} }
}, },
destroyed() { destroyed() {
this.openmct.editor.off('isEditing', this.setEditState);
this.openmct.selection.off('change', this.showSelection); this.openmct.selection.off('change', this.showSelection);
if (this.mutationUnobserver) {
this.mutationUnobserver();
}
if (this.compositionUnlistener) {
this.compositionUnlistener();
}
} }
} }
</script> </script>

View File

@ -7,6 +7,7 @@ define([
return function install(openmct) { return function install(openmct) {
let navigateCall = 0; let navigateCall = 0;
let browseObject; let browseObject;
let unobserve = undefined;
function viewObject(object, viewProvider) { function viewObject(object, viewProvider) {
openmct.layout.$refs.browseObject.show(object, viewProvider.key, true); openmct.layout.$refs.browseObject.show(object, viewProvider.key, true);
@ -18,6 +19,11 @@ define([
navigateCall++; navigateCall++;
let currentNavigation = navigateCall; let currentNavigation = navigateCall;
if (unobserve) {
unobserve();
unobserve = undefined;
}
if (!Array.isArray(path)) { if (!Array.isArray(path)) {
path = path.split('/'); path = path.split('/');
} }
@ -37,6 +43,10 @@ define([
// API for this. // API for this.
openmct.router.path = objects.reverse(); openmct.router.path = objects.reverse();
unobserve = this.openmct.objects.observe(openmct.router.path[0], '*', (newObject) => {
openmct.router.path[0] = newObject;
});
openmct.layout.$refs.browseBar.domainObject = navigatedObject; openmct.layout.$refs.browseBar.domainObject = navigatedObject;
browseObject = navigatedObject; browseObject = navigatedObject;
if (!navigatedObject) { if (!navigatedObject) {