mirror of
https://github.com/nasa/openmct.git
synced 2025-03-28 06:38:40 +00:00
* trying this again * wip * wip * wip * one annotation per tag * fixed too many events firing * syncing works mostly * syncing properly across existing annotations * search with multiple tags * resolve conflicts between different tag editors * resolve conflicts * fix annotation tests * combine search results * modify tests * prevent infinite loop creating annotation * add modified and deleted * revert index checkin * change to standard couch deleted flag * revert throwing of error * resolve conflict issues * work in progress, but load annotations once from notebook * works to add * attempt 1 * wip * last changes * listening works, though still getting conflicts * rename to annotationLastCreated * use local mutable again * works with new tags syncing * listeners wont fire if modification is null * clean up code * fixed local search * cleaned up log messages * remove on more log * add e2e test for network traffic * lint * change to use good old for each * add some local variables for clarity * Update src/api/objects/ObjectAPI.js Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com> * Update src/api/objects/ObjectAPI.js Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com> * Update src/plugins/notebook/components/Notebook.vue Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com> * press enter for last entry * add test explanation of numbers * fix spread typo * add some nice jsdoc * throw some errors * use really small integer instead * remove unneeded binding * make method public and jsdoc it * use mutables * clean up tests * clean up tests * use aria labels for tests * add some proper tsdoc to annotation api * add undelete test Co-authored-by: John Hill <john.c.hill@nasa.gov> Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
270 lines
8.9 KiB
JavaScript
270 lines
8.9 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2014-2022, 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(
|
|
[
|
|
'EventEmitter',
|
|
'lodash'
|
|
],
|
|
function (
|
|
EventEmitter,
|
|
_
|
|
) {
|
|
/**
|
|
* Manages selection state for Open MCT
|
|
* @private
|
|
*/
|
|
function Selection(openmct) {
|
|
EventEmitter.call(this);
|
|
|
|
this.openmct = openmct;
|
|
this.selected = [];
|
|
}
|
|
|
|
Selection.prototype = Object.create(EventEmitter.prototype);
|
|
|
|
/**
|
|
* Gets the selected object.
|
|
* @public
|
|
*/
|
|
Selection.prototype.get = function () {
|
|
return this.selected;
|
|
};
|
|
|
|
/**
|
|
* Selects the selectable object and emits the 'change' event.
|
|
*
|
|
* @param {object} selectable an object with element and context properties
|
|
* @param {Boolean} isMultiSelectEvent flag indication shift key is pressed or not
|
|
* @private
|
|
*/
|
|
Selection.prototype.select = function (selectable, isMultiSelectEvent) {
|
|
if (!Array.isArray(selectable)) {
|
|
selectable = [selectable];
|
|
}
|
|
|
|
let multiSelect = isMultiSelectEvent
|
|
&& this.parentSupportsMultiSelect(selectable)
|
|
&& this.isPeer(selectable)
|
|
&& !this.selectionContainsParent(selectable);
|
|
|
|
if (multiSelect) {
|
|
this.handleMultiSelect(selectable);
|
|
} else {
|
|
this.handleSingleSelect(selectable);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.handleMultiSelect = function (selectable) {
|
|
if (this.elementSelected(selectable)) {
|
|
this.remove(selectable);
|
|
} else {
|
|
this.addSelectionAttributes(selectable);
|
|
this.selected.push(selectable);
|
|
}
|
|
|
|
this.emit('change', this.selected);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.handleSingleSelect = function (selectable) {
|
|
if (!_.isEqual([selectable], this.selected)) {
|
|
this.setSelectionStyles(selectable);
|
|
this.selected = [selectable];
|
|
|
|
this.emit('change', this.selected);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.elementSelected = function (selectable) {
|
|
return this.selected.some(selectionPath => _.isEqual(selectionPath, selectable));
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.remove = function (selectable) {
|
|
this.selected = this.selected.filter(selectionPath => !_.isEqual(selectionPath, selectable));
|
|
|
|
if (this.selected.length === 0) {
|
|
this.removeSelectionAttributes(selectable);
|
|
selectable[1].element.click(); // Select the parent if there is no selection.
|
|
} else {
|
|
this.removeSelectionAttributes(selectable, true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.setSelectionStyles = function (selectable) {
|
|
this.selected.forEach(selectionPath => this.removeSelectionAttributes(selectionPath));
|
|
this.addSelectionAttributes(selectable);
|
|
};
|
|
|
|
Selection.prototype.removeSelectionAttributes = function (selectionPath, keepParentStyle) {
|
|
if (selectionPath[0] && selectionPath[0].element) {
|
|
selectionPath[0].element.removeAttribute('s-selected');
|
|
}
|
|
|
|
if (selectionPath[1] && selectionPath[1].element && !keepParentStyle) {
|
|
selectionPath[1].element.removeAttribute('s-selected-parent');
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Adds selection attributes to the selected element and its parent.
|
|
* @private
|
|
*/
|
|
Selection.prototype.addSelectionAttributes = function (selectable) {
|
|
if (selectable[0] && selectable[0].element) {
|
|
selectable[0].element.setAttribute('s-selected', "");
|
|
}
|
|
|
|
if (selectable[1] && selectable[1].element) {
|
|
selectable[1].element.setAttribute('s-selected-parent', "");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.parentSupportsMultiSelect = function (selectable) {
|
|
return selectable[1] && selectable[1].context.supportsMultiSelect;
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.selectionContainsParent = function (selectable) {
|
|
return this.selected.some(selectionPath => _.isEqual(selectionPath[0], selectable[1]));
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.isPeer = function (selectable) {
|
|
return this.selected.some(selectionPath => _.isEqual(selectionPath[1], selectable[1]));
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.isSelectable = function (element) {
|
|
if (!element) {
|
|
return false;
|
|
}
|
|
|
|
return Boolean(element.closest('[data-selectable]'));
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.capture = function (selectable) {
|
|
let capturingContainsSelectable = this.capturing && this.capturing.includes(selectable);
|
|
|
|
if (!this.capturing || capturingContainsSelectable) {
|
|
this.capturing = [];
|
|
}
|
|
|
|
this.capturing.push(selectable);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Selection.prototype.selectCapture = function (selectable, event) {
|
|
if (!this.capturing) {
|
|
return;
|
|
}
|
|
|
|
let reversedCapturing = this.capturing.reverse();
|
|
delete this.capturing;
|
|
this.select(reversedCapturing, event.shiftKey);
|
|
};
|
|
|
|
/**
|
|
* Attaches the click handlers to the element.
|
|
*
|
|
* @param element an html element
|
|
* @param context object which defines item or other arbitrary properties.
|
|
* e.g. {
|
|
* item: domainObject,
|
|
* elementProxy: element,
|
|
* controller: fixedController
|
|
* }
|
|
* @param select a flag to select the element if true
|
|
* @returns a function that removes the click handlers from the element
|
|
* @public
|
|
*/
|
|
Selection.prototype.selectable = function (element, context, select) {
|
|
if (!this.isSelectable(element)) {
|
|
return () => {};
|
|
}
|
|
|
|
let selectable = {
|
|
context: context,
|
|
element: element
|
|
};
|
|
|
|
const capture = this.capture.bind(this, selectable);
|
|
const selectCapture = this.selectCapture.bind(this, selectable);
|
|
let removeMutable = false;
|
|
|
|
element.addEventListener('click', capture, true);
|
|
element.addEventListener('click', selectCapture);
|
|
|
|
if (context.item && context.item.isMutable !== true) {
|
|
removeMutable = true;
|
|
context.item = this.openmct.objects.toMutable(context.item);
|
|
}
|
|
|
|
if (select) {
|
|
if (typeof select === 'object') {
|
|
element.dispatchEvent(select);
|
|
} else if (typeof select === 'boolean') {
|
|
element.click();
|
|
}
|
|
}
|
|
|
|
return (function () {
|
|
element.removeEventListener('click', capture, true);
|
|
element.removeEventListener('click', selectCapture);
|
|
|
|
if (context.item !== undefined && context.item.isMutable && removeMutable === true) {
|
|
this.openmct.objects.destroyMutable(context.item);
|
|
}
|
|
}).bind(this);
|
|
};
|
|
|
|
return Selection;
|
|
});
|