Compare commits
	
		
			59 Commits
		
	
	
		
			version-1.
			...
			v1.4.0-rc1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d788031019 | ||
| 
						 | 
					d870874649 | ||
| 
						 | 
					711a7a2eb5 | ||
| 
						 | 
					c105a08cfe | ||
| 
						 | 
					b87375a809 | ||
| 
						 | 
					9fed056d22 | ||
| 
						 | 
					251bf21933 | ||
| 
						 | 
					a180bf7c02 | ||
| 
						 | 
					ed8a54f0f9 | ||
| 
						 | 
					ff3c2da0f9 | ||
| 
						 | 
					28d5821120 | ||
| 
						 | 
					f5ee457274 | ||
| 
						 | 
					9d2770e4d2 | ||
| 
						 | 
					8b25009816 | ||
| 
						 | 
					074fe4481a | ||
| 
						 | 
					fbd928b842 | ||
| 
						 | 
					110947db09 | ||
| 
						 | 
					ef91e92fbc | ||
| 
						 | 
					d201cac4ac | ||
| 
						 | 
					dcb3ccfec7 | ||
| 
						 | 
					78522cd4f1 | ||
| 
						 | 
					ca232d45cc | ||
| 
						 | 
					df495c841a | ||
| 
						 | 
					92a37ef36b | ||
| 
						 | 
					fd731ca430 | ||
| 
						 | 
					263b1cd3d5 | ||
| 
						 | 
					978fc8b5a3 | ||
| 
						 | 
					698ccc5a35 | ||
| 
						 | 
					e5aa5b5a5f | ||
| 
						 | 
					b942988ef8 | ||
| 
						 | 
					1eec20f2ea | ||
| 
						 | 
					767a2048eb | ||
| 
						 | 
					e65cf1661c | ||
| 
						 | 
					0eae48646c | ||
| 
						 | 
					0ba8a275d2 | ||
| 
						 | 
					d8d32cc3ac | ||
| 
						 | 
					a800848fe1 | ||
| 
						 | 
					6881d98ba6 | ||
| 
						 | 
					48d077cd2e | ||
| 
						 | 
					030dd93c91 | ||
| 
						 | 
					03bf6fc0a3 | ||
| 
						 | 
					ef0a2ed5d2 | ||
| 
						 | 
					a40aa84752 | ||
| 
						 | 
					d3b69dda82 | ||
| 
						 | 
					d3126ebf5c | ||
| 
						 | 
					4479cbc7a2 | ||
| 
						 | 
					f8ff44dac0 | ||
| 
						 | 
					8f4280d15b | ||
| 
						 | 
					6daa27ff31 | ||
| 
						 | 
					43f6c3f85d | ||
| 
						 | 
					1a7c76cf3e | ||
| 
						 | 
					cee9cd7bd1 | ||
| 
						 | 
					c42df20281 | ||
| 
						 | 
					b4149bd2b3 | ||
| 
						 | 
					f436ac9ba0 | ||
| 
						 | 
					8493b481dd | ||
| 
						 | 
					28723b59b7 | ||
| 
						 | 
					9fa7de0b77 | ||
| 
						 | 
					54bfc84ada | 
@@ -76,7 +76,6 @@ define([
 | 
			
		||||
 | 
			
		||||
            workerRequest[prop] = Number(workerRequest[prop]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        workerRequest.name = domainObject.name;
 | 
			
		||||
 | 
			
		||||
        return workerRequest;
 | 
			
		||||
 
 | 
			
		||||
@@ -108,6 +108,7 @@
 | 
			
		||||
 | 
			
		||||
        for (; nextStep < end && data.length < 5000; nextStep += step) {
 | 
			
		||||
            data.push({
 | 
			
		||||
                name: request.name,
 | 
			
		||||
                utc: nextStep,
 | 
			
		||||
                yesterday: nextStep - 60 * 60 * 24 * 1000,
 | 
			
		||||
                sin: sin(nextStep, period, amplitude, offset, phase, randomness),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										87
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										87
									
								
								index.html
									
									
									
									
									
								
							@@ -30,50 +30,12 @@
 | 
			
		||||
        <link rel="icon" type="image/png" href="dist/favicons/favicon-96x96.png" sizes="96x96" type="image/x-icon">
 | 
			
		||||
        <link rel="icon" type="image/png" href="dist/favicons/favicon-32x32.png" sizes="32x32" type="image/x-icon">
 | 
			
		||||
        <link rel="icon" type="image/png" href="dist/favicons/favicon-16x16.png" sizes="16x16" type="image/x-icon">
 | 
			
		||||
        <style type="text/css">
 | 
			
		||||
            @keyframes splash-spinner {
 | 
			
		||||
                0% {
 | 
			
		||||
                    transform: translate(-50%, -50%) rotate(0deg); }
 | 
			
		||||
                100% {
 | 
			
		||||
                    transform: translate(-50%, -50%) rotate(360deg); } }
 | 
			
		||||
 | 
			
		||||
            #splash-screen {
 | 
			
		||||
                background-color: black;
 | 
			
		||||
                position: absolute;
 | 
			
		||||
                top: 0; right: 0; bottom: 0; left: 0;
 | 
			
		||||
                z-index: 10000;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            #splash-screen:before {
 | 
			
		||||
                animation-name: splash-spinner;
 | 
			
		||||
                animation-duration: 0.5s;
 | 
			
		||||
                animation-iteration-count: infinite;
 | 
			
		||||
                animation-timing-function: linear;
 | 
			
		||||
                border-radius: 50%;
 | 
			
		||||
                border-color: rgba(255,255,255,0.25);
 | 
			
		||||
                border-top-color: white;
 | 
			
		||||
                border-style: solid;
 | 
			
		||||
                border-width: 10px;
 | 
			
		||||
                content: '';
 | 
			
		||||
                display: block;
 | 
			
		||||
                opacity: 0.25;
 | 
			
		||||
                position: absolute;
 | 
			
		||||
                left: 50%; top: 50%;
 | 
			
		||||
                height: 100px; width: 100px;
 | 
			
		||||
            }
 | 
			
		||||
        </style>
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
    </body>
 | 
			
		||||
    <script>
 | 
			
		||||
        const THIRTY_SECONDS = 30 * 1000;
 | 
			
		||||
        const ONE_MINUTE = THIRTY_SECONDS * 2;
 | 
			
		||||
        const FIVE_MINUTES = ONE_MINUTE * 5;
 | 
			
		||||
        const FIFTEEN_MINUTES = FIVE_MINUTES * 3;
 | 
			
		||||
        const THIRTY_MINUTES = FIFTEEN_MINUTES * 2;
 | 
			
		||||
        const ONE_HOUR = THIRTY_MINUTES * 2;
 | 
			
		||||
        const TWO_HOURS = ONE_HOUR * 2;
 | 
			
		||||
        const ONE_DAY = ONE_HOUR * 24;
 | 
			
		||||
        const THIRTY_MINUTES = THIRTY_SECONDS * 60;
 | 
			
		||||
 | 
			
		||||
        [
 | 
			
		||||
            'example/eventGenerator'
 | 
			
		||||
@@ -111,21 +73,21 @@
 | 
			
		||||
                        {
 | 
			
		||||
                            label: 'Last Day',
 | 
			
		||||
                            bounds: {
 | 
			
		||||
                                start: () => Date.now() - ONE_DAY,
 | 
			
		||||
                                start: () => Date.now() - 1000 * 60 * 60 * 24,
 | 
			
		||||
                                end: () => Date.now()
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            label: 'Last 2 hours',
 | 
			
		||||
                            bounds: {
 | 
			
		||||
                                start: () => Date.now() - TWO_HOURS,
 | 
			
		||||
                                start: () => Date.now() - 1000 * 60 * 60 * 2,
 | 
			
		||||
                                end: () => Date.now()
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            label: 'Last hour',
 | 
			
		||||
                            bounds: {
 | 
			
		||||
                                start: () => Date.now() - ONE_HOUR,
 | 
			
		||||
                                start: () => Date.now() - 1000 * 60 * 60,
 | 
			
		||||
                                end: () => Date.now()
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
@@ -134,7 +96,7 @@
 | 
			
		||||
                    records: 10,
 | 
			
		||||
                    // maximum duration between start and end bounds
 | 
			
		||||
                    // for utc-based time systems this is in milliseconds
 | 
			
		||||
                    limit: ONE_DAY
 | 
			
		||||
                    limit: 1000 * 60 * 60 * 24
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: "Realtime",
 | 
			
		||||
@@ -143,44 +105,7 @@
 | 
			
		||||
                    clockOffsets: {
 | 
			
		||||
                        start: - THIRTY_MINUTES,
 | 
			
		||||
                        end: THIRTY_SECONDS
 | 
			
		||||
                    },
 | 
			
		||||
                    presets: [
 | 
			
		||||
                        {
 | 
			
		||||
                            label: '1 Hour',
 | 
			
		||||
                            bounds: {
 | 
			
		||||
                                start: - ONE_HOUR,
 | 
			
		||||
                                end: THIRTY_SECONDS
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            label: '30 Minutes',
 | 
			
		||||
                            bounds: {
 | 
			
		||||
                                start: - THIRTY_MINUTES,
 | 
			
		||||
                                end: THIRTY_SECONDS
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            label: '15 Minutes',
 | 
			
		||||
                            bounds: {
 | 
			
		||||
                                start: - FIFTEEN_MINUTES,
 | 
			
		||||
                                end: THIRTY_SECONDS
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            label: '5 Minutes',
 | 
			
		||||
                            bounds: {
 | 
			
		||||
                                start: - FIVE_MINUTES,
 | 
			
		||||
                                end: THIRTY_SECONDS
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            label: '1 Minute',
 | 
			
		||||
                            bounds: {
 | 
			
		||||
                                start: - ONE_MINUTE,
 | 
			
		||||
                                end: THIRTY_SECONDS
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "openmct",
 | 
			
		||||
  "version": "1.4.0",
 | 
			
		||||
  "version": "1.3.3-SNAPSHOT",
 | 
			
		||||
  "description": "The Open MCT core platform",
 | 
			
		||||
  "dependencies": {},
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
 
 | 
			
		||||
@@ -143,8 +143,8 @@ define([
 | 
			
		||||
                            "$window"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "group": "windowing",
 | 
			
		||||
                        "cssClass": "icon-new-window",
 | 
			
		||||
                        "priority": "preferred"
 | 
			
		||||
                        "priority": 10,
 | 
			
		||||
                        "cssClass": "icon-new-window"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "runs": [
 | 
			
		||||
 
 | 
			
		||||
@@ -139,7 +139,9 @@ define([
 | 
			
		||||
                        ],
 | 
			
		||||
                        "description": "Edit",
 | 
			
		||||
                        "category": "view-control",
 | 
			
		||||
                        "cssClass": "major icon-pencil"
 | 
			
		||||
                        "cssClass": "major icon-pencil",
 | 
			
		||||
                        "group": "action",
 | 
			
		||||
                        "priority": 10
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "properties",
 | 
			
		||||
@@ -150,6 +152,8 @@ define([
 | 
			
		||||
                        "implementation": PropertiesAction,
 | 
			
		||||
                        "cssClass": "major icon-pencil",
 | 
			
		||||
                        "name": "Edit Properties...",
 | 
			
		||||
                        "group": "action",
 | 
			
		||||
                        "priority": 10,
 | 
			
		||||
                        "description": "Edit properties of this object.",
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "dialogService"
 | 
			
		||||
 
 | 
			
		||||
@@ -114,12 +114,7 @@ define(["objectUtils"],
 | 
			
		||||
            var self = this,
 | 
			
		||||
                domainObject = this.domainObject;
 | 
			
		||||
 | 
			
		||||
            const identifier = {
 | 
			
		||||
                namespace: this.getSpace(),
 | 
			
		||||
                key: this.getKey()
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let newStyleObject = objectUtils.toNewFormat(domainObject.getModel(), identifier);
 | 
			
		||||
            let newStyleObject = objectUtils.toNewFormat(domainObject.getModel(), domainObject.getId());
 | 
			
		||||
 | 
			
		||||
            return this.openmct.objects
 | 
			
		||||
                .save(newStyleObject)
 | 
			
		||||
@@ -151,7 +146,6 @@ define(["objectUtils"],
 | 
			
		||||
                    return domainObject.useCapability("mutation", function () {
 | 
			
		||||
                        return model;
 | 
			
		||||
                    }, modified);
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -159,10 +153,11 @@ define(["objectUtils"],
 | 
			
		||||
                return this.$q.when(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return this.persistenceService.readObject(
 | 
			
		||||
                this.getSpace(),
 | 
			
		||||
                this.getKey()
 | 
			
		||||
            ).then(updateModel);
 | 
			
		||||
            return this.openmct.objects.get(domainObject.getId()).then((newStyleObject) => {
 | 
			
		||||
                let oldStyleObject = this.openmct.legacyObject(newStyleObject);
 | 
			
		||||
 | 
			
		||||
                return updateModel(oldStyleObject.getModel());
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@ define(
 | 
			
		||||
                key = "persistence key",
 | 
			
		||||
                id = "object identifier",
 | 
			
		||||
                model,
 | 
			
		||||
                refreshModel,
 | 
			
		||||
                SPACE = "some space",
 | 
			
		||||
                persistence,
 | 
			
		||||
                mockOpenMCT,
 | 
			
		||||
@@ -60,6 +61,7 @@ define(
 | 
			
		||||
                    someKey: "some value",
 | 
			
		||||
                    name: "domain object"
 | 
			
		||||
                };
 | 
			
		||||
                refreshModel = {someOtherKey: "some other value"};
 | 
			
		||||
 | 
			
		||||
                mockPersistenceService = jasmine.createSpyObj(
 | 
			
		||||
                    "persistenceService",
 | 
			
		||||
@@ -99,8 +101,8 @@ define(
 | 
			
		||||
 | 
			
		||||
                mockNewStyleDomainObject = Object.assign({}, model);
 | 
			
		||||
                mockNewStyleDomainObject.identifier = {
 | 
			
		||||
                    namespace: SPACE,
 | 
			
		||||
                    key: key
 | 
			
		||||
                    namespace: "",
 | 
			
		||||
                    key: id
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                // Simulate mutation capability
 | 
			
		||||
@@ -110,8 +112,16 @@ define(
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockOpenMCT = {};
 | 
			
		||||
                mockOpenMCT.objects = jasmine.createSpyObj('Object API', ['save']);
 | 
			
		||||
                mockOpenMCT = {
 | 
			
		||||
                    legacyObject: function (object) {
 | 
			
		||||
                        return {
 | 
			
		||||
                            getModel: function () {
 | 
			
		||||
                                return object;
 | 
			
		||||
                            }
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                mockOpenMCT.objects = jasmine.createSpyObj('Object API', ['save', 'get']);
 | 
			
		||||
 | 
			
		||||
                mockIdentifierService.parse.and.returnValue(mockIdentifier);
 | 
			
		||||
                mockIdentifier.getSpace.and.returnValue(SPACE);
 | 
			
		||||
@@ -131,6 +141,7 @@ define(
 | 
			
		||||
            describe("successful persistence", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockOpenMCT.objects.save.and.returnValue(Promise.resolve(true));
 | 
			
		||||
                    mockOpenMCT.objects.get.and.returnValue(Promise.resolve(refreshModel));
 | 
			
		||||
                });
 | 
			
		||||
                it("creates unpersisted objects with the persistence service", function () {
 | 
			
		||||
                    // Verify precondition; no call made during constructor
 | 
			
		||||
@@ -146,11 +157,10 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("refreshes the domain object model from persistence", function () {
 | 
			
		||||
                    var refreshModel = {someOtherKey: "some other value"};
 | 
			
		||||
                    model.persisted = 1;
 | 
			
		||||
                    mockPersistenceService.readObject.and.returnValue(asPromise(refreshModel));
 | 
			
		||||
                    persistence.refresh();
 | 
			
		||||
                    expect(model).toEqual(refreshModel);
 | 
			
		||||
                    persistence.refresh().then(() => {
 | 
			
		||||
                        expect(model).toEqual(refreshModel);
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("does not trigger error notification on successful"
 | 
			
		||||
 
 | 
			
		||||
@@ -66,6 +66,8 @@ define([
 | 
			
		||||
                        "description": "Move object to another location.",
 | 
			
		||||
                        "cssClass": "icon-move",
 | 
			
		||||
                        "category": "contextual",
 | 
			
		||||
                        "group": "action",
 | 
			
		||||
                        "priority": 9,
 | 
			
		||||
                        "implementation": MoveAction,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "policyService",
 | 
			
		||||
@@ -79,6 +81,8 @@ define([
 | 
			
		||||
                        "description": "Duplicate object to another location.",
 | 
			
		||||
                        "cssClass": "icon-duplicate",
 | 
			
		||||
                        "category": "contextual",
 | 
			
		||||
                        "group": "action",
 | 
			
		||||
                        "priority": 8,
 | 
			
		||||
                        "implementation": CopyAction,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "$log",
 | 
			
		||||
@@ -95,6 +99,8 @@ define([
 | 
			
		||||
                        "description": "Create Link to object in another location.",
 | 
			
		||||
                        "cssClass": "icon-link",
 | 
			
		||||
                        "category": "contextual",
 | 
			
		||||
                        "group": "action",
 | 
			
		||||
                        "priority": 7,
 | 
			
		||||
                        "implementation": LinkAction,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "policyService",
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<div class="c-clock l-time-display u-style-receiver js-style-receiver" ng-controller="ClockController as clock">
 | 
			
		||||
<div class="c-clock l-time-display" ng-controller="ClockController as clock">
 | 
			
		||||
	<div class="c-clock__timezone">
 | 
			
		||||
		{{clock.zone()}}
 | 
			
		||||
	</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<div class="c-timer u-style-receiver js-style-receiver is-{{timer.timerState}}" ng-controller="TimerController as timer">
 | 
			
		||||
<div class="c-timer is-{{timer.timerState}}" ng-controller="TimerController as timer">
 | 
			
		||||
    <div class="c-timer__controls">
 | 
			
		||||
        <button ng-click="timer.clickStopButton()"
 | 
			
		||||
                ng-hide="timer.timerState == 'stopped'"
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,8 @@ define([
 | 
			
		||||
                            "implementation": ExportAsJSONAction,
 | 
			
		||||
                            "category": "contextual",
 | 
			
		||||
                            "cssClass": "icon-export",
 | 
			
		||||
                            "group": "json",
 | 
			
		||||
                            "priority": 2,
 | 
			
		||||
                            "depends": [
 | 
			
		||||
                                "openmct",
 | 
			
		||||
                                "exportService",
 | 
			
		||||
@@ -61,6 +63,8 @@ define([
 | 
			
		||||
                            "implementation": ImportAsJSONAction,
 | 
			
		||||
                            "category": "contextual",
 | 
			
		||||
                            "cssClass": "icon-import",
 | 
			
		||||
                            "group": "json",
 | 
			
		||||
                            "priority": 2,
 | 
			
		||||
                            "depends": [
 | 
			
		||||
                                "exportService",
 | 
			
		||||
                                "identifierService",
 | 
			
		||||
 
 | 
			
		||||
@@ -242,7 +242,9 @@ define([
 | 
			
		||||
 | 
			
		||||
        this.overlays = new OverlayAPI.default();
 | 
			
		||||
 | 
			
		||||
        this.contextMenu = new api.ContextMenuRegistry();
 | 
			
		||||
        this.menus = new api.MenuAPI(this);
 | 
			
		||||
 | 
			
		||||
        this.actions = new api.ActionsAPI(this);
 | 
			
		||||
 | 
			
		||||
        this.router = new ApplicationRouter();
 | 
			
		||||
 | 
			
		||||
@@ -271,6 +273,7 @@ define([
 | 
			
		||||
        this.install(this.plugins.URLTimeSettingsSynchronizer());
 | 
			
		||||
        this.install(this.plugins.NotificationIndicator());
 | 
			
		||||
        this.install(this.plugins.NewFolderAction());
 | 
			
		||||
        this.install(this.plugins.ViewDatumAction());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MCT.prototype = Object.create(EventEmitter.prototype);
 | 
			
		||||
 
 | 
			
		||||
@@ -35,5 +35,5 @@ export default function LegacyActionAdapter(openmct, legacyActions) {
 | 
			
		||||
 | 
			
		||||
    legacyActions.filter(contextualCategoryOnly)
 | 
			
		||||
        .map(LegacyAction => new LegacyContextMenuAction(openmct, LegacyAction))
 | 
			
		||||
        .forEach(openmct.contextMenu.registerAction);
 | 
			
		||||
        .forEach(openmct.actions.register);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,8 @@ export default class LegacyContextMenuAction {
 | 
			
		||||
        this.description = LegacyAction.definition.description;
 | 
			
		||||
        this.cssClass = LegacyAction.definition.cssClass;
 | 
			
		||||
        this.LegacyAction = LegacyAction;
 | 
			
		||||
        this.group = LegacyAction.definition.group;
 | 
			
		||||
        this.priority = LegacyAction.definition.priority;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    invoke(objectPath) {
 | 
			
		||||
 
 | 
			
		||||
@@ -129,6 +129,13 @@ define([
 | 
			
		||||
 | 
			
		||||
    ObjectServiceProvider.prototype.get = function (key) {
 | 
			
		||||
        let keyString = utils.makeKeyString(key);
 | 
			
		||||
        const space = this.getSpace(keyString);
 | 
			
		||||
 | 
			
		||||
        let identifier = utils.parseKeyString(keyString);
 | 
			
		||||
        // We assign to the space for legacy persistence providers since they register themselves using a defaultSpace.
 | 
			
		||||
        // This is the way to make everyone happy.
 | 
			
		||||
        identifier.namespace = space;
 | 
			
		||||
        keyString = utils.makeKeyString(identifier);
 | 
			
		||||
 | 
			
		||||
        return this.objectService.getObjects([keyString])
 | 
			
		||||
            .then(function (results) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										178
									
								
								src/api/actions/ActionCollection.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/api/actions/ActionCollection.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,178 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import EventEmitter from 'EventEmitter';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
class ActionCollection extends EventEmitter {
 | 
			
		||||
    constructor(applicableActions, objectPath, view, openmct) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        this.applicableActions = applicableActions;
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.objectPath = objectPath;
 | 
			
		||||
        this.view = view;
 | 
			
		||||
        this.objectUnsubscribes = [];
 | 
			
		||||
 | 
			
		||||
        let debounceOptions = {
 | 
			
		||||
            leading: false,
 | 
			
		||||
            trailing: true
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this._updateActions = _.debounce(this._updateActions.bind(this), 150, debounceOptions);
 | 
			
		||||
        this._update = _.debounce(this._update.bind(this), 150, debounceOptions);
 | 
			
		||||
 | 
			
		||||
        this._observeObjectPath();
 | 
			
		||||
        this._initializeActions();
 | 
			
		||||
 | 
			
		||||
        this.openmct.editor.on('isEditing', this._updateActions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    disable(actionKeys) {
 | 
			
		||||
        actionKeys.forEach(actionKey => {
 | 
			
		||||
            if (this.applicableActions[actionKey]) {
 | 
			
		||||
                this.applicableActions[actionKey].isDisabled = true;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        this._update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enable(actionKeys) {
 | 
			
		||||
        actionKeys.forEach(actionKey => {
 | 
			
		||||
            if (this.applicableActions[actionKey]) {
 | 
			
		||||
                this.applicableActions[actionKey].isDisabled = false;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        this._update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hide(actionKeys) {
 | 
			
		||||
        actionKeys.forEach(actionKey => {
 | 
			
		||||
            if (this.applicableActions[actionKey]) {
 | 
			
		||||
                this.applicableActions[actionKey].isHidden = true;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        this._update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show(actionKeys) {
 | 
			
		||||
        actionKeys.forEach(actionKey => {
 | 
			
		||||
            if (this.applicableActions[actionKey]) {
 | 
			
		||||
                this.applicableActions[actionKey].isHidden = false;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        this._update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
        this.objectUnsubscribes.forEach(unsubscribe => {
 | 
			
		||||
            unsubscribe();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.openmct.editor.off('isEditing', this._updateActions);
 | 
			
		||||
 | 
			
		||||
        this.emit('destroy', this.view);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getVisibleActions() {
 | 
			
		||||
        let actionsArray = Object.keys(this.applicableActions);
 | 
			
		||||
        let visibleActions = [];
 | 
			
		||||
 | 
			
		||||
        actionsArray.forEach(actionKey => {
 | 
			
		||||
            let action = this.applicableActions[actionKey];
 | 
			
		||||
 | 
			
		||||
            if (!action.isHidden) {
 | 
			
		||||
                visibleActions.push(action);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return visibleActions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getStatusBarActions() {
 | 
			
		||||
        let actionsArray = Object.keys(this.applicableActions);
 | 
			
		||||
        let statusBarActions = [];
 | 
			
		||||
 | 
			
		||||
        actionsArray.forEach(actionKey => {
 | 
			
		||||
            let action = this.applicableActions[actionKey];
 | 
			
		||||
 | 
			
		||||
            if (action.showInStatusBar && !action.isDisabled && !action.isHidden) {
 | 
			
		||||
                statusBarActions.push(action);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return statusBarActions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _update() {
 | 
			
		||||
        this.emit('update', this.applicableActions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _observeObjectPath() {
 | 
			
		||||
        let actionCollection = this;
 | 
			
		||||
 | 
			
		||||
        function updateObject(oldObject, newObject) {
 | 
			
		||||
            Object.assign(oldObject, newObject);
 | 
			
		||||
 | 
			
		||||
            actionCollection._updateActions();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.objectPath.forEach(object => {
 | 
			
		||||
            if (object) {
 | 
			
		||||
                let unsubscribe = this.openmct.objects.observe(object, '*', updateObject.bind(this, object));
 | 
			
		||||
 | 
			
		||||
                this.objectUnsubscribes.push(unsubscribe);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _initializeActions() {
 | 
			
		||||
        Object.keys(this.applicableActions).forEach(key => {
 | 
			
		||||
            this.applicableActions[key].callBack = () => {
 | 
			
		||||
                return this.applicableActions[key].invoke(this.objectPath, this.view);
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _updateActions() {
 | 
			
		||||
        let newApplicableActions = this.openmct.actions._applicableActions(this.objectPath, this.view);
 | 
			
		||||
 | 
			
		||||
        this.applicableActions = this._mergeOldAndNewActions(this.applicableActions, newApplicableActions);
 | 
			
		||||
        this._initializeActions();
 | 
			
		||||
        this._update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _mergeOldAndNewActions(oldActions, newActions) {
 | 
			
		||||
        let mergedActions = {};
 | 
			
		||||
        Object.keys(newActions).forEach(key => {
 | 
			
		||||
            if (oldActions[key]) {
 | 
			
		||||
                mergedActions[key] = oldActions[key];
 | 
			
		||||
            } else {
 | 
			
		||||
                mergedActions[key] = newActions[key];
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return mergedActions;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ActionCollection;
 | 
			
		||||
							
								
								
									
										145
									
								
								src/api/actions/ActionsAPI.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/api/actions/ActionsAPI.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import EventEmitter from 'EventEmitter';
 | 
			
		||||
import ActionCollection from './ActionCollection';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
class ActionsAPI extends EventEmitter {
 | 
			
		||||
    constructor(openmct) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        this._allActions = {};
 | 
			
		||||
        this._actionCollections = new WeakMap();
 | 
			
		||||
        this._openmct = openmct;
 | 
			
		||||
 | 
			
		||||
        this._groupOrder = ['windowing', 'undefined', 'view', 'action', 'json'];
 | 
			
		||||
 | 
			
		||||
        this.register = this.register.bind(this);
 | 
			
		||||
        this.get = this.get.bind(this);
 | 
			
		||||
        this._applicableActions = this._applicableActions.bind(this);
 | 
			
		||||
        this._updateCachedActionCollections = this._updateCachedActionCollections.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    register(actionDefinition) {
 | 
			
		||||
        this._allActions[actionDefinition.key] = actionDefinition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get(objectPath, view) {
 | 
			
		||||
        let viewContext = view && view.getViewContext && view.getViewContext() || {};
 | 
			
		||||
 | 
			
		||||
        if (view && !viewContext.skipCache) {
 | 
			
		||||
            let cachedActionCollection = this._actionCollections.get(view);
 | 
			
		||||
 | 
			
		||||
            if (cachedActionCollection) {
 | 
			
		||||
                return cachedActionCollection;
 | 
			
		||||
            } else {
 | 
			
		||||
                let applicableActions = this._applicableActions(objectPath, view);
 | 
			
		||||
                let actionCollection = new ActionCollection(applicableActions, objectPath, view, this._openmct);
 | 
			
		||||
 | 
			
		||||
                this._actionCollections.set(view, actionCollection);
 | 
			
		||||
                actionCollection.on('destroy', this._updateCachedActionCollections);
 | 
			
		||||
 | 
			
		||||
                return actionCollection;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            let applicableActions = this._applicableActions(objectPath, view);
 | 
			
		||||
 | 
			
		||||
            Object.keys(applicableActions).forEach(key => {
 | 
			
		||||
                let action = applicableActions[key];
 | 
			
		||||
 | 
			
		||||
                action.callBack = () => {
 | 
			
		||||
                    return action.invoke(objectPath, view);
 | 
			
		||||
                };
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return applicableActions;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateGroupOrder(groupArray) {
 | 
			
		||||
        this._groupOrder = groupArray;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _updateCachedActionCollections(key) {
 | 
			
		||||
        if (this._actionCollections.has(key)) {
 | 
			
		||||
            let actionCollection = this._actionCollections.get(key);
 | 
			
		||||
            actionCollection.off('destroy', this._updateCachedActionCollections);
 | 
			
		||||
 | 
			
		||||
            this._actionCollections.delete(key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _applicableActions(objectPath, view) {
 | 
			
		||||
        let actionsObject = {};
 | 
			
		||||
 | 
			
		||||
        let keys = Object.keys(this._allActions).filter(key => {
 | 
			
		||||
            let actionDefinition = this._allActions[key];
 | 
			
		||||
 | 
			
		||||
            if (actionDefinition.appliesTo === undefined) {
 | 
			
		||||
                return true;
 | 
			
		||||
            } else {
 | 
			
		||||
                return actionDefinition.appliesTo(objectPath, view);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        keys.forEach(key => {
 | 
			
		||||
            let action = _.clone(this._allActions[key]);
 | 
			
		||||
 | 
			
		||||
            actionsObject[key] = action;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return actionsObject;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _groupAndSortActions(actionsArray) {
 | 
			
		||||
        if (!Array.isArray(actionsArray) && typeof actionsArray === 'object') {
 | 
			
		||||
            actionsArray = Object.keys(actionsArray).map(key => actionsArray[key]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let actionsObject = {};
 | 
			
		||||
        let groupedSortedActionsArray = [];
 | 
			
		||||
 | 
			
		||||
        function sortDescending(a, b) {
 | 
			
		||||
            return b.priority - a.priority;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        actionsArray.forEach(action => {
 | 
			
		||||
            if (actionsObject[action.group] === undefined) {
 | 
			
		||||
                actionsObject[action.group] = [action];
 | 
			
		||||
            } else {
 | 
			
		||||
                actionsObject[action.group].push(action);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this._groupOrder.forEach(group => {
 | 
			
		||||
            let groupArray = actionsObject[group];
 | 
			
		||||
 | 
			
		||||
            if (groupArray) {
 | 
			
		||||
                groupedSortedActionsArray.push(groupArray.sort(sortDescending));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return groupedSortedActionsArray;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ActionsAPI;
 | 
			
		||||
@@ -28,8 +28,9 @@ define([
 | 
			
		||||
    './telemetry/TelemetryAPI',
 | 
			
		||||
    './indicators/IndicatorAPI',
 | 
			
		||||
    './notifications/NotificationAPI',
 | 
			
		||||
    './contextMenu/ContextMenuAPI',
 | 
			
		||||
    './Editor'
 | 
			
		||||
    './Editor',
 | 
			
		||||
    './menu/MenuAPI',
 | 
			
		||||
    './actions/ActionsAPI'
 | 
			
		||||
 | 
			
		||||
], function (
 | 
			
		||||
    TimeAPI,
 | 
			
		||||
@@ -39,8 +40,9 @@ define([
 | 
			
		||||
    TelemetryAPI,
 | 
			
		||||
    IndicatorAPI,
 | 
			
		||||
    NotificationAPI,
 | 
			
		||||
    ContextMenuAPI,
 | 
			
		||||
    EditorAPI
 | 
			
		||||
    EditorAPI,
 | 
			
		||||
    MenuAPI,
 | 
			
		||||
    ActionsAPI
 | 
			
		||||
) {
 | 
			
		||||
    return {
 | 
			
		||||
        TimeAPI: TimeAPI,
 | 
			
		||||
@@ -51,6 +53,7 @@ define([
 | 
			
		||||
        IndicatorAPI: IndicatorAPI,
 | 
			
		||||
        NotificationAPI: NotificationAPI.default,
 | 
			
		||||
        EditorAPI: EditorAPI,
 | 
			
		||||
        ContextMenuRegistry: ContextMenuAPI.default
 | 
			
		||||
        MenuAPI: MenuAPI.default,
 | 
			
		||||
        ActionsAPI: ActionsAPI.default
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-menu">
 | 
			
		||||
    <ul>
 | 
			
		||||
        <li
 | 
			
		||||
            v-for="action in actions"
 | 
			
		||||
            :key="action.name"
 | 
			
		||||
            :class="action.cssClass"
 | 
			
		||||
            :title="action.description"
 | 
			
		||||
            @click="action.invoke(objectPath)"
 | 
			
		||||
        >
 | 
			
		||||
            {{ action.name }}
 | 
			
		||||
        </li>
 | 
			
		||||
        <li v-if="actions.length === 0">
 | 
			
		||||
            No actions defined.
 | 
			
		||||
        </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['actions', 'objectPath']
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,159 +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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import ContextMenuComponent from './ContextMenu.vue';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The ContextMenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
 | 
			
		||||
 * custom HTML elements.
 | 
			
		||||
 * @interface ContextMenuAPI
 | 
			
		||||
 * @memberof module:openmct
 | 
			
		||||
 */
 | 
			
		||||
class ContextMenuAPI {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this._allActions = [];
 | 
			
		||||
        this._activeContextMenu = undefined;
 | 
			
		||||
 | 
			
		||||
        this._hideActiveContextMenu = this._hideActiveContextMenu.bind(this);
 | 
			
		||||
        this.registerAction = this.registerAction.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Defines an item to be added to context menus. Allows specification of text, appearance, and behavior when
 | 
			
		||||
     * selected. Applicabilioty can be restricted by specification of an `appliesTo` function.
 | 
			
		||||
     *
 | 
			
		||||
     * @interface ContextMenuAction
 | 
			
		||||
     * @memberof module:openmct
 | 
			
		||||
     * @property {string} name the human-readable name of this view
 | 
			
		||||
     * @property {string} description a longer-form description (typically
 | 
			
		||||
     *           a single sentence or short paragraph) of this kind of view
 | 
			
		||||
     * @property {string} cssClass the CSS class to apply to labels for this
 | 
			
		||||
     *           view (to add icons, for instance)
 | 
			
		||||
     * @property {string} key unique key to identify the context menu action
 | 
			
		||||
     *           (used in custom context menu eg table rows, to identify which actions to include)
 | 
			
		||||
     * @property {boolean} hideInDefaultMenu optional flag to hide action from showing in the default context menu (tree item)
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * @method appliesTo
 | 
			
		||||
     * @memberof module:openmct.ContextMenuAction#
 | 
			
		||||
     * @param {DomainObject[]} objectPath the path of the object that the context menu has been invoked on.
 | 
			
		||||
     * @returns {boolean} true if the action applies to the objects specified in the 'objectPath', otherwise false.
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * Code to be executed when the action is selected from a context menu
 | 
			
		||||
     * @method invoke
 | 
			
		||||
     * @memberof module:openmct.ContextMenuAction#
 | 
			
		||||
     * @param {DomainObject[]} objectPath the path of the object to invoke the action on.
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {ContextMenuAction} actionDefinition
 | 
			
		||||
     */
 | 
			
		||||
    registerAction(actionDefinition) {
 | 
			
		||||
        this._allActions.push(actionDefinition);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _showContextMenuForObjectPath(objectPath, x, y, actionsToBeIncluded) {
 | 
			
		||||
 | 
			
		||||
        let applicableActions = this._allActions.filter((action) => {
 | 
			
		||||
 | 
			
		||||
            if (actionsToBeIncluded) {
 | 
			
		||||
                if (action.appliesTo === undefined && actionsToBeIncluded.includes(action.key)) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return action.appliesTo(objectPath, actionsToBeIncluded) && actionsToBeIncluded.includes(action.key);
 | 
			
		||||
            } else {
 | 
			
		||||
                if (action.appliesTo === undefined) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return action.appliesTo(objectPath) && !action.hideInDefaultMenu;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (this._activeContextMenu) {
 | 
			
		||||
            this._hideActiveContextMenu();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._activeContextMenu = this._createContextMenuForObject(objectPath, applicableActions);
 | 
			
		||||
        this._activeContextMenu.$mount();
 | 
			
		||||
        document.body.appendChild(this._activeContextMenu.$el);
 | 
			
		||||
 | 
			
		||||
        let position = this._calculatePopupPosition(x, y, this._activeContextMenu.$el);
 | 
			
		||||
        this._activeContextMenu.$el.style.left = `${position.x}px`;
 | 
			
		||||
        this._activeContextMenu.$el.style.top = `${position.y}px`;
 | 
			
		||||
 | 
			
		||||
        document.addEventListener('click', this._hideActiveContextMenu);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _calculatePopupPosition(eventPosX, eventPosY, menuElement) {
 | 
			
		||||
        let menuDimensions = menuElement.getBoundingClientRect();
 | 
			
		||||
        let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth;
 | 
			
		||||
        let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight;
 | 
			
		||||
 | 
			
		||||
        if (overflowX > 0) {
 | 
			
		||||
            eventPosX = eventPosX - overflowX;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (overflowY > 0) {
 | 
			
		||||
            eventPosY = eventPosY - overflowY;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            x: eventPosX,
 | 
			
		||||
            y: eventPosY
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _hideActiveContextMenu() {
 | 
			
		||||
        document.removeEventListener('click', this._hideActiveContextMenu);
 | 
			
		||||
        document.body.removeChild(this._activeContextMenu.$el);
 | 
			
		||||
        this._activeContextMenu.$destroy();
 | 
			
		||||
        this._activeContextMenu = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _createContextMenuForObject(objectPath, actions) {
 | 
			
		||||
        return new Vue({
 | 
			
		||||
            components: {
 | 
			
		||||
                ContextMenu: ContextMenuComponent
 | 
			
		||||
            },
 | 
			
		||||
            provide: {
 | 
			
		||||
                actions: actions,
 | 
			
		||||
                objectPath: objectPath
 | 
			
		||||
            },
 | 
			
		||||
            template: '<ContextMenu></ContextMenu>'
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
export default ContextMenuAPI;
 | 
			
		||||
							
								
								
									
										67
									
								
								src/api/menu/MenuAPI.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/api/menu/MenuAPI.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import Menu from './menu.js';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The MenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
 | 
			
		||||
 * custom HTML elements.
 | 
			
		||||
 * @interface MenuAPI
 | 
			
		||||
 * @memberof module:openmct
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class MenuAPI {
 | 
			
		||||
    constructor(openmct) {
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
 | 
			
		||||
        this.showMenu = this.showMenu.bind(this);
 | 
			
		||||
        this._clearMenuComponent = this._clearMenuComponent.bind(this);
 | 
			
		||||
        this._showObjectMenu = this._showObjectMenu.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    showMenu(x, y, actions) {
 | 
			
		||||
        if (this.menuComponent) {
 | 
			
		||||
            this.menuComponent.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let options = {
 | 
			
		||||
            x,
 | 
			
		||||
            y,
 | 
			
		||||
            actions
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.menuComponent = new Menu(options);
 | 
			
		||||
        this.menuComponent.once('destroy', this._clearMenuComponent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _clearMenuComponent() {
 | 
			
		||||
        this.menuComponent = undefined;
 | 
			
		||||
        delete this.menuComponent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _showObjectMenu(objectPath, x, y, actionsToBeIncluded) {
 | 
			
		||||
        let applicableActions = this.openmct.actions._groupedAndSortedObjectActions(objectPath, actionsToBeIncluded);
 | 
			
		||||
 | 
			
		||||
        this.showMenu(x, y, applicableActions);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
export default MenuAPI;
 | 
			
		||||
							
								
								
									
										52
									
								
								src/api/menu/components/Menu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/api/menu/components/Menu.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-menu">
 | 
			
		||||
    <ul v-if="actions.length && actions[0].length">
 | 
			
		||||
        <template
 | 
			
		||||
            v-for="(actionGroups, index) in actions"
 | 
			
		||||
        >
 | 
			
		||||
            <li
 | 
			
		||||
                v-for="action in actionGroups"
 | 
			
		||||
                :key="action.name"
 | 
			
		||||
                :class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
 | 
			
		||||
                :title="action.description"
 | 
			
		||||
                @click="action.callBack"
 | 
			
		||||
            >
 | 
			
		||||
                {{ action.name }}
 | 
			
		||||
            </li>
 | 
			
		||||
            <div
 | 
			
		||||
                v-if="index !== actions.length - 1"
 | 
			
		||||
                :key="index"
 | 
			
		||||
                class="c-menu__section-separator"
 | 
			
		||||
            >
 | 
			
		||||
            </div>
 | 
			
		||||
            <li
 | 
			
		||||
                v-if="actionGroups.length === 0"
 | 
			
		||||
                :key="index"
 | 
			
		||||
            >
 | 
			
		||||
                No actions defined.
 | 
			
		||||
            </li>
 | 
			
		||||
        </template>
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    <ul v-else>
 | 
			
		||||
        <li
 | 
			
		||||
            v-for="action in actions"
 | 
			
		||||
            :key="action.name"
 | 
			
		||||
            :class="action.cssClass"
 | 
			
		||||
            :title="action.description"
 | 
			
		||||
            @click="action.callBack"
 | 
			
		||||
        >
 | 
			
		||||
            {{ action.name }}
 | 
			
		||||
        </li>
 | 
			
		||||
        <li v-if="actions.length === 0">
 | 
			
		||||
            No actions defined.
 | 
			
		||||
        </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['actions']
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										94
									
								
								src/api/menu/menu.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/api/menu/menu.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import EventEmitter from 'EventEmitter';
 | 
			
		||||
import MenuComponent from './components/Menu.vue';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
class Menu extends EventEmitter {
 | 
			
		||||
    constructor(options) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        this.options = options;
 | 
			
		||||
 | 
			
		||||
        this.component = new Vue({
 | 
			
		||||
            provide: {
 | 
			
		||||
                actions: options.actions
 | 
			
		||||
            },
 | 
			
		||||
            components: {
 | 
			
		||||
                MenuComponent
 | 
			
		||||
            },
 | 
			
		||||
            template: '<menu-component />'
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (options.onDestroy) {
 | 
			
		||||
            this.once('destroy', options.onDestroy);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.dismiss = this.dismiss.bind(this);
 | 
			
		||||
        this.show = this.show.bind(this);
 | 
			
		||||
 | 
			
		||||
        this.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dismiss() {
 | 
			
		||||
        this.emit('destroy');
 | 
			
		||||
        document.body.removeChild(this.component.$el);
 | 
			
		||||
        document.removeEventListener('click', this.dismiss);
 | 
			
		||||
        this.component.$destroy();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show() {
 | 
			
		||||
        this.component.$mount();
 | 
			
		||||
        document.body.appendChild(this.component.$el);
 | 
			
		||||
 | 
			
		||||
        let position = this._calculatePopupPosition(this.options.x, this.options.y, this.component.$el);
 | 
			
		||||
 | 
			
		||||
        this.component.$el.style.left = `${position.x}px`;
 | 
			
		||||
        this.component.$el.style.top = `${position.y}px`;
 | 
			
		||||
 | 
			
		||||
        document.addEventListener('click', this.dismiss);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _calculatePopupPosition(eventPosX, eventPosY, menuElement) {
 | 
			
		||||
        let menuDimensions = menuElement.getBoundingClientRect();
 | 
			
		||||
        let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth;
 | 
			
		||||
        let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight;
 | 
			
		||||
 | 
			
		||||
        if (overflowX > 0) {
 | 
			
		||||
            eventPosX = eventPosX - overflowX;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (overflowY > 0) {
 | 
			
		||||
            eventPosY = eventPosY - overflowY;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            x: eventPosX,
 | 
			
		||||
            y: eventPosY
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Menu;
 | 
			
		||||
@@ -22,6 +22,7 @@ class OverlayAPI {
 | 
			
		||||
                this.dismissLastOverlay();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -127,6 +128,7 @@ class OverlayAPI {
 | 
			
		||||
 | 
			
		||||
        return progressDialog;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default OverlayAPI;
 | 
			
		||||
 
 | 
			
		||||
@@ -176,7 +176,10 @@ export default {
 | 
			
		||||
            this.timestampKey = timeSystem.key;
 | 
			
		||||
        },
 | 
			
		||||
        showContextMenu(event) {
 | 
			
		||||
            this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
 | 
			
		||||
            let allActions = this.openmct.actions.get(this.currentObjectPath, {}, {viewHistoricalData: true});
 | 
			
		||||
            let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
 | 
			
		||||
 | 
			
		||||
            this.openmct.menus.showMenu(event.x, event.y, applicableActions);
 | 
			
		||||
        },
 | 
			
		||||
        resetValues() {
 | 
			
		||||
            this.value = '---';
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-lad-table-wrapper u-style-receiver js-style-receiver">
 | 
			
		||||
<div class="c-lad-table-wrapper">
 | 
			
		||||
    <table class="c-table c-lad-table">
 | 
			
		||||
        <thead>
 | 
			
		||||
            <tr>
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@
 | 
			
		||||
export default class ClearDataAction {
 | 
			
		||||
    constructor(openmct, appliesToObjects) {
 | 
			
		||||
        this.name = 'Clear Data for Object';
 | 
			
		||||
        this.key = 'clear-data-action';
 | 
			
		||||
        this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
 | 
			
		||||
        this.cssClass = 'icon-clear-data';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@ define([
 | 
			
		||||
                openmct.indicators.add(indicator);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
 | 
			
		||||
            openmct.actions.register(new ClearDataAction.default(openmct, appliesToObjects));
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -26,12 +26,12 @@ import ClearDataAction from '../clearDataAction.js';
 | 
			
		||||
describe('When the Clear Data Plugin is installed,', function () {
 | 
			
		||||
    const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
 | 
			
		||||
    const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
 | 
			
		||||
    const mockContextMenuProvider = jasmine.createSpyObj('contextMenu', ['registerAction']);
 | 
			
		||||
    const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
 | 
			
		||||
 | 
			
		||||
    const openmct = {
 | 
			
		||||
        objectViews: mockObjectViews,
 | 
			
		||||
        indicators: mockIndicatorProvider,
 | 
			
		||||
        contextMenu: mockContextMenuProvider,
 | 
			
		||||
        actions: mockActionsProvider,
 | 
			
		||||
        install: function (plugin) {
 | 
			
		||||
            plugin(this);
 | 
			
		||||
        }
 | 
			
		||||
@@ -51,7 +51,7 @@ describe('When the Clear Data Plugin is installed,', function () {
 | 
			
		||||
    it('Clear Data context menu action is installed', function () {
 | 
			
		||||
        openmct.install(ClearDataActionPlugin([]));
 | 
			
		||||
 | 
			
		||||
        expect(mockContextMenuProvider.registerAction).toHaveBeenCalled();
 | 
			
		||||
        expect(mockActionsProvider.register).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('clear data action emits a clearData event when invoked', function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,6 @@
 | 
			
		||||
.c-cs {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    flex: 1 1 auto;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,22 +21,21 @@
 | 
			
		||||
*****************************************************************************/
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-style has-local-controls c-toolbar">
 | 
			
		||||
    <div class="c-style__controls">
 | 
			
		||||
        <div :class="[
 | 
			
		||||
                 { 'is-style-invisible': styleItem.style && 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"
 | 
			
		||||
<div class="c-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': !hasProperty(styleItem.style.color) }"
 | 
			
		||||
        >
 | 
			
		||||
            <span class="c-style-thumb__text"
 | 
			
		||||
                  :class="{ 'hide-nice': !hasProperty(styleItem.style.color) }"
 | 
			
		||||
            >
 | 
			
		||||
                ABC
 | 
			
		||||
            </span>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
            ABC
 | 
			
		||||
        </span>
 | 
			
		||||
    </span>
 | 
			
		||||
    <span class="c-toolbar">
 | 
			
		||||
        <toolbar-color-picker v-if="hasProperty(styleItem.style.border)"
 | 
			
		||||
                              class="c-style__toolbar-button--border-color u-menu-to--center"
 | 
			
		||||
                              :options="borderColorOption"
 | 
			
		||||
@@ -62,14 +61,7 @@
 | 
			
		||||
                               :options="isStyleInvisibleOption"
 | 
			
		||||
                               @change="updateStyleValue"
 | 
			
		||||
        />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Save Styles -->
 | 
			
		||||
    <toolbar-button v-if="canSaveStyle"
 | 
			
		||||
                    class="c-style__toolbar-button--save c-local-controls--show-on-hover c-icon-button c-icon-button--major"
 | 
			
		||||
                    :options="saveOptions"
 | 
			
		||||
                    @click="saveItemStyle()"
 | 
			
		||||
    />
 | 
			
		||||
    </span>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -88,11 +80,12 @@ export default {
 | 
			
		||||
        ToolbarColorPicker,
 | 
			
		||||
        ToolbarToggleButton
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    inject: [
 | 
			
		||||
        'openmct'
 | 
			
		||||
    ],
 | 
			
		||||
    props: {
 | 
			
		||||
        isEditing: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            required: true
 | 
			
		||||
            type: Boolean
 | 
			
		||||
        },
 | 
			
		||||
        mixedStyles: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
@@ -100,10 +93,6 @@ export default {
 | 
			
		||||
                return [];
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        nonSpecificFontProperties: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
            required: true
 | 
			
		||||
        },
 | 
			
		||||
        styleItem: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            required: true
 | 
			
		||||
@@ -193,16 +182,7 @@ export default {
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        saveOptions() {
 | 
			
		||||
            return {
 | 
			
		||||
                icon: 'icon-save',
 | 
			
		||||
                title: 'Save style',
 | 
			
		||||
                isEditing: this.isEditing
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        canSaveStyle() {
 | 
			
		||||
            return this.isEditing && !this.mixedStyles.length && !this.nonSpecificFontProperties.length;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
@@ -236,9 +216,6 @@ export default {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.$emit('persist', this.styleItem, item.property);
 | 
			
		||||
        },
 | 
			
		||||
        saveItemStyle() {
 | 
			
		||||
            this.$emit('save-style', this.itemStyle);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -31,11 +31,6 @@
 | 
			
		||||
        <div class="c-inspect-styles__header">
 | 
			
		||||
            Object Style
 | 
			
		||||
        </div>
 | 
			
		||||
        <FontStyleEditor
 | 
			
		||||
            v-if="canStyleFont"
 | 
			
		||||
            :font-style="consolidatedFontStyle"
 | 
			
		||||
            @set-font-property="setFontProperty"
 | 
			
		||||
        />
 | 
			
		||||
        <div class="c-inspect-styles__content">
 | 
			
		||||
            <div v-if="staticStyle"
 | 
			
		||||
                 class="c-inspect-styles__style"
 | 
			
		||||
@@ -44,9 +39,7 @@
 | 
			
		||||
                              :style-item="staticStyle"
 | 
			
		||||
                              :is-editing="allowEditing"
 | 
			
		||||
                              :mixed-styles="mixedStyles"
 | 
			
		||||
                              :non-specific-font-properties="nonSpecificFontProperties"
 | 
			
		||||
                              @persist="updateStaticStyle"
 | 
			
		||||
                              @save-style="saveStyle"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
            <button
 | 
			
		||||
@@ -65,11 +58,10 @@
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-inspect-styles__content c-inspect-styles__condition-set">
 | 
			
		||||
            <a v-if="conditionSetDomainObject"
 | 
			
		||||
               class="c-object-label"
 | 
			
		||||
               class="c-object-label icon-conditional"
 | 
			
		||||
               :href="navigateToPath"
 | 
			
		||||
               @click="navigateOrPreview"
 | 
			
		||||
            >
 | 
			
		||||
                <span class="c-object-label__type-icon icon-conditional"></span>
 | 
			
		||||
                <span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
 | 
			
		||||
            </a>
 | 
			
		||||
            <template v-if="allowEditing">
 | 
			
		||||
@@ -88,12 +80,6 @@
 | 
			
		||||
            </template>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <FontStyleEditor
 | 
			
		||||
            v-if="canStyleFont"
 | 
			
		||||
            :font-style="consolidatedFontStyle"
 | 
			
		||||
            @set-font-property="setFontProperty"
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <div v-if="conditionsLoaded"
 | 
			
		||||
             class="c-inspect-styles__conditions"
 | 
			
		||||
        >
 | 
			
		||||
@@ -111,10 +97,8 @@
 | 
			
		||||
                />
 | 
			
		||||
                <style-editor class="c-inspect-styles__editor"
 | 
			
		||||
                              :style-item="conditionStyle"
 | 
			
		||||
                              :non-specific-font-properties="nonSpecificFontProperties"
 | 
			
		||||
                              :is-editing="allowEditing"
 | 
			
		||||
                              @persist="updateConditionalStyle"
 | 
			
		||||
                              @save-style="saveStyle"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -124,7 +108,6 @@
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
import FontStyleEditor from '@/ui/inspector/styles/FontStyleEditor.vue';
 | 
			
		||||
import StyleEditor from "./StyleEditor.vue";
 | 
			
		||||
import PreviewAction from "@/ui/preview/PreviewAction.js";
 | 
			
		||||
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionSetIdentifierForItem } from "@/plugins/condition/utils/styleUtils";
 | 
			
		||||
@@ -133,30 +116,16 @@ import ConditionError from "@/plugins/condition/components/ConditionError.vue";
 | 
			
		||||
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
const NON_SPECIFIC = '??';
 | 
			
		||||
const NON_STYLEABLE_CONTAINER_TYPES = [
 | 
			
		||||
    'layout',
 | 
			
		||||
    'flexible-layout',
 | 
			
		||||
    'tabs'
 | 
			
		||||
];
 | 
			
		||||
const NON_STYLEABLE_LAYOUT_ITEM_TYPES = [
 | 
			
		||||
    'line-view',
 | 
			
		||||
    'box-view',
 | 
			
		||||
    'image-view'
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'StylesView',
 | 
			
		||||
    components: {
 | 
			
		||||
        FontStyleEditor,
 | 
			
		||||
        StyleEditor,
 | 
			
		||||
        ConditionError,
 | 
			
		||||
        ConditionDescription
 | 
			
		||||
    },
 | 
			
		||||
    inject: [
 | 
			
		||||
        'openmct',
 | 
			
		||||
        'selection',
 | 
			
		||||
        'stylesManager'
 | 
			
		||||
        'selection'
 | 
			
		||||
    ],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
@@ -170,80 +139,19 @@ export default {
 | 
			
		||||
            conditionsLoaded: false,
 | 
			
		||||
            navigateToPath: '',
 | 
			
		||||
            selectedConditionId: '',
 | 
			
		||||
            items: [],
 | 
			
		||||
            domainObject: undefined,
 | 
			
		||||
            consolidatedFontStyle: {}
 | 
			
		||||
            locked: false
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        locked() {
 | 
			
		||||
            return this.selection.some(selectionPath => {
 | 
			
		||||
                const self = selectionPath[0].context.item;
 | 
			
		||||
                const parent = selectionPath.length > 1 ? selectionPath[1].context.item : undefined;
 | 
			
		||||
 | 
			
		||||
                return (self && self.locked) || (parent && parent.locked);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        allowEditing() {
 | 
			
		||||
            return this.isEditing && !this.locked;
 | 
			
		||||
        },
 | 
			
		||||
        styleableFontItems() {
 | 
			
		||||
            return this.selection.filter(selectionPath => {
 | 
			
		||||
                const item = selectionPath[0].context.item;
 | 
			
		||||
                const itemType = item && item.type;
 | 
			
		||||
                const layoutItem = selectionPath[0].context.layoutItem;
 | 
			
		||||
                const layoutItemType = layoutItem && layoutItem.type;
 | 
			
		||||
 | 
			
		||||
                if (itemType && NON_STYLEABLE_CONTAINER_TYPES.includes(itemType)) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (layoutItemType && NON_STYLEABLE_LAYOUT_ITEM_TYPES.includes(layoutItemType)) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        computedconsolidatedFontStyle() {
 | 
			
		||||
            let consolidatedFontStyle;
 | 
			
		||||
            const styles = [];
 | 
			
		||||
 | 
			
		||||
            this.styleableFontItems.forEach(styleable => {
 | 
			
		||||
                const fontStyle = this.getFontStyle(styleable[0]);
 | 
			
		||||
 | 
			
		||||
                styles.push(fontStyle);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (styles.length) {
 | 
			
		||||
                const hasConsolidatedFontSize = styles.length && styles.every((fontStyle, i, arr) => fontStyle.fontSize === arr[0].fontSize);
 | 
			
		||||
                const hasConsolidatedFont = styles.length && styles.every((fontStyle, i, arr) => fontStyle.font === arr[0].font);
 | 
			
		||||
 | 
			
		||||
                consolidatedFontStyle = {
 | 
			
		||||
                    fontSize: hasConsolidatedFontSize ? styles[0].fontSize : NON_SPECIFIC,
 | 
			
		||||
                    font: hasConsolidatedFont ? styles[0].font : NON_SPECIFIC
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return consolidatedFontStyle;
 | 
			
		||||
        },
 | 
			
		||||
        nonSpecificFontProperties() {
 | 
			
		||||
            if (!this.consolidatedFontStyle) {
 | 
			
		||||
                return [];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Object.keys(this.consolidatedFontStyle).filter(property => this.consolidatedFontStyle[property] === NON_SPECIFIC);
 | 
			
		||||
        },
 | 
			
		||||
        canStyleFont() {
 | 
			
		||||
            return this.styleableFontItems.length && this.allowEditing;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
        this.removeListeners();
 | 
			
		||||
        this.openmct.editor.off('isEditing', this.setEditState);
 | 
			
		||||
        this.stylesManager.off('styleSelected', this.applyStyleToSelection);
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.items = [];
 | 
			
		||||
        this.previewAction = new PreviewAction(this.openmct);
 | 
			
		||||
        this.isMultipleSelection = this.selection.length > 1;
 | 
			
		||||
        this.getObjectsAndItemsFromSelection();
 | 
			
		||||
@@ -258,10 +166,7 @@ export default {
 | 
			
		||||
            this.initializeStaticStyle();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.setConsolidatedFontStyle();
 | 
			
		||||
 | 
			
		||||
        this.openmct.editor.on('isEditing', this.setEditState);
 | 
			
		||||
        this.stylesManager.on('styleSelected', this.applyStyleToSelection);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        getObjectStyles() {
 | 
			
		||||
@@ -273,10 +178,10 @@ export default {
 | 
			
		||||
                }
 | 
			
		||||
            } else if (this.items.length) {
 | 
			
		||||
                const itemId = this.items[0].id;
 | 
			
		||||
                if (this.domainObject && this.domainObject.configuration && this.domainObject.configuration.objectStyles && this.domainObject.configuration.objectStyles[itemId]) {
 | 
			
		||||
                if (this.domainObject.configuration && this.domainObject.configuration.objectStyles && this.domainObject.configuration.objectStyles[itemId]) {
 | 
			
		||||
                    objectStyles = this.domainObject.configuration.objectStyles[itemId];
 | 
			
		||||
                }
 | 
			
		||||
            } else if (this.domainObject && this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
 | 
			
		||||
            } else if (this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
 | 
			
		||||
                objectStyles = this.domainObject.configuration.objectStyles;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -314,18 +219,6 @@ export default {
 | 
			
		||||
        isItemType(type, item) {
 | 
			
		||||
            return item && (item.type === type);
 | 
			
		||||
        },
 | 
			
		||||
        canPersistObject(item) {
 | 
			
		||||
            // for now the only way to tell if an object can be persisted is if it is creatable.
 | 
			
		||||
            let creatable = false;
 | 
			
		||||
            if (item) {
 | 
			
		||||
                const type = this.openmct.types.get(item.type);
 | 
			
		||||
                if (type && type.definition) {
 | 
			
		||||
                    creatable = (type.definition.creatable === true);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return creatable;
 | 
			
		||||
        },
 | 
			
		||||
        hasConditionalStyle(domainObject, layoutItem) {
 | 
			
		||||
            const id = layoutItem ? layoutItem.id : undefined;
 | 
			
		||||
 | 
			
		||||
@@ -342,8 +235,13 @@ export default {
 | 
			
		||||
            this.selection.forEach((selectionItem) => {
 | 
			
		||||
                const item = selectionItem[0].context.item;
 | 
			
		||||
                const layoutItem = selectionItem[0].context.layoutItem;
 | 
			
		||||
                const layoutDomainObject = selectionItem[0].context.item;
 | 
			
		||||
                const isChildItem = selectionItem.length > 1;
 | 
			
		||||
 | 
			
		||||
                if (layoutDomainObject && layoutDomainObject.locked) {
 | 
			
		||||
                    this.locked = true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!isChildItem) {
 | 
			
		||||
                    domainObject = item;
 | 
			
		||||
                    itemStyle = getApplicableStylesForItem(item);
 | 
			
		||||
@@ -353,7 +251,7 @@ export default {
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.canHide = true;
 | 
			
		||||
                    domainObject = selectionItem[1].context.item;
 | 
			
		||||
                    if (item && !layoutItem || (this.isItemType('subobject-view', layoutItem) && this.canPersistObject(item))) {
 | 
			
		||||
                    if (item && !layoutItem || this.isItemType('subobject-view', layoutItem)) {
 | 
			
		||||
                        subObjects.push(item);
 | 
			
		||||
                        itemStyle = getApplicableStylesForItem(item);
 | 
			
		||||
                        if (this.hasConditionalStyle(item)) {
 | 
			
		||||
@@ -377,7 +275,7 @@ export default {
 | 
			
		||||
            const {styles, mixedStyles} = getConsolidatedStyleValues(itemInitialStyles);
 | 
			
		||||
            this.initialStyles = styles;
 | 
			
		||||
            this.mixedStyles = mixedStyles;
 | 
			
		||||
            // main layout
 | 
			
		||||
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
            this.removeListeners();
 | 
			
		||||
            if (this.domainObject) {
 | 
			
		||||
@@ -400,7 +298,6 @@ export default {
 | 
			
		||||
        isKeyItemId(key) {
 | 
			
		||||
            return (key !== 'styles')
 | 
			
		||||
                && (key !== 'staticStyle')
 | 
			
		||||
                && (key !== 'fontStyle')
 | 
			
		||||
                && (key !== 'defaultConditionId')
 | 
			
		||||
                && (key !== 'selectedConditionId')
 | 
			
		||||
                && (key !== 'conditionSetIdentifier');
 | 
			
		||||
@@ -740,124 +637,6 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        persist(domainObject, style) {
 | 
			
		||||
            this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
 | 
			
		||||
        },
 | 
			
		||||
        applyStyleToSelection(style) {
 | 
			
		||||
            if (!this.allowEditing) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.updateSelectionFontStyle(style);
 | 
			
		||||
            this.updateSelectionStyle(style);
 | 
			
		||||
        },
 | 
			
		||||
        updateSelectionFontStyle(style) {
 | 
			
		||||
            const fontSizeProperty = {
 | 
			
		||||
                fontSize: style.fontSize
 | 
			
		||||
            };
 | 
			
		||||
            const fontProperty = {
 | 
			
		||||
                font: style.font
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.setFontProperty(fontSizeProperty);
 | 
			
		||||
            this.setFontProperty(fontProperty);
 | 
			
		||||
        },
 | 
			
		||||
        updateSelectionStyle(style) {
 | 
			
		||||
            const foundStyle = this.findStyleByConditionId(this.selectedConditionId);
 | 
			
		||||
 | 
			
		||||
            if (foundStyle && !this.isStaticAndConditionalStyles) {
 | 
			
		||||
                Object.entries(style).forEach(([property, value]) => {
 | 
			
		||||
                    if (foundStyle.style[property] !== undefined && foundStyle.style[property] !== value) {
 | 
			
		||||
                        foundStyle.style[property] = value;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                this.getAndPersistStyles();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.removeConditionSet();
 | 
			
		||||
                Object.entries(style).forEach(([property, value]) => {
 | 
			
		||||
                    if (this.staticStyle.style[property] !== undefined && this.staticStyle.style[property] !== value) {
 | 
			
		||||
                        this.staticStyle.style[property] = value;
 | 
			
		||||
                        this.getAndPersistStyles(property);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        saveStyle(style) {
 | 
			
		||||
            const styleToSave = {
 | 
			
		||||
                ...style,
 | 
			
		||||
                ...this.consolidatedFontStyle
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.stylesManager.save(styleToSave);
 | 
			
		||||
        },
 | 
			
		||||
        setConsolidatedFontStyle() {
 | 
			
		||||
            const styles = [];
 | 
			
		||||
 | 
			
		||||
            this.styleableFontItems.forEach(styleable => {
 | 
			
		||||
                const fontStyle = this.getFontStyle(styleable[0]);
 | 
			
		||||
 | 
			
		||||
                styles.push(fontStyle);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (styles.length) {
 | 
			
		||||
                const hasConsolidatedFontSize = styles.length && styles.every((fontStyle, i, arr) => fontStyle.fontSize === arr[0].fontSize);
 | 
			
		||||
                const hasConsolidatedFont = styles.length && styles.every((fontStyle, i, arr) => fontStyle.font === arr[0].font);
 | 
			
		||||
 | 
			
		||||
                const fontSize = hasConsolidatedFontSize ? styles[0].fontSize : NON_SPECIFIC;
 | 
			
		||||
                const font = hasConsolidatedFont ? styles[0].font : NON_SPECIFIC;
 | 
			
		||||
 | 
			
		||||
                this.$set(this.consolidatedFontStyle, 'fontSize', fontSize);
 | 
			
		||||
                this.$set(this.consolidatedFontStyle, 'font', font);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        getFontStyle(selectionPath) {
 | 
			
		||||
            const item = selectionPath.context.item;
 | 
			
		||||
            const layoutItem = selectionPath.context.layoutItem;
 | 
			
		||||
            let fontStyle = item && item.configuration && item.configuration.fontStyle;
 | 
			
		||||
 | 
			
		||||
            // support for legacy where font styling in layouts only
 | 
			
		||||
            if (!fontStyle) {
 | 
			
		||||
                fontStyle = {
 | 
			
		||||
                    fontSize: layoutItem && layoutItem.fontSize || 'default',
 | 
			
		||||
                    font: layoutItem && layoutItem.font || 'default'
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return fontStyle;
 | 
			
		||||
        },
 | 
			
		||||
        setFontProperty(fontStyleObject) {
 | 
			
		||||
            let layoutDomainObject;
 | 
			
		||||
            const [property, value] = Object.entries(fontStyleObject)[0];
 | 
			
		||||
 | 
			
		||||
            this.styleableFontItems.forEach(styleable => {
 | 
			
		||||
                if (!this.isLayoutObject(styleable)) {
 | 
			
		||||
                    const fontStyle = this.getFontStyle(styleable[0]);
 | 
			
		||||
                    fontStyle[property] = value;
 | 
			
		||||
 | 
			
		||||
                    this.openmct.objects.mutate(styleable[0].context.item, 'configuration.fontStyle', fontStyle);
 | 
			
		||||
                } else {
 | 
			
		||||
                    // all layoutItems in this context will share same parent layout
 | 
			
		||||
                    if (!layoutDomainObject) {
 | 
			
		||||
                        layoutDomainObject = styleable[1].context.item;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // save layout item font style to parent layout configuration
 | 
			
		||||
                    const layoutItemIndex = styleable[0].context.index;
 | 
			
		||||
                    const layoutItemConfiguration = layoutDomainObject.configuration.items[layoutItemIndex];
 | 
			
		||||
 | 
			
		||||
                    layoutItemConfiguration[property] = value;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (layoutDomainObject) {
 | 
			
		||||
                this.openmct.objects.mutate(layoutDomainObject, 'configuration.items', layoutDomainObject.configuration.items);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // sync vue component on font update
 | 
			
		||||
            this.$set(this.consolidatedFontStyle, property, value);
 | 
			
		||||
        },
 | 
			
		||||
        isLayoutObject(selectionPath) {
 | 
			
		||||
            const layoutItemType = selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type;
 | 
			
		||||
 | 
			
		||||
            return layoutItemType && layoutItemType !== 'subobject-view';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -40,11 +40,9 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__condition-set {
 | 
			
		||||
        align-items: baseline;
 | 
			
		||||
        border-bottom: 1px solid $colorInteriorBorder;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: row;
 | 
			
		||||
        padding-bottom: $interiorMargin;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
 | 
			
		||||
        .c-object-label {
 | 
			
		||||
            flex: 1 1 auto;
 | 
			
		||||
@@ -55,10 +53,7 @@
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__style {
 | 
			
		||||
        padding-bottom: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__style,
 | 
			
		||||
    &__condition {
 | 
			
		||||
        padding: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -146,8 +146,6 @@ describe('the plugin', function () {
 | 
			
		||||
        let displayLayoutItem;
 | 
			
		||||
        let lineLayoutItem;
 | 
			
		||||
        let boxLayoutItem;
 | 
			
		||||
        let notCreatableObjectItem;
 | 
			
		||||
        let notCreatableObject;
 | 
			
		||||
        let selection;
 | 
			
		||||
        let component;
 | 
			
		||||
        let styleViewComponentObject;
 | 
			
		||||
@@ -266,19 +264,6 @@ describe('the plugin', function () {
 | 
			
		||||
                            "stroke": "#717171",
 | 
			
		||||
                            "type": "line-view",
 | 
			
		||||
                            "id": "57d49a28-7863-43bd-9593-6570758916f0"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "width": 32,
 | 
			
		||||
                            "height": 18,
 | 
			
		||||
                            "x": 36,
 | 
			
		||||
                            "y": 8,
 | 
			
		||||
                            "identifier": {
 | 
			
		||||
                                "key": "~TEST~image",
 | 
			
		||||
                                "namespace": "test-space"
 | 
			
		||||
                            },
 | 
			
		||||
                            "hasFrame": true,
 | 
			
		||||
                            "type": "subobject-view",
 | 
			
		||||
                            "id": "6d9fe81b-a3ce-4e59-b404-a4a0be1a5d85"
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    "layoutGrid": [
 | 
			
		||||
@@ -312,52 +297,6 @@ describe('the plugin', function () {
 | 
			
		||||
                "type": "box-view",
 | 
			
		||||
                "id": "89b88746-d325-487b-aec4-11b79afff9e8"
 | 
			
		||||
            };
 | 
			
		||||
            notCreatableObjectItem = {
 | 
			
		||||
                "width": 32,
 | 
			
		||||
                "height": 18,
 | 
			
		||||
                "x": 36,
 | 
			
		||||
                "y": 8,
 | 
			
		||||
                "identifier": {
 | 
			
		||||
                    "key": "~TEST~image",
 | 
			
		||||
                    "namespace": "test-space"
 | 
			
		||||
                },
 | 
			
		||||
                "hasFrame": true,
 | 
			
		||||
                "type": "subobject-view",
 | 
			
		||||
                "id": "6d9fe81b-a3ce-4e59-b404-a4a0be1a5d85"
 | 
			
		||||
            };
 | 
			
		||||
            notCreatableObject = {
 | 
			
		||||
                "identifier": {
 | 
			
		||||
                    "key": "~TEST~image",
 | 
			
		||||
                    "namespace": "test-space"
 | 
			
		||||
                },
 | 
			
		||||
                "name": "test~image",
 | 
			
		||||
                "location": "test-space:~TEST",
 | 
			
		||||
                "type": "test.image",
 | 
			
		||||
                "telemetry": {
 | 
			
		||||
                    "values": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "key": "value",
 | 
			
		||||
                            "name": "Value",
 | 
			
		||||
                            "hints": {
 | 
			
		||||
                                "image": 1,
 | 
			
		||||
                                "priority": 0
 | 
			
		||||
                            },
 | 
			
		||||
                            "format": "image",
 | 
			
		||||
                            "source": "value"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "key": "utc",
 | 
			
		||||
                            "source": "timestamp",
 | 
			
		||||
                            "name": "Timestamp",
 | 
			
		||||
                            "format": "iso",
 | 
			
		||||
                            "hints": {
 | 
			
		||||
                                "domain": 1,
 | 
			
		||||
                                "priority": 1
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            selection = [
 | 
			
		||||
                [{
 | 
			
		||||
                    context: {
 | 
			
		||||
@@ -377,19 +316,6 @@ describe('the plugin', function () {
 | 
			
		||||
                        "index": 0
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    context: {
 | 
			
		||||
                        item: displayLayoutItem,
 | 
			
		||||
                        "supportsMultiSelect": true
 | 
			
		||||
                    }
 | 
			
		||||
                }],
 | 
			
		||||
                [{
 | 
			
		||||
                    context: {
 | 
			
		||||
                        "item": notCreatableObject,
 | 
			
		||||
                        "layoutItem": notCreatableObjectItem,
 | 
			
		||||
                        "index": 2
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    context: {
 | 
			
		||||
                        item: displayLayoutItem,
 | 
			
		||||
@@ -418,7 +344,7 @@ describe('the plugin', function () {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('initializes the items in the view', () => {
 | 
			
		||||
            expect(styleViewComponentObject.items.length).toBe(3);
 | 
			
		||||
            expect(styleViewComponentObject.items.length).toBe(2);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('initializes conditional styles', () => {
 | 
			
		||||
@@ -437,7 +363,7 @@ describe('the plugin', function () {
 | 
			
		||||
 | 
			
		||||
            return Vue.nextTick().then(() => {
 | 
			
		||||
                expect(styleViewComponentObject.domainObject.configuration.objectStyles).toBeDefined();
 | 
			
		||||
                [boxLayoutItem, lineLayoutItem, notCreatableObjectItem].forEach((item) => {
 | 
			
		||||
                [boxLayoutItem, lineLayoutItem].forEach((item) => {
 | 
			
		||||
                    const itemStyles = styleViewComponentObject.domainObject.configuration.objectStyles[item.id].styles;
 | 
			
		||||
                    expect(itemStyles.length).toBe(2);
 | 
			
		||||
                    const foundStyle = itemStyles.find((style) => {
 | 
			
		||||
@@ -459,7 +385,7 @@ describe('the plugin', function () {
 | 
			
		||||
 | 
			
		||||
            return Vue.nextTick().then(() => {
 | 
			
		||||
                expect(styleViewComponentObject.domainObject.configuration.objectStyles).toBeDefined();
 | 
			
		||||
                [boxLayoutItem, lineLayoutItem, notCreatableObjectItem].forEach((item) => {
 | 
			
		||||
                [boxLayoutItem, lineLayoutItem].forEach((item) => {
 | 
			
		||||
                    const itemStyle = styleViewComponentObject.domainObject.configuration.objectStyles[item.id].staticStyle;
 | 
			
		||||
                    expect(itemStyle).toBeDefined();
 | 
			
		||||
                    const applicableStyles = getApplicableStylesForItem(styleViewComponentObject.domainObject, item);
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<component :is="urlDefined ? 'a' : 'span'"
 | 
			
		||||
           class="c-condition-widget u-style-receiver js-style-receiver"
 | 
			
		||||
           class="c-condition-widget"
 | 
			
		||||
           :href="urlDefined ? internalDomainObject.url : null"
 | 
			
		||||
>
 | 
			
		||||
    <div class="c-condition-widget__label">
 | 
			
		||||
 
 | 
			
		||||
@@ -64,9 +64,16 @@ define([
 | 
			
		||||
                            components: {
 | 
			
		||||
                                AlphanumericFormatView: AlphanumericFormatView.default
 | 
			
		||||
                            },
 | 
			
		||||
                            template: '<alphanumeric-format-view></alphanumeric-format-view>'
 | 
			
		||||
                            template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>'
 | 
			
		||||
                        });
 | 
			
		||||
                    },
 | 
			
		||||
                    getViewContext() {
 | 
			
		||||
                        if (component) {
 | 
			
		||||
                            return component.$refs.alphanumericFormatView.getViewContext();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            return {};
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    destroy: function () {
 | 
			
		||||
                        component.$destroy();
 | 
			
		||||
                        component = undefined;
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,7 @@ define(['lodash'], function (_) {
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                const VIEW_TYPES = {
 | 
			
		||||
                    'telemetry-view': {
 | 
			
		||||
                        value: 'telemetry-view',
 | 
			
		||||
@@ -95,6 +96,7 @@ define(['lodash'], function (_) {
 | 
			
		||||
                        class: 'icon-tabular-realtime'
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                const APPLICABLE_VIEWS = {
 | 
			
		||||
                    'telemetry-view': [
 | 
			
		||||
                        VIEW_TYPES['telemetry.plot.overlay'],
 | 
			
		||||
@@ -388,6 +390,29 @@ define(['lodash'], function (_) {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function getTextSizeMenu(selectedParent, selection) {
 | 
			
		||||
                    const TEXT_SIZE = [8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96, 128];
 | 
			
		||||
 | 
			
		||||
                    return {
 | 
			
		||||
                        control: "select-menu",
 | 
			
		||||
                        domainObject: selectedParent,
 | 
			
		||||
                        applicableSelectedItems: selection.filter(selectionPath => {
 | 
			
		||||
                            let type = selectionPath[0].context.layoutItem.type;
 | 
			
		||||
 | 
			
		||||
                            return type === 'text-view' || type === 'telemetry-view';
 | 
			
		||||
                        }),
 | 
			
		||||
                        property: function (selectionPath) {
 | 
			
		||||
                            return getPath(selectionPath) + ".size";
 | 
			
		||||
                        },
 | 
			
		||||
                        title: "Set text size",
 | 
			
		||||
                        options: TEXT_SIZE.map(size => {
 | 
			
		||||
                            return {
 | 
			
		||||
                                value: size + "px"
 | 
			
		||||
                            };
 | 
			
		||||
                        })
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function getTextButton(selectedParent, selection) {
 | 
			
		||||
                    return {
 | 
			
		||||
                        control: "button",
 | 
			
		||||
@@ -398,7 +423,7 @@ define(['lodash'], function (_) {
 | 
			
		||||
                        property: function (selectionPath) {
 | 
			
		||||
                            return getPath(selectionPath);
 | 
			
		||||
                        },
 | 
			
		||||
                        icon: "icon-pencil",
 | 
			
		||||
                        icon: "icon-font",
 | 
			
		||||
                        title: "Edit text properties",
 | 
			
		||||
                        dialog: DIALOG_FORM.text
 | 
			
		||||
                    };
 | 
			
		||||
@@ -598,33 +623,6 @@ define(['lodash'], function (_) {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function getToggleGridButton(selection, selectionPath) {
 | 
			
		||||
                    const ICON_GRID_SHOW = 'icon-grid-on';
 | 
			
		||||
                    const ICON_GRID_HIDE = 'icon-grid-off';
 | 
			
		||||
 | 
			
		||||
                    let displayLayoutContext;
 | 
			
		||||
 | 
			
		||||
                    if (selection.length === 1 && selectionPath === undefined) {
 | 
			
		||||
                        displayLayoutContext = selection[0][0].context;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        displayLayoutContext = selectionPath[1].context;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return {
 | 
			
		||||
                        control: "button",
 | 
			
		||||
                        domainObject: displayLayoutContext.item,
 | 
			
		||||
                        icon: ICON_GRID_SHOW,
 | 
			
		||||
                        method: function () {
 | 
			
		||||
                            displayLayoutContext.toggleGrid();
 | 
			
		||||
 | 
			
		||||
                            this.icon = this.icon === ICON_GRID_SHOW
 | 
			
		||||
                                ? ICON_GRID_HIDE
 | 
			
		||||
                                : ICON_GRID_SHOW;
 | 
			
		||||
                        },
 | 
			
		||||
                        secondary: true
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function getSeparator() {
 | 
			
		||||
                    return {
 | 
			
		||||
                        control: "separator"
 | 
			
		||||
@@ -639,9 +637,7 @@ define(['lodash'], function (_) {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (isMainLayoutSelected(selectedObjects[0])) {
 | 
			
		||||
                    return [
 | 
			
		||||
                        getToggleGridButton(selectedObjects),
 | 
			
		||||
                        getAddButton(selectedObjects)];
 | 
			
		||||
                    return [getAddButton(selectedObjects)];
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let toolbar = {
 | 
			
		||||
@@ -653,11 +649,11 @@ define(['lodash'], function (_) {
 | 
			
		||||
                    'display-mode': [],
 | 
			
		||||
                    'telemetry-value': [],
 | 
			
		||||
                    'style': [],
 | 
			
		||||
                    'text-style': [],
 | 
			
		||||
                    'position': [],
 | 
			
		||||
                    'duplicate': [],
 | 
			
		||||
                    'unit-toggle': [],
 | 
			
		||||
                    'remove': [],
 | 
			
		||||
                    'toggle-grid': []
 | 
			
		||||
                    'remove': []
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                selectedObjects.forEach(selectionPath => {
 | 
			
		||||
@@ -703,6 +699,12 @@ define(['lodash'], function (_) {
 | 
			
		||||
                            toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selectedObjects)];
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (toolbar['text-style'].length === 0) {
 | 
			
		||||
                            toolbar['text-style'] = [
 | 
			
		||||
                                getTextSizeMenu(selectedParent, selectedObjects)
 | 
			
		||||
                            ];
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (toolbar.position.length === 0) {
 | 
			
		||||
                            toolbar.position = [
 | 
			
		||||
                                getStackOrder(selectedParent, selectionPath),
 | 
			
		||||
@@ -728,6 +730,12 @@ define(['lodash'], function (_) {
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (layoutItem.type === 'text-view') {
 | 
			
		||||
                        if (toolbar['text-style'].length === 0) {
 | 
			
		||||
                            toolbar['text-style'] = [
 | 
			
		||||
                                getTextSizeMenu(selectedParent, selectedObjects)
 | 
			
		||||
                            ];
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (toolbar.position.length === 0) {
 | 
			
		||||
                            toolbar.position = [
 | 
			
		||||
                                getStackOrder(selectedParent, selectionPath),
 | 
			
		||||
@@ -792,10 +800,6 @@ define(['lodash'], function (_) {
 | 
			
		||||
                    if (toolbar.duplicate.length === 0) {
 | 
			
		||||
                        toolbar.duplicate = [getDuplicateButton(selectedParent, selectionPath, selectedObjects)];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (toolbar['toggle-grid'].length === 0) {
 | 
			
		||||
                        toolbar['toggle-grid'] = [getToggleGridButton(selectedObjects, selectionPath)];
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                let toolbarArray = Object.values(toolbar);
 | 
			
		||||
 
 | 
			
		||||
@@ -56,28 +56,6 @@ define(function () {
 | 
			
		||||
                        1
 | 
			
		||||
                    ],
 | 
			
		||||
                    required: true
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: "Horizontal size (px)",
 | 
			
		||||
                    control: "numberfield",
 | 
			
		||||
                    cssClass: "l-input-sm l-numeric",
 | 
			
		||||
                    property: [
 | 
			
		||||
                        "configuration",
 | 
			
		||||
                        "layoutDimensions",
 | 
			
		||||
                        0
 | 
			
		||||
                    ],
 | 
			
		||||
                    required: false
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: "Vertical size (px)",
 | 
			
		||||
                    control: "numberfield",
 | 
			
		||||
                    cssClass: "l-input-sm l-numeric",
 | 
			
		||||
                    property: [
 | 
			
		||||
                        "configuration",
 | 
			
		||||
                        "layoutDimensions",
 | 
			
		||||
                        1
 | 
			
		||||
                    ],
 | 
			
		||||
                    required: false
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								src/plugins/displayLayout/actions/CopyToClipboardAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/plugins/displayLayout/actions/CopyToClipboardAction.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
import clipboard from '@/utils/clipboard';
 | 
			
		||||
 | 
			
		||||
export default class CopyToClipboardAction {
 | 
			
		||||
    constructor(openmct) {
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
 | 
			
		||||
        this.cssClass = 'icon-duplicate';
 | 
			
		||||
        this.description = 'Copy to Clipboard action';
 | 
			
		||||
        this.group = "action";
 | 
			
		||||
        this.key = 'copyToClipboard';
 | 
			
		||||
        this.name = 'Copy to Clipboard';
 | 
			
		||||
        this.priority = 9;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    invoke(objectPath, viewContext) {
 | 
			
		||||
        const formattedValue = viewContext.formattedValueForCopy();
 | 
			
		||||
        clipboard.updateClipboard(formattedValue)
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                this.openmct.notifications.info(`Success : copied to clipboard '${formattedValue}'`);
 | 
			
		||||
            })
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
                this.openmct.notifications.error(`Failed : to copy to clipboard '${formattedValue}'`);
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    appliesTo(objectPath, viewContext) {
 | 
			
		||||
        if (viewContext && viewContext.getViewKey) {
 | 
			
		||||
            return viewContext.getViewKey().includes('alphanumeric-format');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -29,7 +29,7 @@
 | 
			
		||||
    @endMove="() => $emit('endMove')"
 | 
			
		||||
>
 | 
			
		||||
    <div
 | 
			
		||||
        class="c-box-view u-style-receiver js-style-receiver"
 | 
			
		||||
        class="c-box-view"
 | 
			
		||||
        :class="[styleClass]"
 | 
			
		||||
        :style="style"
 | 
			
		||||
    ></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div
 | 
			
		||||
    class="l-layout u-style-receiver js-style-receiver"
 | 
			
		||||
    class="l-layout"
 | 
			
		||||
    :class="{
 | 
			
		||||
        'is-multi-selected': selectedLayoutItems.length > 1,
 | 
			
		||||
        'allow-editing': isEditing
 | 
			
		||||
@@ -31,19 +31,21 @@
 | 
			
		||||
    @click.capture="bypassSelection"
 | 
			
		||||
    @drop="handleDrop"
 | 
			
		||||
>
 | 
			
		||||
    <display-layout-grid
 | 
			
		||||
        v-if="isEditing"
 | 
			
		||||
        :grid-size="gridSize"
 | 
			
		||||
        :show-grid="showGrid"
 | 
			
		||||
    />
 | 
			
		||||
    <!-- Background grid -->
 | 
			
		||||
    <div
 | 
			
		||||
        v-if="shouldDisplayLayoutDimensions"
 | 
			
		||||
        class="l-layout__dimensions"
 | 
			
		||||
        :style="layoutDimensionsStyle"
 | 
			
		||||
        v-if="isEditing"
 | 
			
		||||
        class="l-layout__grid-holder c-grid"
 | 
			
		||||
    >
 | 
			
		||||
        <div class="l-layout__dimensions-vals">
 | 
			
		||||
            {{ layoutDimensions[0] }},{{ layoutDimensions[1] }}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div
 | 
			
		||||
            v-if="gridSize[0] >= 3"
 | 
			
		||||
            class="c-grid__x l-grid l-grid-x"
 | 
			
		||||
            :style="[{ backgroundSize: gridSize[0] + 'px 100%' }]"
 | 
			
		||||
        ></div>
 | 
			
		||||
        <div
 | 
			
		||||
            v-if="gridSize[1] >= 3"
 | 
			
		||||
            class="c-grid__y l-grid l-grid-y"
 | 
			
		||||
            :style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"
 | 
			
		||||
        ></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <component
 | 
			
		||||
        :is="item.type"
 | 
			
		||||
@@ -79,7 +81,6 @@ import TextView from './TextView.vue';
 | 
			
		||||
import LineView from './LineView.vue';
 | 
			
		||||
import ImageView from './ImageView.vue';
 | 
			
		||||
import EditMarquee from './EditMarquee.vue';
 | 
			
		||||
import DisplayLayoutGrid from './DisplayLayoutGrid.vue';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
const TELEMETRY_IDENTIFIER_FUNCTIONS = {
 | 
			
		||||
@@ -126,7 +127,6 @@ const DUPLICATE_OFFSET = 3;
 | 
			
		||||
 | 
			
		||||
let components = ITEM_TYPE_VIEW_MAP;
 | 
			
		||||
components['edit-marquee'] = EditMarquee;
 | 
			
		||||
components['display-layout-grid'] = DisplayLayoutGrid;
 | 
			
		||||
 | 
			
		||||
function getItemDefinition(itemType, ...options) {
 | 
			
		||||
    let itemView = ITEM_TYPE_VIEW_MAP[itemType];
 | 
			
		||||
@@ -140,7 +140,6 @@ function getItemDefinition(itemType, ...options) {
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: components,
 | 
			
		||||
    inject: ['openmct', 'options', 'objectPath'],
 | 
			
		||||
    props: {
 | 
			
		||||
        domainObject: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -157,8 +156,7 @@ export default {
 | 
			
		||||
        return {
 | 
			
		||||
            internalDomainObject: domainObject,
 | 
			
		||||
            initSelectIndex: undefined,
 | 
			
		||||
            selection: [],
 | 
			
		||||
            showGrid: true
 | 
			
		||||
            selection: []
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -173,23 +171,6 @@ export default {
 | 
			
		||||
                return this.itemIsInCurrentSelection(item);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        layoutDimensions() {
 | 
			
		||||
            return this.internalDomainObject.configuration.layoutDimensions;
 | 
			
		||||
        },
 | 
			
		||||
        shouldDisplayLayoutDimensions() {
 | 
			
		||||
            return this.layoutDimensions
 | 
			
		||||
                && this.layoutDimensions[0] > 0
 | 
			
		||||
                && this.layoutDimensions[1] > 0;
 | 
			
		||||
        },
 | 
			
		||||
        layoutDimensionsStyle() {
 | 
			
		||||
            const width = `${this.layoutDimensions[0]}px`;
 | 
			
		||||
            const height = `${this.layoutDimensions[1]}px`;
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                width,
 | 
			
		||||
                height
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        showMarquee() {
 | 
			
		||||
            let selectionPath = this.selection[0];
 | 
			
		||||
            let singleSelectedLine = this.selection.length === 1
 | 
			
		||||
@@ -198,13 +179,7 @@ export default {
 | 
			
		||||
            return this.isEditing && selectionPath && selectionPath.length > 1 && !singleSelectedLine;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
        isEditing(value) {
 | 
			
		||||
            if (value) {
 | 
			
		||||
                this.showGrid = value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct', 'options', 'objectPath'],
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) {
 | 
			
		||||
            this.internalDomainObject = JSON.parse(JSON.stringify(obj));
 | 
			
		||||
@@ -823,9 +798,6 @@ export default {
 | 
			
		||||
 | 
			
		||||
            this.removeItem(selection);
 | 
			
		||||
            this.initSelectIndex = this.layoutItems.length - 1; //restore selection
 | 
			
		||||
        },
 | 
			
		||||
        toggleGrid() {
 | 
			
		||||
            this.showGrid = !this.showGrid;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div
 | 
			
		||||
    class="l-layout__grid-holder"
 | 
			
		||||
    :class="{ 'c-grid': showGrid }"
 | 
			
		||||
>
 | 
			
		||||
    <div
 | 
			
		||||
        v-if="gridSize[0] >= 3"
 | 
			
		||||
        class="c-grid__x l-grid l-grid-x"
 | 
			
		||||
        :style="[{ backgroundSize: gridSize[0] + 'px 100%' }]"
 | 
			
		||||
    ></div>
 | 
			
		||||
    <div
 | 
			
		||||
        v-if="gridSize[1] >= 3"
 | 
			
		||||
        class="c-grid__y l-grid l-grid-y"
 | 
			
		||||
        :style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"
 | 
			
		||||
    ></div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        gridSize: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
            required: true,
 | 
			
		||||
            validator: (arr) => arr && arr.length === 2
 | 
			
		||||
                && arr.every(el => typeof el === 'number')
 | 
			
		||||
        },
 | 
			
		||||
        showGrid: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            required: true
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
@@ -81,7 +81,6 @@ export default {
 | 
			
		||||
        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 + ')';
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,6 @@
 | 
			
		||||
        :object-path="currentObjectPath"
 | 
			
		||||
        :has-frame="item.hasFrame"
 | 
			
		||||
        :show-edit-view="false"
 | 
			
		||||
        :layout-font-size="item.fontSize"
 | 
			
		||||
        :layout-font="item.font"
 | 
			
		||||
    />
 | 
			
		||||
</layout-frame>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -75,8 +73,6 @@ export default {
 | 
			
		||||
            y: position[1],
 | 
			
		||||
            identifier: domainObject.identifier,
 | 
			
		||||
            hasFrame: hasFrameByDefault(domainObject.type),
 | 
			
		||||
            fontSize: 'default',
 | 
			
		||||
            font: 'default',
 | 
			
		||||
            viewKey
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
@@ -142,14 +138,18 @@ export default {
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
            this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                let childContext = this.$refs.objectFrame.getSelectionContext();
 | 
			
		||||
                childContext.item = domainObject;
 | 
			
		||||
                childContext.layoutItem = this.item;
 | 
			
		||||
                childContext.index = this.index;
 | 
			
		||||
                this.context = childContext;
 | 
			
		||||
                this.removeSelectable = this.openmct.selection.selectable(
 | 
			
		||||
                    this.$el, this.context, this.immediatelySelect || this.initSelect);
 | 
			
		||||
                delete this.immediatelySelect;
 | 
			
		||||
                let reference = this.$refs.objectFrame;
 | 
			
		||||
 | 
			
		||||
                if (reference) {
 | 
			
		||||
                    let childContext = this.$refs.objectFrame.getSelectionContext();
 | 
			
		||||
                    childContext.item = domainObject;
 | 
			
		||||
                    childContext.layoutItem = this.item;
 | 
			
		||||
                    childContext.index = this.index;
 | 
			
		||||
                    this.context = childContext;
 | 
			
		||||
                    this.removeSelectable = this.openmct.selection.selectable(
 | 
			
		||||
                        this.$el, this.context, this.immediatelySelect || this.initSelect);
 | 
			
		||||
                    delete this.immediatelySelect;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -30,15 +30,13 @@
 | 
			
		||||
>
 | 
			
		||||
    <div
 | 
			
		||||
        v-if="domainObject"
 | 
			
		||||
        class="u-style-receiver c-telemetry-view"
 | 
			
		||||
        class="c-telemetry-view"
 | 
			
		||||
        :class="{
 | 
			
		||||
            styleClass,
 | 
			
		||||
            'is-missing': domainObject.status === 'missing'
 | 
			
		||||
        }"
 | 
			
		||||
        :style="styleObject"
 | 
			
		||||
        :data-font-size="item.fontSize"
 | 
			
		||||
        :data-font="item.font"
 | 
			
		||||
        @contextmenu.prevent="showContextMenu"
 | 
			
		||||
        @contextmenu.prevent.stop="showContextMenu"
 | 
			
		||||
    >
 | 
			
		||||
        <div class="is-missing__indicator"
 | 
			
		||||
             title="This item is missing"
 | 
			
		||||
@@ -76,10 +74,11 @@
 | 
			
		||||
import LayoutFrame from './LayoutFrame.vue';
 | 
			
		||||
import printj from 'printj';
 | 
			
		||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
 | 
			
		||||
import { getDefaultNotebook } from '@/plugins/notebook/utils/notebook-storage.js';
 | 
			
		||||
 | 
			
		||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
 | 
			
		||||
const DEFAULT_POSITION = [1, 1];
 | 
			
		||||
const CONTEXT_MENU_ACTIONS = ['viewHistoricalData'];
 | 
			
		||||
const CONTEXT_MENU_ACTIONS = ['copyToClipboard', 'copyToNotebook', 'viewHistoricalData'];
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    makeDefinition(openmct, gridSize, domainObject, position) {
 | 
			
		||||
@@ -97,8 +96,7 @@ export default {
 | 
			
		||||
            stroke: "",
 | 
			
		||||
            fill: "",
 | 
			
		||||
            color: "",
 | 
			
		||||
            fontSize: 'default',
 | 
			
		||||
            font: 'default'
 | 
			
		||||
            size: "13px"
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct', 'objectPath'],
 | 
			
		||||
@@ -129,10 +127,11 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            currentObjectPath: undefined,
 | 
			
		||||
            datum: undefined,
 | 
			
		||||
            formats: undefined,
 | 
			
		||||
            domainObject: undefined,
 | 
			
		||||
            currentObjectPath: undefined
 | 
			
		||||
            formats: undefined,
 | 
			
		||||
            viewKey: `alphanumeric-format-${Math.random()}`
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -153,15 +152,10 @@ export default {
 | 
			
		||||
            return unit;
 | 
			
		||||
        },
 | 
			
		||||
        styleObject() {
 | 
			
		||||
            let size;
 | 
			
		||||
            //for legacy size support
 | 
			
		||||
            if (!this.item.fontSize) {
 | 
			
		||||
                size = this.item.size;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Object.assign({}, {
 | 
			
		||||
                size
 | 
			
		||||
                fontSize: this.item.size
 | 
			
		||||
            }, this.itemStyle);
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
        fieldName() {
 | 
			
		||||
            return this.valueMetadata && this.valueMetadata.name;
 | 
			
		||||
@@ -224,6 +218,18 @@ export default {
 | 
			
		||||
        this.openmct.time.off("bounds", this.refreshData);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        getViewContext() {
 | 
			
		||||
            return {
 | 
			
		||||
                getViewKey: () => this.viewKey,
 | 
			
		||||
                formattedValueForCopy: this.formattedValueForCopy
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        formattedValueForCopy() {
 | 
			
		||||
            const timeFormatterKey = this.openmct.time.timeSystem().key;
 | 
			
		||||
            const timeFormatter = this.formats[timeFormatterKey];
 | 
			
		||||
 | 
			
		||||
            return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue} ${this.unit}`;
 | 
			
		||||
        },
 | 
			
		||||
        requestHistoricalData() {
 | 
			
		||||
            let bounds = this.openmct.time.bounds();
 | 
			
		||||
            let options = {
 | 
			
		||||
@@ -261,6 +267,16 @@ export default {
 | 
			
		||||
                this.requestHistoricalData(this.domainObject);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        getView() {
 | 
			
		||||
            return {
 | 
			
		||||
                getViewContext() {
 | 
			
		||||
                    return {
 | 
			
		||||
                        viewHistoricalData: true,
 | 
			
		||||
                        skipCache: true
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        setObject(domainObject) {
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
            this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
 | 
			
		||||
@@ -284,12 +300,37 @@ export default {
 | 
			
		||||
            this.removeSelectable = this.openmct.selection.selectable(
 | 
			
		||||
                this.$el, this.context, this.immediatelySelect || this.initSelect);
 | 
			
		||||
            delete this.immediatelySelect;
 | 
			
		||||
 | 
			
		||||
            let allActions = this.openmct.actions.get(this.currentObjectPath, this.getView());
 | 
			
		||||
 | 
			
		||||
            this.applicableActions = CONTEXT_MENU_ACTIONS.map(actionKey => {
 | 
			
		||||
                return allActions[actionKey];
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        updateTelemetryFormat(format) {
 | 
			
		||||
            this.$emit('formatChanged', this.item, format);
 | 
			
		||||
        },
 | 
			
		||||
        showContextMenu(event) {
 | 
			
		||||
            this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
 | 
			
		||||
        async getContextMenuActions() {
 | 
			
		||||
            const defaultNotebook = getDefaultNotebook();
 | 
			
		||||
            const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
 | 
			
		||||
 | 
			
		||||
            const actionsObject = this.openmct.actions.get(this.currentObjectPath, this.getViewContext(), { viewHistoricalData: true }).applicableActions;
 | 
			
		||||
            let applicableActionKeys = Object.keys(actionsObject)
 | 
			
		||||
                .filter(key => {
 | 
			
		||||
                    const isCopyToNotebook = actionsObject[key].key === 'copyToNotebook';
 | 
			
		||||
                    if (defaultNotebook && isCopyToNotebook) {
 | 
			
		||||
                        const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
 | 
			
		||||
                        actionsObject[key].name = `Copy to Notebook ${defaultPath}`;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return CONTEXT_MENU_ACTIONS.includes(actionsObject[key].key);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            return applicableActionKeys.map(key => actionsObject[key]);
 | 
			
		||||
        },
 | 
			
		||||
        async showContextMenu(event) {
 | 
			
		||||
            const contextMenuActions = await this.getContextMenuActions();
 | 
			
		||||
            this.openmct.menus.showMenu(event.x, event.y, contextMenuActions);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -29,9 +29,7 @@
 | 
			
		||||
    @endMove="() => $emit('endMove')"
 | 
			
		||||
>
 | 
			
		||||
    <div
 | 
			
		||||
        class="c-text-view u-style-receiver js-style-receiver"
 | 
			
		||||
        :data-font-size="item.fontSize"
 | 
			
		||||
        :data-font="item.font"
 | 
			
		||||
        class="c-text-view"
 | 
			
		||||
        :class="[styleClass]"
 | 
			
		||||
        :style="style"
 | 
			
		||||
    >
 | 
			
		||||
@@ -49,14 +47,13 @@ export default {
 | 
			
		||||
        return {
 | 
			
		||||
            fill: '',
 | 
			
		||||
            stroke: '',
 | 
			
		||||
            size: '13px',
 | 
			
		||||
            color: '',
 | 
			
		||||
            x: 1,
 | 
			
		||||
            y: 1,
 | 
			
		||||
            width: 10,
 | 
			
		||||
            height: 5,
 | 
			
		||||
            text: element.text,
 | 
			
		||||
            fontSize: 'default',
 | 
			
		||||
            font: 'default'
 | 
			
		||||
            text: element.text
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
@@ -87,14 +84,8 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        style() {
 | 
			
		||||
            let size;
 | 
			
		||||
            //legacy size support
 | 
			
		||||
            if (!this.item.fontSize) {
 | 
			
		||||
                size = this.item.size;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Object.assign({
 | 
			
		||||
                size
 | 
			
		||||
                fontSize: this.item.size
 | 
			
		||||
            }, this.itemStyle);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -17,29 +17,10 @@
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
 | 
			
		||||
    &__grid-holder,
 | 
			
		||||
    &__dimensions {
 | 
			
		||||
    &__grid-holder {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__dimensions {
 | 
			
		||||
        $b: 1px dashed $editDimensionsColor;
 | 
			
		||||
        border-right: $b;
 | 
			
		||||
        border-bottom: $b;
 | 
			
		||||
        pointer-events: none;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
 | 
			
		||||
        &-vals {
 | 
			
		||||
            $p: 2px;
 | 
			
		||||
            color: $editDimensionsColor;
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            font-style: italic;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            bottom: $p; right: $p;
 | 
			
		||||
            opacity: 0.7;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__frame {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
    }
 | 
			
		||||
@@ -53,10 +34,6 @@
 | 
			
		||||
            > .l-layout {
 | 
			
		||||
                background: $editUIGridColorBg;
 | 
			
		||||
 | 
			
		||||
                > [class*="__dimensions"] {
 | 
			
		||||
                    display: block;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                > [class*="__grid-holder"] {
 | 
			
		||||
                    display: block;
 | 
			
		||||
                }
 | 
			
		||||
@@ -65,16 +42,12 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .l-layout__frame {
 | 
			
		||||
        &[s-selected]:not([multi-select="true"]),
 | 
			
		||||
        &[s-selected],
 | 
			
		||||
        &[s-selected-parent] {
 | 
			
		||||
            // Display grid and allow edit marquee to display in nested layouts when editing
 | 
			
		||||
            > * > * > .l-layout.allow-editing {
 | 
			
		||||
            > * > * > .l-layout + .allow-editing {
 | 
			
		||||
                box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
 | 
			
		||||
 | 
			
		||||
                > [class*="__dimensions"] {
 | 
			
		||||
                    display: block;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                > [class*='grid-holder'] {
 | 
			
		||||
                    display: block;
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ export default {
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            objectStyle: undefined,
 | 
			
		||||
            itemStyle: undefined,
 | 
			
		||||
            styleClass: ''
 | 
			
		||||
        };
 | 
			
		||||
 
 | 
			
		||||
@@ -26,9 +26,12 @@ import objectUtils from 'objectUtils';
 | 
			
		||||
import DisplayLayoutType from './DisplayLayoutType.js';
 | 
			
		||||
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
 | 
			
		||||
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
 | 
			
		||||
import CopyToClipboardAction from './actions/CopyToClipboardAction';
 | 
			
		||||
 | 
			
		||||
export default function DisplayLayoutPlugin(options) {
 | 
			
		||||
    return function (openmct) {
 | 
			
		||||
        openmct.actions.register(new CopyToClipboardAction(openmct));
 | 
			
		||||
 | 
			
		||||
        openmct.objectViews.addProvider({
 | 
			
		||||
            key: 'layout.view',
 | 
			
		||||
            canView: function (domainObject) {
 | 
			
		||||
@@ -72,8 +75,7 @@ export default function DisplayLayoutPlugin(options) {
 | 
			
		||||
                            duplicateItem: component && component.$refs.displayLayout.duplicateItem,
 | 
			
		||||
                            switchViewType: component && component.$refs.displayLayout.switchViewType,
 | 
			
		||||
                            mergeMultipleTelemetryViews: component && component.$refs.displayLayout.mergeMultipleTelemetryViews,
 | 
			
		||||
                            mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots,
 | 
			
		||||
                            toggleGrid: component && component.$refs.displayLayout.toggleGrid
 | 
			
		||||
                            mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots
 | 
			
		||||
                        };
 | 
			
		||||
                    },
 | 
			
		||||
                    onEditModeChange: function (isEditing) {
 | 
			
		||||
 
 | 
			
		||||
@@ -340,7 +340,6 @@ describe('the plugin', function () {
 | 
			
		||||
 | 
			
		||||
        it('provides controls including separators', () => {
 | 
			
		||||
            const displayLayoutToolbar = openmct.toolbars.get(selection);
 | 
			
		||||
 | 
			
		||||
            expect(displayLayoutToolbar.length).toBe(9);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -3,26 +3,20 @@
 | 
			
		||||
    @include userSelectNone();
 | 
			
		||||
    background: $colorFilterBg;
 | 
			
		||||
    color: $colorFilterFg;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
    margin-top: $interiorMarginSm;
 | 
			
		||||
    padding: 2px;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
 | 
			
		||||
    &:before {
 | 
			
		||||
        font-family: symbolsfont-12px;
 | 
			
		||||
        content: $glyph-icon-filter;
 | 
			
		||||
        display: block;
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
        margin-right: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &--mixed {
 | 
			
		||||
        .c-filter-indication__mixed {
 | 
			
		||||
            font-style: italic;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__label {
 | 
			
		||||
        + .c-filter-indication__label {
 | 
			
		||||
            &:before {
 | 
			
		||||
                content: ', ';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-filter-tree-item {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,14 +12,14 @@
 | 
			
		||||
            :href="objectLink"
 | 
			
		||||
        >
 | 
			
		||||
            <div
 | 
			
		||||
                class="c-object-label__type-icon c-list-item__type-icon"
 | 
			
		||||
                class="c-object-label__type-icon c-list-item__name__type-icon"
 | 
			
		||||
                :class="item.type.cssClass"
 | 
			
		||||
            >
 | 
			
		||||
                <span class="is-missing__indicator"
 | 
			
		||||
                      title="This item is missing"
 | 
			
		||||
                ></span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="c-object-label__name c-list-item__name">{{ item.model.name }}</div>
 | 
			
		||||
            <div class="c-object-label__name c-list-item__name__name">{{ item.model.name }}</div>
 | 
			
		||||
        </a>
 | 
			
		||||
    </td>
 | 
			
		||||
    <td class="c-list-item__type">
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,6 @@
 | 
			
		||||
 | 
			
		||||
    body.desktop & {
 | 
			
		||||
        flex-flow: row wrap;
 | 
			
		||||
        align-content: flex-start;
 | 
			
		||||
 | 
			
		||||
        &__item {
 | 
			
		||||
            height: $gridItemDesk;
 | 
			
		||||
            width: $gridItemDesk;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,19 @@
 | 
			
		||||
/******************************* LIST ITEM */
 | 
			
		||||
.c-list-item {
 | 
			
		||||
    &__type-icon {
 | 
			
		||||
    &__name__type-icon {
 | 
			
		||||
        color: $colorItemTreeIcon;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__name {
 | 
			
		||||
    &__name__name {
 | 
			
		||||
        @include ellipsize();
 | 
			
		||||
 | 
			
		||||
        a & {
 | 
			
		||||
            color: $colorItemFg;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &:not(.c-list-item__name) {
 | 
			
		||||
        color: $colorItemFgDetails;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-alias {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,9 +28,5 @@
 | 
			
		||||
        padding-top: $p;
 | 
			
		||||
        padding-bottom: $p;
 | 
			
		||||
        width: 25%;
 | 
			
		||||
 | 
			
		||||
        &:not(.c-list-item__name) {
 | 
			
		||||
            color: $colorItemFgDetails;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,8 @@ export default class GoToOriginalAction {
 | 
			
		||||
        this.name = 'Go To Original';
 | 
			
		||||
        this.key = 'goToOriginal';
 | 
			
		||||
        this.description = 'Go to the original unlinked instance of this object';
 | 
			
		||||
        this.group = 'action';
 | 
			
		||||
        this.priority = 4;
 | 
			
		||||
 | 
			
		||||
        this._openmct = openmct;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,6 @@ import GoToOriginalAction from './goToOriginalAction';
 | 
			
		||||
 | 
			
		||||
export default function () {
 | 
			
		||||
    return function (openmct) {
 | 
			
		||||
        openmct.contextMenu.registerAction(new GoToOriginalAction(openmct));
 | 
			
		||||
        openmct.actions.register(new GoToOriginalAction(openmct));
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,67 +7,58 @@
 | 
			
		||||
    @mouseover="focusElement"
 | 
			
		||||
>
 | 
			
		||||
    <div class="c-imagery__main-image-wrapper has-local-controls">
 | 
			
		||||
        <div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover c-image-controls__controls">
 | 
			
		||||
            <span class="c-image-controls__sliders"
 | 
			
		||||
                  draggable="true"
 | 
			
		||||
                  @dragstart="startDrag"
 | 
			
		||||
            >
 | 
			
		||||
                <div class="c-image-controls__slider-wrapper icon-brightness">
 | 
			
		||||
                    <input v-model="filters.brightness"
 | 
			
		||||
                           type="range"
 | 
			
		||||
                           min="0"
 | 
			
		||||
                           max="500"
 | 
			
		||||
                    >
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="c-image-controls__slider-wrapper icon-contrast">
 | 
			
		||||
                    <input v-model="filters.contrast"
 | 
			
		||||
                           type="range"
 | 
			
		||||
                           min="0"
 | 
			
		||||
                           max="500"
 | 
			
		||||
                    >
 | 
			
		||||
                </div>
 | 
			
		||||
        <div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc">
 | 
			
		||||
            <span class="holder flex-elem grows c-imagery__lc__sliders">
 | 
			
		||||
                <input v-model="filters.brightness"
 | 
			
		||||
                       class="icon-brightness"
 | 
			
		||||
                       type="range"
 | 
			
		||||
                       min="0"
 | 
			
		||||
                       max="500"
 | 
			
		||||
                >
 | 
			
		||||
                <input v-model="filters.contrast"
 | 
			
		||||
                       class="icon-contrast"
 | 
			
		||||
                       type="range"
 | 
			
		||||
                       min="0"
 | 
			
		||||
                       max="500"
 | 
			
		||||
                >
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="t-reset-btn-holder c-imagery__lc__reset-btn c-image-controls__btn-reset">
 | 
			
		||||
            <span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
 | 
			
		||||
                <a class="s-icon-button icon-reset t-btn-reset"
 | 
			
		||||
                   @click="filters={brightness: 100, contrast: 100}"
 | 
			
		||||
                ></a>
 | 
			
		||||
            </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-imagery__main-image__bg"
 | 
			
		||||
        <div class="main-image s-image-main c-imagery__main-image has-local-controls"
 | 
			
		||||
             :class="{'paused unnsynced': isPaused,'stale':false }"
 | 
			
		||||
             :style="{'background-image': imageUrl ? `url(${imageUrl})` : 'none',
 | 
			
		||||
                      'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
 | 
			
		||||
             :data-openmct-image-timestamp="time"
 | 
			
		||||
             :data-openmct-object-keystring="keyString"
 | 
			
		||||
        >
 | 
			
		||||
            <div class="c-imagery__main-image__image"
 | 
			
		||||
                 :style="{
 | 
			
		||||
                     'background-image': imageUrl ? `url(${imageUrl})` : 'none',
 | 
			
		||||
                     'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
 | 
			
		||||
                 }"
 | 
			
		||||
                 :data-openmct-image-timestamp="time"
 | 
			
		||||
                 :data-openmct-object-keystring="keyString"
 | 
			
		||||
            ></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
 | 
			
		||||
            <button class="c-nav c-nav--prev"
 | 
			
		||||
                    title="Previous image"
 | 
			
		||||
                    :disabled="isPrevDisabled"
 | 
			
		||||
                    @click="prevImage()"
 | 
			
		||||
            ></button>
 | 
			
		||||
            <button class="c-nav c-nav--next"
 | 
			
		||||
                    title="Next image"
 | 
			
		||||
                    :disabled="isNextDisabled"
 | 
			
		||||
                    @click="nextImage()"
 | 
			
		||||
            ></button>
 | 
			
		||||
            <div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
 | 
			
		||||
                <button class="c-nav c-nav--prev"
 | 
			
		||||
                        title="Previous image"
 | 
			
		||||
                        :disabled="isPrevDisabled"
 | 
			
		||||
                        @click="prevImage()"
 | 
			
		||||
                ></button>
 | 
			
		||||
                <button class="c-nav c-nav--next"
 | 
			
		||||
                        title="Next image"
 | 
			
		||||
                        :disabled="isNextDisabled"
 | 
			
		||||
                        @click="nextImage()"
 | 
			
		||||
                ></button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="c-imagery__control-bar">
 | 
			
		||||
            <div class="c-imagery__time">
 | 
			
		||||
                <div class="c-imagery__timestamp u-style-receiver js-style-receiver">{{ time }}</div>
 | 
			
		||||
                <div class="c-imagery__timestamp">{{ time }}</div>
 | 
			
		||||
                <div
 | 
			
		||||
                    v-if="canTrackDuration"
 | 
			
		||||
                    :class="{'c-imagery--new': isImageNew && !refreshCSS}"
 | 
			
		||||
                    class="c-imagery__age icon-timer"
 | 
			
		||||
                >{{ formattedDuration }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="h-local-controls">
 | 
			
		||||
            <div class="h-local-controls flex-elem">
 | 
			
		||||
                <button
 | 
			
		||||
                    class="c-button icon-pause pause-play"
 | 
			
		||||
                    :class="{'is-paused': isPaused}"
 | 
			
		||||
@@ -455,10 +446,6 @@ export default {
 | 
			
		||||
                this.setFocusedImage(--index, THUMBNAIL_CLICKED);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        startDrag(e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
        },
 | 
			
		||||
        arrowDownHandler(event) {
 | 
			
		||||
            let key = event.keyCode;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
.c-imagery {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    flex: 1 1 auto;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
 | 
			
		||||
    &:focus {
 | 
			
		||||
        outline: none;
 | 
			
		||||
@@ -19,21 +19,13 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__main-image {
 | 
			
		||||
        &__bg {
 | 
			
		||||
            background-color: $colorPlotBg;
 | 
			
		||||
            border: 1px solid transparent;
 | 
			
		||||
            flex: 1 1 auto;
 | 
			
		||||
        background-position: center;
 | 
			
		||||
        background-repeat: no-repeat;
 | 
			
		||||
        background-size: contain;
 | 
			
		||||
        height: 100%;
 | 
			
		||||
 | 
			
		||||
            &.unnsynced{
 | 
			
		||||
                @include sUnsynced();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &__image {
 | 
			
		||||
            @include abs(); // Safari fix
 | 
			
		||||
            background-position: center;
 | 
			
		||||
            background-repeat: no-repeat;
 | 
			
		||||
            background-size: contain;
 | 
			
		||||
        &.unnsynced{
 | 
			
		||||
            @include sUnsynced();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -146,6 +138,11 @@
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.s-image-main {
 | 
			
		||||
    background-color: $colorPlotBg;
 | 
			
		||||
    border: 1px solid transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*************************************** IMAGERY LOCAL CONTROLS*/
 | 
			
		||||
.c-imagery {
 | 
			
		||||
    .h-local-controls--overlay-content {
 | 
			
		||||
@@ -155,7 +152,7 @@
 | 
			
		||||
        background: $colorLocalControlOvrBg;
 | 
			
		||||
        border-radius: $basicCr;
 | 
			
		||||
        max-width: 200px;
 | 
			
		||||
        min-width: 70px;
 | 
			
		||||
        min-width: 100px;
 | 
			
		||||
        width: 35%;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        padding: $interiorMargin $interiorMarginLg;
 | 
			
		||||
@@ -176,7 +173,6 @@
 | 
			
		||||
    &__lc {
 | 
			
		||||
        &__reset-btn {
 | 
			
		||||
            $bc: $scrollbarTrackColorBg;
 | 
			
		||||
 | 
			
		||||
            &:before,
 | 
			
		||||
            &:after {
 | 
			
		||||
                border-right: 1px solid $bc;
 | 
			
		||||
@@ -199,46 +195,6 @@
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-image-controls {
 | 
			
		||||
    // Brightness/contrast
 | 
			
		||||
 | 
			
		||||
    &__controls {
 | 
			
		||||
        // Sliders and reset element
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        margin-right: $interiorMargin; // Need some extra space due to proximity to close button
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__sliders {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
 | 
			
		||||
        > * + * {
 | 
			
		||||
            margin-top: 11px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__slider-wrapper {
 | 
			
		||||
        // A wrapper is needed to add the type icon to left of each range input
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
 | 
			
		||||
        &:before {
 | 
			
		||||
            color: rgba($colorMenuFg, 0.5);
 | 
			
		||||
            margin-right: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        input[type='range'] {
 | 
			
		||||
            width: 100px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__btn-reset {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*************************************** BUTTONS */
 | 
			
		||||
.c-button.pause-play {
 | 
			
		||||
    // Pause icon set by default in markup
 | 
			
		||||
@@ -255,13 +211,14 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-imagery__prev-next-buttons {
 | 
			
		||||
    //background: rgba(deeppink, 0.2);
 | 
			
		||||
    display: flex;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    transform: translateY(-75%);
 | 
			
		||||
    transform: translateY(-50%);
 | 
			
		||||
 | 
			
		||||
    .c-nav {
 | 
			
		||||
        pointer-events: all;
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,8 @@ export default class NewFolderAction {
 | 
			
		||||
        this.key = 'newFolder';
 | 
			
		||||
        this.description = 'Create a new folder';
 | 
			
		||||
        this.cssClass = 'icon-folder-new';
 | 
			
		||||
        this.group = "action";
 | 
			
		||||
        this.priority = 9;
 | 
			
		||||
 | 
			
		||||
        this._openmct = openmct;
 | 
			
		||||
        this._dialogForm = {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,6 @@ import NewFolderAction from './newFolderAction';
 | 
			
		||||
 | 
			
		||||
export default function () {
 | 
			
		||||
    return function (openmct) {
 | 
			
		||||
        openmct.contextMenu.registerAction(new NewFolderAction(openmct));
 | 
			
		||||
        openmct.actions.register(new NewFolderAction(openmct));
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,9 +40,7 @@ describe("the plugin", () => {
 | 
			
		||||
        openmct.on('start', done);
 | 
			
		||||
        openmct.startHeadless();
 | 
			
		||||
 | 
			
		||||
        newFolderAction = openmct.contextMenu._allActions.filter(action => {
 | 
			
		||||
            return action.key === 'newFolder';
 | 
			
		||||
        })[0];
 | 
			
		||||
        newFolderAction = openmct.actions._allActions.newFolder;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								src/plugins/notebook/actions/CopyToNotebookAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/plugins/notebook/actions/CopyToNotebookAction.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import { getDefaultNotebook } from '../utils/notebook-storage';
 | 
			
		||||
import { addNotebookEntry } from '../utils/notebook-entries';
 | 
			
		||||
 | 
			
		||||
export default class CopyToNotebookAction {
 | 
			
		||||
    constructor(openmct) {
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
 | 
			
		||||
        this.cssClass = 'icon-duplicate';
 | 
			
		||||
        this.description = 'Copy to Notebook action';
 | 
			
		||||
        this.group = "action";
 | 
			
		||||
        this.key = 'copyToNotebook';
 | 
			
		||||
        this.name = 'Copy to Notebook';
 | 
			
		||||
        this.priority = 9;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    copyToNotebook(entryText) {
 | 
			
		||||
        const notebookStorage = getDefaultNotebook();
 | 
			
		||||
        this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
 | 
			
		||||
            .then(domainObject => {
 | 
			
		||||
                addNotebookEntry(this.openmct, domainObject, notebookStorage, null, entryText);
 | 
			
		||||
 | 
			
		||||
                const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
 | 
			
		||||
                const msg = `Saved to Notebook ${defaultPath}`;
 | 
			
		||||
                this.openmct.notifications.info(msg);
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    invoke(objectPath, viewContext) {
 | 
			
		||||
        this.copyToNotebook(viewContext.formattedValueForCopy());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    appliesTo(objectPath, viewContext) {
 | 
			
		||||
        if (viewContext && viewContext.getViewKey) {
 | 
			
		||||
            return viewContext.getViewKey().includes('alphanumeric-format');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -112,8 +112,6 @@ import SearchResults from './SearchResults.vue';
 | 
			
		||||
import Sidebar from './Sidebar.vue';
 | 
			
		||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
 | 
			
		||||
import { DEFAULT_CLASS, addNotebookEntry, createNewEmbed, getNotebookEntries } from '../utils/notebook-entries';
 | 
			
		||||
import objectUtils from 'objectUtils';
 | 
			
		||||
 | 
			
		||||
import { throttle } from 'lodash';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
@@ -433,9 +431,7 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        async updateDefaultNotebook(notebookStorage) {
 | 
			
		||||
            const defaultNotebookObject = await this.getDefaultNotebookObject();
 | 
			
		||||
            if (!defaultNotebookObject) {
 | 
			
		||||
                setDefaultNotebook(this.openmct, notebookStorage);
 | 
			
		||||
            } else if (objectUtils.makeKeyString(defaultNotebookObject.identifier) !== objectUtils.makeKeyString(notebookStorage.notebookMeta.identifier)) {
 | 
			
		||||
            if (defaultNotebookObject.identifier.key !== notebookStorage.notebookMeta.identifier.key) {
 | 
			
		||||
                this.removeDefaultClass(defaultNotebookObject);
 | 
			
		||||
                setDefaultNotebook(this.openmct, notebookStorage);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -143,8 +143,7 @@ export default {
 | 
			
		||||
                this.openmct.notifications.alert(message);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const url = new URL(link);
 | 
			
		||||
            window.location.href = url.hash;
 | 
			
		||||
            window.location.href = link;
 | 
			
		||||
        },
 | 
			
		||||
        formatTime(unixTime, timeFormat) {
 | 
			
		||||
            return Moment.utc(unixTime).format(timeFormat);
 | 
			
		||||
 
 | 
			
		||||
@@ -12,11 +12,12 @@
 | 
			
		||||
        <div class="c-ne__content">
 | 
			
		||||
            <div :id="entry.id"
 | 
			
		||||
                 class="c-ne__text"
 | 
			
		||||
                 :class="{'c-ne__input' : !readOnly }"
 | 
			
		||||
                 :class="{'c-input-inline' : !readOnly }"
 | 
			
		||||
                 :contenteditable="!readOnly"
 | 
			
		||||
                 :style="!entry.text.length ? defaultEntryStyle : ''"
 | 
			
		||||
                 @blur="updateEntryValue($event, entry.id)"
 | 
			
		||||
                 @focus="updateCurrentEntryValue($event, entry.id)"
 | 
			
		||||
            >{{ entry.text }}</div>
 | 
			
		||||
            >{{ entry.text.length ? entry.text : defaultText }}</div>
 | 
			
		||||
            <div class="c-snapshots c-ne__embeds">
 | 
			
		||||
                <NotebookEmbed v-for="embed in entry.embeds"
 | 
			
		||||
                               :key="embed.id"
 | 
			
		||||
@@ -105,7 +106,12 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            currentEntryValue: ''
 | 
			
		||||
            currentEntryValue: '',
 | 
			
		||||
            defaultEntryStyle: {
 | 
			
		||||
                fontStyle: 'italic',
 | 
			
		||||
                color: '#6e6e6e'
 | 
			
		||||
            },
 | 
			
		||||
            defaultText: 'add description'
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -229,13 +235,24 @@ export default {
 | 
			
		||||
            this.entry.embeds.splice(embedPosition, 1);
 | 
			
		||||
            this.updateEntry(this.entry);
 | 
			
		||||
        },
 | 
			
		||||
        selectTextInsideElement(element) {
 | 
			
		||||
            const range = document.createRange();
 | 
			
		||||
            range.selectNodeContents(element);
 | 
			
		||||
            let selection = window.getSelection();
 | 
			
		||||
            selection.removeAllRanges();
 | 
			
		||||
            selection.addRange(range);
 | 
			
		||||
        },
 | 
			
		||||
        updateCurrentEntryValue($event) {
 | 
			
		||||
            if (this.readOnly) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const target = $event.target;
 | 
			
		||||
            this.currentEntryValue = target ? target.textContent : '';
 | 
			
		||||
            this.currentEntryValue = target ? target.innerText : '';
 | 
			
		||||
 | 
			
		||||
            if (!this.entry.text.length) {
 | 
			
		||||
                this.selectTextInsideElement(target);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        updateEmbed(newEmbed) {
 | 
			
		||||
            this.entry.embeds.some(e => {
 | 
			
		||||
@@ -275,8 +292,6 @@ export default {
 | 
			
		||||
            const entryPos = this.entryPosById(entryId);
 | 
			
		||||
            const value = target.textContent.trim();
 | 
			
		||||
            if (this.currentEntryValue !== value) {
 | 
			
		||||
                target.textContent = value;
 | 
			
		||||
 | 
			
		||||
                const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
 | 
			
		||||
                entries[entryPos].text = value;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,17 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
 | 
			
		||||
    <button
 | 
			
		||||
        class="c-button--menu icon-notebook"
 | 
			
		||||
        class="c-icon-button c-button--menu icon-camera"
 | 
			
		||||
        title="Take a Notebook Snapshot"
 | 
			
		||||
        @click="setNotebookTypes"
 | 
			
		||||
        @click.stop="toggleMenu"
 | 
			
		||||
        @click.stop.prevent="showMenu"
 | 
			
		||||
    >
 | 
			
		||||
        <span class="c-button__label"></span>
 | 
			
		||||
        <span
 | 
			
		||||
            title="Take Notebook Snapshot"
 | 
			
		||||
            class="c-icon-button__label"
 | 
			
		||||
        >
 | 
			
		||||
            Snapshot
 | 
			
		||||
        </span>
 | 
			
		||||
    </button>
 | 
			
		||||
    <div
 | 
			
		||||
        v-show="showMenu"
 | 
			
		||||
        class="c-menu"
 | 
			
		||||
    >
 | 
			
		||||
        <ul>
 | 
			
		||||
            <li
 | 
			
		||||
                v-for="(type, index) in notebookTypes"
 | 
			
		||||
                :key="index"
 | 
			
		||||
                :class="type.cssClass"
 | 
			
		||||
                :title="type.name"
 | 
			
		||||
                @click="snapshot(type)"
 | 
			
		||||
            >
 | 
			
		||||
                {{ type.name }}
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -57,22 +45,19 @@ export default {
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            notebookSnapshot: null,
 | 
			
		||||
            notebookTypes: [],
 | 
			
		||||
            showMenu: false
 | 
			
		||||
            notebookTypes: []
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.notebookSnapshot = new Snapshot(this.openmct);
 | 
			
		||||
 | 
			
		||||
        document.addEventListener('click', this.hideMenu);
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
        document.removeEventListener('click', this.hideMenu);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        setNotebookTypes() {
 | 
			
		||||
        showMenu(event) {
 | 
			
		||||
            const notebookTypes = [];
 | 
			
		||||
            const defaultNotebook = getDefaultNotebook();
 | 
			
		||||
            const elementBoundingClientRect = this.$el.getBoundingClientRect();
 | 
			
		||||
            const x = elementBoundingClientRect.x;
 | 
			
		||||
            const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
 | 
			
		||||
 | 
			
		||||
            if (defaultNotebook) {
 | 
			
		||||
                const domainObject = defaultNotebook.domainObject;
 | 
			
		||||
@@ -83,35 +68,31 @@ export default {
 | 
			
		||||
                    notebookTypes.push({
 | 
			
		||||
                        cssClass: 'icon-notebook',
 | 
			
		||||
                        name: `Save to Notebook ${defaultPath}`,
 | 
			
		||||
                        type: NOTEBOOK_DEFAULT
 | 
			
		||||
                        callBack: () => {
 | 
			
		||||
                            return this.snapshot(NOTEBOOK_DEFAULT);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            notebookTypes.push({
 | 
			
		||||
                cssClass: 'icon-notebook',
 | 
			
		||||
                cssClass: 'icon-camera',
 | 
			
		||||
                name: 'Save to Notebook Snapshots',
 | 
			
		||||
                type: NOTEBOOK_SNAPSHOT
 | 
			
		||||
                callBack: () => {
 | 
			
		||||
                    return this.snapshot(NOTEBOOK_SNAPSHOT);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.notebookTypes = notebookTypes;
 | 
			
		||||
        },
 | 
			
		||||
        toggleMenu() {
 | 
			
		||||
            this.showMenu = !this.showMenu;
 | 
			
		||||
        },
 | 
			
		||||
        hideMenu() {
 | 
			
		||||
            this.showMenu = false;
 | 
			
		||||
            this.openmct.menus.showMenu(x, y, notebookTypes);
 | 
			
		||||
        },
 | 
			
		||||
        snapshot(notebook) {
 | 
			
		||||
            this.hideMenu();
 | 
			
		||||
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                const element = document.querySelector('.c-overlay__contents')
 | 
			
		||||
                    || document.getElementsByClassName('l-shell__main-container')[0];
 | 
			
		||||
 | 
			
		||||
                const bounds = this.openmct.time.bounds();
 | 
			
		||||
                const link = !this.ignoreLink
 | 
			
		||||
                    ? window.location.hash
 | 
			
		||||
                    ? window.location.href
 | 
			
		||||
                    : null;
 | 
			
		||||
 | 
			
		||||
                const objectPath = this.objectPath || this.openmct.router.path;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
        <div class="l-browse-bar__start">
 | 
			
		||||
            <div class="l-browse-bar__object-name--w">
 | 
			
		||||
                <div class="l-browse-bar__object-name c-object-label">
 | 
			
		||||
                    <div class="c-object-label__type-icon icon-notebook"></div>
 | 
			
		||||
                    <div class="c-object-label__type-icon icon-camera"></div>
 | 
			
		||||
                    <div class="c-object-label__name">
 | 
			
		||||
                        Notebook Snapshots
 | 
			
		||||
                        <span v-if="snapshots.length"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-indicator c-indicator--clickable icon-notebook"
 | 
			
		||||
<div class="c-indicator c-indicator--clickable icon-camera"
 | 
			
		||||
     :class="[
 | 
			
		||||
         { 's-status-off': snapshotCount === 0 },
 | 
			
		||||
         { 's-status-on': snapshotCount > 0 },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import CopyToNotebookAction from './actions/CopyToNotebookAction';
 | 
			
		||||
import Notebook from './components/Notebook.vue';
 | 
			
		||||
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
 | 
			
		||||
import SnapshotContainer from './snapshot-container';
 | 
			
		||||
@@ -13,6 +14,8 @@ export default function NotebookPlugin() {
 | 
			
		||||
 | 
			
		||||
        installed = true;
 | 
			
		||||
 | 
			
		||||
        openmct.actions.register(new CopyToNotebookAction(openmct));
 | 
			
		||||
 | 
			
		||||
        const notebookType = {
 | 
			
		||||
            name: 'Notebook',
 | 
			
		||||
            description: 'Create and save timestamped notes with embedded object snapshots.',
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import { createOpenMct, createMouseEvent, resetApplicationState } from 'utils/testing';
 | 
			
		||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
 | 
			
		||||
import NotebookPlugin from './plugin';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
@@ -133,90 +133,4 @@ describe("Notebook plugin:", () => {
 | 
			
		||||
            expect(hasMajorElements).toBe(true);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe("Notebook Snapshots view:", () => {
 | 
			
		||||
        let snapshotIndicator;
 | 
			
		||||
        let drawerElement;
 | 
			
		||||
 | 
			
		||||
        function clickSnapshotIndicator() {
 | 
			
		||||
            const indicator = element.querySelector('.icon-notebook');
 | 
			
		||||
            const button = indicator.querySelector('button');
 | 
			
		||||
            const clickEvent = createMouseEvent('click');
 | 
			
		||||
 | 
			
		||||
            button.dispatchEvent(clickEvent);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        beforeAll(() => {
 | 
			
		||||
            snapshotIndicator = openmct.indicators.indicatorObjects
 | 
			
		||||
                .find(indicator => indicator.key === 'notebook-snapshot-indicator').element;
 | 
			
		||||
 | 
			
		||||
            element.append(snapshotIndicator);
 | 
			
		||||
 | 
			
		||||
            return Vue.nextTick();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        afterAll(() => {
 | 
			
		||||
            snapshotIndicator.remove();
 | 
			
		||||
            if (drawerElement) {
 | 
			
		||||
                drawerElement.remove();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        beforeEach(() => {
 | 
			
		||||
            drawerElement = document.querySelector('.l-shell__drawer');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        afterEach(() => {
 | 
			
		||||
            if (drawerElement) {
 | 
			
		||||
                drawerElement.classList.remove('is-expanded');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("has Snapshots indicator", () => {
 | 
			
		||||
            const hasSnapshotIndicator = snapshotIndicator !== null && snapshotIndicator !== undefined;
 | 
			
		||||
            expect(hasSnapshotIndicator).toBe(true);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("snapshots container has class isExpanded", () => {
 | 
			
		||||
            let classes = drawerElement.classList;
 | 
			
		||||
            const isExpandedBefore = classes.contains('is-expanded');
 | 
			
		||||
 | 
			
		||||
            clickSnapshotIndicator();
 | 
			
		||||
            classes = drawerElement.classList;
 | 
			
		||||
            const isExpandedAfterFirstClick = classes.contains('is-expanded');
 | 
			
		||||
 | 
			
		||||
            const success = isExpandedBefore === false
 | 
			
		||||
                && isExpandedAfterFirstClick === true;
 | 
			
		||||
 | 
			
		||||
            expect(success).toBe(true);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("snapshots container does not have class isExpanded", () => {
 | 
			
		||||
            let classes = drawerElement.classList;
 | 
			
		||||
            const isExpandedBefore = classes.contains('is-expanded');
 | 
			
		||||
 | 
			
		||||
            clickSnapshotIndicator();
 | 
			
		||||
            classes = drawerElement.classList;
 | 
			
		||||
            const isExpandedAfterFirstClick = classes.contains('is-expanded');
 | 
			
		||||
 | 
			
		||||
            clickSnapshotIndicator();
 | 
			
		||||
            classes = drawerElement.classList;
 | 
			
		||||
            const isExpandedAfterSecondClick = classes.contains('is-expanded');
 | 
			
		||||
 | 
			
		||||
            const success = isExpandedBefore === false
 | 
			
		||||
                && isExpandedAfterFirstClick === true
 | 
			
		||||
                && isExpandedAfterSecondClick === false;
 | 
			
		||||
 | 
			
		||||
            expect(success).toBe(true);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("show notebook snapshots container text", () => {
 | 
			
		||||
            clickSnapshotIndicator();
 | 
			
		||||
 | 
			
		||||
            const notebookSnapshots = drawerElement.querySelector('.l-browse-bar__object-name');
 | 
			
		||||
            const snapshotsText = notebookSnapshots.textContent.trim();
 | 
			
		||||
 | 
			
		||||
            expect(snapshotsText).toBe('Notebook Snapshots');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ export default class Snapshot {
 | 
			
		||||
            .then(domainObject => {
 | 
			
		||||
                addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
 | 
			
		||||
 | 
			
		||||
                const defaultPath = `${domainObject.name} > ${notebookStorage.section.name} > ${notebookStorage.page.name}`;
 | 
			
		||||
                const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
 | 
			
		||||
                const msg = `Saved to Notebook ${defaultPath}`;
 | 
			
		||||
                this._showNotification(msg);
 | 
			
		||||
            });
 | 
			
		||||
 
 | 
			
		||||
@@ -103,7 +103,7 @@ export function createNewEmbed(snapshotMeta, snapshot = '') {
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null) {
 | 
			
		||||
export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null, entryText = '') {
 | 
			
		||||
    if (!openmct || !domainObject || !notebookStorage) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -125,7 +125,7 @@ export function addNotebookEntry(openmct, domainObject, notebookStorage, embed =
 | 
			
		||||
    defaultEntries.push({
 | 
			
		||||
        id,
 | 
			
		||||
        createdOn: date,
 | 
			
		||||
        text: '',
 | 
			
		||||
        text: entryText,
 | 
			
		||||
        embeds
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -86,10 +86,7 @@ export default class CouchObjectProvider {
 | 
			
		||||
                this.objectQueue[key] = new CouchObjectQueue(undefined, response[REV]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress
 | 
			
		||||
            if (!this.objectQueue[key].pending) {
 | 
			
		||||
                this.objectQueue[key].updateRevision(response[REV]);
 | 
			
		||||
            }
 | 
			
		||||
            this.objectQueue[key].updateRevision(response[REV]);
 | 
			
		||||
 | 
			
		||||
            return object;
 | 
			
		||||
        } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
 | 
			
		||||
import CouchObjectProvider from './CouchObjectProvider';
 | 
			
		||||
const NAMESPACE = '';
 | 
			
		||||
const PERSISTENCE_SPACE = 'mct';
 | 
			
		||||
const PERSISTENCE_SPACE = '';
 | 
			
		||||
 | 
			
		||||
export default function CouchPlugin(url) {
 | 
			
		||||
    return function install(openmct) {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,18 +31,19 @@ describe('the plugin', () => {
 | 
			
		||||
    let element;
 | 
			
		||||
    let child;
 | 
			
		||||
    let provider;
 | 
			
		||||
    let testSpace = 'testSpace';
 | 
			
		||||
    let testPath = '/test/db';
 | 
			
		||||
    let mockDomainObject;
 | 
			
		||||
 | 
			
		||||
    beforeEach((done) => {
 | 
			
		||||
        mockDomainObject = {
 | 
			
		||||
            identifier: {
 | 
			
		||||
                namespace: 'mct',
 | 
			
		||||
                namespace: '',
 | 
			
		||||
                key: 'some-value'
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        openmct = createOpenMct(false);
 | 
			
		||||
        openmct.install(new CouchPlugin(testPath));
 | 
			
		||||
        openmct.install(new CouchPlugin(testSpace, testPath));
 | 
			
		||||
 | 
			
		||||
        element = document.createElement('div');
 | 
			
		||||
        child = document.createElement('div');
 | 
			
		||||
 
 | 
			
		||||
@@ -188,19 +188,15 @@
 | 
			
		||||
                         ng-style="{
 | 
			
		||||
                             right: (100 * (max - tick.value) / interval) + '%',
 | 
			
		||||
                             height: '100%'
 | 
			
		||||
                         }"
 | 
			
		||||
                         ng-show="plot.gridLines"
 | 
			
		||||
                    >
 | 
			
		||||
                    </div>
 | 
			
		||||
                         }">
 | 
			
		||||
                     </div>
 | 
			
		||||
                </mct-ticks>
 | 
			
		||||
 | 
			
		||||
                <mct-ticks axis="yAxis">
 | 
			
		||||
                    <div class="gl-plot-hash hash-h"
 | 
			
		||||
                     <div class="gl-plot-hash hash-h"
 | 
			
		||||
                          ng-repeat="tick in ticks track by tick.value"
 | 
			
		||||
                          ng-style="{ bottom: (100 * (tick.value - min) / interval) + '%', width: '100%' }"
 | 
			
		||||
                          ng-show="plot.gridLines"
 | 
			
		||||
                    >
 | 
			
		||||
                    </div>
 | 
			
		||||
                          ng-style="{ bottom: (100 * (tick.value - min) / interval) + '%', width: '100%' }">
 | 
			
		||||
                     </div>
 | 
			
		||||
                </mct-ticks>
 | 
			
		||||
 | 
			
		||||
                <mct-chart config="config"
 | 
			
		||||
 
 | 
			
		||||
@@ -22,16 +22,16 @@
 | 
			
		||||
<div ng-controller="PlotController as controller"
 | 
			
		||||
    class="c-plot holder holder-plot has-control-bar">
 | 
			
		||||
    <div class="c-control-bar" ng-show="!controller.hideExportButtons">
 | 
			
		||||
        <span class="c-button-set c-button-set--strip-h">
 | 
			
		||||
         <span class="c-button-set c-button-set--strip-h">
 | 
			
		||||
            <button class="c-button icon-download"
 | 
			
		||||
                ng-click="controller.exportPNG()"
 | 
			
		||||
                title="Export This View's Data as PNG">
 | 
			
		||||
                <span class="c-button__label">PNG</span>
 | 
			
		||||
               ng-click="controller.exportPNG()"
 | 
			
		||||
               title="Export This View's Data as PNG">
 | 
			
		||||
                 <span class="c-button__label">PNG</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button class="c-button"
 | 
			
		||||
                ng-click="controller.exportJPG()"
 | 
			
		||||
                title="Export This View's Data as JPG">
 | 
			
		||||
                <span class="c-button__label">JPG</span>
 | 
			
		||||
               ng-click="controller.exportJPG()"
 | 
			
		||||
               title="Export This View's Data as JPG">
 | 
			
		||||
                 <span class="c-button__label">JPG</span>
 | 
			
		||||
            </button>
 | 
			
		||||
        </span>
 | 
			
		||||
        <button class="c-button icon-crosshair"
 | 
			
		||||
@@ -39,14 +39,9 @@
 | 
			
		||||
                ng-click="controller.toggleCursorGuide($event)"
 | 
			
		||||
                title="Toggle cursor guides">
 | 
			
		||||
        </button>
 | 
			
		||||
        <button class="c-button"
 | 
			
		||||
                ng-class="{ 'icon-grid-on': controller.gridLines, 'icon-grid-off': !controller.gridLines }"
 | 
			
		||||
                ng-click="controller.toggleGridLines($event)"
 | 
			
		||||
                title="Toggle grid lines">
 | 
			
		||||
        </button>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="l-view-section u-style-receiver js-style-receiver">
 | 
			
		||||
    <div class="l-view-section">
 | 
			
		||||
        <div class="c-loading--overlay loading"
 | 
			
		||||
            ng-show="!!pending"></div>
 | 
			
		||||
        <mct-plot config="controller.config"
 | 
			
		||||
 
 | 
			
		||||
@@ -22,30 +22,25 @@
 | 
			
		||||
<div ng-controller="StackedPlotController as stackedPlot"
 | 
			
		||||
      class="c-plot c-plot--stacked holder holder-plot has-control-bar">
 | 
			
		||||
    <div class="c-control-bar" ng-show="!stackedPlot.hideExportButtons">
 | 
			
		||||
        <span class="c-button-set c-button-set--strip-h">
 | 
			
		||||
            <button class="c-button icon-download"
 | 
			
		||||
                ng-click="stackedPlot.exportPNG()"
 | 
			
		||||
                title="Export This View's Data as PNG">
 | 
			
		||||
                <span class="c-button__label">PNG</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button class="c-button"
 | 
			
		||||
                ng-click="stackedPlot.exportJPG()"
 | 
			
		||||
                title="Export This View's Data as JPG">
 | 
			
		||||
                <span class="c-button__label">JPG</span>
 | 
			
		||||
            </button>
 | 
			
		||||
       <span class="c-button-set c-button-set--strip-h">
 | 
			
		||||
          <button class="c-button icon-download"
 | 
			
		||||
             ng-click="stackedPlot.exportPNG()"
 | 
			
		||||
             title="Export This View's Data as PNG">
 | 
			
		||||
              <span class="c-button__label">PNG</span>
 | 
			
		||||
          </button>
 | 
			
		||||
          <button class="c-button"
 | 
			
		||||
             ng-click="stackedPlot.exportJPG()"
 | 
			
		||||
             title="Export This View's Data as JPG">
 | 
			
		||||
              <span class="c-button__label">JPG</span>
 | 
			
		||||
          </button>
 | 
			
		||||
        </span>
 | 
			
		||||
        <button class="c-button icon-crosshair"
 | 
			
		||||
                ng-class="{ 'is-active': stackedPlot.cursorGuide }"
 | 
			
		||||
                ng-click="stackedPlot.toggleCursorGuide($event)"
 | 
			
		||||
                title="Toggle cursor guides">
 | 
			
		||||
        </button>
 | 
			
		||||
        <button class="c-button"
 | 
			
		||||
                ng-class="{ 'icon-grid-on': stackedPlot.gridLines, 'icon-grid-off': !stackedPlot.gridLines }"
 | 
			
		||||
                ng-click="stackedPlot.toggleGridLines($event)"
 | 
			
		||||
                title="Toggle grid lines">
 | 
			
		||||
        </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="l-view-section u-style-receiver js-style-receiver">
 | 
			
		||||
    <div class="l-view-section">
 | 
			
		||||
        <div class="c-loading--overlay loading"
 | 
			
		||||
             ng-show="!!currentRequest.pending"></div>
 | 
			
		||||
        <div class="gl-plot child-frame u-inspectable"
 | 
			
		||||
 
 | 
			
		||||
@@ -96,10 +96,7 @@ define([
 | 
			
		||||
        this.cursorGuideHorizontal = this.$element[0].querySelector('.js-cursor-guide--h');
 | 
			
		||||
        this.cursorGuide = false;
 | 
			
		||||
 | 
			
		||||
        this.gridLines = true;
 | 
			
		||||
 | 
			
		||||
        this.listenTo(this.$scope, 'cursorguide', this.toggleCursorGuide, this);
 | 
			
		||||
        this.listenTo(this.$scope, 'toggleGridLines', this.toggleGridLines, this);
 | 
			
		||||
 | 
			
		||||
        this.listenTo(this.$scope, '$destroy', this.destroy, this);
 | 
			
		||||
        this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this);
 | 
			
		||||
@@ -557,10 +554,6 @@ define([
 | 
			
		||||
        this.cursorGuide = !this.cursorGuide;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    MCTPlotController.prototype.toggleGridLines = function ($event) {
 | 
			
		||||
        this.gridLines = !this.gridLines;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    MCTPlotController.prototype.getXKeyOption = function (key) {
 | 
			
		||||
        return this.$scope.xKeyOptions.find(option => option.key === key);
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,6 @@ define([
 | 
			
		||||
        this.objectService = objectService;
 | 
			
		||||
        this.exportImageService = exportImageService;
 | 
			
		||||
        this.cursorGuide = false;
 | 
			
		||||
        this.gridLines = true;
 | 
			
		||||
 | 
			
		||||
        $scope.pending = 0;
 | 
			
		||||
 | 
			
		||||
@@ -332,11 +331,6 @@ define([
 | 
			
		||||
        this.$scope.$broadcast('cursorguide', $event);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    PlotController.prototype.toggleGridLines = function ($event) {
 | 
			
		||||
        this.gridLines = !this.gridLines;
 | 
			
		||||
        this.$scope.$broadcast('toggleGridLines', $event);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return PlotController;
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -160,10 +160,5 @@ define([], function () {
 | 
			
		||||
        this.$scope.$broadcast('cursorguide', $event);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    StackedPlotController.prototype.toggleGridLines = function ($event) {
 | 
			
		||||
        this.gridLines = !this.gridLines;
 | 
			
		||||
        this.$scope.$broadcast('toggleGridLines', $event);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return StackedPlotController;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,8 @@ define([
 | 
			
		||||
    './newFolderAction/plugin',
 | 
			
		||||
    './persistence/couch/plugin',
 | 
			
		||||
    './defaultRootName/plugin',
 | 
			
		||||
    './timeline/plugin'
 | 
			
		||||
    './timeline/plugin',
 | 
			
		||||
    './viewDatumAction/plugin'
 | 
			
		||||
], function (
 | 
			
		||||
    _,
 | 
			
		||||
    UTCTimeSystem,
 | 
			
		||||
@@ -97,7 +98,8 @@ define([
 | 
			
		||||
    NewFolderAction,
 | 
			
		||||
    CouchDBPlugin,
 | 
			
		||||
    DefaultRootName,
 | 
			
		||||
    Timeline
 | 
			
		||||
    Timeline,
 | 
			
		||||
    ViewDatumAction
 | 
			
		||||
) {
 | 
			
		||||
    const bundleMap = {
 | 
			
		||||
        LocalStorage: 'platform/persistence/local',
 | 
			
		||||
@@ -191,6 +193,7 @@ define([
 | 
			
		||||
    plugins.ISOTimeFormat = ISOTimeFormat.default;
 | 
			
		||||
    plugins.DefaultRootName = DefaultRootName.default;
 | 
			
		||||
    plugins.Timeline = Timeline.default;
 | 
			
		||||
    plugins.ViewDatumAction = ViewDatumAction.default;
 | 
			
		||||
 | 
			
		||||
    return plugins;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,8 @@ export default class RemoveAction {
 | 
			
		||||
        this.key = 'remove';
 | 
			
		||||
        this.description = 'Remove this object from its containing object.';
 | 
			
		||||
        this.cssClass = "icon-trash";
 | 
			
		||||
        this.group = "action";
 | 
			
		||||
        this.priority = 1;
 | 
			
		||||
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
    }
 | 
			
		||||
@@ -103,6 +105,16 @@ export default class RemoveAction {
 | 
			
		||||
        let parentType = parent && this.openmct.types.get(parent.type);
 | 
			
		||||
        let child = objectPath[0];
 | 
			
		||||
        let locked = child.locked ? child.locked : parent && parent.locked;
 | 
			
		||||
        let isEditing = this.openmct.editor.isEditing();
 | 
			
		||||
 | 
			
		||||
        if (isEditing) {
 | 
			
		||||
            let currentItemInView = this.openmct.router.path[0];
 | 
			
		||||
            let domainObject = objectPath[0];
 | 
			
		||||
 | 
			
		||||
            if (this.openmct.objects.areIdsEqual(currentItemInView.identifier, domainObject.identifier)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (locked) {
 | 
			
		||||
            return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,6 @@ import RemoveAction from "./RemoveAction";
 | 
			
		||||
 | 
			
		||||
export default function () {
 | 
			
		||||
    return function (openmct) {
 | 
			
		||||
        openmct.contextMenu.registerAction(new RemoveAction(openmct));
 | 
			
		||||
        openmct.actions.register(new RemoveAction(openmct));
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ define([
 | 
			
		||||
    'lodash',
 | 
			
		||||
    './collections/BoundedTableRowCollection',
 | 
			
		||||
    './collections/FilteredTableRowCollection',
 | 
			
		||||
    './TelemetryTableNameColumn',
 | 
			
		||||
    './TelemetryTableRow',
 | 
			
		||||
    './TelemetryTableColumn',
 | 
			
		||||
    './TelemetryTableUnitColumn',
 | 
			
		||||
@@ -35,7 +34,6 @@ define([
 | 
			
		||||
    _,
 | 
			
		||||
    BoundedTableRowCollection,
 | 
			
		||||
    FilteredTableRowCollection,
 | 
			
		||||
    TelemetryTableNameColumn,
 | 
			
		||||
    TelemetryTableRow,
 | 
			
		||||
    TelemetryTableColumn,
 | 
			
		||||
    TelemetryTableUnitColumn,
 | 
			
		||||
@@ -73,24 +71,6 @@ define([
 | 
			
		||||
            openmct.time.on('timeSystem', this.refreshData);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        addNameColumn(telemetryObject, metadataValues) {
 | 
			
		||||
            let metadatum = metadataValues.find(m => m.key === 'name');
 | 
			
		||||
            if (!metadatum) {
 | 
			
		||||
                metadatum = {
 | 
			
		||||
                    format: 'string',
 | 
			
		||||
                    key: 'name',
 | 
			
		||||
                    name: 'Name'
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const column = new TelemetryTableNameColumn(this.openmct, telemetryObject, metadatum);
 | 
			
		||||
 | 
			
		||||
            this.configuration.addSingleColumnForObject(telemetryObject, column);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        initialize() {
 | 
			
		||||
            if (this.domainObject.type === 'table') {
 | 
			
		||||
                this.filterObserver = this.openmct.objects.observe(this.domainObject, 'configuration.filters', this.updateFilters);
 | 
			
		||||
@@ -180,7 +160,6 @@ define([
 | 
			
		||||
        processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) {
 | 
			
		||||
            let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
 | 
			
		||||
            this.boundedRows.add(telemetryRows);
 | 
			
		||||
            this.emit('historical-rows-processed');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -232,13 +211,7 @@ define([
 | 
			
		||||
 | 
			
		||||
        addColumnsForObject(telemetryObject) {
 | 
			
		||||
            let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
 | 
			
		||||
 | 
			
		||||
            this.addNameColumn(telemetryObject, metadataValues);
 | 
			
		||||
            metadataValues.forEach(metadatum => {
 | 
			
		||||
                if (metadatum.key === 'name') {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let column = this.createColumn(metadatum);
 | 
			
		||||
                this.configuration.addSingleColumnForObject(telemetryObject, column);
 | 
			
		||||
                // add units column if available
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ define([], function () {
 | 
			
		||||
            this.columns = columns;
 | 
			
		||||
 | 
			
		||||
            this.datum = createNormalizedDatum(datum, columns);
 | 
			
		||||
            this.fullDatum = datum;
 | 
			
		||||
            this.limitEvaluator = limitEvaluator;
 | 
			
		||||
            this.objectKeyString = objectKeyString;
 | 
			
		||||
        }
 | 
			
		||||
@@ -87,7 +88,7 @@ define([], function () {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        getContextMenuActions() {
 | 
			
		||||
            return [];
 | 
			
		||||
            return ['viewDatumAction'];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -54,15 +54,13 @@ define([
 | 
			
		||||
            view(domainObject, objectPath) {
 | 
			
		||||
                let table = new TelemetryTable(domainObject, openmct);
 | 
			
		||||
                let component;
 | 
			
		||||
 | 
			
		||||
                let markingProp = {
 | 
			
		||||
                    enable: true,
 | 
			
		||||
                    useAlternateControlBar: false,
 | 
			
		||||
                    rowName: '',
 | 
			
		||||
                    rowNamePlural: ''
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                return {
 | 
			
		||||
                const view = {
 | 
			
		||||
                    show: function (element, editMode) {
 | 
			
		||||
                        component = new Vue({
 | 
			
		||||
                            el: element,
 | 
			
		||||
@@ -78,9 +76,10 @@ define([
 | 
			
		||||
                            provide: {
 | 
			
		||||
                                openmct,
 | 
			
		||||
                                table,
 | 
			
		||||
                                objectPath
 | 
			
		||||
                                objectPath,
 | 
			
		||||
                                view
 | 
			
		||||
                            },
 | 
			
		||||
                            template: '<table-component :isEditing="isEditing" :marking="markingProp"/>'
 | 
			
		||||
                            template: '<table-component ref="tableComponent" :isEditing="isEditing" :marking="markingProp"/>'
 | 
			
		||||
                        });
 | 
			
		||||
                    },
 | 
			
		||||
                    onEditModeChange(editMode) {
 | 
			
		||||
@@ -89,11 +88,22 @@ define([
 | 
			
		||||
                    onClearData() {
 | 
			
		||||
                        table.clearData();
 | 
			
		||||
                    },
 | 
			
		||||
                    getViewContext() {
 | 
			
		||||
                        if (component) {
 | 
			
		||||
                            return component.$refs.tableComponent.getViewContext();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            return {
 | 
			
		||||
                                type: 'telemetry-table'
 | 
			
		||||
                            };
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    destroy: function (element) {
 | 
			
		||||
                        component.$destroy();
 | 
			
		||||
                        component = undefined;
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                return view;
 | 
			
		||||
            },
 | 
			
		||||
            priority() {
 | 
			
		||||
                return 1;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										123
									
								
								src/plugins/telemetryTable/ViewActions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/plugins/telemetryTable/ViewActions.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
let exportCSV = {
 | 
			
		||||
    name: 'Export Table Data',
 | 
			
		||||
    key: 'export-csv-all',
 | 
			
		||||
    description: "Export this view's data",
 | 
			
		||||
    cssClass: 'icon-download labeled',
 | 
			
		||||
    invoke: (objectPath, viewProvider) => {
 | 
			
		||||
        viewProvider.getViewContext().exportAllDataAsCSV();
 | 
			
		||||
    },
 | 
			
		||||
    group: 'view'
 | 
			
		||||
};
 | 
			
		||||
let exportMarkedRows = {
 | 
			
		||||
    name: 'Export Marked Rows',
 | 
			
		||||
    key: 'export-csv-marked',
 | 
			
		||||
    description: "Export marked rows as CSV",
 | 
			
		||||
    cssClass: 'icon-download labeled',
 | 
			
		||||
    invoke: (objectPath, viewProvider) => {
 | 
			
		||||
        viewProvider.getViewContext().exportMarkedDataAsCSV();
 | 
			
		||||
    },
 | 
			
		||||
    group: 'view'
 | 
			
		||||
};
 | 
			
		||||
let unmarkAllRows = {
 | 
			
		||||
    name: 'Unmark All Rows',
 | 
			
		||||
    key: 'unmark-all-rows',
 | 
			
		||||
    description: 'Unmark all rows',
 | 
			
		||||
    cssClass: 'icon-x labeled',
 | 
			
		||||
    invoke: (objectPath, viewProvider) => {
 | 
			
		||||
        viewProvider.getViewContext().unmarkAllRows();
 | 
			
		||||
    },
 | 
			
		||||
    showInStatusBar: true,
 | 
			
		||||
    group: 'view'
 | 
			
		||||
};
 | 
			
		||||
let pause = {
 | 
			
		||||
    name: 'Pause',
 | 
			
		||||
    key: 'pause-data',
 | 
			
		||||
    description: 'Pause real-time data flow',
 | 
			
		||||
    cssClass: 'icon-pause',
 | 
			
		||||
    invoke: (objectPath, viewProvider) => {
 | 
			
		||||
        viewProvider.getViewContext().togglePauseByButton();
 | 
			
		||||
    },
 | 
			
		||||
    showInStatusBar: true,
 | 
			
		||||
    group: 'view'
 | 
			
		||||
};
 | 
			
		||||
let play = {
 | 
			
		||||
    name: 'Play',
 | 
			
		||||
    key: 'play-data',
 | 
			
		||||
    description: 'Continue real-time data flow',
 | 
			
		||||
    cssClass: 'c-button pause-play is-paused',
 | 
			
		||||
    invoke: (objectPath, viewProvider) => {
 | 
			
		||||
        viewProvider.getViewContext().togglePauseByButton();
 | 
			
		||||
    },
 | 
			
		||||
    showInStatusBar: true,
 | 
			
		||||
    group: 'view'
 | 
			
		||||
};
 | 
			
		||||
let expandColumns = {
 | 
			
		||||
    name: 'Expand Columns',
 | 
			
		||||
    key: 'expand-columns',
 | 
			
		||||
    description: "Increase column widths to fit currently available data.",
 | 
			
		||||
    cssClass: 'icon-arrows-right-left labeled',
 | 
			
		||||
    invoke: (objectPath, viewProvider) => {
 | 
			
		||||
        viewProvider.getViewContext().expandColumns();
 | 
			
		||||
    },
 | 
			
		||||
    showInStatusBar: true,
 | 
			
		||||
    group: 'view'
 | 
			
		||||
};
 | 
			
		||||
let autosizeColumns = {
 | 
			
		||||
    name: 'Autosize Columns',
 | 
			
		||||
    key: 'autosize-columns',
 | 
			
		||||
    description: "Automatically size columns to fit the table into the available space.",
 | 
			
		||||
    cssClass: 'icon-expand labeled',
 | 
			
		||||
    invoke: (objectPath, viewProvider) => {
 | 
			
		||||
        viewProvider.getViewContext().autosizeColumns();
 | 
			
		||||
    },
 | 
			
		||||
    showInStatusBar: true,
 | 
			
		||||
    group: 'view'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let viewActions = [
 | 
			
		||||
    exportCSV,
 | 
			
		||||
    exportMarkedRows,
 | 
			
		||||
    unmarkAllRows,
 | 
			
		||||
    pause,
 | 
			
		||||
    play,
 | 
			
		||||
    expandColumns,
 | 
			
		||||
    autosizeColumns
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
viewActions.forEach(action => {
 | 
			
		||||
    action.appliesTo = (objectPath, viewProvider = {}) => {
 | 
			
		||||
        let viewContext = viewProvider.getViewContext && viewProvider.getViewContext();
 | 
			
		||||
 | 
			
		||||
        if (viewContext) {
 | 
			
		||||
            let type = viewContext.type;
 | 
			
		||||
 | 
			
		||||
            return type === 'telemetry-table';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default viewActions;
 | 
			
		||||
@@ -1,41 +1,18 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div
 | 
			
		||||
    class="c-table-indicator"
 | 
			
		||||
    :class="{ 'is-filtering': filterNames.length > 0 }"
 | 
			
		||||
    v-if="filterNames.length > 0"
 | 
			
		||||
    :title="title"
 | 
			
		||||
    class="c-filter-indication"
 | 
			
		||||
    :class="{ 'c-filter-indication--mixed': hasMixedFilters }"
 | 
			
		||||
>
 | 
			
		||||
    <div
 | 
			
		||||
        v-if="filterNames.length > 0"
 | 
			
		||||
        class="c-table-indicator__filter c-table-indicator__elem c-filter-indication"
 | 
			
		||||
        :class="{ 'c-filter-indication--mixed': hasMixedFilters }"
 | 
			
		||||
        :title="title"
 | 
			
		||||
    <span class="c-filter-indication__mixed">{{ label }}</span>
 | 
			
		||||
    <span
 | 
			
		||||
        v-for="(name, index) in filterNames"
 | 
			
		||||
        :key="index"
 | 
			
		||||
        class="c-filter-indication__label"
 | 
			
		||||
    >
 | 
			
		||||
        <span class="c-filter-indication__mixed">{{ label }}</span>
 | 
			
		||||
        <span
 | 
			
		||||
            v-for="(name, index) in filterNames"
 | 
			
		||||
            :key="index"
 | 
			
		||||
            class="c-filter-indication__label"
 | 
			
		||||
        >
 | 
			
		||||
            {{ name }}
 | 
			
		||||
        </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="c-table-indicator__counts">
 | 
			
		||||
        <span
 | 
			
		||||
            :title="totalRows + ' rows visible after any filtering'"
 | 
			
		||||
            class="c-table-indicator__elem c-table-indicator__row-count"
 | 
			
		||||
        >
 | 
			
		||||
            {{ totalRows }} Rows
 | 
			
		||||
        </span>
 | 
			
		||||
 | 
			
		||||
        <span
 | 
			
		||||
            v-if="markedRows"
 | 
			
		||||
            class="c-table-indicator__elem c-table-indicator__marked-count"
 | 
			
		||||
            :title="markedRows + ' rows selected'"
 | 
			
		||||
        >
 | 
			
		||||
            {{ markedRows }} Marked
 | 
			
		||||
        </span>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
        {{ name }}
 | 
			
		||||
    </span>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -50,16 +27,6 @@ const USE_GLOBAL = 'useGlobal';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['openmct', 'table'],
 | 
			
		||||
    props: {
 | 
			
		||||
        markedRows: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: 0
 | 
			
		||||
        },
 | 
			
		||||
        totalRows: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: 0
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            filterNames: [],
 | 
			
		||||
@@ -1,54 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<tr class="c-telemetry-table__sizing-tr"><td>SIZING ROW</td></tr>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        isEditing: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            default: false
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
        isEditing: function (isEditing) {
 | 
			
		||||
            if (isEditing) {
 | 
			
		||||
                this.pollForRowHeight();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.clearPoll();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.$nextTick().then(() => {
 | 
			
		||||
            this.height = this.$el.offsetHeight;
 | 
			
		||||
            this.$emit('change-height', this.height);
 | 
			
		||||
        });
 | 
			
		||||
        if (this.isEditing) {
 | 
			
		||||
            this.pollForRowHeight();
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    destroyed() {
 | 
			
		||||
        this.clearPoll();
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        pollForRowHeight() {
 | 
			
		||||
            this.clearPoll();
 | 
			
		||||
            this.pollID = window.setInterval(this.heightPoll, 300);
 | 
			
		||||
        },
 | 
			
		||||
        clearPoll() {
 | 
			
		||||
            if (this.pollID) {
 | 
			
		||||
                window.clearInterval(this.pollID);
 | 
			
		||||
                this.pollID = undefined;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        heightPoll() {
 | 
			
		||||
            let height = this.$el.offsetHeight;
 | 
			
		||||
            if (height !== this.height) {
 | 
			
		||||
                this.$emit('change-height', height);
 | 
			
		||||
                this.height = height;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
.c-table-indicator {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    &__elem {
 | 
			
		||||
        @include ellipsize();
 | 
			
		||||
        flex: 0 1 auto;
 | 
			
		||||
        padding: 2px;
 | 
			
		||||
        text-transform: uppercase;
 | 
			
		||||
 | 
			
		||||
        > * {
 | 
			
		||||
            //display: contents;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__counts {
 | 
			
		||||
        //background: rgba(deeppink, 0.1);
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex: 1 1 auto;
 | 
			
		||||
        justify-content: flex-end;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
 | 
			
		||||
        > * {
 | 
			
		||||
            margin-left: $interiorMargin;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -102,7 +102,17 @@ export default {
 | 
			
		||||
                selectable[columnKeys] = this.row.columns[columnKeys].selectable;
 | 
			
		||||
 | 
			
		||||
                return selectable;
 | 
			
		||||
            }, {})
 | 
			
		||||
            }, {}),
 | 
			
		||||
            actionsViewContext: {
 | 
			
		||||
                getViewContext: () => {
 | 
			
		||||
                    return {
 | 
			
		||||
                        viewHistoricalData: true,
 | 
			
		||||
                        viewDatumAction: true,
 | 
			
		||||
                        getDatum: this.getDatum,
 | 
			
		||||
                        skipCache: true
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -170,14 +180,24 @@ export default {
 | 
			
		||||
                event.stopPropagation();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        getDatum() {
 | 
			
		||||
            return this.row.fullDatum;
 | 
			
		||||
        },
 | 
			
		||||
        showContextMenu: function (event) {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
 | 
			
		||||
            this.markRow(event);
 | 
			
		||||
 | 
			
		||||
            this.row.getContextualDomainObject(this.openmct, this.row.objectKeyString).then(domainObject => {
 | 
			
		||||
                let contextualObjectPath = this.objectPath.slice();
 | 
			
		||||
                contextualObjectPath.unshift(domainObject);
 | 
			
		||||
 | 
			
		||||
                this.openmct.contextMenu._showContextMenuForObjectPath(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions());
 | 
			
		||||
                let allActions = this.openmct.actions.get(contextualObjectPath, this.actionsViewContext);
 | 
			
		||||
                let applicableActions = this.row.getContextMenuActions().map(key => allActions[key]);
 | 
			
		||||
 | 
			
		||||
                if (applicableActions.length) {
 | 
			
		||||
                    this.openmct.menus.showMenu(event.x, event.y, applicableActions);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,6 @@
 | 
			
		||||
 | 
			
		||||
.c-telemetry-table {
 | 
			
		||||
    // Table that displays telemetry in a scrolling body area
 | 
			
		||||
 | 
			
		||||
    @include fontAndSize();
 | 
			
		||||
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-flow: column nowrap;
 | 
			
		||||
    justify-content: flex-start;
 | 
			
		||||
@@ -111,7 +108,7 @@
 | 
			
		||||
            display: flex; // flex-flow defaults to row nowrap (which is what we want) so no need to define
 | 
			
		||||
            align-items: stretch;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            min-height: 18px; // Needed when a row has empty values in its cells
 | 
			
		||||
            height: 18px; // Needed when a row has empty values in its cells
 | 
			
		||||
 | 
			
		||||
            .is-editing .l-layout__frame & {
 | 
			
		||||
                pointer-events: none;
 | 
			
		||||
@@ -153,41 +150,6 @@
 | 
			
		||||
            white-space: nowrap;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__sizing-tr {
 | 
			
		||||
        // A row element used to determine sizing of rows based on font size
 | 
			
		||||
        visibility: hidden;
 | 
			
		||||
        pointer-events: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__footer {
 | 
			
		||||
        $pt: 2px;
 | 
			
		||||
        border-top: 1px solid $colorInteriorBorder;
 | 
			
		||||
        margin-top: $interiorMargin;
 | 
			
		||||
        padding: $pt 0;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        transition: all 250ms;
 | 
			
		||||
 | 
			
		||||
        &:not(.is-filtering) {
 | 
			
		||||
            .c-frame & {
 | 
			
		||||
                height: 0;
 | 
			
		||||
                padding: 0;
 | 
			
		||||
                visibility: hidden;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .c-frame & {
 | 
			
		||||
        // target .c-frame .c-telemetry-table {}
 | 
			
		||||
        $pt: 2px;
 | 
			
		||||
        &:hover {
 | 
			
		||||
            .c-telemetry-table__footer:not(.is-filtering) {
 | 
			
		||||
                height: $pt + 16px;
 | 
			
		||||
                padding: initial;
 | 
			
		||||
                visibility: visible;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/******************************* SPECIFIC CASE WRAPPERS */
 | 
			
		||||
 
 | 
			
		||||
@@ -23,79 +23,6 @@
 | 
			
		||||
<div class="c-table-wrapper"
 | 
			
		||||
     :class="{ 'is-paused': paused }"
 | 
			
		||||
>
 | 
			
		||||
    <!-- main contolbar  start-->
 | 
			
		||||
    <div v-if="!marking.useAlternateControlBar"
 | 
			
		||||
         class="c-table-control-bar c-control-bar"
 | 
			
		||||
    >
 | 
			
		||||
        <button
 | 
			
		||||
            v-if="allowExport"
 | 
			
		||||
            v-show="!markedRows.length"
 | 
			
		||||
            class="c-button icon-download labeled"
 | 
			
		||||
            title="Export this view's data"
 | 
			
		||||
            @click="exportAllDataAsCSV()"
 | 
			
		||||
        >
 | 
			
		||||
            <span class="c-button__label">Export Table Data</span>
 | 
			
		||||
        </button>
 | 
			
		||||
        <button
 | 
			
		||||
            v-if="allowExport"
 | 
			
		||||
            v-show="markedRows.length"
 | 
			
		||||
            class="c-button icon-download labeled"
 | 
			
		||||
            title="Export marked rows as CSV"
 | 
			
		||||
            @click="exportMarkedDataAsCSV()"
 | 
			
		||||
        >
 | 
			
		||||
            <span class="c-button__label">Export Marked Rows</span>
 | 
			
		||||
        </button>
 | 
			
		||||
        <button
 | 
			
		||||
            v-show="markedRows.length"
 | 
			
		||||
            class="c-button icon-x labeled"
 | 
			
		||||
            title="Unmark all rows"
 | 
			
		||||
            @click="unmarkAllRows()"
 | 
			
		||||
        >
 | 
			
		||||
            <span class="c-button__label">Unmark All Rows</span>
 | 
			
		||||
        </button>
 | 
			
		||||
        <div
 | 
			
		||||
            v-if="marking.enable"
 | 
			
		||||
            class="c-separator"
 | 
			
		||||
        ></div>
 | 
			
		||||
        <button
 | 
			
		||||
            v-if="marking.enable"
 | 
			
		||||
            class="c-button icon-pause pause-play labeled"
 | 
			
		||||
            :class=" paused ? 'icon-play is-paused' : 'icon-pause'"
 | 
			
		||||
            :title="paused ? 'Continue real-time data flow' : 'Pause real-time data flow'"
 | 
			
		||||
            @click="togglePauseByButton()"
 | 
			
		||||
        >
 | 
			
		||||
            <span class="c-button__label">
 | 
			
		||||
                {{ paused ? 'Play' : 'Pause' }}
 | 
			
		||||
            </span>
 | 
			
		||||
        </button>
 | 
			
		||||
 | 
			
		||||
        <template v-if="!isEditing">
 | 
			
		||||
            <div
 | 
			
		||||
                class="c-separator"
 | 
			
		||||
            >
 | 
			
		||||
            </div>
 | 
			
		||||
            <button
 | 
			
		||||
                v-if="isAutosizeEnabled"
 | 
			
		||||
                class="c-button icon-arrows-right-left labeled"
 | 
			
		||||
                title="Increase column widths to fit currently available data."
 | 
			
		||||
                @click="recalculateColumnWidths"
 | 
			
		||||
            >
 | 
			
		||||
                <span class="c-button__label">Expand Columns</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
                v-else
 | 
			
		||||
                class="c-button icon-expand labeled"
 | 
			
		||||
                title="Automatically size columns to fit the table into the available space."
 | 
			
		||||
                @click="autosizeColumns"
 | 
			
		||||
            >
 | 
			
		||||
                <span class="c-button__label">Autosize Columns</span>
 | 
			
		||||
            </button>
 | 
			
		||||
        </template>
 | 
			
		||||
 | 
			
		||||
        <slot name="buttons"></slot>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- main controlbar end -->
 | 
			
		||||
 | 
			
		||||
    <!-- alternate controlbar start -->
 | 
			
		||||
    <div v-if="marking.useAlternateControlBar"
 | 
			
		||||
         class="c-table-control-bar c-control-bar"
 | 
			
		||||
@@ -113,11 +40,11 @@
 | 
			
		||||
 | 
			
		||||
        <button
 | 
			
		||||
            :class="{'hide-nice': !markedRows.length}"
 | 
			
		||||
            class="c-button icon-x labeled"
 | 
			
		||||
            class="c-icon-button icon-x labeled"
 | 
			
		||||
            title="Deselect All"
 | 
			
		||||
            @click="unmarkAllRows()"
 | 
			
		||||
        >
 | 
			
		||||
            <span class="c-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span>
 | 
			
		||||
            <span class="c-icon-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span>
 | 
			
		||||
        </button>
 | 
			
		||||
 | 
			
		||||
        <slot name="buttons"></slot>
 | 
			
		||||
@@ -125,7 +52,7 @@
 | 
			
		||||
    <!-- alternate controlbar end  -->
 | 
			
		||||
 | 
			
		||||
    <div
 | 
			
		||||
        class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar u-style-receiver js-style-receiver"
 | 
			
		||||
        class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
 | 
			
		||||
        :class="{
 | 
			
		||||
            'loading': loading,
 | 
			
		||||
            'is-paused' : paused
 | 
			
		||||
@@ -234,10 +161,6 @@
 | 
			
		||||
            class="c-telemetry-table__sizing js-telemetry-table__sizing"
 | 
			
		||||
            :style="sizingTableWidth"
 | 
			
		||||
        >
 | 
			
		||||
            <sizing-row
 | 
			
		||||
                :is-editing="isEditing"
 | 
			
		||||
                @change-height="setRowHeight"
 | 
			
		||||
            />
 | 
			
		||||
            <tr>
 | 
			
		||||
                <template v-for="(title, key) in headers">
 | 
			
		||||
                    <th
 | 
			
		||||
@@ -257,11 +180,7 @@
 | 
			
		||||
                :object-path="objectPath"
 | 
			
		||||
            />
 | 
			
		||||
        </table>
 | 
			
		||||
        <table-footer-indicator
 | 
			
		||||
            class="c-telemetry-table__footer"
 | 
			
		||||
            :marked-rows="markedRows.length"
 | 
			
		||||
            :total-rows="totalNumberOfRows"
 | 
			
		||||
        />
 | 
			
		||||
        <telemetry-filter-indicator />
 | 
			
		||||
    </div>
 | 
			
		||||
</div><!-- closes c-table-wrapper -->
 | 
			
		||||
</template>
 | 
			
		||||
@@ -270,11 +189,10 @@
 | 
			
		||||
import TelemetryTableRow from './table-row.vue';
 | 
			
		||||
import search from '../../../ui/components/search.vue';
 | 
			
		||||
import TableColumnHeader from './table-column-header.vue';
 | 
			
		||||
import TableFooterIndicator from './table-footer-indicator.vue';
 | 
			
		||||
import TelemetryFilterIndicator from './TelemetryFilterIndicator.vue';
 | 
			
		||||
import CSVExporter from '../../../exporters/CSVExporter.js';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
 | 
			
		||||
import SizingRow from './sizing-row.vue';
 | 
			
		||||
 | 
			
		||||
const VISIBLE_ROW_COUNT = 100;
 | 
			
		||||
const ROW_HEIGHT = 17;
 | 
			
		||||
@@ -286,11 +204,10 @@ export default {
 | 
			
		||||
        TelemetryTableRow,
 | 
			
		||||
        TableColumnHeader,
 | 
			
		||||
        search,
 | 
			
		||||
        TableFooterIndicator,
 | 
			
		||||
        ToggleSwitch,
 | 
			
		||||
        SizingRow
 | 
			
		||||
        TelemetryFilterIndicator,
 | 
			
		||||
        ToggleSwitch
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['table', 'openmct', 'objectPath'],
 | 
			
		||||
    inject: ['table', 'openmct', 'objectPath', 'view'],
 | 
			
		||||
    props: {
 | 
			
		||||
        isEditing: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
@@ -352,8 +269,7 @@ export default {
 | 
			
		||||
            paused: false,
 | 
			
		||||
            markedRows: [],
 | 
			
		||||
            isShowingMarkedRowsOnly: false,
 | 
			
		||||
            hideHeaders: configuration.hideHeaders,
 | 
			
		||||
            totalNumberOfRows: 0
 | 
			
		||||
            hideHeaders: configuration.hideHeaders
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -394,6 +310,34 @@ export default {
 | 
			
		||||
        markedRows: {
 | 
			
		||||
            handler(newVal, oldVal) {
 | 
			
		||||
                this.$emit('marked-rows-updated', newVal, oldVal);
 | 
			
		||||
 | 
			
		||||
                if (newVal.length > 0) {
 | 
			
		||||
                    this.viewActionsCollection.enable(['export-csv-marked', 'unmark-all-rows']);
 | 
			
		||||
                } else if (newVal.length === 0) {
 | 
			
		||||
                    this.viewActionsCollection.disable(['export-csv-marked', 'unmark-all-rows']);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        paused: {
 | 
			
		||||
            handler(newVal) {
 | 
			
		||||
                if (newVal) {
 | 
			
		||||
                    this.viewActionsCollection.hide(['pause-data']);
 | 
			
		||||
                    this.viewActionsCollection.show(['play-data']);
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.viewActionsCollection.hide(['play-data']);
 | 
			
		||||
                    this.viewActionsCollection.show(['pause-data']);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        isAutosizeEnabled: {
 | 
			
		||||
            handler(newVal) {
 | 
			
		||||
                if (newVal) {
 | 
			
		||||
                    this.viewActionsCollection.show(['expand-columns']);
 | 
			
		||||
                    this.viewActionsCollection.hide(['autosize-columns']);
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.viewActionsCollection.show(['autosize-columns']);
 | 
			
		||||
                    this.viewActionsCollection.hide(['expand-columns']);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
@@ -406,6 +350,9 @@ export default {
 | 
			
		||||
        this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
 | 
			
		||||
        this.scroll = _.throttle(this.scroll, 100);
 | 
			
		||||
 | 
			
		||||
        this.viewActionsCollection = this.openmct.actions.get(this.objectPath, this.view);
 | 
			
		||||
        this.initializeViewActions();
 | 
			
		||||
 | 
			
		||||
        this.table.on('object-added', this.addObject);
 | 
			
		||||
        this.table.on('object-removed', this.removeObject);
 | 
			
		||||
        this.table.on('outstanding-requests', this.outstandingRequests);
 | 
			
		||||
@@ -462,8 +409,6 @@ export default {
 | 
			
		||||
                    let filteredRows = this.table.filteredRows.getRows();
 | 
			
		||||
                    let filteredRowsLength = filteredRows.length;
 | 
			
		||||
 | 
			
		||||
                    this.totalNumberOfRows = filteredRowsLength;
 | 
			
		||||
 | 
			
		||||
                    if (filteredRowsLength < VISIBLE_ROW_COUNT) {
 | 
			
		||||
                        end = filteredRowsLength;
 | 
			
		||||
                    } else {
 | 
			
		||||
@@ -512,7 +457,7 @@ export default {
 | 
			
		||||
            let columnWidths = {};
 | 
			
		||||
            let totalWidth = 0;
 | 
			
		||||
            let headerKeys = Object.keys(this.headers);
 | 
			
		||||
            let sizingTableRow = this.sizingTable.children[1];
 | 
			
		||||
            let sizingTableRow = this.sizingTable.children[0];
 | 
			
		||||
            let sizingCells = sizingTableRow.children;
 | 
			
		||||
 | 
			
		||||
            headerKeys.forEach((headerKey, headerIndex, array) => {
 | 
			
		||||
@@ -846,7 +791,7 @@ export default {
 | 
			
		||||
 | 
			
		||||
                for (let i = firstRowIndex; i <= lastRowIndex; i++) {
 | 
			
		||||
                    let row = allRows[i];
 | 
			
		||||
                    row.marked = true;
 | 
			
		||||
                    this.$set(row, 'marked', true);
 | 
			
		||||
 | 
			
		||||
                    if (row !== baseRow) {
 | 
			
		||||
                        this.markedRows.push(row);
 | 
			
		||||
@@ -908,11 +853,39 @@ export default {
 | 
			
		||||
 | 
			
		||||
            this.$nextTick().then(this.calculateColumnWidths);
 | 
			
		||||
        },
 | 
			
		||||
        setRowHeight(height) {
 | 
			
		||||
            this.rowHeight = height;
 | 
			
		||||
            this.setHeight();
 | 
			
		||||
            this.calculateTableSize();
 | 
			
		||||
            this.clearRowsAndRerender();
 | 
			
		||||
        getViewContext() {
 | 
			
		||||
            return {
 | 
			
		||||
                type: 'telemetry-table',
 | 
			
		||||
                exportAllDataAsCSV: this.exportAllDataAsCSV,
 | 
			
		||||
                exportMarkedRows: this.exportMarkedRows,
 | 
			
		||||
                unmarkAllRows: this.unmarkAllRows,
 | 
			
		||||
                togglePauseByButton: this.togglePauseByButton,
 | 
			
		||||
                expandColumns: this.recalculateColumnWidths,
 | 
			
		||||
                autosizeColumns: this.autosizeColumns
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        initializeViewActions() {
 | 
			
		||||
            if (this.markedRows.length > 0) {
 | 
			
		||||
                this.viewActionsCollection.enable(['export-csv-marked', 'unmark-all-rows']);
 | 
			
		||||
            } else if (this.markedRows.length === 0) {
 | 
			
		||||
                this.viewActionsCollection.disable(['export-csv-marked', 'unmark-all-rows']);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.paused) {
 | 
			
		||||
                this.viewActionsCollection.hide(['pause-data']);
 | 
			
		||||
                this.viewActionsCollection.show(['play-data']);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.viewActionsCollection.hide(['play-data']);
 | 
			
		||||
                this.viewActionsCollection.show(['pause-data']);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.isAutosizeEnabled) {
 | 
			
		||||
                this.viewActionsCollection.show(['expand-columns']);
 | 
			
		||||
                this.viewActionsCollection.hide(['autosize-columns']);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.viewActionsCollection.show(['autosize-columns']);
 | 
			
		||||
                this.viewActionsCollection.hide(['expand-columns']);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
.c-filter-indication {
 | 
			
		||||
    @include userSelectNone();
 | 
			
		||||
    background: $colorFilterBg;
 | 
			
		||||
    color: $colorFilterFg;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
    margin-top: $interiorMarginSm;
 | 
			
		||||
    padding: 2px;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
 | 
			
		||||
    &:before {
 | 
			
		||||
        font-family: symbolsfont-12px;
 | 
			
		||||
        content: $glyph-icon-filter;
 | 
			
		||||
        display: block;
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
        margin-right: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__mixed {
 | 
			
		||||
        margin-right: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &--mixed {
 | 
			
		||||
        .c-filter-indication__mixed {
 | 
			
		||||
            font-style: italic;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__label {
 | 
			
		||||
        + .c-filter-indication__label {
 | 
			
		||||
            &:before {
 | 
			
		||||
                content: ',';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,11 +23,13 @@
 | 
			
		||||
define([
 | 
			
		||||
    './TelemetryTableViewProvider',
 | 
			
		||||
    './TableConfigurationViewProvider',
 | 
			
		||||
    './TelemetryTableType'
 | 
			
		||||
    './TelemetryTableType',
 | 
			
		||||
    './ViewActions'
 | 
			
		||||
], function (
 | 
			
		||||
    TelemetryTableViewProvider,
 | 
			
		||||
    TableConfigurationViewProvider,
 | 
			
		||||
    TelemetryTableType
 | 
			
		||||
    TelemetryTableType,
 | 
			
		||||
    TelemetryTableViewActions
 | 
			
		||||
) {
 | 
			
		||||
    return function plugin() {
 | 
			
		||||
        return function install(openmct) {
 | 
			
		||||
@@ -41,6 +43,10 @@ define([
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            TelemetryTableViewActions.default.forEach(action => {
 | 
			
		||||
                openmct.actions.register(action);
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -168,6 +168,8 @@ describe("the plugin", () => {
 | 
			
		||||
                return telemetryPromise;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            openmct.router.path = [testTelemetryObject];
 | 
			
		||||
 | 
			
		||||
            applicableViews = openmct.objectViews.get(testTelemetryObject);
 | 
			
		||||
            tableViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'table');
 | 
			
		||||
            tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]);
 | 
			
		||||
@@ -183,11 +185,10 @@ describe("the plugin", () => {
 | 
			
		||||
 | 
			
		||||
        it("Renders a column for every item in telemetry metadata", () => {
 | 
			
		||||
            let headers = element.querySelectorAll('span.c-telemetry-table__headers__label');
 | 
			
		||||
            expect(headers.length).toBe(4);
 | 
			
		||||
            expect(headers[0].innerText).toBe('Name');
 | 
			
		||||
            expect(headers[1].innerText).toBe('Time');
 | 
			
		||||
            expect(headers[2].innerText).toBe('Some attribute');
 | 
			
		||||
            expect(headers[3].innerText).toBe('Another attribute');
 | 
			
		||||
            expect(headers.length).toBe(3);
 | 
			
		||||
            expect(headers[0].innerText).toBe('Time');
 | 
			
		||||
            expect(headers[1].innerText).toBe('Some attribute');
 | 
			
		||||
            expect(headers[2].innerText).toBe('Another attribute');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Supports column reordering via drag and drop", () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,21 @@
 | 
			
		||||
@import "../../styles/vendor/normalize-min";
 | 
			
		||||
@import "../../styles/constants";
 | 
			
		||||
@import "../../styles/constants-mobile.scss";
 | 
			
		||||
@import "~styles/vendor/normalize-min";
 | 
			
		||||
@import "~styles/constants";
 | 
			
		||||
@import "~styles/constants-mobile.scss";
 | 
			
		||||
 | 
			
		||||
@import "../../styles/constants-espresso";
 | 
			
		||||
@import "~styles/constants-espresso";
 | 
			
		||||
 | 
			
		||||
@import "../../styles/mixins";
 | 
			
		||||
@import "../../styles/animations";
 | 
			
		||||
@import "../../styles/about";
 | 
			
		||||
@import "../../styles/glyphs";
 | 
			
		||||
@import "../../styles/global";
 | 
			
		||||
@import "../../styles/status";
 | 
			
		||||
@import "../../styles/controls";
 | 
			
		||||
@import "../../styles/forms";
 | 
			
		||||
@import "../../styles/table";
 | 
			
		||||
@import "../../styles/legacy";
 | 
			
		||||
@import "../../styles/legacy-plots";
 | 
			
		||||
@import "../../styles/plotly";
 | 
			
		||||
@import "../../styles/legacy-messages";
 | 
			
		||||
@import "~styles/mixins";
 | 
			
		||||
@import "~styles/animations";
 | 
			
		||||
@import "~styles/about";
 | 
			
		||||
@import "~styles/glyphs";
 | 
			
		||||
@import "~styles/global";
 | 
			
		||||
@import "~styles/status";
 | 
			
		||||
@import "~styles/controls";
 | 
			
		||||
@import "~styles/forms";
 | 
			
		||||
@import "~styles/table";
 | 
			
		||||
@import "~styles/legacy";
 | 
			
		||||
@import "~styles/legacy-plots";
 | 
			
		||||
@import "~styles/plotly";
 | 
			
		||||
@import "~styles/legacy-messages";
 | 
			
		||||
 | 
			
		||||
@import "../../styles/vue-styles.scss";
 | 
			
		||||
@import "~styles/vue-styles.scss";
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user