Compare commits

..

2 Commits

Author SHA1 Message Date
Andrew Henry
d026a67ac0 Update on sort 2019-02-26 13:08:41 -08:00
Andrew Henry
78018628ce Remove slice operation 2019-02-26 09:16:37 -08:00
296 changed files with 14480 additions and 9435 deletions

View File

@@ -27,7 +27,7 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<title></title>
<script src="dist/openmct.js"></script>
<link rel="stylesheet" href="dist/styles/openmct.css">
<link rel="stylesheet" href="dist/openmct.css">
<link rel="icon" type="image/png" href="dist/favicons/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="dist/favicons/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="dist/favicons/favicon-16x16.png" sizes="16x16">
@@ -54,9 +54,6 @@
openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel"
}));
openmct.install(openmct.plugins.DisplayLayout({
showAsView: ['summary-widget', 'example.imagery']
}));
openmct.install(openmct.plugins.Conductor({
menuOptions: [
{
@@ -84,9 +81,6 @@
openmct.install(openmct.plugins.Tabs());
openmct.install(openmct.plugins.FlexibleLayout());
openmct.install(openmct.plugins.LADTable());
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
openmct.install(openmct.plugins.ObjectMigration());
openmct.install(openmct.plugins.GoToOriginalAction());
openmct.start();
</script>
</html>

View File

@@ -25,7 +25,7 @@
"eventemitter3": "^1.2.0",
"exports-loader": "^0.7.0",
"express": "^4.13.1",
"fast-sass-loader": "1.4.6",
"fast-sass-loader": "^1.4.5",
"file-loader": "^1.1.11",
"file-saver": "^1.3.8",
"git-rev-sync": "^1.4.0",
@@ -55,7 +55,7 @@
"node-bourbon": "^4.2.3",
"node-sass": "^4.9.2",
"painterro": "^0.2.65",
"printj": "^1.2.1",
"printj": "^1.1.0",
"raw-loader": "^0.5.1",
"request": "^2.69.0",
"split": "^1.0.0",

View File

@@ -31,6 +31,7 @@ define([
"./src/navigation/NavigateAction",
"./src/navigation/OrphanNavigationHandler",
"./src/windowing/NewTabAction",
"./src/windowing/WindowTitler",
"./res/templates/browse.html",
"./res/templates/browse-object.html",
"./res/templates/browse/object-header.html",
@@ -51,6 +52,7 @@ define([
NavigateAction,
OrphanNavigationHandler,
NewTabAction,
WindowTitler,
browseTemplate,
browseObjectTemplate,
objectHeaderTemplate,
@@ -224,6 +226,14 @@ define([
}
],
"runs": [
{
"implementation": WindowTitler,
"depends": [
"navigationService",
"$rootScope",
"$document"
]
},
{
"implementation": OrphanNavigationHandler,
"depends": [

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, United States Government
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -20,26 +20,32 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
let brandingOptions = {};
define(
[],
function () {
/**
* @typedef {Object} BrandingOptions
* @memberOf openmct/branding
* @property {string} smallLogoImage URL to the image to use as the applications logo.
* This logo will appear on every screen and when clicked will launch the about dialog.
* @property {string} aboutHtml Custom content for the about screen. When defined the
* supplied content will be inserted at the start of the about dialog, and the default
* Open MCT splash logo will be suppressed.
*/
/**
* Updates the title of the current window to reflect the name
* of the currently navigated-to domain object.
* @memberof platform/commonUI/browse
* @constructor
*/
function WindowTitler(navigationService, $rootScope, $document) {
// Look up name of the navigated domain object...
function getNavigatedObjectName() {
var navigatedObject = navigationService.getNavigation();
return navigatedObject && navigatedObject.getModel().name;
}
/**
* Set branding options for the application. These will override certain visual elements
* of the application and allow for customization of the application.
* @param {BrandingOptions} options
*/
export default function Branding(options) {
if (arguments.length === 1) {
brandingOptions = options;
// Set the window title...
function setTitle(name) {
$document[0].title = name;
}
// Watch the former, and invoke the latter
$rootScope.$watch(getNavigatedObjectName, setTitle);
}
return WindowTitler;
}
return brandingOptions;
}
);

View File

@@ -0,0 +1,78 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* WindowTitlerSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../../src/windowing/WindowTitler"],
function (WindowTitler) {
describe("The window titler", function () {
var mockNavigationService,
mockRootScope,
mockDocument,
mockDomainObject,
titler; // eslint-disable-line
beforeEach(function () {
mockNavigationService = jasmine.createSpyObj(
'navigationService',
['getNavigation']
);
mockRootScope = jasmine.createSpyObj(
'$rootScope',
['$watch']
);
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getModel']
);
mockDocument = [{}];
mockDomainObject.getModel.and.returnValue({ name: 'Test name' });
mockNavigationService.getNavigation.and.returnValue(mockDomainObject);
titler = new WindowTitler(
mockNavigationService,
mockRootScope,
mockDocument
);
});
it("listens for changes to the name of the navigated object", function () {
expect(mockRootScope.$watch).toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Function)
);
expect(mockRootScope.$watch.calls.mostRecent().args[0]())
.toEqual('Test name');
});
it("sets the title to the name of the navigated object", function () {
mockRootScope.$watch.calls.mostRecent().args[1]("Some name");
expect(mockDocument[0].title).toEqual("Some name");
});
});
}
);

View File

@@ -28,7 +28,6 @@ define([
"./res/templates/dialog.html",
"./res/templates/overlay-blocking-message.html",
"./res/templates/message.html",
"./res/templates/notification-message.html",
"./res/templates/overlay-message-list.html",
"./res/templates/overlay.html",
'legacyRegistry'
@@ -40,7 +39,6 @@ define([
dialogTemplate,
overlayBlockingMessageTemplate,
messageTemplate,
notificationMessageTemplate,
overlayMessageListTemplate,
overlayTemplate,
legacyRegistry
@@ -65,8 +63,7 @@ define([
"depends": [
"$document",
"$compile",
"$rootScope",
"$timeout"
"$rootScope"
]
}
],
@@ -91,10 +88,6 @@ define([
"key": "message",
"template": messageTemplate
},
{
"key": "notification-message",
"template": notificationMessageTemplate
},
{
"key": "overlay-message-list",
"template": overlayMessageListTemplate

View File

@@ -19,24 +19,24 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="c-overlay__top-bar">
<div class="c-overlay__dialog-title">{{ngModel.title}}</div>
<div class="c-overlay__dialog-hint hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
<div class="abs top-bar">
<div class="dialog-title">{{ngModel.title}}</div>
<div class="hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
</div>
<div class='c-overlay__contents-main'>
<div class='abs editor'>
<mct-form ng-model="ngModel.value"
structure="ngModel.structure"
class="validates"
name="createForm">
</mct-form>
</div>
<div class="c-overlay__button-bar">
<a class='c-button c-button--major'
<div class="abs bottom-bar">
<a class='s-button major'
ng-class="{ disabled: !createForm.$valid }"
ng-click="ngModel.confirm()">
OK
</a>
<a class='c-button '
<a class='s-button'
ng-click="ngModel.cancel()">
Cancel
</a>

View File

@@ -1,32 +1,25 @@
<div class="c-message"
<div class="l-message"
ng-class="'message-severity-' + ngModel.severity">
<div class="w-message-contents">
<div class="c-message__top-bar">
<div class="c-message__title">{{ngModel.title}}</div>
</div>
<div class="c-message__hint" ng-hide="ngModel.hint === undefined">
{{ngModel.hint}}
<span ng-if="ngModel.timestamp !== undefined">[{{ngModel.timestamp}}]</span>
<div class="top-bar">
<div class="title">{{ngModel.message}}</div>
</div>
<div class="message-body">
<div class="message-action">
{{ngModel.actionText}}
</div>
<mct-include key="'progress-bar'"
ng-model="ngModel"
ng-show="ngModel.progress !== undefined || ngModel.unknownProgress"></mct-include>
ng-show="ngModel.progressPerc !== undefined"></mct-include>
</div>
<div class="c-overlay__button-bar">
<button ng-repeat="dialogOption in ngModel.options"
class="c-button"
ng-click="dialogOption.callback()">
{{dialogOption.label}}
</button>
<button class="c-button c-button--major"
ng-if="ngModel.primaryOption"
ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}}
</button>
<div class="bottom-bar">
<a ng-repeat="dialogOption in ngModel.options"
class="s-button"
ng-click="dialogOption.callback()">
{{dialogOption.label}}
</a>
<a class="s-button major"
ng-if="ngModel.primaryOption"
ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}}
</a>
</div>
</div>
</div>
</div>

View File

@@ -1,25 +0,0 @@
<div class="c-message"
ng-class="'message-severity-' + ngModel.severity">
<div class="w-message-contents">
<div class="c-message__top-bar">
<div class="c-message__title">{{ngModel.message}}</div>
</div>
<div class="message-body">
<mct-include key="'progress-bar'"
ng-model="ngModel"
ng-show="ngModel.progressPerc !== undefined"></mct-include>
</div>
</div>
<div class="c-overlay__button-bar">
<button ng-repeat="dialogOption in ngModel.options"
class="c-button"
ng-click="dialogOption.callback()">
{{dialogOption.label}}
</button>
<button class="c-button c-button--major"
ng-if="ngModel.primaryOption"
ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}}
</button>
</div>
</div>

View File

@@ -1,23 +1,22 @@
<mct-container key="overlay">
<div class="t-message-list c-overlay__contents">
<div class="c-overlay__top-bar">
<div class="c-overlay__dialog-title">{{ngModel.dialog.title}}</div>
<div class="c-overlay__dialog-hint">Displaying {{ngModel.dialog.messages.length}} message<span
ng-show="ngModel.dialog.messages.length > 1 ||
ngModel.dialog.messages.length == 0">s</span>
<div class="t-message-list">
<div class="top-bar">
<div class="dialog-title">{{ngModel.dialog.title}}</div>
<div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1 ||
ngModel.dialog.messages.length == 0">s</span>
</div>
</div>
<div class="w-messages c-overlay__messages">
<div class="w-messages">
<mct-include
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
key="'notification-message'" ng-model="msg.model"></mct-include>
key="'message'" ng-model="msg.model"></mct-include>
</div>
<div class="c-overlay__bottom-bar">
<button ng-repeat="dialogAction in ngModel.dialog.actions"
class="c-button c-button--major"
<div class="bottom-bar">
<a ng-repeat="dialogAction in ngModel.dialog.actions"
class="s-button major"
ng-click="dialogAction.action()">
{{dialogAction.label}}
</button>
</a>
</div>
</div>
</mct-container>

View File

@@ -19,18 +19,18 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<mct-container key="c-overlay__contents">
<div class=c-overlay__top-bar">
<div class="c-overlay__dialog-title">{{ngModel.dialog.title}}</div>
<div class="c-overlay__dialog-hint hint">{{ngModel.dialog.hint}}</div>
<mct-container key="overlay">
<div class="abs top-bar">
<div class="dialog-title">{{ngModel.dialog.title}}</div>
<div class="hint">{{ngModel.dialog.hint}}</div>
</div>
<div class='c-overlay__contents-main'>
<div class='abs editor'>
<mct-include key="ngModel.dialog.template"
parameters="ngModel.dialog.parameters"
ng-model="ngModel.dialog.model">
</mct-include>
</div>
<div class="c-overlay__button-bar">
<div class="abs bottom-bar">
<a ng-repeat="option in ngModel.dialog.options"
href=''
class="s-button lg"

View File

@@ -19,12 +19,12 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="c-overlay l-overlay-small" ng-class="{'delayEntry100ms' : ngModel.delay}">
<div class="c-overlay__blocker"></div>
<div class="c-overlay__outer">
<button ng-click="ngModel.cancel()"
<div class="abs overlay l-dialog" ng-class="{'delayEntry100ms' : ngModel.delay}">
<div class="abs blocker"></div>
<div class="abs outer-holder">
<a ng-click="ngModel.cancel()"
ng-if="ngModel.cancel"
class="c-click-icon c-overlay__close-button icon-x-in-circle"></button>
<div class="c-overlay__contents" ng-transclude></div>
class="close icon-x-in-circle"></a>
<div class="abs inner-holder contents" ng-transclude></div>
</div>
</div>

View File

@@ -44,9 +44,8 @@ define(
* @memberof platform/commonUI/dialog
* @constructor
*/
function OverlayService($document, $compile, $rootScope, $timeout) {
function OverlayService($document, $compile, $rootScope) {
this.$compile = $compile;
this.$timeout = $timeout;
// Don't include $document and $rootScope directly;
// avoids https://docs.angularjs.org/error/ng/cpws
@@ -94,14 +93,12 @@ define(
scope.key = key;
scope.typeClass = typeClass || 't-dialog';
this.$timeout(() => {
// Create the overlay element and add it to the document's body
element = this.$compile(TEMPLATE)(scope);
// Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when
// multiple overlays with the same z-index are active.
this.findBody().append(element);
});
// Create the overlay element and add it to the document's body
element = this.$compile(TEMPLATE)(scope);
// Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when
// multiple overlays with the same z-index are active.
this.findBody().append(element);
return {
dismiss: dismiss

View File

@@ -35,20 +35,16 @@ define(
mockTemplate,
mockElement,
mockScope,
mockTimeout,
overlayService;
beforeEach(function () {
mockDocument = jasmine.createSpyObj("$document", ["find"]);
mockCompile = jasmine.createSpy("$compile");
mockRootScope = jasmine.createSpyObj("$rootScope", ["$new"]);
mockBody = jasmine.createSpyObj("body", ["append"]);
mockBody = jasmine.createSpyObj("body", ["prepend"]);
mockTemplate = jasmine.createSpy("template");
mockElement = jasmine.createSpyObj("element", ["remove"]);
mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
mockTimeout = function (callback) {
callback();
}
mockDocument.find.and.returnValue(mockBody);
mockCompile.and.returnValue(mockTemplate);
@@ -58,8 +54,7 @@ define(
overlayService = new OverlayService(
mockDocument,
mockCompile,
mockRootScope,
mockTimeout
mockRootScope
);
});
@@ -72,7 +67,7 @@ define(
it("adds the templated element to the body", function () {
overlayService.createOverlay("test", {});
expect(mockBody.append).toHaveBeenCalledWith(mockElement);
expect(mockBody.prepend).toHaveBeenCalledWith(mockElement);
});
it("places the provided model/key in its template's scope", function () {

View File

@@ -160,7 +160,7 @@ define([
},
{
"key": "remove",
"category": "legacy",
"category": "contextual",
"implementation": RemoveAction,
"cssClass": "icon-trash",
"name": "Remove",

View File

@@ -49,7 +49,7 @@ define(
name: "Properties",
rows: this.properties.map(function (property, index) {
// Property definition is same as form row definition
var row = JSON.parse(JSON.stringify(property.getDefinition()));
var row = Object.create(property.getDefinition());
row.key = index;
return row;
}).filter(function (row) {

View File

@@ -23,7 +23,11 @@
/**
* Module defining RemoveAction. Created by vwoeltje on 11/17/14.
*/
define([], function () {
define([
'./RemoveDialog'
], function (
RemoveDialog
) {
/**
* Construct an action which will remove the provided object manifestation.
@@ -110,7 +114,12 @@ define([], function () {
return parent.useCapability('mutation', doMutate);
}
removeFromContext();
/*
* Pass in the function to remove the domain object so it can be
* associated with an 'OK' button press
*/
dialog = new RemoveDialog(this.openmct, domainObject, removeFromContext);
dialog.show();
};
// Object needs to have a parent for Remove to be applicable

View File

@@ -0,0 +1,72 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-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([], function () {
/**
* @callback removeCallback
* @param {DomainObject} domainObject the domain object to be removed
*/
/**
* Construct a new Remove dialog.
*
* @param {DialogService} dialogService the service that shows the dialog
* @param {DomainObject} domainObject the domain object to be removed
* @param {removeCallback} removeCallback callback that handles removal of the domain object
* @memberof platform/commonUI/edit
* @constructor
*/
function RemoveDialog(openmct, domainObject, removeCallback) {
this.openmct = openmct;
this.domainObject = domainObject;
this.removeCallback = removeCallback;
}
/**
* Display a dialog to confirm the removal of a domain object.
*/
RemoveDialog.prototype.show = function () {
let dialog = this.openmct.overlays.dialog({
title: 'Remove ' + this.domainObject.getModel().name,
iconClass: 'alert',
message: 'Warning! This action will permanently remove this object. Are you sure you want to continue?',
buttons: [
{
label: 'OK',
callback: () => {
this.removeCallback();
dialog.dismiss();
}
},
{
label: 'Cancel',
callback: () => {
dialog.dismiss();
}
}
]
});
};
return RemoveDialog;
});

View File

@@ -92,7 +92,16 @@ function (
* @memberof platform/commonUI/edit.SaveAction#
*/
SaveAsAction.prototype.perform = function () {
return this.save();
// Discard the current root view (which will be the editing
// UI, which will have been pushed atop the Browse UI.)
function returnToBrowse(object) {
if (object) {
object.getCapability("action").perform("navigate");
}
return object;
}
return this.save().then(returnToBrowse);
};
/**
@@ -162,6 +171,9 @@ function (
function saveAfterClone(clonedObject) {
return this.openmct.editor.save().then(() => {
// Force mutation for search indexing
clonedObject.useCapability('mutation', (model) => {
return model;
});
return clonedObject;
})
}
@@ -170,14 +182,6 @@ function (
return fetchObject(clonedObject.getId())
}
function indexForSearch(savedObject) {
savedObject.useCapability('mutation', (model) => {
return model;
});
return savedObject;
}
function onSuccess(object) {
self.notificationService.info("Save Succeeded");
return object;
@@ -188,7 +192,7 @@ function (
if (reason !== "user canceled") {
self.notificationService.error("Save Failed");
}
throw reason;
return false;
}
return getParent(domainObject)
@@ -199,7 +203,6 @@ function (
.then(undirtyOriginals)
.then(saveAfterClone)
.then(finishEditing)
.then(indexForSearch)
.then(hideBlockingDialog)
.then(onSuccess)
.catch(onFailure);

View File

@@ -64,6 +64,7 @@ define(
* @returns boolean
*/
EditorCapability.prototype.inEditContext = function () {
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
return this.openmct.editor.isEditing();
};
@@ -73,6 +74,7 @@ define(
* @returns {*}
*/
EditorCapability.prototype.isEditContextRoot = function () {
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
return this.openmct.editor.isEditing();
};

View File

@@ -67,38 +67,20 @@ define(
openmct = this.openmct,
newObject;
function onSave() {
// openmct.editor.save();
}
function onCancel() {
openmct.editor.cancel();
}
function isFirstViewEditable(domainObject) {
let firstView = openmct.objectViews.get(domainObject)[0];
return firstView && firstView.canEdit && firstView.canEdit(domainObject);
}
function navigateAndEdit(object) {
let objectPath = object.getCapability('context').getPath(),
url = '#/browse/' + objectPath
.slice(1)
.map(function (o) {
return o && openmct.objects.makeKeyString(o.getId());
})
.join('/');
window.location.href = url;
if (isFirstViewEditable(object.useCapability('adapter'))) {
openmct.editor.edit();
}
}
newModel.type = this.type.getKey();
newModel.location = this.parent.getId();
newObject = this.parent.useCapability('instantiation', newModel);
openmct.editor.edit();
newObject.getCapability("action").perform("save-as").then(navigateAndEdit, onCancel);
newObject.getCapability("action").perform("save-as").then(onSave, onCancel);
// TODO: support editing object without saving object first.
// Which means we have to toggle createwizard afterwards. For now,
// We will disable this.

View File

@@ -66,7 +66,7 @@ define(
name: "Properties",
rows: this.properties.map(function (property, index) {
// Property definition is same as form row definition
var row = JSON.parse(JSON.stringify(property.getDefinition()));
var row = Object.create(property.getDefinition());
// Use index as the key into the formValue;
// this correlates to the indexing provided by

View File

@@ -77,19 +77,14 @@ define([], function () {
return promiseFn().then(nextFn);
};
}
/**
* Clear any existing persistence calls for object with given ID. This ensures only the most recent persistence
* call is executed. This should prevent stale objects being persisted and overwriting fresh ones.
*/
if (this.isScheduled(id)) {
this.clearTransactionsFor(id);
}
this.clearTransactionFns[id] =
this.transactionService.addToTransaction(
chain(onCommit, release),
chain(onCancel, release)
);
if (!this.isScheduled(id)) {
this.clearTransactionFns[id] =
this.transactionService.addToTransaction(
chain(onCommit, release),
chain(onCancel, release)
);
}
};
/**

View File

@@ -93,33 +93,24 @@ define(
expect(mockOnCancel).toHaveBeenCalled();
});
describe("Adds callbacks to transaction", function () {
beforeEach(function () {
spyOn(manager, 'clearTransactionsFor');
manager.clearTransactionsFor.and.callThrough();
});
it("ignores subsequent calls for the same object", function () {
manager.addToTransaction(
testId,
jasmine.createSpy(),
jasmine.createSpy()
);
expect(mockTransactionService.addToTransaction.calls.count())
.toEqual(1);
});
it("and clears pending calls if same object", function () {
manager.addToTransaction(
testId,
jasmine.createSpy(),
jasmine.createSpy()
);
expect(manager.clearTransactionsFor).toHaveBeenCalledWith(testId);
});
it("and does not clear pending calls if different object", function () {
manager.addToTransaction(
'other-id',
jasmine.createSpy(),
jasmine.createSpy()
);
expect(manager.clearTransactionsFor).not.toHaveBeenCalled();
});
afterEach(function () {
expect(mockTransactionService.addToTransaction.calls.count()).toEqual(2);
});
it("accepts subsequent calls for other objects", function () {
manager.addToTransaction(
'other-id',
jasmine.createSpy(),
jasmine.createSpy()
);
expect(mockTransactionService.addToTransaction.calls.count())
.toEqual(2);
});
it("does not remove callbacks from the transaction", function () {

View File

@@ -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-object-label">
<div class="c-object-label__type-icon {{type.getCssClass()}}" ng-class="{ 'l-icon-link':location.isLink() }"></div>
<div class='c-object-label__name'>{{model.name}}</div>
<div class="t-object-label l-flex-row flex-elem grows">
<div class="t-item-icon flex-elem {{type.getCssClass()}}" ng-class="{ 'l-icon-link':location.isLink() }"></div>
<div class='t-title-label flex-elem grows'>{{model.name}}</div>
</div>

View File

@@ -1,13 +1,13 @@
<div ng-controller="BannerController" ng-show="active.notification"
class="c-message-banner {{active.notification.model.severity}}" ng-class="{
class="l-message-banner s-message-banner {{active.notification.model.severity}}" ng-class="{
'minimized': active.notification.model.minimized,
'new': !active.notification.model.minimized}"
ng-click="maximize(active.notification)">
<span class="c-message-banner__message">
<span class="banner-elem label">
{{active.notification.model.title}}
</span>
<span ng-show="active.notification.model.progress !== undefined || active.notification.model.unknownProgress">
<mct-include key="'progress-bar'" class="c-message-banner__progress-bar"
<mct-include key="'progress-bar'" class="banner-elem"
ng-model="active.notification.model">
</mct-include>
</span>
@@ -16,5 +16,5 @@
ng-click="action(active.notification.model.primaryOption.callback, $event)">
{{active.notification.model.primaryOption.label}}
</a>
<button class="c-message-banner__close-button c-click-icon icon-x-in-circle" ng-click="dismiss(active.notification, $event)"></button>
<a class="banner-elem close icon-x" ng-click="dismiss(active.notification, $event)"></a>
</div>

View File

@@ -20,11 +20,14 @@
at runtime from the About dialog for additional information.
-->
<span ng-controller="ToggleController as toggle">
<div class="u-contents" ng-controller="TreeNodeController as treeNode">
<div class="c-tree__item menus-to-left"
ng-class="{selected: treeNode.isSelected()}">
<span class='c-disclosure-triangle c-tree__item__view-control'
ng-class="{ 'is-enabled': model.composition !== undefined, 'c-disclosure-triangle--expanded': toggle.isActive() }"
<span ng-controller="TreeNodeController as treeNode">
<span
class="tree-item menus-to-left"
ng-class="{selected: treeNode.isSelected()}"
>
<span
class='ui-symbol view-control flex-elem'
ng-class="{ 'has-children': model.composition !== undefined, expanded: toggle.isActive() }"
ng-click="toggle.toggle(); treeNode.trackExpansion()"
>
</span>
@@ -36,15 +39,19 @@
ng-click="treeNode.select()"
>
</mct-representation>
</div>
<div class="u-contents"
</span>
<span
class="tree-item-subtree"
ng-show="toggle.isActive()"
ng-if="model.composition !== undefined">
ng-if="model.composition !== undefined"
>
<mct-representation key="'subtree'"
ng-model="ngModel"
parameters="parameters"
mct-object="treeNode.hasBeenExpanded() && domainObject">
</mct-representation>
</div>
</div>
</span>
</span>
</span>

View File

@@ -19,8 +19,8 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<ul class="c-tree">
<li class="c-tree__item-h">
<ul class="tree">
<li>
<mct-representation key="'tree-node'"
mct-object="domainObject"
ng-model="ngModel"

View File

@@ -1,2 +1,4 @@
<span class="c-tree__item js-tree__item"></span>
<span class="c-tree__item-subtree"></span>
<span class="tree-item menus-to-left">
</span>
<span class="tree-item-subtree">
</span>

View File

@@ -1 +1,2 @@
<span class='c-disclosure-triangle c-tree__item__view-control'></span>
<span class='ui-symbol view-control flex-elem'>
</span>

View File

@@ -1,4 +1,6 @@
<div class="rep-object-label c-object-label c-tree__item__label">
<div class="c-object-label__type-icon c-tree__item__type-icon t-item-icon"></div>
<div class="c-object-label__name c-tree__item__name t-title-label"></div>
</div>
<span class="rep-object-label">
<div class="t-object-label l-flex-row flex-elem grows">
<div class="t-item-icon flex-elem"></div>
<div class='t-title-label flex-elem grows'></div>
</div>
</span>

View File

@@ -54,7 +54,6 @@ define(
if (isDestroyed) {
return;
}
var removeSelectable = openmct.selection.selectable(
element[0],
scope.$eval(attrs.mctSelectable),

View File

@@ -37,9 +37,9 @@ define([
this.expanded = state;
if (state) {
this.el.addClass('c-disclosure-triangle--expanded');
this.el.addClass('expanded');
} else {
this.el.removeClass('c-disclosure-triangle--expanded');
this.el.removeClass('expanded');
}
this.callbacks.forEach(function (callback) {

View File

@@ -28,7 +28,7 @@ define([
], function ($, nodeTemplate, ToggleView, TreeLabelView) {
function TreeNodeView(gestureService, subtreeFactory, selectFn, openmct) {
this.li = $('<li class="c-tree__item-h">');
this.li = $('<li>');
this.openmct = openmct;
this.statusClasses = [];
@@ -38,7 +38,7 @@ define([
if (!this.subtreeView) {
this.subtreeView = subtreeFactory();
this.subtreeView.model(this.activeObject);
this.li.find('.c-tree__item-subtree').eq(0)
this.li.find('.tree-item-subtree').eq(0)
.append($(this.subtreeView.elements()));
}
$(this.subtreeView.elements()).removeClass('hidden');
@@ -85,9 +85,9 @@ define([
var obj = domainObject.useCapability('adapter');
var hasComposition = this.openmct.composition.get(obj) !== undefined;
if (hasComposition) {
$(this.toggleView.elements()).addClass('is-enabled');
$(this.toggleView.elements()).removeClass('no-children');
} else {
$(this.toggleView.elements()).removeClass('is-enabled');
$(this.toggleView.elements()).addClass('no-children');
}
}
@@ -120,7 +120,7 @@ define([
selectedIdPath = getIdPath(domainObject);
if (this.onSelectionPath) {
this.li.find('.js-tree__item').eq(0).removeClass('is-selected');
this.li.find('.tree-item').eq(0).removeClass('selected');
if (this.subtreeView) {
this.subtreeView.value(undefined);
}
@@ -136,7 +136,7 @@ define([
if (this.onSelectionPath) {
if (activeIdPath.length === selectedIdPath.length) {
this.li.find('.js-tree__item').eq(0).addClass('is-selected');
this.li.find('.tree-item').eq(0).addClass('selected');
} else {
// Expand to reveal the selection
this.toggleView.value(true);

View File

@@ -27,7 +27,7 @@ define([
], function ($, TreeNodeView, spinnerTemplate) {
function TreeView(gestureService, openmct, selectFn) {
this.ul = $('<ul class="c-tree"></ul>');
this.ul = $('<ul class="tree"></ul>');
this.nodeViews = [];
this.callbacks = [];
this.selectFn = selectFn || this.value.bind(this);

View File

@@ -43,10 +43,23 @@ define([], function () {
var mutationTopic = topic('mutation');
mutationTopic.listen(function (domainObject) {
var persistence = domainObject.getCapability('persistence');
var wasActive = transactionService.isActive();
cacheService.put(domainObject.getId(), domainObject.getModel());
if (hasChanged(domainObject)) {
persistence.persist();
if (!wasActive) {
transactionService.startTransaction();
}
transactionService.addToTransaction(
persistence.persist.bind(persistence),
persistence.refresh.bind(persistence)
);
if (!wasActive) {
transactionService.commit();
}
}
});
}

View File

@@ -24,27 +24,22 @@ define(
["../../src/runs/TransactingMutationListener"],
function (TransactingMutationListener) {
describe("TransactingMutationListener", function () {
xdescribe("TransactingMutationListener", function () {
var mockTopic,
mockMutationTopic,
mockCacheService,
mockTransactionService,
mockDomainObject,
mockModel,
mockPersistence;
beforeEach(function () {
mockTopic = jasmine.createSpy('topic');
mockMutationTopic =
jasmine.createSpyObj('mutation', ['listen']);
mockCacheService =
jasmine.createSpyObj('cacheService', [
'put'
]);
mockTransactionService =
jasmine.createSpyObj('transactionService', [
'isActive',
'startTransaction',
'addToTransaction',
'commit'
]);
mockDomainObject = jasmine.createSpyObj(
@@ -57,24 +52,18 @@ define(
);
mockTopic.and.callFake(function (t) {
expect(t).toBe('mutation');
return mockMutationTopic;
return (t === 'mutation') && mockMutationTopic;
});
mockDomainObject.getId.and.returnValue('mockId');
mockDomainObject.getCapability.and.callFake(function (c) {
expect(c).toBe('persistence');
return mockPersistence;
return (c === 'persistence') && mockPersistence;
});
mockModel = {};
mockDomainObject.getModel.and.returnValue(mockModel);
mockPersistence.persisted.and.returnValue(true);
return new TransactingMutationListener(
mockTopic,
mockTransactionService,
mockCacheService
mockTransactionService
);
});
@@ -83,27 +72,48 @@ define(
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("calls persist if the model has changed", function () {
mockModel.persisted = Date.now();
[false, true].forEach(function (isActive) {
var verb = isActive ? "is" : "isn't";
//Mark the model dirty by setting the mutated date later than the last persisted date.
mockModel.modified = mockModel.persisted + 1;
function onlyWhenInactive(expectation) {
return isActive ? expectation.not : expectation;
}
mockMutationTopic.listen.calls.mostRecent()
.args[0](mockDomainObject);
describe("when a transaction " + verb + " active", function () {
var innerVerb = isActive ? "does" : "doesn't";
expect(mockPersistence.persist).toHaveBeenCalled();
});
beforeEach(function () {
mockTransactionService.isActive.and.returnValue(isActive);
});
it("does not call persist if the model has not changed", function () {
mockModel.persisted = Date.now();
describe("and mutation occurs", function () {
beforeEach(function () {
mockMutationTopic.listen.calls.mostRecent()
.args[0](mockDomainObject);
});
mockModel.modified = mockModel.persisted;
mockMutationTopic.listen.calls.mostRecent()
.args[0](mockDomainObject);
it(innerVerb + " start a new transaction", function () {
onlyWhenInactive(
expect(mockTransactionService.startTransaction)
).toHaveBeenCalled();
});
expect(mockPersistence.persist).not.toHaveBeenCalled();
it("adds to the active transaction", function () {
expect(mockTransactionService.addToTransaction)
.toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Function)
);
});
it(innerVerb + " immediately commit", function () {
onlyWhenInactive(
expect(mockTransactionService.commit)
).toHaveBeenCalled();
});
});
});
});
});
}

View File

@@ -24,6 +24,7 @@ define([
"./src/actions/MoveAction",
"./src/actions/CopyAction",
"./src/actions/LinkAction",
"./src/actions/GoToOriginalAction",
"./src/actions/SetPrimaryLocationAction",
"./src/services/LocatingCreationDecorator",
"./src/services/LocatingObjectDecorator",
@@ -40,6 +41,7 @@ define([
MoveAction,
CopyAction,
LinkAction,
GoToOriginalAction,
SetPrimaryLocationAction,
LocatingCreationDecorator,
LocatingObjectDecorator,
@@ -102,6 +104,14 @@ define([
"linkService"
]
},
{
"key": "follow",
"name": "Go To Original",
"description": "Go to the original, un-linked instance of this object.",
"cssClass": "",
"category": "contextual",
"implementation": GoToOriginalAction
},
{
"key": "locate",
"name": "Set Primary Location",

View File

@@ -0,0 +1,60 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
function () {
/**
* Implements the "Go To Original" action, which follows a link back
* to an original instance of an object.
*
* @implements {Action}
* @constructor
* @private
* @memberof platform/entanglement
* @param {ActionContext} context the context in which the action
* will be performed
*/
function GoToOriginalAction(context) {
this.domainObject = context.domainObject;
}
GoToOriginalAction.prototype.perform = function () {
return this.domainObject.getCapability("location").getOriginal()
.then(function (originalObject) {
var actionCapability =
originalObject.getCapability("action");
return actionCapability &&
actionCapability.perform("navigate");
});
};
GoToOriginalAction.appliesTo = function (context) {
var domainObject = context.domainObject;
return domainObject && domainObject.hasCapability("location") &&
domainObject.getCapability("location").isLink();
};
return GoToOriginalAction;
}
);

View File

@@ -0,0 +1,93 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[
'../../src/actions/GoToOriginalAction',
'../DomainObjectFactory',
'../ControlledPromise'
],
function (GoToOriginalAction, domainObjectFactory, ControlledPromise) {
describe("The 'go to original' action", function () {
var testContext,
originalDomainObject,
mockLocationCapability,
mockOriginalActionCapability,
originalPromise,
action;
beforeEach(function () {
mockLocationCapability = jasmine.createSpyObj(
'location',
['isLink', 'isOriginal', 'getOriginal']
);
mockOriginalActionCapability = jasmine.createSpyObj(
'action',
['perform', 'getActions']
);
originalPromise = new ControlledPromise();
mockLocationCapability.getOriginal.and.returnValue(originalPromise);
mockLocationCapability.isLink.and.returnValue(true);
mockLocationCapability.isOriginal.and.callFake(function () {
return !mockLocationCapability.isLink();
});
testContext = {
domainObject: domainObjectFactory({
capabilities: {
location: mockLocationCapability
}
})
};
originalDomainObject = domainObjectFactory({
capabilities: {
action: mockOriginalActionCapability
}
});
action = new GoToOriginalAction(testContext);
});
it("is applicable to links", function () {
expect(GoToOriginalAction.appliesTo(testContext))
.toBeTruthy();
});
it("is not applicable to originals", function () {
mockLocationCapability.isLink.and.returnValue(false);
expect(GoToOriginalAction.appliesTo(testContext))
.toBeFalsy();
});
it("navigates to original objects when performed", function () {
expect(mockOriginalActionCapability.perform)
.not.toHaveBeenCalled();
action.perform();
originalPromise.resolve(originalDomainObject);
expect(mockOriginalActionCapability.perform)
.toHaveBeenCalledWith('navigate');
});
});
}
);

View File

@@ -24,10 +24,10 @@
<button ng-click="timer.clickStopButton()"
ng-hide="timer.timerState == 'stopped'"
title="Reset"
class="c-timer__ctrl-reset c-icon-button c-icon-button--major icon-reset"></button>
class="c-timer__ctrl-reset c-click-icon c-click-icon--major icon-reset"></button>
<button ng-click="timer.clickButton()"
title="{{timer.buttonText()}}"
class="c-timer__ctrl-pause-play c-icon-button c-icon-button--major {{timer.buttonCssClass()}}"></button>
class="c-timer__ctrl-pause-play c-click-icon c-click-icon--major {{timer.buttonCssClass()}}"></button>
</div>
<div class="c-timer__direction {{timer.signClass()}}"
ng-hide="!timer.signClass()"></div>

View File

@@ -45,6 +45,7 @@ define([
"key": "url",
"name": "URL",
"control": "textfield",
"pattern": "^(ftp|https?)\\:\\/\\/",
"required": true,
"cssClass": "l-input-lg"
},

View File

@@ -1,18 +1,22 @@
<div class="t-imagery c-imagery" ng-controller="ImageryController as imagery">
<div class="t-imagery" ng-controller="ImageryController as imagery">
<mct-split-pane class='abs' anchor="bottom" alias="imagery">
<div class="split-pane-component has-local-controls l-image-main-wrapper l-flex-col">
<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">
<div class="split-pane-component has-local-controls l-image-main-wrapper l-flex-col"
ng-mouseenter="showLocalControls = true;"
ng-mouseleave="showLocalControls = false;">
<div class="h-local-controls h-local-controls-overlay-content h-local-controls-trans s-local-controls local-controls-hidden l-flex-row">
<span class="holder flex-elem grows">
<input class="icon-brightness" type="range"
min="0"
max="500"
ng-model="filters.brightness" />
ng-model="filters.brightness">
</input>
<input class="icon-contrast" type="range"
min="0"
max="500"
ng-model="filters.contrast" />
ng-model="filters.contrast">
</input>
</span>
<span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
<span class="holder flex-elem t-reset-btn-holder">
<a class="s-icon-button icon-reset t-btn-reset"
ng-click="filters = { brightness: 100, contrast: 100 }"></a>
</span>
@@ -29,14 +33,14 @@
<div class="l-image-main-controlbar flex-elem l-flex-row">
<div class="l-datetime-w flex-elem grows">
<a class="c-button show-thumbs sm hidden icon-thumbs-strip"
<a class="s-button show-thumbs sm hidden icon-thumbs-strip"
ng-click="showThumbsBubble = (showThumbsBubble) ? false:true"></a>
<span class="l-time">{{imagery.getTime()}}</span>
</div>
<div class="h-local-controls flex-elem">
<a class="c-button icon-pause pause-play"
<a class="s-button pause-play"
ng-click="imagery.paused(!imagery.paused())"
ng-class="{ 'is-paused': imagery.paused() }"></a>
ng-class="{ paused: imagery.paused() }"></a>
<a href=""
class="s-button l-mag s-mag vsm icon-reset"
ng-click="clipped = false"

View File

@@ -47,6 +47,7 @@ define([
"key": "url",
"name": "URL",
"control": "textfield",
"pattern": "^(ftp|https?)\\:\\/\\/",
"required": true,
"cssClass": "l-input-lg"
}

View File

@@ -19,13 +19,13 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<form name="mctForm" novalidate class="form c-form" autocomplete="off">
<form name="mctForm" novalidate class="form l-flex-col">
<span ng-repeat="section in structure.sections"
class="l-form-section c-form__section {{ section.cssClass }}">
<h2 class="c-form__header" ng-if="section.name">
class="l-form-section l-flex-col flex-elem {{ section.cssClass }}">
<h2 class="section-header flex-elem" ng-if="section.name">
{{section.name}}
</h2>
<ng-form class="form-row c-form__row validates {{ section.cssClass }}"
<ng-form class="form-row validates l-flex-row flex-elem {{ section.cssClass }}"
ng-class="{
first:$index < 1,
req: row.required,
@@ -37,11 +37,11 @@
}"
name="mctFormInner"
ng-repeat="row in section.rows">
<div class='c-form__row__label label flex-elem' title="{{row.description}}">
<div class='label flex-elem' title="{{row.description}}">
{{row.name}}
</div>
<div class='c-form__row__controls controls flex-elem'>
<div class="c-form__controls-wrapper wrapper" ng-if="row.control">
<div class='controls flex-elem'>
<div class="wrapper" ng-if="row.control">
<mct-control key="row.control"
ng-model="ngModel"
ng-required="row.required"

View File

@@ -29,13 +29,12 @@ define(
function SnapshotPreviewController($scope, openmct) {
$scope.previewImage = function (imageUrl) {
let imageDiv = document.createElement('div');
imageDiv.classList = 'image-main s-image-main';
imageDiv.style.backgroundImage = `url(${imageUrl})`;
let image = document.createElement('img');
image.src = imageUrl;
let previewImageOverlay = openmct.overlays.overlay(
{
element: imageDiv,
element: image,
size: 'large',
buttons: [
{

View File

@@ -64,30 +64,12 @@ define(['zepto'], function ($) {
var tree = this.generateNewIdentifiers(objTree);
var rootId = tree.rootId;
var rootObj = this.instantiate(tree.openmct[rootId], rootId);
var newStyleParent = parent.useCapability('adapter');
var newStyleRootObj = rootObj.useCapability('adapter');
if (this.openmct.composition.checkPolicy(newStyleParent, newStyleRootObj)) {
// Instantiate all objects in tree with their newly generated ids,
// adding each to its rightful parent's composition
rootObj.getCapability("location").setPrimaryLocation(parent.getId());
this.deepInstantiate(rootObj, tree.openmct, []);
parent.getCapability("composition").add(rootObj);
} else {
var dialog = this.openmct.overlays.dialog({
iconClass: 'alert',
message: "We're sorry, but you cannot import that object type into this object.",
buttons: [
{
label: "Ok",
emphasis: true,
callback: function () {
dialog.dismiss();
}
}
]
});
}
// Instantiate all objects in tree with their newly genereated ids,
// adding each to its rightful parent's composition
rootObj.getCapability("location").setPrimaryLocation(parent.getId());
this.deepInstantiate(rootObj, tree.openmct, []);
parent.getCapability("composition").add(rootObj);
};
ImportAsJSONAction.prototype.deepInstantiate = function (parent, tree, seen) {
@@ -98,17 +80,15 @@ define(['zepto'], function ($) {
var newObj;
seen.push(parent.getId());
parentModel.composition.forEach(function (childId) {
let keystring = this.openmct.objects.makeKeyString(childId);
if (!tree[keystring] || seen.includes(keystring)) {
parentModel.composition.forEach(function (childId, index) {
if (!tree[childId] || seen.includes(childId)) {
return;
}
newObj = this.instantiate(tree[keystring], keystring);
newObj = this.instantiate(tree[childId], childId);
parent.getCapability("composition").add(newObj);
newObj.getCapability("location")
.setPrimaryLocation(tree[keystring].location);
.setPrimaryLocation(tree[childId].location);
this.deepInstantiate(newObj, tree, seen);
}, this);
}

View File

@@ -37,17 +37,75 @@ define(
}
/**
* Discard failures
* Handle persistence failures by providing the user with a
* dialog summarizing these failures, and giving the option
* to overwrite/cancel as appropriate.
* @param {Array} failures persistence failures, as prepared
* by PersistenceQueueHandler
* @memberof platform/persistence/queue.PersistenceFailureHandler#
*/
PersistenceFailureHandler.prototype.handle = function handleFailures(failures) {
// Prepare dialog for display
var dialogModel = new PersistenceFailureDialog(failures),
revisionErrors = dialogModel.model.revised,
$q = this.$q;
// Refresh revision information for the domain object associated
// with this persistence failure
function refresh(failure) {
// Refresh the domain object to the latest from persistence
return failure.persistence.refresh();
}
// Issue a new persist call for the domain object associated with
// this failure.
function persist(failure) {
// Note that we reissue the persist request here, but don't
// return it, to avoid a circular wait. We trust that the
// PersistenceQueue will behave correctly on the next round
// of flushing.
failure.requeue();
}
// Retry persistence (overwrite) for this set of failed attempts
function retry(failuresToRetry) {
var models = {};
// Cache a copy of the model
function cacheModel(failure) {
// Clone...
models[failure.id] = JSON.parse(JSON.stringify(
failure.domainObject.getModel()
));
}
// Mutate a domain object to restore its model
function remutate(failure) {
var model = models[failure.id];
return failure.domainObject.useCapability(
"mutation",
function () {
return model;
},
model.modified
);
}
// Cache the object models we might want to save
failuresToRetry.forEach(cacheModel);
// Strategy here:
// * Cache all of the models we might want to save (above)
// * Refresh all domain objects (so they are latest versions)
// * Re-insert the cached domain object models
// * Invoke persistence again
return $q.all(failuresToRetry.map(refresh)).then(function () {
return $q.all(failuresToRetry.map(remutate));
}).then(function () {
return $q.all(failuresToRetry.map(persist));
});
}
// Discard changes for a failed refresh
function discard(failure) {
var persistence =
@@ -60,7 +118,19 @@ define(
return $q.all(failuresToDiscard.map(discard));
}
return discardAll(revisionErrors);
// Handle user input (did they choose to overwrite?)
function handleChoice(key) {
// If so, try again
if (key === PersistenceFailureConstants.OVERWRITE_KEY) {
return retry(revisionErrors);
} else {
return discardAll(revisionErrors);
}
}
// Prompt for user input, the overwrite if they said so.
return this.dialogService.getUserChoice(dialogModel)
.then(handleChoice, handleChoice);
};
return PersistenceFailureHandler;

View File

@@ -74,14 +74,43 @@ define(
handler = new PersistenceFailureHandler(mockQ, mockDialogService);
});
it("discards on handle", function () {
it("shows a dialog to handle failures", function () {
handler.handle(mockFailures);
expect(mockDialogService.getUserChoice).toHaveBeenCalled();
});
it("overwrites on request", function () {
mockQ.all.and.returnValue(asPromise([]));
handler.handle(mockFailures);
// User chooses overwrite
mockPromise.then.calls.mostRecent().args[0](Constants.OVERWRITE_KEY);
// Should refresh, remutate, and requeue all objects
mockFailures.forEach(function (mockFailure, i) {
expect(mockFailure.persistence.refresh).toHaveBeenCalled();
expect(mockFailure.requeue).toHaveBeenCalled();
expect(mockFailure.domainObject.useCapability).toHaveBeenCalledWith(
'mutation',
jasmine.any(Function),
i // timestamp
);
expect(mockFailure.domainObject.useCapability.calls.mostRecent().args[1]())
.toEqual({ id: mockFailure.id, modified: i });
});
});
it("discards on request", function () {
mockQ.all.and.returnValue(asPromise([]));
handler.handle(mockFailures);
// User chooses overwrite
mockPromise.then.calls.mostRecent().args[0](false);
// Should refresh, but not remutate, and requeue all objects
mockFailures.forEach(function (mockFailure) {
expect(mockFailure.persistence.refresh).toHaveBeenCalled();
expect(mockFailure.requeue).not.toHaveBeenCalled();
expect(mockFailure.domainObject.useCapability).not.toHaveBeenCalled();
});
});
});
}
);

View File

@@ -45,7 +45,7 @@
</mct-include>
</div>
<a class="c-button c-search__btn-cancel"
<a class="s-button c-search__btn-cancel"
ng-show="!(ngModel.input === '' || ngModel.input === undefined)"
ng-click="ngModel.input = ''; ngModel.checkAll = true; menuController.checkAll(); controller.search()">
Cancel</a>

View File

@@ -44,9 +44,6 @@ define([
'../platform/core/src/objects/DomainObjectImpl',
'../platform/core/src/capabilities/ContextualDomainObject',
'./ui/preview/plugin',
'./api/Branding',
'./plugins/licenses/plugin',
'./plugins/remove/plugin',
'vue'
], function (
EventEmitter,
@@ -72,9 +69,6 @@ define([
DomainObjectImpl,
ContextualDomainObject,
PreviewPlugin,
BrandingAPI,
LicensesPlugin,
RemoveActionPlugin,
Vue
) {
/**
@@ -95,13 +89,6 @@ define([
*/
function MCT() {
EventEmitter.call(this);
this.buildInfo = {
version: __OPENMCT_VERSION__,
buildDate: __OPENMCT_BUILD_DATE__,
revision: __OPENMCT_REVISION__,
branch: __OPENMCT_BUILD_BRANCH__
};
this.legacyBundle = { extensions: {
services: [
{
@@ -241,21 +228,16 @@ define([
this.contextMenu = new api.ContextMenuRegistry();
this.router = new ApplicationRouter();
this.branding = BrandingAPI.default;
this.legacyRegistry = defaultRegistry;
this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable());
this.install(this.plugins.DisplayLayout());
this.install(PreviewPlugin.default());
this.install(LegacyIndicatorsPlugin());
this.install(LicensesPlugin.default());
this.install(RemoveActionPlugin.default());
if (typeof BUILD_CONSTANTS !== 'undefined') {
this.install(buildInfoPlugin(BUILD_CONSTANTS));
}
}
MCT.prototype = Object.create(EventEmitter.prototype);
@@ -326,12 +308,6 @@ define([
* MCT; if undefined, MCT will be run in the body of the document
*/
MCT.prototype.start = function (domElement) {
if (!this.plugins.DisplayLayout._installed) {
this.install(this.plugins.DisplayLayout({
showAsView: ['summary-widget']
}));
}
if (!domElement) {
domElement = document.body;
}
@@ -355,8 +331,12 @@ define([
legacyRegistry.register('adapter', this.legacyBundle);
legacyRegistry.enable('adapter');
this.install(LegacyIndicatorsPlugin());
this.router = new ApplicationRouter();
this.router.route(/^\/$/, () => {
this.router.setPath('/browse/');
this.router.setPath('/browse/mine');
});
/**

View File

@@ -33,25 +33,20 @@ export default class LegacyContextMenuAction {
}
invoke(objectPath) {
this.openmct.objects.getRoot().then((root) => {
let pathWithRoot = objectPath.slice();
pathWithRoot.push(root);
let context = {
category: 'contextual',
domainObject: this.openmct.legacyObject(objectPath)
}
let legacyAction = new this.LegacyAction(context);
let context = {
category: 'contextual',
domainObject: this.openmct.legacyObject(pathWithRoot)
}
let legacyAction = new this.LegacyAction(context);
if (!legacyAction.getMetadata) {
let metadata = Object.create(this.LegacyAction.definition);
metadata.context = context;
legacyAction.getMetadata = function () {
return metadata;
}.bind(legacyAction);
}
legacyAction.perform();
});
if (!legacyAction.getMetadata) {
let metadata = Object.create(this.LegacyAction.definition);
metadata.context = context;
legacyAction.getMetadata = function () {
return metadata;
}.bind(legacyAction);
}
legacyAction.perform();
}
appliesTo(objectPath) {

View File

@@ -36,7 +36,7 @@ define([
'./runs/RegisterLegacyTypes',
'./services/LegacyObjectAPIInterceptor',
'./views/installLegacyViews',
'./policies/LegacyCompositionPolicyAdapter',
'./policies/legacyCompositionPolicyAdapter',
'./actions/LegacyActionAdapter'
], function (
legacyRegistry,

View File

@@ -137,7 +137,8 @@ define([
function callbackWrapper(series) {
callback(createDatum(domainObject, metadata, series, series.getPointCount() - 1));
}
return capability.subscribe(callbackWrapper, request) || function () {};
return capability.subscribe(callbackWrapper, request);
};
LegacyTelemetryProvider.prototype.supportsLimits = function (domainObject) {
@@ -157,7 +158,7 @@ define([
return {
evaluate: function (datum, property) {
return limitEvaluator.evaluate(datum, property && property.key);
return limitEvaluator.evaluate(datum, property.key);
}
};
};

View File

@@ -57,10 +57,8 @@ define([
}.bind(this);
handleLegacyMutation = function (legacyObject) {
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId()),
keystring = utils.makeKeyString(newStyleObject.identifier);
this.eventEmitter.emit(keystring + ":*", newStyleObject);
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
this.eventEmitter.emit(newStyleObject.identifier.key + ":*", newStyleObject);
this.eventEmitter.emit('mutation', newStyleObject);
}.bind(this);

View File

@@ -45,30 +45,15 @@ define([
view: function (domainObject) {
let $rootScope = openmct.$injector.get('$rootScope');
let templateLinker = openmct.$injector.get('templateLinker');
let scope = $rootScope.$new(true);
let scope = $rootScope.$new();
let legacyObject = convertToLegacyObject(domainObject);
let isDestroyed = false;
let unlistenToStatus;
let element;
scope.domainObject = legacyObject;
scope.model = legacyObject.getModel();
let child;
let parent;
return {
show: function (container) {
parent = container;
child = document.createElement('div');
parent.appendChild(child);
let statusCapability = legacyObject.getCapability('status');
unlistenToStatus = statusCapability.listen((newStatus) => {
child.classList.remove('s-status-timeconductor-unsynced');
if (newStatus.includes('timeconductor-unsynced')) {
child.classList.add('s-status-timeconductor-unsynced');
}
});
// TODO: implement "gestures" support ?
let uses = legacyView.uses || [];
let promises = [];
@@ -89,13 +74,12 @@ define([
uses.forEach(function (key, i) {
scope[key] = results[i];
});
element = openmct.$angular.element(child);
templateLinker.link(
scope,
element,
openmct.$angular.element(container),
legacyView
);
child.classList.add('u-contents');
container.classList.add('u-contents');
}
if (promises.length) {
@@ -109,12 +93,7 @@ define([
}
},
destroy: function () {
element.off();
element.remove();
scope.$destroy();
element = null;
scope = null;
unlistenToStatus();
}
}
},

View File

@@ -25,34 +25,25 @@ define([
cssClass: representation.cssClass,
description: representation.description,
canView: function (selection) {
if (selection.length !== 1 || selection[0].length === 0) {
if (!selection[0] || !selection[0].context.item) {
return false;
}
let selectionContext = selection[0][0].context;
if (!selectionContext.item) {
return false;
}
return selectionContext.item.type === typeDefinition.key;
let domainObject = selection[0].context.item;
return domainObject.type === typeDefinition.key;
},
view: function (selection) {
let domainObject = selection[0][0].context.item;
let domainObject = selection[0].context.item;
let $rootScope = openmct.$injector.get('$rootScope');
let templateLinker = openmct.$injector.get('templateLinker');
let scope = $rootScope.$new(true);
let scope = $rootScope.$new();
let legacyObject = convertToLegacyObject(domainObject);
let isDestroyed = false;
let element;
scope.domainObject = legacyObject;
scope.model = legacyObject.getModel();
return {
show: function (container) {
let child = document.createElement('div');
container.appendChild(child);
// TODO: implement "gestures" support ?
let uses = representation.uses || [];
let promises = [];
@@ -73,10 +64,9 @@ define([
uses.forEach(function (key, i) {
scope[key] = results[i];
});
element = openmct.$angular.element(child)
templateLinker.link(
scope,
element,
openmct.$angular.element(container),
representation
);
container.style.height = '100%';
@@ -93,11 +83,7 @@ define([
}
},
destroy: function () {
element.off();
element.remove();
scope.$destroy();
element = null;
scope = null;
}
}
}

View File

@@ -28,6 +28,11 @@ export default class Editor extends EventEmitter {
super();
this.editing = false;
this.openmct = openmct;
document.addEventListener('drop', (event) => {
if (!this.isEditing()) {
this.edit();
}
}, {capture: true});
}
/**
@@ -74,11 +79,9 @@ export default class Editor extends EventEmitter {
* @private
*/
cancel() {
let cancelPromise = this.getTransactionService().cancel();
this.getTransactionService().cancel();
this.editing = false;
this.emit('isEditing', false);
return cancelPromise;
}
/**

View File

@@ -21,23 +21,7 @@ define([
topicService.and.returnValue(mutationTopic);
publicAPI = {};
publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [
'get',
'mutate',
'observe',
'areIdsEqual'
]);
publicAPI.objects.areIdsEqual.and.callFake(function (id1, id2) {
return id1.namespace === id2.namespace && id1.key === id2.key;
});
publicAPI.composition = jasmine.createSpyObj('CompositionAPI', [
'checkPolicy'
]);
publicAPI.composition.checkPolicy.and.returnValue(true);
publicAPI.objects.eventEmitter = jasmine.createSpyObj('eventemitter', [
'on'
'get'
]);
publicAPI.objects.get.and.callFake(function (identifier) {
return Promise.resolve({identifier: identifier});
@@ -68,14 +52,6 @@ define([
{
namespace: 'test',
key: 'a'
},
{
namespace: 'test',
key: 'b'
},
{
namespace: 'test',
key: 'c'
}
]
};
@@ -92,55 +68,55 @@ define([
composition.on('add', listener);
return composition.load().then(function () {
expect(listener.calls.count()).toBe(3);
expect(listener.calls.count()).toBe(1);
expect(listener).toHaveBeenCalledWith({
identifier: {namespace: 'test', key: 'a'}
});
});
});
describe('supports reordering of composition', function () {
var listener;
beforeEach(function () {
listener = jasmine.createSpy('reorderListener');
composition.on('reorder', listener);
return composition.load();
});
it('', function () {
composition.reorder(1, 0);
let newComposition =
publicAPI.objects.mutate.calls.mostRecent().args[2];
let reorderPlan = listener.calls.mostRecent().args[0][0];
expect(reorderPlan.oldIndex).toBe(1);
expect(reorderPlan.newIndex).toBe(0);
expect(newComposition[0].key).toEqual('b');
expect(newComposition[1].key).toEqual('a');
expect(newComposition[2].key).toEqual('c');
});
it('', function () {
composition.reorder(0, 2);
let newComposition =
publicAPI.objects.mutate.calls.mostRecent().args[2];
let reorderPlan = listener.calls.mostRecent().args[0][0];
expect(reorderPlan.oldIndex).toBe(0);
expect(reorderPlan.newIndex).toBe(2);
expect(newComposition[0].key).toEqual('b');
expect(newComposition[1].key).toEqual('c');
expect(newComposition[2].key).toEqual('a');
})
});
it('supports adding an object to composition', function () {
let addListener = jasmine.createSpy('addListener');
let mockChildObject = {
identifier: {key: 'mock-key', namespace: ''}
};
// TODO: Implement add/removal in new default provider.
xit('synchronizes changes between instances', function () {
var otherComposition = compositionAPI.get(domainObject);
var addListener = jasmine.createSpy('addListener');
var removeListener = jasmine.createSpy('removeListener');
var otherAddListener = jasmine.createSpy('otherAddListener');
var otherRemoveListener = jasmine.createSpy('otherRemoveListener');
composition.on('add', addListener);
composition.add(mockChildObject);
composition.on('remove', removeListener);
otherComposition.on('add', otherAddListener);
otherComposition.on('remove', otherRemoveListener);
expect(domainObject.composition.length).toBe(4);
expect(domainObject.composition[3]).toEqual(mockChildObject.identifier);
return Promise.all([composition.load(), otherComposition.load()])
.then(function () {
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
expect(removeListener).not.toHaveBeenCalled();
expect(otherRemoveListener).not.toHaveBeenCalled();
var object = addListener.calls.mostRecent().args[0];
composition.remove(object);
expect(removeListener).toHaveBeenCalled();
expect(otherRemoveListener).toHaveBeenCalled();
addListener.reset();
otherAddListener.reset();
composition.add(object);
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
removeListener.reset();
otherRemoveListener.reset();
otherComposition.remove(object);
expect(removeListener).toHaveBeenCalled();
expect(otherRemoveListener).toHaveBeenCalled();
addListener.reset();
otherAddListener.reset();
otherComposition.add(object);
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
});
});
});
@@ -163,9 +139,7 @@ define([
key: 'thing'
}
]);
},
add: jasmine.createSpy('add'),
remove: jasmine.createSpy('remove')
}
};
domainObject = {
identifier: {
@@ -195,25 +169,6 @@ define([
});
});
});
describe('Calling add or remove', function () {
let mockChildObject;
beforeEach(function () {
mockChildObject = {
identifier: {key: 'mock-key', namespace: ''}
};
composition.add(mockChildObject);
});
it('calls add on the provider', function () {
expect(customProvider.add).toHaveBeenCalledWith(domainObject, mockChildObject.identifier);
});
it('calls remove on the provider', function () {
composition.remove(mockChildObject);
expect(customProvider.remove).toHaveBeenCalledWith(domainObject, mockChildObject.identifier);
});
});
});
describe('dynamic custom composition', function () {

View File

@@ -25,6 +25,7 @@ define([
], function (
_
) {
/**
* A CompositionCollection represents the list of domain objects contained
* by another domain object. It provides methods for loading this
@@ -55,13 +56,13 @@ define([
this.listeners = {
add: [],
remove: [],
load: [],
reorder: []
load: []
};
this.onProviderAdd = this.onProviderAdd.bind(this);
this.onProviderRemove = this.onProviderRemove.bind(this);
}
/**
* Listen for changes to this composition. Supports 'add', 'remove', and
* 'load' events.
@@ -74,9 +75,7 @@ define([
if (!this.listeners[event]) {
throw new Error('Event not supported by composition: ' + event);
}
if (!this.mutationListener) {
this._synchronize();
}
if (this.provider.on && this.provider.off) {
if (event === 'add') {
this.provider.on(
@@ -92,13 +91,6 @@ define([
this.onProviderRemove,
this
);
} if (event === 'reorder') {
this.provider.on(
this.domainObject,
'reorder',
this.onProviderReorder,
this
)
}
}
@@ -132,8 +124,6 @@ define([
this.listeners[event].splice(index, 1);
if (this.listeners[event].length === 0) {
this._destroy();
// Remove provider listener if this is the last callback to
// be removed.
if (this.provider.off && this.provider.on) {
@@ -151,13 +141,6 @@ define([
this.onProviderRemove,
this
);
} else if (event === 'reorder') {
this.provider.off(
this.domainObject,
'reorder',
this.onProviderReorder,
this
);
}
}
}
@@ -177,9 +160,6 @@ define([
*/
CompositionCollection.prototype.add = function (child, skipMutate) {
if (!skipMutate) {
if (!this.publicAPI.composition.checkPolicy(this.domainObject, child)) {
throw `Object of type ${child.type} cannot be added to object of type ${this.domainObject.type}`;
}
this.provider.add(this.domainObject, child.identifier);
} else {
this.emit('add', child);
@@ -229,29 +209,6 @@ define([
}
};
/**
* Reorder the domain objects in this composition.
*
* A call to [load]{@link module:openmct.CompositionCollection#load}
* must have resolved before using this method.
*
* @param {number} oldIndex
* @param {number} newIndex
* @memberof module:openmct.CompositionCollection#
* @name remove
*/
CompositionCollection.prototype.reorder = function (oldIndex, newIndex, skipMutate) {
this.provider.reorder(this.domainObject, oldIndex, newIndex);
};
/**
* Handle reorder from provider.
* @private
*/
CompositionCollection.prototype.onProviderReorder = function (reorderMap) {
this.emit('reorder', reorderMap);
};
/**
* Handle adds from provider.
* @private
@@ -271,29 +228,16 @@ define([
this.remove(child, true);
};
CompositionCollection.prototype._synchronize = function () {
this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => {
this.domainObject = JSON.parse(JSON.stringify(newDomainObject));
});
};
CompositionCollection.prototype._destroy = function () {
if (this.mutationListener) {
this.mutationListener();
delete this.mutationListener;
}
};
/**
* Emit events.
* @private
*/
CompositionCollection.prototype.emit = function (event, ...payload) {
CompositionCollection.prototype.emit = function (event, payload) {
this.listeners[event].forEach(function (l) {
if (l.context) {
l.callback.apply(l.context, payload);
l.callback.call(l.context, payload);
} else {
l.callback(...payload);
l.callback(payload);
}
});
};

View File

@@ -48,11 +48,24 @@ define([
this.listeningTo = {};
this.onMutation = this.onMutation.bind(this);
this.cannotContainDuplicates = this.cannotContainDuplicates.bind(this);
this.cannotContainItself = this.cannotContainItself.bind(this);
compositionAPI.addPolicy(this.cannotContainDuplicates);
compositionAPI.addPolicy(this.cannotContainItself);
}
/**
* @private
*/
DefaultCompositionProvider.prototype.cannotContainDuplicates = function (parent, child) {
return this.appliesTo(parent) &&
parent.composition.findIndex((composeeId) => {
return composeeId.namespace === child.identifier.namespace &&
composeeId.key === child.identifier.key;
}) === -1;
}
/**
* @private
*/
@@ -113,7 +126,6 @@ define([
objectListeners = this.listeningTo[keyString] = {
add: [],
remove: [],
reorder: [],
composition: [].slice.apply(domainObject.composition)
};
}
@@ -148,7 +160,7 @@ define([
});
objectListeners[event].splice(index, 1);
if (!objectListeners.add.length && !objectListeners.remove.length && !objectListeners.reorder.length) {
if (!objectListeners.add.length && !objectListeners.remove.length) {
delete this.listeningTo[keyString];
}
};
@@ -166,12 +178,8 @@ define([
* @method remove
*/
DefaultCompositionProvider.prototype.remove = function (domainObject, childId) {
let composition = domainObject.composition.filter(function (child) {
return !(childId.namespace === child.namespace &&
childId.key === child.key);
});
this.publicAPI.objects.mutate(domainObject, 'composition', composition);
// TODO: this needs to be synchronized via mutation.
throw new Error('Default Provider does not implement removal.');
};
/**
@@ -186,66 +194,9 @@ define([
* @memberof module:openmct.CompositionProvider#
* @method add
*/
DefaultCompositionProvider.prototype.add = function (parent, childId) {
if (!this.includes(parent, childId)) {
parent.composition.push(childId);
this.publicAPI.objects.mutate(parent, 'composition', parent.composition);
}
};
/**
* @private
*/
DefaultCompositionProvider.prototype.includes = function (parent, childId) {
return parent.composition.findIndex(composee =>
this.publicAPI.objects.areIdsEqual(composee, childId)) !== -1;
};
DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {
let newComposition = domainObject.composition.slice();
let removeId = oldIndex > newIndex ? oldIndex + 1 : oldIndex;
let insertPosition = oldIndex < newIndex ? newIndex + 1 : newIndex;
//Insert object in new position
newComposition.splice(insertPosition, 0, domainObject.composition[oldIndex]);
newComposition.splice(removeId, 1);
let reorderPlan = [{
oldIndex,
newIndex
}];
if (oldIndex > newIndex) {
for (let i = newIndex; i < oldIndex; i++) {
reorderPlan.push({
oldIndex: i,
newIndex: i + 1
});
}
} else {
for (let i = oldIndex + 1; i <= newIndex; i++) {
reorderPlan.push({
oldIndex: i,
newIndex: i - 1
});
}
}
this.publicAPI.objects.mutate(domainObject, 'composition', newComposition);
let id = objectUtils.makeKeyString(domainObject.identifier);
var listeners = this.listeningTo[id];
if (!listeners) {
return;
}
listeners.reorder.forEach(notify);
function notify(listener) {
if (listener.context) {
listener.callback.call(listener.context, reorderPlan);
} else {
listener.callback(reorderPlan);
}
}
DefaultCompositionProvider.prototype.add = function (domainObject, child) {
throw new Error('Default Provider does not implement adding.');
// TODO: this needs to be synchronized via mutation
};
/**

View File

@@ -21,10 +21,8 @@
*****************************************************************************/
define([
'./object-utils.js',
'lodash'
], function (
utils,
_
) {
var ANY_OBJECT_EVENT = "mutation";
@@ -43,9 +41,7 @@ define([
}
function qualifiedEventName(object, eventName) {
var keystring = utils.makeKeyString(object.identifier);
return [keystring, eventName].join(':');
return [object.identifier.key, eventName].join(':');
}
MutableObject.prototype.stopListening = function () {

View File

@@ -226,20 +226,7 @@ define([
(identifier.namespace === identifiers[0].namespace &&
identifier.key === identifiers[0].key);
});
};
ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
return this.get(identifier).then((domainObject) => {
path.push(domainObject);
let location = domainObject.location;
if (location) {
return this.getOriginalPath(utils.parseKeyString(location), path);
} else {
return path;
}
});
};
}
/**
* Uniquely identifies a domain object.

View File

@@ -5,8 +5,7 @@ import Vue from 'vue';
const cssClasses = {
large: 'l-overlay-large',
small: 'l-overlay-small',
fit: 'l-overlay-fit',
fullscreen: 'l-overlay-fullscreen'
fit: 'l-overlay-fit'
};
class Overlay extends EventEmitter {

View File

@@ -27,16 +27,10 @@
<style lang="scss">
@import "~styles/sass-base";
@mixin legacyMessage() {
flex: 0 1 auto;
font-family: symbolsfont;
font-size: $messageIconD; // Singleton message in a dialog
margin-right: $interiorMarginLg;
}
.c-message {
display: flex;
align-items: center;
padding: $interiorMarginLg;
> * + * {
margin-left: $interiorMarginLg;
@@ -64,44 +58,7 @@
&__title,
&__action-text {
font-size: 1.2em; // TEMP
}
&--simple {
// Icon and text elements only
&:before {
font-size: 30px !important;
}
[class*='__text'] {
font-size: 1.25em;
}
}
/************************** LEGACY */
&.message-severity-info:before {
@include legacyMessage();
content: $glyph-icon-info;
color: $colorInfo;
}
&.message-severity-alert:before {
@include legacyMessage();
content: $glyph-icon-alert-rect;
color: $colorWarningLo;
}
&.message-severity-error:before {
@include legacyMessage();
content: $glyph-icon-alert-triangle;
color: $colorWarningLo;
}
// Messages in a list
.c-overlay__messages & {
padding: $interiorMarginLg;
&:before {
font-size: $messageListIconD;
}
}
}
</style>

View File

@@ -56,45 +56,16 @@
}
&__close-button {
$p: $interiorMargin;
$p: $interiorMarginSm;
border-radius: 100% !important;
color: $overlayColorFg;
display: inline-block;
font-size: 1.25em;
position: absolute;
top: $p; right: $p;
}
&__contents {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
&__top-bar {
flex: 0 0 auto;
flex-direction: column;
display: flex;
> * {
flex: 0 0 auto;
margin-bottom: $interiorMargin;
}
}
&__dialog-title {
@include ellipsize();
font-size: 1.5em;
line-height: 120%;
}
&__contents-main {
display: flex;
flex-direction: column;
flex: 1 1 auto;
height: 0; // Chrome 73 overflow bug fix
overflow: auto;
padding-right: $interiorMargin; // fend off scroll bar
}
&__button-bar {
@@ -118,29 +89,18 @@
.c-overlay {
&__blocker {
@include abs();
background: $colorOvrBlocker;
background: rgba(black, 0.7);
cursor: pointer;
display: block;
}
}
// Overlay types, styling for desktop. Appended to .l-overlay-wrapper element.
.l-overlay-large,
.l-overlay-small,
.l-overlay-fit {
.c-overlay__outer {
&__outer {
border-radius: $overlayCr;
box-shadow: rgba(black, 0.5) 0 2px 25px;
}
}
.l-overlay-fullscreen {
// Used by About > Licenses display
.c-overlay__outer {
@include overlaySizing($overlayOuterMarginFullscreen);
}
}
// Overlay types, styling for desktop. Appended to .l-overlay-wrapper element.
.l-overlay-large {
// Default
.c-overlay__outer {
@@ -154,7 +114,6 @@
}
}
.t-dialog-sm .l-overlay-small, // Legacy dialog support
.l-overlay-fit {
.c-overlay__outer {
@include overlaySizing(auto);

View File

@@ -280,11 +280,7 @@ define([
if (!provider) {
return Promise.reject('No provider found');
}
return provider.request.apply(provider, arguments).catch((rejected) => {
this.openmct.notifications.error('Error requesting telemetry data, see console for details');
console.error(rejected);
return Promise.reject(rejected);
});
return provider.request.apply(provider, arguments);
};
/**
@@ -301,7 +297,7 @@ define([
* @returns {Function} a function which may be called to terminate
* the subscription
*/
TelemetryAPI.prototype.subscribe = function (domainObject, callback, options) {
TelemetryAPI.prototype.subscribe = function (domainObject, callback) {
var provider = this.findSubscriptionProvider(domainObject);
if (!this.subscribeCache) {
@@ -320,7 +316,7 @@ define([
subscriber.callbacks.forEach(function (cb) {
cb(value);
});
}, options);
});
} else {
subscriber.unsubscribe = function () {};
}

View File

@@ -28,22 +28,14 @@ define([
describe('Telemetry API', function () {
var openmct;
var telemetryAPI;
var mockTypeService;
beforeEach(function () {
openmct = {
time: jasmine.createSpyObj('timeAPI', [
'timeSystem',
'bounds'
]),
$injector: jasmine.createSpyObj('injector', [
'get'
])
};
mockTypeService = jasmine.createSpyObj('typeService', [
'getType'
]);
openmct.$injector.get.and.returnValue(mockTypeService);
openmct.time.timeSystem.and.returnValue({key: 'system'});
openmct.time.bounds.and.returnValue({start: 0, end: 1});
telemetryAPI = new TelemetryAPI(openmct);
@@ -304,233 +296,5 @@ define([
);
});
});
describe('metadata', function () {
let mockMetadata = {};
let mockObjectType = {
typeDef: {}
};
beforeEach(function () {
telemetryAPI.addProvider({
key: 'mockMetadataProvider',
supportsMetadata() {
return true;
},
getMetadata() {
return mockMetadata;
}
});
mockTypeService.getType.and.returnValue(mockObjectType);
})
it('respects explicit priority', function () {
mockMetadata.values = [
{
key: "name",
name: "Name",
hints: {
priority: 2
}
},
{
key: "timestamp",
name: "Timestamp",
hints: {
priority: 1
}
},
{
key: "sin",
name: "Sine",
hints: {
priority: 4
}
},
{
key: "cos",
name: "Cosine",
hints: {
priority: 3
}
}
];
let metadata = telemetryAPI.getMetadata({});
let values = metadata.values();
values.forEach((value, index) => {
expect(value.hints.priority).toBe(index + 1);
});
});
it('if no explicit priority, defaults to order defined', function () {
mockMetadata.values = [
{
key: "name",
name: "Name"
},
{
key: "timestamp",
name: "Timestamp"
},
{
key: "sin",
name: "Sine"
},
{
key: "cos",
name: "Cosine"
}
];
let metadata = telemetryAPI.getMetadata({});
let values = metadata.values();
values.forEach((value, index) => {
expect(value.key).toBe(mockMetadata.values[index].key);
});
});
it('respects domain priority', function () {
mockMetadata.values = [
{
key: "name",
name: "Name"
},
{
key: "timestamp-utc",
name: "Timestamp UTC",
hints: {
domain: 2
}
},
{
key: "timestamp-local",
name: "Timestamp Local",
hints: {
domain: 1
}
},
{
key: "sin",
name: "Sine",
hints: {
range: 2
}
},
{
key: "cos",
name: "Cosine",
hints: {
range: 1
}
}
];
let metadata = telemetryAPI.getMetadata({});
let values = metadata.valuesForHints(['domain']);
expect(values[0].key).toBe('timestamp-local');
expect(values[1].key).toBe('timestamp-utc');
});
it('respects range priority', function () {
mockMetadata.values = [
{
key: "name",
name: "Name"
},
{
key: "timestamp-utc",
name: "Timestamp UTC",
hints: {
domain: 2
}
},
{
key: "timestamp-local",
name: "Timestamp Local",
hints: {
domain: 1
}
},
{
key: "sin",
name: "Sine",
hints: {
range: 2
}
},
{
key: "cos",
name: "Cosine",
hints: {
range: 1
}
}
];
let metadata = telemetryAPI.getMetadata({});
let values = metadata.valuesForHints(['range']);
expect(values[0].key).toBe('cos');
expect(values[1].key).toBe('sin');
});
it('respects priority and domain ordering', function () {
mockMetadata.values = [
{
key: "id",
name: "ID",
hints: {
priority: 2
}
},
{
key: "name",
name: "Name",
hints: {
priority: 1
}
},
{
key: "timestamp-utc",
name: "Timestamp UTC",
hints: {
domain: 2,
priority: 1
}
},
{
key: "timestamp-local",
name: "Timestamp Local",
hints: {
domain: 1,
priority: 2
}
},
{
key: "timestamp-pst",
name: "Timestamp PST",
hints: {
domain: 3,
priority: 2
}
},
{
key: "sin",
name: "Sine"
},
{
key: "cos",
name: "Cosine"
}
];
let metadata = telemetryAPI.getMetadata({});
let values = metadata.valuesForHints(['priority', 'domain']);
[
'timestamp-utc',
'timestamp-local',
'timestamp-pst'
].forEach((key, index) => {
expect(values[index].key).toBe(key);
});
});
})
});
});

View File

@@ -116,18 +116,14 @@ define([
return hints.every(hasHint, metadata);
}
var matchingMetadata = this.valueMetadatas.filter(hasHints);
let iteratees = hints.map(hint => {
return (metadata) => {
var sortedMetadata = _.sortBy(matchingMetadata, function (metadata) {
return hints.map(function (hint) {
return metadata.hints[hint];
}
});
});
return _.sortByAll(matchingMetadata, ...iteratees);
return sortedMetadata;
};
TelemetryMetadataManager.prototype.getFilterableValues = function () {
return this.valueMetadatas.filter(metadatum => metadatum.filters && metadatum.filters.length > 0);
}
TelemetryMetadataManager.prototype.getDefaultDisplayValue = function () {
let valueMetadata = this.valuesForHints(['range'])[0];

View File

@@ -35,9 +35,6 @@ define([
canView: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
view: function (domainObject) {
let component;

View File

@@ -21,7 +21,7 @@
*****************************************************************************/
define([
'./components/LADTable.vue',
'./components/LadTable.vue',
'vue'
], function (
LadTableComponent,
@@ -35,9 +35,6 @@ define([
canView: function (domainObject) {
return domainObject.type === 'LadTable';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTable';
},
view: function (domainObject) {
let component;

View File

@@ -41,7 +41,7 @@
<script>
import lodash from 'lodash';
import LadRow from './LADRow.vue';
import LadRow from './LadRow.vue';
export default {
inject: ['openmct', 'domainObject'],
@@ -65,25 +65,17 @@ export default {
let index = _.findIndex(this.items, (item) => this.openmct.objects.makeKeyString(identifier) === item.key);
this.items.splice(index, 1);
},
reorder(reorderPlan) {
let oldItems = this.items.slice();
reorderPlan.forEach((reorderEvent) => {
this.$set(this.items, reorderEvent.newIndex, oldItems[reorderEvent.oldIndex]);
});
}
},
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.reorder);
this.composition.load();
},
destroyed() {
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.reorder);
}
}
</script>

View File

@@ -52,7 +52,7 @@
<script>
import lodash from 'lodash';
import LadRow from './LADRow.vue';
import LadRow from './LadRow.vue';
export default {
inject: ['openmct', 'domainObject'],
@@ -93,12 +93,6 @@
this.primaryTelemetryObjects.splice(index,1);
primary = undefined;
},
reorderPrimary(reorderPlan) {
let oldComposition = this.primaryTelemetryObjects.slice();
reorderPlan.forEach(reorderEvent => {
this.$set(this.primaryTelemetryObjects, reorderEvent.newIndex, oldComposition[reorderEvent.oldIndex]);
});
},
addSecondary(primary) {
return (domainObject) => {
let secondary = {};
@@ -126,13 +120,11 @@
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addPrimary);
this.composition.on('remove', this.removePrimary);
this.composition.on('reorder', this.reorderPrimary);
this.composition.load();
},
destroyed() {
this.composition.off('add', this.addPrimary);
this.composition.off('remove', this.removePrimary);
this.composition.off('reorder', this.reorderPrimary);
this.compositions.forEach(c => {
c.composition.off('add', c.addCallback);
c.composition.off('remove', c.removeCallback);

View File

@@ -1,77 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./components/AlphanumericFormatView.vue',
'vue'
], function (AlphanumericFormatView, Vue) {
function AlphanumericFormatViewProvider(openmct, options) {
function isTelemetryObject(selectionPath) {
let selectedObject = selectionPath[0].context.item;
let parentObject = selectionPath[1].context.item;
return parentObject &&
parentObject.type === 'layout' &&
selectedObject &&
openmct.telemetry.isTelemetryObject(selectedObject) &&
!options.showAsView.includes(selectedObject.type)
}
return {
key: 'alphanumeric-format',
name: 'Alphanumeric Format',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 1) {
return false;
}
return selection.every(isTelemetryObject);
},
view: function (selection) {
let component;
return {
show: function (element) {
component = new Vue({
provide: {
openmct
},
components: {
AlphanumericFormatView: AlphanumericFormatView.default
},
template: '<alphanumeric-format-view></alphanumeric-format-view>',
el: element
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
}
},
priority: function () {
return 1;
}
}
}
return AlphanumericFormatViewProvider;
});

View File

@@ -28,17 +28,11 @@ define([], function () {
key: "layout",
description: "A toolbar for objects inside a display layout.",
forSelection: function (selection) {
if (!selection || selection.length === 0) {
return false;
}
let selectionPath = selection[0];
let selectedObject = selectionPath[0];
let selectedParent = selectionPath[1];
// Apply the layout toolbar if the selected object is inside a layout, or the main layout is selected.
return (selectedParent && selectedParent.context.item && selectedParent.context.item.type === 'layout') ||
(selectedObject.context.item && selectedObject.context.item.type === 'layout');
// Apply the layout toolbar if the edit mode is on, and the selected object
// is inside a layout, or the main layout is selected.
return (openmct.editor.isEditing() && selection &&
((selection[1] && selection[1].context.item && selection[1].context.item.type === 'layout') ||
(selection[0].context.item && selection[0].context.item.type === 'layout')));
},
toolbar: function (selection) {
const DIALOG_FORM = {
@@ -79,72 +73,190 @@ define([], function () {
return openmct.$injector.get('dialogService').getUserInput(form, {});
}
function getPath(selectionPath) {
return `configuration.items[${selectionPath[0].context.index}]`;
function getPath() {
return `configuration.items[${selection[0].context.index}]`;
}
function getAllTypes(selection) {
return selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'image-view' ||
type === 'line-view' ||
type === 'subobject-view';
let selectedParent = selection[1] && selection[1].context.item,
selectedObject = selection[0].context.item,
layoutItem = selection[0].context.layoutItem,
toolbar = [];
if (selectedObject && selectedObject.type === 'layout') {
toolbar.push({
control: "menu",
domainObject: selectedObject,
method: function (option) {
let name = option.name.toLowerCase();
let form = DIALOG_FORM[name];
if (form) {
getUserInput(form)
.then(element => selection[0].context.addElement(name, element));
} else {
selection[0].context.addElement(name);
}
},
key: "add",
icon: "icon-plus",
label: "Add",
options: [
{
"name": "Box",
"class": "icon-box-round-corners"
},
{
"name": "Line",
"class": "icon-line-horz"
},
{
"name": "Text",
"class": "icon-font"
},
{
"name": "Image",
"class": "icon-image"
}
]
});
}
function getAddButton(selection, selectionPath) {
if (selection.length === 1) {
selectionPath = selectionPath || selection[0];
return {
control: "menu",
domainObject: selectionPath[0].context.item,
method: function (option) {
let name = option.name.toLowerCase();
let form = DIALOG_FORM[name];
if (form) {
getUserInput(form)
.then(element => selectionPath[0].context.addElement(name, element));
} else {
selectionPath[0].context.addElement(name);
}
},
key: "add",
icon: "icon-plus",
label: "Add",
options: [
{
"name": "Box",
"class": "icon-box-round-corners"
},
{
"name": "Line",
"class": "icon-line-horz"
},
{
"name": "Text",
"class": "icon-font"
},
{
"name": "Image",
"class": "icon-image"
}
]
};
}
if (!layoutItem) {
return toolbar;
}
function getToggleFrameButton(selectedParent, selection) {
return {
let separator = {
control: "separator"
};
let remove = {
control: "button",
domainObject: selectedParent,
icon: "icon-trash",
title: "Delete the selected object",
method: function () {
let removeItem = selection[1].context.removeItem;
let prompt = openmct.overlays.dialog({
iconClass: 'alert',
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
buttons: [
{
label: 'Ok',
emphasis: 'true',
callback: function () {
removeItem(layoutItem, selection[0].context.index);
prompt.dismiss();
}
},
{
label: 'Cancel',
callback: function () {
prompt.dismiss();
}
}
]
});
}
};
let stackOrder = {
control: "menu",
domainObject: selectedParent,
icon: "icon-layers",
title: "Move the selected object above or below other objects",
options: [
{
name: "Move to Top",
value: "top",
class: "icon-arrow-double-up"
},
{
name: "Move Up",
value: "up",
class: "icon-arrow-up"
},
{
name: "Move Down",
value: "down",
class: "icon-arrow-down"
},
{
name: "Move to Bottom",
value: "bottom",
class: "icon-arrow-double-down"
}
],
method: function (option) {
selection[1].context.orderItem(option.value, selection[0].context.index);
}
};
let useGrid = {
control: "toggle-button",
domainObject: selectedParent,
property: function () {
return getPath() + ".useGrid";
},
options: [
{
value: false,
icon: "icon-grid-snap-to",
title: "Grid snapping enabled"
},
{
value: true,
icon: "icon-grid-snap-no",
title: "Grid snapping disabled"
}
]
};
let x = {
control: "input",
type: "number",
domainObject: selectedParent,
property: function () {
return getPath() + ".x";
},
label: "X:",
title: "X position"
},
y = {
control: "input",
type: "number",
domainObject: selectedParent,
property: function () {
return getPath() + ".y";
},
label: "Y:",
title: "Y position",
},
width = {
control: 'input',
type: 'number',
domainObject: selectedParent,
property: function () {
return getPath() + ".width";
},
label: 'W:',
title: 'Resize object width'
},
height = {
control: 'input',
type: 'number',
domainObject: selectedParent,
property: function () {
return getPath() + ".height";
},
label: 'H:',
title: 'Resize object height'
};
if (layoutItem.type === 'subobject-view') {
if (toolbar.length > 0) {
toolbar.push(separator);
}
toolbar.push({
control: "toggle-button",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath =>
selectionPath[0].context.layoutItem.type === 'subobject-view'
),
property: function (selectionPath) {
return getPath(selectionPath) + ".hasFrame";
property: function () {
return getPath() + ".hasFrame";
},
options: [
{
@@ -158,186 +270,52 @@ define([], function () {
title: "Frame hidden"
}
]
};
}
function getRemoveButton(selectedParent, selectionPath, selection) {
return {
control: "button",
domainObject: selectedParent,
icon: "icon-trash",
title: "Delete the selected object",
method: function () {
let removeItem = selectionPath[1].context.removeItem;
let prompt = openmct.overlays.dialog({
iconClass: 'alert',
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
buttons: [
{
label: 'Ok',
emphasis: 'true',
callback: function () {
removeItem(getAllTypes(selection));
prompt.dismiss();
}
},
{
label: 'Cancel',
callback: function () {
prompt.dismiss();
}
}
]
});
}
};
}
function getStackOrder(selectedParent, selectionPath) {
return {
control: "menu",
domainObject: selectedParent,
icon: "icon-layers",
title: "Move the selected object above or below other objects",
options: [
{
name: "Move to Top",
value: "top",
class: "icon-arrow-double-up"
},
{
name: "Move Up",
value: "up",
class: "icon-arrow-up"
},
{
name: "Move Down",
value: "down",
class: "icon-arrow-down"
},
{
name: "Move to Bottom",
value: "bottom",
class: "icon-arrow-double-down"
}
],
method: function (option) {
selectionPath[1].context.orderItem(option.value, getAllTypes(selection));
}
};
}
function getXInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: "input",
type: "number",
domainObject: selectedParent,
applicableSelectedItems: getAllTypes(selection),
property: function (selectionPath) {
return getPath(selectionPath) + ".x";
},
label: "X:",
title: "X position"
};
}
}
function getYInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: "input",
type: "number",
domainObject: selectedParent,
applicableSelectedItems: getAllTypes(selection),
property: function (selectionPath) {
return getPath(selectionPath) + ".y";
},
label: "Y:",
title: "Y position",
};
}
}
function getWidthInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: 'input',
type: 'number',
domainObject: selectedParent,
applicableSelectedItems: getAllTypes(selection),
property: function (selectionPath) {
return getPath(selectionPath) + ".width";
},
label: 'W:',
title: 'Resize object width'
};
}
}
function getHeightInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: 'input',
type: 'number',
domainObject: selectedParent,
applicableSelectedItems: getAllTypes(selection),
property: function (selectionPath) {
return getPath(selectionPath) + ".height";
},
label: 'H:',
title: 'Resize object height'
};
}
}
function getX2Input(selectedParent, selection) {
if (selection.length === 1) {
return {
control: "input",
type: "number",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'line-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".x2";
},
label: "X2:",
title: "X2 position"
};
}
}
function getY2Input(selectedParent, selection) {
if (selection.length === 1) {
return {
control: "input",
type: "number",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'line-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".y2";
},
label: "Y2:",
title: "Y2 position",
};
}
}
function getTextSizeMenu(selectedParent, selection) {
});
toolbar.push(separator);
toolbar.push(stackOrder);
toolbar.push(x);
toolbar.push(y);
toolbar.push(width);
toolbar.push(height);
toolbar.push(useGrid);
toolbar.push(separator);
toolbar.push(remove);
} else {
const TEXT_SIZE = [8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96, 128];
return {
let fill = {
control: "color-picker",
domainObject: selectedParent,
property: function () {
return getPath() + ".fill";
},
icon: "icon-paint-bucket",
title: "Set fill color"
},
stroke = {
control: "color-picker",
domainObject: selectedParent,
property: function () {
return getPath() + ".stroke";
},
icon: "icon-line-horz",
title: "Set border color"
},
color = {
control: "color-picker",
domainObject: selectedParent,
property: function () {
return getPath() + ".color";
},
icon: "icon-font",
mandatory: true,
title: "Set text color",
preventNone: true
},
size = {
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";
property: function () {
return getPath() + ".size";
},
title: "Set text size",
options: TEXT_SIZE.map(size => {
@@ -346,128 +324,13 @@ define([], function () {
};
})
};
}
function getFillMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".fill";
},
icon: "icon-paint-bucket",
title: "Set fill color"
};
}
function getStrokeMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'image-view' ||
type === 'line-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".stroke";
},
icon: "icon-line-horz",
title: "Set border color"
};
}
function getTextColorMenu(selectedParent, selection) {
return {
control: "color-picker",
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) + ".color";
},
icon: "icon-font",
mandatory: true,
title: "Set text color",
preventNone: true
};
}
function getURLButton(selectedParent, selection) {
return {
control: "button",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'image-view';
}),
property: function (selectionPath) {
return getPath(selectionPath);
},
icon: "icon-image",
title: "Edit image properties",
dialog: DIALOG_FORM['image']
};
}
function getTextButton(selectedParent, selection) {
return {
control: "button",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'text-view';
}),
property: function (selectionPath) {
return getPath(selectionPath);
},
icon: "icon-gear",
title: "Edit text properties",
dialog: DIALOG_FORM['text']
};
}
function getTelemetryValueMenu(selectionPath, selection) {
if (selection.length === 1) {
return {
control: "select-menu",
domainObject: selectionPath[1].context.item,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'telemetry-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".value";
},
title: "Set value",
options: openmct.telemetry.getMetadata(selectionPath[0].context.item).values().map(value => {
return {
name: value.name,
value: value.key
}
})
};
}
}
function getDisplayModeMenu(selectedParent, selection) {
if (selection.length === 1) {
return {
if (layoutItem.type === 'telemetry-view') {
let displayMode = {
control: "select-menu",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'telemetry-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".displayMode";
property: function () {
return getPath() + ".displayMode";
},
title: "Set display mode",
options: [
@@ -484,196 +347,146 @@ define([], function () {
value: "value"
}
]
},
value = {
control: "select-menu",
domainObject: selectedParent,
property: function () {
return getPath() + ".value";
},
title: "Set value",
options: openmct.telemetry.getMetadata(selectedObject).values().map(value => {
return {
name: value.name,
value: value.key
}
})
};
}
}
function getSeparator() {
return {
control: "separator"
};
}
function isMainLayoutSelected(selectionPath) {
let selectedObject = selectionPath[0].context.item;
return selectedObject && selectedObject.type === 'layout' &&
!selectionPath[0].context.layoutItem;
}
if (isMainLayoutSelected(selection[0])) {
return [getAddButton(selection)];
}
let toolbar = {
'add-menu': [],
'toggle-frame': [],
'display-mode': [],
'telemetry-value': [],
'style': [],
'text-style': [],
'position': [],
'text': [],
'url': [],
'remove': [],
};
selection.forEach(selectionPath => {
let selectedParent = selectionPath[1].context.item;
let layoutItem = selectionPath[0].context.layoutItem;
if (layoutItem.type === 'subobject-view') {
if (toolbar['add-menu'].length === 0 && selectionPath[0].context.item.type === 'layout') {
toolbar['add-menu'] = [getAddButton(selection, selectionPath)];
}
if (toolbar['toggle-frame'].length === 0) {
toolbar['toggle-frame'] = [getToggleFrameButton(selectedParent, selection)];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getHeightInput(selectedParent, selection),
getWidthInput(selectedParent, selection)
];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
} else if (layoutItem.type === 'telemetry-view') {
if (toolbar['display-mode'].length === 0) {
toolbar['display-mode'] = [getDisplayModeMenu(selectedParent, selection)];
}
if (toolbar['telemetry-value'].length === 0) {
toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selection)];
}
if (toolbar['style'].length < 2) {
toolbar['style'] = [
getFillMenu(selectedParent, selection),
getStrokeMenu(selectedParent, selection)
];
}
if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [
getTextColorMenu(selectedParent, selection),
getTextSizeMenu(selectedParent, selection)
];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getHeightInput(selectedParent, selection),
getWidthInput(selectedParent, selection)
];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
toolbar = [
displayMode,
separator,
value,
separator,
fill,
stroke,
color,
separator,
size,
separator,
stackOrder,
x,
y,
height,
width,
useGrid,
separator,
remove
];
} else if (layoutItem.type === 'text-view') {
if (toolbar['style'].length < 2) {
toolbar['style'] = [
getFillMenu(selectedParent, selection),
getStrokeMenu(selectedParent, selection)
];
}
if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [
getTextColorMenu(selectedParent, selection),
getTextSizeMenu(selectedParent, selection)
];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getHeightInput(selectedParent, selection),
getWidthInput(selectedParent, selection)
];
}
if (toolbar['text'].length === 0) {
toolbar['text'] = [getTextButton(selectedParent, selection)];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
let text = {
control: "button",
domainObject: selectedParent,
property: function () {
return getPath();
},
icon: "icon-gear",
title: "Edit text properties",
dialog: DIALOG_FORM['text']
};
toolbar = [
fill,
stroke,
separator,
color,
size,
separator,
stackOrder,
x,
y,
height,
width,
useGrid,
separator,
text,
separator,
remove
];
} else if (layoutItem.type === 'box-view') {
if (toolbar['style'].length < 2) {
toolbar['style'] = [
getFillMenu(selectedParent, selection),
getStrokeMenu(selectedParent, selection)
];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getHeightInput(selectedParent, selection),
getWidthInput(selectedParent, selection)
];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
toolbar = [
fill,
stroke,
separator,
stackOrder,
x,
y,
height,
width,
useGrid,
separator,
remove
];
} else if (layoutItem.type === 'image-view') {
if (toolbar['style'].length === 0) {
toolbar['style'] = [
getStrokeMenu(selectedParent, selection)
];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getHeightInput(selectedParent, selection),
getWidthInput(selectedParent, selection)
];
}
if (toolbar['url'].length === 0) {
toolbar['url'] = [getURLButton(selectedParent, selection)];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
let url = {
control: "button",
domainObject: selectedParent,
property: function () {
return getPath();
},
icon: "icon-image",
title: "Edit image properties",
dialog: DIALOG_FORM['image']
};
toolbar = [
stroke,
separator,
stackOrder,
x,
y,
height,
width,
useGrid,
separator,
url,
separator,
remove
];
} else if (layoutItem.type === 'line-view') {
if (toolbar['style'].length === 0) {
toolbar['style'] = [
getStrokeMenu(selectedParent, selection)
];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getX2Input(selectedParent, selection),
getY2Input(selectedParent, selection)
];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
let x2 = {
control: "input",
type: "number",
domainObject: selectedParent,
property: function () {
return getPath() + ".x2";
},
label: "X2:",
title: "X2 position"
},
y2 = {
control: "input",
type: "number",
domainObject: selectedParent,
property: function () {
return getPath() + ".y2";
},
label: "Y2:",
title: "Y2 position",
};
toolbar = [
stroke,
separator,
stackOrder,
x,
y,
x2,
y2,
useGrid,
separator,
remove
];
}
});
}
let toolbarArray = Object.values(toolbar);
return _.flatten(toolbarArray.reduce((accumulator, group, index) => {
group = group.filter(control => control !== undefined);
if (group.length > 0) {
accumulator.push(group);
if (index < toolbarArray.length - 1) {
accumulator.push(getSeparator());
}
}
return accumulator;
}, []));
return toolbar;
}
}
}

View File

@@ -95,7 +95,7 @@ define(
* @param {number[]} pixelDelta the offset from the
* original position, in pixels
*/
LayoutDrag.prototype.getAdjustedPositionAndDimensions = function (pixelDelta) {
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
var gridDelta = toGridDelta(this.gridSize, pixelDelta);
return {
position: max(add(
@@ -109,16 +109,6 @@ define(
};
};
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
var gridDelta = toGridDelta(this.gridSize, pixelDelta);
return {
position: max(add(
this.rawPosition.position,
multiply(gridDelta, this.posFactor)
), [0, 0])
};
};
return LayoutDrag;
}

View File

@@ -1,90 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-properties" v-if="isEditing">
<div class="c-properties__header">Alphanumeric Format</div>
<ul class="c-properties__section">
<li class="c-properties__row">
<div class="c-properties__label" title="Printf formatting for the selected telemetry">
<label for="telemetryPrintfFormat">Format</label>
</div>
<div class="c-properties__value">
<input id="telemetryPrintfFormat"
type="text"
@change="formatTelemetry"
:value="telemetryFormat"
:placeholder="nonMixedFormat ? '' : 'Mixed'"
>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
inject: ['openmct'],
data() {
let selectionPath = this.openmct.selection.get()[0];
return {
isEditing: this.openmct.editor.isEditing(),
telemetryFormat: undefined,
nonMixedFormat: false
}
},
methods: {
toggleEdit(isEditing) {
this.isEditing = isEditing;
},
formatTelemetry(event) {
let newFormat = event.currentTarget.value;
this.openmct.selection.get().forEach(selectionPath => {
selectionPath[0].context.updateTelemetryFormat(newFormat);
});
this.telemetryFormat = newFormat;
},
handleSelection(selection) {
if (selection.length === 0 || selection[0].length < 2) {
return;
}
let format = selection[0][0].context.layoutItem.format;
this.nonMixedFormat = selection.every(selectionPath => {
return selectionPath[0].context.layoutItem.format === format;
});
this.telemetryFormat = this.nonMixedFormat ? format : '';
}
},
mounted() {
this.openmct.editor.on('isEditing', this.toggleEdit);
this.openmct.selection.on('change', this.handleSelection);
this.handleSelection(this.openmct.selection.get());
},
destroyed() {
this.openmct.editor.off('isEditing', this.toggleEdit);
this.openmct.selection.off('change', this.handleSelection);
}
}
</script>

View File

@@ -23,8 +23,7 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
<div class="c-box-view"
:style="style">
</div>
@@ -55,7 +54,8 @@
x: 1,
y: 1,
width: 10,
height: 5
height: 5,
useGrid: true
};
},
inject: ['openmct'],

View File

@@ -23,11 +23,8 @@
<template>
<div class="l-layout"
@dragover="handleDragOver"
@click.capture="bypassSelection"
@drop="handleDrop"
:class="{
'is-multi-selected': selectedLayoutItems.length > 1
}">
@click="bypassSelection"
@drop="handleDrop">
<!-- Background grid -->
<div class="l-layout__grid-holder c-grid">
<div class="c-grid__x l-grid l-grid-x"
@@ -42,39 +39,18 @@
:is="item.type"
:item="item"
:key="item.id"
:gridSize="gridSize"
:gridSize="item.useGrid ? gridSize : [1, 1]"
:initSelect="initSelectIndex === index"
:index="index"
:multiSelect="selectedLayoutItems.length > 1"
@move="move"
@endMove="endMove"
@endLineResize='endLineResize'
@formatChanged='updateTelemetryFormat'>
@endDrag="endDrag"
>
</component>
<edit-marquee v-if='showMarquee'
:gridSize="gridSize"
:selectedLayoutItems="selectedLayoutItems"
@endResize="endResize">
</edit-marquee>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
@mixin displayMarquee($c) {
> .c-frame-edit {
// All other frames
//@include test($c, 0.4);
display: block;
}
> .c-frame > .c-frame-edit {
// Line object frame
//@include test($c, 0.4);
display: block;
}
}
.l-layout {
@include abs();
display: flex;
@@ -94,7 +70,7 @@
.l-shell__main-container {
&[s-selected],
&[s-selected-parent] {
// Display grid and allow edit marquee to display in main layout holder when editing
// Display grid in main layout holder when editing
> .l-layout {
background: $editUIGridColorBg;
@@ -108,7 +84,7 @@
.l-layout__frame {
&[s-selected],
&[s-selected-parent] {
// Display grid and allow edit marquee to display in nested layouts when editing
// Display grid in nested layouts when editing
> * > * > .l-layout {
background: $editUIGridColorBg;
box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
@@ -119,21 +95,10 @@
}
}
}
/*********************** EDIT MARQUEE CONTROL */
*[s-selected-parent] {
> .l-layout {
// When main shell layout is the parent
@include displayMarquee(deeppink);
}
> * > * > * {
// When a sub-layout is the parent
@include displayMarquee(blue);
}
}
}
</style>
<script>
import uuid from 'uuid';
@@ -143,7 +108,6 @@
import TextView from './TextView.vue'
import LineView from './LineView.vue'
import ImageView from './ImageView.vue'
import EditMarquee from './EditMarquee.vue'
const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView,
@@ -159,10 +123,8 @@
down: -1,
bottom: Number.NEGATIVE_INFINITY
};
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
let components = ITEM_TYPE_VIEW_MAP;
components['edit-marquee'] = EditMarquee;
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
function getItemDefinition(itemType, ...options) {
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
@@ -179,8 +141,7 @@
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
return {
internalDomainObject: domainObject,
initSelectIndex: undefined,
selection: []
initSelectIndex: undefined
};
},
computed: {
@@ -189,145 +150,82 @@
},
layoutItems() {
return this.internalDomainObject.configuration.items;
},
selectedLayoutItems() {
return this.layoutItems.filter(item => {
return this.itemIsInCurrentSelection(item);
});
},
showMarquee() {
let selectionPath = this.selection[0];
let singleSelectedLine = this.selection.length === 1 &&
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view';
return selectionPath && selectionPath.length > 1 && !singleSelectedLine;
}
},
inject: ['openmct', 'options'],
inject: ['openmct'],
props: ['domainObject'],
components: components,
components: ITEM_TYPE_VIEW_MAP,
methods: {
addElement(itemType, element) {
this.addItem(itemType + '-view', element);
},
setSelection(selection) {
this.selection = selection;
if (selection.length === 0) {
return;
}
if (this.removeSelectionListener) {
this.removeSelectionListener();
}
let itemIndex = selection[0].context.index;
if (itemIndex !== undefined) {
this.attachSelectionListener(itemIndex);
}
},
itemIsInCurrentSelection(item) {
return this.selection.some(selectionPath =>
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.id === item.id);
attachSelectionListener(index) {
let path = `configuration.items[${index}].useGrid`;
this.removeSelectionListener = this.openmct.objects.observe(this.internalDomainObject, path, function (value) {
let item = this.layoutItems[index];
if (value) {
item.x = Math.round(item.x / this.gridSize[0]);
item.y = Math.round(item.y / this.gridSize[1]);
item.width = Math.round(item.width / this.gridSize[0]);
item.height = Math.round(item.height / this.gridSize[1]);
if (item.x2) {
item.x2 = Math.round(item.x2 / this.gridSize[0]);
}
if (item.y2) {
item.y2 = Math.round(item.y2 / this.gridSize[1]);
}
} else {
item.x = this.gridSize[0] * item.x;
item.y = this.gridSize[1] * item.y;
item.width = this.gridSize[0] * item.width;
item.height = this.gridSize[1] * item.height;
if (item.x2) {
item.x2 = this.gridSize[0] * item.x2;
}
if (item.y2) {
item.y2 = this.gridSize[1] * item.y2;
}
}
item.useGrid = value;
this.mutate(`configuration.items[${index}]`, item);
}.bind(this));
},
bypassSelection($event) {
if (this.dragInProgress) {
if ($event) {
$event.stopImmediatePropagation();
}
this.dragInProgress = false;
return;
}
},
endLineResize(item, updates) {
endDrag(item, updates) {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
let index = this.layoutItems.indexOf(item);
Object.assign(item, updates);
this.mutate(`configuration.items[${index}]`, item);
},
endResize(scaleWidth, scaleHeight, marqueeStart, marqueeOffset) {
this.dragInProgress = true;
this.layoutItems.forEach(item => {
if (this.itemIsInCurrentSelection(item)) {
let itemXInMarqueeSpace = item.x - marqueeStart.x;
let itemXInMarqueeSpaceAfterScale = Math.round(itemXInMarqueeSpace * scaleWidth);
item.x = itemXInMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x;
let itemYInMarqueeSpace = item.y - marqueeStart.y;
let itemYInMarqueeSpaceAfterScale = Math.round(itemYInMarqueeSpace * scaleHeight);
item.y = itemYInMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
if (item.x2) {
let itemX2InMarqueeSpace = item.x2 - marqueeStart.x;
let itemX2InMarqueeSpaceAfterScale = Math.round(itemX2InMarqueeSpace * scaleWidth);
item.x2 = itemX2InMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x;
} else {
item.width = Math.round(item.width * scaleWidth);
}
if (item.y2) {
let itemY2InMarqueeSpace = item.y2 - marqueeStart.y;
let itemY2InMarqueeSpaceAfterScale = Math.round(itemY2InMarqueeSpace * scaleHeight);
item.y2 = itemY2InMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
} else {
item.height = Math.round(item.height * scaleHeight);
}
}
});
this.mutate("configuration.items", this.layoutItems);
},
move(gridDelta) {
this.dragInProgress = true;
if (!this.initialPositions) {
this.initialPositions = {};
_.cloneDeep(this.selectedLayoutItems).forEach(selectedItem => {
if (selectedItem.type === 'line-view') {
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y, selectedItem.x2, selectedItem.y2];
this.startingMinX2 = this.startingMinX2 !== undefined ? Math.min(this.startingMinX2, selectedItem.x2) : selectedItem.x2;
this.startingMinY2 = this.startingMinY2 !== undefined ? Math.min(this.startingMinY2, selectedItem.y2) : selectedItem.y2;
} else {
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y];
}
this.startingMinX = this.startingMinX !== undefined ? Math.min(this.startingMinX, selectedItem.x) : selectedItem.x;
this.startingMinY = this.startingMinY !== undefined ? Math.min(this.startingMinY, selectedItem.y) : selectedItem.y;
});
}
let layoutItems = this.layoutItems.map(item => {
if (this.initialPositions[item.id]) {
this.updateItemPosition(item, gridDelta);
}
return item;
});
},
updateItemPosition(item, gridDelta) {
let startingPosition = this.initialPositions[item.id];
let [startingX, startingY, startingX2, startingY2] = startingPosition;
if (this.startingMinX + gridDelta[0] >= 0) {
if (item.x2 !== undefined) {
if (this.startingMinX2 + gridDelta[0] >= 0) {
item.x = startingX + gridDelta[0];
}
} else {
item.x = startingX + gridDelta[0];
}
}
if (this.startingMinY + gridDelta[1] >= 0) {
if (item.y2 !== undefined) {
if (this.startingMinY2 + gridDelta[1] >= 0) {
item.y = startingY + gridDelta[1];
}
} else {
item.y = startingY + gridDelta[1];
}
}
if (item.x2 !== undefined && this.startingMinX2 + gridDelta[0] >= 0 && this.startingMinX + gridDelta[0] >= 0) {
item.x2 = startingX2 + gridDelta[0];
}
if (item.y2 !== undefined && this.startingMinY2 + gridDelta[1] >= 0 && this.startingMinY + gridDelta[1] >= 0) {
item.y2 = startingY2 + gridDelta[1];
}
},
endMove() {
this.mutate('configuration.items', this.layoutItems);
this.initialPositions = undefined;
this.startingMinX = undefined;
this.startingMinY = undefined;
this.startingMinX2 = undefined;
this.startingMinY2 = undefined;
},
mutate(path, value) {
this.openmct.objects.mutate(this.internalDomainObject, path, value);
},
@@ -385,8 +283,9 @@
}
},
isTelemetry(domainObject) {
if (this.openmct.telemetry.isTelemetryObject(domainObject) &&
!this.options.showAsView.includes(domainObject.type)) {
if (this.openmct.telemetry.isTelemetryObject(domainObject)
&& domainObject.type !== 'summary-widget'
&& domainObject.type !== 'example.imagery') {
return true;
} else {
return false;
@@ -415,15 +314,11 @@
this.objectViewMap[keyString] = true;
}
},
removeItem(selectedItems) {
let indices = [];
removeItem(item, index) {
this.initSelectIndex = -1;
selectedItems.forEach(selectedItem => {
indices.push(selectedItem[0].context.index);
this.untrackItem(selectedItem[0].context.layoutItem);
});
_.pullAt(this.layoutItems, indices);
this.layoutItems.splice(index, 1);
this.mutate("configuration.items", this.layoutItems);
this.untrackItem(item);
this.$el.click();
},
untrackItem(item) {
@@ -489,80 +384,21 @@
this.mutate("configuration.items", layoutItems);
this.$el.click();
},
orderItem(position, selectedItems) {
orderItem(position, index) {
let delta = ORDERS[position];
let indices = [];
let newIndex = -1;
let items = [];
let newIndex = Math.max(Math.min(index + delta, this.layoutItems.length - 1), 0);
let item = this.layoutItems[index];
Object.assign(items, this.layoutItems);
this.selectedLayoutItems.forEach(selectedItem => {
indices.push(this.layoutItems.indexOf(selectedItem));
});
indices.sort((a, b) => a - b);
if (newIndex !== index) {
this.layoutItems.splice(index, 1);
this.layoutItems.splice(newIndex, 0, item);
this.mutate('configuration.items', this.layoutItems);
if (position === 'top' || position === 'up') {
indices.reverse();
}
if (position === 'top' || position === 'bottom') {
this.moveToTopOrBottom(position, indices, items, delta);
} else {
this.moveUpOrDown(position, indices, items, delta);
}
this.mutate('configuration.items', this.layoutItems);
},
moveUpOrDown(position, indices, items, delta) {
let previousItemIndex = -1;
let newIndex = -1;
indices.forEach((itemIndex, index) => {
let isAdjacentItemSelected = position === 'up' ?
itemIndex + 1 === previousItemIndex :
itemIndex - 1 === previousItemIndex;
if (index > 0 && isAdjacentItemSelected) {
if (position === 'up') {
newIndex -= 1;
} else {
newIndex += 1;
}
} else {
newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0);
if (this.removeSelectionListener) {
this.removeSelectionListener();
this.attachSelectionListener(newIndex);
}
previousItemIndex = itemIndex;
this.updateItemOrder(newIndex, itemIndex, items);
});
},
moveToTopOrBottom(position, indices, items, delta) {
let newIndex = -1;
indices.forEach((itemIndex, index) => {
if (index === 0) {
newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0);
} else {
if (position === 'top') {
newIndex -= 1;
} else {
newIndex += 1;
}
}
this.updateItemOrder(newIndex, itemIndex, items);
});
},
updateItemOrder(newIndex, itemIndex, items) {
if (newIndex !== itemIndex) {
this.layoutItems.splice(itemIndex, 1);
this.layoutItems.splice(newIndex, 0, items[itemIndex]);
}
},
updateTelemetryFormat(item, format) {
let index = _.findIndex(this.layoutItems, item);
item.format = format;
this.mutate(`configuration.items[${index}]`, item);
}
},
mounted() {
@@ -577,10 +413,14 @@
this.composition.load();
},
destroyed: function () {
this.openmct.selection.off('change', this.setSelection);
this.openmct.off('change', this.setSelection);
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
this.unlisten();
if (this.removeSelectionListener) {
this.removeSelectionListener();
}
}
}
</script>

View File

@@ -1,233 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<!-- Resize handles -->
<div class="c-frame-edit" :style="style">
<div class="c-frame-edit__handle c-frame-edit__handle--nw"
@mousedown="startResize([1,1], [-1,-1], $event)"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--ne"
@mousedown="startResize([0,1], [1,-1], $event)"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--sw"
@mousedown="startResize([1,0], [-1,1], $event)"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--se"
@mousedown="startResize([0,0], [1,1], $event)"></div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-frame-edit {
// In Layouts, this is the editing rect and handles
display: none; // Set to display: block in DisplayLayout.vue
pointer-events: none;
@include abs();
border: $editMarqueeBorder;
&__handle {
$d: 6px;
$o: floor($d * -0.5);
background: $editFrameColorHandleFg;
box-shadow: $editFrameColorHandleBg 0 0 0 2px;
pointer-events: all;
position: absolute;
width: $d; height: $d;
top: auto; right: auto; bottom: auto; left: auto;
&:before {
// Extended hit area
@include abs(-10px);
content: '';
display: block;
z-index: 0;
}
&:hover {
background: $editUIColor;
}
&--nwse {
cursor: nwse-resize;
}
&--nw {
cursor: nw-resize;
left: $o; top: $o;
}
&--ne {
cursor: ne-resize;
right: $o; top: $o;
}
&--se {
cursor: se-resize;
right: $o; bottom: $o;
}
&--sw {
cursor: sw-resize;
left: $o; bottom: $o;
}
}
}
</style>
<script>
import LayoutDrag from './../LayoutDrag'
export default {
inject: ['openmct'],
props: {
selectedLayoutItems: Array,
gridSize: Array
},
data() {
return {
dragPosition: undefined
}
},
computed: {
style() {
let x = Number.POSITIVE_INFINITY;
let y = Number.POSITIVE_INFINITY;
let width = Number.NEGATIVE_INFINITY;
let height = Number.NEGATIVE_INFINITY;
this.selectedLayoutItems.forEach(item => {
if (item.x2 !== undefined) {
let lineWidth = Math.abs(item.x - item.x2);
let lineMinX = Math.min(item.x, item.x2);
x = Math.min(lineMinX, x);
width = Math.max(lineWidth + lineMinX, width);
} else {
x = Math.min(item.x, x);
width = Math.max(item.width + item.x, width);
}
if (item.y2 !== undefined) {
let lineHeight = Math.abs(item.y - item.y2);
let lineMinY = Math.min(item.y, item.y2);
y = Math.min(lineMinY, y);
height = Math.max(lineHeight + lineMinY, height);
} else {
y = Math.min(item.y, y);
height = Math.max(item.height + item.y, height);
}
});
if (this.dragPosition) {
[x, y] = this.dragPosition.position;
[width, height] = this.dragPosition.dimensions;
} else {
width = width - x;
height = height - y;
}
this.marqueePosition = {
x: x,
y: y,
width: width,
height: height
}
return this.getMarqueeStyle(x, y, width, height);
}
},
methods: {
getMarqueeStyle(x, y, width, height) {
return {
left: (this.gridSize[0] * x) + 'px',
top: (this.gridSize[1] * y) + 'px',
width: (this.gridSize[0] * width) + 'px',
height: (this.gridSize[1] * height) + 'px'
};
},
updatePosition(event) {
let currentPosition = [event.pageX, event.pageY];
this.initialPosition = this.initialPosition || currentPosition;
this.delta = currentPosition.map(function (value, index) {
return value - this.initialPosition[index];
}.bind(this));
},
startResize(posFactor, dimFactor, event) {
document.body.addEventListener('mousemove', this.continueResize);
document.body.addEventListener('mouseup', this.endResize);
this.marqueeStartPosition = {
position: [this.marqueePosition.x, this.marqueePosition.y],
dimensions: [this.marqueePosition.width, this.marqueePosition.height]
};
this.updatePosition(event);
this.activeDrag = new LayoutDrag(this.marqueeStartPosition, posFactor, dimFactor, this.gridSize);
event.preventDefault();
},
continueResize(event) {
event.preventDefault();
this.updatePosition(event);
this.dragPosition = this.activeDrag.getAdjustedPositionAndDimensions(this.delta);
},
endResize(event) {
document.body.removeEventListener('mousemove', this.continueResize);
document.body.removeEventListener('mouseup', this.endResize);
this.continueResize(event);
let marqueeStartWidth = this.marqueeStartPosition.dimensions[0];
let marqueeStartHeight = this.marqueeStartPosition.dimensions[1];
let marqueeStartX = this.marqueeStartPosition.position[0];
let marqueeStartY = this.marqueeStartPosition.position[1];
let marqueeEndX = this.dragPosition.position[0];
let marqueeEndY = this.dragPosition.position[1];
let marqueeEndWidth = this.dragPosition.dimensions[0];
let marqueeEndHeight = this.dragPosition.dimensions[1];
let scaleWidth = marqueeEndWidth / marqueeStartWidth;
let scaleHeight = marqueeEndHeight / marqueeStartHeight;
let marqueeStart = {
x: marqueeStartX,
y: marqueeStartY,
height: marqueeStartWidth,
width: marqueeStartHeight
};
let marqueeEnd = {
x: marqueeEndX,
y: marqueeEndY,
width: marqueeEndWidth,
height: marqueeEndHeight
};
let marqueeOffset = {
x: marqueeEnd.x - marqueeStart.x,
y: marqueeEnd.y - marqueeStart.y
};
this.$emit('endResize', scaleWidth, scaleHeight, marqueeStart, marqueeOffset);
this.dragPosition = undefined;
this.initialPosition = undefined;
this.marqueeStartPosition = undefined;
this.delta = undefined;
event.preventDefault();
}
}
}
</script>

View File

@@ -23,8 +23,7 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
<div class="c-image-view"
:style="style">
</div>
@@ -57,7 +56,8 @@
y: 1,
width: 10,
height: 5,
url: element.url
url: element.url,
useGrid: true
};
},
inject: ['openmct'],

View File

@@ -24,14 +24,25 @@
<div class="l-layout__frame c-frame"
:class="{
'no-frame': !item.hasFrame,
'u-inspectable': inspectable
'u-inspectable': inspectable,
'is-resizing': isResizing
}"
:style="style">
<slot></slot>
<div class="c-frame-edit__move"
@mousedown="startMove([1,1], [0,0], $event)">
<!-- Drag handles -->
<div class="c-frame-edit">
<div class="c-frame-edit__move"
@mousedown="startDrag([1,1], [0,0], $event, 'move')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--nw"
@mousedown="startDrag([1,1], [-1,-1], $event, 'resize')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--ne"
@mousedown="startDrag([0,1], [1,-1], $event, 'resize')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--sw"
@mousedown="startDrag([1,0], [-1,1], $event, 'resize')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--se"
@mousedown="startDrag([0,0], [1,1], $event, 'resize')"></div>
</div>
</div>
</template>
@@ -39,7 +50,7 @@
<style lang="scss">
@import "~styles/sass-base";
/******************* FRAME */
/******************************* FRAME */
.c-frame {
display: flex;
flex-direction: column;
@@ -48,15 +59,124 @@
> *:first-child {
flex: 1 1 auto;
}
&:not(.no-frame) {
background: $colorBodyBg;
border: $browseFrameBorder;
padding: $interiorMargin;
}
}
.c-frame-edit__move {
.c-frame-edit {
// In Layouts, this is the editing rect and handles
// In Fixed Position, this is a wrapper element
@include abs();
display: none;
&__move {
@include abs();
cursor: move;
}
&__handle {
$d: 6px;
$o: floor($d * -0.5);
background: $editFrameColorHandleFg;
box-shadow: $editFrameColorHandleBg 0 0 0 2px;
display: none; // Set to block via s-selected selector
position: absolute;
width: $d; height: $d;
top: auto; right: auto; bottom: auto; left: auto;
&:before {
// Extended hit area
@include abs(-10px);
content: '';
display: block;
z-index: 0;
}
&:hover {
background: $editUIColor;
}
&--nwse {
cursor: nwse-resize;
}
&--nw {
cursor: nw-resize;
left: $o; top: $o;
}
&--ne {
cursor: ne-resize;
right: $o; top: $o;
}
&--se {
cursor: se-resize;
right: $o; bottom: $o;
}
&--sw {
cursor: sw-resize;
left: $o; bottom: $o;
}
}
}
.c-so-view.has-complex-content + .c-frame-edit {
// Target frames that hold domain objects that include header elements, as opposed to drawing and alpha objects
// Make the __move element a more affordable drag UI element
.c-frame-edit__move {
@include userSelectNone();
background: $editFrameMovebarColorBg;
box-shadow: rgba(black, 0.2) 0 1px;
bottom: auto;
height: 0; // Height is set on hover on s-selected.c-frame
opacity: 0.8;
max-height: 100%;
overflow: hidden;
text-align: center;
&:before {
// Grippy
$h: 4px;
$tbOffset: ($editFrameMovebarH - $h) / 2;
$lrOffset: 25%;
@include grippy($editFrameMovebarColorFg);
content: '';
display: block;
position: absolute;
top: $tbOffset; right: $lrOffset; bottom: $tbOffset; left: $lrOffset;
}
&:hover {
background: $editFrameHovMovebarColorBg;
&:before { @include grippy($editFrameHovMovebarColorFg); }
}
}
}
.is-editing {
/******************* STYLES FOR C-FRAME WHILE EDITING */
.c-frame {
$moveBarOutDelay: 500ms;
&.no-frame {
border: $editFrameBorder; // Base border style for a frame element while editing.
}
&-edit {
display: contents;
}
&-edit__move,
.c-so-view {
transition: $transOut;
transition-delay: $moveBarOutDelay;
}
&:not([s-selected]) {
&:hover {
border: $editFrameBorderHov;
@@ -68,110 +188,37 @@
border: $editFrameSelectedBorder;
box-shadow: $editFrameSelectedShdw;
.c-frame-edit__move {
cursor: move;
}
}
}
/******************* DEFAULT STYLES FOR -EDIT__MOVE */
// All object types
.c-frame-edit__move {
@include abs();
display: block;
}
// Has-complex-content objects
.c-so-view.has-complex-content {
transition: $transOut;
transition-delay: $moveBarOutDelay;
> .c-so-view__local-controls {
transition: transform 250ms ease-in-out;
transition-delay: $moveBarOutDelay;
}
+ .c-frame-edit__move {
display: none;
}
}
.l-layout {
/******************* 0 - 1 ITEM SELECTED */
&:not(.is-multi-selected) {
> .l-layout__frame[s-selected] {
> .c-so-view.has-complex-content {
> .c-so-view__local-controls {
transition: transform $transOutTime ease-in-out;
transition-delay: $moveBarOutDelay;
}
+ .c-frame-edit__move {
transition: $transOut;
transition-delay: $moveBarOutDelay;
@include userSelectNone();
background: $editFrameMovebarColorBg;
box-shadow: rgba(black, 0.2) 0 1px;
bottom: auto;
display: block;
height: 0; // Height is set on hover below
opacity: 0.8;
max-height: 100%;
overflow: hidden;
text-align: center;
&:before {
// Grippy
$h: 4px;
$tbOffset: ($editFrameMovebarH - $h) / 2;
$lrOffset: 25%;
@include grippy($editFrameMovebarColorFg);
content: '';
display: block;
position: absolute;
top: $tbOffset;
right: $lrOffset;
bottom: $tbOffset;
left: $lrOffset;
}
}
}
&:hover {
> .c-so-view.has-complex-content {
transition: $transIn;
transition-delay: 0s;
padding-top: $editFrameMovebarH + $interiorMarginSm;
> .c-so-view__local-controls {
transform: translateY($editFrameMovebarH);
transition: transform $transInTime ease-in-out;
transition-delay: 0s;
}
+ .c-frame-edit__move {
transition: $transIn;
transition-delay: 0s;
height: $editFrameMovebarH;
}
}
}
}
}
/******************* > 1 ITEMS SELECTED */
&.is-multi-selected {
.l-layout__frame[s-selected] {
> .c-so-view.has-complex-content + .c-frame-edit__move {
> .c-frame-edit {
[class*='__handle'] {
display: block;
}
}
}
}
.l-layout__frame:not(.is-resizing) {
// Show and animate the __move bar for sub-object views with complex content
&:hover > .c-so-view.has-complex-content {
// Move content down so the __move bar doesn't cover it.
padding-top: $editFrameMovebarH;
transition: $transIn;
&.c-so-view--no-frame {
// Move content down with a bit more space
padding-top: $editFrameMovebarH + $interiorMarginSm;
}
// Show the move bar
+ .c-frame-edit .c-frame-edit__move {
height: $editFrameMovebarH;
transition: $transIn;
}
}
}
}
</style>
<script>
import LayoutDrag from './../LayoutDrag'
@@ -181,9 +228,21 @@
item: Object,
gridSize: Array
},
data() {
return {
dragPosition: undefined,
isResizing: undefined
}
},
computed: {
style() {
let {x, y, width, height} = this.item;
if (this.dragPosition) {
[x, y] = this.dragPosition.position;
[width, height] = this.dragPosition.dimensions;
}
return {
left: (this.gridSize[0] * x) + 'px',
top: (this.gridSize[1] * y) + 'px',
@@ -205,40 +264,36 @@
return value - this.initialPosition[index];
}.bind(this));
},
startMove(posFactor, dimFactor, event) {
document.body.addEventListener('mousemove', this.continueMove);
document.body.addEventListener('mouseup', this.endMove);
startDrag(posFactor, dimFactor, event, type) {
document.body.addEventListener('mousemove', this.continueDrag);
document.body.addEventListener('mouseup', this.endDrag);
this.dragPosition = {
position: [this.item.x, this.item.y]
position: [this.item.x, this.item.y],
dimensions: [this.item.width, this.item.height]
};
this.updatePosition(event);
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
this.isResizing = type === 'resize';
event.preventDefault();
},
continueMove(event) {
continueDrag(event) {
event.preventDefault();
this.updatePosition(event);
let newPosition = this.activeDrag.getAdjustedPosition(this.delta);
if (!_.isEqual(newPosition, this.dragPosition)) {
this.dragPosition = newPosition;
this.$emit('move', this.toGridDelta(this.delta));
}
this.dragPosition = this.activeDrag.getAdjustedPosition(this.delta);
},
endMove(event) {
document.body.removeEventListener('mousemove', this.continueMove);
document.body.removeEventListener('mouseup', this.endMove);
this.continueMove(event);
this.$emit('endMove');
endDrag(event) {
document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endDrag);
this.continueDrag(event);
let [x, y] = this.dragPosition.position;
let [width, height] = this.dragPosition.dimensions;
this.$emit('endDrag', this.item, {x, y, width, height});
this.dragPosition = undefined;
this.initialPosition = undefined;
this.delta = undefined;
this.isResizing = undefined;
event.preventDefault();
},
toGridDelta(pixelDelta) {
return pixelDelta.map((v, i) => {
return Math.round(v / this.gridSize[i]);
});
}
}
}

View File

@@ -30,9 +30,9 @@
</line>
</svg>
<div class="c-frame-edit__move"
@mousedown="startDrag($event)"></div>
<div class="c-frame-edit" v-if="showFrameEdit">
<div class="c-frame-edit">
<div class="c-frame-edit__move"
@mousedown="startDrag($event)"></div>
<div class="c-frame-edit__handle"
:class="startHandleClass"
@mousedown="startDrag($event, 'start')"></div>
@@ -66,7 +66,8 @@
y: 10,
x2: 10,
y2: 5,
stroke: '#717171'
stroke: '#717171',
useGrid: true
};
},
inject: ['openmct'],
@@ -75,31 +76,24 @@
gridSize: Array,
initSelect: Boolean,
index: Number,
multiSelect: Boolean
},
data() {
return {
dragPosition: undefined,
dragging: undefined,
selection: []
dragPosition: undefined
};
},
computed: {
showFrameEdit() {
let layoutItem = this.selection.length > 0 && this.selection[0][0].context.layoutItem;
return !this.multiSelect && layoutItem && layoutItem.id === this.item.id;
},
position() {
let {x, y, x2, y2} = this.item;
if (this.dragging && this.dragPosition) {
if (this.dragPosition) {
({x, y, x2, y2} = this.dragPosition);
}
return {x, y, x2, y2};
},
style() {
let {x, y, x2, y2} = this.position;
let width = Math.max(this.gridSize[0] * Math.abs(x - x2), 1);
let height = Math.max(this.gridSize[1] * Math.abs(y - y2), 1);
let width = this.gridSize[0] * Math.abs(x - x2);
let height = this.gridSize[1] * Math.abs(y - y2);
let left = this.gridSize[0] * Math.min(x, x2);
let top = this.gridSize[1] * Math.min(y, y2);
return {
@@ -181,27 +175,13 @@
event.preventDefault();
let pxDeltaX = this.startPosition[0] - event.pageX;
let pxDeltaY = this.startPosition[1] - event.pageY;
let newPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
if (!this.dragging) {
if (!_.isEqual(newPosition, this.dragPosition)) {
let gridDelta = [event.pageX - this.startPosition[0], event.pageY - this.startPosition[1]];
this.dragPosition = newPosition;
this.$emit('move', this.toGridDelta(gridDelta));
}
} else {
this.dragPosition = newPosition;
}
this.dragPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
},
endDrag(event) {
document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endDrag);
let {x, y, x2, y2} = this.dragPosition;
if (!this.dragging) {
this.$emit('endMove');
} else {
this.$emit('endLineResize', this.item, {x, y, x2, y2});
}
this.$emit('endDrag', this.item, {x, y, x2, y2});
this.dragPosition = undefined;
this.dragging = undefined;
event.preventDefault();
@@ -211,7 +191,6 @@
let gridDeltaY = Math.round(pxDeltaY / this.gridSize[0]); // TODO: should this be gridSize[1]?
let {x, y, x2, y2} = this.item;
let dragPosition = {x, y, x2, y2};
if (this.dragging === 'start') {
dragPosition.x -= gridDeltaX;
dragPosition.y -= gridDeltaY;
@@ -226,14 +205,6 @@
dragPosition.y2 -= gridDeltaY;
}
return dragPosition;
},
setSelection(selection) {
this.selection = selection;
},
toGridDelta(pixelDelta) {
return pixelDelta.map((v, i) => {
return Math.round(v / this.gridSize[i]);
});
}
},
watch: {
@@ -246,7 +217,6 @@
}
},
mounted() {
this.openmct.selection.on('change', this.setSelection);
this.context = {
layoutItem: this.item,
index: this.index
@@ -258,7 +228,6 @@
if (this.removeSelectable) {
this.removeSelectable();
}
this.openmct.selection.off('change', this.setSelection);
}
}
</script>

View File

@@ -22,14 +22,11 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
:title="domainObject && domainObject.name"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
<object-frame v-if="domainObject"
:domain-object="domainObject"
:object-path="objectPath"
:has-frame="item.hasFrame"
:show-edit-view="false"
ref="objectFrame">
</object-frame>
</layout-frame>
@@ -68,7 +65,8 @@
x: position[0],
y: position[1],
identifier: domainObject.identifier,
hasFrame: hasFrameByDefault(domainObject.type)
hasFrame: hasFrameByDefault(domainObject.type),
useGrid: true
};
},
inject: ['openmct'],

View File

@@ -23,8 +23,7 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
<div class="c-telemetry-view"
:style="styleObject"
v-if="domainObject">
@@ -79,7 +78,6 @@
<script>
import LayoutFrame from './LayoutFrame.vue'
import printj from 'printj'
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
DEFAULT_POSITION = [1, 1];
@@ -98,9 +96,10 @@
displayMode: 'all',
value: metadata.getDefaultDisplayValue(),
stroke: "transparent",
fill: "transparent",
fill: "",
color: "",
size: "13px"
size: "13px",
useGrid: true
};
},
inject: ['openmct'],
@@ -144,10 +143,6 @@
return;
}
if (this.item.format) {
return printj.sprintf(this.item.format, this.datum[this.valueMetadata.key]);
}
return this.valueFormatter && this.valueFormatter.format(this.datum);
},
telemetryClass() {
@@ -173,9 +168,6 @@
}
this.context.index = newIndex;
},
item(newItem) {
this.context.layoutItem = newItem;
}
},
methods: {
@@ -184,8 +176,7 @@
let options = {
start: bounds.start,
end: bounds.end,
size: 1,
strategy: 'latest'
size: 1
};
this.openmct.telemetry.request(this.domainObject, options)
.then(data => {
@@ -227,14 +218,10 @@
this.context = {
item: domainObject,
layoutItem: this.item,
index: this.index,
updateTelemetryFormat: this.updateTelemetryFormat
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
updateTelemetryFormat(format) {
this.$emit('formatChanged', this.item, format);
}
},
mounted() {

View File

@@ -23,8 +23,7 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
<div class="c-text-view"
:style="style">
{{ item.text }}
@@ -60,7 +59,8 @@
y: 1,
width: 10,
height: 5,
text: element.text
text: element.text,
useGrid: true
};
},
inject: ['openmct'],

View File

@@ -25,9 +25,8 @@ import Vue from 'vue'
import objectUtils from '../../api/objects/object-utils.js'
import DisplayLayoutType from './DisplayLayoutType.js'
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'
import AlphaNumericFormatViewProvider from './AlphaNumericFormatViewProvider.js'
export default function DisplayLayoutPlugin(options) {
export default function () {
return function (openmct) {
openmct.objectViews.addProvider({
key: 'layout.view',
@@ -48,8 +47,7 @@ export default function DisplayLayoutPlugin(options) {
template: '<layout ref="displayLayout" :domain-object="domainObject"></layout>',
provide: {
openmct,
objectUtils,
options
objectUtils
},
el: container,
data () {
@@ -62,7 +60,6 @@ export default function DisplayLayoutPlugin(options) {
getSelectionContext() {
return {
item: domainObject,
supportsMultiSelect: true,
addElement: component && component.$refs.displayLayout.addElement,
removeItem: component && component.$refs.displayLayout.removeItem,
orderItem: component && component.$refs.displayLayout.orderItem
@@ -78,8 +75,7 @@ export default function DisplayLayoutPlugin(options) {
}
});
openmct.types.addType('layout', DisplayLayoutType());
openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct, options));
openmct.inspectorViews.addProvider(new AlphaNumericFormatViewProvider(openmct, options));
openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct));
openmct.composition.addPolicy((parent, child) => {
if (parent.type === 'layout' && child.type === 'folder') {
return false;
@@ -87,6 +83,5 @@ export default function DisplayLayoutPlugin(options) {
return true;
}
});
DisplayLayoutPlugin._installed = true;
}
}

View File

@@ -1,116 +0,0 @@
<template>
<div class="u-contents c-filter-settings">
<li class="grid-row c-filter-settings__setting"
v-for="(filter, index) in filterField.filters"
:key="index">
<div class="grid-cell label">
{{ filterField.name }} =
</div>
<div class="grid-cell value">
<!-- EDITING -->
<!-- String input, editing -->
<template v-if="!filter.possibleValues && isEditing">
<input class="c-input--flex"
type="text"
placeholder="Enter Value"
:id="`${filter}filterControl`"
:value="persistedValue(filter)"
@change="updateFilterValue($event, filter)">
</template>
<!-- Checkbox list, editing -->
<template v-if="filter.possibleValues && isEditing">
<div class="c-checkbox-list__row"
v-for="value in filter.possibleValues"
:key="value">
<input class="c-checkbox-list__input"
type="checkbox"
:id="`${value}filterControl`"
@change="onUserSelect($event, filter.comparator, value)"
:checked="isChecked(filter.comparator, value)">
<span class="c-checkbox-list__value">
{{ value }}
</span>
</div>
</template>
<!-- BROWSING -->
<!-- String input, NOT editing -->
<template v-if="!filter.possibleValues && !isEditing">
{{ persistedValue(filter) }}
</template>
<!-- Checkbox list, NOT editing -->
<template v-if="filter.possibleValues && !isEditing">
<span
v-if="persistedFilters[filter.comparator]">
{{persistedFilters[filter.comparator].join(', ')}}
</span>
</template>
</div>
</li>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-filter-settings {
&__setting {
.grid-cell.label {
white-space: nowrap;
}
}
}
</style>
<script>
export default {
inject: [
'openmct'
],
props: {
filterField: Object,
persistedFilters: {
type: Object,
default: () => {
return {}
}
}
},
data() {
return {
expanded: false,
isEditing: this.openmct.editor.isEditing()
}
},
methods: {
toggleIsEditing(isEditing) {
this.isEditing = isEditing;
},
onUserSelect(event, comparator, value){
this.$emit('onUserSelect', this.filterField.key, comparator, value, event.target.checked);
},
isChecked(comparator, value) {
if (this.persistedFilters[comparator] && this.persistedFilters[comparator].includes(value)) {
return true;
} else {
return false;
}
},
persistedValue(comparator) {
return this.persistedFilters && this.persistedFilters[comparator];
},
updateFilterValue(event, comparator) {
this.$emit('onTextEnter', this.filterField.key, comparator, event.target.value);
}
},
mounted() {
this.openmct.editor.on('isEditing', this.toggleIsEditing);
},
beforeDestroy() {
this.openmct.editor.off('isEditing', this.toggleIsEditing);
}
}
</script>

View File

@@ -1,94 +0,0 @@
<template>
<li>
<div class="c-tree__item menus-to-left"
@click="toggleExpanded">
<span class="c-disclosure-triangle is-enabled flex-elem"
:class="{'c-disclosure-triangle--expanded': expanded}"></span>
<div class="c-tree__item__label">
<div class="c-object-label">
<div class="c-object-label__type-icon"
:class="objectCssClass">
</div>
<div class="c-object-label__name flex-elem grows">{{ filterObject.name }}</div>
</div>
</div>
</div>
<ul class="grid-properties" v-if="expanded">
<filter-field
v-for="field in filterObject.valuesWithFilters"
:key="field.key"
:filterField="field"
:persistedFilters="persistedFilters[field.key]"
@onUserSelect="collectUserSelects"
@onTextEnter="updateTextFilter">
</filter-field>
</ul>
</li>
</template>
<style lang="scss">
</style>
<script>
import FilterField from './FilterField.vue';
export default {
inject: ['openmct'],
components: {
FilterField
},
props: {
filterObject: Object,
persistedFilters: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
expanded: false,
objectCssClass: undefined,
updatedFilters: this.persistedFilters
}
},
methods: {
toggleExpanded() {
this.expanded = !this.expanded;
},
collectUserSelects(key, comparator, valueName, value) {
let filterValue = this.updatedFilters[key];
if (filterValue && filterValue[comparator]) {
if (value === false) {
filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName);
} else {
filterValue[comparator].push(valueName);
}
} else {
if (!this.updatedFilters[key]) {
this.$set(this.updatedFilters, key, {});
}
this.$set(this.updatedFilters[key], comparator, [value ? valueName : undefined]);
}
this.$emit('updateFilters', this.keyString, this.updatedFilters);
},
updateTextFilter(key, comparator, value) {
if (!this.updatedFilters[key]) {
this.$set(this.updatedFilters, key, {});
this.$set(this.updatedFilters[key], comparator, '');
}
this.updatedFilters[key][comparator] = value;
this.$emit('updateFilters', this.keyString, this.updatedFilters);
}
},
mounted() {
let type = this.openmct.types.get(this.filterObject.domainObject.type) || {};
this.keyString = this.openmct.objects.makeKeyString(this.filterObject.domainObject.identifier);
this.objectCssClass = type.definition.cssClass;
}
}
</script>

View File

@@ -1,87 +0,0 @@
<template>
<ul class="tree c-tree c-properties__section" v-if="Object.keys(children).length">
<h2 class="c-properties__header">Filters</h2>
<filter-object
v-for="(child, key) in children"
:key="key"
:filterObject="child"
:persistedFilters="persistedFilters[key]"
@updateFilters="persistFilters">
</filter-object>
</ul>
</template>
<style lang="scss">
</style>
<script>
import FilterObject from './FilterObject.vue';
export default {
components: {
FilterObject
},
inject: [
'openmct'
],
data() {
let providedObject = this.openmct.selection.get()[0][0].context.item;
let persistedFilters = {};
if (providedObject.configuration && providedObject.configuration.filters) {
persistedFilters = providedObject.configuration.filters;
}
return {
providedObject,
persistedFilters,
children: {}
}
},
methods: {
addChildren(child) {
let keyString = this.openmct.objects.makeKeyString(child.identifier),
metadata = this.openmct.telemetry.getMetadata(child),
valuesWithFilters = metadata.valueMetadatas.filter((value) => value.filters),
childObject = {
name: child.name,
domainObject: child,
valuesWithFilters
};
if (childObject.valuesWithFilters.length) {
this.$set(this.children, keyString, childObject);
} else {
return;
}
},
removeChildren(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
this.$delete(this.children, keyString);
this.persistFilters(keyString);
},
persistFilters(keyString, userSelects) {
this.persistedFilters[keyString] = userSelects;
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters);
},
updatePersistedFilters(filters) {
this.persistedFilters = filters;
}
},
mounted(){
this.composition = this.openmct.composition.get(this.providedObject);
this.composition.on('add', this.addChildren);
this.composition.on('remove', this.removeChildren);
this.composition.load();
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
},
beforeDestroy() {
this.composition.off('add', this.addChildren);
this.composition.off('remove', this.removeChildren);
this.unobserve();
this.unobserveAllMutation();
}
}
</script>

View File

@@ -1,72 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./components/FiltersView.vue',
'vue'
], function (
FiltersView,
Vue
) {
function FiltersInspectorViewProvider(openmct, supportedObjectTypesArray) {
return {
key: 'filters-inspector',
name: 'Filters Inspector View',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 0) {
return false;
}
let object = selection[0][0].context.item;
return object && supportedObjectTypesArray.some(type => object.type === type);
},
view: function (selection) {
let component;
return {
show: function (element) {
component = new Vue({
provide: {
openmct
},
components: {
FiltersView: FiltersView.default
},
template: '<filters-view></filters-view>',
el: element
});
},
destroy: function () {
if (component) {
component.$destroy();
component = undefined;
}
}
}
},
priority: function () {
return 1;
}
}
}
return FiltersInspectorViewProvider;
});

View File

@@ -106,6 +106,9 @@
.c-fl {
@include abs();
display: flex;
flex-direction: column; // TEMP: only needed to support temp-toolbar element
> * + * { margin-top: $interiorMargin; }
.temp-toolbar {
flex: 0 0 auto;
@@ -113,8 +116,7 @@
&__container-holder {
display: flex;
flex: 1 1 100%; // Must be 100% to work
overflow: auto;
flex: 1 1 100%; // Must needs to be 100% to work
// Columns by default
flex-direction: row;
@@ -290,6 +292,11 @@
margin-bottom: $interiorMargin;
}
&__object-view {
flex: 1 1 auto;
overflow: auto;
}
&__size-indicator {
$size: 35px;
@@ -415,7 +422,6 @@ import Container from '../utils/container';
import Frame from '../utils/frame';
import ResizeHandle from './resizeHandle.vue';
import DropHint from './dropHint.vue';
import RemoveAction from '../../remove/RemoveAction.js';
const MIN_CONTAINER_SIZE = 5;
@@ -500,29 +506,10 @@ export default {
this.persist();
},
deleteContainer(containerId) {
let container = this.containers.filter(c => c.id === containerId)[0],
containerIndex = this.containers.indexOf(container);
/*
remove associated domainObjects from composition
*/
container.frames.forEach(f => {
this.removeFromComposition(f.domainObjectIdentifier);
});
let container = this.containers.filter(c => c.id === containerId)[0];
let containerIndex = this.containers.indexOf(container);
this.containers.splice(containerIndex, 1);
/*
add a container when there are no containers in the FL,
to prevent user from not being able to add a frame via
drag and drop.
*/
if (this.containers.length === 0) {
this.containers.push(new Container(100));
}
sizeToFill(this.containers);
this.setSelectionToParent();
this.persist();
},
moveFrame(toContainerIndex, toFrameIndex, frameId, fromContainerIndex) {
@@ -556,23 +543,14 @@ export default {
deleteFrame(frameId) {
let container = this.containers
.filter(c => c.frames.some(f => f.id === frameId))[0];
let containerIndex = this.containers.indexOf(container);
let frame = container
.frames
.filter((f => f.id === frameId))[0];
this.removeFromComposition(frame.domainObjectIdentifier)
.then(() => {
sizeToFill(container.frames)
this.setSelectionToParent();
});
},
removeFromComposition(identifier) {
return this.openmct.objects.get(identifier).then((childDomainObject) => {
this.RemoveAction.removeFromComposition(this.domainObject, childDomainObject);
});
},
setSelectionToParent() {
this.$el.click();
let frameIndex = container.frames.indexOf(frame);
container.frames.splice(frameIndex, 1);
sizeToFill(container.frames);
this.persist(containerIndex);
},
allowContainerDrop(event, index) {
if (!event.dataTransfer.types.includes('containerid')) {
@@ -642,7 +620,7 @@ export default {
} else {
this.containers.splice(toIndex, 0, container);
}
this.persist();
this.persist(index);
},
removeChildObject(identifier) {
let removeIdentifier = this.openmct.objects.makeKeyString(identifier);
@@ -663,8 +641,6 @@ export default {
this.composition.on('remove', this.removeChildObject);
this.composition.on('add', this.addFrame);
this.RemoveAction = new RemoveAction(this.openmct);
this.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
},
beforeDestroy() {

View File

@@ -36,7 +36,6 @@
:domain-object="domainObject"
:object-path="objectPath"
:has-frame="hasFrame"
:show-edit-view="false"
ref="objectFrame">
</object-frame>
@@ -79,14 +78,12 @@ export default {
},
setSelection() {
this.$nextTick(function () {
if (this.$refs && this.$refs.objectFrame) {
let childContext = this.$refs.objectFrame.getSelectionContext();
childContext.item = this.domainObject;
childContext.type = 'frame';
childContext.frameId = this.frame.id;
this.unsubscribeSelection = this.openmct.selection.selectable(
this.$refs.frame, childContext, false);
}
let childContext = this.$refs.objectFrame.getSelectionContext();
childContext.item = this.domainObject;
childContext.type = 'frame';
childContext.frameId = this.frame.id;
this.unsubscribeSelection = this.openmct.selection.selectable(
this.$refs.frame, childContext, false);
});
},
initDrag(event) {

View File

@@ -79,12 +79,10 @@ export default {
mounted() {
document.addEventListener('dragstart', this.setDragging);
document.addEventListener('dragend', this.unsetDragging);
document.addEventListener('drop', this.unsetDragging);
},
destroyed() {
document.removeEventListener('dragstart', this.setDragging);
document.removeEventListener('dragend', this.unsetDragging);
document.removeEventListener('drop', this.unsetDragging);
}
}
</script>

View File

@@ -57,7 +57,7 @@ define([
layoutObject: domainObject
},
el: element,
template: '<flexible-layout-component ref="flexibleLayout" :isEditing="isEditing"></flexible-layout-component>'
template: '<flexible-layout-component ref="flexibleLayout"></flexible-layout-component>'
});
},
getSelectionContext: function () {

View File

@@ -27,22 +27,28 @@ function ToolbarProvider(openmct) {
key: "flex-layout",
description: "A toolbar for objects inside a Flexible Layout.",
forSelection: function (selection) {
let context = selection[0][0].context;
let context = selection[0].context;
return (context && context.type &&
return (openmct.editor.isEditing() && context && context.type &&
(context.type === 'flexible-layout' || context.type === 'container' || context.type === 'frame'));
},
toolbar: function (selection) {
let selectionPath = selection[0],
primary = selectionPath[0],
secondary = selectionPath[1],
tertiary = selectionPath[2],
let primary = selection[0],
secondary = selection[1],
tertiary = selection[2],
deleteFrame,
toggleContainer,
deleteContainer,
addContainer,
toggleFrame;
toggleFrame,
separator;
separator = {
control: "separator",
domainObject: selection[0].context.item,
key: "separator"
};
toggleContainer = {
control: 'toggle-button',
@@ -63,12 +69,6 @@ function ToolbarProvider(openmct) {
]
};
function getSeparator() {
return {
control: "separator"
};
}
if (primary.context.type === 'frame') {
let frameId = primary.context.frameId;
let layoutObject = tertiary.context.item;
@@ -77,11 +77,11 @@ function ToolbarProvider(openmct) {
.containers;
let container = containers
.filter(c => c.frames.some(f => f.id === frameId))[0];
let containerIndex = containers.indexOf(container);
let frame = container && container
let frame = container
.frames
.filter((f => f.id === frameId))[0];
let frameIndex = container && container.frames.indexOf(frame);
let containerIndex = containers.indexOf(container);
let frameIndex = container.frames.indexOf(frame);
deleteFrame = {
control: "button",
@@ -202,9 +202,9 @@ function ToolbarProvider(openmct) {
let toolbar = [
toggleContainer,
addContainer,
toggleFrame ? getSeparator() : undefined,
toggleFrame ? separator: undefined,
toggleFrame,
deleteFrame || deleteContainer ? getSeparator() : undefined,
deleteFrame || deleteContainer ? separator: undefined,
deleteFrame,
deleteContainer
];

View File

@@ -16,7 +16,7 @@
</div>
<div class="c-grid-item__controls">
<div class="icon-people" title='Shared'></div>
<button class="c-icon-button icon-info c-info-button" title='More Info'></button>
<button class="c-click-icon icon-info c-info-button" title='More Info'></button>
<div class="icon-pointer-right c-pointer-icon"></div>
</div>
</a>

View File

@@ -14,7 +14,6 @@ export default {
},
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier);
if (!this.composition) {
return;
}
@@ -35,7 +34,7 @@ export default {
this.items.push({
model: child,
type: type.definition,
isAlias: this.keystring !== child.location,
isAlias: this.domainObject.identifier.key !== child.location,
objectPath: [child].concat(this.openmct.router.path),
objectKeyString: this.openmct.objects.makeKeyString(child.identifier)
});

View File

@@ -1,52 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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-about c-about--licenses">
<h1>Open MCT Third Party Licenses</h1>
<p>This software includes components released under the following licenses:</p>
<div v-for="(pkg, key) in packages" :key="key" class="c-license">
<h2 class="c-license__name">{{key}}</h2>
<div class="c-license__details">
<span class="c-license__author"><em>Author</em> {{pkg.publisher}}</span> |
<span class="c-license__license"><em>License(s)</em> {{pkg.licenses}}</span> |
<span class="c-license__repo"><em>Repository</em> <a :href="pkg.repository" target="_blank">{{pkg.repository}}</a></span>
</div>
<div class="c-license__text">
<p>{{pkg.licenseText}}</p>
</div>
</div>
</div>
</template>
<style lang="sass">
</style>
<script>
import packages from './third-party-licenses.json';
export default {
data() {
return {
packages: packages
}
}
}
</script>

Some files were not shown because too many files have changed in this diff Show More