Compare commits

..

1 Commits

Author SHA1 Message Date
charlesh88
db2c92edce Add new icon for aggregate telemetry 2021-08-30 11:03:09 -07:00
50 changed files with 862 additions and 1331 deletions

View File

@@ -1,33 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ master ]
schedule:
- cron: '28 21 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: javascript
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -195,7 +195,6 @@
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
{indicator: true}
));
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
openmct.start();
</script>
</html>

View File

@@ -21,24 +21,32 @@
*****************************************************************************/
define([
"moment-timezone",
"./src/indicators/ClockIndicator",
"./src/services/TickerService",
"./src/services/TimerService",
"./src/controllers/ClockController",
"./src/controllers/TimerController",
"./src/controllers/RefreshingController",
"./src/actions/StartTimerAction",
"./src/actions/RestartTimerAction",
"./src/actions/StopTimerAction",
"./src/actions/PauseTimerAction",
"./res/templates/clock.html",
"./res/templates/timer.html"
], function (
MomentTimezone,
ClockIndicator,
TickerService,
TimerService,
ClockController,
TimerController,
RefreshingController,
StartTimerAction,
RestartTimerAction,
StopTimerAction,
PauseTimerAction,
clockTemplate,
timerTemplate
) {
return {
@@ -65,6 +73,16 @@ define([
"value": "YYYY/MM/DD HH:mm:ss"
}
],
"indicators": [
{
"implementation": ClockIndicator,
"depends": [
"tickerService",
"CLOCK_INDICATOR_FORMAT"
],
"priority": "preferred"
}
],
"services": [
{
"key": "tickerService",
@@ -81,6 +99,14 @@ define([
}
],
"controllers": [
{
"key": "ClockController",
"implementation": ClockController,
"depends": [
"$scope",
"tickerService"
]
},
{
"key": "TimerController",
"implementation": TimerController,
@@ -100,6 +126,12 @@ define([
}
],
"views": [
{
"key": "clock",
"type": "clock",
"editable": false,
"template": clockTemplate
},
{
"key": "timer",
"type": "timer",
@@ -154,6 +186,70 @@ define([
}
],
"types": [
{
"key": "clock",
"name": "Clock",
"cssClass": "icon-clock",
"description": "A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.",
"priority": 101,
"features": [
"creation"
],
"properties": [
{
"key": "clockFormat",
"name": "Display Format",
"control": "composite",
"items": [
{
"control": "select",
"options": [
{
"value": "YYYY/MM/DD hh:mm:ss",
"name": "YYYY/MM/DD hh:mm:ss"
},
{
"value": "YYYY/DDD hh:mm:ss",
"name": "YYYY/DDD hh:mm:ss"
},
{
"value": "hh:mm:ss",
"name": "hh:mm:ss"
}
],
"cssClass": "l-inline"
},
{
"control": "select",
"options": [
{
"value": "clock12",
"name": "12hr"
},
{
"value": "clock24",
"name": "24hr"
}
],
"cssClass": "l-inline"
}
]
},
{
"key": "timezone",
"name": "Timezone",
"control": "autocomplete",
"options": MomentTimezone.tz.names()
}
],
"model": {
"clockFormat": [
"YYYY/MM/DD hh:mm:ss",
"clock12"
],
"timezone": "UTC"
}
},
{
"key": "timer",
"name": "Timer",

View File

@@ -0,0 +1,32 @@
<!--
Open MCT, Copyright (c) 2009-2016, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="c-clock l-time-display u-style-receiver js-style-receiver" ng-controller="ClockController as clock">
<div class="c-clock__timezone">
{{clock.zone()}}
</div>
<div class="c-clock__value">
{{clock.text()}}
</div>
<div class="c-clock__ampm">
{{clock.ampm()}}
</div>
</div>

View File

@@ -0,0 +1,110 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'moment',
'moment-timezone'
],
function (
moment,
momentTimezone
) {
/**
* Controller for views of a Clock domain object.
*
* @constructor
* @memberof platform/features/clock
* @param {angular.Scope} $scope the Angular scope
* @param {platform/features/clock.TickerService} tickerService
* a service used to align behavior with clock ticks
*/
function ClockController($scope, tickerService) {
var lastTimestamp,
unlisten,
timeFormat,
zoneName,
self = this;
function update() {
var m = zoneName
? moment.utc(lastTimestamp).tz(zoneName) : moment.utc(lastTimestamp);
self.zoneAbbr = m.zoneAbbr();
self.textValue = timeFormat && m.format(timeFormat);
self.ampmValue = m.format("A"); // Just the AM or PM part
}
function tick(timestamp) {
lastTimestamp = timestamp;
update();
}
function updateModel(model) {
var baseFormat;
if (model !== undefined) {
baseFormat = model.clockFormat[0];
self.use24 = model.clockFormat[1] === 'clock24';
timeFormat = self.use24
? baseFormat.replace('hh', "HH") : baseFormat;
// If wrong timezone is provided, the UTC will be used
zoneName = momentTimezone.tz.names().includes(model.timezone)
? model.timezone : "UTC";
update();
}
}
// Pull in the model (clockFormat and timezone) from the domain object model
$scope.$watch('model', updateModel);
// Listen for clock ticks ... and stop listening on destroy
unlisten = tickerService.listen(tick);
$scope.$on('$destroy', unlisten);
}
/**
* Get the clock's time zone, as displayable text.
* @returns {string}
*/
ClockController.prototype.zone = function () {
return this.zoneAbbr;
};
/**
* Get the current time, as displayable text.
* @returns {string}
*/
ClockController.prototype.text = function () {
return this.textValue;
};
/**
* Get the text to display to qualify a time as AM or PM.
* @returns {string}
*/
ClockController.prototype.ampm = function () {
return this.use24 ? '' : this.ampmValue;
};
return ClockController;
}
);

View File

@@ -0,0 +1,65 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['moment'],
function (moment) {
/**
* Indicator that displays the current UTC time in the status area.
* @implements {Indicator}
* @memberof platform/features/clock
* @param {platform/features/clock.TickerService} tickerService
* a service used to align behavior with clock ticks
* @param {string} indicatorFormat format string for timestamps
* shown in this indicator
*/
function ClockIndicator(tickerService, indicatorFormat) {
var self = this;
this.text = "";
tickerService.listen(function (timestamp) {
self.text = moment.utc(timestamp)
.format(indicatorFormat) + " UTC";
});
}
ClockIndicator.prototype.getGlyphClass = function () {
return "";
};
ClockIndicator.prototype.getCssClass = function () {
return "t-indicator-clock icon-clock no-minify c-indicator--not-clickable";
};
ClockIndicator.prototype.getText = function () {
return this.text;
};
ClockIndicator.prototype.getDescription = function () {
return "";
};
return ClockIndicator;
}
);

View File

@@ -0,0 +1,107 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/controllers/ClockController"],
function (ClockController) {
// Wed, 03 Jun 2015 17:56:14 GMT
var TEST_TIMESTAMP = 1433354174000;
describe("A clock view's controller", function () {
var mockScope,
mockTicker,
mockUnticker,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']);
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
mockUnticker = jasmine.createSpy('unticker');
mockTicker.listen.and.returnValue(mockUnticker);
controller = new ClockController(mockScope, mockTicker);
});
it("watches for model (clockFormat and timezone) from the domain object model", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"model",
jasmine.any(Function)
);
});
it("subscribes to clock ticks", function () {
expect(mockTicker.listen)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("unsubscribes to ticks when destroyed", function () {
// Make sure $destroy is being listened for...
expect(mockScope.$on.calls.mostRecent().args[0]).toEqual('$destroy');
expect(mockUnticker).not.toHaveBeenCalled();
// ...and makes sure that its listener unsubscribes from ticker
mockScope.$on.calls.mostRecent().args[1]();
expect(mockUnticker).toHaveBeenCalled();
});
it("formats using the format string from the model", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
mockScope.$watch.calls.mostRecent().args[1]({
"clockFormat": [
"YYYY-DDD hh:mm:ss",
"clock24"
],
"timezone": "Canada/Eastern"
});
expect(controller.zone()).toEqual("EDT");
expect(controller.text()).toEqual("2015-154 13:56:14");
expect(controller.ampm()).toEqual("");
});
it("formats 12-hour time", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
mockScope.$watch.calls.mostRecent().args[1]({
"clockFormat": [
"YYYY-DDD hh:mm:ss",
"clock12"
],
"timezone": ""
});
expect(controller.zone()).toEqual("UTC");
expect(controller.text()).toEqual("2015-154 05:56:14");
expect(controller.ampm()).toEqual("PM");
});
it("does not throw exceptions when model is undefined", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
expect(function () {
mockScope.$watch.calls.mostRecent().args[1](undefined);
}).not.toThrow();
});
});
}
);

View File

@@ -0,0 +1,58 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/indicators/ClockIndicator"],
function (ClockIndicator) {
// Wed, 03 Jun 2015 17:56:14 GMT
var TEST_TIMESTAMP = 1433354174000,
TEST_FORMAT = "YYYY-DDD HH:mm:ss";
describe("The clock indicator", function () {
var mockTicker,
mockUnticker,
indicator;
beforeEach(function () {
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
mockUnticker = jasmine.createSpy('unticker');
mockTicker.listen.and.returnValue(mockUnticker);
indicator = new ClockIndicator(mockTicker, TEST_FORMAT);
});
it("displays the current time", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
expect(indicator.getText()).toEqual("2015-154 17:56:14 UTC");
});
it("implements the Indicator interface", function () {
expect(indicator.getCssClass()).toEqual(jasmine.any(String));
expect(indicator.getText()).toEqual(jasmine.any(String));
expect(indicator.getDescription()).toEqual(jasmine.any(String));
});
});
}
);

View File

@@ -30,8 +30,8 @@ define([
return function ImportExportPlugin() {
return function (openmct) {
ExportAsJSONAction.prototype.appliesTo = function (context) {
return this.openmct.$injector.get('policyService')
ExportAsJSONAction.appliesTo = function (context) {
return openmct.$injector.get('policyService')
.allow("creation", context.domainObject.getCapability("type")
);
};

View File

@@ -29,7 +29,7 @@ define(
],
function (ExportAsJSONAction, domainObjectFactory, MCT, AdapterCapability) {
describe("The export JSON action", function () {
xdescribe("The export JSON action", function () {
var context,
action,
@@ -102,7 +102,7 @@ define(
expect(action).toBeDefined();
});
xit("doesn't export non-creatable objects in tree", function () {
it("doesn't export non-creatable objects in tree", function () {
var nonCreatableType = {
hasFeature:
function (feature) {
@@ -149,7 +149,7 @@ define(
});
});
xit("can export self-containing objects", function () {
it("can export self-containing objects", function () {
var parent = domainObjectFactory({
name: 'parent',
model: {
@@ -191,7 +191,7 @@ define(
});
});
xit("exports links to external objects as new objects", function () {
it("exports links to external objects as new objects", function () {
var parent = domainObjectFactory({
name: 'parent',
model: {

View File

@@ -27,7 +27,7 @@ define(
],
function (ImportAsJSONAction, domainObjectFactory) {
describe("The import JSON action", function () {
xdescribe("The import JSON action", function () {
var context = {};
var action,
@@ -146,7 +146,7 @@ define(
});
});
xit("can import self-containing objects", function () {
it("can import self-containing objects", function () {
var compDomainObject = domainObjectFactory({
name: 'compObject',
model: { name: 'compObject'},
@@ -198,7 +198,7 @@ define(
});
});
xit("assigns new ids to each imported object", function () {
it("assigns new ids to each imported object", function () {
dialogService.getUserInput.and.returnValue(Promise.resolve(
{
selectFile: {

View File

@@ -358,20 +358,6 @@ ObjectAPI.prototype.applyGetInterceptors = function (identifier, domainObject) {
return domainObject;
};
/**
* Return relative url path from a given object path
* eg: #/browse/mine/cb56f6bf-c900-43b7-b923-2e3b64b412db/6e89e858-77ce-46e4-a1ad-749240286497/....
* @param {Array} objectPath
* @returns {string} relative url for object
*/
ObjectAPI.prototype.getRelativePath = function (objectPath) {
return objectPath
.map(p => this.makeKeyString(p.identifier))
.reverse()
.join('/')
;
};
/**
* Modify a domain object.
* @param {module:openmct.DomainObject} object the object to mutate

View File

@@ -10,37 +10,28 @@ const cssClasses = {
};
class Overlay extends EventEmitter {
constructor({
buttons,
autoHide = true,
dismissable = true,
element,
onDestroy,
size
} = {}) {
constructor(options) {
super();
this.dismissable = options.dismissable !== false;
this.container = document.createElement('div');
this.container.classList.add('l-overlay-wrapper', cssClasses[size]);
this.autoHide = autoHide;
this.dismissable = dismissable !== false;
this.container.classList.add('l-overlay-wrapper', cssClasses[options.size]);
this.component = new Vue({
components: {
OverlayComponent: OverlayComponent
},
provide: {
dismiss: this.dismiss.bind(this),
element,
buttons,
element: options.element,
buttons: options.buttons,
dismissable: this.dismissable
},
components: {
OverlayComponent: OverlayComponent
},
template: '<overlay-component></overlay-component>'
});
if (onDestroy) {
this.once('destroy', onDestroy);
if (options.onDestroy) {
this.once('destroy', options.onDestroy);
}
}

View File

@@ -30,10 +30,7 @@ class OverlayAPI {
*/
showOverlay(overlay) {
if (this.activeOverlays.length) {
const previousOverlay = this.activeOverlays[this.activeOverlays.length - 1];
if (previousOverlay.autoHide) {
previousOverlay.container.classList.add('invisible');
}
this.activeOverlays[this.activeOverlays.length - 1].container.classList.add('invisible');
}
this.activeOverlays.push(overlay);

View File

@@ -342,8 +342,6 @@ export class TelemetryCollection extends EventEmitter {
this.boundedTelemetry = [];
this.futureBuffer = [];
this.emit('clear');
this._requestHistoricalTelemetry();
}

View File

@@ -90,7 +90,7 @@ class ImageExporter {
element.id = oldId;
},
removeContainer: true // Set to false to debug what html2canvas renders
}).then(canvas => {
}).then(function (canvas) {
dialog.dismiss();
return new Promise(function (resolve, reject) {
@@ -105,10 +105,9 @@ class ImageExporter {
return canvas.toBlob(blob => resolve({ blob }), mimeType);
});
}).catch(error => {
}, function (error) {
console.log('error capturing image', error);
dialog.dismiss();
console.error('error capturing image', error);
const errorDialog = overlays.dialog({
iconClass: 'error',
message: 'Image was not captured successfully!',

View File

@@ -1,78 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
function inSelectionPath(openmct, domainObject) {
const domainObjectIdentifier = domainObject.identifier;
return openmct.selection.get().some(selectionPath => {
return selectionPath.some(objectInPath => {
const objectInPathIdentifier = objectInPath.context.item.identifier;
return openmct.objects.areIdsEqual(objectInPathIdentifier, domainObjectIdentifier);
});
});
}
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';
this._openmct = openmct;
this._appliesToObjects = appliesToObjects;
}
invoke(objectPath) {
let domainObject = null;
if (objectPath) {
domainObject = objectPath[0];
}
this._openmct.objectViews.emit('clearData', domainObject);
}
appliesTo(objectPath) {
if (!objectPath) {
return false;
}
const contextualDomainObject = objectPath[0];
// first check to see if this action applies to this sort of object at all
const appliesToThisObject = this._appliesToObjects.some(type => {
return contextualDomainObject.type === type;
});
if (!appliesToThisObject) {
// we've selected something not applicable
return false;
}
const objectInSelectionPath = inSelectionPath(this._openmct, contextualDomainObject);
if (objectInSelectionPath) {
return true;
} else {
// if this it doesn't match up, check to see if we're in a composition (i.e., layout)
const routerPath = this._openmct.router.path[0];
return routerPath.type === 'layout';
}
}
}

View File

@@ -20,40 +20,22 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Clock from './components/Clock.vue';
import Vue from 'vue';
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';
export default function ClockViewProvider(openmct) {
return {
key: 'clock.view',
name: 'Clock',
cssClass: 'icon-clock',
canView(domainObject) {
return domainObject.type === 'clock';
},
this._openmct = openmct;
this._appliesToObjects = appliesToObjects;
}
invoke(objectPath) {
this._openmct.objectViews.emit('clearData', objectPath[0]);
}
appliesTo(objectPath) {
let contextualDomainObject = objectPath[0];
view: function (domainObject) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
Clock
},
provide: {
openmct,
domainObject
},
template: '<clock />'
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
};
}
};
return this._appliesToObjects.filter(type => contextualDomainObject.type === type).length;
}
}

View File

@@ -22,7 +22,7 @@
define([
'./components/globalClearIndicator.vue',
'./ClearDataAction',
'./clearDataAction',
'vue'
], function (
GlobaClearIndicator,

View File

@@ -1,140 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 ClearDataActionPlugin from '../plugin.js';
import ClearDataAction from '../ClearDataAction.js';
describe('When the Clear Data Plugin is installed,', () => {
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
const goodMockSelectionPath = [[{
context: {
item: {
identifier: {
key: 'apple',
namespace: ''
}
}
}
}]];
const openmct = {
objectViews: mockObjectViews,
indicators: mockIndicatorProvider,
actions: mockActionsProvider,
install: function (plugin) {
plugin(this);
},
selection: {
get: function () {
return goodMockSelectionPath;
}
},
objects: {
areIdsEqual: function (obj1, obj2) {
return true;
}
}
};
const mockObjectPath = [
{
name: 'mockObject1',
type: 'apple'
},
{
name: 'mockObject2',
type: 'banana'
}
];
it('Global Clear Indicator is installed', () => {
openmct.install(ClearDataActionPlugin([]));
expect(mockIndicatorProvider.add).toHaveBeenCalled();
});
it('Clear Data context menu action is installed', () => {
openmct.install(ClearDataActionPlugin([]));
expect(mockActionsProvider.register).toHaveBeenCalled();
});
it('clear data action emits a clearData event when invoked', () => {
const action = new ClearDataAction(openmct);
action.invoke(mockObjectPath);
expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
});
it('clears data on applicable objects', () => {
let action = new ClearDataAction(openmct, ['apple']);
const actionApplies = action.appliesTo(mockObjectPath);
expect(actionApplies).toBe(true);
});
it('does not clear data on inapplicable objects', () => {
let action = new ClearDataAction(openmct, ['pineapple']);
const actionApplies = action.appliesTo(mockObjectPath);
expect(actionApplies).toBe(false);
});
it('does not clear data if not in the selection path and not a layout', () => {
openmct.objects = {
areIdsEqual: function (obj1, obj2) {
return false;
}
};
openmct.router = {
path: [{type: 'not-a-layout'}]
};
let action = new ClearDataAction(openmct, ['apple']);
const actionApplies = action.appliesTo(mockObjectPath);
expect(actionApplies).toBe(false);
});
it('does clear data if not in the selection path and is a layout', () => {
openmct.objects = {
areIdsEqual: function (obj1, obj2) {
return false;
}
};
openmct.router = {
path: [{type: 'layout'}]
};
let action = new ClearDataAction(openmct, ['apple']);
const actionApplies = action.appliesTo(mockObjectPath);
expect(actionApplies).toBe(true);
});
});

View File

@@ -0,0 +1,64 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 ClearDataActionPlugin from '../plugin.js';
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 mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
const openmct = {
objectViews: mockObjectViews,
indicators: mockIndicatorProvider,
actions: mockActionsProvider,
install: function (plugin) {
plugin(this);
}
};
const mockObjectPath = [
{name: 'mockObject1'},
{name: 'mockObject2'}
];
it('Global Clear Indicator is installed', function () {
openmct.install(ClearDataActionPlugin([]));
expect(mockIndicatorProvider.add).toHaveBeenCalled();
});
it('Clear Data context menu action is installed', function () {
openmct.install(ClearDataActionPlugin([]));
expect(mockActionsProvider.register).toHaveBeenCalled();
});
it('clear data action emits a clearData event when invoked', function () {
let action = new ClearDataAction(openmct);
action.invoke(mockObjectPath);
expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
});
});

View File

@@ -1,99 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2021, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div class="l-angular-ov-wrapper">
<div class="u-contents">
<div class="c-clock l-time-display u-style-receiver js-style-receiver">
<div class="c-clock__timezone">
{{ timeZoneAbbr }}
</div>
<div class="c-clock__value">
{{ timeTextValue }}
</div>
<div class="c-clock__ampm">
{{ timeAmPm }}
</div>
</div>
</div>
</div>
</template>
<script>
import moment from 'moment';
import momentTimezone from 'moment-timezone';
export default {
inject: ['openmct', 'domainObject'],
data() {
return {
lastTimestamp: null
};
},
computed: {
configuration() {
return this.domainObject.configuration;
},
baseFormat() {
return this.configuration.baseFormat;
},
use24() {
return this.configuration.use24 === 'clock24';
},
timezone() {
return this.configuration.timezone;
},
timeFormat() {
return this.use24 ? this.baseFormat.replace('hh', "HH") : this.baseFormat;
},
zoneName() {
return momentTimezone.tz.names().includes(this.timezone) ? this.timezone : "UTC";
},
momentTime() {
return this.zoneName ? moment.utc(this.lastTimestamp).tz(this.zoneName) : moment.utc(this.lastTimestamp);
},
timeZoneAbbr() {
return this.momentTime.zoneAbbr();
},
timeTextValue() {
return this.timeFormat && this.momentTime.format(this.timeFormat);
},
timeAmPm() {
return this.use24 ? '' : this.momentTime.format("A");
}
},
mounted() {
const TickerService = this.openmct.$injector.get('tickerService');
this.unlisten = TickerService.listen(this.tick);
},
beforeDestroy() {
if (this.unlisten) {
this.unlisten();
}
},
methods: {
tick(timestamp) {
this.lastTimestamp = timestamp;
}
}
};
</script>

View File

@@ -1,64 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2021, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div class="c-indicator t-indicator-clock icon-clock no-minify c-indicator--not-clickable">
<span class="label c-indicator__label">
{{ timeTextValue }}
</span>
</div>
</template>
<script>
import moment from 'moment';
export default {
inject: ['openmct'],
props: {
indicatorFormat: {
type: String,
required: true
}
},
data() {
return {
timeTextValue: null
};
},
mounted() {
this.openmct.on('start', () => {
const TickerService = this.openmct.$injector.get('tickerService');
this.unlisten = TickerService.listen(this.tick);
});
},
beforeDestroy() {
if (this.unlisten) {
this.unlisten();
}
},
methods: {
tick(timestamp) {
this.timeTextValue = `${moment.utc(timestamp).format(this.indicatorFormat)} UTC`;
}
}
};
</script>

View File

@@ -1,154 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 ClockViewProvider from './ClockViewProvider';
import ClockIndicator from './components/ClockIndicator.vue';
import momentTimezone from 'moment-timezone';
import Vue from 'vue';
export default function ClockPlugin(options) {
return function install(openmct) {
const CLOCK_INDICATOR_FORMAT = 'YYYY/MM/DD HH:mm:ss';
openmct.types.addType('clock', {
name: 'Clock',
description: 'A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.',
creatable: true,
cssClass: 'icon-clock',
initialize: function (domainObject) {
domainObject.configuration = {
baseFormat: 'YYYY/MM/DD hh:mm:ss',
use24: 'clock12',
timezone: 'UTC'
};
},
"form": [
{
"key": "displayFormat",
"name": "Display Format",
control: 'select',
options: [
{
value: 'YYYY/MM/DD hh:mm:ss',
name: 'YYYY/MM/DD hh:mm:ss'
},
{
value: 'YYYY/DDD hh:mm:ss',
name: 'YYYY/DDD hh:mm:ss'
},
{
value: 'hh:mm:ss',
name: 'hh:mm:ss'
}
],
cssClass: 'l-inline',
property: [
'configuration',
'baseFormat'
]
},
{
control: 'select',
options: [
{
value: 'clock12',
name: '12hr'
},
{
value: 'clock24',
name: '24hr'
}
],
cssClass: 'l-inline',
property: [
'configuration',
'use24'
]
},
{
"key": "timezone",
"name": "Timezone",
"control": "autocomplete",
"options": momentTimezone.tz.names(),
property: [
'configuration',
'timezone'
]
}
]
});
openmct.objectViews.addProvider(new ClockViewProvider(openmct));
if (options && options.enableClockIndicator === true) {
const clockIndicator = new Vue ({
components: {
ClockIndicator
},
provide: {
openmct
},
data() {
return {
indicatorFormat: CLOCK_INDICATOR_FORMAT
};
},
template: '<ClockIndicator :indicator-format="indicatorFormat" />'
});
const indicator = {
element: clockIndicator.$mount().$el,
key: 'clock-indicator'
};
openmct.indicators.add(indicator);
}
openmct.objects.addGetInterceptor({
appliesTo: (identifier, domainObject) => {
return domainObject && domainObject.type === 'clock';
},
invoke: (identifier, domainObject) => {
if (domainObject.configuration) {
return domainObject;
}
if (domainObject.clockFormat
&& domainObject.timezone) {
const baseFormat = domainObject.clockFormat[0];
const use24 = domainObject.clockFormat[1];
const timezone = domainObject.timezone;
domainObject.configuration = {
baseFormat,
use24,
timezone
};
openmct.objects.mutate(domainObject, 'configuration', domainObject.configuration);
}
return domainObject;
}
});
};
}

View File

@@ -1,231 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 { createOpenMct, resetApplicationState } from 'utils/testing';
import clockPlugin from './plugin';
import Vue from 'vue';
describe("Clock plugin:", () => {
let openmct;
let clockDefinition;
let element;
let child;
let appHolder;
let clockDomainObject;
function setupClock(enableClockIndicator) {
return new Promise((resolve, reject) => {
clockDomainObject = {
identifier: {
key: 'clock',
namespace: 'test-namespace'
},
type: 'clock'
};
appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
document.body.appendChild(appHolder);
openmct = createOpenMct();
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
openmct.install(clockPlugin({ enableClockIndicator }));
clockDefinition = openmct.types.get('clock').definition;
clockDefinition.initialize(clockDomainObject);
openmct.on('start', resolve);
openmct.start(appHolder);
});
}
describe("Clock view:", () => {
let clockViewProvider;
let clockView;
let clockViewObject;
let mutableClockObject;
beforeEach(async () => {
await setupClock(true);
clockViewObject = {
...clockDomainObject,
id: "test-object",
name: 'Clock',
configuration: {
baseFormat: 'YYYY/MM/DD hh:mm:ss',
use24: 'clock12',
timezone: 'UTC'
}
};
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve(clockViewObject));
spyOn(openmct.objects, 'save').and.returnValue(Promise.resolve(true));
const applicableViews = openmct.objectViews.get(clockViewObject, [clockViewObject]);
clockViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'clock.view');
mutableClockObject = await openmct.objects.getMutable(clockViewObject.identifier);
clockView = clockViewProvider.view(mutableClockObject);
clockView.show(child);
await Vue.nextTick();
});
afterEach(() => {
clockView.destroy();
openmct.objects.destroyMutable(mutableClockObject);
if (appHolder) {
appHolder.remove();
}
return resetApplicationState(openmct);
});
it("has name as Clock", () => {
expect(clockDefinition.name).toEqual('Clock');
});
it("is creatable", () => {
expect(clockDefinition.creatable).toEqual(true);
});
it("provides clock view", () => {
expect(clockViewProvider).toBeDefined();
});
it("renders clock element", () => {
const clockElement = element.querySelectorAll('.c-clock');
expect(clockElement.length).toBe(1);
});
it("renders major elements", () => {
const clockElement = element.querySelector('.c-clock');
const timezone = clockElement.querySelector('.c-clock__timezone');
const time = clockElement.querySelector('.c-clock__value');
const amPm = clockElement.querySelector('.c-clock__ampm');
const hasMajorElements = Boolean(timezone && time && amPm);
expect(hasMajorElements).toBe(true);
});
it("renders time in UTC", () => {
const clockElement = element.querySelector('.c-clock');
const timezone = clockElement.querySelector('.c-clock__timezone').textContent.trim();
expect(timezone).toBe('UTC');
});
it("updates the 24 hour option in the configuration", (done) => {
expect(clockDomainObject.configuration.use24).toBe('clock12');
const new24Option = 'clock24';
openmct.objects.observe(clockViewObject, 'configuration', (changedDomainObject) => {
expect(changedDomainObject.use24).toBe(new24Option);
done();
});
openmct.objects.mutate(clockViewObject, 'configuration.use24', new24Option);
});
it("updates the timezone option in the configuration", (done) => {
expect(clockDomainObject.configuration.timezone).toBe('UTC');
const newZone = 'CST6CDT';
openmct.objects.observe(clockViewObject, 'configuration', (changedDomainObject) => {
expect(changedDomainObject.timezone).toBe(newZone);
done();
});
openmct.objects.mutate(clockViewObject, 'configuration.timezone', newZone);
});
it("updates the time format option in the configuration", (done) => {
expect(clockDomainObject.configuration.baseFormat).toBe('YYYY/MM/DD hh:mm:ss');
const newFormat = 'hh:mm:ss';
openmct.objects.observe(clockViewObject, 'configuration', (changedDomainObject) => {
expect(changedDomainObject.baseFormat).toBe(newFormat);
done();
});
openmct.objects.mutate(clockViewObject, 'configuration.baseFormat', newFormat);
});
});
describe("Clock Indicator view:", () => {
let clockIndicator;
afterEach(() => {
if (clockIndicator) {
clockIndicator.remove();
}
clockIndicator = undefined;
if (appHolder) {
appHolder.remove();
}
return resetApplicationState(openmct);
});
it("doesn't exist", async () => {
await setupClock(false);
clockIndicator = openmct.indicators.indicatorObjects
.find(indicator => indicator.key === 'clock-indicator');
const clockIndicatorMissing = clockIndicator === null || clockIndicator === undefined;
expect(clockIndicatorMissing).toBe(true);
});
it("exists", async () => {
await setupClock(true);
clockIndicator = openmct.indicators.indicatorObjects
.find(indicator => indicator.key === 'clock-indicator').element;
const hasClockIndicator = clockIndicator !== null && clockIndicator !== undefined;
expect(hasClockIndicator).toBe(true);
});
it("contains text", async () => {
await setupClock(true);
clockIndicator = openmct.indicators.indicatorObjects
.find(indicator => indicator.key === 'clock-indicator').element;
const clockIndicatorText = clockIndicator.textContent.trim();
const textIncludesUTC = clockIndicatorText.includes('UTC');
expect(textIncludesUTC).toBe(true);
});
});
});

View File

@@ -273,7 +273,10 @@ export default {
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
(objectPath) => {
this.objectPath = objectPath;
this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath);
this.navigateToPath = '#/browse/' + this.objectPath
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
.reverse()
.join('/');
}
);
},

View File

@@ -297,7 +297,10 @@ export default {
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
(objectPath) => {
this.objectPath = objectPath;
this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath);
this.navigateToPath = '#/browse/' + this.objectPath
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
.reverse()
.join('/');
}
);
},

View File

@@ -140,7 +140,6 @@ import SearchResults from './SearchResults.vue';
import Sidebar from './Sidebar.vue';
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSectionId, setDefaultNotebookPageId } from '../utils/notebook-storage';
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
import { saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from '../utils/notebook-image';
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
import objectUtils from 'objectUtils';
@@ -386,13 +385,9 @@ export default {
const snapshotId = event.dataTransfer.getData('openmct/snapshot/id');
if (snapshotId.length) {
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
this.newEntry(snapshot.embedObject);
this.newEntry(snapshot);
this.snapshotContainer.removeSnapshot(snapshotId);
const namespace = this.domainObject.identifier.namespace;
const notebookImageDomainObject = updateNamespaceOfDomainObject(snapshot.notebookImageDomainObject, namespace);
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
return;
}
@@ -456,9 +451,12 @@ export default {
: undefined;
},
getDefaultNotebookObject() {
const defaultNotebook = getDefaultNotebook();
const oldNotebookStorage = getDefaultNotebook();
if (!oldNotebookStorage) {
return null;
}
return defaultNotebook && this.openmct.objects.get(defaultNotebook.identifier);
return this.openmct.objects.get(oldNotebookStorage.identifier);
},
getLinktoNotebook() {
const objectPath = this.openmct.router.path;

View File

@@ -40,7 +40,7 @@ export default {
components: {
PopupMenu
},
inject: ['openmct', 'snapshotContainer'],
inject: ['openmct'],
props: {
embed: {
type: Object,
@@ -48,12 +48,6 @@ export default {
return {};
}
},
isSnapshotContainer: {
type: Boolean,
default() {
return false;
}
},
removeActionString: {
type: String,
default() {
@@ -141,14 +135,6 @@ export default {
return;
}
if (this.isSnapshotContainer) {
const snapshot = this.snapshotContainer.getSnapshot(this.embed.id);
const fullSizeImageURL = snapshot.notebookImageDomainObject.configuration.fullSizeImageURL;
painterroInstance.show(fullSizeImageURL);
return;
}
this.openmct.objects.get(fullSizeImageObjectIdentifier)
.then(object => {
painterroInstance.show(object.configuration.fullSizeImageURL);
@@ -204,14 +190,6 @@ export default {
return;
}
if (this.isSnapshotContainer) {
const snapshot = this.snapshotContainer.getSnapshot(this.embed.id);
const fullSizeImageURL = snapshot.notebookImageDomainObject.configuration.fullSizeImageURL;
this.openSnapshotOverlay(fullSizeImageURL);
return;
}
this.openmct.objects.get(fullSizeImageObjectIdentifier)
.then(object => {
this.openSnapshotOverlay(object.configuration.fullSizeImageURL);
@@ -281,20 +259,8 @@ export default {
updateSnapshot(snapshotObject) {
this.embed.snapshot.thumbnailImage = snapshotObject.thumbnailImage;
this.updateNotebookImageDomainObjectSnapshot(snapshotObject);
updateNotebookImageDomainObject(this.openmct, this.embed.snapshot.fullSizeImageObjectIdentifier, snapshotObject.fullSizeImage);
this.updateEmbed(this.embed);
},
updateNotebookImageDomainObjectSnapshot(snapshotObject) {
if (this.isSnapshotContainer) {
const snapshot = this.snapshotContainer.getSnapshot(this.embed.id);
snapshot.embedObject.snapshot.thumbnailImage = snapshotObject.thumbnailImage;
snapshot.notebookImageDomainObject.configuration.fullSizeImageURL = snapshotObject.fullSizeImage.src;
this.snapshotContainer.updateSnapshot(snapshot);
} else {
updateNotebookImageDomainObject(this.openmct, this.embed.snapshot.fullSizeImageObjectIdentifier, snapshotObject.fullSizeImage);
}
}
}
};

View File

@@ -102,11 +102,9 @@
<script>
import NotebookEmbed from './NotebookEmbed.vue';
import TextHighlight from '../../../utils/textHighlight/TextHighlight.vue';
import { createNewEmbed } from '../utils/notebook-entries';
import { saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from '../utils/notebook-image';
import Moment from 'moment';
import TextHighlight from '../../../utils/textHighlight/TextHighlight.vue';
export default {
components: {
@@ -212,12 +210,8 @@ export default {
const snapshotId = $event.dataTransfer.getData('openmct/snapshot/id');
if (snapshotId.length) {
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
this.entry.embeds.push(snapshot.embedObject);
this.snapshotContainer.removeSnapshot(snapshotId);
const namespace = this.domainObject.identifier.namespace;
const notebookImageDomainObject = updateNamespaceOfDomainObject(snapshot.notebookImageDomainObject, namespace);
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
this.entry.embeds.push(snapshot);
} else {
const data = $event.dataTransfer.getData('openmct/domain-object-path');
const objectPath = JSON.parse(data);

View File

@@ -17,26 +17,19 @@
<script>
import Snapshot from '../snapshot';
import { getDefaultNotebook, validateNotebookStorageObject } from '../utils/notebook-storage';
import { getDefaultNotebook, getNotebookSectionAndPage, validateNotebookStorageObject } from '../utils/notebook-storage';
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
import { getMenuItems } from '../utils/notebook-snapshot-menu';
export default {
inject: ['openmct'],
props: {
currentView: {
type: Object,
default() {
return {};
}
},
domainObject: {
type: Object,
default() {
return {};
}
},
isPreview: {
ignoreLink: {
type: Boolean,
default() {
return false;
@@ -57,40 +50,51 @@ export default {
},
mounted() {
validateNotebookStorageObject();
this.getDefaultNotebookObject();
this.notebookSnapshot = new Snapshot(this.openmct);
this.setDefaultNotebookStatus();
},
methods: {
getPreviewObjectLink() {
const relativePath = this.openmct.objects.getRelativePath(this.objectPath);
const urlParams = this.openmct.router.getParams();
urlParams.view = this.currentView.key;
getDefaultNotebookObject() {
const defaultNotebook = getDefaultNotebook();
const urlParamsString = Object.entries(urlParams)
.map(([key, value]) => `${key}=${value}`)
.join('&');
return `#/browse/${relativePath}?${urlParamsString}`;
return defaultNotebook && this.openmct.objects.get(defaultNotebook.identifier);
},
async showMenu(event) {
const menuItemOptions = {
default: {
cssClass: 'icon-notebook',
name: `Save to Notebook`,
onItemClicked: () => this.snapshot(NOTEBOOK_DEFAULT, event.target)
},
snapshot: {
cssClass: 'icon-camera',
name: 'Save to Notebook Snapshots',
onItemClicked: () => this.snapshot(NOTEBOOK_SNAPSHOT, event.target)
}
};
const notebookTypes = await getMenuItems(this.openmct, menuItemOptions);
const notebookTypes = [];
const elementBoundingClientRect = this.$el.getBoundingClientRect();
const x = elementBoundingClientRect.x;
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
const defaultNotebookObject = await this.getDefaultNotebookObject();
if (defaultNotebookObject) {
const defaultNotebook = getDefaultNotebook();
const { section, page } = getNotebookSectionAndPage(defaultNotebookObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
if (section && page) {
const name = defaultNotebookObject.name;
const sectionName = section.name;
const pageName = page.name;
const defaultPath = `${name} - ${sectionName} - ${pageName}`;
notebookTypes.push({
cssClass: 'icon-notebook',
name: `Save to Notebook ${defaultPath}`,
onItemClicked: () => {
return this.snapshot(NOTEBOOK_DEFAULT, event.target);
}
});
}
}
notebookTypes.push({
cssClass: 'icon-camera',
name: 'Save to Notebook Snapshots',
onItemClicked: () => {
return this.snapshot(NOTEBOOK_SNAPSHOT, event.target);
}
});
this.openmct.menus.showMenu(x, y, notebookTypes);
},
snapshot(notebookType, target) {
@@ -98,12 +102,15 @@ export default {
const wrapper = target && target.closest('.js-notebook-snapshot-item-wrapper')
|| document;
const element = wrapper.querySelector('.js-notebook-snapshot-item');
const bounds = this.openmct.time.bounds();
const link = !this.ignoreLink
? window.location.hash
: null;
const objectPath = this.objectPath || this.openmct.router.path;
const link = this.isPreview
? this.getPreviewObjectLink()
: window.location.hash;
const snapshotMeta = {
bounds: this.openmct.time.bounds(),
bounds,
link,
objectPath,
openmct: this.openmct

View File

@@ -27,15 +27,15 @@
</div><!-- closes l-browse-bar -->
<div class="c-snapshots">
<span v-for="snapshot in snapshots"
:key="snapshot.embedObject.id"
:key="snapshot.id"
draggable="true"
@dragstart="startEmbedDrag(snapshot, $event)"
>
<NotebookEmbed ref="notebookEmbed"
:key="snapshot.embedObject.id"
:embed="snapshot.embedObject"
:is-snapshot-container="true"
:key="snapshot.id"
:embed="snapshot"
:remove-action-string="'Delete Snapshot'"
@updateEmbed="updateSnapshot"
@removeEmbed="removeSnapshot"
/>
</span>
@@ -119,8 +119,11 @@ export default {
this.snapshots = this.snapshotContainer.getSnapshots();
},
startEmbedDrag(snapshot, event) {
event.dataTransfer.setData('text/plain', snapshot.embedObject.id);
event.dataTransfer.setData('openmct/snapshot/id', snapshot.embedObject.id);
event.dataTransfer.setData('text/plain', snapshot.id);
event.dataTransfer.setData('openmct/snapshot/id', snapshot.id);
},
updateSnapshot(snapshot) {
this.snapshotContainer.updateSnapshot(snapshot);
}
}
};

View File

@@ -18,18 +18,13 @@ export default class SnapshotContainer extends EventEmitter {
return SnapshotContainer.instance;
}
addSnapshot(notebookImageDomainObject, embedObject) {
addSnapshot(embedObject) {
const snapshots = this.getSnapshots();
if (snapshots.length >= NOTEBOOK_SNAPSHOT_MAX_COUNT) {
snapshots.pop();
}
const snapshotObject = {
notebookImageDomainObject,
embedObject
};
snapshots.unshift(snapshotObject);
snapshots.unshift(embedObject);
return this.saveSnapshots(snapshots);
}
@@ -37,7 +32,7 @@ export default class SnapshotContainer extends EventEmitter {
getSnapshot(id) {
const snapshots = this.getSnapshots();
return snapshots.find(s => s.embedObject.id === id);
return snapshots.find(s => s.id === id);
}
getSnapshots() {
@@ -52,7 +47,7 @@ export default class SnapshotContainer extends EventEmitter {
}
const snapshots = this.getSnapshots();
const filteredsnapshots = snapshots.filter(snapshot => snapshot.embedObject.id !== id);
const filteredsnapshots = snapshots.filter(snapshot => snapshot.id !== id);
return this.saveSnapshots(filteredsnapshots);
}
@@ -78,7 +73,7 @@ export default class SnapshotContainer extends EventEmitter {
updateSnapshot(snapshot) {
const snapshots = this.getSnapshots();
const updatedSnapshots = snapshots.map(s => {
return s.embedObject.id === snapshot.embedObject.id
return s.id === snapshot.id
? snapshot
: s;
});

View File

@@ -1,7 +1,7 @@
import { addNotebookEntry, createNewEmbed } from './utils/notebook-entries';
import { getDefaultNotebook, getNotebookSectionAndPage, getDefaultNotebookLink, setDefaultNotebook } from './utils/notebook-storage';
import { NOTEBOOK_DEFAULT } from '@/plugins/notebook/notebook-constants';
import { createNotebookImageDomainObject, saveNotebookImageDomainObject, updateNamespaceOfDomainObject, DEFAULT_SIZE } from './utils/notebook-image';
import { createNotebookImageDomainObject, DEFAULT_SIZE } from './utils/notebook-image';
import SnapshotContainer from './snapshot-container';
import ImageExporter from '../../exporters/ImageExporter';
@@ -35,28 +35,29 @@ export default class Snapshot {
* @private
*/
_saveSnapShot(notebookType, fullSizeImageURL, thumbnailImageURL, snapshotMeta) {
const object = createNotebookImageDomainObject(fullSizeImageURL);
const thumbnailImage = { src: thumbnailImageURL || '' };
const snapshot = {
fullSizeImageObjectIdentifier: object.identifier,
thumbnailImage
};
const embed = createNewEmbed(snapshotMeta, snapshot);
if (notebookType === NOTEBOOK_DEFAULT) {
const notebookStorage = getDefaultNotebook();
createNotebookImageDomainObject(this.openmct, fullSizeImageURL)
.then(object => {
const thumbnailImage = { src: thumbnailImageURL || '' };
const snapshot = {
fullSizeImageObjectIdentifier: object.identifier,
thumbnailImage
};
const embed = createNewEmbed(snapshotMeta, snapshot);
if (notebookType === NOTEBOOK_DEFAULT) {
this._saveToDefaultNoteBook(embed);
this._saveToDefaultNoteBook(notebookStorage, embed);
const notebookImageDomainObject = updateNamespaceOfDomainObject(object, notebookStorage.identifier.namespace);
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
} else {
this._saveToNotebookSnapshots(object, embed);
}
return;
}
this._saveToNotebookSnapshots(embed);
});
}
/**
* @private
*/
_saveToDefaultNoteBook(notebookStorage, embed) {
_saveToDefaultNoteBook(embed) {
const notebookStorage = getDefaultNotebook();
this.openmct.objects.get(notebookStorage.identifier)
.then(async (domainObject) => {
addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
@@ -84,22 +85,19 @@ export default class Snapshot {
/**
* @private
*/
_saveToNotebookSnapshots(notebookImageDomainObject, embed) {
this.snapshotContainer.addSnapshot(notebookImageDomainObject, embed);
_saveToNotebookSnapshots(embed) {
this.snapshotContainer.addSnapshot(embed);
}
_showNotification(msg, url) {
const options = {
autoDismissTimeout: 30000
};
if (!this.openmct.editor.isEditing()) {
options.link = {
autoDismissTimeout: 30000,
link: {
cssClass: '',
text: 'click to view',
onClick: this._navigateToNotebook(url)
};
}
}
};
this.openmct.notifications.info(msg, options);
}
@@ -110,8 +108,7 @@ export default class Snapshot {
}
return () => {
const path = window.location.href.split('#');
window.location.href = path[0] + url;
window.location.href = window.location.origin + url;
};
}
}

View File

@@ -5,14 +5,14 @@ export const DEFAULT_SIZE = {
height: 30
};
export function createNotebookImageDomainObject(fullSizeImageURL) {
export function createNotebookImageDomainObject(openmct, fullSizeImageURL) {
const identifier = {
key: uuid(),
namespace: ''
};
const viewType = 'notebookSnapshotImage';
return {
const object = {
name: 'Notebook Snapshot Image',
type: viewType,
identifier,
@@ -20,6 +20,21 @@ export function createNotebookImageDomainObject(fullSizeImageURL) {
fullSizeImageURL
}
};
return new Promise((resolve, reject) => {
openmct.objects.save(object)
.then(result => {
if (result) {
resolve(object);
}
reject();
})
.catch(e => {
console.error(e);
reject();
});
});
}
export function getThumbnailURLFromCanvas(canvas, size = DEFAULT_SIZE) {
@@ -52,23 +67,6 @@ export function getThumbnailURLFromimageUrl(imageUrl, size = DEFAULT_SIZE) {
});
}
export function saveNotebookImageDomainObject(openmct, object) {
return new Promise((resolve, reject) => {
openmct.objects.save(object)
.then(result => {
if (result) {
resolve(object);
} else {
reject();
}
})
.catch(e => {
console.error(e);
reject();
});
});
}
export function updateNotebookImageDomainObject(openmct, identifier, fullSizeImage) {
openmct.objects.get(identifier)
.then(domainObject => {
@@ -78,9 +76,3 @@ export function updateNotebookImageDomainObject(openmct, identifier, fullSizeIma
openmct.objects.mutate(domainObject, 'configuration', configuration);
});
}
export function updateNamespaceOfDomainObject(object, namespace) {
object.identifier.namespace = namespace;
return object;
}

View File

@@ -1,18 +1,16 @@
import { createNotebookImageDomainObject, getThumbnailURLFromimageUrl, saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from './notebook-image';
import { createNotebookImageDomainObject, getThumbnailURLFromimageUrl } from './notebook-image';
import { mutateObject } from './notebook-entries';
const IMAGE_MIGRATION_VER = "v1";
export function notebookImageMigration(openmct, domainObject) {
const configuration = domainObject.configuration;
const notebookEntries = configuration.entries;
const imageMigrationVer = configuration.imageMigrationVer;
if (imageMigrationVer && imageMigrationVer === IMAGE_MIGRATION_VER) {
if (imageMigrationVer && imageMigrationVer === 'v1') {
return;
}
configuration.imageMigrationVer = IMAGE_MIGRATION_VER;
configuration.imageMigrationVer = 'v1';
// to avoid muliple notebookImageMigration calls updating images.
mutateObject(openmct, domainObject, 'configuration', configuration);
@@ -29,16 +27,14 @@ export function notebookImageMigration(openmct, domainObject) {
const fullSizeImageURL = snapshot.src;
if (fullSizeImageURL) {
const thumbnailImageURL = await getThumbnailURLFromimageUrl(fullSizeImageURL);
const object = createNotebookImageDomainObject(fullSizeImageURL);
const notebookImageDomainObject = updateNamespaceOfDomainObject(object, domainObject.identifier.namespace);
const notebookImageDomainObject = await createNotebookImageDomainObject(openmct, fullSizeImageURL);
embed.snapshot = {
fullSizeImageObjectIdentifier: notebookImageDomainObject.identifier,
thumbnailImage: { src: thumbnailImageURL || '' }
};
mutateObject(openmct, domainObject, 'configuration.entries', notebookEntries);
saveNotebookImageDomainObject(openmct, notebookImageDomainObject);
}
});
});

View File

@@ -1,31 +0,0 @@
import { getDefaultNotebook, getNotebookSectionAndPage } from './notebook-storage';
export async function getMenuItems(openmct, menuItemOptions) {
const notebookTypes = [];
const defaultNotebook = getDefaultNotebook();
const defaultNotebookObject = defaultNotebook && await openmct.objects.get(defaultNotebook.identifier);
if (defaultNotebookObject) {
const { section, page } = getNotebookSectionAndPage(defaultNotebookObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
if (section && page) {
const name = defaultNotebookObject.name;
const sectionName = section.name;
const pageName = page.name;
const defaultPath = `${name} - ${sectionName} - ${pageName}`;
notebookTypes.push({
cssClass: menuItemOptions.default.cssClass,
name: `${menuItemOptions.default.name} ${defaultPath}`,
onItemClicked: menuItemOptions.default.onItemClicked
});
}
}
notebookTypes.push({
cssClass: menuItemOptions.snapshot.cssClass,
name: menuItemOptions.snapshot.name,
onItemClicked: menuItemOptions.snapshot.onItemClicked
});
return notebookTypes;
}

View File

@@ -71,7 +71,11 @@ export async function getDefaultNotebookLink(openmct, domainObject = null) {
}
const path = await openmct.objects.getOriginalPath(domainObject.identifier)
.then(openmct.objects.getRelativePath);
.then(objectPath => objectPath
.map(o => o && openmct.objects.makeKeyString(o.identifier))
.reverse()
.join('/')
);
const { defaultPageId, defaultSectionId } = getDefaultNotebook();
return `#/browse/${path}?sectionId=${defaultSectionId}&pageId=${defaultPageId}`;

View File

@@ -393,31 +393,12 @@ export default {
});
},
clearSeries() {
clearData() {
this.config.series.forEach(function (series) {
series.reset();
});
},
compositionPathContainsId(domainObjectToClear) {
return domainObjectToClear.composition.some((compositionIdentifier) => {
return this.openmct.objects.areIdsEqual(compositionIdentifier, this.domainObject.identifier);
});
},
clearData(domainObjectToClear) {
// If we don't have an object to clear (global), or the IDs are equal, just clear the data.
// If we have an object to clear, but the IDs don't match, we need to check the composition
// of the object we've been asked to clear to see if it contains the id we're looking for.
// This happens with stacked plots for example.
// If we find the ID, clear the plot.
if (!domainObjectToClear
|| this.openmct.objects.areIdsEqual(domainObjectToClear.identifier, this.domainObject.identifier)
|| this.compositionPathContainsId(domainObjectToClear)) {
this.clearSeries();
}
},
setDisplayRange(series, xKey) {
if (this.config.series.length !== 1) {
return;
@@ -1029,8 +1010,7 @@ export default {
this.$emit('statusUpdated', status);
},
handleWindowResize() {
if (this.$parent.$refs.plotWrapper
&& (this.offsetWidth !== this.$parent.$refs.plotWrapper.offsetWidth)) {
if (this.offsetWidth !== this.$parent.$refs.plotWrapper.offsetWidth) {
this.offsetWidth = this.$parent.$refs.plotWrapper.offsetWidth;
this.config.series.models.forEach(this.loadSeriesData, this);
}

View File

@@ -69,7 +69,6 @@ define([
'./CouchDBSearchFolder/plugin',
'./timeline/plugin',
'./hyperlink/plugin',
'./clock/plugin',
'./DeviceClassifier/plugin'
], function (
_,
@@ -120,7 +119,6 @@ define([
CouchDBSearchFolder,
Timeline,
Hyperlink,
Clock,
DeviceClassifier
) {
const bundleMap = {
@@ -225,7 +223,6 @@ define([
plugins.CouchDBSearchFolder = CouchDBSearchFolder.default;
plugins.Timeline = Timeline.default;
plugins.Hyperlink = Hyperlink.default;
plugins.Clock = Clock.default;
plugins.DeviceClassifier = DeviceClassifier.default;
return plugins;

View File

@@ -153,7 +153,6 @@ define([
this.telemetryCollections[keyString].on('remove', telemetryRemover);
this.telemetryCollections[keyString].on('add', telemetryProcessor);
this.telemetryCollections[keyString].on('clear', this.tableRows.clear);
this.telemetryCollections[keyString].load();
this.decrementOutstandingRequests();

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Preview from '@/ui/preview/Preview.vue';
import PreviewHeader from '@/ui/preview/preview-header.vue';
import Vue from 'vue';
@@ -46,7 +46,7 @@ export default class ViewLargeAction {
throw new Error(message);
}
this._expand(objectPath, childElement);
this._expand(objectPath, childElement, view);
}
appliesTo(objectPath, view = {}) {
@@ -58,29 +58,49 @@ export default class ViewLargeAction {
return viewLargeAction;
}
_expand(objectPath, childElement) {
_expand(objectPath, childElement, view) {
const parentElement = childElement.parentElement;
this.overlay = this.openmct.overlays.overlay({
element: this._getPreview(objectPath),
element: this._getOverlayElement(objectPath, childElement, view),
size: 'large',
autoHide: false,
onDestroy() {
parentElement.append(childElement);
}
});
}
_getPreview(objectPath) {
_getOverlayElement(objectPath, childElement, view) {
const fragment = new DocumentFragment();
const header = this._getPreviewHeader(objectPath, view);
fragment.append(header);
const wrapper = document.createElement('div');
wrapper.classList.add('l-preview-window__object-view');
wrapper.append(childElement);
fragment.append(wrapper);
return fragment;
}
_getPreviewHeader(objectPath, view) {
const domainObject = objectPath[0];
const actionCollection = this.openmct.actions.getActionsCollection(objectPath, view);
const preview = new Vue({
components: {
Preview
PreviewHeader
},
provide: {
openmct: this.openmct,
objectPath
objectPath: this.objectPath
},
template: '<Preview></Preview>'
data() {
return {
domainObject,
actionCollection
};
},
template: '<PreviewHeader :actionCollection="actionCollection" :domainObject="domainObject" :hideViewSwitcher="true" :showNotebookMenuSwitcher="true"></PreviewHeader>'
});
return preview.$mount().$el;

View File

@@ -39,7 +39,13 @@ export function paramsToArray(openmct) {
}
export function identifierToString(openmct, objectPath) {
return '#/browse/' + openmct.objects.getRelativePath(objectPath);
let identifier = '#/browse/' + objectPath.map(function (o) {
return o && openmct.objects.makeKeyString(o.identifier);
})
.reverse()
.join('/');
return identifier;
}
export default function objectPathToUrl(openmct, objectPath) {

View File

@@ -53,7 +53,7 @@
class="l-shell__pane-tree"
handle="after"
label="Browse"
hide-param="hideTree"
collapsable
@start-resizing="onStartResizing"
@end-resizing="onEndResizing"
>
@@ -104,7 +104,7 @@
class="l-shell__pane-inspector l-pane--holds-multipane"
handle="before"
label="Inspect"
hide-param="hideInspector"
collapsable
@start-resizing="onStartResizing"
@end-resizing="onEndResizing"
>

View File

@@ -1,171 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 {
createOpenMct,
resetApplicationState
} from 'utils/testing';
import Vue from 'vue';
import Layout from './Layout.vue';
describe('Open MCT Layout:', () => {
let openmct;
let element;
let components;
beforeEach((done) => {
openmct = createOpenMct();
openmct.on('start', done);
// to silence error from BrowseBar.vue
spyOn(openmct.objectViews, 'get')
.and.callFake(() => []);
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState(openmct);
});
describe('the pane:', () => {
it('is displayed on layout load', async () => {
await createLayout();
await Vue.nextTick();
Object.entries(components).forEach(([name, component]) => {
expect(
component.pane
).toBeTruthy();
expect(
isCollapsed(component.pane)
).toBeFalse();
});
});
it('is collapsed on layout load if specified by a hide param', async () => {
setHideParams();
await createLayout();
await Vue.nextTick();
Object.entries(components).forEach(([name, component]) => {
expect(
isCollapsed(component.pane)
).toBeTrue();
});
});
it('on toggle collapses if expanded', async () => {
await createLayout();
toggleCollapseButtons();
await Vue.nextTick();
Object.entries(components).forEach(([name, component]) => {
expect(
openmct.router.getSearchParam(component.param)
).toEqual('true');
expect(
isCollapsed(component.pane)
).toBeTrue();
});
});
it('on toggle expands if collapsed', async () => {
setHideParams();
await createLayout();
toggleExpandButtons();
await Vue.nextTick();
Object.entries(components).forEach(([name, component]) => {
expect(
openmct.router.getSearchParam(component.param)
).not.toEqual('true');
expect(
isCollapsed(component.pane)
).toBeFalse();
});
});
});
async function createLayout() {
const el = document.createElement('div');
const child = document.createElement('div');
el.appendChild(child);
element = await new Vue({
el,
components: {
Layout
},
provide: {
openmct
},
template: `<Layout ref="layout"/>`
}).$mount().$el;
setComponents();
}
function setComponents() {
components = {
tree: {
param: 'hideTree',
pane: element.querySelector('.l-shell__pane-tree'),
collapseButton: element.querySelector('.l-shell__pane-tree .l-pane__collapse-button'),
expandButton: element.querySelector('.l-shell__pane-tree .l-pane__expand-button')
},
inspector: {
param: 'hideInspector',
pane: element.querySelector('.l-shell__pane-inspector'),
collapseButton: element.querySelector('.l-shell__pane-inspector .l-pane__collapse-button'),
expandButton: element.querySelector('.l-shell__pane-inspector .l-pane__expand-button')
}
};
}
function isCollapsed(el) {
return el.classList.contains('l-pane--collapsed');
}
function setHideParams() {
Object.entries(components).forEach(([name, component]) => {
openmct.router.setSearchParam(component.param, true);
});
}
function toggleCollapseButtons() {
Object.entries(components).forEach(([name, component]) => {
component.collapseButton.click();
});
}
function toggleExpandButtons() {
Object.entries(components).forEach(([name, component]) => {
component.expandButton.click();
});
}
});

View File

@@ -41,6 +41,10 @@
<script>
const COLLAPSE_THRESHOLD_PX = 40;
const HIDE_TREE_PARAM = 'hideTree';
const HIDE_INSPECTOR_PARAM = 'hideInspector';
const PANE_INSPECTOR = 'Inspect';
const PANE_TREE = 'Browse';
export default {
inject: ['openmct'],
@@ -52,11 +56,11 @@ export default {
return ['', 'before', 'after'].indexOf(value) !== -1;
}
},
label: {
type: String,
default: ''
collapsable: {
type: Boolean,
default: false
},
hideParam: {
label: {
type: String,
default: ''
}
@@ -67,11 +71,6 @@ export default {
resizing: false
};
},
computed: {
collapsable() {
return this.hideParam && this.hideParam.length;
}
},
beforeMount() {
this.type = this.$parent.type;
this.styleProp = (this.type === 'horizontal') ? 'width' : 'height';
@@ -79,25 +78,39 @@ export default {
async mounted() {
await this.$nextTick();
// Hide tree and/or inspector pane if specified in URL
if (this.collapsable) {
this.handleHideUrl();
}
this.handleHideUrl();
this.openmct.router.on('change:params', this.handleHideUrl);
},
beforeDestroy() {
this.openmct.router.off('change:params', this.handleHideUrl);
},
methods: {
toggleCollapse: function (e) {
let target = this.label === PANE_TREE ? HIDE_TREE_PARAM : HIDE_INSPECTOR_PARAM;
this.collapsed = !this.collapsed;
if (this.collapsed) {
this.handleExpand();
this.removeHideParam(this.hideParam);
} else {
this.handleCollapse();
this.addHideParam(this.hideParam);
this.addHideParam(target);
} else {
this.handleExpand();
this.removeHideParam(target);
}
},
handleHideUrl: function () {
const hideParam = this.openmct.router.getSearchParam(this.hideParam);
if (!this.collapsable) {
return;
}
if (hideParam === 'true') {
let hideTreeParam = this.openmct.router.getSearchParam(HIDE_TREE_PARAM);
let hideInspectorParam = this.openmct.router.getSearchParam(HIDE_INSPECTOR_PARAM);
let hideTree = hideTreeParam === 'true' && this.label === PANE_TREE;
let hideInspector = hideInspectorParam === 'true' && this.label === PANE_INSPECTOR;
if (hideTree || hideInspector) {
this.collapsed = true;
this.handleCollapse();
} else {
this.collapsed = false;
this.handleExpand();
}
},
addHideParam: function (target) {
@@ -109,13 +122,11 @@ export default {
handleCollapse: function () {
this.currentSize = (this.dragCollapse === true) ? this.initial : this.$el.style[this.styleProp];
this.$el.style[this.styleProp] = '';
this.collapsed = true;
},
handleExpand: function () {
this.$el.style[this.styleProp] = this.currentSize;
delete this.currentSize;
delete this.dragCollapse;
this.collapsed = false;
},
trackSize: function () {
if (!this.dragCollapse === true) {

90
src/ui/layout/paneSpec.js Normal file
View File

@@ -0,0 +1,90 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 {
createOpenMct,
resetApplicationState
} from 'utils/testing';
describe("the pane", () => {
let openmct;
let appHolder;
let element;
let child;
let resolveFunction;
beforeEach((done) => {
openmct = createOpenMct();
appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalTimeSystem());
openmct.install(openmct.plugins.UTCTimeSystem());
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
openmct.on('start', done);
openmct.start(appHolder);
document.body.append(appHolder);
});
afterEach(() => {
return resetApplicationState(openmct);
});
it('toggling tree will toggle tree hide params', (done) => {
document.querySelector('.l-shell__pane-tree .l-pane__collapse-button').click();
expect(openmct.router.getSearchParam('hideTree')).toBe('true');
done();
});
it('tree pane collapses when adding hide tree param in URL', () => {
openmct.router.setSearchParam('hideTree', 'true');
expect(document.querySelector('.l-shell__pane-tree.l-pane--collapsed')).toBeDefined();
});
it('inspector pane collapses when adding hide inspector param in URL', () => {
openmct.router.setSearchParam('hideInspector', 'true');
expect(document.querySelector('.l-shell__pane-inspector.l-pane--collapsed')).toBeDefined();
});
it('toggle inspector pane will toggle inspector hide param', (done) => {
// There's a short delay on addubg the param.
resolveFunction = () => {
setTimeout(() => {
expect(openmct.router.getSearchParam('hideInspector')).toBe('true');
done();
}, 500);
};
openmct.router.on('change:params', resolveFunction);
document.querySelector('.l-shell__pane-inspector .l-pane__collapse-button').click();
});
});

View File

@@ -59,13 +59,12 @@ export default {
},
mounted() {
this.views = this.openmct.objectViews.get(this.domainObject, this.objectPath).map((view) => {
view.onItemClicked = () => {
view.callBack = () => {
return this.setView(view);
};
return view;
});
this.setView(this.views[0]);
},
beforeDestroy() {

View File

@@ -60,7 +60,6 @@ export default class PreviewAction {
let overlay = this._openmct.overlays.overlay({
element: preview.$el,
size: 'large',
autoHide: false,
buttons: [
{
label: 'Done',

View File

@@ -18,12 +18,6 @@
:views="views"
:current-view="currentView"
/>
<NotebookMenuSwitcher :domain-object="domainObject"
:object-path="objectPath"
:is-preview="true"
:current-view="currentView"
class="c-notebook-snapshot-menubutton"
/>
<div class="l-browse-bar__actions">
<button
v-for="(item, index) in statusBarItems"
@@ -44,9 +38,7 @@
</template>
<script>
import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue';
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
const HIDDEN_ACTIONS = [
'remove',
'move',
@@ -56,12 +48,10 @@ const HIDDEN_ACTIONS = [
export default {
components: {
NotebookMenuSwitcher,
ViewSwitcher
},
inject: [
'openmct',
'objectPath'
'openmct'
],
props: {
currentView: {
@@ -153,7 +143,6 @@ export default {
showMenuItems(event) {
let sortedActions = this.openmct.actions._groupAndSortActions(this.menuActionItems);
const menuItems = this.openmct.menus.actionsToMenuItems(sortedActions, this.actionCollection.objectPath, this.actionCollection.view);
const visibleMenuItems = this.filterHiddenItems(menuItems);
this.openmct.menus.showMenu(event.x, event.y, visibleMenuItems);
}