Compare commits
39 Commits
flexible-l
...
drag-and-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d760bae75 | ||
|
|
6180bb23e6 | ||
|
|
3d351ec8d1 | ||
|
|
850fa28bf6 | ||
|
|
270684c5fd | ||
|
|
afa1589cb5 | ||
|
|
18a94d938f | ||
|
|
c0b7276787 | ||
|
|
bb8342f62b | ||
|
|
0d8dad1559 | ||
|
|
c1ef701eb2 | ||
|
|
c6a181a2e7 | ||
|
|
981392ea07 | ||
|
|
5928a102a6 | ||
|
|
c748569433 | ||
|
|
a87fc51fbb | ||
|
|
e07cfc9394 | ||
|
|
32a0baa7a3 | ||
|
|
f06427cb3e | ||
|
|
9ae4e66c91 | ||
|
|
eeab6e9bde | ||
|
|
cabc410e0a | ||
|
|
2dcff00fa7 | ||
|
|
94cdce3551 | ||
|
|
a7948ce83e | ||
|
|
74faf1bd48 | ||
|
|
3e7527d55c | ||
|
|
9733674d6e | ||
|
|
e05dbadea2 | ||
|
|
bc512f3766 | ||
|
|
ae51e2e437 | ||
|
|
0e06a7b403 | ||
|
|
ff7df9ad1e | ||
|
|
1069a45cfc | ||
|
|
d13d59bfa0 | ||
|
|
55d3ab5e8a | ||
|
|
c073a21ba6 | ||
|
|
ed8137726d | ||
|
|
3ebdab5e51 |
@@ -49,7 +49,7 @@ define([
|
||||
{
|
||||
"key": "eventGenerator",
|
||||
"name": "Event Message Generator",
|
||||
"cssClass": "icon-folder-new",
|
||||
"cssClass": "icon-generator-events",
|
||||
"description": "For development use. Creates sample event message data that mimics a live data stream.",
|
||||
"priority": 10,
|
||||
"features": "creation",
|
||||
|
||||
@@ -38,7 +38,7 @@ define([
|
||||
openmct.types.addType("example.state-generator", {
|
||||
name: "State Generator",
|
||||
description: "For development use. Generates test enumerated telemetry by cycling through a given set of states",
|
||||
cssClass: "icon-telemetry",
|
||||
cssClass: "icon-generator-telemetry",
|
||||
creatable: true,
|
||||
form: [
|
||||
{
|
||||
@@ -66,7 +66,7 @@ define([
|
||||
openmct.types.addType("generator", {
|
||||
name: "Sine Wave Generator",
|
||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||
cssClass: "icon-telemetry",
|
||||
cssClass: "icon-generator-telemetry",
|
||||
creatable: true,
|
||||
form: [
|
||||
{
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
openmct.install(openmct.plugins.Notebook());
|
||||
openmct.install(openmct.plugins.FolderView());
|
||||
openmct.install(openmct.plugins.Tabs());
|
||||
openmct.install(openmct.plugins.FlexibleLayout());
|
||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||
openmct.time.timeSystem('utc');
|
||||
openmct.start();
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
"printj": "^1.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"request": "^2.69.0",
|
||||
"screenfull": "^3.3.2",
|
||||
"split": "^1.0.0",
|
||||
"style-loader": "^0.21.0",
|
||||
"v8-compile-cache": "^1.1.0",
|
||||
|
||||
@@ -31,7 +31,6 @@ define([
|
||||
"./src/navigation/NavigateAction",
|
||||
"./src/navigation/OrphanNavigationHandler",
|
||||
"./src/windowing/NewTabAction",
|
||||
"./src/windowing/FullscreenAction",
|
||||
"./src/windowing/WindowTitler",
|
||||
"./res/templates/browse.html",
|
||||
"./res/templates/browse-object.html",
|
||||
@@ -53,7 +52,6 @@ define([
|
||||
NavigateAction,
|
||||
OrphanNavigationHandler,
|
||||
NewTabAction,
|
||||
FullscreenAction,
|
||||
WindowTitler,
|
||||
browseTemplate,
|
||||
browseObjectTemplate,
|
||||
@@ -225,13 +223,6 @@ define([
|
||||
"group": "windowing",
|
||||
"cssClass": "icon-new-window",
|
||||
"priority": "preferred"
|
||||
},
|
||||
{
|
||||
"key": "fullscreen",
|
||||
"implementation": FullscreenAction,
|
||||
"category": "view-control",
|
||||
"group": "windowing",
|
||||
"priority": "default"
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
@@ -265,18 +256,6 @@ define([
|
||||
key: "inspectorRegion",
|
||||
template: inspectorRegionTemplate
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"name": "screenfull.js",
|
||||
"version": "1.2.0",
|
||||
"description": "Wrapper for cross-browser usage of fullscreen API",
|
||||
"author": "Sindre Sorhus",
|
||||
"website": "https://github.com/sindresorhus/screenfull.js/",
|
||||
"copyright": "Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)",
|
||||
"license": "license-mit",
|
||||
"link": "https://github.com/sindresorhus/screenfull.js/blob/gh-pages/license"
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,64 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining FullscreenAction. Created by vwoeltje on 11/18/14.
|
||||
*/
|
||||
define(
|
||||
["screenfull"],
|
||||
function (screenfull) {
|
||||
|
||||
var ENTER_FULLSCREEN = "Enter full screen mode",
|
||||
EXIT_FULLSCREEN = "Exit full screen mode";
|
||||
|
||||
/**
|
||||
* The fullscreen action toggles between fullscreen display
|
||||
* and regular in-window display.
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
* @implements {Action}
|
||||
*/
|
||||
function FullscreenAction(context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
FullscreenAction.prototype.perform = function () {
|
||||
screenfull.toggle();
|
||||
};
|
||||
|
||||
FullscreenAction.prototype.getMetadata = function () {
|
||||
// We override getMetadata, because the icon cssClass and
|
||||
// description need to be determined at run-time
|
||||
// based on whether or not we are currently
|
||||
// full screen.
|
||||
var metadata = Object.create(FullscreenAction);
|
||||
metadata.cssClass = screenfull.isFullscreen ? "icon-fullscreen-expand" : "icon-fullscreen-collapse";
|
||||
metadata.description = screenfull.isFullscreen ?
|
||||
EXIT_FULLSCREEN : ENTER_FULLSCREEN;
|
||||
metadata.group = "windowing";
|
||||
metadata.context = this.context;
|
||||
return metadata;
|
||||
};
|
||||
|
||||
return FullscreenAction;
|
||||
}
|
||||
);
|
||||
@@ -1,59 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* MCTRepresentationSpec. Created by vwoeltje on 11/6/14.
|
||||
*/
|
||||
define(
|
||||
["../../src/windowing/FullscreenAction", "screenfull"],
|
||||
function (FullscreenAction, screenfull) {
|
||||
|
||||
describe("The fullscreen action", function () {
|
||||
var action,
|
||||
oldToggle;
|
||||
|
||||
beforeEach(function () {
|
||||
// Screenfull is not shimmed or injected, so
|
||||
// we need to spy on it in the global scope.
|
||||
oldToggle = screenfull.toggle;
|
||||
|
||||
screenfull.toggle = jasmine.createSpy("toggle");
|
||||
|
||||
action = new FullscreenAction({});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
screenfull.toggle = oldToggle;
|
||||
});
|
||||
|
||||
it("toggles fullscreen mode when performed", function () {
|
||||
action.perform();
|
||||
expect(screenfull.toggle).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("provides displayable metadata", function () {
|
||||
expect(action.getMetadata().cssClass).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -95,7 +95,10 @@ define(
|
||||
|
||||
// Create the overlay element and add it to the document's body
|
||||
element = this.$compile(TEMPLATE)(scope);
|
||||
this.findBody().prepend(element);
|
||||
|
||||
// Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when
|
||||
// multiple overlays with the same z-index are active.
|
||||
this.findBody().append(element);
|
||||
|
||||
return {
|
||||
dismiss: dismiss
|
||||
|
||||
@@ -34,9 +34,6 @@ define([
|
||||
"./src/actions/CancelAction",
|
||||
"./src/policies/EditActionPolicy",
|
||||
"./src/policies/EditPersistableObjectsPolicy",
|
||||
"./src/policies/EditableLinkPolicy",
|
||||
"./src/policies/EditableMovePolicy",
|
||||
"./src/policies/EditContextualActionPolicy",
|
||||
"./src/representers/EditRepresenter",
|
||||
"./src/capabilities/EditorCapability",
|
||||
"./src/capabilities/TransactionCapabilityDecorator",
|
||||
@@ -69,9 +66,6 @@ define([
|
||||
CancelAction,
|
||||
EditActionPolicy,
|
||||
EditPersistableObjectsPolicy,
|
||||
EditableLinkPolicy,
|
||||
EditableMovePolicy,
|
||||
EditContextualActionPolicy,
|
||||
EditRepresenter,
|
||||
EditorCapability,
|
||||
TransactionCapabilityDecorator,
|
||||
@@ -174,7 +168,7 @@ define([
|
||||
"name": "Remove",
|
||||
"description": "Remove this object from its containing object.",
|
||||
"depends": [
|
||||
"dialogService",
|
||||
"openmct",
|
||||
"navigationService"
|
||||
]
|
||||
},
|
||||
@@ -240,19 +234,6 @@ define([
|
||||
"implementation": EditPersistableObjectsPolicy,
|
||||
"depends": ["openmct"]
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": EditContextualActionPolicy,
|
||||
"depends": ["navigationService", "editModeBlacklist", "nonEditContextBlacklist"]
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": EditableMovePolicy
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": EditableLinkPolicy
|
||||
},
|
||||
{
|
||||
"implementation": CreationPolicy,
|
||||
"category": "creation"
|
||||
@@ -349,16 +330,6 @@ define([
|
||||
]
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "editModeBlacklist",
|
||||
"value": ["copy", "follow", "link", "locate"]
|
||||
},
|
||||
{
|
||||
"key": "nonEditContextBlacklist",
|
||||
"value": ["copy", "follow", "properties", "move", "link", "remove", "locate"]
|
||||
}
|
||||
],
|
||||
"capabilities": [
|
||||
{
|
||||
"key": "editor",
|
||||
|
||||
@@ -42,9 +42,9 @@ define([
|
||||
* @constructor
|
||||
* @implements {Action}
|
||||
*/
|
||||
function RemoveAction(dialogService, navigationService, context) {
|
||||
function RemoveAction(openmct, navigationService, context) {
|
||||
this.domainObject = (context || {}).domainObject;
|
||||
this.dialogService = dialogService;
|
||||
this.openmct = openmct;
|
||||
this.navigationService = navigationService;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ define([
|
||||
*/
|
||||
RemoveAction.prototype.perform = function () {
|
||||
var dialog,
|
||||
dialogService = this.dialogService,
|
||||
domainObject = this.domainObject,
|
||||
navigationService = this.navigationService;
|
||||
/*
|
||||
@@ -104,13 +103,13 @@ define([
|
||||
* capability. Based on object's location and selected object's location
|
||||
* user may be navigated to existing parent object
|
||||
*/
|
||||
function removeFromContext(object) {
|
||||
var contextCapability = object.getCapability('context'),
|
||||
function removeFromContext() {
|
||||
var contextCapability = domainObject.getCapability('context'),
|
||||
parent = contextCapability.getParent();
|
||||
|
||||
// If currently within path of removed object(s),
|
||||
// navigates to existing object up tree
|
||||
checkObjectNavigation(object, parent);
|
||||
checkObjectNavigation(domainObject, parent);
|
||||
|
||||
return parent.useCapability('mutation', doMutate);
|
||||
}
|
||||
@@ -119,7 +118,7 @@ define([
|
||||
* Pass in the function to remove the domain object so it can be
|
||||
* associated with an 'OK' button press
|
||||
*/
|
||||
dialog = new RemoveDialog(dialogService, domainObject, removeFromContext);
|
||||
dialog = new RemoveDialog(this.openmct, domainObject, removeFromContext);
|
||||
dialog.show();
|
||||
};
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ define([], function () {
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function RemoveDialog(dialogService, domainObject, removeCallback) {
|
||||
this.dialogService = dialogService;
|
||||
function RemoveDialog(openmct, domainObject, removeCallback) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.removeCallback = removeCallback;
|
||||
}
|
||||
@@ -46,31 +46,26 @@ define([], function () {
|
||||
* Display a dialog to confirm the removal of a domain object.
|
||||
*/
|
||||
RemoveDialog.prototype.show = function () {
|
||||
var dialog,
|
||||
domainObject = this.domainObject,
|
||||
removeCallback = this.removeCallback,
|
||||
model = {
|
||||
title: 'Remove ' + domainObject.getModel().name,
|
||||
actionText: 'Warning! This action will permanently remove this object. Are you sure you want to continue?',
|
||||
severity: 'alert',
|
||||
primaryOption: {
|
||||
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: function () {
|
||||
removeCallback(domainObject);
|
||||
callback: () => {
|
||||
this.removeCallback();
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
dialog.dismiss();
|
||||
}
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
dialog = this.dialogService.showBlockingMessage(model);
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
return RemoveDialog;
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Policy controlling whether the context menu is visible when
|
||||
* objects are being edited
|
||||
* @param navigationService
|
||||
* @param editModeBlacklist A blacklist of actions disallowed from
|
||||
* context menu when navigated object is being edited
|
||||
* @param nonEditContextBlacklist A blacklist of actions disallowed
|
||||
* from context menu of non-editable objects, when navigated object
|
||||
* is being edited
|
||||
* @constructor
|
||||
* @param editModeBlacklist A blacklist of actions disallowed from
|
||||
* context menu when navigated object is being edited
|
||||
* @param nonEditContextBlacklist A blacklist of actions disallowed
|
||||
* from context menu of non-editable objects, when navigated object
|
||||
* @implements {Policy.<Action, ActionContext>}
|
||||
*/
|
||||
function EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist) {
|
||||
this.navigationService = navigationService;
|
||||
|
||||
//The list of objects disallowed on target object when in edit mode
|
||||
this.editModeBlacklist = editModeBlacklist;
|
||||
//The list of objects disallowed on target object that is not in
|
||||
// edit mode (ie. the context menu in the tree on the LHS).
|
||||
this.nonEditContextBlacklist = nonEditContextBlacklist;
|
||||
}
|
||||
|
||||
EditContextualActionPolicy.prototype.allow = function (action, context) {
|
||||
var selectedObject = context.domainObject,
|
||||
navigatedObject = this.navigationService.getNavigation(),
|
||||
actionMetadata = action.getMetadata ? action.getMetadata() : {};
|
||||
|
||||
// if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
|
||||
if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()) {
|
||||
return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
|
||||
} else {
|
||||
//Target is in the context menu
|
||||
return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1;
|
||||
}
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
};
|
||||
|
||||
return EditContextualActionPolicy;
|
||||
}
|
||||
);
|
||||
@@ -1,51 +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([], function () {
|
||||
|
||||
/**
|
||||
* Policy suppressing links when the linked-to domain object is in
|
||||
* edit mode. Domain objects being edited may not have been persisted,
|
||||
* so creating links to these can result in inconsistent state.
|
||||
*
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Policy.<View, DomainObject>}
|
||||
*/
|
||||
function EditableLinkPolicy() {
|
||||
}
|
||||
|
||||
EditableLinkPolicy.prototype.allow = function (action, context) {
|
||||
var key = action.getMetadata().key,
|
||||
object;
|
||||
|
||||
if (key === 'link') {
|
||||
object = context.selectedObject || context.domainObject;
|
||||
return !(object.hasCapability("editor") && object.getCapability("editor").inEditContext());
|
||||
}
|
||||
|
||||
// Like all policies, allow by default.
|
||||
return true;
|
||||
};
|
||||
|
||||
return EditableLinkPolicy;
|
||||
});
|
||||
@@ -1,52 +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([], function () {
|
||||
|
||||
/**
|
||||
* Policy suppressing move actions among editable and non-editable
|
||||
* domain objects.
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Policy.<View, DomainObject>}
|
||||
*/
|
||||
function EditableMovePolicy() {
|
||||
}
|
||||
|
||||
EditableMovePolicy.prototype.allow = function (action, context) {
|
||||
var domainObject = context.domainObject,
|
||||
selectedObject = context.selectedObject,
|
||||
key = action.getMetadata().key,
|
||||
isDomainObjectEditing = domainObject.hasCapability('editor') &&
|
||||
domainObject.getCapability('editor').inEditContext();
|
||||
|
||||
if (key === 'move' && isDomainObjectEditing) {
|
||||
return !!selectedObject && selectedObject.hasCapability('editor') &&
|
||||
selectedObject.getCapability('editor').inEditContext();
|
||||
}
|
||||
|
||||
// Like all policies, allow by default.
|
||||
return true;
|
||||
};
|
||||
|
||||
return EditableMovePolicy;
|
||||
});
|
||||
@@ -29,7 +29,7 @@ define(
|
||||
actionContext,
|
||||
capabilities,
|
||||
mockContext,
|
||||
mockDialogService,
|
||||
mockOverlayAPI,
|
||||
mockDomainObject,
|
||||
mockMutation,
|
||||
mockNavigationService,
|
||||
@@ -68,9 +68,9 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
mockDialogService = jasmine.createSpyObj(
|
||||
"dialogService",
|
||||
["showBlockingMessage"]
|
||||
mockOverlayAPI = jasmine.createSpyObj(
|
||||
"overlayAPI",
|
||||
["dialog"]
|
||||
);
|
||||
|
||||
mockNavigationService = jasmine.createSpyObj(
|
||||
@@ -96,7 +96,7 @@ define(
|
||||
|
||||
actionContext = { domainObject: mockDomainObject };
|
||||
|
||||
action = new RemoveAction(mockDialogService, mockNavigationService, actionContext);
|
||||
action = new RemoveAction({overlays: mockOverlayAPI}, mockNavigationService, actionContext);
|
||||
});
|
||||
|
||||
it("only applies to objects with parents", function () {
|
||||
@@ -118,7 +118,7 @@ define(
|
||||
|
||||
action.perform();
|
||||
|
||||
expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
|
||||
expect(mockOverlayAPI.dialog).toHaveBeenCalled();
|
||||
|
||||
// Also check that no mutation happens at this point
|
||||
expect(mockParent.useCapability).not.toHaveBeenCalledWith("mutation", jasmine.any(Function));
|
||||
@@ -158,13 +158,13 @@ define(
|
||||
mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]);
|
||||
mockRootContext = jasmine.createSpyObj("context", ["getParent"]);
|
||||
|
||||
mockDialogService.showBlockingMessage.and.returnValue(mockDialogHandle);
|
||||
mockOverlayAPI.dialog.and.returnValue(mockDialogHandle);
|
||||
});
|
||||
|
||||
it("mutates the parent when performed", function () {
|
||||
action.perform();
|
||||
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||
.primaryOption.callback();
|
||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
||||
.buttons[0].callback();
|
||||
|
||||
expect(mockMutation.invoke)
|
||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||
@@ -174,8 +174,8 @@ define(
|
||||
var mutator, result;
|
||||
|
||||
action.perform();
|
||||
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||
.primaryOption.callback();
|
||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
||||
.buttons[0].callback();
|
||||
|
||||
mutator = mockMutation.invoke.calls.mostRecent().args[0];
|
||||
result = mutator(model);
|
||||
@@ -212,8 +212,8 @@ define(
|
||||
mockType.hasFeature.and.returnValue(true);
|
||||
|
||||
action.perform();
|
||||
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||
.primaryOption.callback();
|
||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
||||
.buttons[0].callback();
|
||||
|
||||
// Expects navigation to parent of domainObject (removed object)
|
||||
expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(mockParent);
|
||||
@@ -242,8 +242,8 @@ define(
|
||||
mockType.hasFeature.and.returnValue(true);
|
||||
|
||||
action.perform();
|
||||
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||
.primaryOption.callback();
|
||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
||||
.buttons[0].callback();
|
||||
|
||||
// Expects no navigation to occur
|
||||
expect(mockNavigationService.setNavigation).not.toHaveBeenCalled();
|
||||
|
||||
@@ -1,120 +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.
|
||||
*****************************************************************************/
|
||||
/*global describe,it,expect,beforeEach,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/policies/EditContextualActionPolicy"],
|
||||
function (EditContextualActionPolicy) {
|
||||
|
||||
describe("The Edit contextual action policy", function () {
|
||||
var policy,
|
||||
navigationService,
|
||||
mockAction,
|
||||
context,
|
||||
navigatedObject,
|
||||
mockDomainObject,
|
||||
mockEditorCapability,
|
||||
metadata,
|
||||
editModeBlacklist = ["copy", "follow", "window", "link", "locate"],
|
||||
nonEditContextBlacklist = ["copy", "follow", "properties", "move", "link", "remove", "locate"];
|
||||
|
||||
beforeEach(function () {
|
||||
mockEditorCapability = jasmine.createSpyObj("editorCapability", ["isEditContextRoot", "inEditContext"]);
|
||||
|
||||
navigatedObject = jasmine.createSpyObj("navigatedObject", ["hasCapability", "getCapability"]);
|
||||
navigatedObject.getCapability.and.returnValue(mockEditorCapability);
|
||||
navigatedObject.hasCapability.and.returnValue(false);
|
||||
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj("domainObject", ["hasCapability", "getCapability"]);
|
||||
mockDomainObject.hasCapability.and.returnValue(false);
|
||||
mockDomainObject.getCapability.and.returnValue(mockEditorCapability);
|
||||
|
||||
navigationService = jasmine.createSpyObj("navigationService", ["getNavigation"]);
|
||||
navigationService.getNavigation.and.returnValue(navigatedObject);
|
||||
|
||||
metadata = {key: "move"};
|
||||
mockAction = jasmine.createSpyObj("action", ["getMetadata"]);
|
||||
mockAction.getMetadata.and.returnValue(metadata);
|
||||
|
||||
context = {domainObject: mockDomainObject};
|
||||
|
||||
policy = new EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist);
|
||||
});
|
||||
|
||||
it('Allows all actions when navigated object not in edit mode', function () {
|
||||
expect(policy.allow(mockAction, context)).toBe(true);
|
||||
});
|
||||
|
||||
it('Allows "window" action when navigated object in edit mode,' +
|
||||
' but selected object not in edit mode ', function () {
|
||||
navigatedObject.hasCapability.and.returnValue(true);
|
||||
mockEditorCapability.isEditContextRoot.and.returnValue(true);
|
||||
metadata.key = "window";
|
||||
expect(policy.allow(mockAction, context)).toBe(true);
|
||||
});
|
||||
|
||||
it('Allows "remove" action when navigated object in edit mode,' +
|
||||
' and selected object not editable, but its parent is.',
|
||||
function () {
|
||||
var mockParent = jasmine.createSpyObj("parentObject", ["hasCapability"]),
|
||||
mockContextCapability = jasmine.createSpyObj("contextCapability", ["getParent"]);
|
||||
|
||||
mockParent.hasCapability.and.returnValue(true);
|
||||
mockContextCapability.getParent.and.returnValue(mockParent);
|
||||
navigatedObject.hasCapability.and.returnValue(true);
|
||||
|
||||
mockDomainObject.getCapability.and.returnValue(mockContextCapability);
|
||||
mockDomainObject.hasCapability.and.callFake(function (capability) {
|
||||
switch (capability) {
|
||||
case "editor": return false;
|
||||
case "context": return true;
|
||||
}
|
||||
});
|
||||
metadata.key = "remove";
|
||||
|
||||
expect(policy.allow(mockAction, context)).toBe(true);
|
||||
});
|
||||
|
||||
it('Disallows "move" action when navigated object in edit mode,' +
|
||||
' but selected object not in edit mode ', function () {
|
||||
navigatedObject.hasCapability.and.returnValue(true);
|
||||
mockEditorCapability.isEditContextRoot.and.returnValue(true);
|
||||
mockEditorCapability.inEditContext.and.returnValue(false);
|
||||
metadata.key = "move";
|
||||
expect(policy.allow(mockAction, context)).toBe(false);
|
||||
});
|
||||
|
||||
it('Disallows copy action when navigated object and' +
|
||||
' selected object in edit mode', function () {
|
||||
navigatedObject.hasCapability.and.returnValue(true);
|
||||
mockDomainObject.hasCapability.and.returnValue(true);
|
||||
mockEditorCapability.isEditContextRoot.and.returnValue(true);
|
||||
mockEditorCapability.inEditContext.and.returnValue(true);
|
||||
|
||||
metadata.key = "copy";
|
||||
expect(policy.allow(mockAction, context)).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -31,7 +31,6 @@ define([
|
||||
"./src/controllers/TreeNodeController",
|
||||
"./src/controllers/ActionGroupController",
|
||||
"./src/controllers/ToggleController",
|
||||
"./src/controllers/ContextMenuController",
|
||||
"./src/controllers/ClickAwayController",
|
||||
"./src/controllers/ViewSwitcherController",
|
||||
"./src/controllers/GetterSetterController",
|
||||
@@ -49,8 +48,6 @@ define([
|
||||
"./src/directives/MCTSplitter",
|
||||
"./src/directives/MCTTree",
|
||||
"./src/directives/MCTIndicators",
|
||||
"./src/directives/MCTPreview",
|
||||
"./src/actions/MCTPreviewAction",
|
||||
"./src/filters/ReverseFilter",
|
||||
"./res/templates/bottombar.html",
|
||||
"./res/templates/controls/action-button.html",
|
||||
@@ -65,13 +62,11 @@ define([
|
||||
"./res/templates/tree-node.html",
|
||||
"./res/templates/label.html",
|
||||
"./res/templates/controls/action-group.html",
|
||||
"./res/templates/menu/context-menu.html",
|
||||
"./res/templates/controls/switcher.html",
|
||||
"./res/templates/object-inspector.html",
|
||||
"./res/templates/controls/selector.html",
|
||||
"./res/templates/controls/datetime-picker.html",
|
||||
"./res/templates/controls/datetime-field.html",
|
||||
"./res/templates/preview.html",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
UrlService,
|
||||
@@ -84,7 +79,6 @@ define([
|
||||
TreeNodeController,
|
||||
ActionGroupController,
|
||||
ToggleController,
|
||||
ContextMenuController,
|
||||
ClickAwayController,
|
||||
ViewSwitcherController,
|
||||
GetterSetterController,
|
||||
@@ -102,8 +96,6 @@ define([
|
||||
MCTSplitter,
|
||||
MCTTree,
|
||||
MCTIndicators,
|
||||
MCTPreview,
|
||||
MCTPreviewAction,
|
||||
ReverseFilter,
|
||||
bottombarTemplate,
|
||||
actionButtonTemplate,
|
||||
@@ -118,13 +110,11 @@ define([
|
||||
treeNodeTemplate,
|
||||
labelTemplate,
|
||||
actionGroupTemplate,
|
||||
contextMenuTemplate,
|
||||
switcherTemplate,
|
||||
objectInspectorTemplate,
|
||||
selectorTemplate,
|
||||
datetimePickerTemplate,
|
||||
datetimeFieldTemplate,
|
||||
previewTemplate,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
@@ -252,13 +242,6 @@ define([
|
||||
"key": "ToggleController",
|
||||
"implementation": ToggleController
|
||||
},
|
||||
{
|
||||
"key": "ContextMenuController",
|
||||
"implementation": ContextMenuController,
|
||||
"depends": [
|
||||
"$scope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "ClickAwayController",
|
||||
"implementation": ClickAwayController,
|
||||
@@ -394,31 +377,6 @@ define([
|
||||
"key": "mctIndicators",
|
||||
"implementation": MCTIndicators,
|
||||
"depends": ['openmct']
|
||||
},
|
||||
{
|
||||
"key": "mctPreview",
|
||||
"implementation": MCTPreview,
|
||||
"depends": [
|
||||
"$document"
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"key": "mct-preview-action",
|
||||
"implementation": MCTPreviewAction,
|
||||
"name": "Preview",
|
||||
"cssClass": "hide-in-t-main-view icon-eye-open",
|
||||
"description": "Preview in large dialog",
|
||||
"category": [
|
||||
"contextual",
|
||||
"view-control"
|
||||
],
|
||||
"depends": [
|
||||
"$compile",
|
||||
"$rootScope"
|
||||
],
|
||||
"priority": "preferred"
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
@@ -517,13 +475,6 @@ define([
|
||||
"action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "context-menu",
|
||||
"template": contextMenuTemplate,
|
||||
"uses": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "switcher",
|
||||
"template": switcherTemplate,
|
||||
@@ -534,10 +485,6 @@ define([
|
||||
{
|
||||
"key": "object-inspector",
|
||||
"template": objectInspectorTemplate
|
||||
},
|
||||
{
|
||||
"key": "mct-preview",
|
||||
"template": previewTemplate
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<div class="t-frame-inner abs t-object-type-{{ domainObject.getModel().type }}" mct-preview>
|
||||
<div class="abs object-browse-bar l-flex-row">
|
||||
<div class="left flex-elem l-flex-row grows">
|
||||
<mct-representation
|
||||
key="'object-header-frame'"
|
||||
mct-object="domainObject"
|
||||
class="l-flex-row flex-elem object-header grows">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||
<mct-representation
|
||||
key="'switcher'"
|
||||
ng-model="representation"
|
||||
mct-object="domainObject">
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
<div class="abs object-holder">
|
||||
<mct-representation
|
||||
key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject">
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,55 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
|
||||
var PREVIEW_TEMPLATE = '<mct-representation key="\'mct-preview\'"' +
|
||||
'class="t-rep-frame holder"' +
|
||||
'mct-object="domainObject">' +
|
||||
'</mct-representation>';
|
||||
|
||||
function MCTPreviewAction($compile, $rootScope, context) {
|
||||
context = context || {};
|
||||
this.domainObject = context.selectedObject || context.domainObject;
|
||||
this.$rootScope = $rootScope;
|
||||
this.$compile = $compile;
|
||||
}
|
||||
|
||||
MCTPreviewAction.prototype.perform = function () {
|
||||
var newScope = this.$rootScope.$new();
|
||||
newScope.domainObject = this.domainObject;
|
||||
|
||||
this.$compile(PREVIEW_TEMPLATE)(newScope);
|
||||
};
|
||||
|
||||
MCTPreviewAction.appliesTo = function (context) {
|
||||
var domainObject = (context || {}).domainObject,
|
||||
status = domainObject.getCapability('status');
|
||||
|
||||
return !(status && status.get('editing'));
|
||||
};
|
||||
|
||||
return MCTPreviewAction;
|
||||
}
|
||||
);
|
||||
@@ -1,64 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['zepto', '../services/Overlay'], function ($, Overlay) {
|
||||
function MCTPreview($document) {
|
||||
|
||||
function link($scope, $element) {
|
||||
var actions = $scope.domainObject.getCapability('action'),
|
||||
notebookAction = actions.getActions({key: 'notebook-new-entry'})[0];
|
||||
|
||||
var notebookButton = notebookAction ?
|
||||
[
|
||||
{
|
||||
class: 'icon-notebook new-notebook-entry',
|
||||
title: 'New Notebook Entry',
|
||||
clickHandler: function (event) {
|
||||
event.stopPropagation();
|
||||
notebookAction.perform();
|
||||
}
|
||||
}
|
||||
] : [];
|
||||
|
||||
var overlayService = new Overlay({
|
||||
$document: $document,
|
||||
$element: $element[0],
|
||||
$scope: $scope,
|
||||
browseBarButtons: notebookButton
|
||||
});
|
||||
|
||||
overlayService.toggleOverlay();
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
$element.remove();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: link
|
||||
};
|
||||
}
|
||||
|
||||
return MCTPreview;
|
||||
|
||||
});
|
||||
@@ -82,7 +82,7 @@ define(
|
||||
}
|
||||
var searchPath = "?" + arr.join('&'),
|
||||
newTabPath =
|
||||
"index.html#" + this.urlForLocation(mode, domainObject) +
|
||||
"#" + this.urlForLocation(mode, domainObject) +
|
||||
searchPath;
|
||||
return newTabPath;
|
||||
};
|
||||
|
||||
@@ -1,60 +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(
|
||||
["../../src/controllers/ContextMenuController"],
|
||||
function (ContextMenuController) {
|
||||
|
||||
describe("The context menu controller", function () {
|
||||
var mockScope,
|
||||
mockActions,
|
||||
controller;
|
||||
|
||||
beforeEach(function () {
|
||||
mockActions = jasmine.createSpyObj("action", ["getActions"]);
|
||||
mockScope = jasmine.createSpyObj("$scope", ["$watch"]);
|
||||
controller = new ContextMenuController(mockScope);
|
||||
});
|
||||
|
||||
it("watches scope that may change applicable actions", function () {
|
||||
// The action capability
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"action",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("populates the scope with grouped and ungrouped actions", function () {
|
||||
mockScope.action = mockActions;
|
||||
mockScope.parameters = { category: "test" };
|
||||
|
||||
mockActions.getActions.and.returnValue(["a", "b", "c"]);
|
||||
|
||||
// Call the watch
|
||||
mockScope.$watch.calls.mostRecent().args[1]();
|
||||
|
||||
// Should have grouped and ungrouped actions in scope now
|
||||
expect(mockScope.menuActions.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -32,7 +32,7 @@ define([
|
||||
{
|
||||
key: "exportService",
|
||||
implementation: function () {
|
||||
return new ExportService(saveAs);
|
||||
return new ExportService(saveAs.saveAs);
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -104,9 +104,9 @@ define([
|
||||
}
|
||||
|
||||
return (openmct.editor.isEditing() &&
|
||||
(selection[0] && selection[0].context.elementProxy &&
|
||||
selection[1] && selection[1].context.item.type === 'telemetry.fixed' ||
|
||||
selection[0] && selection[0].context.item.type === 'telemetry.fixed'));
|
||||
selection[0] && selection[0].context.elementProxy &&
|
||||
((selection[1] && selection[1].context.item.type === 'telemetry.fixed') ||
|
||||
(selection[0] && selection[0].context.item && selection[0].context.item.type === 'telemetry.fixed')));
|
||||
},
|
||||
toolbar: function (selection) {
|
||||
var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"];
|
||||
|
||||
@@ -30,6 +30,7 @@ define([
|
||||
"./src/controllers/CompositeController",
|
||||
"./src/controllers/ColorController",
|
||||
"./src/controllers/DialogButtonController",
|
||||
"./src/controllers/SnapshotPreviewController",
|
||||
"./res/templates/controls/autocomplete.html",
|
||||
"./res/templates/controls/checkbox.html",
|
||||
"./res/templates/controls/datetime.html",
|
||||
@@ -44,6 +45,7 @@ define([
|
||||
"./res/templates/controls/dialog.html",
|
||||
"./res/templates/controls/radio.html",
|
||||
"./res/templates/controls/file-input.html",
|
||||
"./res/templates/controls/snap-view.html",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
MCTForm,
|
||||
@@ -55,6 +57,7 @@ define([
|
||||
CompositeController,
|
||||
ColorController,
|
||||
DialogButtonController,
|
||||
SnapshotPreviewController,
|
||||
autocompleteTemplate,
|
||||
checkboxTemplate,
|
||||
datetimeTemplate,
|
||||
@@ -69,6 +72,7 @@ define([
|
||||
dialogTemplate,
|
||||
radioTemplate,
|
||||
fileInputTemplate,
|
||||
snapViewTemplate,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
@@ -153,6 +157,10 @@ define([
|
||||
{
|
||||
"key": "file-input",
|
||||
"template": fileInputTemplate
|
||||
},
|
||||
{
|
||||
"key": "snap-view",
|
||||
"template": snapViewTemplate
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
@@ -186,6 +194,14 @@ define([
|
||||
"$scope",
|
||||
"dialogService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "SnapshotPreviewController",
|
||||
"implementation": SnapshotPreviewController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
|
||||
@@ -19,15 +19,18 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="menu-element context-menu-wrapper mobile-disable-select" ng-controller="ContextMenuController">
|
||||
<div class="menu context-menu">
|
||||
<ul>
|
||||
<li ng-repeat="menuAction in menuActions"
|
||||
ng-click="menuAction.perform()"
|
||||
title="{{menuAction.getMetadata().description}}"
|
||||
class="{{menuAction.getMetadata().cssClass}}">
|
||||
{{menuAction.getMetadata().name}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<span ng-controller="SnapshotPreviewController"
|
||||
class='form-control shell'>
|
||||
<span class='field control {{structure.cssClass}}'>
|
||||
<image
|
||||
class="c-ne__embed__snap-thumb"
|
||||
src="{{imageUrl || structure.src}}"
|
||||
ng-click="previewImage(imageUrl || structure.src)"
|
||||
name="mctControl">
|
||||
</image>
|
||||
<br>
|
||||
<a title="Annotate" class="s-button icon-pencil" ng-click="annotateImage(ngModel, field, imageUrl || structure.src)">
|
||||
<span class="title-label">Annotate</span>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
120
platform/forms/src/controllers/SnapshotPreviewController.js
Normal file
120
platform/forms/src/controllers/SnapshotPreviewController.js
Normal file
@@ -0,0 +1,120 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[
|
||||
'painterro'
|
||||
],
|
||||
function (Painterro) {
|
||||
|
||||
function SnapshotPreviewController($scope, openmct) {
|
||||
|
||||
$scope.previewImage = function (imageUrl) {
|
||||
var image = document.createElement('img');
|
||||
image.src = imageUrl;
|
||||
|
||||
openmct.overlays.overlay(
|
||||
{
|
||||
element: image,
|
||||
size: 'large'
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.annotateImage = function (ngModel, field, imageUrl) {
|
||||
$scope.imageUrl = imageUrl;
|
||||
|
||||
var div = document.createElement('div'),
|
||||
painterroInstance = {},
|
||||
save = false;
|
||||
|
||||
div.id = 'snap-annotation';
|
||||
|
||||
openmct.overlays.overlay(
|
||||
{
|
||||
element: div,
|
||||
size: 'large',
|
||||
buttons: [
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
save = false;
|
||||
painterroInstance.save();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Save',
|
||||
callback: function () {
|
||||
save = true;
|
||||
painterroInstance.save();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
painterroInstance = Painterro({
|
||||
id: 'snap-annotation',
|
||||
activeColor: '#ff0000',
|
||||
activeColorAlpha: 1.0,
|
||||
activeFillColor: '#fff',
|
||||
activeFillColorAlpha: 0.0,
|
||||
backgroundFillColor: '#000',
|
||||
backgroundFillColorAlpha: 0.0,
|
||||
defaultFontSize: 16,
|
||||
defaultLineWidth: 2,
|
||||
defaultTool: 'ellipse',
|
||||
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
|
||||
translation: {
|
||||
name: 'en',
|
||||
strings: {
|
||||
lineColor: 'Line',
|
||||
fillColor: 'Fill',
|
||||
lineWidth: 'Size',
|
||||
textColor: 'Color',
|
||||
fontSize: 'Size',
|
||||
fontStyle: 'Style'
|
||||
}
|
||||
},
|
||||
saveHandler: function (image, done) {
|
||||
if (save) {
|
||||
var url = image.asBlob(),
|
||||
reader = new window.FileReader();
|
||||
|
||||
reader.readAsDataURL(url);
|
||||
reader.onloadend = function () {
|
||||
$scope.imageUrl = reader.result;
|
||||
ngModel[field] = reader.result;
|
||||
};
|
||||
} else {
|
||||
ngModel.field = imageUrl;
|
||||
console.warn('You cancelled the annotation!!!');
|
||||
}
|
||||
done(true);
|
||||
}
|
||||
}).show(imageUrl);
|
||||
};
|
||||
}
|
||||
|
||||
return SnapshotPreviewController;
|
||||
}
|
||||
);
|
||||
@@ -25,12 +25,10 @@ define([
|
||||
"./src/MCTRepresentation",
|
||||
"./src/gestures/DragGesture",
|
||||
"./src/gestures/DropGesture",
|
||||
"./src/gestures/ContextMenuGesture",
|
||||
"./src/gestures/GestureProvider",
|
||||
"./src/gestures/GestureRepresenter",
|
||||
"./src/services/DndService",
|
||||
"./src/TemplateLinker",
|
||||
"./src/actions/ContextMenuAction",
|
||||
"./src/TemplatePrefetcher",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
@@ -38,12 +36,10 @@ define([
|
||||
MCTRepresentation,
|
||||
DragGesture,
|
||||
DropGesture,
|
||||
ContextMenuGesture,
|
||||
GestureProvider,
|
||||
GestureRepresenter,
|
||||
DndService,
|
||||
TemplateLinker,
|
||||
ContextMenuAction,
|
||||
TemplatePrefetcher,
|
||||
legacyRegistry
|
||||
) {
|
||||
@@ -88,14 +84,6 @@ define([
|
||||
"dndService",
|
||||
"$q"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "menu",
|
||||
"implementation": ContextMenuGesture,
|
||||
"depends": [
|
||||
"$timeout",
|
||||
"agentService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
@@ -136,19 +124,6 @@ define([
|
||||
"comment": "For internal use by mct-include and mct-representation."
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"key": "menu",
|
||||
"implementation": ContextMenuAction,
|
||||
"depends": [
|
||||
"$compile",
|
||||
"$document",
|
||||
"$rootScope",
|
||||
"popupService",
|
||||
"agentService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
{
|
||||
"priority": "mandatory",
|
||||
|
||||
@@ -1,138 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining ContextMenuAction. Created by shale on 06/30/2015.
|
||||
*/
|
||||
define(
|
||||
["../gestures/GestureConstants"],
|
||||
function (GestureConstants) {
|
||||
|
||||
var MENU_TEMPLATE = "<mct-representation key=\"'context-menu'\" " +
|
||||
"mct-object=\"domainObject\" " +
|
||||
"ng-class=\"menuClass\" " +
|
||||
"ng-style=\"menuStyle\">" +
|
||||
"</mct-representation>",
|
||||
dismissExistingMenu;
|
||||
|
||||
/**
|
||||
* Launches a custom context menu for the domain object it contains.
|
||||
*
|
||||
* @memberof platform/representation
|
||||
* @constructor
|
||||
* @param $compile Angular's $compile service
|
||||
* @param $document the current document
|
||||
* @param $rootScope Angular's root scope
|
||||
* @param {platform/commonUI/general.PopupService} popupService
|
||||
* @param actionContext the context in which the action
|
||||
* should be performed
|
||||
* @implements {Action}
|
||||
*/
|
||||
function ContextMenuAction(
|
||||
$compile,
|
||||
$document,
|
||||
$rootScope,
|
||||
popupService,
|
||||
agentService,
|
||||
actionContext
|
||||
) {
|
||||
this.$compile = $compile;
|
||||
this.agentService = agentService;
|
||||
this.actionContext = actionContext;
|
||||
this.popupService = popupService;
|
||||
this.getDocument = function () {
|
||||
return $document;
|
||||
};
|
||||
this.getRootScope = function () {
|
||||
return $rootScope;
|
||||
};
|
||||
}
|
||||
|
||||
ContextMenuAction.prototype.perform = function () {
|
||||
var $compile = this.$compile,
|
||||
$document = this.getDocument(),
|
||||
$rootScope = this.getRootScope(),
|
||||
actionContext = this.actionContext,
|
||||
eventCoords = [
|
||||
actionContext.event.pageX,
|
||||
actionContext.event.pageY
|
||||
],
|
||||
menuDim = GestureConstants.MCT_MENU_DIMENSIONS,
|
||||
body = $document.find('body'),
|
||||
scope = $rootScope.$new(),
|
||||
initiatingEvent = this.agentService.isMobile() ?
|
||||
'touchstart' : 'mousedown',
|
||||
menu,
|
||||
popup;
|
||||
|
||||
// Remove the context menu
|
||||
function dismiss() {
|
||||
if (popup) {
|
||||
popup.dismiss();
|
||||
popup = undefined;
|
||||
}
|
||||
scope.$destroy();
|
||||
body.off("mousedown", dismiss);
|
||||
dismissExistingMenu = undefined;
|
||||
}
|
||||
|
||||
// Dismiss any menu which was already showing
|
||||
if (dismissExistingMenu) {
|
||||
dismissExistingMenu();
|
||||
}
|
||||
|
||||
// ...and record the presence of this menu.
|
||||
dismissExistingMenu = dismiss;
|
||||
|
||||
// Set up the scope, including menu positioning
|
||||
scope.domainObject = actionContext.domainObject;
|
||||
scope.menuClass = { "context-menu-holder": true };
|
||||
// Create the context menu
|
||||
menu = $compile(MENU_TEMPLATE)(scope);
|
||||
|
||||
popup = this.popupService.display(menu, eventCoords, {
|
||||
marginX: -menuDim[0],
|
||||
marginY: -menuDim[1]
|
||||
});
|
||||
|
||||
scope.menuClass['go-left'] = popup.goesLeft();
|
||||
scope.menuClass['go-up'] = popup.goesUp();
|
||||
|
||||
// Stop propagation so that clicks or touches on the menu do not close the menu
|
||||
menu.on(initiatingEvent, function (event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
// Dismiss the menu when body is clicked/touched elsewhere
|
||||
// ('mousedown' because 'click' breaks left-click context menus)
|
||||
// ('touchstart' because 'touch' breaks context menus up)
|
||||
body.on(initiatingEvent, dismiss);
|
||||
// NOTE: Apply to mobile?
|
||||
menu.on('click', dismiss);
|
||||
|
||||
// Don't launch browser's context menu
|
||||
actionContext.event.preventDefault();
|
||||
};
|
||||
|
||||
return ContextMenuAction;
|
||||
}
|
||||
);
|
||||
@@ -1,100 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining ContextMenuGesture.
|
||||
* Created by vwoeltje on 11/17/14. Modified by shale on 06/30/2015.
|
||||
*/
|
||||
define(
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Add listeners to a representation such that it calls the
|
||||
* context menu action for the domain object it contains.
|
||||
*
|
||||
* @memberof platform/representation
|
||||
* @constructor
|
||||
* @param element the jqLite-wrapped element which should exhibit
|
||||
* the context menu
|
||||
* @param {DomainObject} domainObject the object on which actions
|
||||
* in the context menu will be performed
|
||||
* @implements {Gesture}
|
||||
*/
|
||||
function ContextMenuGesture($timeout, agentService, element, domainObject) {
|
||||
var isPressing,
|
||||
isDragging,
|
||||
longTouchTime = 500;
|
||||
|
||||
function showMenu(event) {
|
||||
domainObject.getCapability('action').perform({
|
||||
key: 'menu',
|
||||
domainObject: domainObject,
|
||||
event: event
|
||||
});
|
||||
}
|
||||
|
||||
// When context menu event occurs, show object actions instead
|
||||
if (!agentService.isMobile()) {
|
||||
|
||||
// When context menu event occurs, show object actions instead
|
||||
element.on('contextmenu', showMenu);
|
||||
} else if (agentService.isMobile()) {
|
||||
|
||||
// If on mobile device, then start timeout for the single touch event
|
||||
// during the timeout 'isPressing' is true.
|
||||
element.on('touchstart', function (event) {
|
||||
if (event.touches.length < 2) {
|
||||
isPressing = true;
|
||||
|
||||
// After the timeout, if 'isPressing' is
|
||||
// true, display context menu for object
|
||||
$timeout(function () {
|
||||
if (isPressing && !isDragging) {
|
||||
showMenu(event);
|
||||
}
|
||||
}, longTouchTime);
|
||||
}
|
||||
});
|
||||
|
||||
// If on Mobile Device, and user scrolls/drags set flag to true
|
||||
element.on('touchmove', function () {
|
||||
isDragging = true;
|
||||
});
|
||||
|
||||
// Whenever the touch event ends, 'isPressing' & 'isDragging' is false.
|
||||
element.on('touchend', function () {
|
||||
isPressing = false;
|
||||
isDragging = false;
|
||||
});
|
||||
}
|
||||
|
||||
this.showMenuCallback = showMenu;
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
ContextMenuGesture.prototype.destroy = function () {
|
||||
this.element.off('contextmenu', this.showMenu);
|
||||
};
|
||||
|
||||
return ContextMenuGesture;
|
||||
}
|
||||
);
|
||||
@@ -1,202 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* Module defining ContextMenuActionSpec. Created by shale on 07/02/2015.
|
||||
*/
|
||||
define(
|
||||
["../../src/actions/ContextMenuAction"],
|
||||
function (ContextMenuAction) {
|
||||
|
||||
var JQLITE_FUNCTIONS = ["on", "off", "find", "append", "remove"],
|
||||
DOMAIN_OBJECT_METHODS = ["getId", "getModel", "getCapability", "hasCapability", "useCapability"];
|
||||
|
||||
|
||||
describe("The 'context menu' action", function () {
|
||||
var mockCompile,
|
||||
mockCompiledTemplate,
|
||||
mockMenu,
|
||||
mockDocument,
|
||||
mockBody,
|
||||
mockPopupService,
|
||||
mockRootScope,
|
||||
mockAgentService,
|
||||
mockScope,
|
||||
mockDomainObject,
|
||||
mockEvent,
|
||||
mockPopup,
|
||||
mockActionContext,
|
||||
action;
|
||||
|
||||
beforeEach(function () {
|
||||
mockCompile = jasmine.createSpy("$compile");
|
||||
mockCompiledTemplate = jasmine.createSpy("template");
|
||||
mockMenu = jasmine.createSpyObj("menu", JQLITE_FUNCTIONS);
|
||||
mockDocument = jasmine.createSpyObj("$document", JQLITE_FUNCTIONS);
|
||||
mockBody = jasmine.createSpyObj("body", JQLITE_FUNCTIONS);
|
||||
mockPopupService =
|
||||
jasmine.createSpyObj("popupService", ["display"]);
|
||||
mockPopup = jasmine.createSpyObj("popup", [
|
||||
"dismiss",
|
||||
"goesLeft",
|
||||
"goesUp"
|
||||
]);
|
||||
mockRootScope = jasmine.createSpyObj("$rootScope", ["$new"]);
|
||||
mockAgentService = jasmine.createSpyObj("agentService", ["isMobile"]);
|
||||
mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
|
||||
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
||||
mockEvent = jasmine.createSpyObj("event", ["preventDefault", "stopPropagation"]);
|
||||
mockEvent.pageX = 123;
|
||||
mockEvent.pageY = 321;
|
||||
|
||||
mockCompile.and.returnValue(mockCompiledTemplate);
|
||||
mockCompiledTemplate.and.returnValue(mockMenu);
|
||||
mockDocument.find.and.returnValue(mockBody);
|
||||
mockRootScope.$new.and.returnValue(mockScope);
|
||||
mockPopupService.display.and.returnValue(mockPopup);
|
||||
|
||||
mockActionContext = {key: 'menu', domainObject: mockDomainObject, event: mockEvent};
|
||||
|
||||
action = new ContextMenuAction(
|
||||
mockCompile,
|
||||
mockDocument,
|
||||
mockRootScope,
|
||||
mockPopupService,
|
||||
mockAgentService,
|
||||
mockActionContext
|
||||
);
|
||||
});
|
||||
|
||||
it("displays a popup when performed", function () {
|
||||
action.perform();
|
||||
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||
mockMenu,
|
||||
[mockEvent.pageX, mockEvent.pageY],
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("prevents the default context menu behavior", function () {
|
||||
action.perform();
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("adds classes to menus based on position", function () {
|
||||
var booleans = [false, true];
|
||||
|
||||
booleans.forEach(function (goLeft) {
|
||||
booleans.forEach(function (goUp) {
|
||||
mockPopup.goesLeft.and.returnValue(goLeft);
|
||||
mockPopup.goesUp.and.returnValue(goUp);
|
||||
action.perform();
|
||||
expect(!!mockScope.menuClass['go-up'])
|
||||
.toEqual(goUp);
|
||||
expect(!!mockScope.menuClass['go-left'])
|
||||
.toEqual(goLeft);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("removes a menu when body is clicked", function () {
|
||||
// Show the menu
|
||||
action.perform();
|
||||
|
||||
// Verify precondition
|
||||
expect(mockBody.remove).not.toHaveBeenCalled();
|
||||
|
||||
// Find and fire body's mousedown listener
|
||||
mockBody.on.calls.all().forEach(function (call) {
|
||||
if (call.args[0] === 'mousedown') {
|
||||
call.args[1]();
|
||||
}
|
||||
});
|
||||
|
||||
// Menu should have been removed
|
||||
expect(mockPopup.dismiss).toHaveBeenCalled();
|
||||
|
||||
// Listener should have been detached from body
|
||||
expect(mockBody.off).toHaveBeenCalledWith(
|
||||
'mousedown',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("removes a menu when it is clicked", function () {
|
||||
// Show the menu
|
||||
action.perform();
|
||||
|
||||
// Verify precondition
|
||||
expect(mockMenu.remove).not.toHaveBeenCalled();
|
||||
|
||||
// Find and fire menu's click listener
|
||||
mockMenu.on.calls.all().forEach(function (call) {
|
||||
if (call.args[0] === 'click') {
|
||||
call.args[1]();
|
||||
}
|
||||
});
|
||||
|
||||
// Menu should have been removed
|
||||
expect(mockPopup.dismiss).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps a menu when menu is clicked", function () {
|
||||
// Show the menu
|
||||
action.perform();
|
||||
// Find and fire body's mousedown listener
|
||||
mockMenu.on.calls.all().forEach(function (call) {
|
||||
if (call.args[0] === 'mousedown') {
|
||||
call.args[1](mockEvent);
|
||||
}
|
||||
});
|
||||
|
||||
// Menu should have been removed
|
||||
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||
|
||||
// Listener should have been detached from body
|
||||
expect(mockBody.off).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps a menu when menu is clicked on mobile", function () {
|
||||
mockAgentService.isMobile.and.returnValue(true);
|
||||
action = new ContextMenuAction(
|
||||
mockCompile,
|
||||
mockDocument,
|
||||
mockRootScope,
|
||||
mockPopupService,
|
||||
mockAgentService,
|
||||
mockActionContext
|
||||
);
|
||||
action.perform();
|
||||
|
||||
mockMenu.on.calls.all().forEach(function (call) {
|
||||
if (call.args[0] === 'touchstart') {
|
||||
call.args[1](mockEvent);
|
||||
}
|
||||
});
|
||||
|
||||
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,119 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* Module defining ContextMenuGestureSpec. Created by vwoeltje on 11/22/14.
|
||||
*/
|
||||
define(
|
||||
["../../src/gestures/ContextMenuGesture"],
|
||||
function (ContextMenuGesture) {
|
||||
|
||||
var JQLITE_FUNCTIONS = ["on", "off", "find", "append", "remove"],
|
||||
DOMAIN_OBJECT_METHODS = ["getId", "getModel", "getCapability", "hasCapability", "useCapability"];
|
||||
|
||||
|
||||
describe("The 'context menu' gesture", function () {
|
||||
var mockTimeout,
|
||||
mockElement,
|
||||
mockAgentService,
|
||||
mockDomainObject,
|
||||
mockTouchEvent,
|
||||
mockContextMenuAction,
|
||||
mockTouch,
|
||||
gesture,
|
||||
fireGesture,
|
||||
fireTouchStartGesture,
|
||||
fireTouchEndGesture;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
||||
mockAgentService = jasmine.createSpyObj("agentService", ["isMobile"]);
|
||||
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
||||
mockContextMenuAction = jasmine.createSpyObj(
|
||||
"action",
|
||||
["perform", "getActions"]
|
||||
);
|
||||
|
||||
mockDomainObject.getCapability.and.returnValue(mockContextMenuAction);
|
||||
mockContextMenuAction.perform.and.returnValue(jasmine.any(Function));
|
||||
mockAgentService.isMobile.and.returnValue(false);
|
||||
|
||||
|
||||
gesture = new ContextMenuGesture(mockTimeout, mockAgentService, mockElement, mockDomainObject);
|
||||
|
||||
// Capture the contextmenu callback
|
||||
fireGesture = mockElement.on.calls.mostRecent().args[1];
|
||||
});
|
||||
|
||||
it("attaches a callback for context menu events", function () {
|
||||
// Fire a click and expect it to happen
|
||||
fireGesture();
|
||||
expect(mockElement.on).toHaveBeenCalledWith(
|
||||
"contextmenu",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("detaches a callback for context menu events when destroyed", function () {
|
||||
expect(mockElement.off).not.toHaveBeenCalled();
|
||||
|
||||
gesture.destroy();
|
||||
|
||||
expect(mockElement.off).toHaveBeenCalledWith(
|
||||
"contextmenu",
|
||||
//mockElement.on.calls.mostRecent().args[1]
|
||||
mockDomainObject.calls
|
||||
);
|
||||
});
|
||||
|
||||
it("attaches a callback for context menu events on mobile", function () {
|
||||
// Mock touch event and set to mobile device
|
||||
mockTouchEvent = jasmine.createSpyObj("event", ["preventDefault", "touches"]);
|
||||
mockTouch = jasmine.createSpyObj("touch", ["length"]);
|
||||
mockTouch.length = 1;
|
||||
mockTouchEvent.touches.and.returnValue(mockTouch);
|
||||
mockAgentService.isMobile.and.returnValue(true);
|
||||
|
||||
// Then create new (mobile) gesture
|
||||
gesture = new ContextMenuGesture(mockTimeout, mockAgentService, mockElement, mockDomainObject);
|
||||
|
||||
// Set calls for the touchstart and touchend gestures
|
||||
fireTouchStartGesture = mockElement.on.calls.all()[1].args[1];
|
||||
fireTouchEndGesture = mockElement.on.calls.mostRecent().args[1];
|
||||
|
||||
// Fire touchstart and expect touch start to begin
|
||||
fireTouchStartGesture(mockTouchEvent);
|
||||
expect(mockElement.on).toHaveBeenCalledWith(
|
||||
"touchstart",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
// Expect timeout to begin and then fireTouchEnd
|
||||
expect(mockTimeout).toHaveBeenCalled();
|
||||
mockTimeout.calls.mostRecent().args[0]();
|
||||
fireTouchEndGesture(mockTouchEvent);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
45
src/MCT.js
45
src/MCT.js
@@ -40,7 +40,9 @@ define([
|
||||
'../platform/framework/src/Main',
|
||||
'./styles-new/core.scss',
|
||||
'./styles-new/notebook.scss',
|
||||
'./ui/components/layout/Layout.vue',
|
||||
'./ui/layout/Layout.vue',
|
||||
'../platform/core/src/objects/DomainObjectImpl',
|
||||
'../platform/core/src/capabilities/ContextualDomainObject',
|
||||
'vue'
|
||||
], function (
|
||||
EventEmitter,
|
||||
@@ -63,6 +65,8 @@ define([
|
||||
coreStyles,
|
||||
NotebookStyles,
|
||||
Layout,
|
||||
DomainObjectImpl,
|
||||
ContextualDomainObject,
|
||||
Vue
|
||||
) {
|
||||
/**
|
||||
@@ -220,10 +224,13 @@ define([
|
||||
|
||||
this.overlays = new OverlayAPI.default();
|
||||
|
||||
this.contextMenu = new api.ContextMenuRegistry();
|
||||
|
||||
this.legacyRegistry = defaultRegistry;
|
||||
this.install(this.plugins.Plot());
|
||||
this.install(this.plugins.TelemetryTable());
|
||||
this.install(this.plugins.DisplayLayout());
|
||||
this.install(this.plugins.Preview());
|
||||
|
||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||
this.install(buildInfoPlugin(BUILD_CONSTANTS));
|
||||
@@ -241,6 +248,42 @@ define([
|
||||
this.legacyBundle.extensions[category].push(extension);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a legacy object, for compatibility purposes only. This method
|
||||
* will be deprecated and removed in the future.
|
||||
* @private
|
||||
*/
|
||||
MCT.prototype.legacyObject = function (domainObject) {
|
||||
let capabilityService = this.$injector.get('capabilityService');
|
||||
|
||||
function instantiate(model, keyString) {
|
||||
var capabilities = capabilityService.getCapabilities(model, keyString);
|
||||
model.id = keyString;
|
||||
return new DomainObjectImpl(keyString, model, capabilities);
|
||||
}
|
||||
|
||||
if (Array.isArray(domainObject)) {
|
||||
// an array of domain objects. [object, ...ancestors] representing
|
||||
// a single object with a given chain of ancestors. We instantiate
|
||||
// as a single contextual domain object.
|
||||
return domainObject
|
||||
.map((o) => {
|
||||
let keyString = objectUtils.makeKeyString(o.identifier);
|
||||
let oldModel = objectUtils.toOldFormat(o);
|
||||
return instantiate(oldModel, keyString);
|
||||
})
|
||||
.reverse()
|
||||
.reduce((parent, child) => {
|
||||
return new ContextualDomainObject(child, parent);
|
||||
});
|
||||
|
||||
} else {
|
||||
let keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
let oldModel = objectUtils.toOldFormat(domainObject);
|
||||
return instantiate(oldModel, keyString);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set path to where assets are hosted. This should be the path to main.js.
|
||||
* @memberof module:openmct.MCT#
|
||||
|
||||
@@ -20,32 +20,18 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining ContextMenuController. Created by vwoeltje on 11/17/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
import LegacyContextMenuAction from './LegacyContextMenuAction';
|
||||
|
||||
/**
|
||||
* Controller for the context menu. Maintains an up-to-date
|
||||
* list of applicable actions (those from category "contextual")
|
||||
*
|
||||
* @memberof platform/commonUI/general
|
||||
* @constructor
|
||||
*/
|
||||
function ContextMenuController($scope) {
|
||||
// Refresh variable "menuActions" in the scope
|
||||
function updateActions() {
|
||||
$scope.menuActions = $scope.action ?
|
||||
$scope.action.getActions({ category: 'contextual' }) :
|
||||
[];
|
||||
}
|
||||
|
||||
// Update using the action capability
|
||||
$scope.$watch("action", updateActions);
|
||||
export default function LegacyActionAdapter(openmct, legacyActions) {
|
||||
function contextualCategoryOnly(action) {
|
||||
if (action.category === 'contextual' || (Array.isArray(action.category) && action.category.includes('contextual'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ContextMenuController;
|
||||
console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
legacyActions.filter(contextualCategoryOnly)
|
||||
.map(LegacyAction => new LegacyContextMenuAction(openmct, LegacyAction))
|
||||
.forEach(openmct.contextMenu.registerAction);
|
||||
}
|
||||
83
src/adapter/actions/LegacyContextMenuAction.js
Normal file
83
src/adapter/actions/LegacyContextMenuAction.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/*****************************************************************************
|
||||
* 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 _ from 'lodash';
|
||||
const INSIDE_EDIT_PATH_BLACKLIST = ["copy", "follow", "link", "locate", "move", "link"];
|
||||
const OUTSIDE_EDIT_PATH_BLACKLIST = ["copy", "follow", "properties", "move", "link", "remove", "locate"];
|
||||
|
||||
export default class LegacyContextMenuAction {
|
||||
constructor(openmct, LegacyAction) {
|
||||
this.openmct = openmct;
|
||||
this.name = LegacyAction.definition.name;
|
||||
this.description = LegacyAction.definition.description;
|
||||
this.cssClass = LegacyAction.definition.cssClass;
|
||||
this.LegacyAction = LegacyAction;
|
||||
}
|
||||
|
||||
invoke(objectPath) {
|
||||
let context = {
|
||||
category: 'contextual',
|
||||
domainObject: this.openmct.legacyObject(objectPath)
|
||||
}
|
||||
let legacyAction = new this.LegacyAction(context);
|
||||
|
||||
if (!legacyAction.getMetadata) {
|
||||
let metadata = Object.create(this.LegacyAction.definition);
|
||||
metadata.context = context;
|
||||
legacyAction.getMetadata = function () {
|
||||
return metadata;
|
||||
}.bind(legacyAction);
|
||||
}
|
||||
legacyAction.perform();
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
let legacyObject = this.openmct.legacyObject(objectPath);
|
||||
|
||||
return (this.LegacyAction.appliesTo === undefined ||
|
||||
this.LegacyAction.appliesTo({domainObject: legacyObject})) &&
|
||||
!this.isBlacklisted(objectPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
isBlacklisted(objectPath) {
|
||||
let navigatedObject = this.openmct.router.path[0];
|
||||
let isEditing = this.openmct.editor.isEditing();
|
||||
|
||||
/**
|
||||
* Is the object being edited, or a child of the object being edited?
|
||||
*/
|
||||
function isInsideEditPath() {
|
||||
return objectPath.some((object) => _.eq(object.identifier, navigatedObject.identifier));
|
||||
}
|
||||
|
||||
if (isEditing) {
|
||||
if (isInsideEditPath()) {
|
||||
return INSIDE_EDIT_PATH_BLACKLIST.some(actionKey => this.LegacyAction.key === actionKey);
|
||||
} else {
|
||||
return OUTSIDE_EDIT_PATH_BLACKLIST.some(actionKey => this.LegacyAction.key === actionKey);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,8 @@ define([
|
||||
'./runs/RegisterLegacyTypes',
|
||||
'./services/LegacyObjectAPIInterceptor',
|
||||
'./views/installLegacyViews',
|
||||
'./policies/legacyCompositionPolicyAdapter'
|
||||
'./policies/legacyCompositionPolicyAdapter',
|
||||
'./actions/LegacyActionAdapter'
|
||||
], function (
|
||||
legacyRegistry,
|
||||
ActionDialogDecorator,
|
||||
@@ -53,7 +54,8 @@ define([
|
||||
RegisterLegacyTypes,
|
||||
LegacyObjectAPIInterceptor,
|
||||
installLegacyViews,
|
||||
legacyCompositionPolicyAdapter
|
||||
legacyCompositionPolicyAdapter,
|
||||
LegacyActionAdapter
|
||||
) {
|
||||
legacyRegistry.register('src/adapter', {
|
||||
"extensions": {
|
||||
@@ -169,6 +171,13 @@ define([
|
||||
depends: [
|
||||
"openmct"
|
||||
]
|
||||
},
|
||||
{
|
||||
implementation: LegacyActionAdapter.default,
|
||||
depends: [
|
||||
"openmct",
|
||||
"actions[]"
|
||||
]
|
||||
}
|
||||
],
|
||||
licenses: [
|
||||
|
||||
@@ -77,7 +77,7 @@ define([
|
||||
openmct.$angular.element(container),
|
||||
legacyView
|
||||
);
|
||||
container.style.height = '100%';
|
||||
container.classList.add('u-contents');
|
||||
}
|
||||
|
||||
if (promises.length) {
|
||||
|
||||
@@ -28,6 +28,11 @@ export default class Editor extends EventEmitter {
|
||||
super();
|
||||
this.editing = false;
|
||||
this.openmct = openmct;
|
||||
document.addEventListener('drop', (event) => {
|
||||
if (!this.isEditing()) {
|
||||
this.edit();
|
||||
}
|
||||
}, {capture: true});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ define([
|
||||
'./telemetry/TelemetryAPI',
|
||||
'./indicators/IndicatorAPI',
|
||||
'./notifications/NotificationAPI',
|
||||
'./contextMenu/ContextMenuAPI',
|
||||
'./Editor'
|
||||
|
||||
], function (
|
||||
@@ -38,6 +39,7 @@ define([
|
||||
TelemetryAPI,
|
||||
IndicatorAPI,
|
||||
NotificationAPI,
|
||||
ContextMenuAPI,
|
||||
EditorAPI
|
||||
) {
|
||||
return {
|
||||
@@ -48,6 +50,7 @@ define([
|
||||
TelemetryAPI: TelemetryAPI,
|
||||
IndicatorAPI: IndicatorAPI,
|
||||
NotificationAPI: NotificationAPI.default,
|
||||
EditorAPI: EditorAPI
|
||||
EditorAPI: EditorAPI,
|
||||
ContextMenuRegistry: ContextMenuAPI.default
|
||||
};
|
||||
});
|
||||
|
||||
@@ -177,7 +177,11 @@ define([
|
||||
CompositionCollection.prototype.load = function () {
|
||||
return this.provider.load(this.domainObject)
|
||||
.then(function (children) {
|
||||
return Promise.all(children.map(this.onProviderAdd, this));
|
||||
return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
|
||||
}.bind(this))
|
||||
.then(function (childObjects) {
|
||||
childObjects.forEach(c => this.add(c, true));
|
||||
return childObjects;
|
||||
}.bind(this))
|
||||
.then(function (children) {
|
||||
this.emit('load');
|
||||
|
||||
20
src/api/contextMenu/ContextMenu.vue
Normal file
20
src/api/contextMenu/ContextMenu.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="c-menu">
|
||||
<ul>
|
||||
<li v-for="action in actions"
|
||||
:key="action.name"
|
||||
:class="action.cssClass"
|
||||
:title="action.description"
|
||||
@click="action.invoke(objectPath)">
|
||||
{{ action.name }}
|
||||
</li>
|
||||
<li v-if="actions.length === 0">No actions defined.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['actions', 'objectPath']
|
||||
}
|
||||
</script>
|
||||
145
src/api/contextMenu/ContextMenuAPI.js
Normal file
145
src/api/contextMenu/ContextMenuAPI.js
Normal file
@@ -0,0 +1,145 @@
|
||||
/*****************************************************************************
|
||||
* 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 ContextMenuComponent from './ContextMenu.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
/**
|
||||
* The ContextMenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
|
||||
* custom HTML elements.
|
||||
* @interface ContextMenuAPI
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
class ContextMenuAPI {
|
||||
constructor() {
|
||||
this._allActions = [];
|
||||
this._activeContextMenu = undefined;
|
||||
|
||||
this._hideActiveContextMenu = this._hideActiveContextMenu.bind(this);
|
||||
this.registerAction = this.registerAction.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an item to be added to context menus. Allows specification of text, appearance, and behavior when
|
||||
* selected. Applicabilioty can be restricted by specification of an `appliesTo` function.
|
||||
*
|
||||
* @interface ContextMenuAction
|
||||
* @memberof module:openmct
|
||||
* @property {string} name the human-readable name of this view
|
||||
* @property {string} description a longer-form description (typically
|
||||
* a single sentence or short paragraph) of this kind of view
|
||||
* @property {string} cssClass the CSS class to apply to labels for this
|
||||
* view (to add icons, for instance)
|
||||
*/
|
||||
/**
|
||||
* @method appliesTo
|
||||
* @memberof module:openmct.ContextMenuAction#
|
||||
* @param {DomainObject[]} objectPath the path of the object that the context menu has been invoked on.
|
||||
* @returns {boolean} true if the action applies to the objects specified in the 'objectPath', otherwise false.
|
||||
*/
|
||||
/**
|
||||
* Code to be executed when the action is selected from a context menu
|
||||
* @method invoke
|
||||
* @memberof module:openmct.ContextMenuAction#
|
||||
* @param {DomainObject[]} objectPath the path of the object to invoke the action on.
|
||||
*/
|
||||
/**
|
||||
* @param {ContextMenuAction} actionDefinition
|
||||
*/
|
||||
registerAction(actionDefinition) {
|
||||
this._allActions.push(actionDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_showContextMenuForObjectPath(objectPath, x, y) {
|
||||
let applicableActions = this._allActions.filter((action) => {
|
||||
if (action.appliesTo === undefined) {
|
||||
return true;
|
||||
}
|
||||
return action.appliesTo(objectPath);
|
||||
});
|
||||
|
||||
if (this._activeContextMenu) {
|
||||
this._hideActiveContextMenu();
|
||||
}
|
||||
|
||||
this._activeContextMenu = this._createContextMenuForObject(objectPath, applicableActions);
|
||||
this._activeContextMenu.$mount();
|
||||
document.body.appendChild(this._activeContextMenu.$el);
|
||||
|
||||
let position = this._calculatePopupPosition(x, y, this._activeContextMenu.$el);
|
||||
this._activeContextMenu.$el.style.left = `${position.x}px`;
|
||||
this._activeContextMenu.$el.style.top = `${position.y}px`;
|
||||
|
||||
document.addEventListener('click', this._hideActiveContextMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_calculatePopupPosition(eventPosX, eventPosY, menuElement) {
|
||||
let menuDimensions = menuElement.getBoundingClientRect();
|
||||
let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth;
|
||||
let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight;
|
||||
|
||||
if (overflowX > 0) {
|
||||
eventPosX = eventPosX - overflowX;
|
||||
}
|
||||
|
||||
if (overflowY > 0) {
|
||||
eventPosY = eventPosY - overflowY;
|
||||
}
|
||||
|
||||
return {
|
||||
x: eventPosX,
|
||||
y: eventPosY
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_hideActiveContextMenu() {
|
||||
document.removeEventListener('click', this._hideActiveContextMenu);
|
||||
document.body.removeChild(this._activeContextMenu.$el);
|
||||
this._activeContextMenu.$destroy();
|
||||
this._activeContextMenu = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_createContextMenuForObject(objectPath, actions) {
|
||||
return new Vue({
|
||||
components: {
|
||||
ContextMenu: ContextMenuComponent
|
||||
},
|
||||
provide: {
|
||||
actions: actions,
|
||||
objectPath: objectPath
|
||||
},
|
||||
template: '<ContextMenu></ContextMenu>'
|
||||
});
|
||||
}
|
||||
}
|
||||
export default ContextMenuAPI;
|
||||
@@ -3,13 +3,15 @@ import Overlay from './Overlay';
|
||||
import Vue from 'vue';
|
||||
|
||||
class Dialog extends Overlay {
|
||||
constructor({iconClass, message, title, ...options}) {
|
||||
constructor({iconClass, message, title, hint, timestamp, ...options}) {
|
||||
|
||||
let component = new Vue({
|
||||
provide: {
|
||||
iconClass,
|
||||
message,
|
||||
title
|
||||
title,
|
||||
hint,
|
||||
timestamp
|
||||
},
|
||||
components: {
|
||||
DialogComponent: DialogComponent
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ProgressComponent from '../../ui/components/layout/ProgressBar.vue';
|
||||
import ProgressComponent from '../../ui/components/ProgressBar.vue';
|
||||
import Overlay from './Overlay';
|
||||
import Vue from 'vue';
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
flex: 1 1 auto;
|
||||
|
||||
> * + * {
|
||||
@include test();
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
.c-overlay {
|
||||
@include abs();
|
||||
z-index: 100;
|
||||
z-index: 70;
|
||||
|
||||
&__blocker {
|
||||
display: none; // Mobile-first
|
||||
|
||||
@@ -124,6 +124,22 @@ define([
|
||||
return sortedMetadata;
|
||||
};
|
||||
|
||||
TelemetryMetadataManager.prototype.getDefaultDisplayValue = function () {
|
||||
let valueMetadata = this.valuesForHints(['range'])[0];
|
||||
|
||||
if (valueMetadata === undefined) {
|
||||
valueMetadata = this.values().filter(values => {
|
||||
return !(values.hints.domain);
|
||||
})[0];
|
||||
}
|
||||
|
||||
if (valueMetadata === undefined) {
|
||||
valueMetadata = this.values()[0];
|
||||
}
|
||||
|
||||
return valueMetadata.key;
|
||||
};
|
||||
|
||||
|
||||
return TelemetryMetadataManager;
|
||||
|
||||
|
||||
@@ -1,312 +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="l-layout"
|
||||
@dragover="handleDragOver"
|
||||
@click="bypassSelection"
|
||||
@drop="handleDrop">
|
||||
<div class="l-layout__object">
|
||||
<!-- Background grid -->
|
||||
<div class="l-layout__grid-holder c-grid"
|
||||
v-if="!drilledIn">
|
||||
<div class="c-grid__x l-grid l-grid-x"
|
||||
v-if="gridSize[0] >= 3"
|
||||
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
|
||||
</div>
|
||||
<div class="c-grid__y l-grid l-grid-y"
|
||||
v-if="gridSize[1] >= 3"
|
||||
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
||||
</div>
|
||||
<layout-frame v-for="item in frameItems"
|
||||
class="l-layout__frame"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:gridSize="gridSize"
|
||||
@drilledIn="updateDrilledInState"
|
||||
@dragInProgress="updatePosition"
|
||||
@endDrag="endDrag">
|
||||
</layout-frame>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.l-layout {
|
||||
@include abs();
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__grid-holder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__object {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__frame {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.l-shell__main-container {
|
||||
> .l-layout {
|
||||
[s-selected] {
|
||||
border: $browseBorderSelected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Styles moved to _global.scss;
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue';
|
||||
|
||||
const DEFAULT_GRID_SIZE = [32, 32],
|
||||
DEFAULT_DIMENSIONS = [12, 8],
|
||||
DEFAULT_POSITION = [0, 0],
|
||||
MINIMUM_FRAME_SIZE = [320, 180],
|
||||
DEFAULT_HIDDEN_FRAME_TYPES = [
|
||||
'hyperlink'
|
||||
];
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
gridSize: [],
|
||||
frameItems: [],
|
||||
frames: [],
|
||||
frameStyles: [],
|
||||
rawPositions: {},
|
||||
drilledIn: undefined
|
||||
}
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: ['domainObject'],
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
created: function () {
|
||||
this.newDomainObject = this.domainObject;
|
||||
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;
|
||||
this.composition = this.openmct.composition.get(this.newDomainObject);
|
||||
this.Listeners = [];
|
||||
let panels = (((this.newDomainObject.configuration || {}).layout || {}).panels || {});
|
||||
|
||||
if (this.composition !== undefined) {
|
||||
this.composition.load().then((composition) => {
|
||||
composition.forEach(function (domainObject) {
|
||||
this.readLayoutConfiguration(domainObject, panels);
|
||||
this.makeFrameItem(domainObject, false);
|
||||
}.bind(this));
|
||||
this.composition.on('add', this.onAddComposition);
|
||||
this.composition.on('remove', this.onRemoveComposition);
|
||||
});
|
||||
}
|
||||
|
||||
this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) {
|
||||
this.newDomainObject = JSON.parse(JSON.stringify(obj));
|
||||
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;;
|
||||
}.bind(this));
|
||||
},
|
||||
methods: {
|
||||
readLayoutConfiguration(domainObject, panels) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.rawPositions[id] = {
|
||||
position: panels[id].position || DEFAULT_POSITION,
|
||||
dimensions: panels[id].dimensions || this.defaultDimensions()
|
||||
};
|
||||
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
|
||||
this.frames[id] = panels[id].hasOwnProperty('hasFrame') ?
|
||||
panels[id].hasFrame :
|
||||
this.hasFrameByDefault(domainObject.type);
|
||||
},
|
||||
makeFrameItem(domainObject, initSelect) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.frameItems.push({
|
||||
id: id,
|
||||
hasFrame: this.frames[id],
|
||||
domainObject,
|
||||
style: this.frameStyles[id],
|
||||
drilledIn: this.isDrilledIn(id),
|
||||
initSelect: initSelect,
|
||||
rawPosition: this.rawPositions[id]
|
||||
});
|
||||
},
|
||||
onAddComposition(domainObject) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.rawPositions[id] = {
|
||||
position: [
|
||||
Math.floor(this.droppedObjectPosition.x / this.gridSize[0]),
|
||||
Math.floor(this.droppedObjectPosition.y / this.gridSize[1])
|
||||
],
|
||||
dimensions: this.defaultDimensions()
|
||||
};
|
||||
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
|
||||
this.frames[id] = this.hasFrameByDefault(domainObject.type);
|
||||
|
||||
let newPanel = this.rawPositions[id];
|
||||
newPanel.hasFrame = this.frames[id];
|
||||
this.mutate("configuration.layout.panels[" + id + "]", newPanel);
|
||||
this.makeFrameItem(domainObject, true);
|
||||
},
|
||||
onRemoveComposition(identifier) {
|
||||
// TODO: remove the object from frameItems
|
||||
},
|
||||
defaultDimensions() {
|
||||
let gridSize = this.gridSize;
|
||||
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
||||
return Math.max(
|
||||
Math.ceil(min / gridSize[i]),
|
||||
DEFAULT_DIMENSIONS[i]
|
||||
);
|
||||
});
|
||||
},
|
||||
convertPosition(raw) {
|
||||
return {
|
||||
left: (this.gridSize[0] * raw.position[0]) + 'px',
|
||||
top: (this.gridSize[1] * raw.position[1]) + 'px',
|
||||
width: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
height: (this.gridSize[1] * raw.dimensions[1]) + 'px',
|
||||
minWidth: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px'
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Checks if the frame should be hidden or not.
|
||||
*
|
||||
* @param type the domain object type
|
||||
* @return {boolean} true if the object should have
|
||||
* frame by default, false, otherwise
|
||||
*/
|
||||
hasFrameByDefault(type) {
|
||||
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
||||
},
|
||||
setSelection(selection) {
|
||||
if (selection.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeListeners();
|
||||
let domainObject = selection[0].context.item;
|
||||
|
||||
if (selection[1] && domainObject) {
|
||||
this.attachSelectionListeners(domainObject.identifier);
|
||||
}
|
||||
|
||||
this.updateDrilledInState();
|
||||
},
|
||||
attachSelectionListeners(identifier) {
|
||||
let id = this.openmct.objects.makeKeyString(identifier);
|
||||
let path = "configuration.layout.panels[" + id + "]";
|
||||
this.listeners.push(this.openmct.objects.observe(this.newDomainObject, path + ".hasFrame", function (newValue) {
|
||||
this.frameItems.forEach(function (item) {
|
||||
if (item.id === id) {
|
||||
item.hasFrame = newValue;
|
||||
}
|
||||
});
|
||||
this.frames[id] = newValue;
|
||||
}.bind(this)));
|
||||
},
|
||||
updateDrilledInState(id) {
|
||||
this.drilledIn = id;
|
||||
this.frameItems.forEach(function (item) {
|
||||
item.drilledIn = item.id === id;
|
||||
});
|
||||
},
|
||||
isDrilledIn(id) {
|
||||
return this.drilledIn === id;
|
||||
},
|
||||
updatePosition(id, newPosition) {
|
||||
let newStyle = this.convertPosition(newPosition);
|
||||
this.frameStyles[id] = newStyle;
|
||||
this.rawPositions[id] = newPosition;
|
||||
this.frameItems.forEach(function (item) {
|
||||
if (item.id === id) {
|
||||
item.style = newStyle;
|
||||
item.rawPosition = newPosition;
|
||||
}
|
||||
});
|
||||
},
|
||||
bypassSelection($event) {
|
||||
if (this.dragInProgress) {
|
||||
if ($event) {
|
||||
$event.stopImmediatePropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
endDrag(id) {
|
||||
this.dragInProgress = true;
|
||||
setTimeout(function () {
|
||||
this.dragInProgress = false;
|
||||
}.bind(this), 0);
|
||||
|
||||
let path = "configuration.layout.panels[" + id + "]";
|
||||
this.mutate(path + ".dimensions", this.rawPositions[id].dimensions);
|
||||
this.mutate(path + ".position", this.rawPositions[id].position);
|
||||
},
|
||||
mutate(path, value) {
|
||||
this.openmct.objects.mutate(this.newDomainObject, path, value);
|
||||
},
|
||||
handleDrop($event) {
|
||||
$event.preventDefault();
|
||||
|
||||
let child = JSON.parse($event.dataTransfer.getData('domainObject'));
|
||||
|
||||
let elementRect = this.$el.getBoundingClientRect();
|
||||
this.droppedObjectPosition = {
|
||||
x: $event.pageX - elementRect.left,
|
||||
y: $event.pageY - elementRect.top
|
||||
}
|
||||
},
|
||||
handleDragOver($event){
|
||||
$event.preventDefault();
|
||||
},
|
||||
removeListeners() {
|
||||
if (this.listeners) {
|
||||
this.listeners.forEach(function (l) {
|
||||
l();
|
||||
})
|
||||
}
|
||||
this.listeners = [];
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.selection.on('change', this.setSelection);
|
||||
},
|
||||
destroyed: function () {
|
||||
this.composition.off('add', this.onAddComposition);
|
||||
this.composition.off('remove', this.onRemoveComposition);
|
||||
this.openmct.off('change', this.selection);
|
||||
this.unlisten();
|
||||
this.removeListeners();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
354
src/plugins/displayLayout/DisplayLayoutToolbar.js
Normal file
354
src/plugins/displayLayout/DisplayLayoutToolbar.js
Normal file
@@ -0,0 +1,354 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
|
||||
function DisplayLayoutToolbar(openmct) {
|
||||
return {
|
||||
name: "Display Layout Toolbar",
|
||||
key: "layout",
|
||||
description: "A toolbar for objects inside a display layout.",
|
||||
forSelection: function (selection) {
|
||||
// Apply the layout toolbar if the edit mode is on, and the selected object
|
||||
// is inside a layout, or the main layout is selected.
|
||||
return (openmct.editor.isEditing() && selection &&
|
||||
((selection[1] && selection[1].context.item && selection[1].context.item.type === 'layout') ||
|
||||
(selection[0].context.item && selection[0].context.item.type === 'layout')));
|
||||
},
|
||||
toolbar: function (selection) {
|
||||
const DIALOG_FORM = {
|
||||
'text': {
|
||||
name: "Text Element Properties",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "text",
|
||||
control: "textfield",
|
||||
name: "Text",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
'image': {
|
||||
name: "Image Properties",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "url",
|
||||
control: "textfield",
|
||||
name: "Image URL",
|
||||
"cssClass": "l-input-lg",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function getUserInput(form) {
|
||||
return openmct.$injector.get('dialogService').getUserInput(form, {});
|
||||
}
|
||||
|
||||
let selectedParent = selection[1] && selection[1].context.item,
|
||||
selectedObject = selection[0].context.item,
|
||||
layoutItem = selection[0].context.layoutItem,
|
||||
layoutItemIndex = selection[0].context.index,
|
||||
toolbar = [];
|
||||
|
||||
if (selectedObject && selectedObject.type === 'layout') {
|
||||
toolbar.push({
|
||||
control: "menu",
|
||||
domainObject: selectedObject,
|
||||
method: function (option) {
|
||||
let name = option.name.toLowerCase();
|
||||
let form = DIALOG_FORM[name];
|
||||
if (form) {
|
||||
getUserInput(form)
|
||||
.then(element => selection[0].context.addElement(name, element));
|
||||
} else {
|
||||
selection[0].context.addElement(name);
|
||||
}
|
||||
},
|
||||
key: "add",
|
||||
icon: "icon-plus",
|
||||
label: "Add",
|
||||
options: [
|
||||
{
|
||||
"name": "Box",
|
||||
"class": "icon-box-round-corners"
|
||||
},
|
||||
{
|
||||
"name": "Line",
|
||||
"class": "icon-line-horz"
|
||||
},
|
||||
{
|
||||
"name": "Text",
|
||||
"class": "icon-font"
|
||||
},
|
||||
{
|
||||
"name": "Image",
|
||||
"class": "icon-image"
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (!layoutItem) {
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
let path = `configuration.items[${layoutItemIndex}]`;
|
||||
let separator = {
|
||||
control: "separator"
|
||||
};
|
||||
|
||||
if (layoutItem.type === 'subobject-view') {
|
||||
if (toolbar.length > 0) {
|
||||
toolbar.push(separator);
|
||||
}
|
||||
toolbar.push({
|
||||
control: "toggle-button",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".hasFrame",
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-frame-hide',
|
||||
title: "Hide frame"
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-frame-show',
|
||||
title: "Show frame"
|
||||
}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
const TEXT_SIZE = [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96];
|
||||
let fill = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".fill",
|
||||
icon: "icon-paint-bucket",
|
||||
title: "Set fill color"
|
||||
},
|
||||
stroke = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".stroke",
|
||||
icon: "icon-line-horz",
|
||||
title: "Set border color"
|
||||
},
|
||||
color = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".color",
|
||||
icon: "icon-font",
|
||||
mandatory: true,
|
||||
title: "Set text color",
|
||||
preventNone: true
|
||||
},
|
||||
size = {
|
||||
control: "select-menu",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".size",
|
||||
title: "Set text size",
|
||||
options: TEXT_SIZE.map(size => {
|
||||
return {
|
||||
value: size + "px"
|
||||
};
|
||||
})
|
||||
},
|
||||
x = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".x",
|
||||
label: "X:",
|
||||
title: "X position"
|
||||
},
|
||||
y = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".y",
|
||||
label: "Y:",
|
||||
title: "Y position",
|
||||
},
|
||||
width = {
|
||||
control: 'input',
|
||||
type: 'number',
|
||||
domainObject: selectedParent,
|
||||
property: path + ".width",
|
||||
label: 'W:',
|
||||
title: 'Resize object width'
|
||||
},
|
||||
height = {
|
||||
control: 'input',
|
||||
type: 'number',
|
||||
domainObject: selectedParent,
|
||||
property: path + ".height",
|
||||
label: 'H:',
|
||||
title: 'Resize object height'
|
||||
};
|
||||
|
||||
if (layoutItem.type === 'telemetry-view') {
|
||||
let displayMode = {
|
||||
control: "select-menu",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".displayMode",
|
||||
title: "Set display mode",
|
||||
options: [
|
||||
{
|
||||
name: 'Label + Value',
|
||||
value: 'all'
|
||||
},
|
||||
{
|
||||
name: "Label only",
|
||||
value: "label"
|
||||
},
|
||||
{
|
||||
name: "Value only",
|
||||
value: "value"
|
||||
}
|
||||
]
|
||||
},
|
||||
value = {
|
||||
control: "select-menu",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".value",
|
||||
title: "Set value",
|
||||
options: openmct.telemetry.getMetadata(selectedObject).values().map(value => {
|
||||
return {
|
||||
name: value.name,
|
||||
value: value.key
|
||||
}
|
||||
})
|
||||
};
|
||||
toolbar = [
|
||||
displayMode,
|
||||
separator,
|
||||
value,
|
||||
separator,
|
||||
fill,
|
||||
stroke,
|
||||
color,
|
||||
separator,
|
||||
size,
|
||||
separator,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width
|
||||
];
|
||||
} else if (layoutItem.type === 'text-view' ) {
|
||||
let text = {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
property: path,
|
||||
icon: "icon-gear",
|
||||
title: "Edit text properties",
|
||||
dialog: DIALOG_FORM['text']
|
||||
};
|
||||
toolbar = [
|
||||
fill,
|
||||
stroke,
|
||||
color,
|
||||
separator,
|
||||
size,
|
||||
separator,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width,
|
||||
separator,
|
||||
text
|
||||
];
|
||||
} else if (layoutItem.type === 'box-view') {
|
||||
toolbar = [
|
||||
fill,
|
||||
stroke,
|
||||
separator,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width
|
||||
];
|
||||
} else if (layoutItem.type === 'image-view') {
|
||||
let url = {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
property: path,
|
||||
icon: "icon-image",
|
||||
title: "Edit image properties",
|
||||
dialog: DIALOG_FORM['image']
|
||||
};
|
||||
toolbar = [
|
||||
stroke,
|
||||
separator,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width,
|
||||
separator,
|
||||
url
|
||||
];
|
||||
} else if (layoutItem.type === 'line-view') {
|
||||
let x2 = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".x2",
|
||||
label: "X2:",
|
||||
title: "X2 position"
|
||||
},
|
||||
y2 = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".y2",
|
||||
label: "Y2:",
|
||||
title: "Y2 position",
|
||||
};
|
||||
toolbar = [
|
||||
stroke,
|
||||
separator,
|
||||
x,
|
||||
y,
|
||||
x2,
|
||||
y2
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return toolbar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DisplayLayoutToolbar;
|
||||
});
|
||||
@@ -29,13 +29,12 @@ define(function () {
|
||||
initialize(domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {
|
||||
layout: {
|
||||
panels: {}
|
||||
}
|
||||
items: [],
|
||||
layoutGrid: [10, 10],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DisplayLayoutType;
|
||||
});
|
||||
});
|
||||
|
||||
96
src/plugins/displayLayout/components/BoxView.vue
Normal file
96
src/plugins/displayLayout/components/BoxView.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||
<div class="c-box-view"
|
||||
:style="style">
|
||||
</div>
|
||||
</layout-frame>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~styles/sass-base';
|
||||
|
||||
.c-box-view {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
.c-frame & {
|
||||
@include abs();
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
|
||||
export default {
|
||||
makeDefinition() {
|
||||
return {
|
||||
fill: '#717171',
|
||||
stroke: 'transparent',
|
||||
x: 1,
|
||||
y: 1,
|
||||
width: 10,
|
||||
height: 5
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
index: Number,
|
||||
initSelect: Boolean
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
backgroundColor: this.item.fill,
|
||||
border: '1px solid ' + this.item.stroke
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let context = {
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el,
|
||||
context,
|
||||
this.initSelect
|
||||
);
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
280
src/plugins/displayLayout/components/DisplayLayout.vue
Normal file
280
src/plugins/displayLayout/components/DisplayLayout.vue
Normal file
@@ -0,0 +1,280 @@
|
||||
/*****************************************************************************
|
||||
* 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="l-layout"
|
||||
@dragover="handleDragOver"
|
||||
@click="bypassSelection"
|
||||
@drop="handleDrop">
|
||||
<div class="l-layout__object">
|
||||
<!-- Background grid -->
|
||||
<div class="l-layout__grid-holder c-grid"
|
||||
v-if="!drilledIn">
|
||||
<div class="c-grid__x l-grid l-grid-x"
|
||||
v-if="gridSize[0] >= 3"
|
||||
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
|
||||
</div>
|
||||
<div class="c-grid__y l-grid l-grid-y"
|
||||
v-if="gridSize[1] >= 3"
|
||||
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
||||
</div>
|
||||
<component v-for="(item, index) in layoutItems"
|
||||
:is="item.type"
|
||||
:item="item"
|
||||
:gridSize="gridSize"
|
||||
:initSelect="initSelectIndex === index"
|
||||
:index="index"
|
||||
@drilledIn="updateDrilledIn"
|
||||
@endDrag="endDrag"
|
||||
>
|
||||
</component>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.l-layout {
|
||||
@include abs();
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__grid-holder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__object {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__frame {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.l-shell__main-container {
|
||||
> .l-layout {
|
||||
[s-selected] {
|
||||
border: $browseBorderSelected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Styles moved to _global.scss;
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import uuid from 'uuid';
|
||||
|
||||
import SubobjectView from './SubobjectView.vue'
|
||||
import TelemetryView from './TelemetryView.vue'
|
||||
import BoxView from './BoxView.vue'
|
||||
import TextView from './TextView.vue'
|
||||
import LineView from './LineView.vue'
|
||||
import ImageView from './ImageView.vue'
|
||||
|
||||
const ITEM_TYPE_VIEW_MAP = {
|
||||
'subobject-view': SubobjectView,
|
||||
'telemetry-view': TelemetryView,
|
||||
'box-view': BoxView,
|
||||
'line-view': LineView,
|
||||
'text-view': TextView,
|
||||
'image-view': ImageView
|
||||
};
|
||||
|
||||
function getItemDefinition(itemType, ...options) {
|
||||
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
|
||||
|
||||
if (!itemView) {
|
||||
throw `Invalid itemType: ${itemType}`;
|
||||
}
|
||||
|
||||
return itemView.makeDefinition(...options);
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
|
||||
return {
|
||||
drilledIn: undefined,
|
||||
internalDomainObject: domainObject,
|
||||
initSelectIndex: undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
gridSize() {
|
||||
return this.internalDomainObject.configuration.layoutGrid;
|
||||
},
|
||||
layoutItems() {
|
||||
return this.internalDomainObject.configuration.items;
|
||||
}
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: ['domainObject'],
|
||||
components: ITEM_TYPE_VIEW_MAP,
|
||||
methods: {
|
||||
addElement(itemType, element) {
|
||||
this.addItem(itemType + '-view', element);
|
||||
},
|
||||
setSelection(selection) {
|
||||
if (selection.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateDrilledIn();
|
||||
},
|
||||
updateDrilledIn(drilledInItem) {
|
||||
let identifier = drilledInItem && this.openmct.objects.makeKeyString(drilledInItem.identifier);
|
||||
this.drilledIn = identifier;
|
||||
this.layoutItems.forEach(item => {
|
||||
if (item.type === 'subobject-view') {
|
||||
item.drilledIn = this.openmct.objects.makeKeyString(item.identifier) === identifier;
|
||||
}
|
||||
});
|
||||
},
|
||||
bypassSelection($event) {
|
||||
if (this.dragInProgress) {
|
||||
if ($event) {
|
||||
$event.stopImmediatePropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
endDrag(item, updates) {
|
||||
this.dragInProgress = true;
|
||||
setTimeout(function () {
|
||||
this.dragInProgress = false;
|
||||
}.bind(this), 0);
|
||||
|
||||
let index = this.layoutItems.indexOf(item);
|
||||
Object.assign(item, updates);
|
||||
this.mutate(`configuration.items[${index}]`, item);
|
||||
},
|
||||
mutate(path, value) {
|
||||
this.openmct.objects.mutate(this.internalDomainObject, path, value);
|
||||
},
|
||||
handleDrop($event) {
|
||||
if (!$event.dataTransfer.types.includes('openmct/domain-object')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event.preventDefault();
|
||||
|
||||
let domainObject = JSON.parse($event.dataTransfer.getData('openmct/domain-object'));
|
||||
let elementRect = this.$el.getBoundingClientRect();
|
||||
let droppedObjectPosition = [
|
||||
Math.floor(($event.pageX - elementRect.left) / this.gridSize[0]),
|
||||
Math.floor(($event.pageY - elementRect.top) / this.gridSize[1])
|
||||
];
|
||||
|
||||
if (this.isTelemetry(domainObject)) {
|
||||
this.addItem('telemetry-view', domainObject, droppedObjectPosition);
|
||||
} else {
|
||||
let identifier = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
|
||||
if (!this.objectViewMap[identifier]) {
|
||||
this.addItem('subobject-view', domainObject, droppedObjectPosition);
|
||||
} else {
|
||||
let prompt = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: "This item is already in layout and will not be added again.",
|
||||
buttons: [
|
||||
{
|
||||
label: 'OK',
|
||||
callback: function () {
|
||||
prompt.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
handleDragOver($event){
|
||||
$event.preventDefault();
|
||||
},
|
||||
isTelemetry(domainObject) {
|
||||
if (this.openmct.telemetry.isTelemetryObject(domainObject)
|
||||
&& domainObject.type !== 'summary-widget') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
addItem(itemType, ...options) {
|
||||
let item = getItemDefinition(itemType, this.openmct, this.gridSize, ...options);
|
||||
item.type = itemType;
|
||||
this.trackItem(item);
|
||||
this.layoutItems.push(item);
|
||||
this.openmct.objects.mutate(this.internalDomainObject, "configuration.items", this.layoutItems);
|
||||
this.initSelectIndex = this.layoutItems.length - 1;
|
||||
},
|
||||
trackItem(item) {
|
||||
if (item.type === "telemetry-view") {
|
||||
this.telemetryViewMap[this.openmct.objects.makeKeyString(item.identifier)] = true;
|
||||
} else if (item.type === "subobject-view") {
|
||||
this.objectViewMap[this.openmct.objects.makeKeyString(item.identifier)] = true;
|
||||
}
|
||||
},
|
||||
initializeItems() {
|
||||
this.telemetryViewMap = {};
|
||||
this.objectViewMap = {};
|
||||
this.layoutItems.forEach(this.trackItem);
|
||||
},
|
||||
addChild(child) {
|
||||
let identifier = this.openmct.objects.makeKeyString(child.identifier);
|
||||
if (this.isTelemetry(child)) {
|
||||
if (!this.telemetryViewMap[identifier]) {
|
||||
this.addItem('telemetry-view', child);
|
||||
}
|
||||
} else if (!this.objectViewMap[identifier]) {
|
||||
this.addItem('subobject-view', child);
|
||||
}
|
||||
},
|
||||
removeChild(identifier) {
|
||||
// TODO: implement
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) {
|
||||
this.internalDomainObject = JSON.parse(JSON.stringify(obj));
|
||||
}.bind(this));
|
||||
|
||||
this.openmct.selection.on('change', this.setSelection);
|
||||
this.initializeItems();
|
||||
this.composition = this.openmct.composition.get(this.internalDomainObject);
|
||||
this.composition.on('add', this.addChild);
|
||||
this.composition.on('remove', this.removeChild);
|
||||
this.composition.load();
|
||||
},
|
||||
destroyed: function () {
|
||||
this.openmct.off('change', this.setSelection);
|
||||
this.composition.off('add', this.addChild);
|
||||
this.composition.off('remove', this.removeChild);
|
||||
this.unlisten();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
94
src/plugins/displayLayout/components/ImageView.vue
Normal file
94
src/plugins/displayLayout/components/ImageView.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||
<div class="c-image-view"
|
||||
:style="style">
|
||||
</div>
|
||||
</layout-frame>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~styles/sass-base';
|
||||
|
||||
.c-image-view {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
|
||||
.c-frame & {
|
||||
@include abs();
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
|
||||
export default {
|
||||
makeDefinition(openmct, gridSize, element) {
|
||||
return {
|
||||
stroke: 'transparent',
|
||||
x: 1,
|
||||
y: 1,
|
||||
width: 10,
|
||||
height: 5,
|
||||
url: element.url
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
index: Number,
|
||||
initSelect: Boolean
|
||||
},
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
backgroundImage: 'url(' + this.item.url + ')',
|
||||
border: '1px solid ' + this.item.stroke
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let context = {
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, context, this.initSelect);
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -21,21 +21,16 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-frame has-local-controls is-selectable is-moveable"
|
||||
:style="item.style"
|
||||
:class="classObject"
|
||||
@dblclick="drill(item.id, $event)">
|
||||
<div class="c-frame__header">
|
||||
<div class="c-frame__header__start">
|
||||
<div class="c-frame__name icon-object">{{ item.domainObject.name }}</div>
|
||||
<div class="c-frame__context-actions c-disclosure-button"></div>
|
||||
</div>
|
||||
<div class="c-frame__header__end">
|
||||
<div class="c-button icon-expand local-controls--hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<object-view class="c-frame__object-view"
|
||||
:object="item.domainObject"></object-view>
|
||||
<div class="l-layout__frame c-frame has-local-controls"
|
||||
:class="{
|
||||
'no-frame': !item.hasFrame,
|
||||
'u-inspectable': inspectable,
|
||||
'is-drilled-in': item.drilledIn
|
||||
}"
|
||||
:style="style"
|
||||
@dblclick="drill($event)">
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<!-- Drag handles -->
|
||||
<div class="c-frame-edit">
|
||||
@@ -60,68 +55,11 @@
|
||||
.c-frame {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-width: 1px;
|
||||
border-color: transparent;
|
||||
|
||||
/*************************** HEADER */
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: $interiorMargin;
|
||||
|
||||
> [class*="__"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
[class*="__start"] {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[class*="__end"] {
|
||||
//justify-content: flex-end;
|
||||
flex: 0 0 auto;
|
||||
|
||||
[class*="button"] {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
flex: 0 1 auto;
|
||||
font-size: 1.2em;
|
||||
|
||||
&:before {
|
||||
// Object type icon
|
||||
flex: 0 0 auto;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************** OBJECT VIEW */
|
||||
&__object-view {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
.c-object-view {
|
||||
.u-fills-container {
|
||||
// Expand component types that fill a container
|
||||
@include abs();
|
||||
}
|
||||
}
|
||||
}
|
||||
border: 1px solid transparent;
|
||||
|
||||
/*************************** NO-FRAME */
|
||||
&.no-frame {
|
||||
> [class*="__header"] {
|
||||
> [class*="contents"] > [class*="__header"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -131,15 +69,12 @@
|
||||
border: 1px solid $colorInteriorBorder;
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
|
||||
// Styles moved to _global.scss;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import ObjectView from '../../ui/components/layout/ObjectView.vue'
|
||||
import LayoutDrag from './LayoutDrag'
|
||||
import LayoutDrag from './../LayoutDrag'
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
@@ -147,41 +82,51 @@
|
||||
item: Object,
|
||||
gridSize: Array
|
||||
},
|
||||
components: {
|
||||
ObjectView
|
||||
data() {
|
||||
return {
|
||||
dragPosition: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
classObject: function () {
|
||||
return {
|
||||
'is-drilled-in': this.item.drilledIn,
|
||||
'no-frame': !this.item.hasFrame
|
||||
style() {
|
||||
let {x, y, width, height} = this.item;
|
||||
|
||||
if (this.dragPosition) {
|
||||
[x, y] = this.dragPosition.position;
|
||||
[width, height] = this.dragPosition.dimensions;
|
||||
}
|
||||
|
||||
return {
|
||||
left: (this.gridSize[0] * x) + 'px',
|
||||
top: (this.gridSize[1] * y) + 'px',
|
||||
width: (this.gridSize[0] * width) + 'px',
|
||||
height: (this.gridSize[1] * height) + 'px',
|
||||
minWidth: (this.gridSize[0] * width) + 'px',
|
||||
minHeight: (this.gridSize[1] * height) + 'px'
|
||||
};
|
||||
},
|
||||
inspectable() {
|
||||
return this.item.type === 'subobject-view' || this.item.type === 'telemetry-view';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
drill(id, $event) {
|
||||
drill($event) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
if (!this.isBeingEdited(this.item.domainObject)) {
|
||||
if (!this.openmct.editor.isEditing() || !this.item.identifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.openmct.composition.get(this.item.domainObject) === undefined) {
|
||||
return;
|
||||
}
|
||||
this.openmct.objects.get(this.item.identifier)
|
||||
.then(domainObject => {
|
||||
if (this.openmct.composition.get(domainObject) === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable for fixed position.
|
||||
if (this.item.domainObject.type === 'telemetry.fixed') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('drilledIn', id);
|
||||
},
|
||||
isBeingEdited(object) {
|
||||
// TODO: add logic when inEditContext() is implemented in Vue.
|
||||
return true;
|
||||
this.$emit('drilledIn', this.item);
|
||||
});
|
||||
},
|
||||
updatePosition(event) {
|
||||
let currentPosition = [event.pageX, event.pageY];
|
||||
@@ -194,43 +139,31 @@
|
||||
document.body.addEventListener('mousemove', this.continueDrag);
|
||||
document.body.addEventListener('mouseup', this.endDrag);
|
||||
|
||||
this.dragPosition = {
|
||||
position: [this.item.x, this.item.y],
|
||||
dimensions: [this.item.width, this.item.height]
|
||||
};
|
||||
this.updatePosition(event);
|
||||
this.activeDrag = new LayoutDrag(
|
||||
this.item.rawPosition,
|
||||
posFactor,
|
||||
dimFactor,
|
||||
this.gridSize
|
||||
);
|
||||
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
|
||||
event.preventDefault();
|
||||
},
|
||||
continueDrag(event) {
|
||||
event.preventDefault();
|
||||
this.updatePosition(event);
|
||||
|
||||
if (this.activeDrag) {
|
||||
this.$emit('dragInProgress', this.item.id, this.activeDrag.getAdjustedPosition(this.delta));
|
||||
}
|
||||
this.dragPosition = this.activeDrag.getAdjustedPosition(this.delta);
|
||||
},
|
||||
endDrag(event) {
|
||||
document.body.removeEventListener('mousemove', this.continueDrag);
|
||||
document.body.removeEventListener('mouseup', this.endDrag);
|
||||
this.continueDrag(event);
|
||||
this.$emit('endDrag', this.item.id);
|
||||
let [x, y] = this.dragPosition.position;
|
||||
let [width, height] = this.dragPosition.dimensions;
|
||||
this.$emit('endDrag', this.item, {x, y, width, height});
|
||||
this.dragPosition = undefined;
|
||||
this.initialPosition = undefined;
|
||||
this.delta = undefined;
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el,
|
||||
{
|
||||
item: this.item.domainObject
|
||||
},
|
||||
this.item.initSelect
|
||||
);
|
||||
},
|
||||
destroyed() {
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
231
src/plugins/displayLayout/components/LineView.vue
Normal file
231
src/plugins/displayLayout/components/LineView.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
/*****************************************************************************
|
||||
* 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="l-layout__frame c-frame has-local-controls no-frame"
|
||||
:style="style">
|
||||
<svg width="100%"
|
||||
height="100%">
|
||||
<line v-bind="linePosition"
|
||||
:stroke="item.stroke"
|
||||
stroke-width="2">
|
||||
</line>
|
||||
</svg>
|
||||
|
||||
<div class="c-frame-edit">
|
||||
<div class="c-frame-edit__move"
|
||||
@mousedown="startDrag($event)"></div>
|
||||
<div class="c-frame-edit__handle"
|
||||
:class="startHandleClass"
|
||||
@mousedown="startDrag($event, 'start')"></div>
|
||||
<div class="c-frame-edit__handle"
|
||||
:class="endHandleClass"
|
||||
@mousedown="startDrag($event, 'end')"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
const START_HANDLE_QUADRANTS = {
|
||||
1: 'c-frame-edit__handle--sw',
|
||||
2: 'c-frame-edit__handle--se',
|
||||
3: 'c-frame-edit__handle--ne',
|
||||
4: 'c-frame-edit__handle--nw'
|
||||
};
|
||||
|
||||
const END_HANDLE_QUADRANTS = {
|
||||
1: 'c-frame-edit__handle--ne',
|
||||
2: 'c-frame-edit__handle--nw',
|
||||
3: 'c-frame-edit__handle--sw',
|
||||
4: 'c-frame-edit__handle--se'
|
||||
};
|
||||
|
||||
export default {
|
||||
makeDefinition() {
|
||||
return {
|
||||
x: 5,
|
||||
y: 10,
|
||||
x2: 10,
|
||||
y2: 5,
|
||||
stroke: '#717171'
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
initSelect: Boolean,
|
||||
index: Number,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragPosition: undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
position() {
|
||||
let {x, y, x2, y2} = this.item;
|
||||
if (this.dragPosition) {
|
||||
({x, y, x2, y2} = this.dragPosition);
|
||||
}
|
||||
return {x, y, x2, y2};
|
||||
},
|
||||
style() {
|
||||
let {x, y, x2, y2} = this.position;
|
||||
let width = this.gridSize[0] * Math.abs(x - x2);
|
||||
let height = this.gridSize[1] * Math.abs(y - y2);
|
||||
let left = this.gridSize[0] * Math.min(x, x2);
|
||||
let top = this.gridSize[1] * Math.min(y, y2);
|
||||
return {
|
||||
left: `${left}px`,
|
||||
top: `${top}px`,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
minWidth: `${width}px`,
|
||||
minHeight: `${height}px`,
|
||||
position: 'absolute'
|
||||
};
|
||||
},
|
||||
startHandleClass() {
|
||||
return START_HANDLE_QUADRANTS[this.vectorQuadrant];
|
||||
},
|
||||
endHandleClass() {
|
||||
return END_HANDLE_QUADRANTS[this.vectorQuadrant];
|
||||
},
|
||||
vectorQuadrant() {
|
||||
let {x, y, x2, y2} = this.position;
|
||||
if (x2 > x) {
|
||||
if (y2 < y) {
|
||||
return 1;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
if (y2 < y) {
|
||||
return 2;
|
||||
}
|
||||
return 3;
|
||||
},
|
||||
linePosition() {
|
||||
if (this.vectorQuadrant === 1) {
|
||||
return {
|
||||
x1: '0%',
|
||||
y1: '100%',
|
||||
x2: '100%',
|
||||
y2: '0%'
|
||||
};
|
||||
}
|
||||
if (this.vectorQuadrant === 4) {
|
||||
return {
|
||||
x1: '0%',
|
||||
y1: '0%',
|
||||
x2: '100%',
|
||||
y2: '100%'
|
||||
};
|
||||
}
|
||||
if (this.vectorQuadrant === 2) {
|
||||
return {
|
||||
x1: '0%',
|
||||
y1: '0%',
|
||||
x2: '100%',
|
||||
y2: '100%'
|
||||
};
|
||||
}
|
||||
if (this.vectorQuadrant === 3) {
|
||||
return {
|
||||
x1: '100%',
|
||||
y1: '0%',
|
||||
x2: '0%',
|
||||
y2: '100%'
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
startDrag(event, position) {
|
||||
this.dragging = position;
|
||||
document.body.addEventListener('mousemove', this.continueDrag);
|
||||
document.body.addEventListener('mouseup', this.endDrag);
|
||||
this.startPosition = [event.pageX, event.pageY];
|
||||
this.dragPosition = {
|
||||
x: this.item.x,
|
||||
y: this.item.y,
|
||||
x2: this.item.x2,
|
||||
y2: this.item.y2
|
||||
};
|
||||
event.preventDefault();
|
||||
},
|
||||
continueDrag(event) {
|
||||
event.preventDefault();
|
||||
let pxDeltaX = this.startPosition[0] - event.pageX;
|
||||
let pxDeltaY = this.startPosition[1] - event.pageY;
|
||||
this.dragPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
|
||||
},
|
||||
endDrag(event) {
|
||||
document.body.removeEventListener('mousemove', this.continueDrag);
|
||||
document.body.removeEventListener('mouseup', this.endDrag);
|
||||
let {x, y, x2, y2} = this.dragPosition;
|
||||
this.$emit('endDrag', this.item, {x, y, x2, y2});
|
||||
this.dragPosition = undefined;
|
||||
this.dragging = undefined;
|
||||
event.preventDefault();
|
||||
},
|
||||
calculateDragPosition(pxDeltaX, pxDeltaY) {
|
||||
let gridDeltaX = Math.round(pxDeltaX / this.gridSize[0]);
|
||||
let gridDeltaY = Math.round(pxDeltaY / this.gridSize[0]); // TODO: should this be gridSize[1]?
|
||||
let {x, y, x2, y2} = this.item;
|
||||
let dragPosition = {x, y, x2, y2};
|
||||
if (this.dragging === 'start') {
|
||||
dragPosition.x -= gridDeltaX;
|
||||
dragPosition.y -= gridDeltaY;
|
||||
} else if (this.dragging === 'end') {
|
||||
dragPosition.x2 -= gridDeltaX;
|
||||
dragPosition.y2 -= gridDeltaY;
|
||||
} else {
|
||||
// dragging entire line.
|
||||
dragPosition.x -= gridDeltaX;
|
||||
dragPosition.y -= gridDeltaY;
|
||||
dragPosition.x2 -= gridDeltaX;
|
||||
dragPosition.y2 -= gridDeltaY;
|
||||
}
|
||||
return dragPosition;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let context = {
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el,
|
||||
context,
|
||||
this.initSelect
|
||||
);
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
112
src/plugins/displayLayout/components/SubobjectView.vue
Normal file
112
src/plugins/displayLayout/components/SubobjectView.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)"
|
||||
@drilledIn="item => $emit('drilledIn', item)">
|
||||
<object-frame v-if="domainObject"
|
||||
:domain-object="domainObject"
|
||||
:object-path="objectPath"
|
||||
:has-frame="item.hasFrame">
|
||||
</object-frame>
|
||||
</layout-frame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ObjectFrame from '../../../ui/components/ObjectFrame.vue'
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
|
||||
const MINIMUM_FRAME_SIZE = [320, 180],
|
||||
DEFAULT_DIMENSIONS = [10, 10],
|
||||
DEFAULT_POSITION = [1, 1],
|
||||
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
|
||||
|
||||
function getDefaultDimensions(gridSize) {
|
||||
return MINIMUM_FRAME_SIZE.map((min, index) => {
|
||||
return Math.max(
|
||||
Math.ceil(min / gridSize[index]),
|
||||
DEFAULT_DIMENSIONS[index]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function hasFrameByDefault(type) {
|
||||
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
||||
}
|
||||
|
||||
export default {
|
||||
makeDefinition(openmct, gridSize, domainObject, position) {
|
||||
let defaultDimensions = getDefaultDimensions(gridSize);
|
||||
position = position || DEFAULT_POSITION;
|
||||
|
||||
return {
|
||||
width: defaultDimensions[0],
|
||||
height: defaultDimensions[1],
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
identifier: domainObject.identifier,
|
||||
hasFrame: hasFrameByDefault(domainObject.type)
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
initSelect: Boolean,
|
||||
index: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainObject: undefined,
|
||||
objectPath: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ObjectFrame,
|
||||
LayoutFrame
|
||||
},
|
||||
methods: {
|
||||
setObject(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.objectPath = [this.domainObject].concat(this.openmct.router.path);
|
||||
let context = {
|
||||
item: domainObject,
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, context, this.initSelect);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.item);
|
||||
this.openmct.objects.get(this.item.identifier)
|
||||
.then(this.setObject);
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
233
src/plugins/displayLayout/components/TelemetryView.vue
Normal file
233
src/plugins/displayLayout/components/TelemetryView.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||
<div class="c-telemetry-view"
|
||||
:style="styleObject"
|
||||
v-if="domainObject">
|
||||
<div v-if="showLabel"
|
||||
class="c-telemetry-view__label">
|
||||
<div class="c-telemetry-view__label-text">{{ domainObject.name }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showValue"
|
||||
:title="fieldName"
|
||||
class="c-telemetry-view__value"
|
||||
:class="[telemetryClass]">
|
||||
<div class="c-telemetry-view__value-text">{{ telemetryValue }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</layout-frame>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~styles/sass-base';
|
||||
|
||||
.c-telemetry-view {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
> * {
|
||||
// Label and value holders
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
// justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding: $interiorMargin;
|
||||
|
||||
> * {
|
||||
// Text elements
|
||||
@include ellipsize();
|
||||
}
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
.c-frame & {
|
||||
@include abs();
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
|
||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
|
||||
DEFAULT_POSITION = [1, 1];
|
||||
|
||||
export default {
|
||||
makeDefinition(openmct, gridSize, domainObject, position) {
|
||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
||||
position = position || DEFAULT_POSITION;
|
||||
|
||||
return {
|
||||
identifier: domainObject.identifier,
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
width: DEFAULT_TELEMETRY_DIMENSIONS[0],
|
||||
height: DEFAULT_TELEMETRY_DIMENSIONS[1],
|
||||
displayMode: 'all',
|
||||
value: metadata.getDefaultDisplayValue(),
|
||||
stroke: "transparent",
|
||||
fill: "",
|
||||
color: "",
|
||||
size: "13px",
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
initSelect: Boolean,
|
||||
index: Number
|
||||
},
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
computed: {
|
||||
showLabel() {
|
||||
let displayMode = this.item.displayMode;
|
||||
return displayMode === 'all' || displayMode === 'label';
|
||||
},
|
||||
showValue() {
|
||||
let displayMode = this.item.displayMode;
|
||||
return displayMode === 'all' || displayMode === 'value';
|
||||
},
|
||||
styleObject() {
|
||||
return {
|
||||
backgroundColor: this.item.fill,
|
||||
borderColor: this.item.stroke,
|
||||
color: this.item.color,
|
||||
fontSize: this.item.size
|
||||
}
|
||||
},
|
||||
fieldName() {
|
||||
return this.valueMetadata && this.valueMetadata.name;
|
||||
},
|
||||
valueMetadata() {
|
||||
return this.datum && this.metadata.value(this.item.value);
|
||||
},
|
||||
valueFormatter() {
|
||||
return this.formats[this.item.value];
|
||||
},
|
||||
telemetryValue() {
|
||||
if (!this.datum) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.valueFormatter && this.valueFormatter.format(this.datum);
|
||||
},
|
||||
telemetryClass() {
|
||||
if (!this.datum) {
|
||||
return;
|
||||
}
|
||||
|
||||
let alarm = this.limitEvaluator && this.limitEvaluator.evaluate(this.datum, this.valueMetadata);
|
||||
return alarm && alarm.cssClass;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
datum: undefined,
|
||||
formats: undefined,
|
||||
domainObject: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
requestHistoricalData() {
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let options = {
|
||||
start: bounds.start,
|
||||
end: bounds.end,
|
||||
size: 1
|
||||
};
|
||||
this.openmct.telemetry.request(this.domainObject, options)
|
||||
.then(data => {
|
||||
if (data.length > 0) {
|
||||
this.updateView(data[data.length - 1]);
|
||||
}
|
||||
});
|
||||
},
|
||||
subscribeToObject() {
|
||||
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
|
||||
if (this.openmct.time.clock() !== undefined) {
|
||||
this.updateView(datum);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
updateView(datum) {
|
||||
this.datum = datum;
|
||||
},
|
||||
removeSubscription() {
|
||||
if (this.subscription) {
|
||||
this.subscription();
|
||||
this.subscription = undefined;
|
||||
}
|
||||
},
|
||||
refreshData(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
this.datum = undefined;
|
||||
this.requestHistoricalData(this.domainObject);
|
||||
}
|
||||
},
|
||||
setObject(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
|
||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||
this.requestHistoricalData();
|
||||
this.subscribeToObject();
|
||||
|
||||
let context = {
|
||||
item: domainObject,
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, context, this.initSelect);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.objects.get(this.item.identifier)
|
||||
.then(this.setObject);
|
||||
this.openmct.time.on("bounds", this.refreshData);
|
||||
},
|
||||
destroyed() {
|
||||
this.removeSubscription();
|
||||
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
|
||||
this.openmct.time.off("bounds", this.refreshData);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
100
src/plugins/displayLayout/components/TextView.vue
Normal file
100
src/plugins/displayLayout/components/TextView.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||
<div class="c-text-view"
|
||||
:style="style">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</layout-frame>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~styles/sass-base';
|
||||
|
||||
.c-text-view {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
.c-frame & {
|
||||
@include abs();
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
|
||||
export default {
|
||||
makeDefinition(openmct, gridSize, element) {
|
||||
return {
|
||||
fill: 'transparent',
|
||||
stroke: 'transparent',
|
||||
size: '13px',
|
||||
color: '',
|
||||
x: 1,
|
||||
y: 1,
|
||||
width: 10,
|
||||
height: 5,
|
||||
text: element.text
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
index: Number,
|
||||
initSelect: Boolean
|
||||
},
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
backgroundColor: this.item.fill,
|
||||
borderColor: this.item.stroke,
|
||||
color: this.item.color,
|
||||
fontSize: this.item.size
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let context = {
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, context, this.initSelect);
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -20,10 +20,11 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import Layout from './DisplayLayout.vue'
|
||||
import Layout from './components/DisplayLayout.vue'
|
||||
import Vue from 'vue'
|
||||
import objectUtils from '../../api/objects/object-utils.js'
|
||||
import DisplayLayoutType from './DisplayLayoutType.js'
|
||||
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'
|
||||
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
@@ -40,7 +41,7 @@ export default function () {
|
||||
components: {
|
||||
Layout
|
||||
},
|
||||
template: '<layout :domain-object="domainObject"></layout>',
|
||||
template: '<layout ref="displayLayout" :domain-object="domainObject"></layout>',
|
||||
provide: {
|
||||
openmct,
|
||||
objectUtils
|
||||
@@ -53,6 +54,12 @@ export default function () {
|
||||
}
|
||||
});
|
||||
},
|
||||
getSelectionContext() {
|
||||
return {
|
||||
item: domainObject,
|
||||
addElement: component && component.$refs.displayLayout.addElement
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
component.$destroy();
|
||||
}
|
||||
@@ -63,6 +70,7 @@ export default function () {
|
||||
}
|
||||
});
|
||||
openmct.types.addType('layout', DisplayLayoutType());
|
||||
openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct));
|
||||
openmct.composition.addPolicy((parent, child) => {
|
||||
if (parent.type === 'layout' && child.type === 'folder') {
|
||||
return false;
|
||||
@@ -70,41 +78,5 @@ export default function () {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
openmct.toolbars.addProvider({
|
||||
name: "Display Layout Toolbar",
|
||||
key: "layout",
|
||||
description: "A toolbar for objects inside a display layout.",
|
||||
forSelection: function (selection) {
|
||||
// Apply the layout toolbar if the selected object is inside a layout,
|
||||
// and in edit mode.
|
||||
return (selection &&
|
||||
selection[1] &&
|
||||
selection[1].context.item &&
|
||||
selection[1].context.item.type === 'layout' &&
|
||||
openmct.editor.isEditing());
|
||||
},
|
||||
toolbar: function (selection) {
|
||||
let id = openmct.objects.makeKeyString(selection[0].context.item.identifier);
|
||||
return [
|
||||
{
|
||||
control: "toggle-button",
|
||||
domainObject: selection[1].context.item,
|
||||
property: "configuration.layout.panels[" + id + "].hasFrame",
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-frame-hide',
|
||||
title: "Hide frame"
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-frame-show',
|
||||
title: "Show frame"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
203
src/plugins/flexibleLayout/components/container.vue
Normal file
203
src/plugins/flexibleLayout/components/container.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
/*****************************************************************************
|
||||
* 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-fl-container"
|
||||
:style="[{'flex-basis': sizeString}]"
|
||||
:class="{'is-empty': !frames.length}">
|
||||
<div class="c-fl-container__header icon-grippy-ew"
|
||||
v-show="isEditing"
|
||||
draggable="true"
|
||||
@dragstart="startContainerDrag">
|
||||
<span class="c-fl-container__size-indicator">{{ sizeString }}</span>
|
||||
</div>
|
||||
|
||||
<drop-hint
|
||||
class="c-fl-frame__drop-hint"
|
||||
:index="-1"
|
||||
:allow-drop="allowDrop"
|
||||
@object-drop-to="moveOrCreateFrame">
|
||||
</drop-hint>
|
||||
|
||||
<div class="c-fl-container__frames-holder">
|
||||
<template
|
||||
v-for="(frame, i) in frames">
|
||||
|
||||
<frame-component
|
||||
class="c-fl-container__frame"
|
||||
:key="frame.id"
|
||||
:frame="frame"
|
||||
:index="i"
|
||||
:containerIndex="index">
|
||||
</frame-component>
|
||||
|
||||
<drop-hint
|
||||
class="c-fl-frame__drop-hint"
|
||||
:key="i"
|
||||
:index="i"
|
||||
:allowDrop="allowDrop"
|
||||
@object-drop-to="moveOrCreateFrame">
|
||||
</drop-hint>
|
||||
|
||||
<resize-handle
|
||||
v-if="(i !== frames.length - 1)"
|
||||
:key="i"
|
||||
:index="i"
|
||||
:orientation="rowsLayout ? 'horizontal' : 'vertical'"
|
||||
@init-move="startFrameResizing"
|
||||
@move="frameResizing"
|
||||
@end-move="endFrameResizing">
|
||||
</resize-handle>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FrameComponent from './frame.vue';
|
||||
import Frame from '../utils/frame';
|
||||
import ResizeHandle from './resizeHandle.vue';
|
||||
import DropHint from './dropHint.vue';
|
||||
import isEditingMixin from '../mixins/isEditing';
|
||||
|
||||
const MIN_FRAME_SIZE = 5;
|
||||
|
||||
export default {
|
||||
inject:['openmct'],
|
||||
props: ['container', 'index', 'rowsLayout'],
|
||||
mixins: [isEditingMixin],
|
||||
components: {
|
||||
FrameComponent,
|
||||
ResizeHandle,
|
||||
DropHint
|
||||
},
|
||||
computed: {
|
||||
frames() {
|
||||
return this.container.frames;
|
||||
},
|
||||
sizeString() {
|
||||
return `${Math.round(this.container.size)}%`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
allowDrop(event, index) {
|
||||
if (event.dataTransfer.getData('openmct/domain-object')) {
|
||||
return true;
|
||||
}
|
||||
let frameId = event.dataTransfer.getData('frameid'),
|
||||
containerIndex = Number(event.dataTransfer.getData('containerIndex'));
|
||||
|
||||
if (!frameId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (containerIndex === this.index) {
|
||||
let frame = this.container.frames.filter((f) => f.id === frameId)[0],
|
||||
framePos = this.container.frames.indexOf(frame);
|
||||
|
||||
if (index === -1) {
|
||||
return framePos !== 0;
|
||||
} else {
|
||||
return framePos !== index && (framePos - 1) !== index
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
moveOrCreateFrame(insertIndex, event) {
|
||||
if (event.dataTransfer.types.includes('openmct/domain-object')) {
|
||||
// create frame using domain object
|
||||
let domainObject = JSON.parse(event.dataTransfer.getData('openmct/domain-object'));
|
||||
this.$emit(
|
||||
'create-frame',
|
||||
this.index,
|
||||
insertIndex,
|
||||
domainObject.identifier
|
||||
);
|
||||
return;
|
||||
};
|
||||
// move frame.
|
||||
let frameId = event.dataTransfer.getData('frameid');
|
||||
let containerIndex = Number(event.dataTransfer.getData('containerIndex'));
|
||||
this.$emit(
|
||||
'move-frame',
|
||||
this.index,
|
||||
insertIndex,
|
||||
frameId,
|
||||
containerIndex
|
||||
);
|
||||
},
|
||||
startFrameResizing(index) {
|
||||
let beforeFrame = this.frames[index],
|
||||
afterFrame = this.frames[index + 1];
|
||||
|
||||
this.maxMoveSize = beforeFrame.size + afterFrame.size;
|
||||
},
|
||||
frameResizing(index, delta, event) {
|
||||
let percentageMoved = Math.round(delta / this.getElSize() * 100),
|
||||
beforeFrame = this.frames[index],
|
||||
afterFrame = this.frames[index + 1];
|
||||
|
||||
beforeFrame.size = this.getFrameSize(beforeFrame.size + percentageMoved);
|
||||
afterFrame.size = this.getFrameSize(afterFrame.size - percentageMoved);
|
||||
},
|
||||
endFrameResizing(index, event) {
|
||||
this.persist();
|
||||
},
|
||||
getElSize() {
|
||||
if (this.rowsLayout) {
|
||||
return this.$el.offsetWidth;
|
||||
} else {
|
||||
return this.$el.offsetHeight;
|
||||
}
|
||||
},
|
||||
getFrameSize(size) {
|
||||
if (size < MIN_FRAME_SIZE) {
|
||||
return MIN_FRAME_SIZE
|
||||
} else if (size > (this.maxMoveSize - MIN_FRAME_SIZE)) {
|
||||
return (this.maxMoveSize - MIN_FRAME_SIZE);
|
||||
} else {
|
||||
return size;
|
||||
}
|
||||
},
|
||||
persist() {
|
||||
this.$emit('persist', this.index);
|
||||
},
|
||||
startContainerDrag(event) {
|
||||
event.dataTransfer.setData('containerid', this.container.id);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let context = {
|
||||
item: this.$parent.domainObject,
|
||||
addContainer: this.addContainer,
|
||||
type: 'container',
|
||||
containerId: this.container.id
|
||||
}
|
||||
|
||||
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, false);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unsubscribeSelection();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
80
src/plugins/flexibleLayout/components/dropHint.vue
Normal file
80
src/plugins/flexibleLayout/components/dropHint.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
/*****************************************************************************
|
||||
* 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 v-show="isValidTarget">
|
||||
<div class="c-drop-hint c-drop-hint--always-show"
|
||||
:class="{'is-mouse-over': isMouseOver}"
|
||||
@dragenter="dragenter"
|
||||
@dragleave="dragleave"
|
||||
@drop="dropHandler">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props:{
|
||||
index: Number,
|
||||
allowDrop: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isMouseOver: false,
|
||||
isValidTarget: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dragenter() {
|
||||
this.isMouseOver = true;
|
||||
},
|
||||
dragleave() {
|
||||
this.isMouseOver = false;
|
||||
},
|
||||
dropHandler(event) {
|
||||
this.$emit('object-drop-to', this.index, event);
|
||||
this.isValidTarget = false;
|
||||
},
|
||||
dragstart(event) {
|
||||
this.isValidTarget = this.allowDrop(event, this.index);
|
||||
},
|
||||
dragend() {
|
||||
this.isValidTarget = false;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('dragstart', this.dragstart);
|
||||
document.addEventListener('dragend', this.dragend);
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('dragstart', this.dragstart);
|
||||
document.removeEventListener('dragend', this.dragend);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
624
src/plugins/flexibleLayout/components/flexibleLayout.vue
Normal file
624
src/plugins/flexibleLayout/components/flexibleLayout.vue
Normal file
@@ -0,0 +1,624 @@
|
||||
/*****************************************************************************
|
||||
* 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-fl">
|
||||
<div class="c-fl__empty"
|
||||
v-if="areAllContainersEmpty()">
|
||||
<span class="c-fl__empty-message">This Flexible Layout is currently empty</span>
|
||||
</div>
|
||||
|
||||
<div class="c-fl__container-holder"
|
||||
:class="{
|
||||
'c-fl--rows': rowsLayout === true
|
||||
}">
|
||||
|
||||
<template v-for="(container, index) in containers">
|
||||
|
||||
<drop-hint
|
||||
class="c-fl-frame__drop-hint"
|
||||
v-if="index === 0 && containers.length > 1"
|
||||
:key="index"
|
||||
:index="-1"
|
||||
:allow-drop="allowContainerDrop"
|
||||
@object-drop-to="moveContainer">
|
||||
</drop-hint>
|
||||
|
||||
<container-component
|
||||
class="c-fl__container"
|
||||
:key="container.id"
|
||||
:index="index"
|
||||
:container="container"
|
||||
:rowsLayout="rowsLayout"
|
||||
@move-frame="moveFrame"
|
||||
@create-frame="createFrame"
|
||||
@persist="persist">
|
||||
</container-component>
|
||||
|
||||
<resize-handle
|
||||
v-if="index !== (containers.length - 1)"
|
||||
:key="index"
|
||||
:index="index"
|
||||
:orientation="rowsLayout ? 'vertical' : 'horizontal'"
|
||||
@init-move="startContainerResizing"
|
||||
@move="containerResizing"
|
||||
@end-move="endContainerResizing">
|
||||
</resize-handle>
|
||||
|
||||
<drop-hint
|
||||
class="c-fl-frame__drop-hint"
|
||||
v-if="containers.length > 1"
|
||||
:key="index"
|
||||
:index="index"
|
||||
:allowDrop="allowContainerDrop"
|
||||
@object-drop-to="moveContainer">
|
||||
</drop-hint>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~styles/sass-base';
|
||||
|
||||
.c-fl {
|
||||
@include abs();
|
||||
display: flex;
|
||||
flex-direction: column; // TEMP: only needed to support temp-toolbar element
|
||||
|
||||
> * + * { margin-top: $interiorMargin; }
|
||||
|
||||
.temp-toolbar {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__container-holder {
|
||||
display: flex;
|
||||
flex: 1 1 100%; // Must needs to be 100% to work
|
||||
|
||||
// Columns by default
|
||||
flex-direction: row;
|
||||
> * + * { margin-left: 1px; }
|
||||
|
||||
&[class*='--rows'] {
|
||||
//@include test(blue, 0.1);
|
||||
flex-direction: column;
|
||||
> * + * {
|
||||
margin-left: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__empty {
|
||||
@include abs();
|
||||
background: rgba($colorBodyFg, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
> * {
|
||||
font-style: italic;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-fl-container {
|
||||
/***************************************************** CONTAINERS */
|
||||
$headerSize: 16px;
|
||||
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
// flex-basis is set with inline style in code, controls size
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
&__header {
|
||||
// Only displayed when editing
|
||||
background: $editSelectableColor;
|
||||
color: $editSelectableColorFg;
|
||||
cursor: move;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 $headerSize;
|
||||
|
||||
&:before {
|
||||
// Drag grippy
|
||||
font-size: 0.8em;
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
left: 50%; top: 50%;
|
||||
transform-origin: center;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__size-indicator {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
right: $interiorMargin;
|
||||
}
|
||||
|
||||
&__frames-holder {
|
||||
display: flex;
|
||||
flex: 1 1 100%; // Must be 100% to work
|
||||
flex-direction: column; // Default
|
||||
align-content: stretch;
|
||||
align-items: stretch;
|
||||
overflow: hidden; // This sucks, but doing in the short-term
|
||||
}
|
||||
|
||||
.is-editing & {
|
||||
//background: $editCanvasColorBg;
|
||||
border-color: $editSelectableColor;
|
||||
|
||||
&:hover {
|
||||
border-color: $editSelectableColorHov;
|
||||
}
|
||||
|
||||
&[s-selected] {
|
||||
border-color: $editSelectableColorSelected;
|
||||
|
||||
.c-fl-container__header {
|
||||
background: $editSelectableColorSelected;
|
||||
color: $editSelectableColorSelectedFg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****** THEIR FRAMES */
|
||||
// Frames get styled here because this is particular to their presence in this layout type
|
||||
.c-fl-frame {
|
||||
@include browserPrefix(margin-collapse, collapse);
|
||||
margin: 1px;
|
||||
|
||||
//&__drag-wrapper {
|
||||
// border: 1px solid $colorInteriorBorder; // Now handled by is-selectable
|
||||
//}
|
||||
}
|
||||
|
||||
/****** ROWS LAYOUT */
|
||||
.c-fl--rows & {
|
||||
// Layout is rows
|
||||
flex-direction: row;
|
||||
|
||||
&__header {
|
||||
flex-basis: $headerSize;
|
||||
overflow: hidden;
|
||||
|
||||
&:before {
|
||||
// Drag grippy
|
||||
transform: rotate(90deg) translate(-50%, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__size-indicator {
|
||||
right: 0;
|
||||
top: $interiorMargin;
|
||||
transform-origin: top right;
|
||||
transform: rotate(-90deg) translateY(-100%);
|
||||
}
|
||||
|
||||
&__frames-holder {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-fl-frame {
|
||||
/***************************************************** CONTAINER FRAMES */
|
||||
$sizeIndicatorM: 16px;
|
||||
$dropHintSize: 15px;
|
||||
|
||||
display: flex;
|
||||
// justify-content: stretch;
|
||||
// align-items: stretch;
|
||||
flex: 1 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden; // Needed to allow frames to collapse when sized down
|
||||
|
||||
&__drag-wrapper {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
.is-editing & {
|
||||
> * {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: $interiorMargin;
|
||||
}
|
||||
|
||||
&__object-view {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__size-indicator {
|
||||
$size: 35px;
|
||||
|
||||
@include ellipsize();
|
||||
background: $colorBtnBg;
|
||||
border-top-left-radius: $controlCr;
|
||||
color: $colorBtnFg;
|
||||
display: inline-block;
|
||||
padding: $interiorMarginSm 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
text-align: center;
|
||||
width: $size;
|
||||
z-index: 2;
|
||||
|
||||
// Changed when layout is different, see below
|
||||
border-top-right-radius: $controlCr;
|
||||
bottom: 1px;
|
||||
right: $sizeIndicatorM;
|
||||
}
|
||||
|
||||
&__drop-hint {
|
||||
flex: 0 0 $dropHintSize;
|
||||
.c-drop-hint {
|
||||
border-radius: $smallCr;
|
||||
}
|
||||
}
|
||||
|
||||
&__resize-handle {
|
||||
$size: 2px;
|
||||
$margin: 3px;
|
||||
$marginHov: 0;
|
||||
$grippyThickness: $size + 6;
|
||||
$grippyLen: $grippyThickness * 2;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 0 ($margin * 2) + $size;
|
||||
transition: $transOut;
|
||||
|
||||
&:before {
|
||||
// The visible resize line
|
||||
background: $editColor;
|
||||
content: '';
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
min-height: $size; min-width: $size;
|
||||
}
|
||||
|
||||
&__grippy {
|
||||
// Grippy element
|
||||
$d: 4px;
|
||||
$c: black;
|
||||
$a: 0.9;
|
||||
$d: 5px;
|
||||
background: $editColor;
|
||||
color: $editColorBg;
|
||||
border-radius: $smallCr;
|
||||
font-size: 0.8em;
|
||||
height: $d;
|
||||
width: $d * 10;
|
||||
position: absolute;
|
||||
left: 50%; top: 50%;
|
||||
text-align: center;
|
||||
transform-origin: center center;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
padding: $margin $size;
|
||||
&:hover{
|
||||
// padding: $marginHov 0;
|
||||
cursor: row-resize;
|
||||
}
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
padding: $size $margin;
|
||||
&:hover{
|
||||
// padding: 0 $marginHov;
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
[class*='grippy'] {
|
||||
transform: translate(-50%) rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition: $transOut;
|
||||
&:before {
|
||||
// The visible resize line
|
||||
background: $editColorHov;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the resize-handles in first and last c-fl-frame elements
|
||||
&:first-child,
|
||||
&:last-child {
|
||||
.c-fl-frame__resize-handle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.c-fl--rows & {
|
||||
flex-direction: row;
|
||||
|
||||
&__size-indicator {
|
||||
border-bottom-left-radius: $controlCr;
|
||||
border-top-right-radius: 0;
|
||||
bottom: $sizeIndicatorM;
|
||||
right: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&--first-in-container {
|
||||
border: none;
|
||||
flex: 0 0 0;
|
||||
.c-fl-frame__drag-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-dragging {
|
||||
flex-basis: $dropHintSize;
|
||||
}
|
||||
}
|
||||
|
||||
.is-empty & {
|
||||
&.c-fl-frame--first-in-container {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__drop-hint {
|
||||
flex: 1 0 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.c-object-view {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ContainerComponent from './container.vue';
|
||||
import Container from '../utils/container';
|
||||
import Frame from '../utils/frame';
|
||||
import ResizeHandle from './resizeHandle.vue';
|
||||
import DropHint from './dropHint.vue';
|
||||
import isEditingMixin from '../mixins/isEditing';
|
||||
|
||||
const MIN_CONTAINER_SIZE = 5;
|
||||
|
||||
// Resize items so that newItem fits proportionally (newItem must be an element
|
||||
// of items). If newItem does not have a size or is sized at 100%, newItem will
|
||||
// have size set to 1/n * 100, where n is the total number of items.
|
||||
function sizeItems(items, newItem) {
|
||||
if (items.length === 1) {
|
||||
newItem.size = 100;
|
||||
} else {
|
||||
if (!newItem.size || newItem.size === 100) {
|
||||
newItem.size = Math.round(100 / items.length);
|
||||
}
|
||||
let oldItems = items.filter(item => item !== newItem);
|
||||
// Resize oldItems to fit inside remaining space;
|
||||
let remainder = 100 - newItem.size;
|
||||
oldItems.forEach((item) => {
|
||||
item.size = Math.round(item.size * remainder / 100);
|
||||
});
|
||||
// Ensure items add up to 100 in case of rounding error.
|
||||
let total = items.reduce((t, item) => t + item.size, 0);
|
||||
let excess = Math.round(100 - total);
|
||||
oldItems[oldItems.length - 1].size += excess;
|
||||
}
|
||||
}
|
||||
|
||||
// Scales items proportionally so total is equal to 100. Assumes that an item
|
||||
// was removed from array.
|
||||
function sizeToFill(items) {
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
let oldTotal = items.reduce((total, item) => total + item.size, 0);
|
||||
items.forEach((item) => {
|
||||
item.size = Math.round(item.size * 100 / oldTotal);
|
||||
});
|
||||
// Ensure items add up to 100 in case of rounding error.
|
||||
let total = items.reduce((t, item) => t + item.size, 0);
|
||||
let excess = Math.round(100 - total);
|
||||
items[items.length - 1].size += excess;
|
||||
}
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'layoutObject'],
|
||||
mixins: [isEditingMixin],
|
||||
components: {
|
||||
ContainerComponent,
|
||||
ResizeHandle,
|
||||
DropHint
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainObject: this.layoutObject
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
layoutDirectionStr() {
|
||||
if (this.rowsLayout) {
|
||||
return 'Rows'
|
||||
} else {
|
||||
return 'Columns'
|
||||
}
|
||||
},
|
||||
containers() {
|
||||
return this.domainObject.configuration.containers;
|
||||
},
|
||||
rowsLayout() {
|
||||
return this.domainObject.configuration.rowsLayout;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
areAllContainersEmpty() {
|
||||
return !!!this.containers.filter(container => container.frames.length).length;
|
||||
},
|
||||
addContainer() {
|
||||
let container = new Container();
|
||||
this.containers.push(container);
|
||||
sizeItems(this.containers, container);
|
||||
this.persist();
|
||||
},
|
||||
deleteContainer(containerId) {
|
||||
let container = this.containers.filter(c => c.id === containerId)[0];
|
||||
let containerIndex = this.containers.indexOf(container);
|
||||
this.containers.splice(containerIndex, 1);
|
||||
sizeToFill(this.containers);
|
||||
this.persist();
|
||||
},
|
||||
moveFrame(toContainerIndex, toFrameIndex, frameId, fromContainerIndex) {
|
||||
let toContainer = this.containers[toContainerIndex];
|
||||
let fromContainer = this.containers[fromContainerIndex];
|
||||
let frame = fromContainer.frames.filter(f => f.id === frameId)[0];
|
||||
let fromIndex = fromContainer.frames.indexOf(frame);
|
||||
fromContainer.frames.splice(fromIndex, 1);
|
||||
sizeToFill(fromContainer.frames);
|
||||
toContainer.frames.splice(toFrameIndex + 1, 0, frame);
|
||||
sizeItems(toContainer.frames, frame);
|
||||
this.persist();
|
||||
},
|
||||
createFrame(containerIndex, insertFrameIndex, objectIdentifier) {
|
||||
let frame = new Frame(objectIdentifier);
|
||||
let container = this.containers[containerIndex];
|
||||
container.frames.splice(insertFrameIndex + 1, 0, frame);
|
||||
sizeItems(container.frames, frame);
|
||||
this.persist();
|
||||
},
|
||||
deleteFrame(frameId) {
|
||||
let container = this.containers
|
||||
.filter(c => c.frames.some(f => f.id === frameId))[0];
|
||||
let containerIndex = this.containers.indexOf(container);
|
||||
let frame = container
|
||||
.frames
|
||||
.filter((f => f.id === frameId))[0];
|
||||
let frameIndex = container.frames.indexOf(frame);
|
||||
container.frames.splice(frameIndex, 1);
|
||||
sizeToFill(container.frames);
|
||||
this.persist(containerIndex);
|
||||
},
|
||||
allowContainerDrop(event, index) {
|
||||
if (!event.dataTransfer.types.includes('containerid')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let containerId = event.dataTransfer.getData('containerid'),
|
||||
container = this.containers.filter((c) => c.id === containerId)[0],
|
||||
containerPos = this.containers.indexOf(container);
|
||||
|
||||
if (index === -1) {
|
||||
return containerPos !== 0;
|
||||
} else {
|
||||
return containerPos !== index && (containerPos - 1) !== index
|
||||
}
|
||||
},
|
||||
persist(index){
|
||||
if (index) {
|
||||
this.openmct.objects.mutate(this.domainObject, `configuration.containers[${index}]`, this.containers[index]);
|
||||
} else {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.containers', this.containers);
|
||||
}
|
||||
},
|
||||
startContainerResizing(index) {
|
||||
let beforeContainer = this.containers[index],
|
||||
afterContainer = this.containers[index + 1];
|
||||
|
||||
this.maxMoveSize = beforeContainer.size + afterContainer.size;
|
||||
},
|
||||
containerResizing(index, delta, event) {
|
||||
let percentageMoved = Math.round(delta / this.getElSize() * 100),
|
||||
beforeContainer = this.containers[index],
|
||||
afterContainer = this.containers[index + 1];
|
||||
|
||||
beforeContainer.size = this.getContainerSize(beforeContainer.size + percentageMoved);
|
||||
afterContainer.size = this.getContainerSize(afterContainer.size - percentageMoved);
|
||||
},
|
||||
endContainerResizing(event) {
|
||||
this.persist();
|
||||
},
|
||||
getElSize() {
|
||||
if (this.rowsLayout) {
|
||||
return this.$el.offsetHeight;
|
||||
} else {
|
||||
return this.$el.offsetWidth;
|
||||
}
|
||||
},
|
||||
getContainerSize(size) {
|
||||
if (size < MIN_CONTAINER_SIZE) {
|
||||
return MIN_CONTAINER_SIZE
|
||||
} else if (size > (this.maxMoveSize - MIN_CONTAINER_SIZE)) {
|
||||
return (this.maxMoveSize - MIN_CONTAINER_SIZE);
|
||||
} else {
|
||||
return size;
|
||||
}
|
||||
},
|
||||
updateDomainObject(newDomainObject) {
|
||||
this.domainObject = newDomainObject;
|
||||
},
|
||||
moveContainer(toIndex, event) {
|
||||
let containerId = event.dataTransfer.getData('containerid');
|
||||
let container = this.containers.filter(c => c.id === containerId)[0];
|
||||
let fromIndex = this.containers.indexOf(container);
|
||||
this.containers.splice(fromIndex, 1);
|
||||
if (fromIndex > toIndex) {
|
||||
this.containers.splice(toIndex + 1, 0, container);
|
||||
} else {
|
||||
this.containers.splice(toIndex, 0, container);
|
||||
}
|
||||
this.persist();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
let context = {
|
||||
item: this.domainObject,
|
||||
addContainer: this.addContainer,
|
||||
deleteContainer: this.deleteContainer,
|
||||
deleteFrame: this.deleteFrame,
|
||||
type: 'flexible-layout'
|
||||
}
|
||||
|
||||
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, true);
|
||||
this.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unsubscribeSelection();
|
||||
this.unobserve();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
109
src/plugins/flexibleLayout/components/frame.vue
Normal file
109
src/plugins/flexibleLayout/components/frame.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
/*****************************************************************************
|
||||
* 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-fl-frame"
|
||||
:style="{
|
||||
'flex-basis': `${frame.size}%`
|
||||
}">
|
||||
|
||||
<div class="c-frame c-fl-frame__drag-wrapper is-selectable is-moveable"
|
||||
draggable="true"
|
||||
@dragstart="initDrag"
|
||||
ref="frame">
|
||||
|
||||
<object-frame
|
||||
v-if="domainObject"
|
||||
:domain-object="domainObject"
|
||||
:object-path="objectPath"
|
||||
:has-frame="hasFrame">
|
||||
</object-frame>
|
||||
|
||||
<div class="c-fl-frame__size-indicator"
|
||||
v-if="isEditing"
|
||||
v-show="frame.size && frame.size < 100">
|
||||
{{frame.size}}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ResizeHandle from './resizeHandle.vue';
|
||||
import ObjectFrame from '../../../ui/components/ObjectFrame.vue';
|
||||
import isEditingMixin from '../mixins/isEditing';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: ['frame', 'index', 'containerIndex'],
|
||||
mixins: [isEditingMixin],
|
||||
data() {
|
||||
return {
|
||||
domainObject: undefined,
|
||||
objectPath: undefined
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ResizeHandle,
|
||||
ObjectFrame
|
||||
},
|
||||
computed: {
|
||||
hasFrame() {
|
||||
return !this.frame.noFrame;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setDomainObject(object) {
|
||||
console.log('setting object!');
|
||||
this.domainObject = object;
|
||||
this.objectPath = [object];
|
||||
this.setSelection();
|
||||
},
|
||||
setSelection() {
|
||||
let context = {
|
||||
item: this.domainObject,
|
||||
addContainer: this.addContainer,
|
||||
type: 'frame',
|
||||
frameId: this.frame.id
|
||||
};
|
||||
|
||||
this.unsubscribeSelection = this.openmct.selection.selectable(this.$refs.frame, context, false);
|
||||
},
|
||||
initDrag(event) {
|
||||
event.dataTransfer.setData('frameid', this.frame.id);
|
||||
event.dataTransfer.setData('containerIndex', this.containerIndex);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.frame.domainObjectIdentifier) {
|
||||
this.openmct.objects.get(this.frame.domainObjectIdentifier).then((object)=>{
|
||||
this.setDomainObject(object);
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unsubscribeSelection) {
|
||||
this.unsubscribeSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
91
src/plugins/flexibleLayout/components/resizeHandle.vue
Normal file
91
src/plugins/flexibleLayout/components/resizeHandle.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
/*****************************************************************************
|
||||
* 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-fl-frame__resize-handle"
|
||||
:class="[orientation]"
|
||||
v-show="isEditing && !isDragging"
|
||||
@mousedown="mousedown">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import isEditingMixin from '../mixins/isEditing';
|
||||
|
||||
export default {
|
||||
props: ['orientation', 'index'],
|
||||
mixins: [isEditingMixin],
|
||||
data() {
|
||||
return {
|
||||
initialPos: 0,
|
||||
isDragging: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mousedown(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.$emit('init-move', this.index);
|
||||
|
||||
document.body.addEventListener('mousemove', this.mousemove);
|
||||
document.body.addEventListener('mouseup', this.mouseup);
|
||||
},
|
||||
mousemove(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let elSize, mousePos, delta;
|
||||
|
||||
if (this.orientation === 'horizontal') {
|
||||
elSize = this.$el.getBoundingClientRect().x;
|
||||
mousePos = event.clientX;
|
||||
} else {
|
||||
elSize = this.$el.getBoundingClientRect().y;
|
||||
mousePos = event.clientY;
|
||||
}
|
||||
|
||||
delta = mousePos - elSize;
|
||||
|
||||
this.$emit('move', this.index, delta, event);
|
||||
},
|
||||
mouseup(event) {
|
||||
this.$emit('end-move', event);
|
||||
|
||||
document.body.removeEventListener('mousemove', this.mousemove);
|
||||
document.body.removeEventListener('mouseup', this.mouseup);
|
||||
},
|
||||
setDragging(event) {
|
||||
this.isDragging = true;
|
||||
},
|
||||
unsetDragging(event) {
|
||||
this.isDragging = false;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('dragstart', this.setDragging);
|
||||
document.addEventListener('dragend', this.unsetDragging);
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('dragstart', this.setDragging);
|
||||
document.removeEventListener('dragend', this.unsetDragging);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
67
src/plugins/flexibleLayout/flexibleLayoutViewProvider.js
Normal file
67
src/plugins/flexibleLayout/flexibleLayoutViewProvider.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/*****************************************************************************
|
||||
* 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/flexibleLayout.vue',
|
||||
'vue'
|
||||
], function (
|
||||
FlexibleLayoutComponent,
|
||||
Vue
|
||||
) {
|
||||
function FlexibleLayoutViewProvider(openmct) {
|
||||
return {
|
||||
key: 'flexible-layout',
|
||||
name: 'FlexibleLayout',
|
||||
cssClass: 'icon-layout-view',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'flexible-layout';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
FlexibleLayoutComponent: FlexibleLayoutComponent.default
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
layoutObject: domainObject
|
||||
},
|
||||
el: element,
|
||||
template: '<flexible-layout-component></flexible-layout-component>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
return FlexibleLayoutViewProvider;
|
||||
});
|
||||
19
src/plugins/flexibleLayout/mixins/isEditing.js
Normal file
19
src/plugins/flexibleLayout/mixins/isEditing.js
Normal file
@@ -0,0 +1,19 @@
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
return {
|
||||
isEditing: this.openmct.editor.isEditing()
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.editor.on('isEditing', this.toggleEditing);
|
||||
},
|
||||
destroyed() {
|
||||
this.openmct.editor.off('isEditing', this.toggleEditing);
|
||||
},
|
||||
methods: {
|
||||
toggleEditing(value) {
|
||||
this.isEditing = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -20,47 +20,37 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
define([
|
||||
'vue',
|
||||
'../../res/templates/viewSnapshot.html'
|
||||
'./flexibleLayoutViewProvider',
|
||||
'./utils/container',
|
||||
'./toolbarProvider'
|
||||
], function (
|
||||
Vue,
|
||||
snapshotOverlayTemplate
|
||||
FlexibleLayoutViewProvider,
|
||||
Container,
|
||||
ToolBarProvider
|
||||
) {
|
||||
function SnapshotOverlay (embedObject, formatTime) {
|
||||
this.embedObject = embedObject;
|
||||
return function plugin() {
|
||||
|
||||
this.snapshotOverlayVue = new Vue({
|
||||
template: snapshotOverlayTemplate,
|
||||
data: function () {
|
||||
return {
|
||||
embed: embedObject
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
close: this.close.bind(this),
|
||||
formatTime: formatTime
|
||||
}
|
||||
});
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new FlexibleLayoutViewProvider(openmct));
|
||||
|
||||
this.open();
|
||||
}
|
||||
openmct.types.addType('flexible-layout', {
|
||||
name: "Flexible Layout",
|
||||
creatable: true,
|
||||
description: "A fluid, flexible layout canvas that can display multiple objects in rows or columns.",
|
||||
cssClass: 'icon-flexible-layout',
|
||||
initialize: function (domainObject) {
|
||||
domainObject.configuration = {
|
||||
containers: [new Container.default(50), new Container.default(50)],
|
||||
rowsLayout: false
|
||||
};
|
||||
domainObject.composition = [];
|
||||
}
|
||||
});
|
||||
|
||||
SnapshotOverlay.prototype.open = function () {
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.classList.add('abs');
|
||||
let toolbar = ToolBarProvider.default(openmct);
|
||||
|
||||
document.body.appendChild(this.overlay);
|
||||
|
||||
this.overlay.appendChild(this.snapshotOverlayVue.$mount().$el);
|
||||
openmct.toolbars.addProvider(toolbar);
|
||||
};
|
||||
};
|
||||
|
||||
SnapshotOverlay.prototype.close = function (event) {
|
||||
event.stopPropagation();
|
||||
this.snapshotOverlayVue.$destroy();
|
||||
this.overlay.parentNode.removeChild(this.overlay);
|
||||
};
|
||||
|
||||
return SnapshotOverlay;
|
||||
});
|
||||
215
src/plugins/flexibleLayout/toolbarProvider.js
Normal file
215
src/plugins/flexibleLayout/toolbarProvider.js
Normal file
@@ -0,0 +1,215 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
function ToolbarProvider(openmct) {
|
||||
|
||||
return {
|
||||
name: "Flexible Layout Toolbar",
|
||||
key: "flex-layout",
|
||||
description: "A toolbar for objects inside a Flexible Layout.",
|
||||
forSelection: function (selection) {
|
||||
let context = selection[0].context;
|
||||
|
||||
return (openmct.editor.isEditing() && context && context.type &&
|
||||
(context.type === 'flexible-layout' || context.type === 'container' || context.type === 'frame'));
|
||||
},
|
||||
toolbar: function (selection) {
|
||||
|
||||
let primary = selection[0],
|
||||
secondary = selection[1],
|
||||
tertiary = selection[2],
|
||||
deleteFrame,
|
||||
toggleContainer,
|
||||
deleteContainer,
|
||||
addContainer,
|
||||
toggleFrame,
|
||||
separator;
|
||||
|
||||
separator = {
|
||||
control: "separator",
|
||||
domainObject: selection[0].context.item,
|
||||
key: "separator"
|
||||
};
|
||||
|
||||
toggleContainer = {
|
||||
control: 'toggle-button',
|
||||
key: 'toggle-layout',
|
||||
domainObject: secondary ? secondary.context.item : primary.context.item,
|
||||
property: 'configuration.rowsLayout',
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-columns',
|
||||
title: 'Columns'
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-rows',
|
||||
title: 'Rows'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
if (primary.context.type === 'frame') {
|
||||
let frameId = primary.context.frameId;
|
||||
let layoutObject = tertiary.context.item;
|
||||
let containers = layoutObject
|
||||
.configuration
|
||||
.containers;
|
||||
let container = containers
|
||||
.filter(c => c.frames.some(f => f.id === frameId))[0];
|
||||
let frame = container
|
||||
.frames
|
||||
.filter((f => f.id === frameId))[0];
|
||||
let containerIndex = containers.indexOf(container);
|
||||
let frameIndex = container.frames.indexOf(frame);
|
||||
|
||||
deleteFrame = {
|
||||
control: "button",
|
||||
domainObject: primary.context.item,
|
||||
method: function () {
|
||||
let deleteFrameAction = tertiary.context.deleteFrame;
|
||||
|
||||
let prompt = openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Ok',
|
||||
emphasis: 'true',
|
||||
callback: function () {
|
||||
deleteFrameAction(primary.context.frameId);
|
||||
prompt.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
prompt.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
key: "remove",
|
||||
icon: "icon-trash",
|
||||
title: "Remove Frame"
|
||||
};
|
||||
toggleFrame = {
|
||||
control: "toggle-button",
|
||||
domainObject: secondary.context.item,
|
||||
property: `configuration.containers[${containerIndex}].frames[${frameIndex}].noFrame`,
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-frame-hide',
|
||||
title: "Hide frame"
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-frame-show',
|
||||
title: "Show frame"
|
||||
}
|
||||
]
|
||||
};
|
||||
addContainer = {
|
||||
control: "button",
|
||||
domainObject: tertiary.context.item,
|
||||
method: tertiary.context.addContainer,
|
||||
key: "add",
|
||||
icon: "icon-plus-in-rect",
|
||||
title: 'Add Container'
|
||||
};
|
||||
|
||||
} else if (primary.context.type === 'container') {
|
||||
|
||||
deleteContainer = {
|
||||
control: "button",
|
||||
domainObject: primary.context.item,
|
||||
method: function () {
|
||||
let removeContainer = secondary.context.deleteContainer,
|
||||
containerId = primary.context.containerId;
|
||||
|
||||
let prompt = openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: `This action will permanently delete container ${containerIndex + 1} from this Flexible Layout`,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Ok',
|
||||
emphasis: 'true',
|
||||
callback: function () {
|
||||
removeContainer(containerId);
|
||||
prompt.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
prompt.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
key: "remove",
|
||||
icon: "icon-trash",
|
||||
title: "Remove Container"
|
||||
};
|
||||
|
||||
addContainer = {
|
||||
control: "button",
|
||||
domainObject: secondary.context.item,
|
||||
method: secondary.context.addContainer,
|
||||
key: "add",
|
||||
icon: "icon-plus-in-rect",
|
||||
title: 'Add Container'
|
||||
};
|
||||
|
||||
} else if (primary.context.type === 'flexible-layout') {
|
||||
|
||||
addContainer = {
|
||||
control: "button",
|
||||
domainObject: primary.context.item,
|
||||
method: primary.context.addContainer,
|
||||
key: "add",
|
||||
icon: "icon-plus-in-rect",
|
||||
title: 'Add Container'
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
let toolbar = [
|
||||
toggleContainer,
|
||||
addContainer,
|
||||
toggleFrame ? separator: undefined,
|
||||
toggleFrame,
|
||||
deleteFrame || deleteContainer ? separator: undefined,
|
||||
deleteFrame,
|
||||
deleteContainer
|
||||
];
|
||||
|
||||
return toolbar.filter(button => button !== undefined);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default ToolbarProvider;
|
||||
11
src/plugins/flexibleLayout/utils/container.js
Normal file
11
src/plugins/flexibleLayout/utils/container.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
class Container {
|
||||
constructor (size) {
|
||||
this.id = uuid();
|
||||
this.frames = [];
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
export default Container;
|
||||
13
src/plugins/flexibleLayout/utils/frame.js
Normal file
13
src/plugins/flexibleLayout/utils/frame.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
class Frame {
|
||||
constructor(domainObjectIdentifier, size) {
|
||||
this.id = uuid();
|
||||
this.domainObjectIdentifier = domainObjectIdentifier;
|
||||
this.size = size;
|
||||
|
||||
this.noFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default Frame;
|
||||
159
src/plugins/folderView/components/GridItem.vue
Normal file
159
src/plugins/folderView/components/GridItem.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<a class="l-grid-view__item c-grid-item"
|
||||
:class="{ 'is-alias': item.isAlias === true }"
|
||||
:href="objectLink">
|
||||
<div class="c-grid-item__type-icon"
|
||||
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'">
|
||||
</div>
|
||||
<div class="c-grid-item__details">
|
||||
<!-- Name and metadata -->
|
||||
<div class="c-grid-item__name"
|
||||
:title="item.model.name">{{item.model.name}}</div>
|
||||
<div class="c-grid-item__metadata"
|
||||
:title="item.type.name">
|
||||
<span class="c-grid-item__metadata__type">{{item.type.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-grid-item__controls">
|
||||
<div class="icon-people" title='Shared'></div>
|
||||
<button class="c-click-icon icon-info c-info-button" title='More Info'></button>
|
||||
<div class="icon-pointer-right c-pointer-icon"></div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* GRID ITEMS */
|
||||
.c-grid-item {
|
||||
// Mobile-first
|
||||
@include button($bg: $colorItemBg, $fg: $colorItemFg);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: $interiorMarginLg;
|
||||
|
||||
&__type-icon {
|
||||
filter: $colorKeyFilter;
|
||||
flex: 0 0 $gridItemMobile;
|
||||
font-size: floor($gridItemMobile / 2);
|
||||
margin-right: $interiorMarginLg;
|
||||
}
|
||||
|
||||
&.is-alias {
|
||||
// Object is an alias to an original.
|
||||
[class*='__type-icon'] {
|
||||
@include isAlias();
|
||||
color: $colorIconAliasForKeyFilter;
|
||||
}
|
||||
}
|
||||
|
||||
&__details {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
color: $colorItemFg;
|
||||
@include headerFont(1.2em);
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&__metadata {
|
||||
color: $colorItemFgDetails;
|
||||
font-size: 0.9em;
|
||||
|
||||
body.mobile & {
|
||||
[class*='__item-count'] {
|
||||
&:before {
|
||||
content: ' - ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__controls {
|
||||
color: $colorItemFgDetails;
|
||||
flex: 0 0 64px;
|
||||
font-size: 1.2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
body.desktop & {
|
||||
$transOutMs: 300ms;
|
||||
flex-flow: column nowrap;
|
||||
transition: background $transOutMs ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background: $colorItemBgHov;
|
||||
transition: $transIn;
|
||||
|
||||
.c-grid-item__type-icon {
|
||||
filter: $colorKeyFilterHov;
|
||||
transform: scale(1);
|
||||
transition: $transInBounce;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
margin: 0; // Reset from mobile
|
||||
}
|
||||
|
||||
&__controls {
|
||||
align-items: start;
|
||||
flex: 0 0 auto;
|
||||
order: 1;
|
||||
.c-info-button,
|
||||
.c-pointer-icon { display: none; }
|
||||
}
|
||||
|
||||
&__type-icon {
|
||||
flex: 1 1 auto;
|
||||
font-size: floor($gridItemDesk / 3);
|
||||
margin: $interiorMargin 22.5% $interiorMargin * 3 22.5%;
|
||||
order: 2;
|
||||
transform: scale(0.9);
|
||||
transform-origin: center;
|
||||
transition: all $transOutMs ease-in-out;
|
||||
}
|
||||
|
||||
&__details {
|
||||
flex: 0 0 auto;
|
||||
justify-content: flex-end;
|
||||
order: 3;
|
||||
}
|
||||
|
||||
&__metadata {
|
||||
display: flex;
|
||||
|
||||
&__type {
|
||||
flex: 1 1 auto;
|
||||
@include ellipsize();
|
||||
}
|
||||
|
||||
&__item-count {
|
||||
opacity: 0.7;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
||||
import objectLink from '../../../ui/mixins/object-link';
|
||||
|
||||
export default {
|
||||
mixins: [contextMenuGesture, objectLink],
|
||||
props: ['item']
|
||||
}
|
||||
</script>
|
||||
@@ -1,28 +1,10 @@
|
||||
<template>
|
||||
<div class="l-grid-view">
|
||||
<div v-for="(item, index) in items"
|
||||
v-bind:key="index"
|
||||
class="l-grid-view__item c-grid-item"
|
||||
:class="{ 'is-alias': item.isAlias === true }"
|
||||
@click="navigate(item)">
|
||||
<div class="c-grid-item__type-icon"
|
||||
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'">
|
||||
</div>
|
||||
<div class="c-grid-item__details">
|
||||
<!-- Name and metadata -->
|
||||
<div class="c-grid-item__name"
|
||||
:title="item.model.name">{{item.model.name}}</div>
|
||||
<div class="c-grid-item__metadata"
|
||||
:title="item.type.name">
|
||||
<span class="c-grid-item__metadata__type">{{item.type.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-grid-item__controls">
|
||||
<div class="icon-people" title='Shared'></div>
|
||||
<button class="c-click-icon icon-info c-info-button" title='More Info'></button>
|
||||
<div class="icon-pointer-right c-pointer-icon"></div>
|
||||
</div>
|
||||
</div>
|
||||
<grid-item v-for="(item, index) in items"
|
||||
:key="index"
|
||||
:item="item"
|
||||
:object-path="item.objectPath">
|
||||
</grid-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -33,6 +15,7 @@
|
||||
.l-grid-view {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
overflow: auto;
|
||||
|
||||
&__item {
|
||||
flex: 0 0 auto;
|
||||
@@ -176,17 +159,11 @@
|
||||
<script>
|
||||
|
||||
import compositionLoader from './composition-loader';
|
||||
import GridItem from './GridItem.vue';
|
||||
|
||||
export default {
|
||||
components: {GridItem},
|
||||
mixins: [compositionLoader],
|
||||
inject: ['domainObject', 'openmct'],
|
||||
methods: {
|
||||
navigate(item) {
|
||||
let currentLocation = this.openmct.router.currentLocation.path,
|
||||
navigateToPath = `${currentLocation}/${this.openmct.objects.makeKeyString(item.model.identifier)}`;
|
||||
|
||||
this.openmct.router.setPath(navigateToPath);
|
||||
}
|
||||
}
|
||||
inject: ['openmct']
|
||||
}
|
||||
</script>
|
||||
|
||||
72
src/plugins/folderView/components/ListItem.vue
Normal file
72
src/plugins/folderView/components/ListItem.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<tr class="c-list-item"
|
||||
:class="{ 'is-alias': item.isAlias === true }"
|
||||
@click="navigate">
|
||||
<td class="c-list-item__name">
|
||||
<a :href="objectLink" ref="objectLink">
|
||||
<div class="c-list-item__type-icon"
|
||||
:class="item.type.cssClass"></div>
|
||||
{{item.model.name}}
|
||||
</a>
|
||||
</td>
|
||||
<td class="c-list-item__type">{{ item.type.name }}</td>
|
||||
<td class="c-list-item__date-created">{{ formatTime(item.model.persisted, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||
<td class="c-list-item__date-updated">{{ formatTime(item.model.modified, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* LIST ITEM */
|
||||
.c-list-item {
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
}
|
||||
|
||||
&__type-icon {
|
||||
color: $colorKey;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
margin-right:$interiorMarginSm;
|
||||
}
|
||||
|
||||
&.is-alias {
|
||||
// Object is an alias to an original.
|
||||
[class*='__type-icon'] {
|
||||
&:after {
|
||||
color: $colorIconAlias;
|
||||
content: $glyph-icon-link;
|
||||
font-family: symbolsfont;
|
||||
display: block;
|
||||
position: absolute;
|
||||
text-shadow: rgba(black, 0.5) 0 1px 2px;
|
||||
top: auto; left: -1px; bottom: 1px; right: auto;
|
||||
transform-origin: bottom left;
|
||||
transform: scale(0.65);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import moment from 'moment';
|
||||
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
||||
import objectLink from '../../../ui/mixins/object-link';
|
||||
|
||||
export default {
|
||||
mixins: [contextMenuGesture, objectLink],
|
||||
props: ['item'],
|
||||
methods: {
|
||||
formatTime(timestamp, format) {
|
||||
return moment(timestamp).format(format);
|
||||
},
|
||||
navigate() {
|
||||
this.$refs.objectLink.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2,60 +2,50 @@
|
||||
<div class="c-table c-table--sortable c-list-view">
|
||||
<table class="c-table__body">
|
||||
<thead class="c-table__header">
|
||||
<tr>
|
||||
<th class="is-sortable"
|
||||
:class="{
|
||||
'is-sorting': sortBy === 'model.name',
|
||||
'asc': ascending,
|
||||
'desc': !ascending
|
||||
}"
|
||||
@click="sort('model.name', true)">
|
||||
Name
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
:class="{
|
||||
'is-sorting': sortBy === 'type.name',
|
||||
'asc': ascending,
|
||||
'desc': !ascending
|
||||
}"
|
||||
@click="sort('type.name', true)">
|
||||
Type
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
:class="{
|
||||
'is-sorting': sortBy === 'model.persisted',
|
||||
'asc': ascending,
|
||||
'desc': !ascending
|
||||
}"
|
||||
@click="sort('model.persisted', false)">
|
||||
Created Date
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
:class="{
|
||||
'is-sorting': sortBy === 'model.modified',
|
||||
'asc': ascending,
|
||||
'desc': !ascending
|
||||
}"
|
||||
@click="sort('model.modified', false)">
|
||||
Updated Date
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="is-sortable"
|
||||
:class="{
|
||||
'is-sorting': sortBy === 'model.name',
|
||||
'asc': ascending,
|
||||
'desc': !ascending
|
||||
}"
|
||||
@click="sort('model.name', true)">
|
||||
Name
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
:class="{
|
||||
'is-sorting': sortBy === 'type.name',
|
||||
'asc': ascending,
|
||||
'desc': !ascending
|
||||
}"
|
||||
@click="sort('type.name', true)">
|
||||
Type
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
:class="{
|
||||
'is-sorting': sortBy === 'model.persisted',
|
||||
'asc': ascending,
|
||||
'desc': !ascending
|
||||
}"
|
||||
@click="sort('model.persisted', false)">
|
||||
Created Date
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
:class="{
|
||||
'is-sorting': sortBy === 'model.modified',
|
||||
'asc': ascending,
|
||||
'desc': !ascending
|
||||
}"
|
||||
@click="sort('model.modified', false)">
|
||||
Updated Date
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="c-list-item"
|
||||
v-for="(item,index) in sortedItems"
|
||||
v-bind:key="index"
|
||||
:class="{ 'is-alias': item.isAlias === true }"
|
||||
@click="navigate(item)">
|
||||
<td class="c-list-item__name">
|
||||
<div class="c-list-item__type-icon"
|
||||
:class="item.type.cssClass"></div>
|
||||
{{item.model.name}}
|
||||
</td>
|
||||
<td class="c-list-item__type">{{ item.type.name }}</td>
|
||||
<td class="c-list-item__date-created">{{ formatTime(item.model.persisted, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||
<td class="c-list-item__date-updated">{{ formatTime(item.model.modified, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||
</tr>
|
||||
<list-item v-for="(item,index) in sortedItems"
|
||||
:item="item"
|
||||
:object-path="item.objectPath">
|
||||
</list-item>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -96,48 +86,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-list-item {
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
}
|
||||
|
||||
&__type-icon {
|
||||
color: $colorKey;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
margin-right:$interiorMarginSm;
|
||||
}
|
||||
|
||||
&.is-alias {
|
||||
// Object is an alias to an original.
|
||||
[class*='__type-icon'] {
|
||||
&:after {
|
||||
color: $colorIconAlias;
|
||||
content: $glyph-icon-link;
|
||||
font-family: symbolsfont;
|
||||
display: block;
|
||||
position: absolute;
|
||||
text-shadow: rgba(black, 0.5) 0 1px 2px;
|
||||
top: auto; left: -1px; bottom: 1px; right: auto;
|
||||
transform-origin: bottom left;
|
||||
transform: scale(0.65);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************************* LIST ITEM */
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import lodash from 'lodash';
|
||||
import moment from 'moment';
|
||||
import compositionLoader from './composition-loader';
|
||||
import ListItem from './ListItem.vue';
|
||||
|
||||
export default {
|
||||
components: {ListItem},
|
||||
mixins: [compositionLoader],
|
||||
inject: ['domainObject', 'openmct'],
|
||||
data() {
|
||||
@@ -156,15 +114,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatTime(timestamp, format) {
|
||||
return moment(timestamp).format(format);
|
||||
},
|
||||
navigate(item) {
|
||||
let currentLocation = this.openmct.router.currentLocation.path,
|
||||
navigateToPath = `${currentLocation}/${this.openmct.objects.makeKeyString(item.model.identifier)}`;
|
||||
|
||||
this.openmct.router.setPath(navigateToPath);
|
||||
},
|
||||
sort(field, defaultDirection) {
|
||||
if (this.sortBy === field) {
|
||||
this.ascending = !this.ascending;
|
||||
|
||||
@@ -31,16 +31,19 @@ export default {
|
||||
methods: {
|
||||
add(child, index, anything) {
|
||||
var type = this.openmct.types.get(child.type) || unknownObjectType;
|
||||
|
||||
this.items.push({
|
||||
model: child,
|
||||
type: type.definition,
|
||||
isAlias: this.domainObject.identifier.key !== child.location
|
||||
isAlias: this.domainObject.identifier.key !== child.location,
|
||||
objectPath: [child].concat(openmct.router.path)
|
||||
});
|
||||
},
|
||||
remove(child) {
|
||||
// TODO: implement remove action
|
||||
console.log('remove child? might be identifier');
|
||||
remove(identifier) {
|
||||
this.items = this.items
|
||||
.filter((i) => {
|
||||
return i.model.identifier.key !== identifier.key
|
||||
|| i.model.identifier.namespace !== identifier.namespace
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,29 +21,9 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/controllers/NotebookController",
|
||||
"./src/controllers/NewEntryController",
|
||||
"./src/controllers/SelectSnapshotController",
|
||||
"./src/actions/NewEntryContextual",
|
||||
"./src/actions/AnnotateSnapshot",
|
||||
"./src/directives/MCTSnapshot",
|
||||
"./src/directives/EntryDnd",
|
||||
"./res/templates/controls/snapSelect.html",
|
||||
"./res/templates/controls/embedControl.html",
|
||||
"./res/templates/annotation.html",
|
||||
"./res/templates/draggedEntry.html"
|
||||
"./src/controllers/NotebookController"
|
||||
], function (
|
||||
NotebookController,
|
||||
NewEntryController,
|
||||
SelectSnapshotController,
|
||||
newEntryAction,
|
||||
AnnotateSnapshotAction,
|
||||
MCTSnapshotDirective,
|
||||
EntryDndDirective,
|
||||
snapSelectTemplate,
|
||||
embedControlTemplate,
|
||||
annotationTemplate,
|
||||
draggedEntryTemplate
|
||||
NotebookController
|
||||
) {
|
||||
var installed = false;
|
||||
|
||||
@@ -67,9 +47,8 @@ define([
|
||||
features: 'creation',
|
||||
model: {
|
||||
entries: [],
|
||||
composition: [],
|
||||
entryTypes: [],
|
||||
defaultSort: '-createdOn'
|
||||
defaultSort: 'oldest'
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
@@ -79,112 +58,17 @@ define([
|
||||
options: [
|
||||
{
|
||||
name: 'Newest First',
|
||||
value: "-createdOn",
|
||||
value: "newest"
|
||||
},
|
||||
{
|
||||
name: 'Oldest First',
|
||||
value: "createdOn"
|
||||
value: "oldest"
|
||||
}
|
||||
],
|
||||
cssClass: 'l-inline'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
"key": "notebook-new-entry",
|
||||
"implementation": newEntryAction,
|
||||
"name": "New Notebook Entry",
|
||||
"cssClass": "icon-notebook labeled",
|
||||
"description": "Add a new Notebook entry",
|
||||
"category": [
|
||||
"view-control"
|
||||
],
|
||||
"depends": [
|
||||
"$compile",
|
||||
"$rootScope",
|
||||
"dialogService",
|
||||
"notificationService",
|
||||
"linkService"
|
||||
],
|
||||
"priority": "preferred"
|
||||
},
|
||||
{
|
||||
"key": "annotate-snapshot",
|
||||
"implementation": AnnotateSnapshotAction,
|
||||
"name": "Annotate Snapshot",
|
||||
"cssClass": "icon-pencil labeled",
|
||||
"description": "Annotate embed's snapshot",
|
||||
"category": "embed",
|
||||
"depends": [
|
||||
"dialogService",
|
||||
"dndService",
|
||||
"$rootScope"
|
||||
]
|
||||
}
|
||||
],
|
||||
controllers: [
|
||||
{
|
||||
"key": "NewEntryController",
|
||||
"implementation": NewEntryController,
|
||||
"depends": ["$scope",
|
||||
"$rootScope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "selectSnapshotController",
|
||||
"implementation": SelectSnapshotController,
|
||||
"depends": ["$scope",
|
||||
"$rootScope"
|
||||
]
|
||||
}
|
||||
],
|
||||
controls: [
|
||||
{
|
||||
"key": "snapshot-select",
|
||||
"template": snapSelectTemplate
|
||||
},
|
||||
{
|
||||
"key": "embed-control",
|
||||
"template": embedControlTemplate
|
||||
}
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
"key": "annotate-snapshot",
|
||||
"template": annotationTemplate
|
||||
}
|
||||
],
|
||||
directives: [
|
||||
{
|
||||
"key": "mctSnapshot",
|
||||
"implementation": MCTSnapshotDirective,
|
||||
"depends": [
|
||||
"$rootScope",
|
||||
"$document",
|
||||
"exportImageService",
|
||||
"dialogService",
|
||||
"notificationService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctEntryDnd",
|
||||
"implementation": EntryDndDirective,
|
||||
"depends": [
|
||||
"$rootScope",
|
||||
"$compile",
|
||||
"dndService",
|
||||
"typeService",
|
||||
"notificationService"
|
||||
]
|
||||
}
|
||||
],
|
||||
representations: [
|
||||
{
|
||||
"key": "draggedEntry",
|
||||
"template": draggedEntryTemplate
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
<div class="snap-annotation" id="snap-annotation" ng-controller="ngModel.controller">
|
||||
</div>
|
||||
@@ -1,51 +0,0 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2009-2016, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<!--
|
||||
This element appears in the overlay dialog when initiating a new Notebook Entry from a view's Notebook button -->
|
||||
<div class='form-control'>
|
||||
<ng-form name="mctControl">
|
||||
<div class='fields' ng-controller="NewEntryController">
|
||||
<div class="l-flex-row new-notebook-entry-embed l-entry-embed {{cssClass}}"
|
||||
ng-class="{ 'has-snapshot' : snapToggle }">
|
||||
<div class="holder flex-elem snap-thumb"
|
||||
ng-if="snapToggle">
|
||||
<img ng-src="{{snapshot.src}}" alt="{{snapshot.modified}}">
|
||||
</div>
|
||||
|
||||
<div class="holder flex-elem embed-info">
|
||||
<div class="embed-title">{{objectName}}</div>
|
||||
<div class="embed-date"
|
||||
ng-if="snapToggle">{{snapshot.modified| date:'yyyy-MM-dd HH:mm:ss'}}</div>
|
||||
</div>
|
||||
|
||||
<div class="holder flex-elem annotate-new"
|
||||
ng-if="snapToggle">
|
||||
<a class="s-button flex-elem icon-pencil "
|
||||
title="Annotate this snapshot"
|
||||
ng-click="annotateSnapshot()">
|
||||
<span class="title-label">Annotate</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
@@ -1,29 +0,0 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<div class='form-control select' ng-controller="selectSnapshotController">
|
||||
<select
|
||||
ng-model="selectModel"
|
||||
ng-options="opt.value as opt.name for opt in options"
|
||||
ng-required="ngRequired"
|
||||
name="mctControl">
|
||||
</select>
|
||||
</div>
|
||||
@@ -1,38 +0,0 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<div class="frame snap-frame frame-template t-frame-inner abs t-object-type-{{ representation.selected.key }}">
|
||||
<div class="abs object-browse-bar l-flex-row">
|
||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||
<mct-representation
|
||||
key="'switcher'"
|
||||
ng-model="representation"
|
||||
mct-object="domainObject">
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
<div class="abs object-holder" data-entry = "{{parameters.entry}}" data-embed = "{{parameters.embed}}" mct-snapshot ng-if="representation.selected.key">
|
||||
<mct-representation
|
||||
key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject">
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
<li class="c-notebook__entry c-ne has-local-controls"
|
||||
v-on:drop="dropOnEntry(entry.id)"
|
||||
v-on:drop="dropOnEntry(entry.id, $event)"
|
||||
v-on:dragover="dragoverOnEntry"
|
||||
>
|
||||
<div class="c-ne__time-and-content">
|
||||
@@ -19,8 +19,9 @@
|
||||
<div class="c-ne__embeds">
|
||||
<notebook-embed
|
||||
v-for="(embed, index) in entry.embeds"
|
||||
v-bind:embed="embed"
|
||||
v-bind:entry="entry"
|
||||
:key="index"
|
||||
:embed="embed"
|
||||
:entry="entry"
|
||||
></notebook-embed>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<div class="c-notebook">
|
||||
<div class="c-notebook__head">
|
||||
<search class="c-notebook__search"
|
||||
v-model="entrySearch"
|
||||
v-on:input="search($event)"
|
||||
v-on:clear="entrySearch = ''; search($event)"></search>
|
||||
:value="entrySearch"
|
||||
@input="search"
|
||||
@clear="search">
|
||||
</search>
|
||||
<div class="c-notebook__controls">
|
||||
<div class="select c-notebook__controls__time">
|
||||
<select v-model="showTime">
|
||||
@@ -15,23 +16,25 @@
|
||||
</div>
|
||||
<div class="select c-notebook__controls__sort">
|
||||
<select v-model="sortEntries">
|
||||
<option value="-createdOn" selected="selected">Newest first</option>
|
||||
<option value="createdOn">Oldest first</option>
|
||||
<option value="newest" :selected="sortEntries === 'newest'">Newest first</option>
|
||||
<option value="oldest" :selected="sortEntries === 'oldest'">Oldest first</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-notebook__drag-area icon-plus"
|
||||
v-on:click="newEntry($event)"
|
||||
id="newEntry" mct-entry-dnd>
|
||||
@click="newEntry($event)"
|
||||
@drop="newEntry($event)"
|
||||
id="newEntry">
|
||||
<span class="c-notebook__drag-area__label">To start a new entry, click here or drag and drop any object</span>
|
||||
</div>
|
||||
<div class="c-notebook__entries" ng-mouseover="handleActive()">
|
||||
<ul>
|
||||
<notebook-entry
|
||||
v-for="entry in filterBySearch(entries, entrySearch)"
|
||||
v-bind:entry="entry"
|
||||
></notebook-entry>
|
||||
<notebook-entry
|
||||
v-for="(entry,index) in filteredAndSortedEntries"
|
||||
:key="entry.key"
|
||||
:entry="entry">
|
||||
</notebook-entry>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,162 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining viewSnapshot (Originally NewWindowAction). Created by vwoeltje on 11/18/14.
|
||||
*/
|
||||
define(
|
||||
["painterro", "zepto"],
|
||||
function (Painterro, $) {
|
||||
|
||||
var annotationStruct = {
|
||||
title: "Annotate Snapshot",
|
||||
template: "annotate-snapshot",
|
||||
options: [{
|
||||
name: "OK",
|
||||
key: "ok",
|
||||
description: "save annotation"
|
||||
},
|
||||
{
|
||||
name: "Cancel",
|
||||
key: "cancel",
|
||||
description: "cancel editing"
|
||||
}]
|
||||
};
|
||||
|
||||
function AnnotateSnapshot(dialogService, dndService, $rootScope, context) {
|
||||
context = context || {};
|
||||
|
||||
// Choose the object to be opened into a new tab
|
||||
this.domainObject = context.selectedObject || context.domainObject;
|
||||
this.dialogService = dialogService;
|
||||
this.dndService = dndService;
|
||||
this.$rootScope = $rootScope;
|
||||
}
|
||||
|
||||
AnnotateSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId) {
|
||||
|
||||
var DOMAIN_OBJECT = this.domainObject;
|
||||
var ROOTSCOPE = this.$rootScope;
|
||||
var painterro;
|
||||
var save = false;
|
||||
|
||||
var controller = ['$scope', '$timeout', function PainterroController($scope, $timeout) {
|
||||
$(document.body).find('.l-dialog .outer-holder').addClass('annotation-dialog');
|
||||
|
||||
// Timeout is necessary because Painterro uses document.getElementById, and mct-include
|
||||
// hasn't added the dialog to the DOM yet.
|
||||
$timeout(function () {
|
||||
painterro = Painterro({
|
||||
id: 'snap-annotation',
|
||||
activeColor: '#ff0000',
|
||||
activeColorAlpha: 1.0,
|
||||
activeFillColor: '#fff',
|
||||
activeFillColorAlpha: 0.0,
|
||||
backgroundFillColor: '#000',
|
||||
backgroundFillColorAlpha: 0.0,
|
||||
defaultFontSize: 16,
|
||||
defaultLineWidth: 2,
|
||||
defaultTool: 'ellipse',
|
||||
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
|
||||
translation: {
|
||||
name: 'en',
|
||||
strings: {
|
||||
lineColor: 'Line',
|
||||
fillColor: 'Fill',
|
||||
lineWidth: 'Size',
|
||||
textColor: 'Color',
|
||||
fontSize: 'Size',
|
||||
fontStyle: 'Style'
|
||||
}
|
||||
},
|
||||
saveHandler: function (image, done) {
|
||||
if (save) {
|
||||
if (entryId && embedId) {
|
||||
var elementPos = DOMAIN_OBJECT.model.entries.map(function (x) {
|
||||
return x.createdOn;
|
||||
}).indexOf(entryId);
|
||||
var entryEmbeds = DOMAIN_OBJECT.model.entries[elementPos].embeds;
|
||||
var embedPos = entryEmbeds.map(function (x) {
|
||||
return x.id;
|
||||
}).indexOf(embedId);
|
||||
|
||||
saveSnap(image.asBlob(), embedPos, elementPos, DOMAIN_OBJECT);
|
||||
}else {
|
||||
ROOTSCOPE.snapshot = {'src': image.asDataURL('image/png'),
|
||||
'modified': Date.now()};
|
||||
}
|
||||
}
|
||||
done(true);
|
||||
}
|
||||
}).show(snapshot);
|
||||
});
|
||||
}];
|
||||
|
||||
annotationStruct.model = {'controller': controller};
|
||||
|
||||
function saveNotes(param) {
|
||||
if (param === 'ok') {
|
||||
save = true;
|
||||
}else {
|
||||
save = false;
|
||||
ROOTSCOPE.snapshot = "annotationCancelled";
|
||||
}
|
||||
painterro.save();
|
||||
}
|
||||
|
||||
function rejectNotes() {
|
||||
save = false;
|
||||
ROOTSCOPE.snapshot = "annotationCancelled";
|
||||
painterro.save();
|
||||
}
|
||||
|
||||
function saveSnap(url, embedPos, entryPos, domainObject) {
|
||||
var snap = false;
|
||||
|
||||
if (embedPos !== -1 && entryPos !== -1) {
|
||||
var reader = new window.FileReader();
|
||||
reader.readAsDataURL(url);
|
||||
reader.onloadend = function () {
|
||||
snap = reader.result;
|
||||
domainObject.useCapability('mutation', function (model) {
|
||||
if (model.entries[entryPos]) {
|
||||
model.entries[entryPos].embeds[embedPos].snapshot = {
|
||||
'src': snap,
|
||||
'type': url.type,
|
||||
'size': url.size,
|
||||
'modified': Date.now()
|
||||
};
|
||||
model.entries[entryPos].embeds[embedPos].id = Date.now();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.dialogService.getUserChoice(annotationStruct)
|
||||
.then(saveNotes, rejectNotes);
|
||||
|
||||
};
|
||||
|
||||
return AnnotateSnapshot;
|
||||
}
|
||||
);
|
||||
@@ -1,204 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
|
||||
var SNAPSHOT_TEMPLATE = '<mct-representation key="\'draggedEntry\'"' +
|
||||
'class="t-rep-frame holder"' +
|
||||
'mct-object="selObj">' +
|
||||
'</mct-representation>';
|
||||
|
||||
var NEW_TASK_FORM = {
|
||||
name: "Create a Notebook Entry",
|
||||
hint: "Please select one Notebook",
|
||||
sections: [{
|
||||
rows: [{
|
||||
name: 'Entry',
|
||||
key: 'entry',
|
||||
control: 'textarea',
|
||||
required: false,
|
||||
"cssClass": "l-textarea-sm"
|
||||
},
|
||||
{
|
||||
name: 'Embed Type',
|
||||
key: 'withSnapshot',
|
||||
control: 'snapshot-select',
|
||||
"options": [
|
||||
{
|
||||
"name": "Link and Snapshot",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "Link only",
|
||||
"value": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Embed',
|
||||
key: 'embedObject',
|
||||
control: 'embed-control'
|
||||
},
|
||||
{
|
||||
name: 'Save in Notebook',
|
||||
key: 'saveNotebook',
|
||||
control: 'locator',
|
||||
validate: validateLocation
|
||||
}]
|
||||
}]
|
||||
};
|
||||
|
||||
function NewEntryContextual($compile, $rootScope, dialogService, notificationService, linkService, context) {
|
||||
context = context || {};
|
||||
this.domainObject = context.selectedObject || context.domainObject;
|
||||
this.dialogService = dialogService;
|
||||
this.notificationService = notificationService;
|
||||
this.linkService = linkService;
|
||||
this.$rootScope = $rootScope;
|
||||
this.$compile = $compile;
|
||||
}
|
||||
|
||||
function validateLocation(newParentObj) {
|
||||
return newParentObj.model.type === 'notebook';
|
||||
}
|
||||
|
||||
|
||||
NewEntryContextual.prototype.perform = function () {
|
||||
var self = this;
|
||||
var domainObj = this.domainObject;
|
||||
var notification = this.notificationService;
|
||||
var dialogService = this.dialogService;
|
||||
var rootScope = this.$rootScope;
|
||||
rootScope.newEntryText = '';
|
||||
// // Create the overlay element and add it to the document's body
|
||||
this.$rootScope.selObj = domainObj;
|
||||
this.$rootScope.selValue = "";
|
||||
var newScope = rootScope.$new();
|
||||
newScope.selObj = domainObj;
|
||||
newScope.selValue = "";
|
||||
this.$compile(SNAPSHOT_TEMPLATE)(newScope);
|
||||
|
||||
this.$rootScope.$watch("snapshot", setSnapshot);
|
||||
|
||||
function setSnapshot(value) {
|
||||
if (value === "annotationCancelled") {
|
||||
rootScope.snapshot = rootScope.lastValue;
|
||||
rootScope.lastValue = '';
|
||||
|
||||
} else if (value && value !== rootScope.lastValue) {
|
||||
var overlayModel = {
|
||||
title: NEW_TASK_FORM.name,
|
||||
message: NEW_TASK_FORM.message,
|
||||
structure: NEW_TASK_FORM,
|
||||
value: {'entry': rootScope.newEntryText || ""}
|
||||
};
|
||||
|
||||
rootScope.currentDialog = overlayModel;
|
||||
|
||||
dialogService.getDialogResponse(
|
||||
"overlay-dialog",
|
||||
overlayModel,
|
||||
function () {
|
||||
return overlayModel.value;
|
||||
}
|
||||
).then(addNewEntry);
|
||||
|
||||
rootScope.lastValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
function addNewEntry(options) {
|
||||
options.selectedModel = options.embedObject.getModel();
|
||||
options.cssClass = options.embedObject.getCapability('type').typeDef.cssClass;
|
||||
if (self.$rootScope.snapshot) {
|
||||
options.snapshot = self.$rootScope.snapshot;
|
||||
self.$rootScope.snapshot = undefined;
|
||||
}else {
|
||||
options.snapshot = undefined;
|
||||
}
|
||||
|
||||
if (!options.withSnapshot) {
|
||||
options.snapshot = '';
|
||||
}
|
||||
|
||||
createSnap(options);
|
||||
}
|
||||
|
||||
function createSnap(options) {
|
||||
options.saveNotebook.useCapability('mutation', function (model) {
|
||||
var entries = model.entries;
|
||||
var lastEntry = entries[entries.length - 1];
|
||||
var date = Date.now();
|
||||
|
||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
|
||||
model.entries.push({
|
||||
'id': date,
|
||||
'createdOn': date,
|
||||
'text': options.entry,
|
||||
'embeds': [{'type': options.embedObject.getId(),
|
||||
'id': '' + date,
|
||||
'cssClass': options.cssClass,
|
||||
'name': options.selectedModel.name,
|
||||
'snapshot': options.snapshot
|
||||
}]
|
||||
});
|
||||
}else {
|
||||
model.entries[entries.length - 1] = {
|
||||
'id': date,
|
||||
'createdOn': date,
|
||||
'text': options.entry,
|
||||
'embeds': [{'type': options.embedObject.getId(),
|
||||
'id': '' + date,
|
||||
'cssClass': options.cssClass,
|
||||
'name': options.selectedModel.name,
|
||||
'snapshot': options.snapshot
|
||||
}]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
notification.info({
|
||||
title: "Notebook Entry created"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
NewEntryContextual.appliesTo = function (context) {
|
||||
var domainObject = context.domainObject;
|
||||
|
||||
if (domainObject) {
|
||||
if (domainObject.getModel().type === 'notebook') {
|
||||
// do not allow in context of a notebook
|
||||
return false;
|
||||
} else if (domainObject.getModel().type.includes('imagery')) {
|
||||
// do not allow in the context of an object with imagery
|
||||
// (because of cross domain issue with snapshot)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return NewEntryContextual;
|
||||
}
|
||||
);
|
||||
@@ -1,130 +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(
|
||||
['zepto'],
|
||||
function ($) {
|
||||
|
||||
function SnapshotAction (exportImageService, dialogService, context) {
|
||||
this.exportImageService = exportImageService;
|
||||
this.dialogService = dialogService;
|
||||
this.domainObject = context.domainObject;
|
||||
}
|
||||
|
||||
SnapshotAction.prototype.perform = function () {
|
||||
var elementToSnapshot =
|
||||
$(document.body).find(".overlay .object-holder")[0] ||
|
||||
$(document.body).find("[key='representation.selected.key']")[0];
|
||||
|
||||
$(elementToSnapshot).addClass("s-status-taking-snapshot");
|
||||
|
||||
this.exportImageService.exportPNGtoSRC(elementToSnapshot).then(function (blob) {
|
||||
$(elementToSnapshot).removeClass("s-status-taking-snapshot");
|
||||
|
||||
if (blob) {
|
||||
var reader = new window.FileReader();
|
||||
reader.readAsDataURL(blob);
|
||||
reader.onloadend = function () {
|
||||
this.saveSnapshot(reader.result, blob.type, blob.size);
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
SnapshotAction.prototype.saveSnapshot = function (imageURL, imageType, imageSize) {
|
||||
var taskForm = this.generateTaskForm(),
|
||||
domainObject = this.domainObject,
|
||||
domainObjectId = domainObject.getId(),
|
||||
cssClass = domainObject.getCapability('type').typeDef.cssClass,
|
||||
name = domainObject.model.name;
|
||||
|
||||
this.dialogService.getDialogResponse(
|
||||
'overlay-dialog',
|
||||
taskForm,
|
||||
function () {
|
||||
return taskForm.value;
|
||||
}
|
||||
).then(function (options) {
|
||||
var snapshotObject = {
|
||||
src: imageURL,
|
||||
type: imageType,
|
||||
size: imageSize
|
||||
};
|
||||
|
||||
options.notebook.useCapability('mutation', function (model) {
|
||||
var date = Date.now();
|
||||
|
||||
model.entries.push({
|
||||
id: 'entry-' + date,
|
||||
createdOn: date,
|
||||
text: options.entry,
|
||||
embeds: [{
|
||||
name: name,
|
||||
cssClass: cssClass,
|
||||
type: domainObjectId,
|
||||
id: 'embed-' + date,
|
||||
createdOn: date,
|
||||
snapshot: snapshotObject
|
||||
}]
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
SnapshotAction.prototype.generateTaskForm = function () {
|
||||
var taskForm = {
|
||||
name: "Create a Notebook Entry",
|
||||
hint: "Please select a Notebook",
|
||||
sections: [{
|
||||
rows: [{
|
||||
name: 'Entry',
|
||||
key: 'entry',
|
||||
control: 'textarea',
|
||||
required: false,
|
||||
"cssClass": "l-textarea-sm"
|
||||
},
|
||||
{
|
||||
name: 'Save in Notebook',
|
||||
key: 'notebook',
|
||||
control: 'locator',
|
||||
validate: validateLocation
|
||||
}]
|
||||
}]
|
||||
};
|
||||
|
||||
var overlayModel = {
|
||||
title: taskForm.name,
|
||||
message: 'AHAHAH',
|
||||
structure: taskForm,
|
||||
value: {'entry': ""}
|
||||
};
|
||||
|
||||
function validateLocation(newParentObj) {
|
||||
return newParentObj.model.type === 'notebook';
|
||||
}
|
||||
|
||||
return overlayModel;
|
||||
};
|
||||
|
||||
return SnapshotAction;
|
||||
}
|
||||
);
|
||||
@@ -275,7 +275,6 @@ function (
|
||||
|
||||
function menuClickHandler(e) {
|
||||
e.stopPropagation();
|
||||
window.setTimeout(dismiss, 300);
|
||||
}
|
||||
|
||||
// Dismiss any menu which was already showing
|
||||
|
||||
@@ -105,23 +105,27 @@ function (
|
||||
}
|
||||
};
|
||||
|
||||
EntryController.prototype.dropOnEntry = function (entryId) {
|
||||
var selectedObject = this.dndService.getData('mct-domain-object'),
|
||||
selectedObjectId = selectedObject.getId(),
|
||||
selectedModel = selectedObject.getModel(),
|
||||
cssClass = selectedObject.getCapability('type').typeDef.cssClass,
|
||||
entryPos = this.entryPosById(entryId),
|
||||
currentEntryEmbeds = this.domainObject.entries[entryPos].embeds,
|
||||
newEmbed = {
|
||||
type: selectedObjectId,
|
||||
id: '' + Date.now(),
|
||||
cssClass: cssClass,
|
||||
name: selectedModel.name,
|
||||
snapshot: ''
|
||||
};
|
||||
EntryController.prototype.dropOnEntry = function (entryid, event) {
|
||||
|
||||
currentEntryEmbeds.push(newEmbed);
|
||||
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].embeds', currentEntryEmbeds);
|
||||
var data = event.dataTransfer.getData('openmct/domain-object');
|
||||
|
||||
if (data) {
|
||||
var selectedObject = JSON.parse(data),
|
||||
selectedObjectId = selectedObject.identifier.key,
|
||||
cssClass = this.openmct.types.get(selectedObject.type),
|
||||
entryPos = this.entryPosById(entryid),
|
||||
currentEntryEmbeds = this.domainObject.entries[entryPos].embeds,
|
||||
newEmbed = {
|
||||
type: selectedObjectId,
|
||||
id: '' + Date.now(),
|
||||
cssClass: cssClass,
|
||||
name: selectedObject.name,
|
||||
snapshot: ''
|
||||
};
|
||||
|
||||
currentEntryEmbeds.push(newEmbed);
|
||||
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].embeds', currentEntryEmbeds);
|
||||
}
|
||||
};
|
||||
|
||||
EntryController.prototype.dragoverOnEntry = function () {
|
||||
@@ -132,7 +136,6 @@ function (
|
||||
return {
|
||||
openmct: this.openmct,
|
||||
domainObject: this.domainObject,
|
||||
dndService: this.dndService,
|
||||
dialogService: this.dialogService,
|
||||
currentEntryValue: this.currentEntryValue
|
||||
};
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining NewEntryController. */
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
function NewEntryController($scope,$rootScope) {
|
||||
|
||||
$scope.snapshot = undefined;
|
||||
$scope.snapToggle = true;
|
||||
$scope.entryText = '';
|
||||
var annotateAction = $rootScope.selObj.getCapability('action').getActions({key: 'annotate-snapshot'})[0];
|
||||
|
||||
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = $rootScope.selObj;
|
||||
$scope.objectName = $rootScope.selObj.getModel().name;
|
||||
$scope.cssClass = $rootScope.selObj.getCapability('type').typeDef.cssClass;
|
||||
|
||||
$scope.annotateSnapshot = function ($event) {
|
||||
if ($rootScope.currentDialog.value) {
|
||||
$rootScope.newEntryText = $scope.$parent.$parent.ngModel.entry;
|
||||
$rootScope.currentDialog.cancel();
|
||||
annotateAction.perform($event, $rootScope.snapshot.src);
|
||||
$rootScope.currentDialog = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
function updateSnapshot(img) {
|
||||
$scope.snapshot = img;
|
||||
}
|
||||
// Update set of actions whenever the action capability
|
||||
// changes or becomes available.
|
||||
$rootScope.$watch("snapshot", updateSnapshot);
|
||||
|
||||
$rootScope.$watch("selValue", toggleEmbed);
|
||||
|
||||
function toggleEmbed(value) {
|
||||
$scope.snapToggle = value;
|
||||
}
|
||||
}
|
||||
|
||||
return NewEntryController;
|
||||
}
|
||||
);
|
||||
@@ -27,7 +27,7 @@ define([
|
||||
'../../res/templates/notebook.html',
|
||||
'../../res/templates/entry.html',
|
||||
'../../res/templates/embed.html',
|
||||
'../../../../ui/components/controls/search.vue'
|
||||
'../../../../ui/components/search.vue'
|
||||
],
|
||||
function (
|
||||
Vue,
|
||||
@@ -90,19 +90,23 @@ function (
|
||||
return {
|
||||
entrySearch: self.entrySearch,
|
||||
showTime: '0',
|
||||
sortEntries: '-createdOn',
|
||||
sortEntries: self.domainObject.defaultSort,
|
||||
entries: self.domainObject.entries,
|
||||
currentEntryValue: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredAndSortedEntries() {
|
||||
return this.sort(this.filterBySearch(this.entries, this.entrySearch), this.sortEntries);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
search: function (event) {
|
||||
if (event.target.value) {
|
||||
this.entrySearch = event.target.value;
|
||||
}
|
||||
search(value) {
|
||||
this.entrySearch = value;
|
||||
},
|
||||
newEntry: self.newEntry,
|
||||
filterBySearch: self.filterBySearch
|
||||
filterBySearch: self.filterBySearch,
|
||||
sort: self.sort
|
||||
}
|
||||
});
|
||||
|
||||
@@ -111,25 +115,48 @@ function (
|
||||
};
|
||||
|
||||
NotebookController.prototype.newEntry = function (event) {
|
||||
this.NotebookVue.search('');
|
||||
|
||||
var date = Date.now(),
|
||||
embed;
|
||||
|
||||
if (event.dataTransfer && event.dataTransfer.getData('openmct/domain-object')) {
|
||||
var selectedObject = JSON.parse(event.dataTransfer.getData('openmct/domain-object')),
|
||||
selectedObjectId = selectedObject.identifier.key,
|
||||
cssClass = this.openmct.types.get(selectedObject.type);
|
||||
|
||||
embed = {
|
||||
type: selectedObjectId,
|
||||
id: '' + date,
|
||||
cssClass: cssClass,
|
||||
name: selectedObject.name,
|
||||
snapshot: ''
|
||||
};
|
||||
}
|
||||
|
||||
var entries = this.domainObject.entries,
|
||||
lastEntryIndex = entries.length - 1,
|
||||
lastEntry = entries[lastEntryIndex],
|
||||
date = Date.now();
|
||||
lastEntryIndex = this.NotebookVue.sortEntries === 'newest' ? 0 : entries.length - 1,
|
||||
lastEntry = entries[lastEntryIndex];
|
||||
|
||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds.length) {
|
||||
var createdEntry = {'id': 'entry-' + date, 'createdOn': date, 'embeds':[]};
|
||||
|
||||
if (embed) {
|
||||
createdEntry.embeds.push(embed);
|
||||
}
|
||||
|
||||
entries.push(createdEntry);
|
||||
this.openmct.objects.mutate(this.domainObject, 'entries', entries);
|
||||
} else {
|
||||
lastEntry.createdOn = date;
|
||||
|
||||
this.openmct.objects.mutate(this.domainObject, 'entries[entries.length-1]', lastEntry);
|
||||
this.focusOnEntry.bind(this.NotebookVue.$children[lastEntryIndex])();
|
||||
}
|
||||
if(embed) {
|
||||
lastEntry.embeds.push(embed);
|
||||
}
|
||||
|
||||
this.entrySearch = '';
|
||||
this.openmct.objects.mutate(this.domainObject, 'entries[entries.length-1]', lastEntry);
|
||||
this.focusOnEntry.bind(this.NotebookVue.$children[lastEntryIndex+1])();
|
||||
}
|
||||
};
|
||||
|
||||
NotebookController.prototype.entryPosById = function (entryId) {
|
||||
@@ -167,6 +194,33 @@ function (
|
||||
}
|
||||
};
|
||||
|
||||
NotebookController.prototype.sort = function (array, sortDirection) {
|
||||
let oldest = (a,b) => {
|
||||
if (a.createdOn < b.createdOn) {
|
||||
return -1;
|
||||
} else if (a.createdOn > b.createdOn) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
newest = (a,b) => {
|
||||
if (a.createdOn < b.createdOn) {
|
||||
return 1;
|
||||
} else if (a.createdOn > b.createdOn) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
if (sortDirection === 'newest') {
|
||||
return array.sort(newest);
|
||||
} else {
|
||||
return array.sort(oldest);
|
||||
}
|
||||
};
|
||||
|
||||
NotebookController.prototype.show = function (container) {
|
||||
this.initializeVue(container);
|
||||
};
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['zepto'], function ($) {
|
||||
|
||||
function EntryDnd($rootScope,$compile,dndService,typeService,notificationService) {
|
||||
|
||||
function link($scope, $element) {
|
||||
|
||||
function drop(e) {
|
||||
var selectedObject = dndService.getData('mct-domain-object');
|
||||
var selectedModel = selectedObject.getModel();
|
||||
var cssClass = selectedObject.getCapability('type').typeDef.cssClass;
|
||||
var entryId = -1;
|
||||
var embedId = -1;
|
||||
$scope.clearSearch();
|
||||
if ($element[0].id === 'newEntry') {
|
||||
entryId = $scope.domainObject.model.entries.length;
|
||||
embedId = 0;
|
||||
var lastEntry = $scope.domainObject.model.entries[entryId - 1];
|
||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries.push({'createdOn': +Date.now(),
|
||||
'id': +Date.now(),
|
||||
'embeds': [{'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
}]
|
||||
});
|
||||
});
|
||||
}else {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[entryId - 1] =
|
||||
{'createdOn': +Date.now(),
|
||||
'embeds': [{'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
}]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
$scope.scrollToTop();
|
||||
notificationService.info({
|
||||
title: "Notebook Entry created"
|
||||
});
|
||||
|
||||
}else {
|
||||
entryId = $scope.findEntryPositionById(Number($element[0].id.replace('entry_', '')));
|
||||
|
||||
if (!$scope.domainObject.model.entries[entryId].embeds) {
|
||||
$scope.domainObject.model.entries[entryId].embeds = [];
|
||||
}
|
||||
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[entryId].embeds.push({'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
});
|
||||
});
|
||||
|
||||
embedId = $scope.domainObject.model.entries[entryId].embeds.length - 1;
|
||||
|
||||
if (selectedObject) {
|
||||
e.preventDefault();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ($(e.currentTarget).hasClass('drag-active')) {
|
||||
$(e.currentTarget).removeClass('drag-active');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function dragover(e) {
|
||||
if (!$(e.currentTarget).hasClass('drag-active')) {
|
||||
$(e.currentTarget).addClass('drag-active');
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for the drop itself
|
||||
$element.on('dragover', dragover);
|
||||
$element.on('drop', drop);
|
||||
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
$element.off('dragover', dragover);
|
||||
$element.off('drop', drop);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: link
|
||||
};
|
||||
}
|
||||
|
||||
return EntryDnd;
|
||||
|
||||
});
|
||||
@@ -1,86 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['zepto'], function ($) {
|
||||
function MCTSnapshot($rootScope, $document, exportImageService, dialogService, notificationService) {
|
||||
var document = $document[0];
|
||||
|
||||
function link($scope, $element, $attrs) {
|
||||
var objectElement = $(document.body).find(".overlay .object-holder")[0] || $(document.body).find("[key='representation.selected.key']")[0],
|
||||
takeSnapshot,
|
||||
makeImg,
|
||||
saveImg;
|
||||
|
||||
$(objectElement).addClass("s-status-taking-snapshot");
|
||||
|
||||
saveImg = function (url, entryId, embedId) {
|
||||
$scope.$parent.$parent.$parent.saveSnap(url, embedId, entryId);
|
||||
};
|
||||
|
||||
makeImg = function (el) {
|
||||
var scope = $scope;
|
||||
|
||||
exportImageService.exportPNGtoSRC(el).then(function (img) {
|
||||
|
||||
$(objectElement).removeClass("s-status-taking-snapshot");
|
||||
|
||||
if (img) {
|
||||
if ($element[0].dataset.entry && $element[0].dataset.embed) {
|
||||
saveImg(img, +$element[0].dataset.entry, +$element[0].dataset.embed);
|
||||
} else {
|
||||
var reader = new window.FileReader();
|
||||
reader.readAsDataURL(img);
|
||||
reader.onloadend = function () {
|
||||
$($element[0]).attr("data-snapshot", reader.result);
|
||||
$rootScope.snapshot = {
|
||||
'src': reader.result,
|
||||
'type': img.type,
|
||||
'size': img.size,
|
||||
'modified': Date.now()
|
||||
};
|
||||
scope.$destroy();
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
takeSnapshot = function () {
|
||||
makeImg(objectElement);
|
||||
};
|
||||
|
||||
takeSnapshot();
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
$element.remove();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: link
|
||||
};
|
||||
}
|
||||
|
||||
return MCTSnapshot;
|
||||
|
||||
});
|
||||
@@ -137,8 +137,8 @@ define(
|
||||
* @returns {promise}
|
||||
*/
|
||||
|
||||
ExportImageService.prototype.exportPNGtoSRC = function (element) {
|
||||
return this.renderElement(element, "png");
|
||||
ExportImageService.prototype.exportPNGtoSRC = function (element, className) {
|
||||
return this.renderElement(element, "png", className);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,8 +37,10 @@ define([
|
||||
'./notebook/plugin',
|
||||
'./displayLayout/plugin',
|
||||
'./folderView/plugin',
|
||||
'./flexibleLayout/plugin',
|
||||
'./tabs/plugin',
|
||||
'../../platform/features/fixed/plugin'
|
||||
'../../platform/features/fixed/plugin',
|
||||
'./preview/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@@ -56,8 +58,10 @@ define([
|
||||
Notebook,
|
||||
DisplayLayoutPlugin,
|
||||
FolderView,
|
||||
FlexibleLayout,
|
||||
Tabs,
|
||||
FixedView
|
||||
FixedView,
|
||||
PreviewPlugin
|
||||
) {
|
||||
var bundleMap = {
|
||||
LocalStorage: 'platform/persistence/local',
|
||||
@@ -171,6 +175,8 @@ define([
|
||||
plugins.FolderView = FolderView;
|
||||
plugins.Tabs = Tabs;
|
||||
plugins.FixedView = FixedView;
|
||||
plugins.FlexibleLayout = FlexibleLayout;
|
||||
plugins.Preview = PreviewPlugin.default;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
||||
93
src/plugins/preview/Preview.vue
Normal file
93
src/plugins/preview/Preview.vue
Normal 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.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div class="l-preview-window">
|
||||
<div class="l-preview-window__object-name l-browse-bar__object-name--w" :class="type.cssClass">
|
||||
<span class="l-browse-bar__object-name">
|
||||
{{ domainObject.name }}
|
||||
</span>
|
||||
<context-menu-drop-down :object-path="objectPath"></context-menu-drop-down>
|
||||
</div>
|
||||
<div class="l-preview-window__object-view">
|
||||
<div ref="objectView">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
@import '~styles/sass-base';
|
||||
|
||||
.l-preview-window {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0; right: 0; bottom: 0; left: 0;
|
||||
|
||||
> * + * {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
|
||||
&__object-name {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__object-view {
|
||||
flex: 1 1 auto;
|
||||
|
||||
> div:not([class]) {
|
||||
// Target an immediate child div without a class and make it display: contents
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContextMenuDropDown
|
||||
},
|
||||
inject: [
|
||||
'openmct',
|
||||
'objectPath'
|
||||
],
|
||||
data() {
|
||||
let domainObject = this.objectPath[0];
|
||||
let type = this.openmct.types.get(domainObject.type);
|
||||
|
||||
return {
|
||||
domainObject: domainObject,
|
||||
type: type
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
let viewProvider = this.openmct.objectViews.get(this.domainObject)[0];
|
||||
this.view = viewProvider.view(this.domainObject);
|
||||
this.view.show(this.$refs.objectView);
|
||||
},
|
||||
destroy() {
|
||||
this.view.destroy();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -19,40 +19,46 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import Preview from './Preview.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
define(['zepto'], function ($) {
|
||||
function HoverGesture(hoverManager) {
|
||||
this.hoverManager = hoverManager;
|
||||
export default class PreviewAction {
|
||||
constructor(openmct) {
|
||||
/**
|
||||
* Metadata
|
||||
*/
|
||||
this.name = 'Preview';
|
||||
this.description = 'Preview in large dialog';
|
||||
this.cssClass = 'icon-eye-open';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
this._openmct = openmct;
|
||||
}
|
||||
invoke(objectPath) {
|
||||
let preview = new Vue({
|
||||
components: {
|
||||
Preview
|
||||
},
|
||||
provide: {
|
||||
openmct: this._openmct,
|
||||
objectPath: objectPath
|
||||
},
|
||||
template: '<Preview></Preview>'
|
||||
});
|
||||
preview.$mount();
|
||||
|
||||
HoverGesture.prototype.apply = function (htmlElement) {
|
||||
var $element = $(htmlElement);
|
||||
var hoverManager = this.hoverManager;
|
||||
|
||||
function update() {
|
||||
$(hoverManager.all()).removeClass('hovering');
|
||||
$(hoverManager.top()).addClass('hovering');
|
||||
}
|
||||
|
||||
function enter() {
|
||||
hoverManager.add(htmlElement);
|
||||
update();
|
||||
}
|
||||
|
||||
function leave() {
|
||||
hoverManager.remove(htmlElement);
|
||||
update();
|
||||
}
|
||||
|
||||
$element.on('mouseenter', enter);
|
||||
$element.on('mouseleave', leave);
|
||||
|
||||
return function () {
|
||||
leave();
|
||||
$element.off('mouseenter', enter);
|
||||
$element.off('mouseleave', leave);
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
return HoverGesture;
|
||||
});
|
||||
let overlay = this._openmct.overlays.overlay({
|
||||
element: preview.$el,
|
||||
size: 'large',
|
||||
buttons: [
|
||||
{
|
||||
label: 'Done',
|
||||
callback: () => overlay.dismiss()
|
||||
}
|
||||
],
|
||||
onDestroy: () => preview.$destroy()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
@@ -19,26 +19,10 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import PreviewAction from './PreviewAction.js';
|
||||
|
||||
/**
|
||||
* Module defining SelectSnapshotController. */
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
function SelectSnapshotController($scope,$rootScope) {
|
||||
|
||||
$scope.selectModel = true;
|
||||
|
||||
function selectprint(value) {
|
||||
$rootScope.selValue = value;
|
||||
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = value;
|
||||
}
|
||||
|
||||
$scope.$watch("selectModel", selectprint);
|
||||
|
||||
}
|
||||
|
||||
return SelectSnapshotController;
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
openmct.contextMenu.registerAction(new PreviewAction(openmct));
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -15,14 +15,14 @@
|
||||
v-for="(tab,index) in tabsList"
|
||||
:key="index"
|
||||
:class="[
|
||||
{'is-current': tab=== currentTab},
|
||||
{'is-current': tab=== currentTab},
|
||||
tab.type.definition.cssClass
|
||||
]"
|
||||
@click="showTab(tab)">
|
||||
<span class="c-button__label">{{tab.model.name}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="c-tabs-view__object-holder"
|
||||
<div class="c-tabs-view__object-holder"
|
||||
v-for="(object, index) in tabsList"
|
||||
:key="index"
|
||||
:class="{'invisible': object !== currentTab}">
|
||||
@@ -66,6 +66,7 @@
|
||||
|
||||
&__object-name {
|
||||
flex: 0 0 auto;
|
||||
@include headerFont();
|
||||
font-size: 1.2em !important;
|
||||
margin: $interiorMargin 0 $interiorMarginLg 0;
|
||||
}
|
||||
@@ -85,7 +86,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ObjectView from '../../../ui/components/layout/ObjectView.vue';
|
||||
import ObjectView from '../../../ui/components/ObjectView.vue';
|
||||
|
||||
var unknownObjectType = {
|
||||
definition: {
|
||||
@@ -146,7 +147,7 @@ export default {
|
||||
this.setCurrentTab = true;
|
||||
},
|
||||
dragstart (e) {
|
||||
if (e.dataTransfer.getData('domainObject')) {
|
||||
if (e.dataTransfer.getData('openmct/domain-object')) {
|
||||
this.isDragging = true;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -46,6 +46,10 @@ define([
|
||||
getConfiguration() {
|
||||
let configuration = this.domainObject.configuration || {};
|
||||
configuration.hiddenColumns = configuration.hiddenColumns || {};
|
||||
configuration.columnWidths = configuration.columnWidths || {};
|
||||
configuration.columnOrder = configuration.columnOrder || [];
|
||||
configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize;
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@@ -113,7 +117,7 @@ define([
|
||||
let headers = _.uniq(flattenedColumns, false, column => column.getKey())
|
||||
.reduce(fromColumnsToHeadersMap, {});
|
||||
|
||||
function fromColumnsToHeadersMap(headersMap, column){
|
||||
function fromColumnsToHeadersMap(headersMap, column) {
|
||||
headersMap[column.getKey()] = column.getTitle();
|
||||
return headersMap;
|
||||
}
|
||||
@@ -122,16 +126,42 @@ define([
|
||||
}
|
||||
|
||||
getVisibleHeaders() {
|
||||
let headers = this.getAllHeaders();
|
||||
let allHeaders = this.getAllHeaders();
|
||||
let configuration = this.getConfiguration();
|
||||
|
||||
Object.keys(headers).forEach((headerKey) => {
|
||||
if (configuration.hiddenColumns[headerKey] === true) {
|
||||
delete headers[headerKey];
|
||||
}
|
||||
});
|
||||
let orderedColumns = this.getColumnOrder();
|
||||
let unorderedColumns = _.difference(Object.keys(allHeaders), orderedColumns);
|
||||
|
||||
return headers;
|
||||
return orderedColumns.concat(unorderedColumns)
|
||||
.filter((headerKey) => {
|
||||
return configuration.hiddenColumns[headerKey] !== true;
|
||||
})
|
||||
.reduce((headers, headerKey) => {
|
||||
headers[headerKey] = allHeaders[headerKey];
|
||||
return headers;
|
||||
}, {});
|
||||
}
|
||||
|
||||
getColumnWidths() {
|
||||
let configuration = this.getConfiguration();
|
||||
return configuration.columnWidths;
|
||||
}
|
||||
|
||||
setColumnWidths(columnWidths) {
|
||||
let configuration = this.getConfiguration();
|
||||
configuration.columnWidths = columnWidths;
|
||||
this.updateConfiguration(configuration);
|
||||
}
|
||||
|
||||
getColumnOrder() {
|
||||
let configuration = this.getConfiguration();
|
||||
return configuration.columnOrder;
|
||||
}
|
||||
|
||||
setColumnOrder(columnOrder) {
|
||||
let configuration = this.getConfiguration();
|
||||
configuration.columnOrder = columnOrder;
|
||||
this.updateConfiguration(configuration);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@@ -140,4 +170,4 @@ define([
|
||||
}
|
||||
|
||||
return TelemetryTableConfiguration;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,7 +29,8 @@ define(function () {
|
||||
initialize(domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {
|
||||
columns: {}
|
||||
columnWidths: {},
|
||||
hiddenColumns: {}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
182
src/plugins/telemetryTable/components/table-column-header.vue
Normal file
182
src/plugins/telemetryTable/components/table-column-header.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
<th
|
||||
:style="{ width: columnWidth + 'px', 'max-width': columnWidth + 'px'}"
|
||||
:draggable="isEditing"
|
||||
@mouseup="sort"
|
||||
v-on="isEditing ? {
|
||||
dragstart: columnMoveStart,
|
||||
drop: columnMoveEnd,
|
||||
dragleave: hideDropTarget,
|
||||
dragover: dragOverColumn
|
||||
} : {}">
|
||||
<div class="c-telemetry-table__headers__content" :class="[
|
||||
isSortable ? 'is-sortable' : '',
|
||||
isSortable && sortOptions.key === headerKey ? 'is-sorting' : '',
|
||||
isSortable && sortOptions.direction].join(' ')">
|
||||
<div v-if="isEditing" class="c-telemetry-table__resize-hotzone c-telemetry-table__resize-hotzone--right"
|
||||
@mousedown="resizeColumnStart"
|
||||
></div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</th>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
@import "~styles/table";
|
||||
|
||||
$hotzone-size: 6px;
|
||||
|
||||
.c-telemetry-table__headers__content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c-table.c-telemetry-table {
|
||||
.c-telemetry-table__resize-hotzone {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: $hotzone-size;
|
||||
min-width: $hotzone-size;
|
||||
cursor: col-resize;
|
||||
border: none;
|
||||
right: 0px;
|
||||
margin-right: -$tabularTdPadLR - 1 - $hotzone-size / 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
const MOVE_COLUMN_DT_TYPE = 'movecolumnfromindex';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
return {
|
||||
isEditing: this.openmct.editor.isEditing()
|
||||
}
|
||||
},
|
||||
props: {
|
||||
headerKey: String,
|
||||
headerIndex: Number,
|
||||
isHeaderTitle: Boolean,
|
||||
sortOptions: Object,
|
||||
columnWidth: Number,
|
||||
hotzone: Boolean
|
||||
},
|
||||
computed: {
|
||||
isSortable() {
|
||||
return this.sortOptions !== undefined;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resizeColumnStart(event) {
|
||||
this.resizeStartX = event.clientX;
|
||||
this.resizeStartWidth = this.columnWidth;
|
||||
|
||||
document.addEventListener('mouseup', this.resizeColumnEnd, {once: true, capture: true});
|
||||
document.addEventListener('mousemove', this.resizeColumn);
|
||||
event.preventDefault();
|
||||
},
|
||||
resizeColumnEnd(event) {
|
||||
this.resizeStartX = undefined;
|
||||
this.resizeStartWidth = undefined;
|
||||
document.removeEventListener('mousemove', this.resizeColumn);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.$emit('resizeColumnEnd');
|
||||
},
|
||||
resizeColumn(event) {
|
||||
let delta = event.clientX - this.resizeStartX;
|
||||
let newWidth = this.resizeStartWidth + delta;
|
||||
let minWidth = parseInt(window.getComputedStyle(this.$el).minWidth);
|
||||
if (newWidth > minWidth) {
|
||||
this.$emit('resizeColumn', this.headerKey, newWidth);
|
||||
}
|
||||
},
|
||||
columnMoveStart(event) {
|
||||
event.dataTransfer.setData(MOVE_COLUMN_DT_TYPE, this.headerIndex);
|
||||
},
|
||||
isColumnMoveEvent(event) {
|
||||
return [...event.dataTransfer.types].includes(MOVE_COLUMN_DT_TYPE);
|
||||
},
|
||||
dragOverColumn(event) {
|
||||
if (this.isColumnMoveEvent(event)){
|
||||
event.preventDefault();
|
||||
this.updateDropOffset(event.currentTarget, event.clientX);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
updateDropOffset(element, clientX) {
|
||||
let thClientLeft = element.getBoundingClientRect().x;
|
||||
let offsetInHeader = clientX - thClientLeft;
|
||||
let dropOffsetLeft;
|
||||
|
||||
if (offsetInHeader < element.offsetWidth / 2) {
|
||||
dropOffsetLeft = element.offsetLeft;
|
||||
} else {
|
||||
dropOffsetLeft = element.offsetLeft + element.offsetWidth;
|
||||
}
|
||||
this.$emit('dropTargetOffsetChanged', dropOffsetLeft);
|
||||
this.$emit('dropTargetActive', true);
|
||||
},
|
||||
hideDropTarget(){
|
||||
this.$emit('dropTargetActive', false);
|
||||
},
|
||||
columnMoveEnd(event){
|
||||
if (this.isColumnMoveEvent(event)){
|
||||
let toIndex = this.headerIndex;
|
||||
let fromIndex = event.dataTransfer.getData(MOVE_COLUMN_DT_TYPE);
|
||||
if (event.offsetX < event.target.offsetWidth / 2) {
|
||||
if (toIndex > fromIndex){
|
||||
toIndex--;
|
||||
}
|
||||
} else {
|
||||
if (toIndex < fromIndex){
|
||||
toIndex++;
|
||||
}
|
||||
}
|
||||
if (toIndex !== fromIndex) {
|
||||
this.$emit('reorderColumn', fromIndex, toIndex);
|
||||
}
|
||||
}
|
||||
},
|
||||
sort() {
|
||||
this.$emit("sort");
|
||||
},
|
||||
toggleEditMode(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.openmct.editor.on('isEditing', this.toggleEditMode);
|
||||
},
|
||||
destroyed() {
|
||||
this.openmct.editor.off('isEditing', this.toggleEditMode);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user