227 lines
9.6 KiB
JavaScript
227 lines
9.6 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2009-2016, United States Government
|
|
* as represented by the Administrator of the National Aeronautics and Space
|
|
* Administration. All rights reserved.
|
|
*
|
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
* Open MCT includes source code licensed under additional open source
|
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
* this source code distribution or the Licensing information page available
|
|
* at runtime from the About dialog for additional information.
|
|
*****************************************************************************/
|
|
|
|
define(
|
|
[],
|
|
function () {
|
|
|
|
/**
|
|
* Handles drop (from drag-and-drop) initiated changes to a swimlane.
|
|
* @constructor
|
|
*/
|
|
function TimelineSwimlaneDropHandler(swimlane) {
|
|
// Utility function; like $q.when, but synchronous (to reduce
|
|
// performance impact when wrapping synchronous values)
|
|
function asPromise(value) {
|
|
return (value && value.then) ? value : {
|
|
then: function (callback) {
|
|
return asPromise(callback(value));
|
|
}
|
|
};
|
|
}
|
|
|
|
// Check if we are in edit mode (also check parents)
|
|
function inEditMode() {
|
|
return swimlane.domainObject.hasCapability('editor') &&
|
|
swimlane.domainObject.getCapability('editor').inEditContext();
|
|
}
|
|
|
|
// Boolean and (for reduce below)
|
|
function or(a, b) {
|
|
return a || b;
|
|
}
|
|
|
|
// Check if pathA entirely contains pathB
|
|
function pathContains(swimlaneToCheck, id) {
|
|
// Check if id at a specific index matches (for map below)
|
|
function matches(pathId) {
|
|
return pathId === id;
|
|
}
|
|
|
|
// Path A contains Path B if it is longer, and all of
|
|
// B's ids match the ids in A.
|
|
return swimlaneToCheck.idPath.map(matches).reduce(or, false);
|
|
}
|
|
|
|
// Check if a swimlane contains a child with the specified id
|
|
function contains(swimlaneToCheck, id) {
|
|
// Check if a child swimlane has a matching domain object id
|
|
function matches(child) {
|
|
return child.domainObject.getId() === id;
|
|
}
|
|
|
|
// Find any one child id that matches this id
|
|
return swimlaneToCheck.children.map(matches).reduce(or, false);
|
|
}
|
|
|
|
// Initiate mutation of a domain object
|
|
function doMutate(domainObject, mutator) {
|
|
return asPromise(
|
|
domainObject.useCapability("mutation", mutator)
|
|
).then(function () {
|
|
// Persist the results of mutation
|
|
var persistence = domainObject.getCapability("persistence");
|
|
if (persistence) {
|
|
// Persist the changes
|
|
persistence.persist();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Check if this swimlane is in a state where a drop-after will
|
|
// act as a drop-into-at-first position (expanded and non-empty)
|
|
function expandedForDropInto() {
|
|
return swimlane.expanded && swimlane.children.length > 0;
|
|
}
|
|
|
|
// Check if the swimlane is ready to accept a drop-into
|
|
// (instead of drop-after)
|
|
function isDropInto() {
|
|
return swimlane.highlight() || expandedForDropInto();
|
|
}
|
|
|
|
function isReorder(targetObject, droppedObject) {
|
|
var droppedContext = droppedObject.getCapability('context'),
|
|
droppedParent =
|
|
droppedContext && droppedContext.getParent(),
|
|
droppedParentId = droppedParent && droppedParent.getId();
|
|
return targetObject.getId() === droppedParentId;
|
|
}
|
|
|
|
// Choose an appropriate composition action for the drag-and-drop
|
|
function chooseAction(targetObject, droppedObject) {
|
|
var actionCapability =
|
|
targetObject.getCapability('action'),
|
|
actionKey = droppedObject.hasCapability('editor') ?
|
|
'move' : 'link';
|
|
|
|
return actionCapability && actionCapability.getActions({
|
|
key: actionKey,
|
|
selectedObject: droppedObject
|
|
})[0];
|
|
}
|
|
|
|
// Choose an index for insertion in a domain object's composition
|
|
function chooseTargetIndex(id, offset, composition) {
|
|
return Math.max(
|
|
Math.min(
|
|
(composition || []).indexOf(id) + offset,
|
|
(composition || []).length
|
|
),
|
|
0
|
|
);
|
|
}
|
|
|
|
// Insert an id into target's composition
|
|
function insert(id, target, indexOffset) {
|
|
var myId = swimlane.domainObject.getId();
|
|
return doMutate(target, function (model) {
|
|
model.composition =
|
|
model.composition.filter(function (compId) {
|
|
return compId !== id;
|
|
});
|
|
model.composition.splice(
|
|
chooseTargetIndex(myId, indexOffset, model.composition),
|
|
0,
|
|
id
|
|
);
|
|
});
|
|
}
|
|
|
|
function canDrop(targetObject, droppedObject) {
|
|
|
|
return isReorder(targetObject, droppedObject) ||
|
|
!!chooseAction(targetObject, droppedObject);
|
|
}
|
|
|
|
function drop(domainObject, targetObject, indexOffset) {
|
|
function changeIndex() {
|
|
var id = domainObject.getId();
|
|
return insert(id, targetObject, indexOffset);
|
|
}
|
|
|
|
return isReorder(targetObject, domainObject) ?
|
|
changeIndex() :
|
|
chooseAction(targetObject, domainObject)
|
|
.perform().then(changeIndex);
|
|
}
|
|
|
|
return {
|
|
/**
|
|
* Check if a drop-into should be allowed for this swimlane,
|
|
* for the provided domain object identifier.
|
|
* @param {string} id identifier for the domain object to be
|
|
* dropped
|
|
* @returns {boolean} true if this should be allowed
|
|
*/
|
|
allowDropIn: function (id, domainObject) {
|
|
return inEditMode() &&
|
|
!pathContains(swimlane, id) &&
|
|
!contains(swimlane, id) &&
|
|
canDrop(swimlane.domainObject, domainObject);
|
|
},
|
|
/**
|
|
* Check if a drop-after should be allowed for this swimlane,
|
|
* for the provided domain object identifier.
|
|
* @param {string} id identifier for the domain object to be
|
|
* dropped
|
|
* @returns {boolean} true if this should be allowed
|
|
*/
|
|
allowDropAfter: function (id, domainObject) {
|
|
var target = expandedForDropInto() ?
|
|
swimlane : swimlane.parent;
|
|
return inEditMode() &&
|
|
target &&
|
|
!pathContains(target, id) &&
|
|
canDrop(target.domainObject, domainObject);
|
|
},
|
|
/**
|
|
* Drop the provided domain object into a timeline. This is
|
|
* provided as a mandatory id, and an optional domain object
|
|
* instance; if the latter is provided, it will be removed
|
|
* from its parent before being added. (This is specifically
|
|
* to support moving Activity objects around within a Timeline.)
|
|
* @param {string} id the identifier for the domain object
|
|
* @param {DomainObject} [domainObject] the object itself
|
|
*/
|
|
drop: function (id, domainObject) {
|
|
// Get the desired drop object, and destination index
|
|
var dropInto = isDropInto(),
|
|
dropTarget = dropInto ?
|
|
swimlane.domainObject :
|
|
swimlane.parent.domainObject,
|
|
dropIndexOffset = (!dropInto) ? 1 :
|
|
(swimlane.expanded && swimlane.highlightBottom()) ?
|
|
Number.NEGATIVE_INFINITY :
|
|
Number.POSITIVE_INFINITY;
|
|
|
|
if (swimlane.highlight() || swimlane.highlightBottom()) {
|
|
return drop(domainObject, dropTarget, dropIndexOffset);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
return TimelineSwimlaneDropHandler;
|
|
}
|
|
);
|