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
326 changed files with 14845 additions and 10452 deletions

View File

@@ -99,10 +99,10 @@ define([
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) { GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return _.extend( return _.extend(
{}, {},
domainObject.telemetry, domainObject.telemetry,
METADATA_BY_TYPE[domainObject.type] METADATA_BY_TYPE[domainObject.type]
); );
}; };
return GeneratorMetadataProvider; return GeneratorMetadataProvider;

View File

@@ -1,9 +1,9 @@
<span class="h-indicator" ng-controller="DialogLaunchController"> <span class="h-indicator" ng-controller="DialogLaunchController">
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class="c-indicator c-indicator--clickable icon-box-with-arrow s-status-available"><span class="label c-indicator__label"> <div class="ls-indicator icon-box-with-arrow s-status-available"><span class="label">
<button ng-click="launchProgress(true)">Known</button> <a ng-click="launchProgress(true)">Known</a>
<button ng-click="launchProgress(false)">Unknown</button> <a ng-click="launchProgress(false)">Unknown</a>
<button ng-click="launchError()">Error</button> <a ng-click="launchError()">Error</a>
<button ng-click="launchInfo()">Info</button> <a ng-click="launchInfo()">Info</a>
</span></div> </span></div>
</span> </span>

View File

@@ -1,9 +1,9 @@
<span class="h-indicator" ng-controller="NotificationLaunchController"> <span class="h-indicator" ng-controller="NotificationLaunchController">
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class="c-indicator c-indicator--clickable icon-bell s-status-available"><span class="label c-indicator__label"> <div class="ls-indicator icon-bell s-status-available"><span class="label">
<button ng-click="newInfo()">Success</button> <a ng-click="newInfo()">Success</a>
<button ng-click="newError()">Error</button> <a ng-click="newError()">Error</a>
<button ng-click="newAlert()">Alert</button> <a ng-click="newAlert()">Alert</a>
<button ng-click="newProgress()">Progress</button> <a ng-click="newProgress()">Progress</a>
</span></div> </span></div>
</span> </span>

View File

@@ -27,7 +27,7 @@
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<title></title> <title></title>
<script src="dist/openmct.js"></script> <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-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-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="dist/favicons/favicon-16x16.png" sizes="16x16"> <link rel="icon" type="image/png" href="dist/favicons/favicon-16x16.png" sizes="16x16">
@@ -50,12 +50,10 @@
openmct.install(openmct.plugins.Generator()); openmct.install(openmct.plugins.Generator());
openmct.install(openmct.plugins.ExampleImagery()); openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.UTCTimeSystem()); openmct.install(openmct.plugins.UTCTimeSystem());
openmct.install(openmct.plugins.ImportExport());
openmct.install(openmct.plugins.AutoflowView({ openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel" type: "telemetry.panel"
})); }));
openmct.install(openmct.plugins.DisplayLayout({
showAsView: ['summary-widget', 'example.imagery']
}));
openmct.install(openmct.plugins.Conductor({ openmct.install(openmct.plugins.Conductor({
menuOptions: [ menuOptions: [
{ {
@@ -79,9 +77,10 @@
})); }));
openmct.install(openmct.plugins.SummaryWidget()); openmct.install(openmct.plugins.SummaryWidget());
openmct.install(openmct.plugins.Notebook()); openmct.install(openmct.plugins.Notebook());
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay'])); openmct.install(openmct.plugins.FolderView());
openmct.install(openmct.plugins.ObjectMigration()); openmct.install(openmct.plugins.Tabs());
openmct.install(openmct.plugins.ClearData(['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'])); openmct.install(openmct.plugins.FlexibleLayout());
openmct.install(openmct.plugins.LADTable());
openmct.start(); openmct.start();
</script> </script>
</html> </html>

View File

@@ -4,7 +4,6 @@
"description": "The Open MCT core platform", "description": "The Open MCT core platform",
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"acorn": "6.2.0",
"angular": "1.4.14", "angular": "1.4.14",
"angular-route": "1.4.14", "angular-route": "1.4.14",
"babel-eslint": "8.2.6", "babel-eslint": "8.2.6",
@@ -26,7 +25,7 @@
"eventemitter3": "^1.2.0", "eventemitter3": "^1.2.0",
"exports-loader": "^0.7.0", "exports-loader": "^0.7.0",
"express": "^4.13.1", "express": "^4.13.1",
"fast-sass-loader": "1.4.6", "fast-sass-loader": "^1.4.5",
"file-loader": "^1.1.11", "file-loader": "^1.1.11",
"file-saver": "^1.3.8", "file-saver": "^1.3.8",
"git-rev-sync": "^1.4.0", "git-rev-sync": "^1.4.0",
@@ -56,7 +55,7 @@
"node-bourbon": "^4.2.3", "node-bourbon": "^4.2.3",
"node-sass": "^4.9.2", "node-sass": "^4.9.2",
"painterro": "^0.2.65", "painterro": "^0.2.65",
"printj": "^1.2.1", "printj": "^1.1.0",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"request": "^2.69.0", "request": "^2.69.0",
"split": "^1.0.0", "split": "^1.0.0",

View File

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

View File

@@ -0,0 +1,51 @@
/*****************************************************************************
* 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 () {
/**
* 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 the window title...
function setTitle(name) {
$document[0].title = name;
}
// Watch the former, and invoke the latter
$rootScope.$watch(getNavigatedObjectName, setTitle);
}
return WindowTitler;
}
);

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

View File

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

View File

@@ -1,32 +1,25 @@
<div class="c-message" <div class="l-message"
ng-class="'message-severity-' + ngModel.severity"> ng-class="'message-severity-' + ngModel.severity">
<div class="w-message-contents"> <div class="w-message-contents">
<div class="c-message__top-bar"> <div class="top-bar">
<div class="c-message__title">{{ngModel.title}}</div> <div class="title">{{ngModel.message}}</div>
</div>
<div class="c-message__hint" ng-hide="ngModel.hint === undefined">
{{ngModel.hint}}
<span ng-if="ngModel.timestamp !== undefined">[{{ngModel.timestamp}}]</span>
</div> </div>
<div class="message-body"> <div class="message-body">
<div class="message-action">
{{ngModel.actionText}}
</div>
<mct-include key="'progress-bar'" <mct-include key="'progress-bar'"
ng-model="ngModel" ng-model="ngModel"
ng-show="ngModel.progress !== undefined || ngModel.unknownProgress"></mct-include> ng-show="ngModel.progressPerc !== undefined"></mct-include>
</div> </div>
<div class="c-overlay__button-bar"> <div class="bottom-bar">
<button ng-repeat="dialogOption in ngModel.options" <a ng-repeat="dialogOption in ngModel.options"
class="c-button" class="s-button"
ng-click="dialogOption.callback()"> ng-click="dialogOption.callback()">
{{dialogOption.label}} {{dialogOption.label}}
</button> </a>
<button class="c-button c-button--major" <a class="s-button major"
ng-if="ngModel.primaryOption" ng-if="ngModel.primaryOption"
ng-click="ngModel.primaryOption.callback()"> ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}} {{ngModel.primaryOption.label}}
</button> </a>
</div> </div>
</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"> <mct-container key="overlay">
<div class="t-message-list c-overlay__contents"> <div class="t-message-list">
<div class="c-overlay__top-bar"> <div class="top-bar">
<div class="c-overlay__dialog-title">{{ngModel.dialog.title}}</div> <div class="dialog-title">{{ngModel.dialog.title}}</div>
<div class="c-overlay__dialog-hint">Displaying {{ngModel.dialog.messages.length}} message<span <div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1 ||
ng-show="ngModel.dialog.messages.length > 1 || ngModel.dialog.messages.length == 0">s</span>
ngModel.dialog.messages.length == 0">s</span>
</div> </div>
</div> </div>
<div class="w-messages c-overlay__messages"> <div class="w-messages">
<mct-include <mct-include
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'" 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>
<div class="c-overlay__bottom-bar"> <div class="bottom-bar">
<button ng-repeat="dialogAction in ngModel.dialog.actions" <a ng-repeat="dialogAction in ngModel.dialog.actions"
class="c-button c-button--major" class="s-button major"
ng-click="dialogAction.action()"> ng-click="dialogAction.action()">
{{dialogAction.label}} {{dialogAction.label}}
</button> </a>
</div> </div>
</div> </div>
</mct-container> </mct-container>

View File

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

View File

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

View File

@@ -44,9 +44,8 @@ define(
* @memberof platform/commonUI/dialog * @memberof platform/commonUI/dialog
* @constructor * @constructor
*/ */
function OverlayService($document, $compile, $rootScope, $timeout) { function OverlayService($document, $compile, $rootScope) {
this.$compile = $compile; this.$compile = $compile;
this.$timeout = $timeout;
// Don't include $document and $rootScope directly; // Don't include $document and $rootScope directly;
// avoids https://docs.angularjs.org/error/ng/cpws // avoids https://docs.angularjs.org/error/ng/cpws
@@ -94,14 +93,12 @@ define(
scope.key = key; scope.key = key;
scope.typeClass = typeClass || 't-dialog'; scope.typeClass = typeClass || 't-dialog';
this.$timeout(() => { // Create the overlay element and add it to the document's body
// Create the overlay element and add it to the document's body element = this.$compile(TEMPLATE)(scope);
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
// 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.
// multiple overlays with the same z-index are active. this.findBody().append(element);
this.findBody().append(element);
});
return { return {
dismiss: dismiss dismiss: dismiss

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ define(
name: "Properties", name: "Properties",
rows: this.properties.map(function (property, index) { rows: this.properties.map(function (property, index) {
// Property definition is same as form row definition // 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; row.key = index;
return row; return row;
}).filter(function (row) { }).filter(function (row) {

View File

@@ -23,7 +23,11 @@
/** /**
* Module defining RemoveAction. Created by vwoeltje on 11/17/14. * 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. * Construct an action which will remove the provided object manifestation.
@@ -110,7 +114,12 @@ define([], function () {
return parent.useCapability('mutation', doMutate); 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 // 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# * @memberof platform/commonUI/edit.SaveAction#
*/ */
SaveAsAction.prototype.perform = function () { 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) { function saveAfterClone(clonedObject) {
return this.openmct.editor.save().then(() => { return this.openmct.editor.save().then(() => {
// Force mutation for search indexing // Force mutation for search indexing
clonedObject.useCapability('mutation', (model) => {
return model;
});
return clonedObject; return clonedObject;
}) })
} }
@@ -170,14 +182,6 @@ function (
return fetchObject(clonedObject.getId()) return fetchObject(clonedObject.getId())
} }
function indexForSearch(savedObject) {
savedObject.useCapability('mutation', (model) => {
return model;
});
return savedObject;
}
function onSuccess(object) { function onSuccess(object) {
self.notificationService.info("Save Succeeded"); self.notificationService.info("Save Succeeded");
return object; return object;
@@ -188,7 +192,7 @@ function (
if (reason !== "user canceled") { if (reason !== "user canceled") {
self.notificationService.error("Save Failed"); self.notificationService.error("Save Failed");
} }
throw reason; return false;
} }
return getParent(domainObject) return getParent(domainObject)
@@ -199,7 +203,6 @@ function (
.then(undirtyOriginals) .then(undirtyOriginals)
.then(saveAfterClone) .then(saveAfterClone)
.then(finishEditing) .then(finishEditing)
.then(indexForSearch)
.then(hideBlockingDialog) .then(hideBlockingDialog)
.then(onSuccess) .then(onSuccess)
.catch(onFailure); .catch(onFailure);

View File

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

View File

@@ -67,38 +67,20 @@ define(
openmct = this.openmct, openmct = this.openmct,
newObject; newObject;
function onSave() {
// openmct.editor.save();
}
function onCancel() { function onCancel() {
openmct.editor.cancel(); 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.type = this.type.getKey();
newModel.location = this.parent.getId(); newModel.location = this.parent.getId();
newObject = this.parent.useCapability('instantiation', newModel); newObject = this.parent.useCapability('instantiation', newModel);
openmct.editor.edit(); 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. // TODO: support editing object without saving object first.
// Which means we have to toggle createwizard afterwards. For now, // Which means we have to toggle createwizard afterwards. For now,
// We will disable this. // We will disable this.

View File

@@ -66,7 +66,7 @@ define(
name: "Properties", name: "Properties",
rows: this.properties.map(function (property, index) { rows: this.properties.map(function (property, index) {
// Property definition is same as form row definition // 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; // Use index as the key into the formValue;
// this correlates to the indexing provided by // this correlates to the indexing provided by

View File

@@ -77,19 +77,14 @@ define([], function () {
return promiseFn().then(nextFn); 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] = if (!this.isScheduled(id)) {
this.transactionService.addToTransaction( this.clearTransactionFns[id] =
chain(onCommit, release), this.transactionService.addToTransaction(
chain(onCancel, release) chain(onCommit, release),
); chain(onCancel, release)
);
}
}; };
/** /**

View File

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

View File

@@ -20,8 +20,8 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class="c-indicator {{ngModel.getCssClass()}}" <div class="ls-indicator {{ngModel.getCssClass()}}"
title="{{ngModel.getDescription()}}" title="{{ngModel.getDescription()}}"
ng-show="ngModel.getText().length > 0"> ng-show="ngModel.getText().length > 0">
<span class="label c-indicator__label">{{ngModel.getText()}}</span> <span class="label">{{ngModel.getText()}}</span>
</div> </div>

View File

@@ -19,7 +19,7 @@
this source code distribution or the Licensing information page available this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<div class="c-object-label"> <div class="t-object-label l-flex-row flex-elem grows">
<div class="c-object-label__type-icon {{type.getCssClass()}}" ng-class="{ 'l-icon-link':location.isLink() }"></div> <div class="t-item-icon flex-elem {{type.getCssClass()}}" ng-class="{ 'l-icon-link':location.isLink() }"></div>
<div class='c-object-label__name'>{{model.name}}</div> <div class='t-title-label flex-elem grows'>{{model.name}}</div>
</div> </div>

View File

@@ -1,13 +1,13 @@
<div ng-controller="BannerController" ng-show="active.notification" <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, 'minimized': active.notification.model.minimized,
'new': !active.notification.model.minimized}" 'new': !active.notification.model.minimized}"
ng-click="maximize(active.notification)"> ng-click="maximize(active.notification)">
<span class="c-message-banner__message"> <span class="banner-elem label">
{{active.notification.model.title}} {{active.notification.model.title}}
</span> </span>
<span ng-show="active.notification.model.progress !== undefined || active.notification.model.unknownProgress"> <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"> ng-model="active.notification.model">
</mct-include> </mct-include>
</span> </span>
@@ -16,5 +16,5 @@
ng-click="action(active.notification.model.primaryOption.callback, $event)"> ng-click="action(active.notification.model.primaryOption.callback, $event)">
{{active.notification.model.primaryOption.label}} {{active.notification.model.primaryOption.label}}
</a> </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> </div>

View File

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

View File

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

View File

@@ -1,2 +1,4 @@
<span class="c-tree__item js-tree__item"></span> <span class="tree-item menus-to-left">
<span class="c-tree__item-subtree"></span> </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"> <span class="rep-object-label">
<div class="c-object-label__type-icon c-tree__item__type-icon t-item-icon"></div> <div class="t-object-label l-flex-row flex-elem grows">
<div class="c-object-label__name c-tree__item__name t-title-label"></div> <div class="t-item-icon flex-elem"></div>
</div> <div class='t-title-label flex-elem grows'></div>
</div>
</span>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div ng-show="notifications.length > 0" class="c-indicator c-indicator--clickable s-status-{{highest.severity}} icon-bell" <div ng-show="notifications.length > 0" class="ls-indicator s-status-{{highest.severity}} icon-bell"
ng-controller="NotificationIndicatorController"> ng-controller="NotificationIndicatorController">
<span class="label c-indicator__label"> <span class="label">
<button ng-click="showNotificationsList()"> <a ng-click="showNotificationsList()">
{{notifications.length}} Notification<span ng-show="notifications.length > 1">s</span></button> {{notifications.length}} Notification<span ng-show="notifications.length > 1">s</span></a>
</span><span class="c-indicator__count">{{notifications.length}}</span> </span><span class="count">{{notifications.length}}</span>
</div> </div>

View File

@@ -43,10 +43,23 @@ define([], function () {
var mutationTopic = topic('mutation'); var mutationTopic = topic('mutation');
mutationTopic.listen(function (domainObject) { mutationTopic.listen(function (domainObject) {
var persistence = domainObject.getCapability('persistence'); var persistence = domainObject.getCapability('persistence');
var wasActive = transactionService.isActive();
cacheService.put(domainObject.getId(), domainObject.getModel()); cacheService.put(domainObject.getId(), domainObject.getModel());
if (hasChanged(domainObject)) { 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"], ["../../src/runs/TransactingMutationListener"],
function (TransactingMutationListener) { function (TransactingMutationListener) {
describe("TransactingMutationListener", function () { xdescribe("TransactingMutationListener", function () {
var mockTopic, var mockTopic,
mockMutationTopic, mockMutationTopic,
mockCacheService,
mockTransactionService, mockTransactionService,
mockDomainObject, mockDomainObject,
mockModel,
mockPersistence; mockPersistence;
beforeEach(function () { beforeEach(function () {
mockTopic = jasmine.createSpy('topic'); mockTopic = jasmine.createSpy('topic');
mockMutationTopic = mockMutationTopic =
jasmine.createSpyObj('mutation', ['listen']); jasmine.createSpyObj('mutation', ['listen']);
mockCacheService =
jasmine.createSpyObj('cacheService', [
'put'
]);
mockTransactionService = mockTransactionService =
jasmine.createSpyObj('transactionService', [ jasmine.createSpyObj('transactionService', [
'isActive', 'isActive',
'startTransaction', 'startTransaction',
'addToTransaction',
'commit' 'commit'
]); ]);
mockDomainObject = jasmine.createSpyObj( mockDomainObject = jasmine.createSpyObj(
@@ -57,24 +52,18 @@ define(
); );
mockTopic.and.callFake(function (t) { mockTopic.and.callFake(function (t) {
expect(t).toBe('mutation'); return (t === 'mutation') && mockMutationTopic;
return mockMutationTopic;
}); });
mockDomainObject.getId.and.returnValue('mockId');
mockDomainObject.getCapability.and.callFake(function (c) { mockDomainObject.getCapability.and.callFake(function (c) {
expect(c).toBe('persistence'); return (c === 'persistence') && mockPersistence;
return mockPersistence;
}); });
mockModel = {};
mockDomainObject.getModel.and.returnValue(mockModel);
mockPersistence.persisted.and.returnValue(true); mockPersistence.persisted.and.returnValue(true);
return new TransactingMutationListener( return new TransactingMutationListener(
mockTopic, mockTopic,
mockTransactionService, mockTransactionService
mockCacheService
); );
}); });
@@ -83,27 +72,48 @@ define(
.toHaveBeenCalledWith(jasmine.any(Function)); .toHaveBeenCalledWith(jasmine.any(Function));
}); });
it("calls persist if the model has changed", function () { [false, true].forEach(function (isActive) {
mockModel.persisted = Date.now(); var verb = isActive ? "is" : "isn't";
//Mark the model dirty by setting the mutated date later than the last persisted date. function onlyWhenInactive(expectation) {
mockModel.modified = mockModel.persisted + 1; return isActive ? expectation.not : expectation;
}
mockMutationTopic.listen.calls.mostRecent() describe("when a transaction " + verb + " active", function () {
.args[0](mockDomainObject); 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 () { describe("and mutation occurs", function () {
mockModel.persisted = Date.now(); beforeEach(function () {
mockMutationTopic.listen.calls.mostRecent()
.args[0](mockDomainObject);
});
mockModel.modified = mockModel.persisted;
mockMutationTopic.listen.calls.mostRecent() it(innerVerb + " start a new transaction", function () {
.args[0](mockDomainObject); 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/MoveAction",
"./src/actions/CopyAction", "./src/actions/CopyAction",
"./src/actions/LinkAction", "./src/actions/LinkAction",
"./src/actions/GoToOriginalAction",
"./src/actions/SetPrimaryLocationAction", "./src/actions/SetPrimaryLocationAction",
"./src/services/LocatingCreationDecorator", "./src/services/LocatingCreationDecorator",
"./src/services/LocatingObjectDecorator", "./src/services/LocatingObjectDecorator",
@@ -40,6 +41,7 @@ define([
MoveAction, MoveAction,
CopyAction, CopyAction,
LinkAction, LinkAction,
GoToOriginalAction,
SetPrimaryLocationAction, SetPrimaryLocationAction,
LocatingCreationDecorator, LocatingCreationDecorator,
LocatingObjectDecorator, LocatingObjectDecorator,
@@ -102,6 +104,14 @@ define([
"linkService" "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", "key": "locate",
"name": "Set Primary Location", "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()" <button ng-click="timer.clickStopButton()"
ng-hide="timer.timerState == 'stopped'" ng-hide="timer.timerState == 'stopped'"
title="Reset" 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()" <button ng-click="timer.clickButton()"
title="{{timer.buttonText()}}" 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>
<div class="c-timer__direction {{timer.signClass()}}" <div class="c-timer__direction {{timer.signClass()}}"
ng-hide="!timer.signClass()"></div> ng-hide="!timer.signClass()"></div>

View File

@@ -49,7 +49,7 @@ define(
}; };
ClockIndicator.prototype.getCssClass = function () { ClockIndicator.prototype.getCssClass = function () {
return "t-indicator-clock icon-clock no-minify c-indicator--not-clickable"; return "t-indicator-clock icon-clock no-collapse float-right";
}; };
ClockIndicator.prototype.getText = function () { ClockIndicator.prototype.getText = function () {

View File

@@ -45,6 +45,7 @@ define([
"key": "url", "key": "url",
"name": "URL", "name": "URL",
"control": "textfield", "control": "textfield",
"pattern": "^(ftp|https?)\\:\\/\\/",
"required": true, "required": true,
"cssClass": "l-input-lg" "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"> <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="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"> ng-mouseenter="showLocalControls = true;"
<span class="holder flex-elem grows c-imagery__lc__sliders"> 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" <input class="icon-brightness" type="range"
min="0" min="0"
max="500" max="500"
ng-model="filters.brightness" /> ng-model="filters.brightness">
</input>
<input class="icon-contrast" type="range" <input class="icon-contrast" type="range"
min="0" min="0"
max="500" max="500"
ng-model="filters.contrast" /> ng-model="filters.contrast">
</input>
</span> </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" <a class="s-icon-button icon-reset t-btn-reset"
ng-click="filters = { brightness: 100, contrast: 100 }"></a> ng-click="filters = { brightness: 100, contrast: 100 }"></a>
</span> </span>
@@ -29,14 +33,14 @@
<div class="l-image-main-controlbar flex-elem l-flex-row"> <div class="l-image-main-controlbar flex-elem l-flex-row">
<div class="l-datetime-w flex-elem grows"> <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> ng-click="showThumbsBubble = (showThumbsBubble) ? false:true"></a>
<span class="l-time">{{imagery.getTime()}}</span> <span class="l-time">{{imagery.getTime()}}</span>
</div> </div>
<div class="h-local-controls flex-elem"> <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-click="imagery.paused(!imagery.paused())"
ng-class="{ 'is-paused': imagery.paused() }"></a> ng-class="{ paused: imagery.paused() }"></a>
<a href="" <a href=""
class="s-button l-mag s-mag vsm icon-reset" class="s-button l-mag s-mag vsm icon-reset"
ng-click="clipped = false" ng-click="clipped = false"

View File

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

View File

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

View File

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

View File

@@ -64,30 +64,12 @@ define(['zepto'], function ($) {
var tree = this.generateNewIdentifiers(objTree); var tree = this.generateNewIdentifiers(objTree);
var rootId = tree.rootId; var rootId = tree.rootId;
var rootObj = this.instantiate(tree.openmct[rootId], 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 genereated ids,
// Instantiate all objects in tree with their newly generated ids, // adding each to its rightful parent's composition
// adding each to its rightful parent's composition rootObj.getCapability("location").setPrimaryLocation(parent.getId());
rootObj.getCapability("location").setPrimaryLocation(parent.getId()); this.deepInstantiate(rootObj, tree.openmct, []);
this.deepInstantiate(rootObj, tree.openmct, []); parent.getCapability("composition").add(rootObj);
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();
}
}
]
});
}
}; };
ImportAsJSONAction.prototype.deepInstantiate = function (parent, tree, seen) { ImportAsJSONAction.prototype.deepInstantiate = function (parent, tree, seen) {
@@ -98,17 +80,15 @@ define(['zepto'], function ($) {
var newObj; var newObj;
seen.push(parent.getId()); seen.push(parent.getId());
parentModel.composition.forEach(function (childId, index) {
parentModel.composition.forEach(function (childId) { if (!tree[childId] || seen.includes(childId)) {
let keystring = this.openmct.objects.makeKeyString(childId);
if (!tree[keystring] || seen.includes(keystring)) {
return; return;
} }
newObj = this.instantiate(tree[keystring], keystring); newObj = this.instantiate(tree[childId], childId);
parent.getCapability("composition").add(newObj);
newObj.getCapability("location") newObj.getCapability("location")
.setPrimaryLocation(tree[keystring].location); .setPrimaryLocation(tree[childId].location);
this.deepInstantiate(newObj, tree, seen); this.deepInstantiate(newObj, tree, seen);
}, this); }, this);
} }

View File

@@ -100,7 +100,7 @@ define(
} }
CouchIndicator.prototype.getCssClass = function () { CouchIndicator.prototype.getCssClass = function () {
return "c-indicator--clickable icon-database " + this.state.statusClass; return "icon-database " + this.state.statusClass;
}; };
CouchIndicator.prototype.getGlyphClass = function () { CouchIndicator.prototype.getGlyphClass = function () {

View File

@@ -84,7 +84,7 @@ define(
} }
ElasticIndicator.prototype.getCssClass = function () { ElasticIndicator.prototype.getCssClass = function () {
return "c-indicator--clickable icon-database"; return "icon-database";
}; };
ElasticIndicator.prototype.getGlyphClass = function () { ElasticIndicator.prototype.getGlyphClass = function () {
return this.state.glyphClass; return this.state.glyphClass;

View File

@@ -41,7 +41,7 @@ define(
} }
LocalStorageIndicator.prototype.getCssClass = function () { LocalStorageIndicator.prototype.getCssClass = function () {
return "c-indicator--clickable icon-database s-status-caution"; return "icon-database s-status-caution";
}; };
LocalStorageIndicator.prototype.getGlyphClass = function () { LocalStorageIndicator.prototype.getGlyphClass = function () {
return 'caution'; return 'caution';

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 * @param {Array} failures persistence failures, as prepared
* by PersistenceQueueHandler * by PersistenceQueueHandler
* @memberof platform/persistence/queue.PersistenceFailureHandler# * @memberof platform/persistence/queue.PersistenceFailureHandler#
*/ */
PersistenceFailureHandler.prototype.handle = function handleFailures(failures) { PersistenceFailureHandler.prototype.handle = function handleFailures(failures) {
// Prepare dialog for display
var dialogModel = new PersistenceFailureDialog(failures), var dialogModel = new PersistenceFailureDialog(failures),
revisionErrors = dialogModel.model.revised, revisionErrors = dialogModel.model.revised,
$q = this.$q; $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 // Discard changes for a failed refresh
function discard(failure) { function discard(failure) {
var persistence = var persistence =
@@ -60,7 +118,19 @@ define(
return $q.all(failuresToDiscard.map(discard)); 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; return PersistenceFailureHandler;

View File

@@ -74,14 +74,43 @@ define(
handler = new PersistenceFailureHandler(mockQ, mockDialogService); handler = new PersistenceFailureHandler(mockQ, mockDialogService);
}); });
it("discards on handle", function () { it("shows a dialog to handle failures", function () {
handler.handle(mockFailures); 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) { mockFailures.forEach(function (mockFailure) {
expect(mockFailure.persistence.refresh).toHaveBeenCalled(); expect(mockFailure.persistence.refresh).toHaveBeenCalled();
expect(mockFailure.requeue).not.toHaveBeenCalled(); expect(mockFailure.requeue).not.toHaveBeenCalled();
expect(mockFailure.domainObject.useCapability).not.toHaveBeenCalled(); expect(mockFailure.domainObject.useCapability).not.toHaveBeenCalled();
}); });
}); });
}); });
} }
); );

View File

@@ -45,7 +45,7 @@
</mct-include> </mct-include>
</div> </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-show="!(ngModel.input === '' || ngModel.input === undefined)"
ng-click="ngModel.input = ''; ngModel.checkAll = true; menuController.checkAll(); controller.search()"> ng-click="ngModel.input = ''; ngModel.checkAll = true; menuController.checkAll(); controller.search()">
Cancel</a> Cancel</a>

View File

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

View File

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

View File

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

View File

@@ -137,7 +137,8 @@ define([
function callbackWrapper(series) { function callbackWrapper(series) {
callback(createDatum(domainObject, metadata, series, series.getPointCount() - 1)); callback(createDatum(domainObject, metadata, series, series.getPointCount() - 1));
} }
return capability.subscribe(callbackWrapper, request) || function () {};
return capability.subscribe(callbackWrapper, request);
}; };
LegacyTelemetryProvider.prototype.supportsLimits = function (domainObject) { LegacyTelemetryProvider.prototype.supportsLimits = function (domainObject) {
@@ -157,7 +158,7 @@ define([
return { return {
evaluate: function (datum, property) { 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); }.bind(this);
handleLegacyMutation = function (legacyObject) { handleLegacyMutation = function (legacyObject) {
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId()), var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
keystring = utils.makeKeyString(newStyleObject.identifier); this.eventEmitter.emit(newStyleObject.identifier.key + ":*", newStyleObject);
this.eventEmitter.emit(keystring + ":*", newStyleObject);
this.eventEmitter.emit('mutation', newStyleObject); this.eventEmitter.emit('mutation', newStyleObject);
}.bind(this); }.bind(this);

View File

@@ -45,30 +45,15 @@ define([
view: function (domainObject) { view: function (domainObject) {
let $rootScope = openmct.$injector.get('$rootScope'); let $rootScope = openmct.$injector.get('$rootScope');
let templateLinker = openmct.$injector.get('templateLinker'); let templateLinker = openmct.$injector.get('templateLinker');
let scope = $rootScope.$new(true); let scope = $rootScope.$new();
let legacyObject = convertToLegacyObject(domainObject); let legacyObject = convertToLegacyObject(domainObject);
let isDestroyed = false; let isDestroyed = false;
let unlistenToStatus;
let element;
scope.domainObject = legacyObject; scope.domainObject = legacyObject;
scope.model = legacyObject.getModel(); scope.model = legacyObject.getModel();
let child;
let parent;
return { return {
show: function (container) { 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 ? // TODO: implement "gestures" support ?
let uses = legacyView.uses || []; let uses = legacyView.uses || [];
let promises = []; let promises = [];
@@ -89,13 +74,12 @@ define([
uses.forEach(function (key, i) { uses.forEach(function (key, i) {
scope[key] = results[i]; scope[key] = results[i];
}); });
element = openmct.$angular.element(child);
templateLinker.link( templateLinker.link(
scope, scope,
element, openmct.$angular.element(container),
legacyView legacyView
); );
child.classList.add('u-contents'); container.classList.add('u-contents');
} }
if (promises.length) { if (promises.length) {
@@ -108,16 +92,8 @@ define([
link(); link();
} }
}, },
onClearData() {
scope.$broadcast('clearData');
},
destroy: function () { destroy: function () {
element.off();
element.remove();
scope.$destroy(); scope.$destroy();
element = null;
scope = null;
unlistenToStatus();
} }
} }
}, },

View File

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

View File

@@ -1,45 +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.
*****************************************************************************/
let brandingOptions = {};
/**
* @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.
*/
/**
* 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;
}
return brandingOptions;
}

View File

@@ -28,6 +28,11 @@ export default class Editor extends EventEmitter {
super(); super();
this.editing = false; this.editing = false;
this.openmct = openmct; 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 * @private
*/ */
cancel() { cancel() {
let cancelPromise = this.getTransactionService().cancel(); this.getTransactionService().cancel();
this.editing = false; this.editing = false;
this.emit('isEditing', false); this.emit('isEditing', false);
return cancelPromise;
} }
/** /**

View File

@@ -21,23 +21,7 @@ define([
topicService.and.returnValue(mutationTopic); topicService.and.returnValue(mutationTopic);
publicAPI = {}; publicAPI = {};
publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [ publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [
'get', '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'
]); ]);
publicAPI.objects.get.and.callFake(function (identifier) { publicAPI.objects.get.and.callFake(function (identifier) {
return Promise.resolve({identifier: identifier}); return Promise.resolve({identifier: identifier});
@@ -68,14 +52,6 @@ define([
{ {
namespace: 'test', namespace: 'test',
key: 'a' key: 'a'
},
{
namespace: 'test',
key: 'b'
},
{
namespace: 'test',
key: 'c'
} }
] ]
}; };
@@ -92,55 +68,55 @@ define([
composition.on('add', listener); composition.on('add', listener);
return composition.load().then(function () { return composition.load().then(function () {
expect(listener.calls.count()).toBe(3); expect(listener.calls.count()).toBe(1);
expect(listener).toHaveBeenCalledWith({ expect(listener).toHaveBeenCalledWith({
identifier: {namespace: 'test', key: 'a'} 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(); // TODO: Implement add/removal in new default provider.
}); xit('synchronizes changes between instances', function () {
it('', function () { var otherComposition = compositionAPI.get(domainObject);
composition.reorder(1, 0); var addListener = jasmine.createSpy('addListener');
let newComposition = var removeListener = jasmine.createSpy('removeListener');
publicAPI.objects.mutate.calls.mostRecent().args[2]; var otherAddListener = jasmine.createSpy('otherAddListener');
let reorderPlan = listener.calls.mostRecent().args[0][0]; var otherRemoveListener = jasmine.createSpy('otherRemoveListener');
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: ''}
};
composition.on('add', addListener); 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); return Promise.all([composition.load(), otherComposition.load()])
expect(domainObject.composition[3]).toEqual(mockChildObject.identifier); .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' key: 'thing'
} }
]); ]);
}, }
add: jasmine.createSpy('add'),
remove: jasmine.createSpy('remove')
}; };
domainObject = { domainObject = {
identifier: { 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 () { describe('dynamic custom composition', function () {

View File

@@ -25,6 +25,7 @@ define([
], function ( ], function (
_ _
) { ) {
/** /**
* A CompositionCollection represents the list of domain objects contained * A CompositionCollection represents the list of domain objects contained
* by another domain object. It provides methods for loading this * by another domain object. It provides methods for loading this
@@ -55,13 +56,13 @@ define([
this.listeners = { this.listeners = {
add: [], add: [],
remove: [], remove: [],
load: [], load: []
reorder: []
}; };
this.onProviderAdd = this.onProviderAdd.bind(this); this.onProviderAdd = this.onProviderAdd.bind(this);
this.onProviderRemove = this.onProviderRemove.bind(this); this.onProviderRemove = this.onProviderRemove.bind(this);
} }
/** /**
* Listen for changes to this composition. Supports 'add', 'remove', and * Listen for changes to this composition. Supports 'add', 'remove', and
* 'load' events. * 'load' events.
@@ -74,9 +75,7 @@ define([
if (!this.listeners[event]) { if (!this.listeners[event]) {
throw new Error('Event not supported by composition: ' + event); throw new Error('Event not supported by composition: ' + event);
} }
if (!this.mutationListener) {
this._synchronize();
}
if (this.provider.on && this.provider.off) { if (this.provider.on && this.provider.off) {
if (event === 'add') { if (event === 'add') {
this.provider.on( this.provider.on(
@@ -92,13 +91,6 @@ define([
this.onProviderRemove, this.onProviderRemove,
this this
); );
} if (event === 'reorder') {
this.provider.on(
this.domainObject,
'reorder',
this.onProviderReorder,
this
)
} }
} }
@@ -132,8 +124,6 @@ define([
this.listeners[event].splice(index, 1); this.listeners[event].splice(index, 1);
if (this.listeners[event].length === 0) { if (this.listeners[event].length === 0) {
this._destroy();
// Remove provider listener if this is the last callback to // Remove provider listener if this is the last callback to
// be removed. // be removed.
if (this.provider.off && this.provider.on) { if (this.provider.off && this.provider.on) {
@@ -151,13 +141,6 @@ define([
this.onProviderRemove, this.onProviderRemove,
this 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) { CompositionCollection.prototype.add = function (child, skipMutate) {
if (!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); this.provider.add(this.domainObject, child.identifier);
} else { } else {
this.emit('add', child); 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. * Handle adds from provider.
* @private * @private
@@ -271,29 +228,16 @@ define([
this.remove(child, true); 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. * Emit events.
* @private * @private
*/ */
CompositionCollection.prototype.emit = function (event, ...payload) { CompositionCollection.prototype.emit = function (event, payload) {
this.listeners[event].forEach(function (l) { this.listeners[event].forEach(function (l) {
if (l.context) { if (l.context) {
l.callback.apply(l.context, payload); l.callback.call(l.context, payload);
} else { } else {
l.callback(...payload); l.callback(payload);
} }
}); });
}; };

View File

@@ -48,11 +48,24 @@ define([
this.listeningTo = {}; this.listeningTo = {};
this.onMutation = this.onMutation.bind(this); this.onMutation = this.onMutation.bind(this);
this.cannotContainDuplicates = this.cannotContainDuplicates.bind(this);
this.cannotContainItself = this.cannotContainItself.bind(this); this.cannotContainItself = this.cannotContainItself.bind(this);
compositionAPI.addPolicy(this.cannotContainDuplicates);
compositionAPI.addPolicy(this.cannotContainItself); 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 * @private
*/ */
@@ -113,7 +126,6 @@ define([
objectListeners = this.listeningTo[keyString] = { objectListeners = this.listeningTo[keyString] = {
add: [], add: [],
remove: [], remove: [],
reorder: [],
composition: [].slice.apply(domainObject.composition) composition: [].slice.apply(domainObject.composition)
}; };
} }
@@ -148,7 +160,7 @@ define([
}); });
objectListeners[event].splice(index, 1); 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]; delete this.listeningTo[keyString];
} }
}; };
@@ -166,12 +178,8 @@ define([
* @method remove * @method remove
*/ */
DefaultCompositionProvider.prototype.remove = function (domainObject, childId) { DefaultCompositionProvider.prototype.remove = function (domainObject, childId) {
let composition = domainObject.composition.filter(function (child) { // TODO: this needs to be synchronized via mutation.
return !(childId.namespace === child.namespace && throw new Error('Default Provider does not implement removal.');
childId.key === child.key);
});
this.publicAPI.objects.mutate(domainObject, 'composition', composition);
}; };
/** /**
@@ -186,66 +194,9 @@ define([
* @memberof module:openmct.CompositionProvider# * @memberof module:openmct.CompositionProvider#
* @method add * @method add
*/ */
DefaultCompositionProvider.prototype.add = function (parent, childId) { DefaultCompositionProvider.prototype.add = function (domainObject, child) {
if (!this.includes(parent, childId)) { throw new Error('Default Provider does not implement adding.');
parent.composition.push(childId); // TODO: this needs to be synchronized via mutation
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);
}
}
}; };
/** /**

View File

@@ -28,7 +28,7 @@ define(['zepto', './res/indicator-template.html'],
this.openmct = openmct; this.openmct = openmct;
this.element = $(indicatorTemplate)[0]; this.element = $(indicatorTemplate)[0];
this.textElement = this.element.querySelector('.js-indicator-text'); this.textElement = this.element.querySelector('.indicator-text');
//Set defaults //Set defaults
this.text('New Indicator'); this.text('New Indicator');

View File

@@ -1,3 +1,3 @@
<div class="c-indicator c-indicator--clickable c-indicator--simple" title=""> <div class="ls-indicator" title="">
<span class="label js-indicator-text c-indicator__label"></span> <span class="label indicator-text"></span>
</div> </div>

View File

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

View File

@@ -226,20 +226,7 @@ define([
(identifier.namespace === identifiers[0].namespace && (identifier.namespace === identifiers[0].namespace &&
identifier.key === identifiers[0].key); 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. * Uniquely identifies a domain object.

View File

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

View File

@@ -27,16 +27,10 @@
<style lang="scss"> <style lang="scss">
@import "~styles/sass-base"; @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 { .c-message {
display: flex; display: flex;
align-items: center; align-items: center;
padding: $interiorMarginLg;
> * + * { > * + * {
margin-left: $interiorMarginLg; margin-left: $interiorMarginLg;
@@ -64,44 +58,7 @@
&__title, &__title,
&__action-text { &__action-text {
font-size: 1.2em; // TEMP 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: $colorWarningHi;
}
// Messages in a list
.c-overlay__messages & {
padding: $interiorMarginLg;
&:before {
font-size: $messageListIconD;
}
} }
} }
</style> </style>

View File

@@ -56,46 +56,16 @@
} }
&__close-button { &__close-button {
$p: $interiorMargin; $p: $interiorMarginSm;
border-radius: 100% !important; border-radius: 100% !important;
color: $overlayColorFg;
display: inline-block; display: inline-block;
font-size: 1.25em;
position: absolute; position: absolute;
top: $p; right: $p; top: $p; right: $p;
} }
&__contents { &__contents {
flex: 1 1 auto; flex: 1 1 auto;
display: flex;
flex-direction: column;
overflow: hidden;
}
&__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; overflow: auto;
padding-right: $interiorMargin; // fend off scroll bar
} }
&__button-bar { &__button-bar {
@@ -119,29 +89,18 @@
.c-overlay { .c-overlay {
&__blocker { &__blocker {
@include abs(); @include abs();
background: $colorOvrBlocker; background: rgba(black, 0.7);
cursor: pointer; cursor: pointer;
display: block; display: block;
} }
}
// Overlay types, styling for desktop. Appended to .l-overlay-wrapper element. &__outer {
.l-overlay-large,
.l-overlay-small,
.l-overlay-fit {
.c-overlay__outer {
border-radius: $overlayCr; border-radius: $overlayCr;
box-shadow: rgba(black, 0.5) 0 2px 25px; 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 { .l-overlay-large {
// Default // Default
.c-overlay__outer { .c-overlay__outer {
@@ -155,7 +114,6 @@
} }
} }
.t-dialog-sm .l-overlay-small, // Legacy dialog support
.l-overlay-fit { .l-overlay-fit {
.c-overlay__outer { .c-overlay__outer {
@include overlaySizing(auto); @include overlaySizing(auto);

View File

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

View File

@@ -28,22 +28,14 @@ define([
describe('Telemetry API', function () { describe('Telemetry API', function () {
var openmct; var openmct;
var telemetryAPI; var telemetryAPI;
var mockTypeService;
beforeEach(function () { beforeEach(function () {
openmct = { openmct = {
time: jasmine.createSpyObj('timeAPI', [ time: jasmine.createSpyObj('timeAPI', [
'timeSystem', 'timeSystem',
'bounds' '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.timeSystem.and.returnValue({key: 'system'});
openmct.time.bounds.and.returnValue({start: 0, end: 1}); openmct.time.bounds.and.returnValue({start: 0, end: 1});
telemetryAPI = new TelemetryAPI(openmct); 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); return hints.every(hasHint, metadata);
} }
var matchingMetadata = this.valueMetadatas.filter(hasHints); var matchingMetadata = this.valueMetadatas.filter(hasHints);
let iteratees = hints.map(hint => { var sortedMetadata = _.sortBy(matchingMetadata, function (metadata) {
return (metadata) => { return hints.map(function (hint) {
return metadata.hints[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 () { TelemetryMetadataManager.prototype.getDefaultDisplayValue = function () {
let valueMetadata = this.valuesForHints(['range'])[0]; let valueMetadata = this.valuesForHints(['range'])[0];

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,18 +0,0 @@
<template>
<div class="c-indicator c-indicator--clickable icon-session">
<span class="label c-indicator__label">
<button @click="globalClearEmit">Clear All Data</button>
</span>
</div>
</template>
<script>
export default {
inject: ['openmct'],
methods: {
globalClearEmit() {
this.openmct.objectViews.emit('clearData');
}
}
}
</script>

View File

@@ -1,54 +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.
*****************************************************************************/
define([
'./components/globalClearIndicator.vue',
'./clearDataAction',
'vue'
], function (
GlobaClearIndicator,
ClearDataAction,
Vue
) {
return function plugin(appliesToObjects) {
appliesToObjects = appliesToObjects || [];
return function install(openmct) {
let component = new Vue ({
provide: {
openmct
},
components: {
GlobalClearIndicator: GlobaClearIndicator.default
},
template: '<GlobalClearIndicator></GlobalClearIndicator>'
}),
indicator = {
element: component.$mount().$el
};
openmct.indicators.add(indicator);
openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
};
};
});

View File

@@ -1,62 +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.
*****************************************************************************/
import ClearDataActionPlugin from '../plugin.js';
import ClearDataAction from '../clearDataAction.js';
describe('When the Clear Data Plugin is installed,', function () {
var mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']),
mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']),
mockContextMenuProvider = jasmine.createSpyObj('contextMenu', ['registerAction']),
openmct = {
objectViews: mockObjectViews,
indicators: mockIndicatorProvider,
contextMenu: mockContextMenuProvider,
install: function (plugin) {
plugin(this);
}
},
mockObjectPath = [
{name: 'mockObject1'},
{name: 'mockObject2'}
];
it('Global Clear Indicator is installed', function () {
openmct.install(ClearDataActionPlugin([]));
expect(mockIndicatorProvider.add).toHaveBeenCalled();
});
it('Clear Data context menu action is installed', function () {
openmct.install(ClearDataActionPlugin([]));
expect(mockContextMenuProvider.registerAction).toHaveBeenCalled();
});
it('clear data action emits a clearData event when invoked', function () {
let action = new ClearDataAction(openmct);
action.invoke(mockObjectPath);
expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
});
});

View File

@@ -1,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", key: "layout",
description: "A toolbar for objects inside a display layout.", description: "A toolbar for objects inside a display layout.",
forSelection: function (selection) { forSelection: function (selection) {
if (!selection || selection.length === 0) { // Apply the layout toolbar if the edit mode is on, and the selected object
return false; // 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') ||
let selectionPath = selection[0]; (selection[0].context.item && selection[0].context.item.type === 'layout')));
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');
}, },
toolbar: function (selection) { toolbar: function (selection) {
const DIALOG_FORM = { const DIALOG_FORM = {
@@ -79,72 +73,190 @@ define([], function () {
return openmct.$injector.get('dialogService').getUserInput(form, {}); return openmct.$injector.get('dialogService').getUserInput(form, {});
} }
function getPath(selectionPath) { function getPath() {
return `configuration.items[${selectionPath[0].context.index}]`; return `configuration.items[${selection[0].context.index}]`;
} }
function getAllTypes(selection) { let selectedParent = selection[1] && selection[1].context.item,
return selection.filter(selectionPath => { selectedObject = selection[0].context.item,
let type = selectionPath[0].context.layoutItem.type; layoutItem = selection[0].context.layoutItem,
return type === 'text-view' || toolbar = [];
type === 'telemetry-view' ||
type === 'box-view' || if (selectedObject && selectedObject.type === 'layout') {
type === 'image-view' || toolbar.push({
type === 'line-view' || control: "menu",
type === 'subobject-view'; 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 (!layoutItem) {
if (selection.length === 1) { return toolbar;
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"
}
]
};
}
} }
function getToggleFrameButton(selectedParent, selection) { let separator = {
return { 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", control: "toggle-button",
domainObject: selectedParent, domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => property: function () {
selectionPath[0].context.layoutItem.type === 'subobject-view' return getPath() + ".hasFrame";
),
property: function (selectionPath) {
return getPath(selectionPath) + ".hasFrame";
}, },
options: [ options: [
{ {
@@ -158,186 +270,52 @@ define([], function () {
title: "Frame hidden" title: "Frame hidden"
} }
] ]
}; });
} toolbar.push(separator);
toolbar.push(stackOrder);
function getRemoveButton(selectedParent, selectionPath, selection) { toolbar.push(x);
return { toolbar.push(y);
control: "button", toolbar.push(width);
domainObject: selectedParent, toolbar.push(height);
icon: "icon-trash", toolbar.push(useGrid);
title: "Delete the selected object", toolbar.push(separator);
method: function () { toolbar.push(remove);
let removeItem = selectionPath[1].context.removeItem; } else {
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) {
const TEXT_SIZE = [8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96, 128]; 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", control: "select-menu",
domainObject: selectedParent, domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => { property: function () {
let type = selectionPath[0].context.layoutItem.type; return getPath() + ".size";
return type === 'text-view' || type === 'telemetry-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".size";
}, },
title: "Set text size", title: "Set text size",
options: TEXT_SIZE.map(size => { options: TEXT_SIZE.map(size => {
@@ -346,128 +324,13 @@ define([], function () {
}; };
}) })
}; };
}
function getFillMenu(selectedParent, selection) { if (layoutItem.type === 'telemetry-view') {
return { let displayMode = {
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 {
control: "select-menu", control: "select-menu",
domainObject: selectedParent, domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => { property: function () {
return selectionPath[0].context.layoutItem.type === 'telemetry-view'; return getPath() + ".displayMode";
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".displayMode";
}, },
title: "Set display mode", title: "Set display mode",
options: [ options: [
@@ -484,196 +347,146 @@ define([], function () {
value: "value" 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
}
})
}; };
} toolbar = [
} displayMode,
separator,
function getSeparator() { value,
return { separator,
control: "separator" fill,
}; stroke,
} color,
separator,
function isMainLayoutSelected(selectionPath) { size,
let selectedObject = selectionPath[0].context.item; separator,
return selectedObject && selectedObject.type === 'layout' && stackOrder,
!selectionPath[0].context.layoutItem; x,
} y,
height,
if (isMainLayoutSelected(selection[0])) { width,
return [getAddButton(selection)]; useGrid,
} separator,
remove
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)];
}
} else if (layoutItem.type === 'text-view') { } else if (layoutItem.type === 'text-view') {
if (toolbar['style'].length < 2) { let text = {
toolbar['style'] = [ control: "button",
getFillMenu(selectedParent, selection), domainObject: selectedParent,
getStrokeMenu(selectedParent, selection) property: function () {
]; return getPath();
} },
if (toolbar['text-style'].length === 0) { icon: "icon-gear",
toolbar['text-style'] = [ title: "Edit text properties",
getTextColorMenu(selectedParent, selection), dialog: DIALOG_FORM['text']
getTextSizeMenu(selectedParent, selection) };
]; toolbar = [
} fill,
if (toolbar['position'].length === 0) { stroke,
toolbar['position'] = [ separator,
getStackOrder(selectedParent, selectionPath), color,
getXInput(selectedParent, selection), size,
getYInput(selectedParent, selection), separator,
getHeightInput(selectedParent, selection), stackOrder,
getWidthInput(selectedParent, selection) x,
]; y,
} height,
if (toolbar['text'].length === 0) { width,
toolbar['text'] = [getTextButton(selectedParent, selection)]; useGrid,
} separator,
if (toolbar['remove'].length === 0) { text,
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)]; separator,
} remove
];
} else if (layoutItem.type === 'box-view') { } else if (layoutItem.type === 'box-view') {
if (toolbar['style'].length < 2) { toolbar = [
toolbar['style'] = [ fill,
getFillMenu(selectedParent, selection), stroke,
getStrokeMenu(selectedParent, selection) separator,
]; stackOrder,
} x,
if (toolbar['position'].length === 0) { y,
toolbar['position'] = [ height,
getStackOrder(selectedParent, selectionPath), width,
getXInput(selectedParent, selection), useGrid,
getYInput(selectedParent, selection), separator,
getHeightInput(selectedParent, selection), remove
getWidthInput(selectedParent, selection) ];
];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
} else if (layoutItem.type === 'image-view') { } else if (layoutItem.type === 'image-view') {
if (toolbar['style'].length === 0) { let url = {
toolbar['style'] = [ control: "button",
getStrokeMenu(selectedParent, selection) domainObject: selectedParent,
]; property: function () {
} return getPath();
if (toolbar['position'].length === 0) { },
toolbar['position'] = [ icon: "icon-image",
getStackOrder(selectedParent, selectionPath), title: "Edit image properties",
getXInput(selectedParent, selection), dialog: DIALOG_FORM['image']
getYInput(selectedParent, selection), };
getHeightInput(selectedParent, selection), toolbar = [
getWidthInput(selectedParent, selection) stroke,
]; separator,
} stackOrder,
if (toolbar['url'].length === 0) { x,
toolbar['url'] = [getURLButton(selectedParent, selection)]; y,
} height,
if (toolbar['remove'].length === 0) { width,
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)]; useGrid,
} separator,
url,
separator,
remove
];
} else if (layoutItem.type === 'line-view') { } else if (layoutItem.type === 'line-view') {
if (toolbar['style'].length === 0) { let x2 = {
toolbar['style'] = [ control: "input",
getStrokeMenu(selectedParent, selection) type: "number",
]; domainObject: selectedParent,
} property: function () {
if (toolbar['position'].length === 0) { return getPath() + ".x2";
toolbar['position'] = [ },
getStackOrder(selectedParent, selectionPath), label: "X2:",
getXInput(selectedParent, selection), title: "X2 position"
getYInput(selectedParent, selection), },
getX2Input(selectedParent, selection), y2 = {
getY2Input(selectedParent, selection) control: "input",
]; type: "number",
} domainObject: selectedParent,
if (toolbar['remove'].length === 0) { property: function () {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)]; 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 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;
}, []));
} }
} }
} }

View File

@@ -95,7 +95,7 @@ define(
* @param {number[]} pixelDelta the offset from the * @param {number[]} pixelDelta the offset from the
* original position, in pixels * original position, in pixels
*/ */
LayoutDrag.prototype.getAdjustedPositionAndDimensions = function (pixelDelta) { LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
var gridDelta = toGridDelta(this.gridSize, pixelDelta); var gridDelta = toGridDelta(this.gridSize, pixelDelta);
return { return {
position: max(add( 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; 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> <template>
<layout-frame :item="item" <layout-frame :item="item"
:grid-size="gridSize" :grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)" @endDrag="(item, updates) => $emit('endDrag', item, updates)">
@endMove="() => $emit('endMove')">
<div class="c-box-view" <div class="c-box-view"
:style="style"> :style="style">
</div> </div>
@@ -55,7 +54,8 @@
x: 1, x: 1,
y: 1, y: 1,
width: 10, width: 10,
height: 5 height: 5,
useGrid: true
}; };
}, },
inject: ['openmct'], inject: ['openmct'],

View File

@@ -23,11 +23,8 @@
<template> <template>
<div class="l-layout" <div class="l-layout"
@dragover="handleDragOver" @dragover="handleDragOver"
@click.capture="bypassSelection" @click="bypassSelection"
@drop="handleDrop" @drop="handleDrop">
:class="{
'is-multi-selected': selectedLayoutItems.length > 1
}">
<!-- Background grid --> <!-- Background grid -->
<div class="l-layout__grid-holder c-grid"> <div class="l-layout__grid-holder c-grid">
<div class="c-grid__x l-grid l-grid-x" <div class="c-grid__x l-grid l-grid-x"
@@ -42,39 +39,18 @@
:is="item.type" :is="item.type"
:item="item" :item="item"
:key="item.id" :key="item.id"
:gridSize="gridSize" :gridSize="item.useGrid ? gridSize : [1, 1]"
:initSelect="initSelectIndex === index" :initSelect="initSelectIndex === index"
:index="index" :index="index"
:multiSelect="selectedLayoutItems.length > 1" @endDrag="endDrag"
@move="move" >
@endMove="endMove"
@endLineResize='endLineResize'
@formatChanged='updateTelemetryFormat'>
</component> </component>
<edit-marquee v-if='showMarquee'
:gridSize="gridSize"
:selectedLayoutItems="selectedLayoutItems"
@endResize="endResize">
</edit-marquee>
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
@import "~styles/sass-base"; @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 { .l-layout {
@include abs(); @include abs();
display: flex; display: flex;
@@ -94,7 +70,7 @@
.l-shell__main-container { .l-shell__main-container {
&[s-selected], &[s-selected],
&[s-selected-parent] { &[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 { > .l-layout {
background: $editUIGridColorBg; background: $editUIGridColorBg;
@@ -108,7 +84,7 @@
.l-layout__frame { .l-layout__frame {
&[s-selected], &[s-selected],
&[s-selected-parent] { &[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 { > * > * > .l-layout {
background: $editUIGridColorBg; background: $editUIGridColorBg;
box-shadow: inset $editUIGridColorFg 0 0 2px 1px; 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> </style>
<script> <script>
import uuid from 'uuid'; import uuid from 'uuid';
@@ -143,7 +108,6 @@
import TextView from './TextView.vue' import TextView from './TextView.vue'
import LineView from './LineView.vue' import LineView from './LineView.vue'
import ImageView from './ImageView.vue' import ImageView from './ImageView.vue'
import EditMarquee from './EditMarquee.vue'
const ITEM_TYPE_VIEW_MAP = { const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView, 'subobject-view': SubobjectView,
@@ -159,10 +123,8 @@
down: -1, down: -1,
bottom: Number.NEGATIVE_INFINITY bottom: Number.NEGATIVE_INFINITY
}; };
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
let components = ITEM_TYPE_VIEW_MAP; const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
components['edit-marquee'] = EditMarquee;
function getItemDefinition(itemType, ...options) { function getItemDefinition(itemType, ...options) {
let itemView = ITEM_TYPE_VIEW_MAP[itemType]; let itemView = ITEM_TYPE_VIEW_MAP[itemType];
@@ -179,8 +141,7 @@
let domainObject = JSON.parse(JSON.stringify(this.domainObject)); let domainObject = JSON.parse(JSON.stringify(this.domainObject));
return { return {
internalDomainObject: domainObject, internalDomainObject: domainObject,
initSelectIndex: undefined, initSelectIndex: undefined
selection: []
}; };
}, },
computed: { computed: {
@@ -189,145 +150,82 @@
}, },
layoutItems() { layoutItems() {
return this.internalDomainObject.configuration.items; 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'], props: ['domainObject'],
components: components, components: ITEM_TYPE_VIEW_MAP,
methods: { methods: {
addElement(itemType, element) { addElement(itemType, element) {
this.addItem(itemType + '-view', element); this.addItem(itemType + '-view', element);
}, },
setSelection(selection) { 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) { attachSelectionListener(index) {
return this.selection.some(selectionPath => let path = `configuration.items[${index}].useGrid`;
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.id === item.id); 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) { bypassSelection($event) {
if (this.dragInProgress) { if (this.dragInProgress) {
if ($event) { if ($event) {
$event.stopImmediatePropagation(); $event.stopImmediatePropagation();
} }
this.dragInProgress = false;
return; return;
} }
}, },
endLineResize(item, updates) { endDrag(item, updates) {
this.dragInProgress = true; this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
let index = this.layoutItems.indexOf(item); let index = this.layoutItems.indexOf(item);
Object.assign(item, updates); Object.assign(item, updates);
this.mutate(`configuration.items[${index}]`, item); 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) { mutate(path, value) {
this.openmct.objects.mutate(this.internalDomainObject, path, value); this.openmct.objects.mutate(this.internalDomainObject, path, value);
}, },
@@ -385,8 +283,9 @@
} }
}, },
isTelemetry(domainObject) { isTelemetry(domainObject) {
if (this.openmct.telemetry.isTelemetryObject(domainObject) && if (this.openmct.telemetry.isTelemetryObject(domainObject)
!this.options.showAsView.includes(domainObject.type)) { && domainObject.type !== 'summary-widget'
&& domainObject.type !== 'example.imagery') {
return true; return true;
} else { } else {
return false; return false;
@@ -415,15 +314,11 @@
this.objectViewMap[keyString] = true; this.objectViewMap[keyString] = true;
} }
}, },
removeItem(selectedItems) { removeItem(item, index) {
let indices = [];
this.initSelectIndex = -1; this.initSelectIndex = -1;
selectedItems.forEach(selectedItem => { this.layoutItems.splice(index, 1);
indices.push(selectedItem[0].context.index);
this.untrackItem(selectedItem[0].context.layoutItem);
});
_.pullAt(this.layoutItems, indices);
this.mutate("configuration.items", this.layoutItems); this.mutate("configuration.items", this.layoutItems);
this.untrackItem(item);
this.$el.click(); this.$el.click();
}, },
untrackItem(item) { untrackItem(item) {
@@ -489,80 +384,21 @@
this.mutate("configuration.items", layoutItems); this.mutate("configuration.items", layoutItems);
this.$el.click(); this.$el.click();
}, },
orderItem(position, selectedItems) { orderItem(position, index) {
let delta = ORDERS[position]; let delta = ORDERS[position];
let indices = []; let newIndex = Math.max(Math.min(index + delta, this.layoutItems.length - 1), 0);
let newIndex = -1; let item = this.layoutItems[index];
let items = [];
Object.assign(items, this.layoutItems); if (newIndex !== index) {
this.selectedLayoutItems.forEach(selectedItem => { this.layoutItems.splice(index, 1);
indices.push(this.layoutItems.indexOf(selectedItem)); this.layoutItems.splice(newIndex, 0, item);
}); this.mutate('configuration.items', this.layoutItems);
indices.sort((a, b) => a - b);
if (position === 'top' || position === 'up') { if (this.removeSelectionListener) {
indices.reverse(); this.removeSelectionListener();
} this.attachSelectionListener(newIndex);
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);
} }
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() { mounted() {
@@ -577,10 +413,14 @@
this.composition.load(); this.composition.load();
}, },
destroyed: function () { destroyed: function () {
this.openmct.selection.off('change', this.setSelection); this.openmct.off('change', this.setSelection);
this.composition.off('add', this.addChild); this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild); this.composition.off('remove', this.removeChild);
this.unlisten(); this.unlisten();
if (this.removeSelectionListener) {
this.removeSelectionListener();
}
} }
} }
</script> </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> <template>
<layout-frame :item="item" <layout-frame :item="item"
:grid-size="gridSize" :grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)" @endDrag="(item, updates) => $emit('endDrag', item, updates)">
@endMove="() => $emit('endMove')">
<div class="c-image-view" <div class="c-image-view"
:style="style"> :style="style">
</div> </div>
@@ -57,7 +56,8 @@
y: 1, y: 1,
width: 10, width: 10,
height: 5, height: 5,
url: element.url url: element.url,
useGrid: true
}; };
}, },
inject: ['openmct'], inject: ['openmct'],

View File

@@ -24,14 +24,25 @@
<div class="l-layout__frame c-frame" <div class="l-layout__frame c-frame"
:class="{ :class="{
'no-frame': !item.hasFrame, 'no-frame': !item.hasFrame,
'u-inspectable': inspectable 'u-inspectable': inspectable,
'is-resizing': isResizing
}" }"
:style="style"> :style="style">
<slot></slot> <slot></slot>
<div class="c-frame-edit__move" <!-- Drag handles -->
@mousedown="startMove([1,1], [0,0], $event)"> <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>
</div> </div>
</template> </template>
@@ -39,7 +50,7 @@
<style lang="scss"> <style lang="scss">
@import "~styles/sass-base"; @import "~styles/sass-base";
/******************* FRAME */ /******************************* FRAME */
.c-frame { .c-frame {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -48,15 +59,124 @@
> *:first-child { > *:first-child {
flex: 1 1 auto; 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; 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 { .is-editing {
/******************* STYLES FOR C-FRAME WHILE EDITING */
.c-frame { .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]) { &:not([s-selected]) {
&:hover { &:hover {
border: $editFrameBorderHov; border: $editFrameBorderHov;
@@ -68,110 +188,37 @@
border: $editFrameSelectedBorder; border: $editFrameSelectedBorder;
box-shadow: $editFrameSelectedShdw; box-shadow: $editFrameSelectedShdw;
.c-frame-edit__move { > .c-frame-edit {
cursor: move; [class*='__handle'] {
}
}
}
/******************* 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 {
display: block; 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> </style>
<script> <script>
import LayoutDrag from './../LayoutDrag' import LayoutDrag from './../LayoutDrag'
@@ -181,9 +228,21 @@
item: Object, item: Object,
gridSize: Array gridSize: Array
}, },
data() {
return {
dragPosition: undefined,
isResizing: undefined
}
},
computed: { computed: {
style() { style() {
let {x, y, width, height} = this.item; let {x, y, width, height} = this.item;
if (this.dragPosition) {
[x, y] = this.dragPosition.position;
[width, height] = this.dragPosition.dimensions;
}
return { return {
left: (this.gridSize[0] * x) + 'px', left: (this.gridSize[0] * x) + 'px',
top: (this.gridSize[1] * y) + 'px', top: (this.gridSize[1] * y) + 'px',
@@ -205,40 +264,36 @@
return value - this.initialPosition[index]; return value - this.initialPosition[index];
}.bind(this)); }.bind(this));
}, },
startMove(posFactor, dimFactor, event) { startDrag(posFactor, dimFactor, event, type) {
document.body.addEventListener('mousemove', this.continueMove); document.body.addEventListener('mousemove', this.continueDrag);
document.body.addEventListener('mouseup', this.endMove); document.body.addEventListener('mouseup', this.endDrag);
this.dragPosition = { 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.updatePosition(event);
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize); this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
this.isResizing = type === 'resize';
event.preventDefault(); event.preventDefault();
}, },
continueMove(event) { continueDrag(event) {
event.preventDefault(); event.preventDefault();
this.updatePosition(event); this.updatePosition(event);
let newPosition = this.activeDrag.getAdjustedPosition(this.delta); this.dragPosition = this.activeDrag.getAdjustedPosition(this.delta);
if (!_.isEqual(newPosition, this.dragPosition)) {
this.dragPosition = newPosition;
this.$emit('move', this.toGridDelta(this.delta));
}
}, },
endMove(event) { endDrag(event) {
document.body.removeEventListener('mousemove', this.continueMove); document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endMove); document.body.removeEventListener('mouseup', this.endDrag);
this.continueMove(event); this.continueDrag(event);
this.$emit('endMove'); 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.dragPosition = undefined;
this.initialPosition = undefined; this.initialPosition = undefined;
this.delta = undefined; this.delta = undefined;
this.isResizing = undefined;
event.preventDefault(); event.preventDefault();
},
toGridDelta(pixelDelta) {
return pixelDelta.map((v, i) => {
return Math.round(v / this.gridSize[i]);
});
} }
} }
} }

View File

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

View File

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

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