Compare commits
	
		
			49 Commits
		
	
	
		
			notebook-p
			...
			V-R4.4-430
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					166cb55945 | ||
| 
						 | 
					83116257fc | ||
| 
						 | 
					775f1048bc | ||
| 
						 | 
					71a8b377bb | ||
| 
						 | 
					c43d3fcfc9 | ||
| 
						 | 
					4a39ddf425 | ||
| 
						 | 
					83c273b976 | ||
| 
						 | 
					7dd81beb03 | ||
| 
						 | 
					1842d3923c | ||
| 
						 | 
					26838635b6 | ||
| 
						 | 
					11f2c35bb2 | ||
| 
						 | 
					766f48c1ba | ||
| 
						 | 
					da7b93f9b3 | ||
| 
						 | 
					99c095a69f | ||
| 
						 | 
					f885e83505 | ||
| 
						 | 
					928bc4c68a | ||
| 
						 | 
					d5539c7ae4 | ||
| 
						 | 
					c86a104fb6 | ||
| 
						 | 
					46fedc1a30 | ||
| 
						 | 
					3b6ef9b44b | ||
| 
						 | 
					c68edd9b7d | ||
| 
						 | 
					11574b7c40 | ||
| 
						 | 
					abc2cd2413 | ||
| 
						 | 
					5d74882646 | ||
| 
						 | 
					9fe7f230e6 | ||
| 
						 | 
					de4c5b3729 | ||
| 
						 | 
					2a7901914a | ||
| 
						 | 
					73b0fc6f79 | ||
| 
						 | 
					ddef16795c | ||
| 
						 | 
					d188b9a056 | ||
| 
						 | 
					f510f3edd0 | ||
| 
						 | 
					e05b0bb562 | ||
| 
						 | 
					713c5e9fb7 | ||
| 
						 | 
					17bca04560 | ||
| 
						 | 
					e0c5bca47d | ||
| 
						 | 
					cdc7c1af64 | ||
| 
						 | 
					3158baa998 | ||
| 
						 | 
					698508fde4 | ||
| 
						 | 
					68a96989e1 | ||
| 
						 | 
					46a6a43234 | ||
| 
						 | 
					d41fc27b55 | ||
| 
						 | 
					24bb96cc90 | ||
| 
						 | 
					483ee173d6 | ||
| 
						 | 
					8f81a45b9b | ||
| 
						 | 
					666459be87 | ||
| 
						 | 
					d3fe2a6811 | ||
| 
						 | 
					97b37edce4 | ||
| 
						 | 
					dd70bb470f | ||
| 
						 | 
					072bf361de | 
@@ -30,6 +30,7 @@ define([
 | 
			
		||||
    "./src/controllers/CompositeController",
 | 
			
		||||
    "./src/controllers/ColorController",
 | 
			
		||||
    "./src/controllers/DialogButtonController",
 | 
			
		||||
    "./src/controllers/SnapshotPreviewController",
 | 
			
		||||
    "./res/templates/controls/autocomplete.html",
 | 
			
		||||
    "./res/templates/controls/checkbox.html",
 | 
			
		||||
    "./res/templates/controls/datetime.html",
 | 
			
		||||
@@ -43,7 +44,8 @@ define([
 | 
			
		||||
    "./res/templates/controls/menu-button.html",
 | 
			
		||||
    "./res/templates/controls/dialog.html",
 | 
			
		||||
    "./res/templates/controls/radio.html",
 | 
			
		||||
    "./res/templates/controls/file-input.html"
 | 
			
		||||
    "./res/templates/controls/file-input.html",
 | 
			
		||||
    "./res/templates/controls/snap-view.html"
 | 
			
		||||
], function (
 | 
			
		||||
    MCTForm,
 | 
			
		||||
    MCTControl,
 | 
			
		||||
@@ -54,6 +56,7 @@ define([
 | 
			
		||||
    CompositeController,
 | 
			
		||||
    ColorController,
 | 
			
		||||
    DialogButtonController,
 | 
			
		||||
    SnapshotPreviewController,
 | 
			
		||||
    autocompleteTemplate,
 | 
			
		||||
    checkboxTemplate,
 | 
			
		||||
    datetimeTemplate,
 | 
			
		||||
@@ -67,7 +70,8 @@ define([
 | 
			
		||||
    menuButtonTemplate,
 | 
			
		||||
    dialogTemplate,
 | 
			
		||||
    radioTemplate,
 | 
			
		||||
    fileInputTemplate
 | 
			
		||||
    fileInputTemplate,
 | 
			
		||||
    snapViewTemplate
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
@@ -153,6 +157,10 @@ define([
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "file-input",
 | 
			
		||||
                        "template": fileInputTemplate
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "snap-view",
 | 
			
		||||
                        "template": snapViewTemplate
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "controllers": [
 | 
			
		||||
@@ -186,6 +194,14 @@ define([
 | 
			
		||||
                            "$scope",
 | 
			
		||||
                            "dialogService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "SnapshotPreviewController",
 | 
			
		||||
                        "implementation": SnapshotPreviewController,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "$scope",
 | 
			
		||||
                            "openmct"
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "components": [
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								platform/forms/res/templates/controls/snap-view.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								platform/forms/res/templates/controls/snap-view.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2014-2018, 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.
 | 
			
		||||
-->
 | 
			
		||||
<span ng-controller="SnapshotPreviewController" 
 | 
			
		||||
    class='form-control shell'>
 | 
			
		||||
    <span class='field control {{structure.cssClass}}'>
 | 
			
		||||
        <image 
 | 
			
		||||
               class="c-ne__embed__snap-thumb"
 | 
			
		||||
               src="{{imageUrl || structure.src}}"
 | 
			
		||||
               ng-click="previewImage(imageUrl || structure.src)"
 | 
			
		||||
               name="mctControl">
 | 
			
		||||
        </image>
 | 
			
		||||
        <br>
 | 
			
		||||
        <a title="Annotate" class="s-button icon-pencil" ng-click="annotateImage(ngModel, field, imageUrl || structure.src)">
 | 
			
		||||
            <span class="title-label">Annotate</span>
 | 
			
		||||
        </a>
 | 
			
		||||
    </span>
 | 
			
		||||
</span>
 | 
			
		||||
							
								
								
									
										131
									
								
								platform/forms/src/controllers/SnapshotPreviewController.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								platform/forms/src/controllers/SnapshotPreviewController.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2018, 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(
 | 
			
		||||
    [
 | 
			
		||||
        'painterro'
 | 
			
		||||
    ],
 | 
			
		||||
    function (Painterro) {
 | 
			
		||||
 | 
			
		||||
        function SnapshotPreviewController($scope, openmct) {
 | 
			
		||||
 | 
			
		||||
            $scope.previewImage = function (imageUrl) {
 | 
			
		||||
                let imageDiv = document.createElement('div');
 | 
			
		||||
                imageDiv.classList = 'image-main s-image-main';
 | 
			
		||||
                imageDiv.style.backgroundImage = `url(${imageUrl})`;
 | 
			
		||||
 | 
			
		||||
                let previewImageOverlay = openmct.overlays.overlay(
 | 
			
		||||
                    {
 | 
			
		||||
                        element: imageDiv,
 | 
			
		||||
                        size: 'large',
 | 
			
		||||
                        buttons: [
 | 
			
		||||
                            {
 | 
			
		||||
                                label: 'Done',
 | 
			
		||||
                                callback: function () {
 | 
			
		||||
                                    previewImageOverlay.dismiss();
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            $scope.annotateImage = function (ngModel, field, imageUrl) {
 | 
			
		||||
                $scope.imageUrl = imageUrl;
 | 
			
		||||
 | 
			
		||||
                let div = document.createElement('div'),
 | 
			
		||||
                    painterroInstance = {},
 | 
			
		||||
                    save = false;
 | 
			
		||||
 | 
			
		||||
                div.id = 'snap-annotation';
 | 
			
		||||
 | 
			
		||||
                let annotateImageOverlay = openmct.overlays.overlay(
 | 
			
		||||
                    {
 | 
			
		||||
                        element: div,
 | 
			
		||||
                        size: 'large',
 | 
			
		||||
                        buttons: [
 | 
			
		||||
                            {
 | 
			
		||||
                                label: 'Cancel',
 | 
			
		||||
                                callback: function () {
 | 
			
		||||
                                    save = false;
 | 
			
		||||
                                    painterroInstance.save();
 | 
			
		||||
                                    annotateImageOverlay.dismiss();
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                label: 'Save',
 | 
			
		||||
                                callback: function () {
 | 
			
		||||
                                    save = true;
 | 
			
		||||
                                    painterroInstance.save();
 | 
			
		||||
                                    annotateImageOverlay.dismiss();
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                painterroInstance = Painterro({
 | 
			
		||||
                    id: 'snap-annotation',
 | 
			
		||||
                    activeColor: '#ff0000',
 | 
			
		||||
                    activeColorAlpha: 1.0,
 | 
			
		||||
                    activeFillColor: '#fff',
 | 
			
		||||
                    activeFillColorAlpha: 0.0,
 | 
			
		||||
                    backgroundFillColor: '#000',
 | 
			
		||||
                    backgroundFillColorAlpha: 0.0,
 | 
			
		||||
                    defaultFontSize: 16,
 | 
			
		||||
                    defaultLineWidth: 2,
 | 
			
		||||
                    defaultTool: 'ellipse',
 | 
			
		||||
                    hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
 | 
			
		||||
                    translation: {
 | 
			
		||||
                        name: 'en',
 | 
			
		||||
                        strings: {
 | 
			
		||||
                            lineColor: 'Line',
 | 
			
		||||
                            fillColor: 'Fill',
 | 
			
		||||
                            lineWidth: 'Size',
 | 
			
		||||
                            textColor: 'Color',
 | 
			
		||||
                            fontSize: 'Size',
 | 
			
		||||
                            fontStyle: 'Style'
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    saveHandler: function (image, done) {
 | 
			
		||||
                        if (save) {
 | 
			
		||||
                            let url = image.asBlob(),
 | 
			
		||||
                                reader = new window.FileReader();
 | 
			
		||||
 | 
			
		||||
                            reader.readAsDataURL(url);
 | 
			
		||||
                            reader.onloadend = function () {
 | 
			
		||||
                                $scope.imageUrl = reader.result;
 | 
			
		||||
                                ngModel[field] = reader.result;
 | 
			
		||||
                            };
 | 
			
		||||
                        } else {
 | 
			
		||||
                            ngModel.field = imageUrl;
 | 
			
		||||
                            console.warn('You cancelled the annotation!!!');
 | 
			
		||||
                        }
 | 
			
		||||
                        done(true);
 | 
			
		||||
                    }
 | 
			
		||||
                }).show(imageUrl);
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return SnapshotPreviewController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -23,8 +23,8 @@
 | 
			
		||||
import EventEmitter from 'EventEmitter';
 | 
			
		||||
import uuid from 'uuid';
 | 
			
		||||
import TelemetryCriterion from "./criterion/TelemetryCriterion";
 | 
			
		||||
import { TRIGGER } from "./utils/constants";
 | 
			
		||||
import {computeCondition, computeConditionByLimit} from "./utils/evaluator";
 | 
			
		||||
import { evaluateResults } from './utils/evaluator';
 | 
			
		||||
import { getLatestTimestamp } from './utils/time';
 | 
			
		||||
import AllTelemetryCriterion from "./criterion/AllTelemetryCriterion";
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -56,29 +56,39 @@ export default class ConditionClass extends EventEmitter {
 | 
			
		||||
        this.conditionManager = conditionManager;
 | 
			
		||||
        this.id = conditionConfiguration.id;
 | 
			
		||||
        this.criteria = [];
 | 
			
		||||
        this.criteriaResults = {};
 | 
			
		||||
        this.result = undefined;
 | 
			
		||||
        this.latestTimestamp = {};
 | 
			
		||||
 | 
			
		||||
        this.timeSystems = this.openmct.time.getAllTimeSystems();
 | 
			
		||||
        if (conditionConfiguration.configuration.criteria) {
 | 
			
		||||
            this.createCriteria(conditionConfiguration.configuration.criteria);
 | 
			
		||||
        }
 | 
			
		||||
        this.trigger = conditionConfiguration.configuration.trigger;
 | 
			
		||||
        this.conditionManager.on('broadcastTelemetry', this.handleBroadcastTelemetry, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleBroadcastTelemetry(datum) {
 | 
			
		||||
    getResult(datum) {
 | 
			
		||||
        if (!datum || !datum.id) {
 | 
			
		||||
            console.log('no data received');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.criteria.forEach(criterion => {
 | 
			
		||||
            if (criterion.telemetry && (criterion.telemetry === 'all' || criterion.telemetry === 'any')) {
 | 
			
		||||
                criterion.handleSubscription(datum, this.conditionManager.telemetryObjects);
 | 
			
		||||
            if (this.isAnyOrAllTelemetry(criterion)) {
 | 
			
		||||
                criterion.getResult(datum, this.conditionManager.telemetryObjects);
 | 
			
		||||
            } else {
 | 
			
		||||
                criterion.emit(`subscription:${datum.id}`, datum);
 | 
			
		||||
                criterion.getResult(datum);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isAnyOrAllTelemetry(criterion) {
 | 
			
		||||
        return (criterion.telemetry && (criterion.telemetry === 'all' || criterion.telemetry === 'any'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isTelemetryUsed(id) {
 | 
			
		||||
        return this.criteria.some(criterion => {
 | 
			
		||||
            return this.isAnyOrAllTelemetry(criterion) || criterion.telemetryObjectIdAsString === id;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update(conditionConfiguration) {
 | 
			
		||||
@@ -89,7 +99,6 @@ export default class ConditionClass extends EventEmitter {
 | 
			
		||||
    updateTrigger(trigger) {
 | 
			
		||||
        if (this.trigger !== trigger) {
 | 
			
		||||
            this.trigger = trigger;
 | 
			
		||||
            this.handleConditionUpdated();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -133,7 +142,6 @@ export default class ConditionClass extends EventEmitter {
 | 
			
		||||
            criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct);
 | 
			
		||||
        }
 | 
			
		||||
        criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
 | 
			
		||||
        criterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
 | 
			
		||||
        if (!this.criteria) {
 | 
			
		||||
            this.criteria = [];
 | 
			
		||||
        }
 | 
			
		||||
@@ -162,22 +170,11 @@ export default class ConditionClass extends EventEmitter {
 | 
			
		||||
            const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
 | 
			
		||||
            let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
 | 
			
		||||
            newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
 | 
			
		||||
            newCriterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
 | 
			
		||||
 | 
			
		||||
            let criterion = found.item;
 | 
			
		||||
            criterion.unsubscribe();
 | 
			
		||||
            criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
 | 
			
		||||
            criterion.off('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
 | 
			
		||||
            this.criteria.splice(found.index, 1, newCriterion);
 | 
			
		||||
            if (this.criteriaResults[criterion.id] !== undefined) {
 | 
			
		||||
                delete this.criteriaResults[criterion.id];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    removeCriterion(id) {
 | 
			
		||||
        if (this.destroyCriterion(id)) {
 | 
			
		||||
            this.handleConditionUpdated();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -185,15 +182,12 @@ export default class ConditionClass extends EventEmitter {
 | 
			
		||||
        let found = this.findCriterion(id);
 | 
			
		||||
        if (found) {
 | 
			
		||||
            let criterion = found.item;
 | 
			
		||||
            criterion.destroy();
 | 
			
		||||
            // TODO this is passing the wrong args
 | 
			
		||||
            criterion.off('criterionUpdated', (result) => {
 | 
			
		||||
                this.handleCriterionUpdated(id, result);
 | 
			
		||||
            criterion.off('criterionUpdated', (obj) => {
 | 
			
		||||
                this.handleCriterionUpdated(obj);
 | 
			
		||||
            });
 | 
			
		||||
            criterion.destroy();
 | 
			
		||||
            this.criteria.splice(found.index, 1);
 | 
			
		||||
            if (this.criteriaResults[criterion.id] !== undefined) {
 | 
			
		||||
                delete this.criteriaResults[criterion.id];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
@@ -203,59 +197,40 @@ export default class ConditionClass extends EventEmitter {
 | 
			
		||||
        let found = this.findCriterion(criterion.id);
 | 
			
		||||
        if (found) {
 | 
			
		||||
            this.criteria[found.index] = criterion.data;
 | 
			
		||||
            // TODO nothing is listening to this
 | 
			
		||||
            this.emitEvent('conditionUpdated', {
 | 
			
		||||
                trigger: this.trigger,
 | 
			
		||||
                criteria: this.criteria
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateCriteriaResults(eventData) {
 | 
			
		||||
        const id = eventData.id;
 | 
			
		||||
 | 
			
		||||
        if (this.findCriterion(id)) {
 | 
			
		||||
            // The !! here is important to convert undefined to false otherwise the criteriaResults won't get deleted when the criteria is destroyed
 | 
			
		||||
            this.criteriaResults[id] = !!eventData.data.result;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleCriterionResult(eventData) {
 | 
			
		||||
        this.updateCriteriaResults(eventData);
 | 
			
		||||
        this.handleConditionUpdated(eventData.data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    requestLADConditionResult() {
 | 
			
		||||
        const criteriaResults = this.criteria
 | 
			
		||||
            .map(criterion => criterion.requestLAD({telemetryObjects: this.conditionManager.telemetryObjects}));
 | 
			
		||||
        let latestTimestamp;
 | 
			
		||||
        let criteriaResults = {};
 | 
			
		||||
        const criteriaRequests = this.criteria
 | 
			
		||||
            .map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects));
 | 
			
		||||
 | 
			
		||||
        return Promise.all(criteriaResults)
 | 
			
		||||
        return Promise.all(criteriaRequests)
 | 
			
		||||
            .then(results => {
 | 
			
		||||
                results.forEach(result => {
 | 
			
		||||
                    this.updateCriteriaResults(result);
 | 
			
		||||
                    this.latestTimestamp = this.getLatestTimestamp(this.latestTimestamp, result.data)
 | 
			
		||||
                results.forEach(resultObj => {
 | 
			
		||||
                    const { id, data, data: { result } } = resultObj;
 | 
			
		||||
                    if (this.findCriterion(id)) {
 | 
			
		||||
                        criteriaResults[id] = !!result;
 | 
			
		||||
                    }
 | 
			
		||||
                    latestTimestamp = getLatestTimestamp(
 | 
			
		||||
                        latestTimestamp,
 | 
			
		||||
                        data,
 | 
			
		||||
                        this.timeSystems,
 | 
			
		||||
                        this.openmct.time.timeSystem()
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
                this.evaluate();
 | 
			
		||||
 | 
			
		||||
                return {
 | 
			
		||||
                    id: this.id,
 | 
			
		||||
                    data: Object.assign({}, this.latestTimestamp, { result: this.result })
 | 
			
		||||
                }
 | 
			
		||||
                    data: Object.assign(
 | 
			
		||||
                        {},
 | 
			
		||||
                        latestTimestamp,
 | 
			
		||||
                        { result: evaluateResults(Object.values(criteriaResults), this.trigger) }
 | 
			
		||||
                    )
 | 
			
		||||
                };
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getTelemetrySubscriptions() {
 | 
			
		||||
        return this.criteria.map(criterion => criterion.telemetryObjectIdAsString);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleConditionUpdated(datum) {
 | 
			
		||||
        // trigger an updated event so that consumers can react accordingly
 | 
			
		||||
        this.evaluate();
 | 
			
		||||
        this.emitEvent('conditionResultUpdated',
 | 
			
		||||
            Object.assign({}, datum, { result: this.result })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getCriteria() {
 | 
			
		||||
        return this.criteria;
 | 
			
		||||
    }
 | 
			
		||||
@@ -269,41 +244,7 @@ export default class ConditionClass extends EventEmitter {
 | 
			
		||||
        return success;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    evaluate() {
 | 
			
		||||
        if (this.trigger && this.trigger === TRIGGER.XOR) {
 | 
			
		||||
            this.result = computeConditionByLimit(this.criteriaResults, 1);
 | 
			
		||||
        } else if (this.trigger && this.trigger === TRIGGER.NOT) {
 | 
			
		||||
            this.result = computeConditionByLimit(this.criteriaResults, 0);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.result = computeCondition(this.criteriaResults, this.trigger === TRIGGER.ALL);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getLatestTimestamp(current, compare) {
 | 
			
		||||
        const timestamp = Object.assign({}, current);
 | 
			
		||||
 | 
			
		||||
        this.openmct.time.getAllTimeSystems().forEach(timeSystem => {
 | 
			
		||||
            if (!timestamp[timeSystem.key]
 | 
			
		||||
                || compare[timeSystem.key] > timestamp[timeSystem.key]
 | 
			
		||||
            ) {
 | 
			
		||||
                timestamp[timeSystem.key] = compare[timeSystem.key];
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return timestamp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    emitEvent(eventName, data) {
 | 
			
		||||
        this.emit(eventName, {
 | 
			
		||||
            id: this.id,
 | 
			
		||||
            data: data
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
        this.conditionManager.off('broadcastTelemetry', this.handleBroadcastTelemetry, this);
 | 
			
		||||
        if (typeof this.stopObservingForChanges === 'function') {
 | 
			
		||||
            this.stopObservingForChanges();
 | 
			
		||||
        }
 | 
			
		||||
        this.destroyCriteria();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import Condition from "./Condition";
 | 
			
		||||
import { getLatestTimestamp } from './utils/time';
 | 
			
		||||
import uuid from "uuid";
 | 
			
		||||
import EventEmitter from 'EventEmitter';
 | 
			
		||||
 | 
			
		||||
@@ -29,8 +30,7 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
        super();
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.conditionSetDomainObject = conditionSetDomainObject;
 | 
			
		||||
        this.timeAPI = this.openmct.time;
 | 
			
		||||
        this.latestTimestamp = {};
 | 
			
		||||
        this.timeSystems = this.openmct.time.getAllTimeSystems();
 | 
			
		||||
        this.composition = this.openmct.composition.get(conditionSetDomainObject);
 | 
			
		||||
        this.composition.on('add', this.subscribeToTelemetry, this);
 | 
			
		||||
        this.composition.on('remove', this.unsubscribeFromTelemetry, this);
 | 
			
		||||
@@ -55,7 +55,7 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
        this.telemetryObjects[id] = Object.assign({}, endpoint, {telemetryMetaData: this.openmct.telemetry.getMetadata(endpoint).valueMetadatas});
 | 
			
		||||
        this.subscriptions[id] = this.openmct.telemetry.subscribe(
 | 
			
		||||
            endpoint,
 | 
			
		||||
            this.broadcastTelemetry.bind(this, id)
 | 
			
		||||
            this.telemetryReceived.bind(this, endpoint)
 | 
			
		||||
        );
 | 
			
		||||
        this.updateConditionTelemetry();
 | 
			
		||||
    }
 | 
			
		||||
@@ -70,10 +70,10 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
        this.subscriptions[id]();
 | 
			
		||||
        delete this.subscriptions[id];
 | 
			
		||||
        delete this.telemetryObjects[id];
 | 
			
		||||
        this.removeConditionTelemetry();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initialize() {
 | 
			
		||||
        this.conditionResults = {};
 | 
			
		||||
        this.conditionClassCollection = [];
 | 
			
		||||
        if (this.conditionSetDomainObject.configuration.conditionCollection.length) {
 | 
			
		||||
            this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
 | 
			
		||||
@@ -86,6 +86,30 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
        this.conditionClassCollection.forEach((condition) => condition.updateTelemetry());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    removeConditionTelemetry() {
 | 
			
		||||
        let conditionsChanged = false;
 | 
			
		||||
        this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration) => {
 | 
			
		||||
            conditionConfiguration.configuration.criteria.forEach((criterion, index) => {
 | 
			
		||||
                const isAnyAllTelemetry = criterion.telemetry && (criterion.telemetry === 'any' || criterion.telemetry === 'all');
 | 
			
		||||
                if (!isAnyAllTelemetry) {
 | 
			
		||||
                    const found = Object.values(this.telemetryObjects).find((telemetryObject) => {
 | 
			
		||||
                        return this.openmct.objects.areIdsEqual(telemetryObject.identifier, criterion.telemetry);
 | 
			
		||||
                    });
 | 
			
		||||
                    if (!found) {
 | 
			
		||||
                        criterion.telemetry = '';
 | 
			
		||||
                        criterion.metadata = '';
 | 
			
		||||
                        criterion.input = [];
 | 
			
		||||
                        criterion.operation = '';
 | 
			
		||||
                        conditionsChanged = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        if (conditionsChanged) {
 | 
			
		||||
            this.persistConditions();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateCondition(conditionConfiguration, index) {
 | 
			
		||||
        let condition = this.conditionClassCollection[index];
 | 
			
		||||
        condition.update(conditionConfiguration);
 | 
			
		||||
@@ -95,7 +119,6 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    initCondition(conditionConfiguration, index) {
 | 
			
		||||
        let condition = new Condition(conditionConfiguration, this.openmct, this);
 | 
			
		||||
        condition.on('conditionResultUpdated', this.handleConditionResult.bind(this));
 | 
			
		||||
        if (index !== undefined) {
 | 
			
		||||
            this.conditionClassCollection.splice(index + 1, 0, condition);
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -159,22 +182,16 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    removeCondition(index) {
 | 
			
		||||
        let condition = this.conditionClassCollection[index];
 | 
			
		||||
        condition.destroyCriteria();
 | 
			
		||||
        condition.off('conditionResultUpdated', this.handleConditionResult.bind(this));
 | 
			
		||||
        condition.destroy();
 | 
			
		||||
        this.conditionClassCollection.splice(index, 1);
 | 
			
		||||
        this.conditionSetDomainObject.configuration.conditionCollection.splice(index, 1);
 | 
			
		||||
        if (this.conditionResults[condition.id] !== undefined) {
 | 
			
		||||
            delete this.conditionResults[condition.id];
 | 
			
		||||
        }
 | 
			
		||||
        this.persistConditions();
 | 
			
		||||
        this.handleConditionResult();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    findConditionById(id) {
 | 
			
		||||
        return this.conditionClassCollection.find(conditionClass => conditionClass.id === id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //this.$set(this.conditionClassCollection, reorderEvent.newIndex, oldConditions[reorderEvent.oldIndex]);
 | 
			
		||||
    reorderConditions(reorderPlan) {
 | 
			
		||||
        let oldConditions = Array.from(this.conditionSetDomainObject.configuration.conditionCollection);
 | 
			
		||||
        let newCollection = [];
 | 
			
		||||
@@ -191,7 +208,23 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
        let currentCondition = conditionCollection[conditionCollection.length-1];
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < conditionCollection.length - 1; i++) {
 | 
			
		||||
            if (this.conditionResults[conditionCollection[i].id]) {
 | 
			
		||||
            const condition = this.findConditionById(conditionCollection[i].id)
 | 
			
		||||
            if (condition.result) {
 | 
			
		||||
                //first condition to be true wins
 | 
			
		||||
                currentCondition = conditionCollection[i];
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return currentCondition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getCurrentConditionLAD(conditionResults) {
 | 
			
		||||
        const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
 | 
			
		||||
        let currentCondition = conditionCollection[conditionCollection.length-1];
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < conditionCollection.length - 1; i++) {
 | 
			
		||||
            if (conditionResults[conditionCollection[i].id]) {
 | 
			
		||||
                //first condition to be true wins
 | 
			
		||||
                currentCondition = conditionCollection[i];
 | 
			
		||||
                break;
 | 
			
		||||
@@ -200,24 +233,79 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
        return currentCondition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateConditionResults(resultObj) {
 | 
			
		||||
        if (!resultObj) {
 | 
			
		||||
    requestLADConditionSetOutput() {
 | 
			
		||||
        if (!this.conditionClassCollection.length) {
 | 
			
		||||
            return Promise.resolve([]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.compositionLoad.then(() => {
 | 
			
		||||
            let latestTimestamp;
 | 
			
		||||
            let conditionResults = {};
 | 
			
		||||
            const conditionRequests = this.conditionClassCollection
 | 
			
		||||
                .map(condition => condition.requestLADConditionResult());
 | 
			
		||||
 | 
			
		||||
            return Promise.all(conditionRequests)
 | 
			
		||||
                .then((results) => {
 | 
			
		||||
                    results.forEach(resultObj => {
 | 
			
		||||
                        const { id, data, data: { result } } = resultObj;
 | 
			
		||||
                        if (this.findConditionById(id)) {
 | 
			
		||||
                            conditionResults[id] = !!result;
 | 
			
		||||
                        }
 | 
			
		||||
                        latestTimestamp = getLatestTimestamp(
 | 
			
		||||
                            latestTimestamp,
 | 
			
		||||
                            data,
 | 
			
		||||
                            this.timeSystems,
 | 
			
		||||
                            this.openmct.time.timeSystem()
 | 
			
		||||
                        );
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    if (!Object.values(latestTimestamp).some(timeSystem => timeSystem)) {
 | 
			
		||||
                        return [];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    const currentCondition = this.getCurrentConditionLAD(conditionResults);
 | 
			
		||||
                    const currentOutput = Object.assign(
 | 
			
		||||
                        {
 | 
			
		||||
                            output: currentCondition.configuration.output,
 | 
			
		||||
                            id: this.conditionSetDomainObject.identifier,
 | 
			
		||||
                            conditionId: currentCondition.id
 | 
			
		||||
                        },
 | 
			
		||||
                        latestTimestamp
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    return [currentOutput];
 | 
			
		||||
                });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isTelemetryUsed(endpoint) {
 | 
			
		||||
        const id = this.openmct.objects.makeKeyString(endpoint.identifier);
 | 
			
		||||
 | 
			
		||||
        for(const condition of this.conditionClassCollection) {
 | 
			
		||||
            if (condition.isTelemetryUsed(id)) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    telemetryReceived(endpoint, datum) {
 | 
			
		||||
        if (!this.isTelemetryUsed(endpoint)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const id = resultObj.id;
 | 
			
		||||
        const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
 | 
			
		||||
        const timeSystemKey = this.openmct.time.timeSystem().key;
 | 
			
		||||
        let timestamp = {};
 | 
			
		||||
        timestamp[timeSystemKey] = normalizedDatum[timeSystemKey];
 | 
			
		||||
 | 
			
		||||
        if (this.findConditionById(id)) {
 | 
			
		||||
            this.conditionResults[id] = resultObj.data.result;
 | 
			
		||||
        }
 | 
			
		||||
        this.conditionClassCollection.forEach(condition => {
 | 
			
		||||
            condition.getResult(normalizedDatum);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.updateTimestamp(resultObj.data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleConditionResult(resultObj) {
 | 
			
		||||
        // update conditions results and then calculate the current condition
 | 
			
		||||
        this.updateConditionResults(resultObj);
 | 
			
		||||
        const currentCondition = this.getCurrentCondition();
 | 
			
		||||
 | 
			
		||||
        this.emit('conditionSetResultUpdated',
 | 
			
		||||
            Object.assign(
 | 
			
		||||
                {
 | 
			
		||||
@@ -225,51 +313,11 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
                    id: this.conditionSetDomainObject.identifier,
 | 
			
		||||
                    conditionId: currentCondition.id
 | 
			
		||||
                },
 | 
			
		||||
                this.latestTimestamp
 | 
			
		||||
                timestamp
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateTimestamp(timestamp) {
 | 
			
		||||
        this.timeAPI.getAllTimeSystems().forEach(timeSystem => {
 | 
			
		||||
            if (!this.latestTimestamp[timeSystem.key]
 | 
			
		||||
                || timestamp[timeSystem.key] > this.latestTimestamp[timeSystem.key]
 | 
			
		||||
            ) {
 | 
			
		||||
                this.latestTimestamp[timeSystem.key] = timestamp[timeSystem.key];
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    requestLADConditionSetOutput() {
 | 
			
		||||
        if (!this.conditionClassCollection.length) {
 | 
			
		||||
            return Promise.resolve([]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.compositionLoad.then(() => {
 | 
			
		||||
            const ladConditionResults = this.conditionClassCollection
 | 
			
		||||
                .map(condition => condition.requestLADConditionResult());
 | 
			
		||||
 | 
			
		||||
            return Promise.all(ladConditionResults)
 | 
			
		||||
                .then((results) => {
 | 
			
		||||
                    results.forEach(resultObj => { this.updateConditionResults(resultObj); });
 | 
			
		||||
                    const currentCondition = this.getCurrentCondition();
 | 
			
		||||
 | 
			
		||||
                    return Object.assign(
 | 
			
		||||
                        {
 | 
			
		||||
                            output: currentCondition.configuration.output,
 | 
			
		||||
                            id: this.conditionSetDomainObject.identifier,
 | 
			
		||||
                            conditionId: currentCondition.id
 | 
			
		||||
                        },
 | 
			
		||||
                        this.latestTimestamp
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    broadcastTelemetry(id, datum) {
 | 
			
		||||
        this.emit(`broadcastTelemetry`, Object.assign({}, this.createNormalizedDatum(datum, id), {id: id}));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getTestData(metadatum) {
 | 
			
		||||
        let data = undefined;
 | 
			
		||||
        if (this.testData.applied) {
 | 
			
		||||
@@ -281,13 +329,20 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
        return data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createNormalizedDatum(telemetryDatum, id) {
 | 
			
		||||
        return Object.values(this.telemetryObjects[id].telemetryMetaData).reduce((normalizedDatum, metadatum) => {
 | 
			
		||||
    createNormalizedDatum(telemetryDatum, endpoint) {
 | 
			
		||||
        const id = this.openmct.objects.makeKeyString(endpoint.identifier);
 | 
			
		||||
        const metadata = this.openmct.telemetry.getMetadata(endpoint).valueMetadatas;
 | 
			
		||||
 | 
			
		||||
        const normalizedDatum = Object.values(metadata).reduce((datum, metadatum) => {
 | 
			
		||||
            const testValue = this.getTestData(metadatum);
 | 
			
		||||
            const formatter = this.openmct.telemetry.getValueFormatter(metadatum);
 | 
			
		||||
            normalizedDatum[metadatum.key] = testValue !== undefined ?  formatter.parse(testValue) : formatter.parse(telemetryDatum[metadatum.source]);
 | 
			
		||||
            return normalizedDatum;
 | 
			
		||||
            datum[metadatum.key] = testValue !== undefined ?  formatter.parse(testValue) : formatter.parse(telemetryDatum[metadatum.source]);
 | 
			
		||||
            return datum;
 | 
			
		||||
        }, {});
 | 
			
		||||
 | 
			
		||||
        normalizedDatum.id = id;
 | 
			
		||||
 | 
			
		||||
        return normalizedDatum;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateTestData(testData) {
 | 
			
		||||
@@ -303,14 +358,13 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
        this.composition.off('add', this.subscribeToTelemetry, this);
 | 
			
		||||
        this.composition.off('remove', this.unsubscribeFromTelemetry, this);
 | 
			
		||||
        Object.values(this.subscriptions).forEach(unsubscribe => unsubscribe());
 | 
			
		||||
        this.subscriptions = undefined;
 | 
			
		||||
        delete this.subscriptions;
 | 
			
		||||
 | 
			
		||||
        if(this.stopObservingForChanges) {
 | 
			
		||||
            this.stopObservingForChanges();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.conditionClassCollection.forEach((condition) => {
 | 
			
		||||
            condition.off('conditionResultUpdated', this.handleConditionResult);
 | 
			
		||||
            condition.destroy();
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,7 @@ describe('ConditionManager', () => {
 | 
			
		||||
    };
 | 
			
		||||
    let mockComposition;
 | 
			
		||||
    let loader;
 | 
			
		||||
    let mockTimeSystems;
 | 
			
		||||
 | 
			
		||||
    function mockAngularComponents() {
 | 
			
		||||
        let mockInjector = jasmine.createSpyObj('$injector', ['get']);
 | 
			
		||||
@@ -111,10 +112,16 @@ describe('ConditionManager', () => {
 | 
			
		||||
        openmct.objects.observe.and.returnValue(function () {});
 | 
			
		||||
        openmct.objects.mutate.and.returnValue(function () {});
 | 
			
		||||
 | 
			
		||||
        mockTimeSystems = {
 | 
			
		||||
            key: 'utc'
 | 
			
		||||
        };
 | 
			
		||||
        openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems']);
 | 
			
		||||
        openmct.time.getAllTimeSystems.and.returnValue([mockTimeSystems]);
 | 
			
		||||
 | 
			
		||||
        conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
 | 
			
		||||
 | 
			
		||||
        conditionMgr.on('conditionSetResultUpdated', mockListener);
 | 
			
		||||
        conditionMgr.on('broadcastTelemetry', mockListener);
 | 
			
		||||
        conditionMgr.on('telemetryReceived', mockListener);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('creates a conditionCollection with a default condition', function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -54,13 +54,22 @@ export default class ConditionSetMetadataProvider {
 | 
			
		||||
        return {
 | 
			
		||||
            values: this.getDomains().concat([
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'Output',
 | 
			
		||||
                    key: 'output',
 | 
			
		||||
                    format: 'enum',
 | 
			
		||||
                    key: "state",
 | 
			
		||||
                    source: "output",
 | 
			
		||||
                    name: "State",
 | 
			
		||||
                    format: "enum",
 | 
			
		||||
                    enumerations: enumerations,
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        range: 1
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "output",
 | 
			
		||||
                    name: "Value",
 | 
			
		||||
                    format: "string",
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        range: 2
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ])
 | 
			
		||||
        };
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ export default class ConditionSetTelemetryProvider {
 | 
			
		||||
 | 
			
		||||
        return conditionManager.requestLADConditionSetOutput()
 | 
			
		||||
            .then(latestOutput => {
 | 
			
		||||
                return latestOutput ? [latestOutput] : [];
 | 
			
		||||
                return latestOutput;
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,12 +25,12 @@ import {TRIGGER} from "./utils/constants";
 | 
			
		||||
import TelemetryCriterion from "./criterion/TelemetryCriterion";
 | 
			
		||||
 | 
			
		||||
let openmct = {},
 | 
			
		||||
    mockListener,
 | 
			
		||||
    testConditionDefinition,
 | 
			
		||||
    testTelemetryObject,
 | 
			
		||||
    conditionObj,
 | 
			
		||||
    conditionManager,
 | 
			
		||||
    mockBroadcastTelemetry;
 | 
			
		||||
    mockTelemetryReceived,
 | 
			
		||||
    mockTimeSystems;
 | 
			
		||||
 | 
			
		||||
describe("The condition", function () {
 | 
			
		||||
 | 
			
		||||
@@ -38,10 +38,9 @@ describe("The condition", function () {
 | 
			
		||||
        conditionManager = jasmine.createSpyObj('conditionManager',
 | 
			
		||||
            ['on']
 | 
			
		||||
        );
 | 
			
		||||
        mockBroadcastTelemetry = jasmine.createSpy('listener');
 | 
			
		||||
        conditionManager.on('broadcastTelemetry', mockBroadcastTelemetry);
 | 
			
		||||
        mockTelemetryReceived = jasmine.createSpy('listener');
 | 
			
		||||
        conditionManager.on('telemetryReceived', mockTelemetryReceived);
 | 
			
		||||
 | 
			
		||||
        mockListener = jasmine.createSpy('listener');
 | 
			
		||||
        testTelemetryObject = {
 | 
			
		||||
            identifier:{ namespace: "", key: "test-object"},
 | 
			
		||||
            type: "test-object",
 | 
			
		||||
@@ -74,6 +73,12 @@ describe("The condition", function () {
 | 
			
		||||
        openmct.telemetry.subscribe.and.returnValue(function () {});
 | 
			
		||||
        openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry.values);
 | 
			
		||||
 | 
			
		||||
        mockTimeSystems = {
 | 
			
		||||
            key: 'utc'
 | 
			
		||||
        };
 | 
			
		||||
        openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems']);
 | 
			
		||||
        openmct.time.getAllTimeSystems.and.returnValue([mockTimeSystems]);
 | 
			
		||||
 | 
			
		||||
        testConditionDefinition = {
 | 
			
		||||
            id: '123-456',
 | 
			
		||||
            configuration: {
 | 
			
		||||
@@ -97,8 +102,6 @@ describe("The condition", function () {
 | 
			
		||||
            openmct,
 | 
			
		||||
            conditionManager
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        conditionObj.on('conditionUpdated', mockListener);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("generates criteria with the correct properties", function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -109,7 +109,7 @@ export default class StyleRuleManager extends EventEmitter {
 | 
			
		||||
        } else {
 | 
			
		||||
            if (this.currentStyle) {
 | 
			
		||||
                Object.keys(this.currentStyle).forEach(key => {
 | 
			
		||||
                    this.currentStyle[key] = 'transparent';
 | 
			
		||||
                    this.currentStyle[key] = '__no_value';
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -21,159 +21,167 @@
 | 
			
		||||
*****************************************************************************/
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div v-if="isEditing"
 | 
			
		||||
     class="c-condition c-condition--edit js-condition-drag-wrapper"
 | 
			
		||||
<div class="c-condition-h"
 | 
			
		||||
     :class="{ 'is-drag-target': draggingOver }"
 | 
			
		||||
     @dragover.prevent
 | 
			
		||||
     @drop.prevent="dropCondition($event, conditionIndex)"
 | 
			
		||||
     @dragenter="dragEnter($event, conditionIndex)"
 | 
			
		||||
     @dragleave="dragLeave($event, conditionIndex)"
 | 
			
		||||
>
 | 
			
		||||
    <!-- Edit view -->
 | 
			
		||||
    <div class="c-condition__header">
 | 
			
		||||
        <span class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
 | 
			
		||||
              title="Drag to reorder conditions"
 | 
			
		||||
              :class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
 | 
			
		||||
              :draggable="!condition.isDefault"
 | 
			
		||||
              @dragstart="dragStart"
 | 
			
		||||
              @dragstop="dragStop"
 | 
			
		||||
              @dragover.stop
 | 
			
		||||
        ></span>
 | 
			
		||||
 | 
			
		||||
        <span class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
 | 
			
		||||
              :class="{ 'c-disclosure-triangle--expanded': expanded }"
 | 
			
		||||
              @click="expanded = !expanded"
 | 
			
		||||
        ></span>
 | 
			
		||||
 | 
			
		||||
        <span class="c-condition__name">{{ condition.configuration.name }}</span>
 | 
			
		||||
        <span class="c-condition__summary">
 | 
			
		||||
            <template v-if="!canEvaluateCriteria">
 | 
			
		||||
                Define criteria
 | 
			
		||||
            </template>
 | 
			
		||||
            <span v-else>
 | 
			
		||||
                <condition-description :show-label="false"
 | 
			
		||||
                                       :condition="condition"
 | 
			
		||||
                />
 | 
			
		||||
            </span>
 | 
			
		||||
        </span>
 | 
			
		||||
 | 
			
		||||
        <div class="c-condition__buttons">
 | 
			
		||||
            <button v-if="!condition.isDefault"
 | 
			
		||||
                    class="c-click-icon c-condition__duplicate-button icon-duplicate"
 | 
			
		||||
                    title="Duplicate this condition"
 | 
			
		||||
                    @click="cloneCondition"
 | 
			
		||||
            ></button>
 | 
			
		||||
 | 
			
		||||
            <button v-if="!condition.isDefault"
 | 
			
		||||
                    class="c-click-icon c-condition__delete-button icon-trash"
 | 
			
		||||
                    title="Delete this condition"
 | 
			
		||||
                    @click="removeCondition"
 | 
			
		||||
            ></button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-if="expanded"
 | 
			
		||||
         class="c-condition__definition c-cdef"
 | 
			
		||||
    <div class="c-condition-h__drop-target"></div>
 | 
			
		||||
    <div v-if="isEditing"
 | 
			
		||||
         class="c-condition c-condition--edit"
 | 
			
		||||
    >
 | 
			
		||||
        <span class="c-cdef__separator c-row-separator"></span>
 | 
			
		||||
        <span class="c-cdef__label">Condition Name</span>
 | 
			
		||||
        <span class="c-cdef__controls">
 | 
			
		||||
            <input v-model="condition.configuration.name"
 | 
			
		||||
                   class="t-condition-input__name"
 | 
			
		||||
                   type="text"
 | 
			
		||||
                   @blur="persist"
 | 
			
		||||
            >
 | 
			
		||||
        </span>
 | 
			
		||||
        <!-- Edit view -->
 | 
			
		||||
        <div class="c-condition__header">
 | 
			
		||||
            <span class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
 | 
			
		||||
                  title="Drag to reorder conditions"
 | 
			
		||||
                  :class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
 | 
			
		||||
                  :draggable="!condition.isDefault"
 | 
			
		||||
                  @dragstart="dragStart"
 | 
			
		||||
                  @dragend="dragEnd"
 | 
			
		||||
            ></span>
 | 
			
		||||
 | 
			
		||||
        <span class="c-cdef__label">Output</span>
 | 
			
		||||
        <span class="c-cdef__controls">
 | 
			
		||||
            <span class="c-cdef__control">
 | 
			
		||||
                <select v-model="selectedOutputSelection"
 | 
			
		||||
                        @change="setOutputValue"
 | 
			
		||||
                >
 | 
			
		||||
                    <option v-for="option in outputOptions"
 | 
			
		||||
                            :key="option"
 | 
			
		||||
                            :value="option"
 | 
			
		||||
                    >
 | 
			
		||||
                        {{ initCap(option) }}
 | 
			
		||||
                    </option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="c-cdef__control">
 | 
			
		||||
                <input v-if="selectedOutputSelection === outputOptions[2]"
 | 
			
		||||
                       v-model="condition.configuration.output"
 | 
			
		||||
                       class="t-condition-name-input"
 | 
			
		||||
                       type="text"
 | 
			
		||||
                       @blur="persist"
 | 
			
		||||
                >
 | 
			
		||||
            </span>
 | 
			
		||||
        </span>
 | 
			
		||||
            <span class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
 | 
			
		||||
                  :class="{ 'c-disclosure-triangle--expanded': expanded }"
 | 
			
		||||
                  @click="expanded = !expanded"
 | 
			
		||||
            ></span>
 | 
			
		||||
 | 
			
		||||
        <div v-if="!condition.isDefault"
 | 
			
		||||
             class="c-cdef__match-and-criteria"
 | 
			
		||||
            <span class="c-condition__name">{{ condition.configuration.name }}</span>
 | 
			
		||||
            <span class="c-condition__summary">
 | 
			
		||||
                <template v-if="!canEvaluateCriteria">
 | 
			
		||||
                    Define criteria
 | 
			
		||||
                </template>
 | 
			
		||||
                <span v-else>
 | 
			
		||||
                    <condition-description :show-label="false"
 | 
			
		||||
                                           :condition="condition"
 | 
			
		||||
                    />
 | 
			
		||||
                </span>
 | 
			
		||||
            </span>
 | 
			
		||||
 | 
			
		||||
            <div class="c-condition__buttons">
 | 
			
		||||
                <button v-if="!condition.isDefault"
 | 
			
		||||
                        class="c-click-icon c-condition__duplicate-button icon-duplicate"
 | 
			
		||||
                        title="Duplicate this condition"
 | 
			
		||||
                        @click="cloneCondition"
 | 
			
		||||
                ></button>
 | 
			
		||||
 | 
			
		||||
                <button v-if="!condition.isDefault"
 | 
			
		||||
                        class="c-click-icon c-condition__delete-button icon-trash"
 | 
			
		||||
                        title="Delete this condition"
 | 
			
		||||
                        @click="removeCondition"
 | 
			
		||||
                ></button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-if="expanded"
 | 
			
		||||
             class="c-condition__definition c-cdef"
 | 
			
		||||
        >
 | 
			
		||||
            <span class="c-cdef__separator c-row-separator"></span>
 | 
			
		||||
            <span class="c-cdef__label">Match</span>
 | 
			
		||||
            <span class="c-cdef__label">Condition Name</span>
 | 
			
		||||
            <span class="c-cdef__controls">
 | 
			
		||||
                <select v-model="condition.configuration.trigger"
 | 
			
		||||
                        @change="persist"
 | 
			
		||||
                <input v-model="condition.configuration.name"
 | 
			
		||||
                       class="t-condition-input__name"
 | 
			
		||||
                       type="text"
 | 
			
		||||
                       @change="persist"
 | 
			
		||||
                >
 | 
			
		||||
                    <option v-for="option in triggers"
 | 
			
		||||
                            :key="option.value"
 | 
			
		||||
                            :value="option.value"
 | 
			
		||||
                    > {{ option.label }}</option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </span>
 | 
			
		||||
 | 
			
		||||
            <template v-if="telemetry.length || condition.configuration.criteria.length">
 | 
			
		||||
                <div v-for="(criterion, index) in condition.configuration.criteria"
 | 
			
		||||
                     :key="criterion.id"
 | 
			
		||||
                     class="c-cdef__criteria"
 | 
			
		||||
                >
 | 
			
		||||
                    <Criterion :telemetry="telemetry"
 | 
			
		||||
                               :criterion="criterion"
 | 
			
		||||
                               :index="index"
 | 
			
		||||
                               :trigger="condition.configuration.trigger"
 | 
			
		||||
                               :is-default="condition.configuration.criteria.length === 1"
 | 
			
		||||
                               @persist="persist"
 | 
			
		||||
                    />
 | 
			
		||||
                    <div class="c-cdef__criteria__buttons">
 | 
			
		||||
                        <button class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
 | 
			
		||||
                                title="Duplicate this criteria"
 | 
			
		||||
                                @click="cloneCriterion(index)"
 | 
			
		||||
                        ></button>
 | 
			
		||||
                        <button v-if="!(condition.configuration.criteria.length === 1)"
 | 
			
		||||
                                class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
 | 
			
		||||
                                title="Delete this criteria"
 | 
			
		||||
                                @click="removeCriterion(index)"
 | 
			
		||||
                        ></button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <div class="c-cdef__separator c-row-separator"></div>
 | 
			
		||||
            <div class="c-cdef__controls"
 | 
			
		||||
                 :disabled="!telemetry.length"
 | 
			
		||||
            <span class="c-cdef__label">Output</span>
 | 
			
		||||
            <span class="c-cdef__controls">
 | 
			
		||||
                <span class="c-cdef__control">
 | 
			
		||||
                    <select v-model="selectedOutputSelection"
 | 
			
		||||
                            @change="setOutputValue"
 | 
			
		||||
                    >
 | 
			
		||||
                        <option v-for="option in outputOptions"
 | 
			
		||||
                                :key="option"
 | 
			
		||||
                                :value="option"
 | 
			
		||||
                        >
 | 
			
		||||
                            {{ initCap(option) }}
 | 
			
		||||
                        </option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                </span>
 | 
			
		||||
                <span class="c-cdef__control">
 | 
			
		||||
                    <input v-if="selectedOutputSelection === outputOptions[2]"
 | 
			
		||||
                           v-model="condition.configuration.output"
 | 
			
		||||
                           class="t-condition-name-input"
 | 
			
		||||
                           type="text"
 | 
			
		||||
                           @change="persist"
 | 
			
		||||
                    >
 | 
			
		||||
                </span>
 | 
			
		||||
            </span>
 | 
			
		||||
 | 
			
		||||
            <div v-if="!condition.isDefault"
 | 
			
		||||
                 class="c-cdef__match-and-criteria"
 | 
			
		||||
            >
 | 
			
		||||
                <button
 | 
			
		||||
                    class="c-cdef__add-criteria-button c-button c-button--labeled icon-plus"
 | 
			
		||||
                    @click="addCriteria"
 | 
			
		||||
                <span class="c-cdef__separator c-row-separator"></span>
 | 
			
		||||
                <span class="c-cdef__label">Match</span>
 | 
			
		||||
                <span class="c-cdef__controls">
 | 
			
		||||
                    <select v-model="condition.configuration.trigger"
 | 
			
		||||
                            @change="persist"
 | 
			
		||||
                    >
 | 
			
		||||
                        <option v-for="option in triggers"
 | 
			
		||||
                                :key="option.value"
 | 
			
		||||
                                :value="option.value"
 | 
			
		||||
                        > {{ option.label }}</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                </span>
 | 
			
		||||
 | 
			
		||||
                <template v-if="telemetry.length || condition.configuration.criteria.length">
 | 
			
		||||
                    <div v-for="(criterion, index) in condition.configuration.criteria"
 | 
			
		||||
                         :key="criterion.id"
 | 
			
		||||
                         class="c-cdef__criteria"
 | 
			
		||||
                    >
 | 
			
		||||
                        <Criterion :telemetry="telemetry"
 | 
			
		||||
                                   :criterion="criterion"
 | 
			
		||||
                                   :index="index"
 | 
			
		||||
                                   :trigger="condition.configuration.trigger"
 | 
			
		||||
                                   :is-default="condition.configuration.criteria.length === 1"
 | 
			
		||||
                                   @persist="persist"
 | 
			
		||||
                        />
 | 
			
		||||
                        <div class="c-cdef__criteria__buttons">
 | 
			
		||||
                            <button class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
 | 
			
		||||
                                    title="Duplicate this criteria"
 | 
			
		||||
                                    @click="cloneCriterion(index)"
 | 
			
		||||
                            ></button>
 | 
			
		||||
                            <button v-if="!(condition.configuration.criteria.length === 1)"
 | 
			
		||||
                                    class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
 | 
			
		||||
                                    title="Delete this criteria"
 | 
			
		||||
                                    @click="removeCriterion(index)"
 | 
			
		||||
                            ></button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </template>
 | 
			
		||||
                <div class="c-cdef__separator c-row-separator"></div>
 | 
			
		||||
                <div class="c-cdef__controls"
 | 
			
		||||
                     :disabled="!telemetry.length"
 | 
			
		||||
                >
 | 
			
		||||
                    <span class="c-button__label">Add Criteria</span>
 | 
			
		||||
                </button>
 | 
			
		||||
                    <button
 | 
			
		||||
                        class="c-cdef__add-criteria-button c-button c-button--labeled icon-plus"
 | 
			
		||||
                        @click="addCriteria"
 | 
			
		||||
                    >
 | 
			
		||||
                        <span class="c-button__label">Add Criteria</span>
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
<div v-else
 | 
			
		||||
     class="c-condition c-condition--browse"
 | 
			
		||||
>
 | 
			
		||||
    <!-- Browse view -->
 | 
			
		||||
    <div class="c-condition__header">
 | 
			
		||||
        <span class="c-condition__name">
 | 
			
		||||
            {{ condition.configuration.name }}
 | 
			
		||||
        </span>
 | 
			
		||||
        <span class="c-condition__output">
 | 
			
		||||
            Output: {{ condition.configuration.output }}
 | 
			
		||||
        </span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="c-condition__summary">
 | 
			
		||||
        <condition-description :show-label="false"
 | 
			
		||||
                               :condition="condition"
 | 
			
		||||
        />
 | 
			
		||||
    <div v-else
 | 
			
		||||
         class="c-condition c-condition--browse"
 | 
			
		||||
    >
 | 
			
		||||
        <!-- Browse view -->
 | 
			
		||||
        <div class="c-condition__header">
 | 
			
		||||
            <span class="c-condition__name">
 | 
			
		||||
                {{ condition.configuration.name }}
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="c-condition__output">
 | 
			
		||||
                Output: {{ condition.configuration.output }}
 | 
			
		||||
            </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-condition__summary">
 | 
			
		||||
            <condition-description :show-label="false"
 | 
			
		||||
                                   :condition="condition"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -207,6 +215,14 @@ export default {
 | 
			
		||||
            type: Array,
 | 
			
		||||
            required: true,
 | 
			
		||||
            default: () => []
 | 
			
		||||
        },
 | 
			
		||||
        isDragging: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            default: false
 | 
			
		||||
        },
 | 
			
		||||
        moveIndex: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: 0
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
@@ -217,8 +233,8 @@ export default {
 | 
			
		||||
            selectedOutputSelection: '',
 | 
			
		||||
            outputOptions: ['false', 'true', 'string'],
 | 
			
		||||
            criterionIndex: 0,
 | 
			
		||||
            selectedTelemetryName: '',
 | 
			
		||||
            selectedFieldName: ''
 | 
			
		||||
            draggingOver: false,
 | 
			
		||||
            isDefault: this.condition.isDefault
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -286,11 +302,39 @@ export default {
 | 
			
		||||
        dragStart(e) {
 | 
			
		||||
            e.dataTransfer.setData('dragging', e.target); // required for FF to initiate drag
 | 
			
		||||
            e.dataTransfer.effectAllowed = "copyMove";
 | 
			
		||||
            e.dataTransfer.setDragImage(e.target.closest('.js-condition-drag-wrapper'), 0, 0);
 | 
			
		||||
            e.dataTransfer.setDragImage(e.target.closest('.c-condition-h'), 0, 0);
 | 
			
		||||
            this.$emit('setMoveIndex', this.conditionIndex);
 | 
			
		||||
        },
 | 
			
		||||
        dragStop(e) {
 | 
			
		||||
            e.dataTransfer.clearData();
 | 
			
		||||
        dragEnd(event) {
 | 
			
		||||
            this.dragStarted = false;
 | 
			
		||||
            event.dataTransfer.clearData();
 | 
			
		||||
            this.$emit('dragComplete');
 | 
			
		||||
        },
 | 
			
		||||
        dropCondition(event, targetIndex) {
 | 
			
		||||
            if (!this.isDragging) { return }
 | 
			
		||||
            if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
 | 
			
		||||
            if (this.isValidTarget(targetIndex)) {
 | 
			
		||||
                this.dragElement = undefined;
 | 
			
		||||
                this.draggingOver = false;
 | 
			
		||||
                this.$emit('dropCondition', targetIndex);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        dragEnter(event, targetIndex) {
 | 
			
		||||
            if (!this.isDragging) { return }
 | 
			
		||||
            if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
 | 
			
		||||
            if (this.isValidTarget(targetIndex)) {
 | 
			
		||||
                this.dragElement = event.target.parentElement;
 | 
			
		||||
                this.draggingOver = true;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        dragLeave(event) {
 | 
			
		||||
            if (event.target.parentElement === this.dragElement) {
 | 
			
		||||
                this.draggingOver = false;
 | 
			
		||||
                this.dragElement = undefined;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        isValidTarget(targetIndex) {
 | 
			
		||||
            return this.moveIndex !== targetIndex;
 | 
			
		||||
        },
 | 
			
		||||
        destroy() {
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,6 @@
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<section id="conditionCollection"
 | 
			
		||||
         class="c-cs__conditions"
 | 
			
		||||
         :class="{ 'is-expanded': expanded }"
 | 
			
		||||
>
 | 
			
		||||
    <div class="c-cs__header c-section__header">
 | 
			
		||||
@@ -53,28 +52,24 @@
 | 
			
		||||
            <span class="c-cs-button__label">Add Condition</span>
 | 
			
		||||
        </button>
 | 
			
		||||
 | 
			
		||||
        <div class="c-cs__conditions-h">
 | 
			
		||||
            <div v-for="(condition, index) in conditionCollection"
 | 
			
		||||
                 :key="condition.id"
 | 
			
		||||
                 class="c-condition-h"
 | 
			
		||||
            >
 | 
			
		||||
                <div v-if="isEditing"
 | 
			
		||||
                     class="c-c__drag-ghost"
 | 
			
		||||
                     @drop.prevent="dropCondition"
 | 
			
		||||
                     @dragenter="dragEnter"
 | 
			
		||||
                     @dragleave="dragLeave"
 | 
			
		||||
                     @dragover.prevent
 | 
			
		||||
                ></div>
 | 
			
		||||
                <Condition :condition="condition"
 | 
			
		||||
                           :condition-index="index"
 | 
			
		||||
                           :telemetry="telemetryObjs"
 | 
			
		||||
                           :is-editing="isEditing"
 | 
			
		||||
                           @updateCondition="updateCondition"
 | 
			
		||||
                           @removeCondition="removeCondition"
 | 
			
		||||
                           @cloneCondition="cloneCondition"
 | 
			
		||||
                           @setMoveIndex="setMoveIndex"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
        <div class="c-cs__conditions-h"
 | 
			
		||||
             :class="{ 'is-active-dragging': isDragging }"
 | 
			
		||||
        >
 | 
			
		||||
            <Condition v-for="(condition, index) in conditionCollection"
 | 
			
		||||
                       :key="condition.id"
 | 
			
		||||
                       :condition="condition"
 | 
			
		||||
                       :condition-index="index"
 | 
			
		||||
                       :telemetry="telemetryObjs"
 | 
			
		||||
                       :is-editing="isEditing"
 | 
			
		||||
                       :move-index="moveIndex"
 | 
			
		||||
                       :is-dragging="isDragging"
 | 
			
		||||
                       @updateCondition="updateCondition"
 | 
			
		||||
                       @removeCondition="removeCondition"
 | 
			
		||||
                       @cloneCondition="cloneCondition"
 | 
			
		||||
                       @setMoveIndex="setMoveIndex"
 | 
			
		||||
                       @dragComplete="dragComplete"
 | 
			
		||||
                       @dropCondition="dropCondition"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</section>
 | 
			
		||||
@@ -109,9 +104,10 @@ export default {
 | 
			
		||||
            conditionResults: {},
 | 
			
		||||
            conditions: [],
 | 
			
		||||
            telemetryObjs: [],
 | 
			
		||||
            moveIndex: Number,
 | 
			
		||||
            moveIndex: undefined,
 | 
			
		||||
            isDragging: false,
 | 
			
		||||
            defaultOutput: undefined
 | 
			
		||||
            defaultOutput: undefined,
 | 
			
		||||
            dragCounter: 0
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
@@ -166,9 +162,7 @@ export default {
 | 
			
		||||
            this.moveIndex = index;
 | 
			
		||||
            this.isDragging = true;
 | 
			
		||||
        },
 | 
			
		||||
        dropCondition(e) {
 | 
			
		||||
            let targetIndex = Array.from(document.querySelectorAll('.c-c__drag-ghost')).indexOf(e.target);
 | 
			
		||||
            if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
 | 
			
		||||
        dropCondition(targetIndex) {
 | 
			
		||||
            const oldIndexArr = Object.keys(this.conditionCollection);
 | 
			
		||||
            const move = function (arr, old_index, new_index) {
 | 
			
		||||
                while (old_index < 0) {
 | 
			
		||||
@@ -194,20 +188,10 @@ export default {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.reorder(reorderPlan);
 | 
			
		||||
 | 
			
		||||
            e.target.classList.remove("dragging");
 | 
			
		||||
        },
 | 
			
		||||
        dragComplete() {
 | 
			
		||||
            this.isDragging = false;
 | 
			
		||||
        },
 | 
			
		||||
        dragEnter(e) {
 | 
			
		||||
            if (!this.isDragging) { return }
 | 
			
		||||
            let targetIndex = Array.from(document.querySelectorAll('.c-c__drag-ghost')).indexOf(e.target);
 | 
			
		||||
            if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
 | 
			
		||||
            if (this.moveIndex === targetIndex) { return }
 | 
			
		||||
            e.target.classList.add("dragging");
 | 
			
		||||
        },
 | 
			
		||||
        dragLeave(e) {
 | 
			
		||||
            e.target.classList.remove("dragging");
 | 
			
		||||
        },
 | 
			
		||||
        addTelemetryObject(domainObject) {
 | 
			
		||||
            this.telemetryObjs.push(domainObject);
 | 
			
		||||
            this.$emit('telemetryUpdated', this.telemetryObjs);
 | 
			
		||||
 
 | 
			
		||||
@@ -23,30 +23,33 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-cs">
 | 
			
		||||
    <section class="c-cs__current-output c-section">
 | 
			
		||||
        <div class="c-cs__header c-section__header">
 | 
			
		||||
            <span class="c-cs__header-label c-section__label">Current Output</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-cs__content c-cs__current-output-value">
 | 
			
		||||
            <template v-if="currentConditionOutput">
 | 
			
		||||
                {{ currentConditionOutput }}
 | 
			
		||||
            </template>
 | 
			
		||||
            <template v-else>
 | 
			
		||||
                {{ defaultConditionOutput }}
 | 
			
		||||
            </template>
 | 
			
		||||
            <span class="c-cs__current-output-value__label">Current Output</span>
 | 
			
		||||
            <span class="c-cs__current-output-value__value">
 | 
			
		||||
                <template v-if="currentConditionOutput">
 | 
			
		||||
                    {{ currentConditionOutput }}
 | 
			
		||||
                </template>
 | 
			
		||||
                <template v-else>
 | 
			
		||||
                    {{ defaultConditionOutput }}
 | 
			
		||||
                </template>
 | 
			
		||||
            </span>
 | 
			
		||||
        </div>
 | 
			
		||||
    </section>
 | 
			
		||||
    <TestData :is-editing="isEditing"
 | 
			
		||||
              :test-data="testData"
 | 
			
		||||
              :telemetry="telemetryObjs"
 | 
			
		||||
              @updateTestData="updateTestData"
 | 
			
		||||
    />
 | 
			
		||||
    <ConditionCollection
 | 
			
		||||
        :is-editing="isEditing"
 | 
			
		||||
        :test-data="testData"
 | 
			
		||||
        @conditionSetResultUpdated="updateCurrentOutput"
 | 
			
		||||
        @updateDefaultOutput="updateDefaultOutput"
 | 
			
		||||
        @telemetryUpdated="updateTelemetry"
 | 
			
		||||
    />
 | 
			
		||||
    <div class="c-cs__test-data-and-conditions-w">
 | 
			
		||||
        <TestData class="c-cs__test-data"
 | 
			
		||||
                  :is-editing="isEditing"
 | 
			
		||||
                  :test-data="testData"
 | 
			
		||||
                  :telemetry="telemetryObjs"
 | 
			
		||||
                  @updateTestData="updateTestData"
 | 
			
		||||
        />
 | 
			
		||||
        <ConditionCollection class="c-cs__conditions"
 | 
			
		||||
                             :is-editing="isEditing"
 | 
			
		||||
                             :test-data="testData"
 | 
			
		||||
                             @conditionSetResultUpdated="updateCurrentOutput"
 | 
			
		||||
                             @updateDefaultOutput="updateDefaultOutput"
 | 
			
		||||
                             @telemetryUpdated="updateTelemetry"
 | 
			
		||||
        />
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								src/plugins/condition/components/CurrentOutput.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/plugins/condition/components/CurrentOutput.vue
									
									
									
									
									
										Normal file
									
								
							@@ -23,7 +23,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<section v-show="isEditing"
 | 
			
		||||
         id="test-data"
 | 
			
		||||
         class="c-cs__test-data"
 | 
			
		||||
         :class="{ 'is-expanded': expanded }"
 | 
			
		||||
>
 | 
			
		||||
    <div class="c-cs__header c-section__header">
 | 
			
		||||
@@ -37,7 +36,7 @@
 | 
			
		||||
    <div v-if="expanded"
 | 
			
		||||
         class="c-cs__content"
 | 
			
		||||
    >
 | 
			
		||||
        <div class="c-cdef__controls"
 | 
			
		||||
        <div class="c-cs__test-data__controls c-cdef__controls"
 | 
			
		||||
             :disabled="!telemetry.length"
 | 
			
		||||
        >
 | 
			
		||||
            <label class="c-toggle-switch">
 | 
			
		||||
@@ -96,7 +95,7 @@
 | 
			
		||||
                        >
 | 
			
		||||
                    </span>
 | 
			
		||||
                </span>
 | 
			
		||||
                <div class="c-test-datum__buttons">
 | 
			
		||||
                <div class="c-cs-test__buttons">
 | 
			
		||||
                    <button class="c-click-icon c-test-data__duplicate-button icon-duplicate"
 | 
			
		||||
                            title="Duplicate this test datum"
 | 
			
		||||
                            @click="addTestInput(testInput)"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,116 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2020, 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
.c-cs {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    &__content {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        flex: 0 1 auto;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
 | 
			
		||||
        > * {
 | 
			
		||||
            flex: 0 0 auto;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            + * {
 | 
			
		||||
                margin-top: $interiorMarginSm;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .c-button {
 | 
			
		||||
            align-self: start;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .is-editing & {
 | 
			
		||||
        // Add some space to kick away from blue editing border indication
 | 
			
		||||
        padding: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    section {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__conditions-h {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
        overflow: auto;
 | 
			
		||||
        padding-right: $interiorMarginSm;
 | 
			
		||||
 | 
			
		||||
        > * + * {
 | 
			
		||||
            margin-top: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__conditions {
 | 
			
		||||
        > * + * {
 | 
			
		||||
            margin-top: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .hint {
 | 
			
		||||
        padding: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /************************** SPECIFIC ITEMS */
 | 
			
		||||
    &__current-output-value {
 | 
			
		||||
        font-size: 1.25em;
 | 
			
		||||
        padding: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***************************** TEST DATA */
 | 
			
		||||
.c-cs-tests {
 | 
			
		||||
    flex: 0 1 auto;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
    padding-right: $interiorMarginSm;
 | 
			
		||||
 | 
			
		||||
    > * + * {
 | 
			
		||||
        margin-top: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-cs-test {
 | 
			
		||||
    > * {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
        + * {
 | 
			
		||||
            margin-left: $interiorMargin;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__controls {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
 | 
			
		||||
        > * + * {
 | 
			
		||||
            margin-left: $interiorMargin;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1,133 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2020, 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
.c-condition,
 | 
			
		||||
.c-test-datum {
 | 
			
		||||
    @include discreteItem();
 | 
			
		||||
    display: flex;
 | 
			
		||||
    padding: $interiorMargin;
 | 
			
		||||
 | 
			
		||||
    &--edit {
 | 
			
		||||
        line-height: 160%; // For layout when inputs wrap, like in criteria
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-condition {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    min-width: 400px;
 | 
			
		||||
 | 
			
		||||
    > * + * {
 | 
			
		||||
        margin-top: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
    &--browse {
 | 
			
		||||
        .c-condition__summary {
 | 
			
		||||
            border-top: 1px solid $colorInteriorBorder;
 | 
			
		||||
            padding-top: $interiorMargin;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***************************** HEADER */
 | 
			
		||||
    &__header {
 | 
			
		||||
        $h: 22px;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: start;
 | 
			
		||||
        align-content: stretch;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        min-height: $h;
 | 
			
		||||
        line-height: $h;
 | 
			
		||||
 | 
			
		||||
        > * {
 | 
			
		||||
            flex: 0 0 auto;
 | 
			
		||||
            + * {
 | 
			
		||||
                margin-left: $interiorMarginSm;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__drag-grippy {
 | 
			
		||||
        transform: translateY(50%);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__name {
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        align-self: baseline; // Fixes bold line-height offset problem
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__output,
 | 
			
		||||
    &__summary {
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***************************** CONDITION DEFINITION, EDITING */
 | 
			
		||||
.c-cdef {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-row-gap: $interiorMarginSm;
 | 
			
		||||
    grid-column-gap: $interiorMargin;
 | 
			
		||||
    grid-auto-columns: min-content 1fr max-content;
 | 
			
		||||
    align-items: start;
 | 
			
		||||
    min-width: 150px;
 | 
			
		||||
    margin-left: 29px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    &__criteria,
 | 
			
		||||
    &__match-and-criteria {
 | 
			
		||||
        display: contents;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__label {
 | 
			
		||||
        grid-column: 1;
 | 
			
		||||
        text-align: right;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__separator {
 | 
			
		||||
        grid-column: 1 / span 3;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__controls {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-wrap: wrap;
 | 
			
		||||
        align-items: flex-start;
 | 
			
		||||
        grid-column: 2;
 | 
			
		||||
 | 
			
		||||
        > * > * {
 | 
			
		||||
            margin-right: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__buttons {
 | 
			
		||||
        grid-column: 3;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-c__drag-ghost {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    min-height: $interiorMarginSm;
 | 
			
		||||
 | 
			
		||||
    &.dragging {
 | 
			
		||||
        min-height: 5em;
 | 
			
		||||
        background-color: lightblue;
 | 
			
		||||
        border-radius: 2px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										311
									
								
								src/plugins/condition/components/conditionals.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								src/plugins/condition/components/conditionals.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,311 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2020, 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
/***************************** DRAGGING */
 | 
			
		||||
.is-active-dragging {
 | 
			
		||||
    .c-condition-h__drop-target {
 | 
			
		||||
        height: 3px;
 | 
			
		||||
        margin-bottom: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-condition-h {
 | 
			
		||||
    &__drop-target {
 | 
			
		||||
        border-radius: $controlCr;
 | 
			
		||||
        height: 0;
 | 
			
		||||
        min-height: 0;
 | 
			
		||||
        transition: background-color, height;
 | 
			
		||||
        transition-duration: 150ms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-drag-target {
 | 
			
		||||
        .c-condition > * {
 | 
			
		||||
            pointer-events: none; // Keeps the JS drop handler from being intercepted by internal elements
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .c-condition-h__drop-target {
 | 
			
		||||
            background-color: rgba($colorKey, 0.7);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-cs {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    /************************** CONDITION SET LAYOUT */
 | 
			
		||||
    &__current-output {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__test-data-and-conditions-w {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
        height: 100%;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__test-data,
 | 
			
		||||
    &__conditions {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__test-data {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
        max-height: 50%;
 | 
			
		||||
 | 
			
		||||
        &.is-expanded {
 | 
			
		||||
            margin-bottom: $interiorMargin * 4;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__conditions {
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
 | 
			
		||||
        > * + * {
 | 
			
		||||
            margin-top: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__content {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        flex: 0 1 auto;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
 | 
			
		||||
        > * {
 | 
			
		||||
            flex: 0 0 auto;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            + * {
 | 
			
		||||
                margin-top: $interiorMarginSm;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .c-button {
 | 
			
		||||
            align-self: start;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .is-editing & {
 | 
			
		||||
        // Add some space to kick away from blue editing border indication
 | 
			
		||||
        padding: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    section {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__conditions-h {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
        overflow: auto;
 | 
			
		||||
        padding-right: $interiorMarginSm;
 | 
			
		||||
 | 
			
		||||
        > * + * {
 | 
			
		||||
            margin-top: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .hint {
 | 
			
		||||
        padding: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /************************** SPECIFIC ITEMS */
 | 
			
		||||
    &__current-output-value {
 | 
			
		||||
        flex-direction: row;
 | 
			
		||||
        align-items: baseline;
 | 
			
		||||
        padding: 0 $interiorMargin $interiorMarginLg $interiorMargin;
 | 
			
		||||
 | 
			
		||||
        > * {
 | 
			
		||||
            padding: $interiorMargin 0; // Must do this to align label and value
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &__label {
 | 
			
		||||
            color: $colorInspectorSectionHeaderFg;
 | 
			
		||||
            opacity: 0.9;
 | 
			
		||||
            text-transform: uppercase;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &__value {
 | 
			
		||||
            $p: $interiorMargin * 3;
 | 
			
		||||
            font-size: 1.25em;
 | 
			
		||||
            margin-left: $interiorMargin;
 | 
			
		||||
            padding-left: $p;
 | 
			
		||||
            padding-right: $p;
 | 
			
		||||
            background: rgba(black, 0.2);
 | 
			
		||||
            border-radius: 5px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***************************** CONDITIONS AND TEST DATUM ELEMENTS */
 | 
			
		||||
.c-condition,
 | 
			
		||||
.c-test-datum {
 | 
			
		||||
    @include discreteItem();
 | 
			
		||||
    display: flex;
 | 
			
		||||
    padding: $interiorMargin;
 | 
			
		||||
    line-height: 170%; // Aligns text with controls like selects
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-cdef,
 | 
			
		||||
.c-cs-test {
 | 
			
		||||
    &__controls {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
        flex-wrap: wrap;
 | 
			
		||||
 | 
			
		||||
        > * > * {
 | 
			
		||||
            margin-right: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__buttons {
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-condition {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    min-width: 400px;
 | 
			
		||||
 | 
			
		||||
    > * + * {
 | 
			
		||||
        margin-top: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
    &--browse {
 | 
			
		||||
        .c-condition__summary {
 | 
			
		||||
            border-top: 1px solid $colorInteriorBorder;
 | 
			
		||||
            padding-top: $interiorMargin;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***************************** HEADER */
 | 
			
		||||
    &__header {
 | 
			
		||||
        $h: 22px;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: start;
 | 
			
		||||
        align-content: stretch;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        min-height: $h;
 | 
			
		||||
        line-height: $h;
 | 
			
		||||
 | 
			
		||||
        > * {
 | 
			
		||||
            flex: 0 0 auto;
 | 
			
		||||
            + * {
 | 
			
		||||
                margin-left: $interiorMarginSm;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__drag-grippy {
 | 
			
		||||
        transform: translateY(50%);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__name {
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        align-self: baseline; // Fixes bold line-height offset problem
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__output,
 | 
			
		||||
    &__summary {
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***************************** CONDITION DEFINITION, EDITING */
 | 
			
		||||
.c-cdef {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-row-gap: $interiorMarginSm;
 | 
			
		||||
    grid-column-gap: $interiorMargin;
 | 
			
		||||
    grid-auto-columns: min-content 1fr max-content;
 | 
			
		||||
    align-items: start;
 | 
			
		||||
    min-width: 150px;
 | 
			
		||||
    margin-left: 29px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    &__criteria,
 | 
			
		||||
    &__match-and-criteria {
 | 
			
		||||
        display: contents;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__label {
 | 
			
		||||
        grid-column: 1;
 | 
			
		||||
        text-align: right;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__separator {
 | 
			
		||||
        grid-column: 1 / span 3;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__controls {
 | 
			
		||||
        align-items: flex-start;
 | 
			
		||||
        grid-column: 2;
 | 
			
		||||
 | 
			
		||||
        > * > * {
 | 
			
		||||
            margin-right: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__buttons {
 | 
			
		||||
        grid-column: 3;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-c__drag-ghost {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    min-height: $interiorMarginSm;
 | 
			
		||||
 | 
			
		||||
    &.dragging {
 | 
			
		||||
        min-height: 5em;
 | 
			
		||||
        background-color: lightblue;
 | 
			
		||||
        border-radius: 2px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***************************** TEST DATA */
 | 
			
		||||
.c-cs__test-data {
 | 
			
		||||
    &__controls {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-cs-tests {
 | 
			
		||||
    flex: 0 1 auto;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
    padding-right: $interiorMarginSm;
 | 
			
		||||
 | 
			
		||||
    > * + * {
 | 
			
		||||
        margin-top: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-cs-test {
 | 
			
		||||
    > * + * {
 | 
			
		||||
        margin-left: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -105,6 +105,7 @@ import ConditionDescription from "@/plugins/condition/components/ConditionDescri
 | 
			
		||||
import ConditionError from "@/plugins/condition/components/ConditionError.vue";
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import PreviewAction from "@/ui/preview/PreviewAction.js";
 | 
			
		||||
import {getApplicableStylesForItem} from "@/plugins/condition/utils/styleUtils";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'ConditionalStylesView',
 | 
			
		||||
@@ -115,24 +116,8 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    inject: [
 | 
			
		||||
        'openmct',
 | 
			
		||||
        'domainObject'
 | 
			
		||||
        'selection'
 | 
			
		||||
    ],
 | 
			
		||||
    props: {
 | 
			
		||||
        itemId: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            default: ''
 | 
			
		||||
        },
 | 
			
		||||
        initialStyles: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            default() {
 | 
			
		||||
                return undefined;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        canHide: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            default: false
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            conditionalStyles: [],
 | 
			
		||||
@@ -145,9 +130,11 @@ export default {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
        this.openmct.editor.off('isEditing', this.setEditState);
 | 
			
		||||
        this.removeListeners();
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.itemId = '';
 | 
			
		||||
        this.getDomainObjectFromSelection();
 | 
			
		||||
        this.previewAction = new PreviewAction(this.openmct);
 | 
			
		||||
        if (this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
 | 
			
		||||
            let objectStyles = this.itemId ? this.domainObject.configuration.objectStyles[this.itemId] : this.domainObject.configuration.objectStyles;
 | 
			
		||||
@@ -162,6 +149,49 @@ export default {
 | 
			
		||||
        this.openmct.editor.on('isEditing', this.setEditState);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        isItemType(type, item) {
 | 
			
		||||
            return item && (item.type === type);
 | 
			
		||||
        },
 | 
			
		||||
        getDomainObjectFromSelection() {
 | 
			
		||||
            let layoutItem;
 | 
			
		||||
            let domainObject;
 | 
			
		||||
 | 
			
		||||
            if (this.selection[0].length > 1) {
 | 
			
		||||
                //If there are more than 1 items in the this.selection[0] list, the first one could either be a sub domain object OR a layout drawing control.
 | 
			
		||||
                //The second item in the this.selection[0] list is the container object (usually a layout)
 | 
			
		||||
                layoutItem = this.selection[0][0].context.layoutItem;
 | 
			
		||||
                const item = this.selection[0][0].context.item;
 | 
			
		||||
                this.canHide = true;
 | 
			
		||||
                if (item &&
 | 
			
		||||
                    (!layoutItem || (this.isItemType('subobject-view', layoutItem)))) {
 | 
			
		||||
                    domainObject = item;
 | 
			
		||||
                } else {
 | 
			
		||||
                    domainObject = this.selection[0][1].context.item;
 | 
			
		||||
                    if (layoutItem) {
 | 
			
		||||
                        this.itemId = layoutItem.id;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                domainObject = this.selection[0][0].context.item;
 | 
			
		||||
            }
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
            this.initialStyles = getApplicableStylesForItem(domainObject, layoutItem);
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                this.removeListeners();
 | 
			
		||||
                if (this.domainObject) {
 | 
			
		||||
                    this.stopObserving = this.openmct.objects.observe(this.domainObject, '*', newDomainObject => this.domainObject = newDomainObject);
 | 
			
		||||
                    this.stopObservingItems = this.openmct.objects.observe(this.domainObject, 'configuration.items', this.updateDomainObjectItemStyles);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        removeListeners() {
 | 
			
		||||
            if (this.stopObserving) {
 | 
			
		||||
                this.stopObserving();
 | 
			
		||||
            }
 | 
			
		||||
            if (this.stopObservingItems) {
 | 
			
		||||
                this.stopObservingItems();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        initialize(conditionSetDomainObject) {
 | 
			
		||||
            //If there are new conditions in the conditionSet we need to set those styles to default
 | 
			
		||||
            this.conditionSetDomainObject = conditionSetDomainObject;
 | 
			
		||||
@@ -258,6 +288,36 @@ export default {
 | 
			
		||||
 | 
			
		||||
            this.persist(domainObjectStyles);
 | 
			
		||||
        },
 | 
			
		||||
        updateDomainObjectItemStyles(newItems) {
 | 
			
		||||
            //check that all items that have been styles still exist. Otherwise delete those styles
 | 
			
		||||
            let domainObjectStyles =  (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
 | 
			
		||||
            let itemsToRemove = [];
 | 
			
		||||
            let keys = Object.keys(domainObjectStyles);
 | 
			
		||||
            keys.forEach((key) => {
 | 
			
		||||
                if ((key !== 'styles') &&
 | 
			
		||||
                    (key !== 'staticStyle') &&
 | 
			
		||||
                    (key !== 'conditionSetIdentifier')) {
 | 
			
		||||
                    if (!(newItems.find(item => item.id === key))) {
 | 
			
		||||
                        itemsToRemove.push(key);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            if (itemsToRemove.length) {
 | 
			
		||||
                this.removeItemStyles(itemsToRemove, domainObjectStyles);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        removeItemStyles(itemIds, domainObjectStyles) {
 | 
			
		||||
            itemIds.forEach(itemId => {
 | 
			
		||||
                if (domainObjectStyles[itemId]) {
 | 
			
		||||
                    domainObjectStyles[itemId] = undefined;
 | 
			
		||||
                    delete domainObjectStyles[this.itemId];
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            if (_.isEmpty(domainObjectStyles)) {
 | 
			
		||||
                domainObjectStyles = undefined;
 | 
			
		||||
            }
 | 
			
		||||
            this.persist(domainObjectStyles);
 | 
			
		||||
        },
 | 
			
		||||
        initializeConditionalStyles() {
 | 
			
		||||
            if (!this.conditions) {
 | 
			
		||||
                this.conditions = {};
 | 
			
		||||
@@ -283,9 +343,15 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        initializeStaticStyle(objectStyles) {
 | 
			
		||||
            let staticStyle = objectStyles && objectStyles.staticStyle;
 | 
			
		||||
            this.staticStyle = staticStyle || {
 | 
			
		||||
                style: Object.assign({}, this.initialStyles)
 | 
			
		||||
            };
 | 
			
		||||
            if (staticStyle) {
 | 
			
		||||
                this.staticStyle = {
 | 
			
		||||
                    style: Object.assign({}, this.initialStyles, staticStyle.style)
 | 
			
		||||
                };
 | 
			
		||||
            } else {
 | 
			
		||||
                this.staticStyle = {
 | 
			
		||||
                    style: Object.assign({}, this.initialStyles)
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        findStyleByConditionId(id) {
 | 
			
		||||
            return this.conditionalStyles.find(conditionalStyle => conditionalStyle.conditionId === id);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,269 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
* Open MCT, Copyright (c) 2014-2020, 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.
 | 
			
		||||
*****************************************************************************/
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-inspector__styles c-inspect-styles">
 | 
			
		||||
    <div class="c-inspect-styles__header">
 | 
			
		||||
        Object Style
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="c-inspect-styles__content">
 | 
			
		||||
        <div v-if="isStaticAndConditionalStyles"
 | 
			
		||||
             class="c-inspect-styles__mixed-static-and-conditional u-alert u-alert--block u-alert--with-icon"
 | 
			
		||||
        >
 | 
			
		||||
            Your selection includes one or more items that use Conditional Styling. Applying a static style below will replace any Conditional Styling with the new choice.
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-if="staticStyle"
 | 
			
		||||
             class="c-inspect-styles__style"
 | 
			
		||||
        >
 | 
			
		||||
            <style-editor class="c-inspect-styles__editor"
 | 
			
		||||
                          :style-item="staticStyle"
 | 
			
		||||
                          :is-editing="isEditing"
 | 
			
		||||
                          :mixed-styles="mixedStyles"
 | 
			
		||||
                          @persist="updateStaticStyle"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
import StyleEditor from "./StyleEditor.vue";
 | 
			
		||||
import PreviewAction from "@/ui/preview/PreviewAction.js";
 | 
			
		||||
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionalStyleForItem } from "@/plugins/condition/utils/styleUtils";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'MultiSelectStylesView',
 | 
			
		||||
    components: {
 | 
			
		||||
        StyleEditor
 | 
			
		||||
    },
 | 
			
		||||
    inject: [
 | 
			
		||||
        'openmct',
 | 
			
		||||
        'selection'
 | 
			
		||||
    ],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            staticStyle: undefined,
 | 
			
		||||
            isEditing: this.openmct.editor.isEditing(),
 | 
			
		||||
            mixedStyles: [],
 | 
			
		||||
            isStaticAndConditionalStyles: false
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
        this.removeListeners();
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.items = [];
 | 
			
		||||
        this.previewAction = new PreviewAction(this.openmct);
 | 
			
		||||
        this.getObjectsAndItemsFromSelection();
 | 
			
		||||
        this.initializeStaticStyle();
 | 
			
		||||
        this.openmct.editor.on('isEditing', this.setEditState);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        isItemType(type, item) {
 | 
			
		||||
            return item && (item.type === type);
 | 
			
		||||
        },
 | 
			
		||||
        hasConditionalStyles(domainObject, id) {
 | 
			
		||||
            return getConditionalStyleForItem(domainObject, id) !== undefined;
 | 
			
		||||
        },
 | 
			
		||||
        getObjectsAndItemsFromSelection() {
 | 
			
		||||
            let domainObject;
 | 
			
		||||
            let subObjects = [];
 | 
			
		||||
 | 
			
		||||
            //multiple selection
 | 
			
		||||
            let itemInitialStyles = [];
 | 
			
		||||
            let itemStyle;
 | 
			
		||||
            this.selection.forEach((selectionItem) => {
 | 
			
		||||
                const item = selectionItem[0].context.item;
 | 
			
		||||
                const layoutItem = selectionItem[0].context.layoutItem;
 | 
			
		||||
                if (item && this.isItemType('subobject-view', layoutItem)) {
 | 
			
		||||
                    subObjects.push(item);
 | 
			
		||||
                    itemStyle = getApplicableStylesForItem(item);
 | 
			
		||||
                    if (!this.isStaticAndConditionalStyles) {
 | 
			
		||||
                        this.isStaticAndConditionalStyles = this.hasConditionalStyles(item);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    domainObject = selectionItem[1].context.item;
 | 
			
		||||
                    itemStyle = getApplicableStylesForItem(domainObject, layoutItem || item);
 | 
			
		||||
                    this.items.push({
 | 
			
		||||
                        id: layoutItem.id,
 | 
			
		||||
                        applicableStyles: itemStyle
 | 
			
		||||
                    });
 | 
			
		||||
                    if (!this.isStaticAndConditionalStyles) {
 | 
			
		||||
                        this.isStaticAndConditionalStyles = this.hasConditionalStyles(domainObject, layoutItem.id);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                itemInitialStyles.push(itemStyle);
 | 
			
		||||
            });
 | 
			
		||||
            const {styles, mixedStyles} = getConsolidatedStyleValues(itemInitialStyles);
 | 
			
		||||
            this.initialStyles = styles;
 | 
			
		||||
            this.mixedStyles = mixedStyles;
 | 
			
		||||
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
            this.removeListeners();
 | 
			
		||||
            if (this.domainObject) {
 | 
			
		||||
                this.stopObserving = this.openmct.objects.observe(this.domainObject, '*', newDomainObject => this.domainObject = newDomainObject);
 | 
			
		||||
                this.stopObservingItems = this.openmct.objects.observe(this.domainObject, 'configuration.items', this.updateDomainObjectItemStyles);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            subObjects.forEach(this.registerListener);
 | 
			
		||||
        },
 | 
			
		||||
        updateDomainObjectItemStyles(newItems) {
 | 
			
		||||
            //check that all items that have been styles still exist. Otherwise delete those styles
 | 
			
		||||
            let keys = Object.keys(this.domainObject.configuration.objectStyles || {});
 | 
			
		||||
            keys.forEach((key) => {
 | 
			
		||||
                if ((key !== 'styles') &&
 | 
			
		||||
                    (key !== 'staticStyle') &&
 | 
			
		||||
                    (key !== 'conditionSetIdentifier')) {
 | 
			
		||||
                    if (!(newItems.find(item => item.id === key))) {
 | 
			
		||||
                        this.removeItemStyles(key);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        registerListener(domainObject) {
 | 
			
		||||
            let id = this.openmct.objects.makeKeyString(domainObject.identifier);
 | 
			
		||||
 | 
			
		||||
            if (!this.domainObjectsById) {
 | 
			
		||||
                this.domainObjectsById = {};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!this.domainObjectsById[id]) {
 | 
			
		||||
                this.domainObjectsById[id] = domainObject;
 | 
			
		||||
                this.observeObject(domainObject, id);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        observeObject(domainObject, id) {
 | 
			
		||||
            let unobserveObject = this.openmct.objects.observe(domainObject, '*', function (newObject) {
 | 
			
		||||
                this.domainObjectsById[id] = JSON.parse(JSON.stringify(newObject));
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
            this.unObserveObjects.push(unobserveObject);
 | 
			
		||||
        },
 | 
			
		||||
        removeListeners() {
 | 
			
		||||
            if (this.stopObserving) {
 | 
			
		||||
                this.stopObserving();
 | 
			
		||||
            }
 | 
			
		||||
            if (this.stopObservingItems) {
 | 
			
		||||
                this.stopObservingItems();
 | 
			
		||||
            }
 | 
			
		||||
            if (this.unObserveObjects) {
 | 
			
		||||
                this.unObserveObjects.forEach((unObserveObject) => {
 | 
			
		||||
                    unObserveObject();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            this.unObserveObjects = [];
 | 
			
		||||
        },
 | 
			
		||||
        removeItemStyles(itemId) {
 | 
			
		||||
            let domainObjectStyles =  (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
 | 
			
		||||
            if (itemId && domainObjectStyles[itemId]) {
 | 
			
		||||
                domainObjectStyles[itemId] = undefined;
 | 
			
		||||
                delete domainObjectStyles[this.itemId];
 | 
			
		||||
 | 
			
		||||
                if (_.isEmpty(domainObjectStyles)) {
 | 
			
		||||
                    domainObjectStyles = undefined;
 | 
			
		||||
                }
 | 
			
		||||
                this.persist(this.domainObject, domainObjectStyles);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        removeConditionalStyles(domainObjectStyles, itemId) {
 | 
			
		||||
            if (itemId) {
 | 
			
		||||
                domainObjectStyles[itemId].conditionSetIdentifier = undefined;
 | 
			
		||||
                delete domainObjectStyles[itemId].conditionSetIdentifier;
 | 
			
		||||
                domainObjectStyles[itemId].styles = undefined;
 | 
			
		||||
                delete domainObjectStyles[itemId].styles;
 | 
			
		||||
            } else {
 | 
			
		||||
                domainObjectStyles.conditionSetIdentifier = undefined;
 | 
			
		||||
                delete domainObjectStyles.conditionSetIdentifier;
 | 
			
		||||
                domainObjectStyles.styles = undefined;
 | 
			
		||||
                delete domainObjectStyles.styles;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        setEditState(isEditing) {
 | 
			
		||||
            this.isEditing = isEditing;
 | 
			
		||||
        },
 | 
			
		||||
        initializeStaticStyle() {
 | 
			
		||||
            this.staticStyle = {
 | 
			
		||||
                style: Object.assign({}, this.initialStyles)
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        updateStaticStyle(staticStyle, property) {
 | 
			
		||||
            //update the static style for each of the layoutItems as well as each sub object item
 | 
			
		||||
            this.staticStyle = staticStyle;
 | 
			
		||||
            this.persist(this.domainObject, this.getDomainObjectStyle(this.domainObject, property, this.items));
 | 
			
		||||
            if (this.domainObjectsById) {
 | 
			
		||||
                const keys = Object.keys(this.domainObjectsById);
 | 
			
		||||
                keys.forEach(key => {
 | 
			
		||||
                    let domainObject = this.domainObjectsById[key];
 | 
			
		||||
                    this.persist(domainObject, this.getDomainObjectStyle(domainObject, property));
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            this.isStaticAndConditionalStyles = false;
 | 
			
		||||
            let foundIndex = this.mixedStyles.indexOf(property);
 | 
			
		||||
            if (foundIndex > -1) {
 | 
			
		||||
                this.mixedStyles.splice(foundIndex, 1);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        getDomainObjectStyle(domainObject, property, items) {
 | 
			
		||||
            let domainObjectStyles =  (domainObject.configuration && domainObject.configuration.objectStyles) || {};
 | 
			
		||||
 | 
			
		||||
            if (items) {
 | 
			
		||||
                items.forEach(item => {
 | 
			
		||||
                    let itemStaticStyle = {};
 | 
			
		||||
                    if (domainObjectStyles[item.id] && domainObjectStyles[item.id].staticStyle) {
 | 
			
		||||
                        itemStaticStyle = domainObjectStyles[item.id].staticStyle.style;
 | 
			
		||||
                    }
 | 
			
		||||
                    Object.keys(item.applicableStyles).forEach(key => {
 | 
			
		||||
                        if (property === key) {
 | 
			
		||||
                            itemStaticStyle[key] = this.staticStyle.style[key];
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    if (this.isStaticAndConditionalStyles) {
 | 
			
		||||
                        this.removeConditionalStyles(domainObjectStyles, item.id);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (_.isEmpty(itemStaticStyle)) {
 | 
			
		||||
                        itemStaticStyle = undefined;
 | 
			
		||||
                        domainObjectStyles[item.id] = undefined;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        domainObjectStyles[item.id] = Object.assign({}, { staticStyle: { style: itemStaticStyle } });
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                if (!domainObjectStyles.staticStyle) {
 | 
			
		||||
                    domainObjectStyles.staticStyle = {
 | 
			
		||||
                        style: {}
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (this.isStaticAndConditionalStyles) {
 | 
			
		||||
                    this.removeConditionalStyles(domainObjectStyles);
 | 
			
		||||
                }
 | 
			
		||||
                domainObjectStyles.staticStyle.style[property] = this.staticStyle.style[property];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return domainObjectStyles;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        persist(domainObject, style) {
 | 
			
		||||
            this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -22,38 +22,41 @@
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-style">
 | 
			
		||||
    <span class="c-style-thumb"
 | 
			
		||||
          :class="{ 'is-style-invisible': styleItem.style.isStyleInvisible }"
 | 
			
		||||
          :style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : styleItem.style ]"
 | 
			
		||||
    <span :class="[
 | 
			
		||||
              { 'is-style-invisible': styleItem.style.isStyleInvisible },
 | 
			
		||||
              { 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
 | 
			
		||||
          ]"
 | 
			
		||||
          :style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
 | 
			
		||||
          class="c-style-thumb"
 | 
			
		||||
    >
 | 
			
		||||
        <span class="c-style-thumb__text"
 | 
			
		||||
              :class="{ 'hide-nice': !styleItem.style.color }"
 | 
			
		||||
              :class="{ 'hide-nice': !hasProperty(styleItem.style.color) }"
 | 
			
		||||
        >
 | 
			
		||||
            ABC
 | 
			
		||||
        </span>
 | 
			
		||||
    </span>
 | 
			
		||||
    <span class="c-toolbar">
 | 
			
		||||
        <toolbar-color-picker v-if="styleItem.style.border"
 | 
			
		||||
        <toolbar-color-picker v-if="hasProperty(styleItem.style.border)"
 | 
			
		||||
                              class="c-style__toolbar-button--border-color u-menu-to--center"
 | 
			
		||||
                              :options="borderColorOption"
 | 
			
		||||
                              @change="updateStyleValue"
 | 
			
		||||
        />
 | 
			
		||||
        <toolbar-color-picker v-if="styleItem.style.backgroundColor"
 | 
			
		||||
        <toolbar-color-picker v-if="hasProperty(styleItem.style.backgroundColor)"
 | 
			
		||||
                              class="c-style__toolbar-button--background-color u-menu-to--center"
 | 
			
		||||
                              :options="backgroundColorOption"
 | 
			
		||||
                              @change="updateStyleValue"
 | 
			
		||||
        />
 | 
			
		||||
        <toolbar-color-picker v-if="styleItem.style.color"
 | 
			
		||||
        <toolbar-color-picker v-if="hasProperty(styleItem.style.color)"
 | 
			
		||||
                              class="c-style__toolbar-button--color u-menu-to--center"
 | 
			
		||||
                              :options="colorOption"
 | 
			
		||||
                              @change="updateStyleValue"
 | 
			
		||||
        />
 | 
			
		||||
        <toolbar-button v-if="styleItem.style.imageUrl !== undefined"
 | 
			
		||||
        <toolbar-button v-if="hasProperty(styleItem.style.imageUrl)"
 | 
			
		||||
                        class="c-style__toolbar-button--image-url"
 | 
			
		||||
                        :options="imageUrlOption"
 | 
			
		||||
                        @change="updateStyleValue"
 | 
			
		||||
        />
 | 
			
		||||
        <toolbar-toggle-button v-if="styleItem.style.isStyleInvisible !== undefined"
 | 
			
		||||
        <toolbar-toggle-button v-if="hasProperty(styleItem.style.isStyleInvisible)"
 | 
			
		||||
                               class="c-style__toolbar-button--toggle-visible"
 | 
			
		||||
                               :options="isStyleInvisibleOption"
 | 
			
		||||
                               @change="updateStyleValue"
 | 
			
		||||
@@ -68,6 +71,7 @@ import ToolbarColorPicker from "@/ui/toolbar/components/toolbar-color-picker.vue
 | 
			
		||||
import ToolbarButton from "@/ui/toolbar/components/toolbar-button.vue";
 | 
			
		||||
import ToolbarToggleButton from "@/ui/toolbar/components/toolbar-toggle-button.vue";
 | 
			
		||||
import {STYLE_CONSTANTS} from "@/plugins/condition/utils/constants";
 | 
			
		||||
import {getStylesWithoutNoneValue} from "@/plugins/condition/utils/styleUtils";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'StyleEditor',
 | 
			
		||||
@@ -83,37 +87,52 @@ export default {
 | 
			
		||||
        isEditing: {
 | 
			
		||||
            type: Boolean
 | 
			
		||||
        },
 | 
			
		||||
        mixedStyles: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
            default() {
 | 
			
		||||
                return [];
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        styleItem: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            required: true
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        itemStyle() {
 | 
			
		||||
            return getStylesWithoutNoneValue(this.styleItem.style);
 | 
			
		||||
        },
 | 
			
		||||
        borderColorOption() {
 | 
			
		||||
            let value = this.styleItem.style.border.replace('1px solid ', '');
 | 
			
		||||
            return {
 | 
			
		||||
                icon: 'icon-line-horz',
 | 
			
		||||
                title: STYLE_CONSTANTS.borderColorTitle,
 | 
			
		||||
                value: this.styleItem.style.border.replace('1px solid ', ''),
 | 
			
		||||
                value: this.normalizeValueForSwatch(value),
 | 
			
		||||
                property: 'border',
 | 
			
		||||
                isEditing: this.isEditing
 | 
			
		||||
                isEditing: this.isEditing,
 | 
			
		||||
                nonSpecific: this.mixedStyles.indexOf('border') > -1
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        backgroundColorOption() {
 | 
			
		||||
            let value = this.styleItem.style.backgroundColor;
 | 
			
		||||
            return {
 | 
			
		||||
                icon: 'icon-paint-bucket',
 | 
			
		||||
                title: STYLE_CONSTANTS.backgroundColorTitle,
 | 
			
		||||
                value: this.styleItem.style.backgroundColor,
 | 
			
		||||
                value: this.normalizeValueForSwatch(value),
 | 
			
		||||
                property: 'backgroundColor',
 | 
			
		||||
                isEditing: this.isEditing
 | 
			
		||||
                isEditing: this.isEditing,
 | 
			
		||||
                nonSpecific: this.mixedStyles.indexOf('backgroundColor') > -1
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        colorOption() {
 | 
			
		||||
            let value = this.styleItem.style.color;
 | 
			
		||||
            return {
 | 
			
		||||
                icon: 'icon-font',
 | 
			
		||||
                title: STYLE_CONSTANTS.textColorTitle,
 | 
			
		||||
                value: this.styleItem.style.color,
 | 
			
		||||
                value: this.normalizeValueForSwatch(value),
 | 
			
		||||
                property: 'color',
 | 
			
		||||
                isEditing: this.isEditing
 | 
			
		||||
                isEditing: this.isEditing,
 | 
			
		||||
                nonSpecific: this.mixedStyles.indexOf('color') > -1
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        imageUrlOption() {
 | 
			
		||||
@@ -138,7 +157,8 @@ export default {
 | 
			
		||||
                property: 'imageUrl',
 | 
			
		||||
                formKeys: ['url'],
 | 
			
		||||
                value: {url: this.styleItem.style.imageUrl},
 | 
			
		||||
                isEditing: this.isEditing
 | 
			
		||||
                isEditing: this.isEditing,
 | 
			
		||||
                nonSpecific: this.mixedStyles.indexOf('imageUrl') > -1
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        isStyleInvisibleOption() {
 | 
			
		||||
@@ -163,7 +183,23 @@ export default {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        hasProperty(property) {
 | 
			
		||||
            return property !== undefined;
 | 
			
		||||
        },
 | 
			
		||||
        normalizeValueForSwatch(value) {
 | 
			
		||||
            if (value && value.indexOf('__no_value') > -1) {
 | 
			
		||||
                return value.replace('__no_value', 'transparent');
 | 
			
		||||
            }
 | 
			
		||||
            return value;
 | 
			
		||||
        },
 | 
			
		||||
        normalizeValueForStyle(value) {
 | 
			
		||||
            if (value && value === 'transparent') {
 | 
			
		||||
                return '__no_value';
 | 
			
		||||
            }
 | 
			
		||||
            return value;
 | 
			
		||||
        },
 | 
			
		||||
        updateStyleValue(value, item) {
 | 
			
		||||
            value = this.normalizeValueForStyle(value);
 | 
			
		||||
            if (item.property === 'border') {
 | 
			
		||||
                value = '1px solid ' + value;
 | 
			
		||||
            }
 | 
			
		||||
@@ -172,7 +208,7 @@ export default {
 | 
			
		||||
            } else {
 | 
			
		||||
                this.styleItem.style[item.property] = value;
 | 
			
		||||
            }
 | 
			
		||||
            this.$emit('persist', this.styleItem);
 | 
			
		||||
            this.$emit('persist', this.styleItem, item.property);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,11 +20,11 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import EventEmitter from 'EventEmitter';
 | 
			
		||||
import {OPERATIONS} from '../utils/operations';
 | 
			
		||||
import {computeCondition} from "@/plugins/condition/utils/evaluator";
 | 
			
		||||
import TelemetryCriterion from './TelemetryCriterion';
 | 
			
		||||
import { evaluateResults } from "../utils/evaluator";
 | 
			
		||||
import { getLatestTimestamp } from '../utils/time';
 | 
			
		||||
 | 
			
		||||
export default class TelemetryCriterion extends EventEmitter {
 | 
			
		||||
export default class AllTelemetryCriterion extends TelemetryCriterion {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Subscribes/Unsubscribes to telemetry and emits the result
 | 
			
		||||
@@ -34,23 +34,35 @@ export default class TelemetryCriterion extends EventEmitter {
 | 
			
		||||
     * @param openmct
 | 
			
		||||
     */
 | 
			
		||||
    constructor(telemetryDomainObjectDefinition, openmct) {
 | 
			
		||||
        super();
 | 
			
		||||
        super(telemetryDomainObjectDefinition, openmct);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.objectAPI = this.openmct.objects;
 | 
			
		||||
        this.telemetryAPI = this.openmct.telemetry;
 | 
			
		||||
        this.timeAPI = this.openmct.time;
 | 
			
		||||
        this.id = telemetryDomainObjectDefinition.id;
 | 
			
		||||
        this.telemetry = telemetryDomainObjectDefinition.telemetry;
 | 
			
		||||
        this.operation = telemetryDomainObjectDefinition.operation;
 | 
			
		||||
        this.telemetryObjects = Object.assign({}, telemetryDomainObjectDefinition.telemetryObjects);
 | 
			
		||||
        this.input = telemetryDomainObjectDefinition.input;
 | 
			
		||||
        this.metadata = telemetryDomainObjectDefinition.metadata;
 | 
			
		||||
    initialize() {
 | 
			
		||||
        this.telemetryObjects = { ...this.telemetryDomainObjectDefinition.telemetryObjects };
 | 
			
		||||
        this.telemetryDataCache = {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isValid() {
 | 
			
		||||
        return (this.telemetry === 'any' || this.telemetry === 'all') && this.metadata && this.operation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateTelemetry(telemetryObjects) {
 | 
			
		||||
        this.telemetryObjects = Object.assign({}, telemetryObjects);
 | 
			
		||||
        this.telemetryObjects = { ...telemetryObjects };
 | 
			
		||||
        this.removeTelemetryDataCache();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    removeTelemetryDataCache() {
 | 
			
		||||
        const telemetryCacheIds = Object.keys(this.telemetryDataCache);
 | 
			
		||||
        Object.values(this.telemetryObjects).forEach(telemetryObject => {
 | 
			
		||||
            const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
 | 
			
		||||
            const foundIndex = telemetryCacheIds.indexOf(id);
 | 
			
		||||
            if (foundIndex > -1) {
 | 
			
		||||
                telemetryCacheIds.splice(foundIndex, 1);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        telemetryCacheIds.forEach(id => {
 | 
			
		||||
            delete (this.telemetryDataCache[id]);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    formatData(data, telemetryObjects) {
 | 
			
		||||
@@ -68,105 +80,87 @@ export default class TelemetryCriterion extends EventEmitter {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const datum = {
 | 
			
		||||
            result: computeCondition(this.telemetryDataCache, this.telemetry === 'all')
 | 
			
		||||
            result: evaluateResults(Object.values(this.telemetryDataCache), this.telemetry)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (data) {
 | 
			
		||||
            // TODO check back to see if we should format times here
 | 
			
		||||
            this.timeAPI.getAllTimeSystems().forEach(timeSystem => {
 | 
			
		||||
            this.openmct.time.getAllTimeSystems().forEach(timeSystem => {
 | 
			
		||||
                datum[timeSystem.key] = data[timeSystem.key]
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return datum;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleSubscription(data, telemetryObjects) {
 | 
			
		||||
        if(this.isValid()) {
 | 
			
		||||
            this.emitEvent('criterionResultUpdated', this.formatData(data, telemetryObjects));
 | 
			
		||||
        } else {
 | 
			
		||||
            this.emitEvent('criterionResultUpdated', this.formatData({}, telemetryObjects));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    getResult(data, telemetryObjects) {
 | 
			
		||||
        const validatedData = this.isValid() ? data : {};
 | 
			
		||||
 | 
			
		||||
    findOperation(operation) {
 | 
			
		||||
        for (let i=0; i < OPERATIONS.length; i++) {
 | 
			
		||||
            if (operation === OPERATIONS[i].name) {
 | 
			
		||||
                return OPERATIONS[i].operation;
 | 
			
		||||
            }
 | 
			
		||||
        if (validatedData) {
 | 
			
		||||
            this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    computeResult(data) {
 | 
			
		||||
        let result = false;
 | 
			
		||||
        if (data) {
 | 
			
		||||
            let comparator = this.findOperation(this.operation);
 | 
			
		||||
            let params = [];
 | 
			
		||||
            params.push(data[this.metadata]);
 | 
			
		||||
            if (this.input instanceof Array && this.input.length) {
 | 
			
		||||
                this.input.forEach(input => params.push(input));
 | 
			
		||||
        Object.values(telemetryObjects).forEach(telemetryObject => {
 | 
			
		||||
            const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
 | 
			
		||||
            if (this.telemetryDataCache[id] === undefined) {
 | 
			
		||||
                this.telemetryDataCache[id] = false;
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof comparator === 'function') {
 | 
			
		||||
                result = comparator(params);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    emitEvent(eventName, data) {
 | 
			
		||||
        this.emit(eventName, {
 | 
			
		||||
            id: this.id,
 | 
			
		||||
            data: data
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isValid() {
 | 
			
		||||
        return (this.telemetry === 'any' || this.telemetry === 'all') && this.metadata && this.operation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    requestLAD(options) {
 | 
			
		||||
        options = Object.assign({},
 | 
			
		||||
            options,
 | 
			
		||||
            {
 | 
			
		||||
                strategy: 'latest',
 | 
			
		||||
                size: 1
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    requestLAD(telemetryObjects) {
 | 
			
		||||
        const options = {
 | 
			
		||||
            strategy: 'latest',
 | 
			
		||||
            size: 1
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (!this.isValid()) {
 | 
			
		||||
            return this.formatData({}, options.telemetryObjects);
 | 
			
		||||
            return this.formatData({}, telemetryObjects);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let keys = Object.keys(Object.assign({}, options.telemetryObjects));
 | 
			
		||||
        let keys = Object.keys(Object.assign({}, telemetryObjects));
 | 
			
		||||
        const telemetryRequests = keys
 | 
			
		||||
            .map(key => this.telemetryAPI.request(
 | 
			
		||||
                options.telemetryObjects[key],
 | 
			
		||||
            .map(key => this.openmct.telemetry.request(
 | 
			
		||||
                telemetryObjects[key],
 | 
			
		||||
                options
 | 
			
		||||
            ));
 | 
			
		||||
 | 
			
		||||
        let telemetryDataCache = {};
 | 
			
		||||
        return Promise.all(telemetryRequests)
 | 
			
		||||
            .then(telemetryRequestsResults => {
 | 
			
		||||
                let latestDatum;
 | 
			
		||||
                let latestTimestamp;
 | 
			
		||||
                const timeSystems = this.openmct.time.getAllTimeSystems();
 | 
			
		||||
                const timeSystem = this.openmct.time.timeSystem();
 | 
			
		||||
 | 
			
		||||
                telemetryRequestsResults.forEach((results, index) => {
 | 
			
		||||
                    latestDatum = results.length ? results[results.length - 1] : {};
 | 
			
		||||
                    if (index < telemetryRequestsResults.length-1) {
 | 
			
		||||
                        if (latestDatum) {
 | 
			
		||||
                            this.telemetryDataCache[latestDatum.id] = this.computeResult(latestDatum);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    const latestDatum = results.length ? results[results.length - 1] : {};
 | 
			
		||||
                    const datumId = keys[index];
 | 
			
		||||
                    const normalizedDatum = this.createNormalizedDatum(latestDatum, telemetryObjects[datumId]);
 | 
			
		||||
 | 
			
		||||
                    telemetryDataCache[datumId] = this.computeResult(normalizedDatum);
 | 
			
		||||
 | 
			
		||||
                    latestTimestamp = getLatestTimestamp(
 | 
			
		||||
                        latestTimestamp,
 | 
			
		||||
                        normalizedDatum,
 | 
			
		||||
                        timeSystems,
 | 
			
		||||
                        timeSystem
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                const datum = {
 | 
			
		||||
                    result: evaluateResults(Object.values(telemetryDataCache), this.telemetry),
 | 
			
		||||
                    ...latestTimestamp
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                return {
 | 
			
		||||
                    id: this.id,
 | 
			
		||||
                    data: this.formatData(latestDatum, options.telemetryObjects)
 | 
			
		||||
                    data: datum
 | 
			
		||||
                };
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
        this.emitEvent('criterionRemoved');
 | 
			
		||||
        delete this.telemetryObjects;
 | 
			
		||||
        delete this.telemetryDataCache;
 | 
			
		||||
        delete this.telemetryObjectIdAsString;
 | 
			
		||||
        delete this.telemetryObject;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import EventEmitter from 'EventEmitter';
 | 
			
		||||
import {OPERATIONS} from '../utils/operations';
 | 
			
		||||
import { OPERATIONS } from '../utils/operations';
 | 
			
		||||
 | 
			
		||||
export default class TelemetryCriterion extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
@@ -36,44 +36,89 @@ export default class TelemetryCriterion extends EventEmitter {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.objectAPI = this.openmct.objects;
 | 
			
		||||
        this.telemetryAPI = this.openmct.telemetry;
 | 
			
		||||
        this.timeAPI = this.openmct.time;
 | 
			
		||||
        this.telemetryDomainObjectDefinition = telemetryDomainObjectDefinition;
 | 
			
		||||
        this.id = telemetryDomainObjectDefinition.id;
 | 
			
		||||
        this.telemetry = telemetryDomainObjectDefinition.telemetry;
 | 
			
		||||
        this.operation = telemetryDomainObjectDefinition.operation;
 | 
			
		||||
        this.input = telemetryDomainObjectDefinition.input;
 | 
			
		||||
        this.metadata = telemetryDomainObjectDefinition.metadata;
 | 
			
		||||
        this.telemetryObject = telemetryDomainObjectDefinition.telemetryObject;
 | 
			
		||||
        this.telemetryObjectIdAsString = this.objectAPI.makeKeyString(telemetryDomainObjectDefinition.telemetry);
 | 
			
		||||
        this.on(`subscription:${this.telemetryObjectIdAsString}`, this.handleSubscription);
 | 
			
		||||
        this.result = undefined;
 | 
			
		||||
 | 
			
		||||
        this.initialize();
 | 
			
		||||
        this.emitEvent('criterionUpdated', this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initialize() {
 | 
			
		||||
        this.telemetryObject = this.telemetryDomainObjectDefinition.telemetryObject;
 | 
			
		||||
        this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isValid() {
 | 
			
		||||
        return this.telemetryObject && this.metadata && this.operation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateTelemetry(telemetryObjects) {
 | 
			
		||||
        this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createNormalizedDatum(telemetryDatum, endpoint) {
 | 
			
		||||
        const id = this.openmct.objects.makeKeyString(endpoint.identifier);
 | 
			
		||||
        const metadata = this.openmct.telemetry.getMetadata(endpoint).valueMetadatas;
 | 
			
		||||
 | 
			
		||||
        const normalizedDatum = Object.values(metadata).reduce((datum, metadatum) => {
 | 
			
		||||
            const formatter = this.openmct.telemetry.getValueFormatter(metadatum);
 | 
			
		||||
            datum[metadatum.key] = formatter.parse(telemetryDatum[metadatum.source]);
 | 
			
		||||
            return datum;
 | 
			
		||||
        }, {});
 | 
			
		||||
 | 
			
		||||
        normalizedDatum.id = id;
 | 
			
		||||
 | 
			
		||||
        return normalizedDatum;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    formatData(data) {
 | 
			
		||||
        const datum = {
 | 
			
		||||
            result: this.computeResult(data)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (data) {
 | 
			
		||||
            // TODO check back to see if we should format times here
 | 
			
		||||
            this.timeAPI.getAllTimeSystems().forEach(timeSystem => {
 | 
			
		||||
            this.openmct.time.getAllTimeSystems().forEach(timeSystem => {
 | 
			
		||||
                datum[timeSystem.key] = data[timeSystem.key]
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return datum;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleSubscription(data) {
 | 
			
		||||
        if(this.isValid()) {
 | 
			
		||||
            this.emitEvent('criterionResultUpdated', this.formatData(data));
 | 
			
		||||
        } else {
 | 
			
		||||
            this.emitEvent('criterionResultUpdated', this.formatData({}));
 | 
			
		||||
    getResult(data) {
 | 
			
		||||
        const validatedData = this.isValid() ? data : {};
 | 
			
		||||
        this.result = this.computeResult(validatedData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    requestLAD() {
 | 
			
		||||
        const options = {
 | 
			
		||||
            strategy: 'latest',
 | 
			
		||||
            size: 1
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (!this.isValid()) {
 | 
			
		||||
            return {
 | 
			
		||||
                id: this.id,
 | 
			
		||||
                data: this.formatData({})
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.openmct.telemetry.request(
 | 
			
		||||
            this.telemetryObject,
 | 
			
		||||
            options
 | 
			
		||||
        ).then(results => {
 | 
			
		||||
            const latestDatum = results.length ? results[results.length - 1] : {};
 | 
			
		||||
            const normalizedDatum = this.createNormalizedDatum(latestDatum, this.telemetryObject);
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                id: this.id,
 | 
			
		||||
                data: this.formatData(normalizedDatum)
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    findOperation(operation) {
 | 
			
		||||
@@ -95,7 +140,7 @@ export default class TelemetryCriterion extends EventEmitter {
 | 
			
		||||
                this.input.forEach(input => params.push(input));
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof comparator === 'function') {
 | 
			
		||||
                result = comparator(params);
 | 
			
		||||
                result = !!comparator(params);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
@@ -108,42 +153,9 @@ export default class TelemetryCriterion extends EventEmitter {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isValid() {
 | 
			
		||||
        return this.telemetryObject && this.metadata && this.operation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    requestLAD(options) {
 | 
			
		||||
        options = Object.assign({},
 | 
			
		||||
            options,
 | 
			
		||||
            {
 | 
			
		||||
                strategy: 'latest',
 | 
			
		||||
                size: 1
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (!this.isValid()) {
 | 
			
		||||
            return {
 | 
			
		||||
                id: this.id,
 | 
			
		||||
                data: this.formatData({})
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.telemetryAPI.request(
 | 
			
		||||
            this.telemetryObject,
 | 
			
		||||
            options
 | 
			
		||||
        ).then(results => {
 | 
			
		||||
            const latestDatum = results.length ? results[results.length - 1] : {};
 | 
			
		||||
            return {
 | 
			
		||||
                id: this.id,
 | 
			
		||||
                data: this.formatData(latestDatum)
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
        this.off(`subscription:${this.telemetryObjectIdAsString}`, this.handleSubscription);
 | 
			
		||||
        this.emitEvent('criterionRemoved');
 | 
			
		||||
        delete this.telemetryObjectIdAsString;
 | 
			
		||||
        delete this.telemetryObject;
 | 
			
		||||
        delete this.telemetryObjectIdAsString;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ describe("The telemetry criterion", function () {
 | 
			
		||||
                    key: "testSource",
 | 
			
		||||
                    source: "value",
 | 
			
		||||
                    name: "Test",
 | 
			
		||||
                    format: "enum"
 | 
			
		||||
                    format: "string"
 | 
			
		||||
                }]
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
@@ -80,8 +80,9 @@ describe("The telemetry criterion", function () {
 | 
			
		||||
        testCriterionDefinition = {
 | 
			
		||||
            id: 'test-criterion-id',
 | 
			
		||||
            telemetry: openmct.objects.makeKeyString(testTelemetryObject.identifier),
 | 
			
		||||
            operation: 'lessThan',
 | 
			
		||||
            metadata: 'sin',
 | 
			
		||||
            operation: 'textContains',
 | 
			
		||||
            metadata: 'value',
 | 
			
		||||
            input: ['Hell'],
 | 
			
		||||
            telemetryObject: testTelemetryObject
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@@ -100,12 +101,21 @@ describe("The telemetry criterion", function () {
 | 
			
		||||
        expect(telemetryCriterion.telemetryObjectIdAsString).toEqual(testTelemetryObject.identifier.key);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("updates and emits event on new data from telemetry providers", function () {
 | 
			
		||||
        spyOn(telemetryCriterion, 'emitEvent').and.callThrough();
 | 
			
		||||
        telemetryCriterion.handleSubscription({
 | 
			
		||||
    it("returns a result on new data from relevant telemetry providers", function () {
 | 
			
		||||
        telemetryCriterion.getResult({
 | 
			
		||||
            value: 'Hello',
 | 
			
		||||
            utc: 'Hi'
 | 
			
		||||
            utc: 'Hi',
 | 
			
		||||
            id: testTelemetryObject.identifier.key
 | 
			
		||||
        });
 | 
			
		||||
        expect(telemetryCriterion.emitEvent).toHaveBeenCalled();
 | 
			
		||||
        expect(telemetryCriterion.result).toBeTrue();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // it("does not return a result on new data from irrelavant telemetry providers", function () {
 | 
			
		||||
    //     telemetryCriterion.getResult({
 | 
			
		||||
    //         value: 'Hello',
 | 
			
		||||
    //         utc: 'Hi',
 | 
			
		||||
    //         id: '1234'
 | 
			
		||||
    //     });
 | 
			
		||||
    //     expect(telemetryCriterion.result).toBeFalse();
 | 
			
		||||
    // });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -19,36 +19,50 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import { TRIGGER } from "./constants";
 | 
			
		||||
 | 
			
		||||
export const computeCondition = (resultMap, allMustBeTrue) => {
 | 
			
		||||
    let result = false;
 | 
			
		||||
    for (let key in resultMap) {
 | 
			
		||||
        if (resultMap.hasOwnProperty(key)) {
 | 
			
		||||
            result = resultMap[key];
 | 
			
		||||
            if (allMustBeTrue && !result) {
 | 
			
		||||
                //If we want all conditions to be true, then even one negative result should break.
 | 
			
		||||
                break;
 | 
			
		||||
            } else if (!allMustBeTrue && result) {
 | 
			
		||||
                //If we want at least one condition to be true, then even one positive result should break.
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
export const evaluateResults = (results, trigger) => {
 | 
			
		||||
    if (trigger && trigger === TRIGGER.XOR) {
 | 
			
		||||
        return matchExact(results, 1);
 | 
			
		||||
    } else if (trigger && trigger === TRIGGER.NOT) {
 | 
			
		||||
        return matchExact(results, 0);
 | 
			
		||||
    } else if (trigger && trigger === TRIGGER.ALL) {
 | 
			
		||||
        return matchAll(results);
 | 
			
		||||
    } else {
 | 
			
		||||
        return matchAny(results);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function matchAll(results) {
 | 
			
		||||
    for (const result of results) {
 | 
			
		||||
        if (!result) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//Returns true only if limit number of results are satisfied
 | 
			
		||||
export const computeConditionByLimit = (resultMap, limit) => {
 | 
			
		||||
    let trueCount = 0;
 | 
			
		||||
    for (let key in resultMap) {
 | 
			
		||||
        if (resultMap.hasOwnProperty(key)) {
 | 
			
		||||
            if (resultMap[key]) {
 | 
			
		||||
                trueCount++;
 | 
			
		||||
            }
 | 
			
		||||
            if (trueCount > limit) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function matchAny(results) {
 | 
			
		||||
    for (const result of results) {
 | 
			
		||||
        if (result) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return trueCount === limit;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function matchExact(results, target) {
 | 
			
		||||
    let matches = 0;
 | 
			
		||||
    for (const result of results) {
 | 
			
		||||
        if (result) {
 | 
			
		||||
            matches++;
 | 
			
		||||
        }
 | 
			
		||||
        if (matches > target) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return matches === target;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,47 +20,185 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import { computeConditionByLimit } from "./evaluator";
 | 
			
		||||
import { evaluateResults } from './evaluator';
 | 
			
		||||
import { TRIGGER } from './constants';
 | 
			
		||||
 | 
			
		||||
describe('evaluate results based on trigger', function () {
 | 
			
		||||
describe('evaluate results', () => {
 | 
			
		||||
    // const allTrue = [true, true, true, true, true];
 | 
			
		||||
    // const oneTrue = [false, false, false, false, true];
 | 
			
		||||
    // const multipleTrue = [false, true, false, true, false];
 | 
			
		||||
    // const noneTrue = [false, false, false, false, false];
 | 
			
		||||
    // const allTrueWithUndefined = [true, true, true, undefined, true];
 | 
			
		||||
    // const oneTrueWithUndefined = [undefined, undefined, undefined, undefined, true];
 | 
			
		||||
    // const multipleTrueWithUndefined = [true, undefined, true, undefined, true];
 | 
			
		||||
    // const allUndefined = [undefined, undefined, undefined, undefined, undefined];
 | 
			
		||||
    // const singleTrue = [true];
 | 
			
		||||
    // const singleFalse = [false];
 | 
			
		||||
    // const singleUndefined = [undefined];
 | 
			
		||||
    // const empty = [];
 | 
			
		||||
 | 
			
		||||
    it('should evaluate to true if trigger is NOT', () => {
 | 
			
		||||
        const results = {
 | 
			
		||||
            result: false,
 | 
			
		||||
            result1: false,
 | 
			
		||||
            result2: false
 | 
			
		||||
        };
 | 
			
		||||
        const result = computeConditionByLimit(results, 0);
 | 
			
		||||
        expect(result).toBeTrue();
 | 
			
		||||
    const tests = [
 | 
			
		||||
        {
 | 
			
		||||
            name: 'allTrue',
 | 
			
		||||
            values: [true, true, true, true, true],
 | 
			
		||||
            any: true,
 | 
			
		||||
            all: true,
 | 
			
		||||
            not: false,
 | 
			
		||||
            xor: false
 | 
			
		||||
        }, {
 | 
			
		||||
            name: 'oneTrue',
 | 
			
		||||
            values: [false, false, false, false, true],
 | 
			
		||||
            any: true,
 | 
			
		||||
            all: false,
 | 
			
		||||
            not: false,
 | 
			
		||||
            xor: true
 | 
			
		||||
        }, {
 | 
			
		||||
            name: 'multipleTrue',
 | 
			
		||||
            values: [false, true, false, true, false],
 | 
			
		||||
            any: true,
 | 
			
		||||
            all: false,
 | 
			
		||||
            not: false,
 | 
			
		||||
            xor: false
 | 
			
		||||
        }, {
 | 
			
		||||
            name: 'noneTrue',
 | 
			
		||||
            values: [false, false, false, false, false],
 | 
			
		||||
            any: false,
 | 
			
		||||
            all: false,
 | 
			
		||||
            not: true,
 | 
			
		||||
            xor: false
 | 
			
		||||
        }, {
 | 
			
		||||
            name: 'allTrueWithUndefined',
 | 
			
		||||
            values: [true, true, true, undefined, true],
 | 
			
		||||
            any: true,
 | 
			
		||||
            all: false,
 | 
			
		||||
            not: false,
 | 
			
		||||
            xor: false
 | 
			
		||||
        }, {
 | 
			
		||||
            name: 'oneTrueWithUndefined',
 | 
			
		||||
            values: [undefined, undefined, undefined, undefined, true],
 | 
			
		||||
            any: true,
 | 
			
		||||
            all: false,
 | 
			
		||||
            not: false,
 | 
			
		||||
            xor: true
 | 
			
		||||
        }, {
 | 
			
		||||
            name: 'multipleTrueWithUndefined',
 | 
			
		||||
            values: [true, undefined, true, undefined, true],
 | 
			
		||||
            any: true,
 | 
			
		||||
            all: false,
 | 
			
		||||
            not: false,
 | 
			
		||||
            xor: false
 | 
			
		||||
        }, {
 | 
			
		||||
            name: 'allUndefined',
 | 
			
		||||
            values: [undefined, undefined, undefined, undefined, undefined],
 | 
			
		||||
            any: false,
 | 
			
		||||
            all: false,
 | 
			
		||||
            not: true,
 | 
			
		||||
            xor: false
 | 
			
		||||
        }, {
 | 
			
		||||
            name: 'singleTrue',
 | 
			
		||||
            values: [true],
 | 
			
		||||
            any: true,
 | 
			
		||||
            all: true,
 | 
			
		||||
            not: false,
 | 
			
		||||
            xor: true
 | 
			
		||||
        }, {
 | 
			
		||||
            name: 'singleFalse',
 | 
			
		||||
            values: [false],
 | 
			
		||||
            any: false,
 | 
			
		||||
            all: false,
 | 
			
		||||
            not: true,
 | 
			
		||||
            xor: false
 | 
			
		||||
        }, {
 | 
			
		||||
            name: 'singleUndefined',
 | 
			
		||||
            values: [undefined],
 | 
			
		||||
            any: false,
 | 
			
		||||
            all: false,
 | 
			
		||||
            not: true,
 | 
			
		||||
            xor: false
 | 
			
		||||
        }
 | 
			
		||||
        // , {
 | 
			
		||||
        //     name: 'empty',
 | 
			
		||||
        //     values: [],
 | 
			
		||||
        //     any: false,
 | 
			
		||||
        //     all: false,
 | 
			
		||||
        //     not: true,
 | 
			
		||||
        //     xor: false
 | 
			
		||||
        // }
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    describe(`based on trigger ${TRIGGER.ANY}`, () => {
 | 
			
		||||
        it('should evaluate to expected result', () => {
 | 
			
		||||
            tests.forEach(test => {
 | 
			
		||||
                const result = evaluateResults(test.values, TRIGGER.ANY);
 | 
			
		||||
                expect(result).toEqual(test[TRIGGER.ANY])
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate to false if trigger is NOT', () => {
 | 
			
		||||
        const results = {
 | 
			
		||||
            result: true,
 | 
			
		||||
            result1: false,
 | 
			
		||||
            result2: false
 | 
			
		||||
        };
 | 
			
		||||
        const result = computeConditionByLimit(results, 0);
 | 
			
		||||
        expect(result).toBeFalse();
 | 
			
		||||
    describe(`based on trigger ${TRIGGER.ALL}`, () => {
 | 
			
		||||
        it('should evaluate to expected result', () => {
 | 
			
		||||
            tests.forEach(test => {
 | 
			
		||||
                const result = evaluateResults(test.values, TRIGGER.ALL);
 | 
			
		||||
                expect(result).toEqual(test[TRIGGER.ALL])
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate to true if trigger is XOR', () => {
 | 
			
		||||
        const results = {
 | 
			
		||||
            result: false,
 | 
			
		||||
            result1: true,
 | 
			
		||||
            result2: false
 | 
			
		||||
        };
 | 
			
		||||
        const result = computeConditionByLimit(results, 1);
 | 
			
		||||
        expect(result).toBeTrue();
 | 
			
		||||
    describe(`based on trigger ${TRIGGER.NOT}`, () => {
 | 
			
		||||
        it('should evaluate to expected result', () => {
 | 
			
		||||
            tests.forEach(test => {
 | 
			
		||||
                const result = evaluateResults(test.values, TRIGGER.NOT);
 | 
			
		||||
                expect(result).toEqual(test[TRIGGER.NOT])
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate to false if trigger is XOR', () => {
 | 
			
		||||
        const results = {
 | 
			
		||||
            result: false,
 | 
			
		||||
            result1: true,
 | 
			
		||||
            result2: true
 | 
			
		||||
        };
 | 
			
		||||
        const result = computeConditionByLimit(results, 1);
 | 
			
		||||
        expect(result).toBeFalse();
 | 
			
		||||
    describe(`based on trigger ${TRIGGER.XOR}`, () => {
 | 
			
		||||
        it('should evaluate to expected result', () => {
 | 
			
		||||
            tests.forEach(test => {
 | 
			
		||||
                const result = evaluateResults(test.values, TRIGGER.XOR);
 | 
			
		||||
                expect(result).toEqual(test[TRIGGER.XOR])
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // it('should evaluate to true if trigger is NOT', () => {
 | 
			
		||||
    //     const results = {
 | 
			
		||||
    //         result: false,
 | 
			
		||||
    //         result1: false,
 | 
			
		||||
    //         result2: false
 | 
			
		||||
    //     };
 | 
			
		||||
    //     const result = computeConditionByLimit(results, 0);
 | 
			
		||||
    //     expect(result).toBeTrue();
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
    // it('should evaluate to false if trigger is NOT', () => {
 | 
			
		||||
    //     const results = {
 | 
			
		||||
    //         result: true,
 | 
			
		||||
    //         result1: false,
 | 
			
		||||
    //         result2: false
 | 
			
		||||
    //     };
 | 
			
		||||
    //     const result = computeConditionByLimit(results, 0);
 | 
			
		||||
    //     expect(result).toBeFalse();
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
    // it('should evaluate to true if trigger is XOR', () => {
 | 
			
		||||
    //     const results = {
 | 
			
		||||
    //         result: false,
 | 
			
		||||
    //         result1: true,
 | 
			
		||||
    //         result2: false
 | 
			
		||||
    //     };
 | 
			
		||||
    //     const result = computeConditionByLimit(results, 1);
 | 
			
		||||
    //     expect(result).toBeTrue();
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
    // it('should evaluate to false if trigger is XOR', () => {
 | 
			
		||||
    //     const results = {
 | 
			
		||||
    //         result: false,
 | 
			
		||||
    //         result1: true,
 | 
			
		||||
    //         result2: true
 | 
			
		||||
    //     };
 | 
			
		||||
    //     const result = computeConditionByLimit(results, 1);
 | 
			
		||||
    //     expect(result).toBeFalse();
 | 
			
		||||
    // });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,18 @@
 | 
			
		||||
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
const convertToNumbers = (input) => {
 | 
			
		||||
    let numberInputs = [];
 | 
			
		||||
    input.forEach(inputValue => numberInputs.push(Number(inputValue)));
 | 
			
		||||
    return numberInputs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const convertToStrings = (input) => {
 | 
			
		||||
    let stringInputs = [];
 | 
			
		||||
    input.forEach(inputValue => stringInputs.push(inputValue !== undefined ? inputValue.toString() : ''));
 | 
			
		||||
    return stringInputs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const OPERATIONS = [
 | 
			
		||||
    {
 | 
			
		||||
        name: 'equalTo',
 | 
			
		||||
@@ -98,8 +110,7 @@ export const OPERATIONS = [
 | 
			
		||||
    {
 | 
			
		||||
        name: 'between',
 | 
			
		||||
        operation: function (input) {
 | 
			
		||||
            let numberInputs = [];
 | 
			
		||||
            input.forEach(inputValue => numberInputs.push(Number(inputValue)));
 | 
			
		||||
            let numberInputs = convertToNumbers(input);
 | 
			
		||||
            let larger = Math.max(...numberInputs.slice(1,3));
 | 
			
		||||
            let smaller = Math.min(...numberInputs.slice(1,3));
 | 
			
		||||
            return (numberInputs[0] > smaller) && (numberInputs[0] < larger);
 | 
			
		||||
@@ -114,8 +125,7 @@ export const OPERATIONS = [
 | 
			
		||||
    {
 | 
			
		||||
        name: 'notBetween',
 | 
			
		||||
        operation: function (input) {
 | 
			
		||||
            let numberInputs = [];
 | 
			
		||||
            input.forEach(inputValue => numberInputs.push(Number(inputValue)));
 | 
			
		||||
            let numberInputs = convertToNumbers(input);
 | 
			
		||||
            let larger = Math.max(...numberInputs.slice(1,3));
 | 
			
		||||
            let smaller = Math.min(...numberInputs.slice(1,3));
 | 
			
		||||
            return (numberInputs[0] < smaller) || (numberInputs[0] > larger);
 | 
			
		||||
@@ -214,7 +224,8 @@ export const OPERATIONS = [
 | 
			
		||||
    {
 | 
			
		||||
        name: 'enumValueIs',
 | 
			
		||||
        operation: function (input) {
 | 
			
		||||
            return input[0] === input[1];
 | 
			
		||||
            let stringInputs = convertToStrings(input);
 | 
			
		||||
            return stringInputs[0] === stringInputs[1];
 | 
			
		||||
        },
 | 
			
		||||
        text: 'is',
 | 
			
		||||
        appliesTo: ['enum'],
 | 
			
		||||
@@ -226,7 +237,8 @@ export const OPERATIONS = [
 | 
			
		||||
    {
 | 
			
		||||
        name: 'enumValueIsNot',
 | 
			
		||||
        operation: function (input) {
 | 
			
		||||
            return input[0] !== input[1];
 | 
			
		||||
            let stringInputs = convertToStrings(input);
 | 
			
		||||
            return stringInputs[0] !== stringInputs[1];
 | 
			
		||||
        },
 | 
			
		||||
        text: 'is not',
 | 
			
		||||
        appliesTo: ['enum'],
 | 
			
		||||
@@ -238,9 +250,10 @@ export const OPERATIONS = [
 | 
			
		||||
    {
 | 
			
		||||
        name: 'valueIs',
 | 
			
		||||
        operation: function (input) {
 | 
			
		||||
            const lhsValue = input[0] !== undefined ? input[0].toString() : '';
 | 
			
		||||
            if (input[1]) {
 | 
			
		||||
                const values = input[1].split(',');
 | 
			
		||||
                return values.find((value) => input[0].toString() === _.trim(value.toString()));
 | 
			
		||||
                return values.find((value) => lhsValue === _.trim(value.toString()));
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
@@ -254,9 +267,10 @@ export const OPERATIONS = [
 | 
			
		||||
    {
 | 
			
		||||
        name: 'valueIsNot',
 | 
			
		||||
        operation: function (input) {
 | 
			
		||||
            const lhsValue = input[0] !== undefined ? input[0].toString() : '';
 | 
			
		||||
            if (input[1]) {
 | 
			
		||||
                const values = input[1].split(',');
 | 
			
		||||
                const found = values.find((value) => input[0].toString() === _.trim(value.toString()));
 | 
			
		||||
                const found = values.find((value) => lhsValue === _.trim(value.toString()));
 | 
			
		||||
                return !found;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,10 @@ let isOneOfOperation = OPERATIONS.find((operation) => operation.name === 'valueI
 | 
			
		||||
let isNotOneOfOperation = OPERATIONS.find((operation) => operation.name === 'valueIsNot');
 | 
			
		||||
let isBetween = OPERATIONS.find((operation) => operation.name === 'between');
 | 
			
		||||
let isNotBetween = OPERATIONS.find((operation) => operation.name === 'notBetween');
 | 
			
		||||
let enumIsOperation = OPERATIONS.find((operation) => operation.name === 'enumValueIs');
 | 
			
		||||
let enumIsNotOperation = OPERATIONS.find((operation) => operation.name === 'enumValueIsNot');
 | 
			
		||||
 | 
			
		||||
describe('Is one of and is not one of operations', function () {
 | 
			
		||||
describe('operations', function () {
 | 
			
		||||
 | 
			
		||||
    it('should evaluate isOneOf to true for number inputs', () => {
 | 
			
		||||
        const inputs = [45, "5,6,45,8"];
 | 
			
		||||
@@ -87,4 +89,54 @@ describe('Is one of and is not one of operations', function () {
 | 
			
		||||
        const inputs = ["45", "30", "50"];
 | 
			
		||||
        expect(!!isNotBetween.operation(inputs)).toBeFalse();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate enumValueIs to true for number inputs', () => {
 | 
			
		||||
        const inputs = [1, "1"];
 | 
			
		||||
        expect(!!enumIsOperation.operation(inputs)).toBeTrue();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate enumValueIs to true for string inputs', () => {
 | 
			
		||||
        const inputs = ["45", "45"];
 | 
			
		||||
        expect(!!enumIsOperation.operation(inputs)).toBeTrue();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate enumValueIsNot to true for number inputs', () => {
 | 
			
		||||
        const inputs = [45, "46"];
 | 
			
		||||
        expect(!!enumIsNotOperation.operation(inputs)).toBeTrue();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate enumValueIsNot to true for string inputs', () => {
 | 
			
		||||
        const inputs = ["45", "46"];
 | 
			
		||||
        expect(!!enumIsNotOperation.operation(inputs)).toBeTrue();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate enumValueIs to false for number inputs', () => {
 | 
			
		||||
        const inputs = [1, "2"];
 | 
			
		||||
        expect(!!enumIsOperation.operation(inputs)).toBeFalse();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate enumValueIs to false for string inputs', () => {
 | 
			
		||||
        const inputs = ["45", "46"];
 | 
			
		||||
        expect(!!enumIsOperation.operation(inputs)).toBeFalse();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate enumValueIsNot to false for number inputs', () => {
 | 
			
		||||
        const inputs = [45, "45"];
 | 
			
		||||
        expect(!!enumIsNotOperation.operation(inputs)).toBeFalse();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate enumValueIsNot to false for string inputs', () => {
 | 
			
		||||
        const inputs = ["45", "45"];
 | 
			
		||||
        expect(!!enumIsNotOperation.operation(inputs)).toBeFalse();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate enumValueIs to false for undefined input', () => {
 | 
			
		||||
        const inputs = [undefined, "45"];
 | 
			
		||||
        expect(!!enumIsOperation.operation(inputs)).toBeFalse();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate enumValueIsNot to true for undefined input', () => {
 | 
			
		||||
        const inputs = [undefined, "45"];
 | 
			
		||||
        expect(!!enumIsNotOperation.operation(inputs)).toBeTrue();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -19,27 +19,154 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
const NONE_VALUE = '__no_value';
 | 
			
		||||
 | 
			
		||||
export const getStyleProp = (key, defaultValue) => {
 | 
			
		||||
    let styleProp = undefined;
 | 
			
		||||
    switch(key) {
 | 
			
		||||
    case 'fill': styleProp = {
 | 
			
		||||
        backgroundColor: defaultValue || 'transparent'
 | 
			
		||||
    };
 | 
			
		||||
        break;
 | 
			
		||||
    case 'stroke': styleProp = {
 | 
			
		||||
        border: '1px solid ' + (defaultValue || 'transparent')
 | 
			
		||||
    };
 | 
			
		||||
        break;
 | 
			
		||||
    case 'color': styleProp = {
 | 
			
		||||
        color: defaultValue || 'transparent'
 | 
			
		||||
    };
 | 
			
		||||
        break;
 | 
			
		||||
    case 'url': styleProp = {
 | 
			
		||||
        imageUrl: defaultValue || 'transparent'
 | 
			
		||||
    };
 | 
			
		||||
        break;
 | 
			
		||||
const styleProps = {
 | 
			
		||||
    backgroundColor: {
 | 
			
		||||
        svgProperty: 'fill',
 | 
			
		||||
        noneValue: NONE_VALUE,
 | 
			
		||||
        applicableForType: type => {
 | 
			
		||||
            return !type ? true : (type === 'text-view' ||
 | 
			
		||||
                                      type === 'telemetry-view' ||
 | 
			
		||||
                                      type === 'box-view' ||
 | 
			
		||||
                                      type === 'subobject-view');
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    border: {
 | 
			
		||||
        svgProperty: 'stroke',
 | 
			
		||||
        noneValue: NONE_VALUE,
 | 
			
		||||
        applicableForType: type => {
 | 
			
		||||
            return !type ? true : (type === 'text-view' ||
 | 
			
		||||
                                            type === 'telemetry-view' ||
 | 
			
		||||
                                            type === 'box-view' ||
 | 
			
		||||
                                            type === 'image-view' ||
 | 
			
		||||
                                            type === 'line-view'||
 | 
			
		||||
                                            type === 'subobject-view');
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    color: {
 | 
			
		||||
        svgProperty: 'color',
 | 
			
		||||
        noneValue: NONE_VALUE,
 | 
			
		||||
        applicableForType: type => {
 | 
			
		||||
            return !type ? true : (type === 'text-view' ||
 | 
			
		||||
                                    type === 'telemetry-view'||
 | 
			
		||||
                                    type === 'subobject-view');
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    imageUrl: {
 | 
			
		||||
        svgProperty: 'url',
 | 
			
		||||
        noneValue: '',
 | 
			
		||||
        applicableForType: type => {
 | 
			
		||||
            return !type ? false : type === 'image-view';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return styleProp;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const aggregateStyleValues = (accumulator, currentStyle) => {
 | 
			
		||||
    const styleKeys = Object.keys(currentStyle);
 | 
			
		||||
    const properties = Object.keys(styleProps);
 | 
			
		||||
    properties.forEach((property) => {
 | 
			
		||||
        if (!accumulator[property]) {
 | 
			
		||||
            accumulator[property] = [];
 | 
			
		||||
        }
 | 
			
		||||
        const found = styleKeys.find(key => key === property);
 | 
			
		||||
        if (found) {
 | 
			
		||||
            accumulator[property].push(currentStyle[found]);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    return accumulator;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Returns a union of styles used by multiple items.
 | 
			
		||||
// Styles that are common to all items but don't have the same value are added to the mixedStyles list
 | 
			
		||||
export const getConsolidatedStyleValues = (multipleItemStyles) => {
 | 
			
		||||
    let aggregatedStyleValues = multipleItemStyles.reduce(aggregateStyleValues, {});
 | 
			
		||||
 | 
			
		||||
    let styleValues = {};
 | 
			
		||||
    let mixedStyles = [];
 | 
			
		||||
    const properties = Object.keys(styleProps);
 | 
			
		||||
    properties.forEach((property) => {
 | 
			
		||||
        const values = aggregatedStyleValues[property];
 | 
			
		||||
        if (values.length) {
 | 
			
		||||
            if (values.every(value => value === values[0])) {
 | 
			
		||||
                styleValues[property] = values[0];
 | 
			
		||||
            } else {
 | 
			
		||||
                styleValues[property] = '';
 | 
			
		||||
                mixedStyles.push(property);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    return {
 | 
			
		||||
        styles: styleValues,
 | 
			
		||||
        mixedStyles
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getStaticStyleForItem = (domainObject, id) => {
 | 
			
		||||
    let domainObjectStyles = domainObject && domainObject.configuration && domainObject.configuration.objectStyles;
 | 
			
		||||
    if (domainObjectStyles) {
 | 
			
		||||
        if (id) {
 | 
			
		||||
            if(domainObjectStyles[id] && domainObjectStyles[id].staticStyle) {
 | 
			
		||||
                return domainObjectStyles[id].staticStyle.style;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (domainObjectStyles.staticStyle) {
 | 
			
		||||
            return domainObjectStyles.staticStyle.style;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getConditionalStyleForItem = (domainObject, id) => {
 | 
			
		||||
    let domainObjectStyles = domainObject && domainObject.configuration && domainObject.configuration.objectStyles;
 | 
			
		||||
    if (domainObjectStyles) {
 | 
			
		||||
        if (id) {
 | 
			
		||||
            if (domainObjectStyles[id] && domainObjectStyles[id].conditionSetIdentifier) {
 | 
			
		||||
                return domainObjectStyles[id].styles;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (domainObjectStyles.staticStyle) {
 | 
			
		||||
            return domainObjectStyles.styles;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//Returns either existing static styles or uses SVG defaults if available
 | 
			
		||||
export const getApplicableStylesForItem = (domainObject, item) => {
 | 
			
		||||
    const type = item && item.type;
 | 
			
		||||
    const id = item && item.id;
 | 
			
		||||
    let style = {};
 | 
			
		||||
 | 
			
		||||
    let staticStyle = getStaticStyleForItem(domainObject, id);
 | 
			
		||||
 | 
			
		||||
    const properties = Object.keys(styleProps);
 | 
			
		||||
    properties.forEach(property => {
 | 
			
		||||
        const styleProp = styleProps[property];
 | 
			
		||||
        if (styleProp.applicableForType(type)) {
 | 
			
		||||
            let defaultValue;
 | 
			
		||||
            if (staticStyle) {
 | 
			
		||||
                defaultValue = staticStyle[property];
 | 
			
		||||
            } else if (item) {
 | 
			
		||||
                defaultValue = item[styleProp.svgProperty];
 | 
			
		||||
            }
 | 
			
		||||
            style[property] = defaultValue === undefined ? styleProp.noneValue : defaultValue;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return style;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getStylesWithoutNoneValue = (style) => {
 | 
			
		||||
    if (_.isEmpty(style) || !style) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    let styleObj = {};
 | 
			
		||||
    const keys = Object.keys(style);
 | 
			
		||||
    keys.forEach(key => {
 | 
			
		||||
        if ((typeof style[key] === 'string')) {
 | 
			
		||||
            if (style[key].indexOf('__no_value') > -1) {
 | 
			
		||||
                style[key] = '';
 | 
			
		||||
            } else {
 | 
			
		||||
                styleObj[key] = style[key];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    return styleObj;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								src/plugins/condition/utils/time.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/plugins/condition/utils/time.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2020, 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
export const getLatestTimestamp = (
 | 
			
		||||
    currentTimestamp,
 | 
			
		||||
    compareTimestamp,
 | 
			
		||||
    timeSystems,
 | 
			
		||||
    currentTimeSystem
 | 
			
		||||
) => {
 | 
			
		||||
    let latest = { ...currentTimestamp };
 | 
			
		||||
    const compare = { ...compareTimestamp };
 | 
			
		||||
    const key = currentTimeSystem.key;
 | 
			
		||||
 | 
			
		||||
    if (!latest || !latest[key]) {
 | 
			
		||||
        latest = updateLatestTimeStamp(compare, timeSystems)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (compare[key] > latest[key]) {
 | 
			
		||||
        latest = updateLatestTimeStamp(compare, timeSystems)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return latest;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateLatestTimeStamp(timestamp, timeSystems) {
 | 
			
		||||
    let latest = {};
 | 
			
		||||
 | 
			
		||||
    timeSystems.forEach(timeSystem => {
 | 
			
		||||
        latest[timeSystem.key] = timestamp[timeSystem.key];
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return latest;
 | 
			
		||||
}
 | 
			
		||||
@@ -21,13 +21,14 @@
 | 
			
		||||
*****************************************************************************/
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<a class="c-condition-widget"
 | 
			
		||||
   :href="internalDomainObject.url"
 | 
			
		||||
<component :is="urlDefined ? 'a' : 'span'"
 | 
			
		||||
           class="c-condition-widget"
 | 
			
		||||
           :href="urlDefined ? internalDomainObject.url : null"
 | 
			
		||||
>
 | 
			
		||||
    <div class="c-condition-widget__label">
 | 
			
		||||
        {{ internalDomainObject.label }}
 | 
			
		||||
    </div>
 | 
			
		||||
</a>
 | 
			
		||||
</component>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -38,6 +39,11 @@ export default {
 | 
			
		||||
            internalDomainObject: this.domainObject
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        urlDefined() {
 | 
			
		||||
            return this.internalDomainObject.url && this.internalDomainObject.url.length > 0;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -28,10 +28,12 @@
 | 
			
		||||
    border: 1px solid transparent;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    padding: $interiorMarginLg $interiorMarginLg * 2;
 | 
			
		||||
    cursor: inherit !important;
 | 
			
		||||
    &[href] {
 | 
			
		||||
        cursor: pointer !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.c-condition-widget {
 | 
			
		||||
    // Widget is conditionally made into a <a> when URL property has been defined
 | 
			
		||||
    cursor: pointer !important;
 | 
			
		||||
    pointer-events: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Make Condition Widget expand when in a hidden frame Layout context
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ export default {
 | 
			
		||||
    makeDefinition() {
 | 
			
		||||
        return {
 | 
			
		||||
            fill: '#717171',
 | 
			
		||||
            stroke: 'transparent',
 | 
			
		||||
            stroke: '',
 | 
			
		||||
            x: 1,
 | 
			
		||||
            y: 1,
 | 
			
		||||
            width: 10,
 | 
			
		||||
@@ -74,13 +74,14 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        style() {
 | 
			
		||||
            return Object.assign({
 | 
			
		||||
                backgroundColor: this.item.fill,
 | 
			
		||||
                border: '1px solid ' + this.item.stroke
 | 
			
		||||
            }, this.itemStyle);
 | 
			
		||||
        },
 | 
			
		||||
        styleClass() {
 | 
			
		||||
            return this.itemStyle && this.itemStyle.isStyleInvisible;
 | 
			
		||||
            if (this.itemStyle) {
 | 
			
		||||
                return this.itemStyle;
 | 
			
		||||
            } else {
 | 
			
		||||
                return {
 | 
			
		||||
                    backgroundColor: this.item.fill,
 | 
			
		||||
                    border: this.item.stroke ? '1px solid ' + this.item.stroke : ''
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
 
 | 
			
		||||
@@ -74,13 +74,18 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        style() {
 | 
			
		||||
            let backgroundImage = 'url(' + this.item.url + ')';
 | 
			
		||||
            let border = '1px solid ' + this.item.stroke;
 | 
			
		||||
            if (this.itemStyle) {
 | 
			
		||||
                if (this.itemStyle.imageUrl !== undefined) {
 | 
			
		||||
                    backgroundImage = 'url(' + this.itemStyle.imageUrl + ')';
 | 
			
		||||
                }
 | 
			
		||||
                border = this.itemStyle.border;
 | 
			
		||||
            }
 | 
			
		||||
            return {
 | 
			
		||||
                backgroundImage: this.itemStyle ? ('url(' + this.itemStyle.imageUrl + ')') : 'url(' + this.item.url + ')',
 | 
			
		||||
                border: (this.itemStyle && this.itemStyle.border) ? this.itemStyle.border : ('1px solid ' + this.item.stroke)
 | 
			
		||||
                backgroundImage,
 | 
			
		||||
                border
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        styleClass() {
 | 
			
		||||
            return this.itemStyle && this.itemStyle.isStyleInvisible;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
 
 | 
			
		||||
@@ -127,8 +127,11 @@ export default {
 | 
			
		||||
            return {x, y, x2, y2};
 | 
			
		||||
        },
 | 
			
		||||
        stroke() {
 | 
			
		||||
            if (this.itemStyle && this.itemStyle.border) {
 | 
			
		||||
                return this.itemStyle.border.replace('1px solid ', '');
 | 
			
		||||
            if (this.itemStyle) {
 | 
			
		||||
                if (this.itemStyle.border) {
 | 
			
		||||
                    return this.itemStyle.border.replace('1px solid ', '');
 | 
			
		||||
                }
 | 
			
		||||
                return '';
 | 
			
		||||
            } else {
 | 
			
		||||
                return this.item.stroke;
 | 
			
		||||
            }
 | 
			
		||||
@@ -146,9 +149,6 @@ export default {
 | 
			
		||||
                height: `${height}px`
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        styleClass() {
 | 
			
		||||
            return this.itemStyle && this.itemStyle.isStyleInvisible;
 | 
			
		||||
        },
 | 
			
		||||
        startHandleClass() {
 | 
			
		||||
            return START_HANDLE_QUADRANTS[this.vectorQuadrant];
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -30,14 +30,13 @@
 | 
			
		||||
    <div
 | 
			
		||||
        v-if="domainObject"
 | 
			
		||||
        class="c-telemetry-view"
 | 
			
		||||
        :class="styleClass"
 | 
			
		||||
        :style="styleObject"
 | 
			
		||||
        @contextmenu.prevent="showContextMenu"
 | 
			
		||||
    >
 | 
			
		||||
        <div
 | 
			
		||||
            v-if="showLabel"
 | 
			
		||||
            class="c-telemetry-view__label"
 | 
			
		||||
            :class="[styleClass]"
 | 
			
		||||
            :style="objectStyle"
 | 
			
		||||
        >
 | 
			
		||||
            <div class="c-telemetry-view__label-text">
 | 
			
		||||
                {{ domainObject.name }}
 | 
			
		||||
@@ -48,8 +47,7 @@
 | 
			
		||||
            v-if="showValue"
 | 
			
		||||
            :title="fieldName"
 | 
			
		||||
            class="c-telemetry-view__value"
 | 
			
		||||
            :class="[telemetryClass, !telemetryClass && styleClass]"
 | 
			
		||||
            :style="!telemetryClass && objectStyle"
 | 
			
		||||
            :class="[telemetryClass]"
 | 
			
		||||
        >
 | 
			
		||||
            <div class="c-telemetry-view__value-text">
 | 
			
		||||
                {{ telemetryValue }}
 | 
			
		||||
@@ -62,7 +60,7 @@
 | 
			
		||||
<script>
 | 
			
		||||
import LayoutFrame from './LayoutFrame.vue'
 | 
			
		||||
import printj from 'printj'
 | 
			
		||||
import StyleRuleManager from "../../condition/StyleRuleManager";
 | 
			
		||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
 | 
			
		||||
 | 
			
		||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
 | 
			
		||||
    DEFAULT_POSITION = [1, 1],
 | 
			
		||||
@@ -81,8 +79,8 @@ export default {
 | 
			
		||||
            height: DEFAULT_TELEMETRY_DIMENSIONS[1],
 | 
			
		||||
            displayMode: 'all',
 | 
			
		||||
            value: metadata.getDefaultDisplayValue(),
 | 
			
		||||
            stroke: "transparent",
 | 
			
		||||
            fill: "transparent",
 | 
			
		||||
            stroke: "",
 | 
			
		||||
            fill: "",
 | 
			
		||||
            color: "",
 | 
			
		||||
            size: "13px"
 | 
			
		||||
        };
 | 
			
		||||
@@ -91,6 +89,7 @@ export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        LayoutFrame
 | 
			
		||||
    },
 | 
			
		||||
    mixins: [conditionalStylesMixin],
 | 
			
		||||
    props: {
 | 
			
		||||
        item: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -113,8 +112,7 @@ export default {
 | 
			
		||||
            datum: undefined,
 | 
			
		||||
            formats: undefined,
 | 
			
		||||
            domainObject: undefined,
 | 
			
		||||
            currentObjectPath: undefined,
 | 
			
		||||
            objectStyle: ''
 | 
			
		||||
            currentObjectPath: undefined
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -127,15 +125,10 @@ export default {
 | 
			
		||||
            return displayMode === 'all' || displayMode === 'value';
 | 
			
		||||
        },
 | 
			
		||||
        styleObject() {
 | 
			
		||||
            return {
 | 
			
		||||
                backgroundColor: this.item.fill,
 | 
			
		||||
                borderColor: this.item.stroke,
 | 
			
		||||
                color: this.item.color,
 | 
			
		||||
            return Object.assign({}, {
 | 
			
		||||
                fontSize: this.item.size
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        styleClass() {
 | 
			
		||||
            return this.objectStyle && this.objectStyle.isStyleInvisible;
 | 
			
		||||
            }, this.itemStyle);
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
        fieldName() {
 | 
			
		||||
            return this.valueMetadata && this.valueMetadata.name;
 | 
			
		||||
@@ -190,15 +183,6 @@ export default {
 | 
			
		||||
            this.removeSelectable();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.unlistenStyles) {
 | 
			
		||||
            this.unlistenStyles();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.styleRuleManager) {
 | 
			
		||||
            this.styleRuleManager.destroy();
 | 
			
		||||
            delete this.styleRuleManager;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.openmct.time.off("bounds", this.refreshData);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
@@ -241,7 +225,6 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        setObject(domainObject) {
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
            this.initObjectStyles();
 | 
			
		||||
            this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
 | 
			
		||||
            this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
 | 
			
		||||
            this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
 | 
			
		||||
@@ -266,30 +249,6 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        showContextMenu(event) {
 | 
			
		||||
            this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
 | 
			
		||||
        },
 | 
			
		||||
        initObjectStyles() {
 | 
			
		||||
            if (this.domainObject.configuration) {
 | 
			
		||||
                this.styleRuleManager = new StyleRuleManager(this.domainObject.configuration.objectStyles, this.openmct, this.updateStyle.bind(this));
 | 
			
		||||
 | 
			
		||||
                if (this.unlistenStyles) {
 | 
			
		||||
                    this.unlistenStyles();
 | 
			
		||||
                }
 | 
			
		||||
                this.unlistenStyles = this.openmct.objects.observe(this.domainObject, 'configuration.objectStyles', (newObjectStyle) => {
 | 
			
		||||
                    //Updating object styles in the inspector view will trigger this so that the changes are reflected immediately
 | 
			
		||||
                    this.styleRuleManager.updateObjectStyleConfig(newObjectStyle);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        updateStyle(styleObj) {
 | 
			
		||||
            let keys = Object.keys(styleObj);
 | 
			
		||||
            keys.forEach(key => {
 | 
			
		||||
                if ((typeof styleObj[key] === 'string') && (styleObj[key].indexOf('transparent') > -1)) {
 | 
			
		||||
                    if (styleObj[key]) {
 | 
			
		||||
                        styleObj[key] = '';
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            this.objectStyle = styleObj;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@
 | 
			
		||||
        :class="[styleClass]"
 | 
			
		||||
        :style="style"
 | 
			
		||||
    >
 | 
			
		||||
        {{ item.text }}
 | 
			
		||||
        <div class="c-text-view__text">{{ item.text }}</div>
 | 
			
		||||
    </div>
 | 
			
		||||
</layout-frame>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -44,8 +44,8 @@ import conditionalStylesMixin from "../mixins/objectStyles-mixin";
 | 
			
		||||
export default {
 | 
			
		||||
    makeDefinition(openmct, gridSize, element) {
 | 
			
		||||
        return {
 | 
			
		||||
            fill: 'transparent',
 | 
			
		||||
            stroke: 'transparent',
 | 
			
		||||
            fill: '',
 | 
			
		||||
            stroke: '',
 | 
			
		||||
            size: '13px',
 | 
			
		||||
            color: '',
 | 
			
		||||
            x: 1,
 | 
			
		||||
@@ -80,14 +80,8 @@ export default {
 | 
			
		||||
    computed: {
 | 
			
		||||
        style() {
 | 
			
		||||
            return Object.assign({
 | 
			
		||||
                backgroundColor: this.item.fill,
 | 
			
		||||
                border: '1px solid ' + this.item.stroke,
 | 
			
		||||
                color: this.item.color,
 | 
			
		||||
                fontSize: this.item.size
 | 
			
		||||
            }, this.itemStyle);
 | 
			
		||||
        },
 | 
			
		||||
        styleClass() {
 | 
			
		||||
            return this.itemStyle && this.itemStyle.isStyleInvisible;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,8 @@
 | 
			
		||||
.is-editing {
 | 
			
		||||
    /******************* STYLES FOR C-FRAME WHILE EDITING */
 | 
			
		||||
    .c-frame {
 | 
			
		||||
        border: 1px solid rgba($editFrameColorHov, 0.3);
 | 
			
		||||
 | 
			
		||||
        &:not([s-selected]) {
 | 
			
		||||
            &:hover {
 | 
			
		||||
                border: $editFrameBorderHov;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
.c-text-view {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: stretch;
 | 
			
		||||
    align-items: center; // Vertically center text
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    padding: $interiorMargin;
 | 
			
		||||
 | 
			
		||||
    .c-frame & {
 | 
			
		||||
        @include abs();
 | 
			
		||||
 
 | 
			
		||||
@@ -21,18 +21,20 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import StyleRuleManager from "@/plugins/condition/StyleRuleManager";
 | 
			
		||||
import {getStylesWithoutNoneValue} from "@/plugins/condition/utils/styleUtils";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            itemStyle: this.itemStyle
 | 
			
		||||
            itemStyle: undefined,
 | 
			
		||||
            styleClass: ''
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.domainObject = this.$parent.domainObject;
 | 
			
		||||
        this.parentDomainObject = this.$parent.domainObject;
 | 
			
		||||
        this.itemId = this.item.id;
 | 
			
		||||
        this.objectStyle = this.getObjectStyleForItem(this.domainObject.configuration.objectStyles);
 | 
			
		||||
        this.objectStyle = this.getObjectStyleForItem(this.parentDomainObject.configuration.objectStyles);
 | 
			
		||||
        this.initObjectStyles();
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
@@ -59,7 +61,7 @@ export default {
 | 
			
		||||
                this.stopListeningObjectStyles();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.stopListeningObjectStyles = this.openmct.objects.observe(this.domainObject, 'configuration.objectStyles', (newObjectStyle) => {
 | 
			
		||||
            this.stopListeningObjectStyles = this.openmct.objects.observe(this.parentDomainObject, 'configuration.objectStyles', (newObjectStyle) => {
 | 
			
		||||
                //Updating object styles in the inspector view will trigger this so that the changes are reflected immediately
 | 
			
		||||
                let newItemObjectStyle = this.getObjectStyleForItem(newObjectStyle);
 | 
			
		||||
                if (this.objectStyle !== newItemObjectStyle) {
 | 
			
		||||
@@ -69,13 +71,8 @@ export default {
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        updateStyle(style) {
 | 
			
		||||
            this.itemStyle = style;
 | 
			
		||||
            let keys = Object.keys(this.itemStyle);
 | 
			
		||||
            keys.forEach((key) => {
 | 
			
		||||
                if ((typeof this.itemStyle[key] === 'string') && (this.itemStyle[key].indexOf('transparent') > -1)) {
 | 
			
		||||
                    delete this.itemStyle[key];
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            this.itemStyle = getStylesWithoutNoneValue(style);
 | 
			
		||||
            this.styleClass = this.itemStyle && this.itemStyle.isStyleInvisible;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
        <div v-if="embed.snapshot"
 | 
			
		||||
             class="c-ne__embed__time"
 | 
			
		||||
        >
 | 
			
		||||
            {{ createdOn }}
 | 
			
		||||
            {{ formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss') }}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -27,7 +27,7 @@
 | 
			
		||||
import Moment from 'moment';
 | 
			
		||||
import PopupMenu from './popup-menu.vue';
 | 
			
		||||
import PreviewAction from '../../../ui/preview/PreviewAction';
 | 
			
		||||
import PainterroInstance from '../utils/painterroInstance';
 | 
			
		||||
import Painterro from 'painterro';
 | 
			
		||||
import RemoveDialog from '../utils/removeDialog';
 | 
			
		||||
import SnapshotTemplate from './snapshot-template.html';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
@@ -56,10 +56,7 @@ export default {
 | 
			
		||||
            popupMenuItems: []
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        createdOn() {
 | 
			
		||||
            return this.formatTime(this.embed.createdOn, 'YYYY-MM-DD HH:mm:ss');
 | 
			
		||||
        }
 | 
			
		||||
    watch: {
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.addPopupMenuItems();
 | 
			
		||||
@@ -80,42 +77,89 @@ export default {
 | 
			
		||||
            this.popupMenuItems = [removeEmbed, preview];
 | 
			
		||||
        },
 | 
			
		||||
        annotateSnapshot() {
 | 
			
		||||
            let painterroInstance = null;
 | 
			
		||||
            const self = this;
 | 
			
		||||
 | 
			
		||||
            let save = false;
 | 
			
		||||
            let painterroInstance = {};
 | 
			
		||||
            const annotateVue = new Vue({
 | 
			
		||||
                template: '<div id="snap-annotation"></div>'
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const annotateOverlay = this.openmct.overlays.overlay({
 | 
			
		||||
            let annotateOverlay = self.openmct.overlays.overlay({
 | 
			
		||||
                element: annotateVue.$mount().$el,
 | 
			
		||||
                size: 'large',
 | 
			
		||||
                dismissable: false,
 | 
			
		||||
                buttons: [
 | 
			
		||||
                    {
 | 
			
		||||
                        label: 'Cancel',
 | 
			
		||||
                        emphasis: true,
 | 
			
		||||
                        callback: () => {
 | 
			
		||||
                            painterroInstance.dismiss();
 | 
			
		||||
                        callback: function () {
 | 
			
		||||
                            save = false;
 | 
			
		||||
                            painterroInstance.save();
 | 
			
		||||
                            annotateOverlay.dismiss();
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        label: 'Save',
 | 
			
		||||
                        callback: () => {
 | 
			
		||||
                        callback: function () {
 | 
			
		||||
 | 
			
		||||
                            save = true;
 | 
			
		||||
                            painterroInstance.save();
 | 
			
		||||
                            annotateOverlay.dismiss();
 | 
			
		||||
                            this.snapshotOverlay.dismiss();
 | 
			
		||||
                            this.openSnapshot();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                onDestroy: () => {
 | 
			
		||||
                onDestroy: function () {
 | 
			
		||||
                    annotateVue.$destroy(true);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            painterroInstance = new PainterroInstance();
 | 
			
		||||
            painterroInstance.callback = this.updateSnapshot;
 | 
			
		||||
            painterroInstance.show(this.embed.snapshot.src);
 | 
			
		||||
            painterroInstance = Painterro({
 | 
			
		||||
                id: 'snap-annotation',
 | 
			
		||||
                activeColor: '#ff0000',
 | 
			
		||||
                activeColorAlpha: 1.0,
 | 
			
		||||
                activeFillColor: '#fff',
 | 
			
		||||
                activeFillColorAlpha: 0.0,
 | 
			
		||||
                backgroundFillColor: '#000',
 | 
			
		||||
                backgroundFillColorAlpha: 0.0,
 | 
			
		||||
                defaultFontSize: 16,
 | 
			
		||||
                defaultLineWidth: 2,
 | 
			
		||||
                defaultTool: 'ellipse',
 | 
			
		||||
                hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
 | 
			
		||||
                translation: {
 | 
			
		||||
                    name: 'en',
 | 
			
		||||
                    strings: {
 | 
			
		||||
                        lineColor: 'Line',
 | 
			
		||||
                        fillColor: 'Fill',
 | 
			
		||||
                        lineWidth: 'Size',
 | 
			
		||||
                        textColor: 'Color',
 | 
			
		||||
                        fontSize: 'Size',
 | 
			
		||||
                        fontStyle: 'Style'
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                saveHandler: function (image, done) {
 | 
			
		||||
                    if (save) {
 | 
			
		||||
                        const url = image.asBlob();
 | 
			
		||||
                        const reader = new window.FileReader();
 | 
			
		||||
                        reader.readAsDataURL(url);
 | 
			
		||||
                        reader.onloadend = function () {
 | 
			
		||||
                            const snapshot = reader.result;
 | 
			
		||||
                            const snapshotObject = {
 | 
			
		||||
                                src: snapshot,
 | 
			
		||||
                                type: url.type,
 | 
			
		||||
                                size: url.size,
 | 
			
		||||
                                modified: Date.now()
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
                            self.embed.snapshot = snapshotObject;
 | 
			
		||||
                            self.updateEmbed(self.embed);
 | 
			
		||||
                        };
 | 
			
		||||
                    } else {
 | 
			
		||||
                        console.log('You cancelled the annotation!!!');
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    done(true);
 | 
			
		||||
                }
 | 
			
		||||
            }).show(this.embed.snapshot.src);
 | 
			
		||||
        },
 | 
			
		||||
        changeLocation() {
 | 
			
		||||
            this.openmct.time.stopClock();
 | 
			
		||||
@@ -125,6 +169,9 @@ export default {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const link = this.embed.historicLink;
 | 
			
		||||
            if (!link) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            window.location.href = link;
 | 
			
		||||
            const message = 'Time bounds changed to fixed timespan mode';
 | 
			
		||||
@@ -142,21 +189,22 @@ export default {
 | 
			
		||||
            removeDialog.show();
 | 
			
		||||
        },
 | 
			
		||||
        openSnapshot() {
 | 
			
		||||
            const self = this;
 | 
			
		||||
            const snapshot = new Vue({
 | 
			
		||||
                data: () => {
 | 
			
		||||
                    return {
 | 
			
		||||
                        createdOn: this.createdOn,
 | 
			
		||||
                        embed: this.embed
 | 
			
		||||
                        embed: self.embed
 | 
			
		||||
                    };
 | 
			
		||||
                },
 | 
			
		||||
                methods: {
 | 
			
		||||
                    annotateSnapshot: this.annotateSnapshot.bind(this)
 | 
			
		||||
                    formatTime: self.formatTime,
 | 
			
		||||
                    annotateSnapshot: self.annotateSnapshot
 | 
			
		||||
                },
 | 
			
		||||
                template: SnapshotTemplate
 | 
			
		||||
            }).$mount();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.snapshotOverlay = this.openmct.overlays.overlay({
 | 
			
		||||
                element: snapshot.$el,
 | 
			
		||||
            const snapshotOverlay = this.openmct.overlays.overlay({
 | 
			
		||||
                element: snapshot.$mount().$el,
 | 
			
		||||
                onDestroy: () => { snapshot.$destroy(true) },
 | 
			
		||||
                size: 'large',
 | 
			
		||||
                dismissable: true,
 | 
			
		||||
@@ -165,7 +213,7 @@ export default {
 | 
			
		||||
                        label: 'Done',
 | 
			
		||||
                        emphasis: true,
 | 
			
		||||
                        callback: () => {
 | 
			
		||||
                            this.snapshotOverlay.dismiss();
 | 
			
		||||
                            snapshotOverlay.dismiss();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
@@ -185,10 +233,6 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        updateEmbed(embed) {
 | 
			
		||||
            this.$emit('updateEmbed', embed);
 | 
			
		||||
        },
 | 
			
		||||
        updateSnapshot(snapshotObject) {
 | 
			
		||||
            this.embed.snapshot = snapshotObject;
 | 
			
		||||
            this.updateEmbed(this.embed);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-notebook__entry c-ne has-local-controls"
 | 
			
		||||
     @dragover="changeCursor"
 | 
			
		||||
     @drop.capture="cancelEditMode"
 | 
			
		||||
     @drop.prevent="dropOnEntry"
 | 
			
		||||
     @dragover="dragover"
 | 
			
		||||
     @drop.capture="dropCapture"
 | 
			
		||||
     @drop.prevent="dropOnEntry(entry.id, $event)"
 | 
			
		||||
>
 | 
			
		||||
    <div class="c-ne__time-and-content">
 | 
			
		||||
        <div class="c-ne__time">
 | 
			
		||||
            <span>{{ createdOnDate }}</span>
 | 
			
		||||
            <span>{{ createdOnTime }}</span>
 | 
			
		||||
            <span>{{ formatTime(entry.createdOn, 'YYYY-MM-DD') }}</span>
 | 
			
		||||
            <span>{{ formatTime(entry.createdOn, 'HH:mm:ss') }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-ne__content">
 | 
			
		||||
            <div :id="entry.id"
 | 
			
		||||
@@ -15,8 +15,8 @@
 | 
			
		||||
                 :class="{'c-input-inline' : !readOnly }"
 | 
			
		||||
                 :contenteditable="!readOnly"
 | 
			
		||||
                 :style="!entry.text.length ? defaultEntryStyle : ''"
 | 
			
		||||
                 @blur="updateEntryValue($event, entry.id)"
 | 
			
		||||
                 @focus="updateCurrentEntryValue($event, entry.id)"
 | 
			
		||||
                 @blur="textBlur($event, entry.id)"
 | 
			
		||||
                 @focus="textFocus($event, entry.id)"
 | 
			
		||||
            >{{ entry.text.length ? entry.text : defaultText }}</div>
 | 
			
		||||
            <div class="c-snapshots c-ne__embeds">
 | 
			
		||||
                <NotebookEmbed v-for="embed in entry.embeds"
 | 
			
		||||
@@ -114,32 +114,29 @@ export default {
 | 
			
		||||
            defaultText: 'add description'
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    computed : {
 | 
			
		||||
        createdOnDate() {
 | 
			
		||||
            return this.formatTime(this.entry.createdOn, 'YYYY-MM-DD');
 | 
			
		||||
    watch: {
 | 
			
		||||
        entry() {
 | 
			
		||||
        },
 | 
			
		||||
        createdOnTime() {
 | 
			
		||||
            return this.formatTime(this.entry.createdOn, 'HH:mm:ss');
 | 
			
		||||
        readOnly(readOnly) {
 | 
			
		||||
        },
 | 
			
		||||
        selectedSection(selectedSection) {
 | 
			
		||||
        },
 | 
			
		||||
        selectedPage(selectedSection) {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.updateEntries = this.updateEntries.bind(this);
 | 
			
		||||
        this.dropOnEntry = this.dropOnEntry.bind(this);
 | 
			
		||||
    },
 | 
			
		||||
    beforeDestory() {
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        cancelEditMode(event) {
 | 
			
		||||
            const isEditing = this.openmct.editor.isEditing();
 | 
			
		||||
            if (isEditing) {
 | 
			
		||||
                this.openmct.editor.cancel();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        changeCursor() {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            event.dataTransfer.dropEffect = "copy";
 | 
			
		||||
        },
 | 
			
		||||
        deleteEntry() {
 | 
			
		||||
            const self = this;
 | 
			
		||||
            const entryPosById = self.entryPosById(self.entry.id);
 | 
			
		||||
            if (!self.domainObject || !self.selectedSection || !self.selectedPage || !self.entry.id) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const entryPosById = this.entryPosById(this.entry.id);
 | 
			
		||||
            if (entryPosById === -1) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@@ -154,7 +151,7 @@ export default {
 | 
			
		||||
                        callback: () => {
 | 
			
		||||
                            const entries = getNotebookEntries(self.domainObject, self.selectedSection, self.selectedPage);
 | 
			
		||||
                            entries.splice(entryPosById, 1);
 | 
			
		||||
                            self.updateEntries(entries);
 | 
			
		||||
                            this.updateEntries(entries);
 | 
			
		||||
                            dialog.dismiss();
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
@@ -167,10 +164,24 @@ export default {
 | 
			
		||||
                ]
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        dropOnEntry($event) {
 | 
			
		||||
        dragover() {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            event.dataTransfer.dropEffect = "copy";
 | 
			
		||||
        },
 | 
			
		||||
        dropCapture(event) {
 | 
			
		||||
            const isEditing = this.openmct.editor.isEditing();
 | 
			
		||||
            if (isEditing) {
 | 
			
		||||
                this.openmct.editor.cancel();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        dropOnEntry(entryId, $event) {
 | 
			
		||||
            event.stopImmediatePropagation();
 | 
			
		||||
 | 
			
		||||
            const snapshotId = $event.dataTransfer.getData('openmct/snapshot/id');
 | 
			
		||||
            if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const snapshotId = $event.dataTransfer.getData('snapshot/id');
 | 
			
		||||
            if (snapshotId.length) {
 | 
			
		||||
                this.moveSnapshot(snapshotId);
 | 
			
		||||
 | 
			
		||||
@@ -179,7 +190,7 @@ export default {
 | 
			
		||||
 | 
			
		||||
            const data = $event.dataTransfer.getData('openmct/domain-object-path');
 | 
			
		||||
            const objectPath = JSON.parse(data);
 | 
			
		||||
            const entryPos = this.entryPosById(this.entry.id);
 | 
			
		||||
            const entryPos = this.entryPosById(entryId);
 | 
			
		||||
            const bounds = this.openmct.time.bounds();
 | 
			
		||||
            const snapshotMeta = {
 | 
			
		||||
                bounds,
 | 
			
		||||
@@ -242,44 +253,7 @@ export default {
 | 
			
		||||
            selection.removeAllRanges();
 | 
			
		||||
            selection.addRange(range);
 | 
			
		||||
        },
 | 
			
		||||
        updateCurrentEntryValue($event) {
 | 
			
		||||
            if (this.readOnly) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const target = $event.target
 | 
			
		||||
            this.currentEntryValue = target ? target.innerText : '';
 | 
			
		||||
 | 
			
		||||
            if (!this.entry.text.length) {
 | 
			
		||||
                this.selectTextInsideElement(target);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        updateEmbed(newEmbed) {
 | 
			
		||||
            this.entry.embeds.some(e => {
 | 
			
		||||
                const found = (e.id === newEmbed.id);
 | 
			
		||||
                if (found) {
 | 
			
		||||
                    e = newEmbed;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return found;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.updateEntry(this.entry);
 | 
			
		||||
        },
 | 
			
		||||
        updateEntry(newEntry) {
 | 
			
		||||
            const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
 | 
			
		||||
            entries.some(entry => {
 | 
			
		||||
                const found = (entry.id === newEntry.id);
 | 
			
		||||
                if (found) {
 | 
			
		||||
                    entry = newEntry;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return found;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.updateEntries(entries);
 | 
			
		||||
        },
 | 
			
		||||
        updateEntryValue($event, entryId) {
 | 
			
		||||
        textBlur($event, entryId) {
 | 
			
		||||
            if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@@ -298,6 +272,42 @@ export default {
 | 
			
		||||
                this.updateEntries(entries);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        textFocus($event) {
 | 
			
		||||
            if (this.readOnly || !this.domainObject || !this.selectedSection || !this.selectedPage) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const target = $event.target
 | 
			
		||||
            this.currentEntryValue = target ? target.innerText : '';
 | 
			
		||||
 | 
			
		||||
            if (!this.entry.text.length) {
 | 
			
		||||
                this.selectTextInsideElement(target);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        updateEmbed(newEmbed) {
 | 
			
		||||
            let embed = this.entry.embeds.find(e => e.id === newEmbed.id);
 | 
			
		||||
 | 
			
		||||
            if (!embed) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            embed = newEmbed;
 | 
			
		||||
            this.updateEntry(this.entry);
 | 
			
		||||
        },
 | 
			
		||||
        updateEntry(newEntry) {
 | 
			
		||||
            if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
 | 
			
		||||
            entries.forEach(entry => {
 | 
			
		||||
                if (entry.id === newEntry.id) {
 | 
			
		||||
                    entry = newEntry;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.updateEntries(entries);
 | 
			
		||||
        },
 | 
			
		||||
        updateEntries(entries) {
 | 
			
		||||
            this.$emit('updateEntries', entries);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left">
 | 
			
		||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
 | 
			
		||||
    <button
 | 
			
		||||
        class="c-button--menu icon-notebook"
 | 
			
		||||
        title="Switch view type"
 | 
			
		||||
        title="Take a Notebook Snapshot"
 | 
			
		||||
        @click="setNotebookTypes"
 | 
			
		||||
        @click.stop="toggleMenu"
 | 
			
		||||
    >
 | 
			
		||||
@@ -64,7 +64,9 @@ export default {
 | 
			
		||||
            const defaultNotebook = getDefaultNotebook();
 | 
			
		||||
 | 
			
		||||
            if (defaultNotebook) {
 | 
			
		||||
                const domainObject = await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
 | 
			
		||||
                const domainObject = await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier)
 | 
			
		||||
                    .then(d => d);
 | 
			
		||||
 | 
			
		||||
                if (!domainObject.location) {
 | 
			
		||||
                    clearDefaultNotebook();
 | 
			
		||||
                } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
            <NotebookEmbed ref="notebookEmbed"
 | 
			
		||||
                           :key="snapshot.id"
 | 
			
		||||
                           :embed="snapshot"
 | 
			
		||||
                           :remove-action-string="'Delete This Snapshot'"
 | 
			
		||||
                           :remove-action-string="'Delete Snapshot'"
 | 
			
		||||
                           @updateEmbed="updateSnapshot"
 | 
			
		||||
                           @removeEmbed="removeSnapshot"
 | 
			
		||||
            />
 | 
			
		||||
@@ -69,7 +69,7 @@ export default {
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            popupMenuItems: [],
 | 
			
		||||
            removeActionString: 'Delete All Snapshots',
 | 
			
		||||
            removeActionString: 'Delete all snapshots',
 | 
			
		||||
            snapshots: []
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
@@ -78,6 +78,8 @@ export default {
 | 
			
		||||
        this.snapshotContainer.on(EVENT_SNAPSHOTS_UPDATED, this.snapshotsUpdated);
 | 
			
		||||
        this.snapshots = this.snapshotContainer.getSnapshots();
 | 
			
		||||
    },
 | 
			
		||||
    beforeDestory() {
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        addPopupMenuItems() {
 | 
			
		||||
            const removeSnapshot = {
 | 
			
		||||
@@ -117,7 +119,7 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        startEmbedDrag(snapshot, event) {
 | 
			
		||||
            event.dataTransfer.setData('text/plain', snapshot.id);
 | 
			
		||||
            event.dataTransfer.setData('openmct/snapshot/id', snapshot.id);
 | 
			
		||||
            event.dataTransfer.setData('snapshot/id', snapshot.id);
 | 
			
		||||
        },
 | 
			
		||||
        updateSnapshot(snapshot) {
 | 
			
		||||
            this.snapshotContainer.updateSnapshot(snapshot);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,8 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <SearchResults v-if="search.length"
 | 
			
		||||
                   ref="searchResults"
 | 
			
		||||
                   :domain-object="internalDomainObject"
 | 
			
		||||
                   :results="searchedEntries"
 | 
			
		||||
                   :results="getSearchResults()"
 | 
			
		||||
                   @changeSectionPage="changeSelectedSection"
 | 
			
		||||
                   @updateEntries="updateEntries"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <div v-if="!search.length"
 | 
			
		||||
@@ -51,13 +49,19 @@
 | 
			
		||||
                            class="c-notebook__controls__time"
 | 
			
		||||
                    >
 | 
			
		||||
                        <option value="0"
 | 
			
		||||
                                selected="selected"
 | 
			
		||||
                                :selected="showTime === 0"
 | 
			
		||||
                        >
 | 
			
		||||
                            Show all
 | 
			
		||||
                        </option>
 | 
			
		||||
                        <option value="1">Last hour</option>
 | 
			
		||||
                        <option value="8">Last 8 hours</option>
 | 
			
		||||
                        <option value="24">Last 24 hours</option>
 | 
			
		||||
                        <option value="1"
 | 
			
		||||
                                :selected="showTime === 1"
 | 
			
		||||
                        >Last hour</option>
 | 
			
		||||
                        <option value="8"
 | 
			
		||||
                                :selected="showTime === 8"
 | 
			
		||||
                        >Last 8 hours</option>
 | 
			
		||||
                        <option value="24"
 | 
			
		||||
                                :selected="showTime === 24"
 | 
			
		||||
                        >Last 24 hours</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                    <select v-model="defaultSort"
 | 
			
		||||
                            class="c-notebook__controls__time"
 | 
			
		||||
@@ -134,16 +138,21 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        filteredAndSortedEntries() {
 | 
			
		||||
            const filterTime = Date.now();
 | 
			
		||||
            const pageEntries = getNotebookEntries(this.internalDomainObject, this.selectedSection, this.selectedPage) || [];
 | 
			
		||||
 | 
			
		||||
            return pageEntries.sort(this.sortEntries);
 | 
			
		||||
            const hours = parseInt(this.showTime, 10);
 | 
			
		||||
            const filteredPageEntriesByTime = hours
 | 
			
		||||
                ? pageEntries.filter(entry => (filterTime - entry.createdOn) <= hours * 60 * 60 * 1000)
 | 
			
		||||
                : pageEntries;
 | 
			
		||||
 | 
			
		||||
            return this.defaultSort === 'oldest'
 | 
			
		||||
                ? filteredPageEntriesByTime
 | 
			
		||||
                : [...filteredPageEntriesByTime].reverse();
 | 
			
		||||
        },
 | 
			
		||||
        pages() {
 | 
			
		||||
            return this.getPages() || [];
 | 
			
		||||
        },
 | 
			
		||||
        searchedEntries() {
 | 
			
		||||
            return this.getSearchResults();
 | 
			
		||||
        },
 | 
			
		||||
        sections() {
 | 
			
		||||
            return this.internalDomainObject.configuration.sections || [];
 | 
			
		||||
        },
 | 
			
		||||
@@ -163,6 +172,8 @@ export default {
 | 
			
		||||
            return this.sections.find(section => section.isSelected);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
    },
 | 
			
		||||
    beforeMount() {
 | 
			
		||||
        this.throttledSearchItem = throttle(this.searchItem, 500);
 | 
			
		||||
    },
 | 
			
		||||
@@ -247,7 +258,7 @@ export default {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            event.stopImmediatePropagation();
 | 
			
		||||
 | 
			
		||||
            const snapshotId = event.dataTransfer.getData('openmct/snapshot/id');
 | 
			
		||||
            const snapshotId = event.dataTransfer.getData('snapshot/id');
 | 
			
		||||
            if (snapshotId.length) {
 | 
			
		||||
                const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
 | 
			
		||||
                this.newEntry(snapshot);
 | 
			
		||||
@@ -423,11 +434,6 @@ export default {
 | 
			
		||||
        searchItem(input) {
 | 
			
		||||
            this.search = input;
 | 
			
		||||
        },
 | 
			
		||||
        sortEntries(right, left) {
 | 
			
		||||
            return this.defaultSort === 'newest'
 | 
			
		||||
                ? left.createdOn - right.createdOn
 | 
			
		||||
                : right.createdOn - left.createdOn;
 | 
			
		||||
        },
 | 
			
		||||
        toggleNav() {
 | 
			
		||||
            this.showNav = !this.showNav;
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,12 @@ export default {
 | 
			
		||||
        return {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        deletePage(id) {
 | 
			
		||||
            const selectedSection = this.sections.find(s => s.isSelected);
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,8 @@ export default {
 | 
			
		||||
        this.addPopupMenuItems();
 | 
			
		||||
        this.toggleContentEditable();
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        addPopupMenuItems() {
 | 
			
		||||
            const removePage = {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left">
 | 
			
		||||
    <button
 | 
			
		||||
        class="l-browse-bar__context-actions c-disclosure-button"
 | 
			
		||||
        title="popup menu"
 | 
			
		||||
        @click="showMenuItems"
 | 
			
		||||
    >
 | 
			
		||||
        <span class="c-button__label"></span>
 | 
			
		||||
    </button>
 | 
			
		||||
</div>
 | 
			
		||||
<button
 | 
			
		||||
    class="c-popup-menu-button c-disclosure-button"
 | 
			
		||||
    title="popup menu"
 | 
			
		||||
    @click="showMenuItems"
 | 
			
		||||
>
 | 
			
		||||
</button>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,12 @@
 | 
			
		||||
    <div class="c-notebook__entries">
 | 
			
		||||
        <NotebookEntry v-for="(result, index) in results"
 | 
			
		||||
                       :key="index"
 | 
			
		||||
                       :domain-object="domainObject"
 | 
			
		||||
                       :result="result"
 | 
			
		||||
                       :entry="result.entry"
 | 
			
		||||
                       :read-only="true"
 | 
			
		||||
                       :selected-page="result.page"
 | 
			
		||||
                       :selected-section="result.section"
 | 
			
		||||
                       :selected-page="null"
 | 
			
		||||
                       :selected-section="null"
 | 
			
		||||
                       @changeSectionPage="changeSectionPage"
 | 
			
		||||
                       @updateEntries="updateEntries"
 | 
			
		||||
        />
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -21,17 +19,11 @@
 | 
			
		||||
import NotebookEntry from './notebook-entry.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['openmct', 'snapshotContainer'],
 | 
			
		||||
    inject: ['openmct', 'domainObject'],
 | 
			
		||||
    components: {
 | 
			
		||||
        NotebookEntry
 | 
			
		||||
    },
 | 
			
		||||
    props:{
 | 
			
		||||
        domainObject: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            default() {
 | 
			
		||||
                return {};
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        results: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
            default() {
 | 
			
		||||
@@ -39,12 +31,19 @@ export default {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {}
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
        results(newResults) {}
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        changeSectionPage(data) {
 | 
			
		||||
            this.$emit('changeSectionPage', data);
 | 
			
		||||
        },
 | 
			
		||||
        updateEntries(entries) {
 | 
			
		||||
            this.$emit('updateEntries', entries);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,12 @@ export default {
 | 
			
		||||
        return {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        deleteSection(id) {
 | 
			
		||||
            const section = this.sections.find(s => s.id === id);
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,8 @@ export default {
 | 
			
		||||
        this.addPopupMenuItems();
 | 
			
		||||
        this.toggleContentEditable();
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        addPopupMenuItems() {
 | 
			
		||||
            const removeSection = {
 | 
			
		||||
 
 | 
			
		||||
@@ -139,6 +139,8 @@ export default {
 | 
			
		||||
            this.addSection();
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        addPage() {
 | 
			
		||||
            const pageTitle = this.pageTitle;
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
 | 
			
		||||
        <div class="l-browse-bar__end">
 | 
			
		||||
            <div class="l-browse-bar__snapshot-datetime">
 | 
			
		||||
                SNAPSHOT {{ createdOn }}
 | 
			
		||||
                SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
 | 
			
		||||
            </div>
 | 
			
		||||
            <a class="l-browse-bar__annotate-button c-button icon-pencil" title="Annotate" @click="annotateSnapshot">
 | 
			
		||||
                <span class="title-label">Annotate</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,75 +0,0 @@
 | 
			
		||||
import Painterro from 'painterro';
 | 
			
		||||
 | 
			
		||||
const defaultConfig = {
 | 
			
		||||
    id: 'snap-annotation',
 | 
			
		||||
    activeColor: '#ff0000',
 | 
			
		||||
    activeColorAlpha: 1.0,
 | 
			
		||||
    activeFillColor: '#fff',
 | 
			
		||||
    activeFillColorAlpha: 0.0,
 | 
			
		||||
    backgroundFillColor: '#000',
 | 
			
		||||
    backgroundFillColorAlpha: 0.0,
 | 
			
		||||
    defaultFontSize: 16,
 | 
			
		||||
    defaultLineWidth: 2,
 | 
			
		||||
    defaultTool: 'ellipse',
 | 
			
		||||
    hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
 | 
			
		||||
    translation: {
 | 
			
		||||
        name: 'en',
 | 
			
		||||
        strings: {
 | 
			
		||||
            lineColor: 'Line',
 | 
			
		||||
            fillColor: 'Fill',
 | 
			
		||||
            lineWidth: 'Size',
 | 
			
		||||
            textColor: 'Color',
 | 
			
		||||
            fontSize: 'Size',
 | 
			
		||||
            fontStyle: 'Style'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default class PainterroInstance {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.callback = null;
 | 
			
		||||
        this.config = Object.assign({}, defaultConfig);
 | 
			
		||||
        this.config.id = this.config.id;
 | 
			
		||||
        this.config.saveHandler = this.saveHandler.bind(this);
 | 
			
		||||
        this.isSave = false;
 | 
			
		||||
 | 
			
		||||
        this.painterro = Painterro(this.config);
 | 
			
		||||
        this.painterroInstance = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dismiss() {
 | 
			
		||||
        this.isSave = false;
 | 
			
		||||
        this.painterroInstance.save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    save() {
 | 
			
		||||
        this.isSave = true;
 | 
			
		||||
        this.painterroInstance.save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    saveHandler(image, done) {
 | 
			
		||||
        if (this.isSave) {
 | 
			
		||||
            const self = this;
 | 
			
		||||
            const url = image.asBlob();
 | 
			
		||||
            const reader = new window.FileReader();
 | 
			
		||||
            reader.readAsDataURL(url);
 | 
			
		||||
            reader.onloadend = () => {
 | 
			
		||||
                const snapshot = reader.result;
 | 
			
		||||
                const snapshotObject = {
 | 
			
		||||
                    src: snapshot,
 | 
			
		||||
                    type: url.type,
 | 
			
		||||
                    size: url.size,
 | 
			
		||||
                    modified: Date.now()
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                self.callback(snapshotObject);
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        done(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show(src) {
 | 
			
		||||
        this.painterroInstance = this.painterro.show(src);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,7 +13,6 @@
 | 
			
		||||
@import "~styles/controls";
 | 
			
		||||
@import "~styles/forms";
 | 
			
		||||
@import "~styles/table";
 | 
			
		||||
@import "~styles/layout";
 | 
			
		||||
@import "~styles/legacy";
 | 
			
		||||
@import "~styles/legacy-plots";
 | 
			
		||||
@import "~styles/plotly";
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,6 @@
 | 
			
		||||
@import "~styles/controls";
 | 
			
		||||
@import "~styles/forms";
 | 
			
		||||
@import "~styles/table";
 | 
			
		||||
@import "~styles/layout";
 | 
			
		||||
@import "~styles/legacy";
 | 
			
		||||
@import "~styles/legacy-plots";
 | 
			
		||||
@import "~styles/plotly";
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,6 @@
 | 
			
		||||
@import "~styles/controls";
 | 
			
		||||
@import "~styles/forms";
 | 
			
		||||
@import "~styles/table";
 | 
			
		||||
@import "~styles/layout";
 | 
			
		||||
@import "~styles/legacy";
 | 
			
		||||
@import "~styles/legacy-plots";
 | 
			
		||||
@import "~styles/plotly";
 | 
			
		||||
 
 | 
			
		||||
@@ -122,13 +122,8 @@ button {
 | 
			
		||||
        margin-left: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $c1: nth($mixedSettingBg, 1);
 | 
			
		||||
    $c2: nth($mixedSettingBg, 2);
 | 
			
		||||
    $mixedBgD: $mixedSettingBgSize $mixedSettingBgSize;
 | 
			
		||||
 | 
			
		||||
    &--mixed {
 | 
			
		||||
        // E.g. click-icons in toolbar that apply to multiple selected items with different settings
 | 
			
		||||
        @include bgStripes2Color($c1, $c2, $bgSize: $mixedBgD);
 | 
			
		||||
        @include mixedBg();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &--swatched {
 | 
			
		||||
@@ -151,13 +146,6 @@ button {
 | 
			
		||||
            flex: 1 1 auto;
 | 
			
		||||
            font-size: 1.1em;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &--mixed {
 | 
			
		||||
            // Styling for swatched buttons when settings are mixed
 | 
			
		||||
            > [class*='swatch'] {
 | 
			
		||||
                @include bgStripes2Color($c1, $c2, $bgSize: $mixedBgD);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -244,18 +232,10 @@ button {
 | 
			
		||||
 | 
			
		||||
/******************************************************** SECTION */
 | 
			
		||||
section {
 | 
			
		||||
    flex: 0 0 auto;
 | 
			
		||||
    flex: 0 1 auto;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    + section {
 | 
			
		||||
        margin-top: $interiorMargin;
 | 
			
		||||
 | 
			
		||||
        &.is-expanded {
 | 
			
		||||
            margin-bottom: $interiorMargin * 3;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-expanded {
 | 
			
		||||
        flex: 0 1 auto;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .c-section__header {
 | 
			
		||||
@@ -829,6 +809,10 @@ select {
 | 
			
		||||
    box-shadow: rgba($colorBodyFg, 0.4) 0 0 3px;
 | 
			
		||||
    flex: 0 0 auto;
 | 
			
		||||
    padding: $interiorMargin $interiorMarginLg;
 | 
			
		||||
 | 
			
		||||
    &--mixed {
 | 
			
		||||
        @include mixedBg();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/******************************************************** SLIDERS */
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,12 @@ body.desktop {
 | 
			
		||||
            background: $scrollbarThumbColorMenuHov;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    div, span {
 | 
			
		||||
        // Firefox
 | 
			
		||||
        scrollbar-color: $scrollbarThumbColor $scrollbarTrackColorBg;
 | 
			
		||||
        scrollbar-width: thin;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/******************************************************** HTML ENTITIES */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,87 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2018, 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/************************** BROWSE BAR */
 | 
			
		||||
.l-browse-bar {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
 | 
			
		||||
    [class*="__"] {
 | 
			
		||||
        // Removes extraneous horizontal white space
 | 
			
		||||
        display: inline-flex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__start {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
        margin-right: $interiorMargin;
 | 
			
		||||
        min-width: 0; // Forces interior to compress when pushed on
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__end {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
 | 
			
		||||
        [class*="__"] + [class*="__"] {
 | 
			
		||||
            margin-left: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__nav-to-parent-button,
 | 
			
		||||
    &__disclosure-button {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__nav-to-parent-button {
 | 
			
		||||
        // This is an icon-button
 | 
			
		||||
        $p: $interiorMargin;
 | 
			
		||||
        margin-right: $interiorMargin;
 | 
			
		||||
        padding-left: $p;
 | 
			
		||||
        padding-right: $p;
 | 
			
		||||
 | 
			
		||||
        .is-editing & {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__object-name--w {
 | 
			
		||||
        flex: 0 1 auto;
 | 
			
		||||
        @include headerFont(1.4em);
 | 
			
		||||
        min-width: 0;
 | 
			
		||||
 | 
			
		||||
        &:before {
 | 
			
		||||
            // Icon
 | 
			
		||||
            margin-right: $interiorMargin;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__object-name {
 | 
			
		||||
        flex: 0 1 auto;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__object-details {
 | 
			
		||||
        opacity: 0.5;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -72,6 +72,7 @@
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    font-size: 1.25em;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    > * {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
@@ -82,6 +83,12 @@
 | 
			
		||||
    &__value {
 | 
			
		||||
        color: $colorBodyFgEm;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .c-frame & {
 | 
			
		||||
        // When in a Display or Flexible Layout
 | 
			
		||||
        @include abs();
 | 
			
		||||
        padding: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-clock {
 | 
			
		||||
 
 | 
			
		||||
@@ -50,6 +50,14 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/************************** EFFECTS */
 | 
			
		||||
@mixin mixedBg() {
 | 
			
		||||
    $c1: nth($mixedSettingBg, 1);
 | 
			
		||||
    $c2: nth($mixedSettingBg, 2);
 | 
			
		||||
    $mixedBgD: $mixedSettingBgSize $mixedSettingBgSize;
 | 
			
		||||
    @include bgStripes2Color($c1, $c2, $bgSize: $mixedBgD);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mixin pulse($animName: pulse, $dur: 500ms, $iteration: infinite, $opacity0: 0.5, $opacity100: 1) {
 | 
			
		||||
    @keyframes #{$animName} {
 | 
			
		||||
        0%   { opacity: $opacity0; }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
@import "../api/overlays/components/dialog-component.scss";
 | 
			
		||||
@import "../api/overlays/components/overlay-component.scss";
 | 
			
		||||
@import "../plugins/condition/components/condition.scss";
 | 
			
		||||
@import "../plugins/condition/components/condition-set.scss";
 | 
			
		||||
@import "../plugins/condition/components/conditionals.scss";
 | 
			
		||||
@import "../plugins/conditionWidget/components/condition-widget.scss";
 | 
			
		||||
@import "../plugins/condition/components/inspector/conditional-styles.scss";
 | 
			
		||||
@import "../plugins/displayLayout/components/box-view.scss";
 | 
			
		||||
 
 | 
			
		||||
@@ -104,7 +104,7 @@ export default {
 | 
			
		||||
            keys.forEach(key => {
 | 
			
		||||
                let firstChild = this.$el.querySelector(':first-child');
 | 
			
		||||
                if (firstChild) {
 | 
			
		||||
                    if ((typeof styleObj[key] === 'string') && (styleObj[key].indexOf('transparent') > -1)) {
 | 
			
		||||
                    if ((typeof styleObj[key] === 'string') && (styleObj[key].indexOf('__no_value') > -1)) {
 | 
			
		||||
                        if (firstChild.style[key]) {
 | 
			
		||||
                            firstChild.style[key] = '';
 | 
			
		||||
                        }
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@ export default {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.excludeObjectTypes = ['folder', 'webPage', 'conditionSet'];
 | 
			
		||||
        this.excludeObjectTypes = ['folder', 'webPage', 'conditionSet', 'summary-widget', 'hyperlink'];
 | 
			
		||||
        this.openmct.selection.on('change', this.updateInspectorViews);
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
@@ -108,7 +108,7 @@ export default {
 | 
			
		||||
                let object = selection[0][0].context.item;
 | 
			
		||||
                if (object) {
 | 
			
		||||
                    let type = this.openmct.types.get(object.type);
 | 
			
		||||
                    this.showStyles = (this.excludeObjectTypes.indexOf(object.type) < 0) && type.definition.creatable;
 | 
			
		||||
                    this.showStyles = this.isLayoutObject(selection[0], object.type) || this.isCreatableObject(object, type);
 | 
			
		||||
                }
 | 
			
		||||
                if (!this.currentTabbedView.key || (!this.showStyles && this.currentTabbedView.key === this.tabbedViews[1].key))
 | 
			
		||||
                {
 | 
			
		||||
@@ -116,6 +116,14 @@ export default {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        isLayoutObject(selection, objectType) {
 | 
			
		||||
            //we allow conditionSets to be styled if they're part of a layout
 | 
			
		||||
            return selection.length > 1 &&
 | 
			
		||||
                ((objectType === 'conditionSet') || (this.excludeObjectTypes.indexOf(objectType) < 0));
 | 
			
		||||
        },
 | 
			
		||||
        isCreatableObject(object, type) {
 | 
			
		||||
            return (this.excludeObjectTypes.indexOf(object.type) < 0) && type.definition.creatable;
 | 
			
		||||
        },
 | 
			
		||||
        updateCurrentTab(view) {
 | 
			
		||||
            this.currentTabbedView = view;
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,8 @@
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import ConditionalStylesView from '../../plugins/condition/components/inspector/ConditionalStylesView.vue';
 | 
			
		||||
import MultiSelectStylesView from '../../plugins/condition/components/inspector/MultiSelectStylesView.vue';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { getStyleProp } from "../../plugins/condition/utils/styleUtils";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
@@ -44,35 +44,9 @@ export default {
 | 
			
		||||
        this.openmct.selection.off('change', this.updateSelection);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        getStyleProperties(item) {
 | 
			
		||||
            let styleProps = {};
 | 
			
		||||
            Object.keys(item).forEach((key) => {
 | 
			
		||||
                Object.assign(styleProps, getStyleProp(key, item[key]));
 | 
			
		||||
            });
 | 
			
		||||
            return styleProps;
 | 
			
		||||
        },
 | 
			
		||||
        updateSelection(selection) {
 | 
			
		||||
            if (selection.length > 0 && selection[0].length > 0) {
 | 
			
		||||
                let isChildItem = false;
 | 
			
		||||
                let domainObject = selection[0][0].context.item;
 | 
			
		||||
                let layoutItem = {};
 | 
			
		||||
                let styleProps = this.getStyleProperties({
 | 
			
		||||
                    fill: 'transparent',
 | 
			
		||||
                    stroke: 'transparent',
 | 
			
		||||
                    color: 'transparent'
 | 
			
		||||
                });
 | 
			
		||||
                if (selection[0].length > 1) {
 | 
			
		||||
                    isChildItem = true;
 | 
			
		||||
                    //If there are more than 1 items in the selection[0] list, the first one could either be a sub domain object OR a layout drawing control.
 | 
			
		||||
                    //The second item in the selection[0] list is the container object (usually a layout)
 | 
			
		||||
                    if (!domainObject) {
 | 
			
		||||
                        styleProps = {};
 | 
			
		||||
                        layoutItem = selection[0][0].context.layoutItem;
 | 
			
		||||
                        styleProps = this.getStyleProperties(layoutItem);
 | 
			
		||||
                        domainObject = selection[0][1].context.item;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let template = selection.length > 1 ? '<multi-select-styles-view></multi-select-styles-view>' : '<conditional-styles-view></conditional-styles-view>';
 | 
			
		||||
                if (this.component) {
 | 
			
		||||
                    this.component.$destroy();
 | 
			
		||||
                    this.component = undefined;
 | 
			
		||||
@@ -83,20 +57,14 @@ export default {
 | 
			
		||||
                this.component = new Vue({
 | 
			
		||||
                    provide: {
 | 
			
		||||
                        openmct: this.openmct,
 | 
			
		||||
                        domainObject: domainObject
 | 
			
		||||
                        selection: selection
 | 
			
		||||
                    },
 | 
			
		||||
                    el: viewContainer,
 | 
			
		||||
                    components: {
 | 
			
		||||
                        ConditionalStylesView
 | 
			
		||||
                        ConditionalStylesView,
 | 
			
		||||
                        MultiSelectStylesView
 | 
			
		||||
                    },
 | 
			
		||||
                    data() {
 | 
			
		||||
                        return {
 | 
			
		||||
                            layoutItem,
 | 
			
		||||
                            styleProps,
 | 
			
		||||
                            isChildItem
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    template: '<conditional-styles-view :can-hide="isChildItem" :item-id="layoutItem.id" :initial-styles="styleProps"></conditional-styles-view>'
 | 
			
		||||
                    template: template
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@
 | 
			
		||||
 | 
			
		||||
    <div class="l-browse-bar__end">
 | 
			
		||||
        <view-switcher
 | 
			
		||||
            v-if="!isEditing"
 | 
			
		||||
            :current-view="currentView"
 | 
			
		||||
            :views="views"
 | 
			
		||||
            @setView="setView"
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
    <button
 | 
			
		||||
        class="c-button--menu"
 | 
			
		||||
        :class="currentView.cssClass"
 | 
			
		||||
        title="Switch view type"
 | 
			
		||||
        title="Change the current view"
 | 
			
		||||
        @click.stop="toggleViewMenu"
 | 
			
		||||
    >
 | 
			
		||||
        <span class="c-button__label">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,24 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2020, 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
/******************************* SHELL */
 | 
			
		||||
.l-shell {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
@@ -126,6 +147,9 @@
 | 
			
		||||
 | 
			
		||||
    body.mobile & .l-shell__main-view-browse-bar {
 | 
			
		||||
        margin-left: $mobileMenuIconD; // Make room for the hamburger!
 | 
			
		||||
        .c-button[class*='__actions__edit'] {
 | 
			
		||||
            display: none; // Hide the main view edit button when in mobile context
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__head {
 | 
			
		||||
@@ -269,6 +293,79 @@
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/************************** BROWSE BAR */
 | 
			
		||||
.l-browse-bar {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
 | 
			
		||||
    [class*="__"] {
 | 
			
		||||
        // Removes extraneous horizontal white space
 | 
			
		||||
        display: inline-flex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__start,
 | 
			
		||||
    &__end,
 | 
			
		||||
    &__actions {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__actions,
 | 
			
		||||
    &__end {
 | 
			
		||||
        > * + * {
 | 
			
		||||
            margin-left: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__start {
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
        margin-right: $interiorMargin;
 | 
			
		||||
        min-width: 0; // Forces interior to compress when pushed on
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__end {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__nav-to-parent-button,
 | 
			
		||||
    &__disclosure-button {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__nav-to-parent-button {
 | 
			
		||||
        // This is an icon-button
 | 
			
		||||
        $p: $interiorMargin;
 | 
			
		||||
        margin-right: $interiorMargin;
 | 
			
		||||
        padding-left: $p;
 | 
			
		||||
        padding-right: $p;
 | 
			
		||||
 | 
			
		||||
        .is-editing & {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__object-name--w,
 | 
			
		||||
    &__object-name {
 | 
			
		||||
        flex: 0 1 auto;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__object-name--w {
 | 
			
		||||
        @include headerFont(1.4em);
 | 
			
		||||
        min-width: 0;
 | 
			
		||||
 | 
			
		||||
        &:before {
 | 
			
		||||
            // Icon
 | 
			
		||||
            margin-right: $interiorMargin;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__object-details {
 | 
			
		||||
        opacity: 0.5;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/************************** DRAWER */
 | 
			
		||||
.c-drawer {
 | 
			
		||||
    /* New sliding overlay or push element to contain things
 | 
			
		||||
    * Designed for mobile and compact desktop scenarios
 | 
			
		||||
@@ -332,4 +429,3 @@
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user