Compare commits
1 Commits
misc-ui-4b
...
layout-imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50e994f982 |
@@ -37,25 +37,25 @@ define([
|
||||
},
|
||||
LIMITS = {
|
||||
rh: {
|
||||
cssClass: "is-limit--upr is-limit--red",
|
||||
cssClass: "s-limit-upr s-limit-red",
|
||||
low: RED,
|
||||
high: Number.POSITIVE_INFINITY,
|
||||
name: "Red High"
|
||||
},
|
||||
rl: {
|
||||
cssClass: "is-limit--lwr is-limit--red",
|
||||
cssClass: "s-limit-lwr s-limit-red",
|
||||
high: -RED,
|
||||
low: Number.NEGATIVE_INFINITY,
|
||||
name: "Red Low"
|
||||
},
|
||||
yh: {
|
||||
cssClass: "is-limit--upr is-limit--yellow",
|
||||
cssClass: "s-limit-upr s-limit-yellow",
|
||||
low: YELLOW,
|
||||
high: RED,
|
||||
name: "Yellow High"
|
||||
},
|
||||
yl: {
|
||||
cssClass: "is-limit--lwr is-limit--yellow",
|
||||
cssClass: "s-limit-lwr s-limit-yellow",
|
||||
low: -RED,
|
||||
high: -YELLOW,
|
||||
name: "Yellow Low"
|
||||
|
||||
@@ -79,7 +79,6 @@
|
||||
openmct.install(openmct.plugins.FolderView());
|
||||
openmct.install(openmct.plugins.Tabs());
|
||||
openmct.install(openmct.plugins.FlexibleLayout());
|
||||
openmct.install(openmct.plugins.LADTable());
|
||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||
openmct.time.timeSystem('utc');
|
||||
openmct.start();
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"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,6 +31,7 @@ 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",
|
||||
@@ -52,6 +53,7 @@ define([
|
||||
NavigateAction,
|
||||
OrphanNavigationHandler,
|
||||
NewTabAction,
|
||||
FullscreenAction,
|
||||
WindowTitler,
|
||||
browseTemplate,
|
||||
browseObjectTemplate,
|
||||
@@ -223,6 +225,13 @@ define([
|
||||
"group": "windowing",
|
||||
"cssClass": "icon-new-window",
|
||||
"priority": "preferred"
|
||||
},
|
||||
{
|
||||
"key": "fullscreen",
|
||||
"implementation": FullscreenAction,
|
||||
"category": "view-control",
|
||||
"group": "windowing",
|
||||
"priority": "default"
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
@@ -256,6 +265,18 @@ 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
64
platform/commonUI/browse/src/windowing/FullscreenAction.js
Normal file
64
platform/commonUI/browse/src/windowing/FullscreenAction.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,59 @@
|
||||
/*****************************************************************************
|
||||
* 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,10 +95,7 @@ define(
|
||||
|
||||
// Create the overlay element and add it to the document's body
|
||||
element = this.$compile(TEMPLATE)(scope);
|
||||
|
||||
// Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when
|
||||
// multiple overlays with the same z-index are active.
|
||||
this.findBody().append(element);
|
||||
this.findBody().prepend(element);
|
||||
|
||||
return {
|
||||
dismiss: dismiss
|
||||
|
||||
@@ -32,7 +32,11 @@ define([
|
||||
"./src/actions/SaveAndStopEditingAction",
|
||||
"./src/actions/SaveAsAction",
|
||||
"./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",
|
||||
@@ -63,7 +67,11 @@ define([
|
||||
SaveAndStopEditingAction,
|
||||
SaveAsAction,
|
||||
CancelAction,
|
||||
EditActionPolicy,
|
||||
EditPersistableObjectsPolicy,
|
||||
EditableLinkPolicy,
|
||||
EditableMovePolicy,
|
||||
EditContextualActionPolicy,
|
||||
EditRepresenter,
|
||||
EditorCapability,
|
||||
TransactionCapabilityDecorator,
|
||||
@@ -166,7 +174,7 @@ define([
|
||||
"name": "Remove",
|
||||
"description": "Remove this object from its containing object.",
|
||||
"depends": [
|
||||
"openmct",
|
||||
"dialogService",
|
||||
"navigationService"
|
||||
]
|
||||
},
|
||||
@@ -223,11 +231,28 @@ define([
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": EditActionPolicy
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": EditPersistableObjectsPolicy,
|
||||
"depends": ["openmct"]
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": EditContextualActionPolicy,
|
||||
"depends": ["navigationService", "editModeBlacklist", "nonEditContextBlacklist"]
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": EditableMovePolicy
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": EditableLinkPolicy
|
||||
},
|
||||
{
|
||||
"implementation": CreationPolicy,
|
||||
"category": "creation"
|
||||
@@ -324,6 +349,16 @@ 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(openmct, navigationService, context) {
|
||||
function RemoveAction(dialogService, navigationService, context) {
|
||||
this.domainObject = (context || {}).domainObject;
|
||||
this.openmct = openmct;
|
||||
this.dialogService = dialogService;
|
||||
this.navigationService = navigationService;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ define([
|
||||
*/
|
||||
RemoveAction.prototype.perform = function () {
|
||||
var dialog,
|
||||
dialogService = this.dialogService,
|
||||
domainObject = this.domainObject,
|
||||
navigationService = this.navigationService;
|
||||
/*
|
||||
@@ -103,13 +104,13 @@ define([
|
||||
* capability. Based on object's location and selected object's location
|
||||
* user may be navigated to existing parent object
|
||||
*/
|
||||
function removeFromContext() {
|
||||
var contextCapability = domainObject.getCapability('context'),
|
||||
function removeFromContext(object) {
|
||||
var contextCapability = object.getCapability('context'),
|
||||
parent = contextCapability.getParent();
|
||||
|
||||
// If currently within path of removed object(s),
|
||||
// navigates to existing object up tree
|
||||
checkObjectNavigation(domainObject, parent);
|
||||
checkObjectNavigation(object, parent);
|
||||
|
||||
return parent.useCapability('mutation', doMutate);
|
||||
}
|
||||
@@ -118,7 +119,7 @@ define([
|
||||
* Pass in the function to remove the domain object so it can be
|
||||
* associated with an 'OK' button press
|
||||
*/
|
||||
dialog = new RemoveDialog(this.openmct, domainObject, removeFromContext);
|
||||
dialog = new RemoveDialog(dialogService, domainObject, removeFromContext);
|
||||
dialog.show();
|
||||
};
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ define([], function () {
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function RemoveDialog(openmct, domainObject, removeCallback) {
|
||||
this.openmct = openmct;
|
||||
function RemoveDialog(dialogService, domainObject, removeCallback) {
|
||||
this.dialogService = dialogService;
|
||||
this.domainObject = domainObject;
|
||||
this.removeCallback = removeCallback;
|
||||
}
|
||||
@@ -46,26 +46,31 @@ define([], function () {
|
||||
* Display a dialog to confirm the removal of a domain object.
|
||||
*/
|
||||
RemoveDialog.prototype.show = function () {
|
||||
let dialog = this.openmct.overlays.dialog({
|
||||
title: 'Remove ' + this.domainObject.getModel().name,
|
||||
iconClass: 'alert',
|
||||
message: 'Warning! This action will permanently remove this object. Are you sure you want to continue?',
|
||||
buttons: [
|
||||
{
|
||||
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: {
|
||||
label: 'OK',
|
||||
callback: () => {
|
||||
this.removeCallback();
|
||||
callback: function () {
|
||||
removeCallback(domainObject);
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
options: [
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
]
|
||||
};
|
||||
setTimeout(() => this.removeCallback(domainObject));
|
||||
|
||||
};
|
||||
|
||||
return RemoveDialog;
|
||||
|
||||
111
platform/commonUI/edit/src/policies/EditActionPolicy.js
Normal file
111
platform/commonUI/edit/src/policies/EditActionPolicy.js
Normal file
@@ -0,0 +1,111 @@
|
||||
/*****************************************************************************
|
||||
* 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 when the `edit` and/or `properties` actions
|
||||
* can appear as applicable actions of the `view-control` category
|
||||
* (shown as buttons in the top-right of browse mode.)
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Policy.<Action, ActionContext>}
|
||||
*/
|
||||
function EditActionPolicy(policyService) {
|
||||
this.policyService = policyService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a count of views which are not flagged as non-editable.
|
||||
* @private
|
||||
*/
|
||||
EditActionPolicy.prototype.countEditableViews = function (context) {
|
||||
var domainObject = context.domainObject,
|
||||
count = 0,
|
||||
type, views;
|
||||
|
||||
if (!domainObject) {
|
||||
return count;
|
||||
}
|
||||
|
||||
type = domainObject.getCapability('type');
|
||||
views = domainObject.useCapability('view');
|
||||
|
||||
|
||||
// A view is editable unless explicitly flagged as not
|
||||
(views || []).forEach(function (view) {
|
||||
if (isEditable(view) ||
|
||||
(view.key === 'plot' && type.getKey() === 'telemetry.panel') ||
|
||||
(view.key === 'table' && type.getKey() === 'table') ||
|
||||
(view.key === 'rt-table' && type.getKey() === 'rttable')
|
||||
) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
function isEditable(view) {
|
||||
if (typeof view.editable === Function) {
|
||||
return view.editable(domainObject.useCapability('adapter'));
|
||||
} else {
|
||||
return view.editable === true;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the domain object is currently being edited. If
|
||||
* so, the edit action is not applicable.
|
||||
* @param context
|
||||
* @returns {*|boolean}
|
||||
*/
|
||||
function isEditing(context) {
|
||||
var domainObject = (context || {}).domainObject;
|
||||
return domainObject &&
|
||||
domainObject.hasCapability('editor') &&
|
||||
domainObject.getCapability('editor').isEditContextRoot();
|
||||
}
|
||||
|
||||
EditActionPolicy.prototype.allow = function (action, context) {
|
||||
var key = action.getMetadata().key,
|
||||
category = (context || {}).category;
|
||||
|
||||
// Restrict 'edit' to cases where there are editable
|
||||
// views (similarly, restrict 'properties' to when
|
||||
// the converse is true), and where the domain object is not
|
||||
// already being edited.
|
||||
if (key === 'edit') {
|
||||
return this.countEditableViews(context) > 0 && !isEditing(context);
|
||||
} else if (key === 'properties' && category === 'view-control') {
|
||||
return this.countEditableViews(context) < 1 && !isEditing(context);
|
||||
}
|
||||
|
||||
// Like all policies, allow by default.
|
||||
return true;
|
||||
};
|
||||
|
||||
return EditActionPolicy;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,75 @@
|
||||
/*****************************************************************************
|
||||
* 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() : {};
|
||||
|
||||
// FIXME: need to restore support for changing contextual actions
|
||||
// based on edit mode.
|
||||
// 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 true;
|
||||
};
|
||||
|
||||
return EditContextualActionPolicy;
|
||||
}
|
||||
);
|
||||
51
platform/commonUI/edit/src/policies/EditableLinkPolicy.js
Normal file
51
platform/commonUI/edit/src/policies/EditableLinkPolicy.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
});
|
||||
52
platform/commonUI/edit/src/policies/EditableMovePolicy.js
Normal file
52
platform/commonUI/edit/src/policies/EditableMovePolicy.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
});
|
||||
49
platform/commonUI/edit/src/policies/EditableViewPolicy.js
Normal file
49
platform/commonUI/edit/src/policies/EditableViewPolicy.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*****************************************************************************
|
||||
* 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 which views should be visible in Edit mode.
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Policy.<View, DomainObject>}
|
||||
*/
|
||||
function EditableViewPolicy() {
|
||||
}
|
||||
|
||||
EditableViewPolicy.prototype.allow = function (view, domainObject) {
|
||||
// If a view is flagged as non-editable, only allow it
|
||||
// while we're not in Edit mode.
|
||||
if ((view || {}).editable === false) {
|
||||
return !(domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext());
|
||||
}
|
||||
|
||||
// Like all policies, allow by default.
|
||||
return true;
|
||||
};
|
||||
|
||||
return EditableViewPolicy;
|
||||
}
|
||||
);
|
||||
@@ -29,7 +29,7 @@ define(
|
||||
actionContext,
|
||||
capabilities,
|
||||
mockContext,
|
||||
mockOverlayAPI,
|
||||
mockDialogService,
|
||||
mockDomainObject,
|
||||
mockMutation,
|
||||
mockNavigationService,
|
||||
@@ -68,9 +68,9 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
mockOverlayAPI = jasmine.createSpyObj(
|
||||
"overlayAPI",
|
||||
["dialog"]
|
||||
mockDialogService = jasmine.createSpyObj(
|
||||
"dialogService",
|
||||
["showBlockingMessage"]
|
||||
);
|
||||
|
||||
mockNavigationService = jasmine.createSpyObj(
|
||||
@@ -96,7 +96,7 @@ define(
|
||||
|
||||
actionContext = { domainObject: mockDomainObject };
|
||||
|
||||
action = new RemoveAction({overlays: mockOverlayAPI}, mockNavigationService, actionContext);
|
||||
action = new RemoveAction(mockDialogService, mockNavigationService, actionContext);
|
||||
});
|
||||
|
||||
it("only applies to objects with parents", function () {
|
||||
@@ -118,7 +118,7 @@ define(
|
||||
|
||||
action.perform();
|
||||
|
||||
expect(mockOverlayAPI.dialog).toHaveBeenCalled();
|
||||
expect(mockDialogService.showBlockingMessage).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"]);
|
||||
|
||||
mockOverlayAPI.dialog.and.returnValue(mockDialogHandle);
|
||||
mockDialogService.showBlockingMessage.and.returnValue(mockDialogHandle);
|
||||
});
|
||||
|
||||
it("mutates the parent when performed", function () {
|
||||
action.perform();
|
||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
||||
.buttons[0].callback();
|
||||
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||
.primaryOption.callback();
|
||||
|
||||
expect(mockMutation.invoke)
|
||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||
@@ -174,8 +174,8 @@ define(
|
||||
var mutator, result;
|
||||
|
||||
action.perform();
|
||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
||||
.buttons[0].callback();
|
||||
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||
.primaryOption.callback();
|
||||
|
||||
mutator = mockMutation.invoke.calls.mostRecent().args[0];
|
||||
result = mutator(model);
|
||||
@@ -212,8 +212,8 @@ define(
|
||||
mockType.hasFeature.and.returnValue(true);
|
||||
|
||||
action.perform();
|
||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
||||
.buttons[0].callback();
|
||||
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||
.primaryOption.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();
|
||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
||||
.buttons[0].callback();
|
||||
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||
.primaryOption.callback();
|
||||
|
||||
// Expects no navigation to occur
|
||||
expect(mockNavigationService.setNavigation).not.toHaveBeenCalled();
|
||||
|
||||
138
platform/commonUI/edit/test/policies/EditActionPolicySpec.js
Normal file
138
platform/commonUI/edit/test/policies/EditActionPolicySpec.js
Normal file
@@ -0,0 +1,138 @@
|
||||
/*****************************************************************************
|
||||
* 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/policies/EditActionPolicy"],
|
||||
function (EditActionPolicy) {
|
||||
|
||||
describe("The Edit action policy", function () {
|
||||
var editableView,
|
||||
nonEditableView,
|
||||
testViews,
|
||||
testContext,
|
||||
mockDomainObject,
|
||||
mockEditAction,
|
||||
mockPropertiesAction,
|
||||
mockTypeCapability,
|
||||
mockEditorCapability,
|
||||
capabilities,
|
||||
plotView,
|
||||
policy;
|
||||
|
||||
beforeEach(function () {
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
[
|
||||
'useCapability',
|
||||
'hasCapability',
|
||||
'getCapability'
|
||||
]
|
||||
);
|
||||
mockEditorCapability = jasmine.createSpyObj('editorCapability', ['isEditContextRoot']);
|
||||
mockTypeCapability = jasmine.createSpyObj('type', ['getKey']);
|
||||
capabilities = {
|
||||
'editor': mockEditorCapability,
|
||||
'type': mockTypeCapability
|
||||
};
|
||||
|
||||
mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||
mockPropertiesAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||
|
||||
mockDomainObject.getCapability.and.callFake(function (capability) {
|
||||
return capabilities[capability];
|
||||
});
|
||||
mockDomainObject.hasCapability.and.callFake(function (capability) {
|
||||
return !!capabilities[capability];
|
||||
});
|
||||
|
||||
editableView = { editable: true };
|
||||
nonEditableView = { editable: false };
|
||||
plotView = { key: "plot", editable: false };
|
||||
testViews = [];
|
||||
|
||||
mockDomainObject.useCapability.and.callFake(function (c) {
|
||||
// Provide test views, only for the view capability
|
||||
return c === 'view' && testViews;
|
||||
});
|
||||
|
||||
mockEditAction.getMetadata.and.returnValue({ key: 'edit' });
|
||||
mockPropertiesAction.getMetadata.and.returnValue({ key: 'properties' });
|
||||
|
||||
testContext = {
|
||||
domainObject: mockDomainObject,
|
||||
category: 'view-control'
|
||||
};
|
||||
|
||||
policy = new EditActionPolicy();
|
||||
});
|
||||
|
||||
it("allows the edit action when there are editable views", function () {
|
||||
testViews = [editableView];
|
||||
expect(policy.allow(mockEditAction, testContext)).toBe(true);
|
||||
});
|
||||
|
||||
it("allows the edit properties action when there are no editable views", function () {
|
||||
testViews = [nonEditableView, nonEditableView];
|
||||
expect(policy.allow(mockPropertiesAction, testContext)).toBe(true);
|
||||
});
|
||||
|
||||
it("disallows the edit action when there are no editable views", function () {
|
||||
testViews = [nonEditableView, nonEditableView];
|
||||
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||
});
|
||||
|
||||
it("disallows the edit properties action when there are" +
|
||||
" editable views", function () {
|
||||
testViews = [editableView];
|
||||
expect(policy.allow(mockPropertiesAction, testContext)).toBe(false);
|
||||
});
|
||||
|
||||
it("disallows the edit action when object is already being" +
|
||||
" edited", function () {
|
||||
testViews = [editableView];
|
||||
mockEditorCapability.isEditContextRoot.and.returnValue(true);
|
||||
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||
});
|
||||
|
||||
it("allows editing of panels in plot view", function () {
|
||||
testViews = [plotView];
|
||||
mockTypeCapability.getKey.and.returnValue('telemetry.panel');
|
||||
|
||||
expect(policy.allow(mockEditAction, testContext)).toBe(true);
|
||||
});
|
||||
|
||||
it("disallows editing of plot view when object not a panel type", function () {
|
||||
testViews = [plotView];
|
||||
mockTypeCapability.getKey.and.returnValue('something.else');
|
||||
|
||||
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it("allows the edit properties outside of the 'view-control' category", function () {
|
||||
testViews = [nonEditableView];
|
||||
testContext.category = "something-else";
|
||||
expect(policy.allow(mockPropertiesAction, testContext)).toBe(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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.
|
||||
*****************************************************************************/
|
||||
/*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);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,79 @@
|
||||
/*****************************************************************************
|
||||
* 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/policies/EditableViewPolicy"],
|
||||
function (EditableViewPolicy) {
|
||||
|
||||
describe("The editable view policy", function () {
|
||||
var mockDomainObject,
|
||||
testMode,
|
||||
policy;
|
||||
|
||||
beforeEach(function () {
|
||||
testMode = true; // Act as if we're in Edit mode by default
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
['hasCapability', 'getCapability']
|
||||
);
|
||||
mockDomainObject.getCapability.and.returnValue({
|
||||
inEditContext: function () {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
mockDomainObject.hasCapability.and.callFake(function (c) {
|
||||
return (c === 'editor') && testMode;
|
||||
});
|
||||
|
||||
policy = new EditableViewPolicy();
|
||||
});
|
||||
|
||||
it("disallows views in edit mode that are flagged as non-editable", function () {
|
||||
expect(policy.allow({ editable: false }, mockDomainObject))
|
||||
.toBeFalsy();
|
||||
});
|
||||
|
||||
it("allows views in edit mode that are flagged as editable", function () {
|
||||
expect(policy.allow({ editable: true }, mockDomainObject))
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
it("allows any view outside of edit mode", function () {
|
||||
var testViews = [
|
||||
{ editable: false },
|
||||
{ editable: true },
|
||||
{ someKey: "some value" }
|
||||
];
|
||||
testMode = false; // Act as if we're not in Edit mode
|
||||
|
||||
testViews.forEach(function (testView) {
|
||||
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("treats views with no defined 'editable' property as editable", function () {
|
||||
expect(policy.allow({ someKey: "some value" }, mockDomainObject))
|
||||
.toBeTruthy();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -31,6 +31,7 @@ define([
|
||||
"./src/controllers/TreeNodeController",
|
||||
"./src/controllers/ActionGroupController",
|
||||
"./src/controllers/ToggleController",
|
||||
"./src/controllers/ContextMenuController",
|
||||
"./src/controllers/ClickAwayController",
|
||||
"./src/controllers/ViewSwitcherController",
|
||||
"./src/controllers/GetterSetterController",
|
||||
@@ -48,6 +49,8 @@ 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",
|
||||
@@ -62,11 +65,13 @@ 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,
|
||||
@@ -79,6 +84,7 @@ define([
|
||||
TreeNodeController,
|
||||
ActionGroupController,
|
||||
ToggleController,
|
||||
ContextMenuController,
|
||||
ClickAwayController,
|
||||
ViewSwitcherController,
|
||||
GetterSetterController,
|
||||
@@ -96,6 +102,8 @@ define([
|
||||
MCTSplitter,
|
||||
MCTTree,
|
||||
MCTIndicators,
|
||||
MCTPreview,
|
||||
MCTPreviewAction,
|
||||
ReverseFilter,
|
||||
bottombarTemplate,
|
||||
actionButtonTemplate,
|
||||
@@ -110,11 +118,13 @@ define([
|
||||
treeNodeTemplate,
|
||||
labelTemplate,
|
||||
actionGroupTemplate,
|
||||
contextMenuTemplate,
|
||||
switcherTemplate,
|
||||
objectInspectorTemplate,
|
||||
selectorTemplate,
|
||||
datetimePickerTemplate,
|
||||
datetimeFieldTemplate,
|
||||
previewTemplate,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
@@ -242,6 +252,13 @@ define([
|
||||
"key": "ToggleController",
|
||||
"implementation": ToggleController
|
||||
},
|
||||
{
|
||||
"key": "ContextMenuController",
|
||||
"implementation": ContextMenuController,
|
||||
"depends": [
|
||||
"$scope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "ClickAwayController",
|
||||
"implementation": ClickAwayController,
|
||||
@@ -377,6 +394,31 @@ 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": [
|
||||
@@ -475,6 +517,13 @@ define([
|
||||
"action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "context-menu",
|
||||
"template": contextMenuTemplate,
|
||||
"uses": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "switcher",
|
||||
"template": switcherTemplate,
|
||||
@@ -485,6 +534,10 @@ define([
|
||||
{
|
||||
"key": "object-inspector",
|
||||
"template": objectInspectorTemplate
|
||||
},
|
||||
{
|
||||
"key": "mct-preview",
|
||||
"template": previewTemplate
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
|
||||
@@ -19,18 +19,15 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<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>
|
||||
<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>
|
||||
45
platform/commonUI/general/res/templates/preview.html
Normal file
45
platform/commonUI/general/res/templates/preview.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
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>
|
||||
55
platform/commonUI/general/src/actions/MCTPreviewAction.js
Normal file
55
platform/commonUI/general/src/actions/MCTPreviewAction.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,51 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining ContextMenuController. Created by vwoeltje on 11/17/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
return ContextMenuController;
|
||||
}
|
||||
);
|
||||
64
platform/commonUI/general/src/directives/MCTPreview.js
Normal file
64
platform/commonUI/general/src/directives/MCTPreview.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/*****************************************************************************
|
||||
* 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 =
|
||||
"#" + this.urlForLocation(mode, domainObject) +
|
||||
"index.html#" + this.urlForLocation(mode, domainObject) +
|
||||
searchPath;
|
||||
return newTabPath;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["../../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);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -19,14 +19,16 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="c-clock l-time-display" ng-controller="ClockController as clock">
|
||||
<div class="c-clock__timezone">
|
||||
{{clock.zone()}}
|
||||
</div>
|
||||
<div class="c-clock__value">
|
||||
{{clock.text()}}
|
||||
</div>
|
||||
<div class="c-clock__ampm">
|
||||
{{clock.ampm()}}
|
||||
<div class="l-time-display l-digital l-clock s-clock" ng-controller="ClockController as clock">
|
||||
<div class="l-elem-wrapper">
|
||||
<span class="l-elem timezone">
|
||||
{{clock.zone()}}
|
||||
</span>
|
||||
<span class="l-elem value active">
|
||||
{{clock.text()}}
|
||||
</span>
|
||||
<span class="l-elem ampm">
|
||||
{{clock.ampm()}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,19 +19,21 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="c-timer is-{{timer.timerState}}" ng-controller="TimerController as timer">
|
||||
<div class="c-timer__controls">
|
||||
<button ng-click="timer.clickStopButton()"
|
||||
ng-hide="timer.timerState == 'stopped'"
|
||||
title="Reset"
|
||||
class="c-timer__ctrl-reset c-click-icon c-click-icon--major icon-reset"></button>
|
||||
<button ng-click="timer.clickButton()"
|
||||
title="{{timer.buttonText()}}"
|
||||
class="c-timer__ctrl-pause-play c-click-icon c-click-icon--major {{timer.buttonCssClass()}}"></button>
|
||||
</div>
|
||||
<div class="c-timer__direction {{timer.signClass()}}"
|
||||
ng-hide="!timer.signClass()"></div>
|
||||
<div class="c-timer__value">{{timer.text() || "--:--:--"}}
|
||||
<div class="l-time-display l-digital l-timer s-timer s-state-{{timer.timerState}}" ng-controller="TimerController as timer">
|
||||
<div class="l-elem-wrapper l-flex-row">
|
||||
<div class="l-elem-wrapper l-flex-row controls">
|
||||
<a ng-click="timer.clickStopButton()"
|
||||
title="Stop"
|
||||
class="flex-elem s-icon-button t-btn-stop icon-box"></a>
|
||||
<a ng-click="timer.clickButton()"
|
||||
title="{{timer.buttonText()}}"
|
||||
class="flex-elem s-icon-button t-btn-pauseplay {{timer.buttonCssClass()}}"></a>
|
||||
</div>
|
||||
<span class="flex-elem l-value {{timer.signClass()}}">
|
||||
<span class="value"
|
||||
ng-class="{ active:timer.text() }">{{timer.text() || "--:--:--"}}
|
||||
</span>
|
||||
</span>
|
||||
<span ng-controller="RefreshingController"></span>
|
||||
</div>
|
||||
<span class="c-timer__ng-controller u-contents" ng-controller="RefreshingController"></span>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,6 @@ 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",
|
||||
@@ -45,7 +44,6 @@ 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,
|
||||
@@ -57,7 +55,6 @@ define([
|
||||
CompositeController,
|
||||
ColorController,
|
||||
DialogButtonController,
|
||||
SnapshotPreviewController,
|
||||
autocompleteTemplate,
|
||||
checkboxTemplate,
|
||||
datetimeTemplate,
|
||||
@@ -72,7 +69,6 @@ define([
|
||||
dialogTemplate,
|
||||
radioTemplate,
|
||||
fileInputTemplate,
|
||||
snapViewTemplate,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
@@ -157,10 +153,6 @@ define([
|
||||
{
|
||||
"key": "file-input",
|
||||
"template": fileInputTemplate
|
||||
},
|
||||
{
|
||||
"key": "snap-view",
|
||||
"template": snapViewTemplate
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
@@ -194,14 +186,6 @@ define([
|
||||
"$scope",
|
||||
"dialogService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "SnapshotPreviewController",
|
||||
"implementation": SnapshotPreviewController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
|
||||
@@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
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,10 +25,12 @@ 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 (
|
||||
@@ -36,10 +38,12 @@ define([
|
||||
MCTRepresentation,
|
||||
DragGesture,
|
||||
DropGesture,
|
||||
ContextMenuGesture,
|
||||
GestureProvider,
|
||||
GestureRepresenter,
|
||||
DndService,
|
||||
TemplateLinker,
|
||||
ContextMenuAction,
|
||||
TemplatePrefetcher,
|
||||
legacyRegistry
|
||||
) {
|
||||
@@ -84,6 +88,14 @@ define([
|
||||
"dndService",
|
||||
"$q"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "menu",
|
||||
"implementation": ContextMenuGesture,
|
||||
"depends": [
|
||||
"$timeout",
|
||||
"agentService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
@@ -124,6 +136,19 @@ 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",
|
||||
|
||||
138
platform/representation/src/actions/ContextMenuAction.js
Normal file
138
platform/representation/src/actions/ContextMenuAction.js
Normal file
@@ -0,0 +1,138 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
);
|
||||
100
platform/representation/src/gestures/ContextMenuGesture.js
Normal file
100
platform/representation/src/gestures/ContextMenuGesture.js
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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
);
|
||||
202
platform/representation/test/actions/ContextMenuActionSpec.js
Normal file
202
platform/representation/test/actions/ContextMenuActionSpec.js
Normal file
@@ -0,0 +1,202 @@
|
||||
/*****************************************************************************
|
||||
* 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();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
119
platform/representation/test/gestures/ContextMenuGestureSpec.js
Normal file
119
platform/representation/test/gestures/ContextMenuGestureSpec.js
Normal file
@@ -0,0 +1,119 @@
|
||||
/*****************************************************************************
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -40,10 +40,9 @@ define([
|
||||
'../platform/framework/src/Main',
|
||||
'./styles-new/core.scss',
|
||||
'./styles-new/notebook.scss',
|
||||
'./ui/layout/Layout.vue',
|
||||
'./ui/components/layout/Layout.vue',
|
||||
'../platform/core/src/objects/DomainObjectImpl',
|
||||
'../platform/core/src/capabilities/ContextualDomainObject',
|
||||
'./ui/preview/plugin',
|
||||
'vue'
|
||||
], function (
|
||||
EventEmitter,
|
||||
@@ -68,7 +67,6 @@ define([
|
||||
Layout,
|
||||
DomainObjectImpl,
|
||||
ContextualDomainObject,
|
||||
PreviewPlugin,
|
||||
Vue
|
||||
) {
|
||||
/**
|
||||
@@ -232,7 +230,6 @@ define([
|
||||
this.install(this.plugins.Plot());
|
||||
this.install(this.plugins.TelemetryTable());
|
||||
this.install(this.plugins.DisplayLayout());
|
||||
this.install(PreviewPlugin.default());
|
||||
|
||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||
this.install(buildInfoPlugin(BUILD_CONSTANTS));
|
||||
|
||||
@@ -24,7 +24,7 @@ import LegacyContextMenuAction from './LegacyContextMenuAction';
|
||||
|
||||
export default function LegacyActionAdapter(openmct, legacyActions) {
|
||||
function contextualCategoryOnly(action) {
|
||||
if (action.category === 'contextual' || (Array.isArray(action.category) && action.category.includes('contextual'))) {
|
||||
if (action.category === 'contextual') {
|
||||
return true;
|
||||
}
|
||||
console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { timingSafeEqual } from "crypto";
|
||||
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
@@ -19,9 +21,6 @@
|
||||
* 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) {
|
||||
@@ -32,6 +31,13 @@ export default class LegacyContextMenuAction {
|
||||
this.LegacyAction = LegacyAction;
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
let legacyObject = this.openmct.legacyObject(objectPath);
|
||||
return this.LegacyAction.appliesTo({
|
||||
domainObject: legacyObject
|
||||
});
|
||||
}
|
||||
|
||||
invoke(objectPath) {
|
||||
let context = {
|
||||
category: 'contextual',
|
||||
@@ -48,36 +54,4 @@ export default class LegacyContextMenuAction {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -58,8 +58,11 @@ define([
|
||||
|
||||
handleLegacyMutation = function (legacyObject) {
|
||||
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
|
||||
|
||||
//Don't trigger self
|
||||
this.eventEmitter.off('mutation', handleMutation);
|
||||
this.eventEmitter.emit(newStyleObject.identifier.key + ":*", newStyleObject);
|
||||
this.eventEmitter.emit('mutation', newStyleObject);
|
||||
this.eventEmitter.on('mutation', handleMutation);
|
||||
}.bind(this);
|
||||
|
||||
this.eventEmitter.on('mutation', handleMutation);
|
||||
|
||||
@@ -21,9 +21,7 @@ define([
|
||||
name: legacyView.name,
|
||||
cssClass: legacyView.cssClass,
|
||||
description: legacyView.description,
|
||||
canEdit: function () {
|
||||
return legacyView.editable === true;
|
||||
},
|
||||
editable: legacyView.editable,
|
||||
canView: function (domainObject) {
|
||||
if (!domainObject || !domainObject.identifier) {
|
||||
return false;
|
||||
@@ -79,7 +77,7 @@ define([
|
||||
openmct.$angular.element(container),
|
||||
legacyView
|
||||
);
|
||||
container.classList.add('u-contents');
|
||||
container.style.height = '100%';
|
||||
}
|
||||
|
||||
if (promises.length) {
|
||||
|
||||
@@ -28,11 +28,6 @@ export default class Editor extends EventEmitter {
|
||||
super();
|
||||
this.editing = false;
|
||||
this.openmct = openmct;
|
||||
document.addEventListener('drop', (event) => {
|
||||
if (!this.isEditing()) {
|
||||
this.edit();
|
||||
}
|
||||
}, {capture: true});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,6 @@ define([
|
||||
function DefaultCompositionProvider(publicAPI, compositionAPI) {
|
||||
this.publicAPI = publicAPI;
|
||||
this.listeningTo = {};
|
||||
this.onMutation = this.onMutation.bind(this);
|
||||
|
||||
this.cannotContainDuplicates = this.cannotContainDuplicates.bind(this);
|
||||
this.cannotContainItself = this.cannotContainItself.bind(this);
|
||||
@@ -209,10 +208,9 @@ define([
|
||||
if (this.topicListener) {
|
||||
return;
|
||||
}
|
||||
this.publicAPI.objects.eventEmitter.on('mutation', this.onMutation);
|
||||
this.topicListener = () => {
|
||||
this.publicAPI.objects.eventEmitter.off('mutation', this.onMutation)
|
||||
};
|
||||
var topic = this.publicAPI.$injector.get('topic');
|
||||
var mutation = topic('mutation');
|
||||
this.topicListener = mutation.listen(this.onMutation.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -222,7 +220,7 @@ define([
|
||||
* @private
|
||||
*/
|
||||
DefaultCompositionProvider.prototype.onMutation = function (oldDomainObject) {
|
||||
var id = objectUtils.makeKeyString(oldDomainObject.identifier);
|
||||
var id = oldDomainObject.getId();
|
||||
var listeners = this.listeningTo[id];
|
||||
|
||||
if (!listeners) {
|
||||
@@ -230,7 +228,7 @@ define([
|
||||
}
|
||||
|
||||
var oldComposition = listeners.composition.map(objectUtils.makeKeyString);
|
||||
var newComposition = oldDomainObject.composition.map(objectUtils.makeKeyString);
|
||||
var newComposition = oldDomainObject.getModel().composition.map(objectUtils.makeKeyString);
|
||||
|
||||
var added = _.difference(newComposition, oldComposition).map(objectUtils.parseKeyString);
|
||||
var removed = _.difference(oldComposition, newComposition).map(objectUtils.parseKeyString);
|
||||
|
||||
@@ -73,12 +73,8 @@ class ContextMenuAPI {
|
||||
* @private
|
||||
*/
|
||||
_showContextMenuForObjectPath(objectPath, x, y) {
|
||||
let applicableActions = this._allActions.filter((action) => {
|
||||
if (action.appliesTo === undefined) {
|
||||
return true;
|
||||
}
|
||||
return action.appliesTo(objectPath);
|
||||
});
|
||||
let applicableActions = this._allActions.filter(
|
||||
(action) => action.appliesTo(objectPath));
|
||||
|
||||
if (this._activeContextMenu) {
|
||||
this._hideActiveContextMenu();
|
||||
|
||||
@@ -83,15 +83,18 @@ define([
|
||||
this.object = newObject;
|
||||
}.bind(this);
|
||||
|
||||
//Emit wildcard event
|
||||
this.eventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
|
||||
//Emit a general "any object" event
|
||||
this.eventEmitter.emit(ANY_OBJECT_EVENT, this.object);
|
||||
|
||||
this.eventEmitter.on(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
|
||||
|
||||
//Emit event specific to property
|
||||
this.eventEmitter.emit(qualifiedEventName(this.object, path), value);
|
||||
|
||||
this.eventEmitter.off(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
|
||||
|
||||
//Emit wildcare event
|
||||
this.eventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
|
||||
|
||||
//Emit a general "any object" event
|
||||
this.eventEmitter.emit(ANY_OBJECT_EVENT, this.object);
|
||||
};
|
||||
|
||||
return MutableObject;
|
||||
|
||||
@@ -3,15 +3,13 @@ import Overlay from './Overlay';
|
||||
import Vue from 'vue';
|
||||
|
||||
class Dialog extends Overlay {
|
||||
constructor({iconClass, message, title, hint, timestamp, ...options}) {
|
||||
constructor({iconClass, message, title, ...options}) {
|
||||
|
||||
let component = new Vue({
|
||||
provide: {
|
||||
iconClass,
|
||||
message,
|
||||
title,
|
||||
hint,
|
||||
timestamp
|
||||
title
|
||||
},
|
||||
components: {
|
||||
DialogComponent: DialogComponent
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ProgressComponent from '../../ui/components/ProgressBar.vue';
|
||||
import ProgressComponent from '../../ui/components/layout/ProgressBar.vue';
|
||||
import Overlay from './Overlay';
|
||||
import Vue from 'vue';
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
flex: 1 1 auto;
|
||||
|
||||
> * + * {
|
||||
@include test();
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
.c-overlay {
|
||||
@include abs();
|
||||
z-index: 70;
|
||||
z-index: 100;
|
||||
|
||||
&__blocker {
|
||||
display: none; // Mobile-first
|
||||
|
||||
@@ -124,22 +124,6 @@ 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,67 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./components/LadTable.vue',
|
||||
'vue'
|
||||
], function (
|
||||
LadTableComponent,
|
||||
Vue
|
||||
) {
|
||||
function LADTableViewProvider(openmct) {
|
||||
return {
|
||||
key: 'LadTable',
|
||||
name: 'LAD Table',
|
||||
cssClass: 'icon-tabular-lad',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'LadTable';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
LadTableComponent: LadTableComponent.default
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
},
|
||||
el: element,
|
||||
template: '<lad-table-component></lad-table-component>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
return LADTableViewProvider;
|
||||
});
|
||||
@@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<tr>
|
||||
<td>{{name}}</td>
|
||||
<td>{{timestamp}}</td>
|
||||
<td :class="valueClass">
|
||||
{{value}}
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: ['domainObject'],
|
||||
data() {
|
||||
return {
|
||||
name: this.domainObject.name,
|
||||
timestamp: '---',
|
||||
value: '---',
|
||||
valueClass: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValues(datum) {
|
||||
this.timestamp = this.formats[this.timestampKey].format(datum);
|
||||
this.value = this.formats[this.valueKey].format(datum);
|
||||
|
||||
var limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
|
||||
|
||||
if (limit) {
|
||||
this.valueClass = limit.cssClass;
|
||||
} else {
|
||||
this.valueClass = '';
|
||||
}
|
||||
},
|
||||
updateName(name){
|
||||
this.name = name;
|
||||
},
|
||||
updateTimeSystem(timeSystem) {
|
||||
this.value = '---';
|
||||
this.timestamp = '---';
|
||||
this.valueClass = '';
|
||||
this.timestampKey = timeSystem.key;
|
||||
|
||||
this.openmct
|
||||
.telemetry
|
||||
.request(this.domainObject, {strategy: 'latest'})
|
||||
.then((array) => this.updateValues(array[array.length - 1]));
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||
|
||||
this.limitEvaluator = openmct
|
||||
.telemetry
|
||||
.limitEvaluator(this.domainObject);
|
||||
|
||||
this.stopWatchingMutation = openmct
|
||||
.objects
|
||||
.observe(
|
||||
this.domainObject,
|
||||
'*',
|
||||
this.updateName
|
||||
);
|
||||
|
||||
this.openmct.time.on('timeSystem', this.updateTimeSystem);
|
||||
|
||||
this.timestampKey = this.openmct.time.timeSystem().key;
|
||||
|
||||
this.valueMetadata = this
|
||||
.metadata
|
||||
.valuesForHints(['range'])[0];
|
||||
|
||||
this.valueKey = this.valueMetadata.key
|
||||
|
||||
this.unsubscribe = this.openmct
|
||||
.telemetry
|
||||
.subscribe(this.domainObject, this.updateValues);
|
||||
|
||||
this.openmct
|
||||
.telemetry
|
||||
.request(this.domainObject, {strategy: 'latest'})
|
||||
.then((array) => this.updateValues(array[array.length - 1]));
|
||||
},
|
||||
destroyed() {
|
||||
this.stopWatchingMutation();
|
||||
this.unsubscribe();
|
||||
this.openmct.off('timeSystem', this.updateTimeSystem);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,82 +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>
|
||||
<table class="c-table c-lad-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Timestamp</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<lad-row
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
:domainObject="item.domainObject">
|
||||
</lad-row>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import lodash from 'lodash';
|
||||
import LadRow from './LadRow.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
components: {
|
||||
LadRow
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
items: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addItem(domainObject) {
|
||||
let item = {};
|
||||
item.domainObject = domainObject;
|
||||
item.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
|
||||
this.items.push(item);
|
||||
},
|
||||
removeItem(identifier) {
|
||||
let index = _.findIndex(this.items, (item) => this.openmct.objects.makeKeyString(identifier) === item.key);
|
||||
|
||||
this.items.splice(index, 1);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
this.composition.on('add', this.addItem);
|
||||
this.composition.on('remove', this.removeItem);
|
||||
this.composition.load();
|
||||
},
|
||||
destroyed() {
|
||||
this.composition.off('add', this.addItem);
|
||||
this.composition.off('remove', this.removeItem);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,135 +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>
|
||||
<table class="c-table c-lad-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Timestamp</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template
|
||||
v-for="primary in primaryTelemetryObjects">
|
||||
<tr class="c-table__group-header"
|
||||
:key="primary.key">
|
||||
<td colspan="10">{{primary.domainObject.name}}</td>
|
||||
</tr>
|
||||
<lad-row
|
||||
v-for="secondary in secondaryTelemetryObjects[primary.key]"
|
||||
:key="secondary.key"
|
||||
:domainObject="secondary.domainObject">
|
||||
</lad-row>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import lodash from 'lodash';
|
||||
import LadRow from './LadRow.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
components: {
|
||||
LadRow
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
primaryTelemetryObjects: [],
|
||||
secondaryTelemetryObjects: {},
|
||||
compositions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addPrimary(domainObject) {
|
||||
let primary = {};
|
||||
primary.domainObject = domainObject;
|
||||
primary.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
|
||||
this.$set(this.secondaryTelemetryObjects, primary.key, []);
|
||||
this.primaryTelemetryObjects.push(primary);
|
||||
|
||||
let composition = openmct.composition.get(primary.domainObject),
|
||||
addCallback = this.addSecondary(primary),
|
||||
removeCallback = this.removeSecondary(primary);
|
||||
|
||||
composition.on('add', addCallback);
|
||||
composition.on('remove', removeCallback);
|
||||
composition.load();
|
||||
|
||||
this.compositions.push({composition, addCallback, removeCallback});
|
||||
},
|
||||
removePrimary(identifier) {
|
||||
let index = _.findIndex(this.primaryTelemetryObjects, (primary) => this.openmct.objects.makeKeyString(identifier) === primary.key),
|
||||
primary = this.primaryTelemetryObjects[index];
|
||||
|
||||
this.$set(this.secondaryTelemetryObjects, primary.key, undefined);
|
||||
this.primaryTelemetryObjects.splice(index,1);
|
||||
primary = undefined;
|
||||
},
|
||||
addSecondary(primary) {
|
||||
return (domainObject) => {
|
||||
let secondary = {};
|
||||
secondary.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
secondary.domainObject = domainObject;
|
||||
|
||||
let array = this.secondaryTelemetryObjects[primary.key];
|
||||
array.push(secondary);
|
||||
|
||||
this.$set(this.secondaryTelemetryObjects, primary.key, array);
|
||||
}
|
||||
},
|
||||
removeSecondary(primary) {
|
||||
return (identifier) => {
|
||||
let array = this.secondaryTelemetryObjects[primary.key],
|
||||
index = _.findIndex(array, (secondary) => this.openmct.objects.makeKeyString(identifier) === secondary.key);
|
||||
|
||||
array.splice(index, 1);
|
||||
|
||||
this.$set(this.secondaryTelemetryObjects, primary.key, array);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
this.composition.on('add', this.addPrimary);
|
||||
this.composition.on('remove', this.removePrimary);
|
||||
this.composition.load();
|
||||
},
|
||||
destroyed() {
|
||||
this.composition.off('add', this.addPrimary);
|
||||
this.composition.off('remove', this.removePrimary);
|
||||
this.compositions.forEach(c => {
|
||||
c.composition.off('add', c.addCallback);
|
||||
c.composition.off('remove', c.removeCallback);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,56 +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([
|
||||
'./LADTableViewProvider',
|
||||
'./LADTableSetViewProvider'
|
||||
], function (
|
||||
LADTableViewProvider,
|
||||
LADTableSetViewProvider
|
||||
) {
|
||||
return function plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new LADTableViewProvider(openmct));
|
||||
openmct.objectViews.addProvider(new LADTableSetViewProvider(openmct));
|
||||
|
||||
openmct.types.addType('LadTable', {
|
||||
name: "LAD Table",
|
||||
creatable: true,
|
||||
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
|
||||
cssClass: 'icon-tabular-lad',
|
||||
initialize(domainObject) {
|
||||
domainObject.composition = [];
|
||||
}
|
||||
});
|
||||
|
||||
openmct.types.addType('LadTableSet', {
|
||||
name: "LAD Table Set",
|
||||
creatable: true,
|
||||
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
|
||||
cssClass: 'icon-tabular-lad-set',
|
||||
initialize(domainObject) {
|
||||
domainObject.composition = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
||||
@@ -32,51 +32,9 @@ define([], function () {
|
||||
// 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')));
|
||||
(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, {});
|
||||
}
|
||||
|
||||
function getPath() {
|
||||
return `configuration.items[${selection[0].context.index}]`;
|
||||
}
|
||||
|
||||
let selectedParent = selection[1] && selection[1].context.item,
|
||||
selectedObject = selection[0].context.item,
|
||||
layoutItem = selection[0].context.layoutItem,
|
||||
@@ -87,14 +45,7 @@ define([], function () {
|
||||
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);
|
||||
}
|
||||
selection[0].context.addElement(option.name.toLowerCase());
|
||||
},
|
||||
key: "add",
|
||||
icon: "icon-plus",
|
||||
@@ -124,244 +75,138 @@ define([], function () {
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
let separator = {
|
||||
control: "separator"
|
||||
};
|
||||
let remove = {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
icon: "icon-trash",
|
||||
title: "Delete the selected object",
|
||||
method: function () {
|
||||
let removeItem = selection[1].context.removeItem;
|
||||
let prompt = openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Ok',
|
||||
emphasis: 'true',
|
||||
callback: function () {
|
||||
removeItem(layoutItem, selection[0].context.index);
|
||||
prompt.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
prompt.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
let stackOrder = {
|
||||
control: "menu",
|
||||
domainObject: selectedParent,
|
||||
icon: "icon-layers",
|
||||
title: "Move the selected object above or below other objects",
|
||||
options: [
|
||||
{
|
||||
name: "Move to Top",
|
||||
value: "top",
|
||||
class: "icon-arrow-double-up"
|
||||
},
|
||||
{
|
||||
name: "Move Up",
|
||||
value: "up",
|
||||
class: "icon-arrow-up"
|
||||
},
|
||||
{
|
||||
name: "Move Down",
|
||||
value: "down",
|
||||
class: "icon-arrow-down"
|
||||
},
|
||||
{
|
||||
name: "Move to Bottom",
|
||||
value: "bottom",
|
||||
class: "icon-arrow-double-down"
|
||||
}
|
||||
],
|
||||
method: function (option) {
|
||||
selection[1].context.orderItem(option.value, selection[0].context.index);
|
||||
}
|
||||
};
|
||||
let useGrid = {
|
||||
control: "toggle-button",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".useGrid";
|
||||
},
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
icon: "icon-grid-snap-to",
|
||||
title: "Grid snapping enabled"
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: "icon-grid-snap-no",
|
||||
title: "Grid snapping disabled"
|
||||
}
|
||||
]
|
||||
};
|
||||
let x = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".x";
|
||||
},
|
||||
label: "X:",
|
||||
title: "X position"
|
||||
},
|
||||
y = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".y";
|
||||
},
|
||||
label: "Y:",
|
||||
title: "Y position",
|
||||
},
|
||||
width = {
|
||||
control: 'input',
|
||||
type: 'number',
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".width";
|
||||
},
|
||||
label: 'W:',
|
||||
title: 'Resize object width'
|
||||
},
|
||||
height = {
|
||||
control: 'input',
|
||||
type: 'number',
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".height";
|
||||
},
|
||||
label: 'H:',
|
||||
title: 'Resize object height'
|
||||
};
|
||||
let path = layoutItem.config.path();
|
||||
|
||||
if (layoutItem.type === 'subobject-view') {
|
||||
if (toolbar.length > 0) {
|
||||
toolbar.push(separator);
|
||||
toolbar.push({
|
||||
control: "separator"
|
||||
});
|
||||
}
|
||||
|
||||
toolbar.push({
|
||||
control: "toggle-button",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".hasFrame";
|
||||
},
|
||||
property: path + ".hasFrame",
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-frame-show',
|
||||
title: "Frame visible"
|
||||
icon: 'icon-frame-hide',
|
||||
title: "Hide frame"
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-frame-hide',
|
||||
title: "Frame hidden"
|
||||
icon: 'icon-frame-show',
|
||||
title: "Show frame"
|
||||
}
|
||||
]
|
||||
});
|
||||
toolbar.push(separator);
|
||||
toolbar.push(stackOrder);
|
||||
toolbar.push(x);
|
||||
toolbar.push(y);
|
||||
toolbar.push(width);
|
||||
toolbar.push(height);
|
||||
toolbar.push(useGrid);
|
||||
toolbar.push(separator);
|
||||
toolbar.push(remove);
|
||||
} else {
|
||||
const TEXT_SIZE = [8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96, 128];
|
||||
let fill = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".fill";
|
||||
const TEXT_SIZE = [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96];
|
||||
let separator = {
|
||||
control: "separator"
|
||||
},
|
||||
icon: "icon-paint-bucket",
|
||||
title: "Set fill color"
|
||||
},
|
||||
stroke = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".stroke";
|
||||
fill = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".fill",
|
||||
icon: "icon-paint-bucket",
|
||||
title: "Set fill color"
|
||||
},
|
||||
icon: "icon-line-horz",
|
||||
title: "Set border color"
|
||||
},
|
||||
color = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".color";
|
||||
stroke = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".stroke",
|
||||
icon: "icon-line-horz",
|
||||
title: "Set border color"
|
||||
},
|
||||
icon: "icon-font",
|
||||
mandatory: true,
|
||||
title: "Set text color",
|
||||
preventNone: true
|
||||
},
|
||||
size = {
|
||||
control: "select-menu",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".size";
|
||||
color = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".color",
|
||||
icon: "icon-font",
|
||||
mandatory: true,
|
||||
title: "Set text color",
|
||||
preventNone: true
|
||||
},
|
||||
title: "Set text size",
|
||||
options: TEXT_SIZE.map(size => {
|
||||
return {
|
||||
value: size + "px"
|
||||
};
|
||||
})
|
||||
};
|
||||
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: function () {
|
||||
return getPath() + ".displayMode";
|
||||
// TODO: add "remove", "order", "useGrid"
|
||||
let metadata = openmct.telemetry.getMetadata(layoutItem.domainObject),
|
||||
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"
|
||||
}
|
||||
]
|
||||
},
|
||||
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: function () {
|
||||
return getPath() + ".value";
|
||||
},
|
||||
title: "Set value",
|
||||
options: openmct.telemetry.getMetadata(selectedObject).values().map(value => {
|
||||
return {
|
||||
name: value.name,
|
||||
value: value.key
|
||||
}
|
||||
})
|
||||
};
|
||||
value = {
|
||||
control: "select-menu",
|
||||
domainObject: selectedParent,
|
||||
property: path + ".value",
|
||||
title: "Set value",
|
||||
options: metadata.values().map(value => {
|
||||
return {
|
||||
name: value.name,
|
||||
value: value.key
|
||||
}
|
||||
})
|
||||
};
|
||||
toolbar = [
|
||||
displayMode,
|
||||
separator,
|
||||
@@ -373,116 +218,98 @@ define([], function () {
|
||||
separator,
|
||||
size,
|
||||
separator,
|
||||
stackOrder,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width,
|
||||
useGrid,
|
||||
separator,
|
||||
remove
|
||||
width
|
||||
];
|
||||
} else if (layoutItem.type === 'text-view') {
|
||||
} else if (layoutItem.type === 'text-view' ) {
|
||||
// TODO: Add "remove", "order", "useGrid"
|
||||
let text = {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath();
|
||||
},
|
||||
property: path,
|
||||
icon: "icon-gear",
|
||||
title: "Edit text properties",
|
||||
dialog: DIALOG_FORM['text']
|
||||
dialog: {
|
||||
name: "Text Element Properties",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "text",
|
||||
control: "textfield",
|
||||
name: "Text",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
toolbar = [
|
||||
fill,
|
||||
stroke,
|
||||
separator,
|
||||
color,
|
||||
separator,
|
||||
size,
|
||||
separator,
|
||||
stackOrder,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width,
|
||||
useGrid,
|
||||
separator,
|
||||
text,
|
||||
separator,
|
||||
remove
|
||||
text
|
||||
];
|
||||
} else if (layoutItem.type === 'box-view') {
|
||||
// TODO: Add "remove", "order", "useGrid"
|
||||
toolbar = [
|
||||
fill,
|
||||
stroke,
|
||||
separator,
|
||||
stackOrder,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width,
|
||||
useGrid,
|
||||
separator,
|
||||
remove
|
||||
width
|
||||
];
|
||||
} else if (layoutItem.type === 'image-view') {
|
||||
// TODO: Add "remove", "order", "useGrid"
|
||||
let url = {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath();
|
||||
},
|
||||
property: path,
|
||||
icon: "icon-image",
|
||||
title: "Edit image properties",
|
||||
dialog: DIALOG_FORM['image']
|
||||
dialog: {
|
||||
name: "Image Properties",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "url",
|
||||
control: "textfield",
|
||||
name: "Image URL",
|
||||
"cssClass": "l-input-lg",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
toolbar = [
|
||||
stroke,
|
||||
separator,
|
||||
stackOrder,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width,
|
||||
useGrid,
|
||||
separator,
|
||||
url,
|
||||
separator,
|
||||
remove
|
||||
url
|
||||
];
|
||||
} else if (layoutItem.type === 'line-view') {
|
||||
let x2 = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".x2";
|
||||
},
|
||||
label: "X2:",
|
||||
title: "X2 position"
|
||||
},
|
||||
y2 = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".y2";
|
||||
},
|
||||
label: "Y2:",
|
||||
title: "Y2 position",
|
||||
};
|
||||
toolbar = [
|
||||
stroke,
|
||||
separator,
|
||||
stackOrder,
|
||||
x,
|
||||
y,
|
||||
x2,
|
||||
y2,
|
||||
useGrid,
|
||||
separator,
|
||||
remove
|
||||
];
|
||||
// TODO: Add "remove", "order", "useGrid", "x1", "y1", x2", "y2"
|
||||
toolbar = [stroke];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,13 +25,11 @@ define(function () {
|
||||
return {
|
||||
name: "Display Layout",
|
||||
creatable: true,
|
||||
description: 'Assemble other objects and components together into a reusable screen layout. Simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.',
|
||||
cssClass: 'icon-layout',
|
||||
initialize(domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {
|
||||
items: [],
|
||||
layoutGrid: [10, 10],
|
||||
items: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
171
src/plugins/displayLayout/ElementViewConfiguration.js
Normal file
171
src/plugins/displayLayout/ElementViewConfiguration.js
Normal file
@@ -0,0 +1,171 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
['./ViewConfiguration'],
|
||||
function (ViewConfiguration) {
|
||||
class ElementViewConfiguration extends ViewConfiguration {
|
||||
|
||||
static create(type, openmct) {
|
||||
const DEFAULT_WIDTH = 10,
|
||||
DEFAULT_HEIGHT = 5,
|
||||
DEFAULT_X = 1,
|
||||
DEFAULT_Y = 1;
|
||||
const INITIAL_STATES = {
|
||||
"image": {
|
||||
stroke: "transparent"
|
||||
},
|
||||
"box": {
|
||||
fill: "#717171",
|
||||
stroke: "transparent"
|
||||
},
|
||||
"line": {
|
||||
x: 5,
|
||||
y: 3,
|
||||
x2: 6,
|
||||
y2: 6,
|
||||
stroke: "#717171"
|
||||
},
|
||||
"text": {
|
||||
fill: "transparent",
|
||||
stroke: "transparent",
|
||||
size: "13px",
|
||||
color: ""
|
||||
}
|
||||
};
|
||||
const DIALOGS = {
|
||||
"image": {
|
||||
name: "Image Properties",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "url",
|
||||
control: "textfield",
|
||||
name: "Image URL",
|
||||
"cssClass": "l-input-lg",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
name: "Text Element Properties",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "text",
|
||||
control: "textfield",
|
||||
name: "Text",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let element = INITIAL_STATES[type] || {};
|
||||
element = JSON.parse(JSON.stringify(element));
|
||||
element.x = element.x || DEFAULT_X;
|
||||
element.y = element.y || DEFAULT_Y;
|
||||
element.width = DEFAULT_WIDTH;
|
||||
element.height = DEFAULT_HEIGHT;
|
||||
element.type = type;
|
||||
|
||||
return DIALOGS[type] ?
|
||||
openmct.$injector.get('dialogService').getUserInput(DIALOGS[type], element) :
|
||||
element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} configuration the element (line, box, text or image) view configuration
|
||||
* @param {Object} configuration.element
|
||||
* @param {Object} configuration.domainObject the telemetry domain object
|
||||
* @param {Object} configuration.openmct the openmct object
|
||||
*/
|
||||
constructor({element, ...rest}) {
|
||||
super(rest);
|
||||
this.element = element;
|
||||
this.updateStyle(this.position());
|
||||
}
|
||||
|
||||
path() {
|
||||
return "configuration.items[" + this.element.index + "]";
|
||||
}
|
||||
|
||||
x() {
|
||||
return this.element.x;
|
||||
}
|
||||
|
||||
y() {
|
||||
return this.element.y;
|
||||
}
|
||||
|
||||
width() {
|
||||
return this.element.width;
|
||||
}
|
||||
|
||||
height() {
|
||||
return this.element.height;
|
||||
}
|
||||
|
||||
observeProperties() {
|
||||
[
|
||||
"width",
|
||||
"height",
|
||||
"stroke",
|
||||
"fill",
|
||||
"x",
|
||||
"y",
|
||||
"x1",
|
||||
"y1",
|
||||
"x2",
|
||||
"y2",
|
||||
"color",
|
||||
"size",
|
||||
"text",
|
||||
"url"
|
||||
].forEach(property => {
|
||||
this.attachListener(property, newValue => {
|
||||
this.element[property] = newValue;
|
||||
|
||||
if (property === 'width' || property === 'height' ||
|
||||
property === 'x' || property === 'y') {
|
||||
this.updateStyle();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: attach listener for useGrid
|
||||
}
|
||||
|
||||
inspectable() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return ElementViewConfiguration;
|
||||
}
|
||||
);
|
||||
122
src/plugins/displayLayout/SubobjectViewConfiguration.js
Normal file
122
src/plugins/displayLayout/SubobjectViewConfiguration.js
Normal file
@@ -0,0 +1,122 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
['./ViewConfiguration'],
|
||||
function (ViewConfiguration) {
|
||||
class SubobjectViewConfiguration extends ViewConfiguration {
|
||||
|
||||
static create(domainObject, gridSize, position) {
|
||||
const MINIMUM_FRAME_SIZE = [320, 180],
|
||||
DEFAULT_DIMENSIONS = [10, 10],
|
||||
DEFAULT_POSITION = [0, 0],
|
||||
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
|
||||
|
||||
function getDefaultDimensions() {
|
||||
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;
|
||||
}
|
||||
|
||||
position = position || DEFAULT_POSITION;
|
||||
let defaultDimensions = getDefaultDimensions();
|
||||
let panel = {
|
||||
identifier: domainObject.identifier,
|
||||
type: 'subobject',
|
||||
width: defaultDimensions[0],
|
||||
height: defaultDimensions[1],
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
hasFrame: hasFrameByDefault(domainObject.type)
|
||||
};
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} configuration the subobject view configuration
|
||||
* @param {Boolean} configuration.panel
|
||||
* @param {Object} configuration.domainObject the domain object to observe the changes on
|
||||
* @param {Object} configuration.openmct the openmct object
|
||||
*/
|
||||
constructor({panel, ...rest}) {
|
||||
super(rest);
|
||||
this.panel = panel;
|
||||
this.hasFrame = this.hasFrame.bind(this);
|
||||
this.updateStyle(this.position());
|
||||
}
|
||||
|
||||
path() {
|
||||
return "configuration.items[" + this.panel.index + "]";
|
||||
}
|
||||
|
||||
x() {
|
||||
return this.panel.x;
|
||||
}
|
||||
|
||||
y() {
|
||||
return this.panel.y;
|
||||
}
|
||||
|
||||
width() {
|
||||
return this.panel.width;
|
||||
}
|
||||
|
||||
height() {
|
||||
return this.panel.height;
|
||||
}
|
||||
|
||||
hasFrame() {
|
||||
return this.panel.hasFrame;
|
||||
}
|
||||
|
||||
observeProperties() {
|
||||
[
|
||||
'hasFrame',
|
||||
'x',
|
||||
'y',
|
||||
'width',
|
||||
'height'
|
||||
].forEach(property => {
|
||||
this.attachListener(property, newValue => {
|
||||
this.panel[property] = newValue;
|
||||
|
||||
if (property === 'width' || property === 'height' ||
|
||||
property === 'x' || property === 'y') {
|
||||
this.updateStyle();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return SubobjectViewConfiguration;
|
||||
}
|
||||
);
|
||||
128
src/plugins/displayLayout/TelemetryViewConfiguration.js
Normal file
128
src/plugins/displayLayout/TelemetryViewConfiguration.js
Normal file
@@ -0,0 +1,128 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
['./ViewConfiguration'],
|
||||
function (ViewConfiguration) {
|
||||
|
||||
class TelemetryViewConfiguration extends ViewConfiguration {
|
||||
static create(domainObject, position, openmct) {
|
||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
|
||||
DEFAULT_POSITION = [1, 1];
|
||||
|
||||
function getDefaultTelemetryValue(domainObject, openmct) {
|
||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
||||
let valueMetadata = metadata.valuesForHints(['range'])[0];
|
||||
|
||||
if (valueMetadata === undefined) {
|
||||
valueMetadata = metadata.values().filter(values => {
|
||||
return !(values.hints.domain);
|
||||
})[0];
|
||||
}
|
||||
|
||||
if (valueMetadata === undefined) {
|
||||
valueMetadata = metadata.values()[0];
|
||||
}
|
||||
|
||||
return valueMetadata.key;
|
||||
}
|
||||
|
||||
position = position || DEFAULT_POSITION;
|
||||
let alphanumeric = {
|
||||
identifier: domainObject.identifier,
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
width: DEFAULT_TELEMETRY_DIMENSIONS[0],
|
||||
height: DEFAULT_TELEMETRY_DIMENSIONS[1],
|
||||
displayMode: 'all',
|
||||
value: getDefaultTelemetryValue(domainObject, openmct),
|
||||
stroke: "transparent",
|
||||
fill: "",
|
||||
color: "",
|
||||
size: "13px",
|
||||
type: "telemetry"
|
||||
};
|
||||
|
||||
return alphanumeric;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} configuration the telemetry object view configuration
|
||||
* @param {Object} configuration.alphanumeric
|
||||
* @param {Object} configuration.domainObject the domain object to observe the changes on
|
||||
* @param {Object} configuration.openmct the openmct object
|
||||
*/
|
||||
constructor({alphanumeric, ...rest}) {
|
||||
super(rest);
|
||||
this.alphanumeric = alphanumeric;
|
||||
this.updateStyle(this.position());
|
||||
}
|
||||
|
||||
path() {
|
||||
console.log("path", "configuration.items[" + this.alphanumeric.index + "]");
|
||||
return "configuration.items[" + this.alphanumeric.index + "]";
|
||||
}
|
||||
|
||||
x() {
|
||||
return this.alphanumeric.x;
|
||||
}
|
||||
|
||||
y() {
|
||||
return this.alphanumeric.y;
|
||||
}
|
||||
|
||||
width() {
|
||||
return this.alphanumeric.width;
|
||||
}
|
||||
|
||||
height() {
|
||||
return this.alphanumeric.height;
|
||||
}
|
||||
|
||||
observeProperties() {
|
||||
[
|
||||
'displayMode',
|
||||
'value',
|
||||
'fill',
|
||||
'stroke',
|
||||
'color',
|
||||
'size',
|
||||
'x',
|
||||
'y',
|
||||
'width',
|
||||
'height'
|
||||
].forEach(property => {
|
||||
this.attachListener(property, newValue => {
|
||||
this.alphanumeric[property] = newValue;
|
||||
|
||||
if (property === 'width' || property === 'height' ||
|
||||
property === 'x' || property === 'y') {
|
||||
this.updateStyle();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return TelemetryViewConfiguration;
|
||||
}
|
||||
);
|
||||
121
src/plugins/displayLayout/ViewConfiguration.js
Normal file
121
src/plugins/displayLayout/ViewConfiguration.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
class ViewConfiguration {
|
||||
|
||||
constructor({domainObject, openmct, gridSize}) {
|
||||
this.domainObject = domainObject;
|
||||
this.gridSize = gridSize;
|
||||
this.mutatePosition = this.mutatePosition.bind(this);
|
||||
this.listeners = [];
|
||||
this.observe = openmct.objects.observe.bind(openmct.objects);
|
||||
this.mutate = function (path, value) {
|
||||
openmct.objects.mutate(this.domainObject, path, value);
|
||||
}.bind(this);
|
||||
this.newPosition = {};
|
||||
}
|
||||
|
||||
mutatePosition() {
|
||||
this.mutate(this.path() + ".x", this.newPosition.position[0]);
|
||||
this.mutate(this.path() + ".y", this.newPosition.position[1]);
|
||||
this.mutate(this.path() + ".width", this.newPosition.dimensions[0]);
|
||||
this.mutate(this.path() + ".height", this.newPosition.dimensions[1]);
|
||||
}
|
||||
|
||||
attachListener(property, callback) {
|
||||
this.listeners.push(this.observe(this.domainObject, this.path() + "." + property, callback));
|
||||
}
|
||||
|
||||
attachListeners() {
|
||||
this.observeProperties();
|
||||
this.listeners.push(this.observe(this.domainObject, '*', function (obj) {
|
||||
this.domainObject = JSON.parse(JSON.stringify(obj));
|
||||
}.bind(this)));
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
this.listeners.forEach(listener => {
|
||||
listener();
|
||||
});
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
position() {
|
||||
return {
|
||||
position: [this.x(), this.y()],
|
||||
dimensions: [this.width(), this.height()]
|
||||
};
|
||||
}
|
||||
|
||||
path() {
|
||||
throw "NOT IMPLEMENTED;"
|
||||
}
|
||||
|
||||
inspectable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
updateStyle(raw) {
|
||||
if (!raw) {
|
||||
raw = this.position();
|
||||
}
|
||||
|
||||
this.style = {
|
||||
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'
|
||||
};
|
||||
}
|
||||
|
||||
observeProperties() {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
x() {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
y() {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
width() {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
height() {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
hasFrame() {
|
||||
// Not implemented
|
||||
}
|
||||
}
|
||||
|
||||
return ViewConfiguration;
|
||||
}
|
||||
);
|
||||
@@ -21,13 +21,9 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<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>
|
||||
<div class="c-box-view"
|
||||
:style="styleObject">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -44,60 +40,24 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
|
||||
export default {
|
||||
makeDefinition() {
|
||||
return {
|
||||
fill: '#717171',
|
||||
stroke: 'transparent',
|
||||
x: 1,
|
||||
y: 1,
|
||||
width: 10,
|
||||
height: 5,
|
||||
useGrid: true
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
index: Number,
|
||||
initSelect: Boolean
|
||||
item: Object
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
styleObject() {
|
||||
let element = this.item.config.element;
|
||||
return {
|
||||
backgroundColor: this.item.fill,
|
||||
border: '1px solid ' + this.item.stroke
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
index(newIndex) {
|
||||
if (!this.context) {
|
||||
return;
|
||||
backgroundColor: element.fill,
|
||||
border: '1px solid ' + element.stroke
|
||||
}
|
||||
|
||||
this.context.index = newIndex;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.context = {
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context, this.initSelect);
|
||||
this.item.config.attachListeners();
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
this.item.config.removeListeners();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -25,26 +25,28 @@
|
||||
@dragover="handleDragOver"
|
||||
@click="bypassSelection"
|
||||
@drop="handleDrop">
|
||||
<!-- Background grid -->
|
||||
<div class="l-layout__grid-holder c-grid">
|
||||
<div class="c-grid__x l-grid l-grid-x"
|
||||
v-if="gridSize[0] >= 3"
|
||||
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
|
||||
<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>
|
||||
<div class="c-grid__y l-grid l-grid-y"
|
||||
v-if="gridSize[1] >= 3"
|
||||
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
||||
<layout-item v-for="(item, index) in layoutItems"
|
||||
class="l-layout__frame"
|
||||
:key="index"
|
||||
:item="item"
|
||||
:gridSize="gridSize"
|
||||
@drilledIn="updateDrilledInState"
|
||||
@dragInProgress="updatePosition"
|
||||
@endDrag="endDrag">
|
||||
</layout-item>
|
||||
</div>
|
||||
<component v-for="(item, index) in layoutItems"
|
||||
:is="item.type"
|
||||
:item="item"
|
||||
:key="item.id"
|
||||
:gridSize="item.useGrid ? gridSize : [1, 1]"
|
||||
:initSelect="initSelectIndex === index"
|
||||
:index="index"
|
||||
@endDrag="endDrag"
|
||||
>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -55,156 +57,150 @@
|
||||
@include abs();
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
&__grid-holder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__object {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__frame {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.is-editing {
|
||||
.l-shell__main-container {
|
||||
&[s-selected],
|
||||
&[s-selected-parent] {
|
||||
// Display grid in main layout holder when editing
|
||||
> .l-layout {
|
||||
background: $editUIGridColorBg;
|
||||
|
||||
> [class*="__grid-holder"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l-layout__frame {
|
||||
&[s-selected],
|
||||
&[s-selected-parent] {
|
||||
// Display grid in nested layouts when editing
|
||||
> * > * > .l-layout {
|
||||
background: $editUIGridColorBg;
|
||||
box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
|
||||
|
||||
> [class*='grid-holder'] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.l-shell__main-container {
|
||||
> .l-layout {
|
||||
[s-selected] {
|
||||
border: $browseBorderSelected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Styles moved to _global.scss;
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import uuid from 'uuid';
|
||||
import LayoutItem from './LayoutItem.vue';
|
||||
import TelemetryViewConfiguration from './../TelemetryViewConfiguration.js'
|
||||
import SubobjectViewConfiguration from './../SubobjectViewConfiguration.js'
|
||||
import ElementViewConfiguration from './../ElementViewConfiguration.js'
|
||||
|
||||
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
|
||||
};
|
||||
const ORDERS = {
|
||||
top: Number.POSITIVE_INFINITY,
|
||||
up: 1,
|
||||
down: -1,
|
||||
bottom: Number.NEGATIVE_INFINITY
|
||||
};
|
||||
|
||||
function getItemDefinition(itemType, ...options) {
|
||||
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
|
||||
|
||||
if (!itemView) {
|
||||
throw `Invalid itemType: ${itemType}`;
|
||||
}
|
||||
|
||||
return itemView.makeDefinition(...options);
|
||||
}
|
||||
const DEFAULT_GRID_SIZE = [10, 10];
|
||||
|
||||
export default {
|
||||
data() {
|
||||
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
|
||||
return {
|
||||
internalDomainObject: domainObject,
|
||||
initSelectIndex: undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
gridSize() {
|
||||
return this.internalDomainObject.configuration.layoutGrid;
|
||||
},
|
||||
layoutItems() {
|
||||
return this.internalDomainObject.configuration.items;
|
||||
gridSize: [],
|
||||
layoutItems: [],
|
||||
drilledIn: undefined
|
||||
}
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: ['domainObject'],
|
||||
components: ITEM_TYPE_VIEW_MAP,
|
||||
components: {
|
||||
LayoutItem
|
||||
},
|
||||
methods: {
|
||||
addElement(itemType, element) {
|
||||
this.addItem(itemType + '-view', element);
|
||||
getConfigurationItems() {
|
||||
this.subobjectViewsById = new Map();
|
||||
this.telemetryViewsById = new Map();
|
||||
let items = this.newDomainObject.configuration.items;
|
||||
items.forEach((item, index) => {
|
||||
if (item.type === 'subobject') {
|
||||
this.subobjectViewsById.set(this.openmct.objects.makeKeyString(item.identifier), true);
|
||||
} else if (item.type === 'telemetry') {
|
||||
this.telemetryViewsById.set(this.openmct.objects.makeKeyString(item.identifier), true);
|
||||
}
|
||||
|
||||
item.index = index;
|
||||
this.makeLayoutItem(item, false);
|
||||
});
|
||||
},
|
||||
makeLayoutItem(item, initSelect) {
|
||||
if (item.type === 'telemetry') {
|
||||
this.makeTelemetryItem(item, initSelect);
|
||||
} else if (item.type === 'subobject') {
|
||||
this.makeSubobjectItem(item, initSelect);
|
||||
} else {
|
||||
this.makeElementItem(item, initSelect);
|
||||
}
|
||||
},
|
||||
makeTelemetryItem(item, initSelect) {
|
||||
let id = this.openmct.objects.makeKeyString(item.identifier);
|
||||
this.openmct.objects.get(id).then(domainObject => {
|
||||
let config = new TelemetryViewConfiguration({
|
||||
domainObject: this.newDomainObject,
|
||||
alphanumeric: item,
|
||||
openmct: openmct,
|
||||
gridSize: this.gridSize
|
||||
});
|
||||
this.layoutItems.push({
|
||||
id: id,
|
||||
domainObject: domainObject,
|
||||
initSelect: initSelect,
|
||||
type: item.type + '-view',
|
||||
config: config
|
||||
});
|
||||
});
|
||||
},
|
||||
makeSubobjectItem(item, initSelect) {
|
||||
let id = this.openmct.objects.makeKeyString(item.identifier);
|
||||
this.openmct.objects.get(id).then(domainObject => {
|
||||
let config = new SubobjectViewConfiguration({
|
||||
domainObject: this.newDomainObject,
|
||||
panel: item,
|
||||
openmct: openmct,
|
||||
gridSize: this.gridSize
|
||||
});
|
||||
this.layoutItems.push({
|
||||
id: id,
|
||||
domainObject: domainObject,
|
||||
initSelect: initSelect,
|
||||
type: item.type + '-view',
|
||||
config: config,
|
||||
drilledIn: this.isItemDrilledIn(id)
|
||||
});
|
||||
});
|
||||
},
|
||||
makeElementItem(item, initSelect) {
|
||||
let config = new ElementViewConfiguration({
|
||||
domainObject: this.newDomainObject,
|
||||
element: item,
|
||||
openmct: openmct,
|
||||
gridSize: this.gridSize
|
||||
});
|
||||
this.layoutItems.push({
|
||||
initSelect: initSelect,
|
||||
type: item.type + '-view',
|
||||
config: config
|
||||
});
|
||||
},
|
||||
setSelection(selection) {
|
||||
if (selection.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.removeSelectionListener) {
|
||||
this.removeSelectionListener();
|
||||
}
|
||||
|
||||
let itemIndex = selection[0].context.index;
|
||||
|
||||
if (itemIndex !== undefined) {
|
||||
this.attachSelectionListener(itemIndex);
|
||||
}
|
||||
this.updateDrilledInState();
|
||||
},
|
||||
attachSelectionListener(index) {
|
||||
let path = `configuration.items[${index}].useGrid`;
|
||||
this.removeSelectionListener = this.openmct.objects.observe(this.internalDomainObject, path, function (value) {
|
||||
let item = this.layoutItems[index];
|
||||
|
||||
if (value) {
|
||||
item.x = Math.round(item.x / this.gridSize[0]);
|
||||
item.y = Math.round(item.y / this.gridSize[1]);
|
||||
item.width = Math.round(item.width / this.gridSize[0]);
|
||||
item.height = Math.round(item.height / this.gridSize[1]);
|
||||
|
||||
if (item.x2) {
|
||||
item.x2 = Math.round(item.x2 / this.gridSize[0]);
|
||||
}
|
||||
if (item.y2) {
|
||||
item.y2 = Math.round(item.y2 / this.gridSize[1]);
|
||||
}
|
||||
} else {
|
||||
item.x = this.gridSize[0] * item.x;
|
||||
item.y = this.gridSize[1] * item.y;
|
||||
item.width = this.gridSize[0] * item.width;
|
||||
item.height = this.gridSize[1] * item.height;
|
||||
|
||||
if (item.x2) {
|
||||
item.x2 = this.gridSize[0] * item.x2;
|
||||
}
|
||||
if (item.y2) {
|
||||
item.y2 = this.gridSize[1] * item.y2;
|
||||
}
|
||||
updateDrilledInState(id) {
|
||||
this.drilledIn = id;
|
||||
this.layoutItems.forEach(function (item) {
|
||||
if (item.type === 'subobject-view') {
|
||||
item.drilledIn = item.id === id;
|
||||
}
|
||||
item.useGrid = value;
|
||||
this.mutate(`configuration.items[${index}]`, item);
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
isItemDrilledIn(id) {
|
||||
return this.drilledIn === id;
|
||||
},
|
||||
updatePosition(item, newPosition) {
|
||||
item.config.newPosition = newPosition;
|
||||
item.config.updateStyle(newPosition);
|
||||
},
|
||||
bypassSelection($event) {
|
||||
if (this.dragInProgress) {
|
||||
@@ -214,56 +210,69 @@
|
||||
return;
|
||||
}
|
||||
},
|
||||
endDrag(item, updates) {
|
||||
endDrag(item) {
|
||||
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);
|
||||
// TODO: emit "finishResizing" for view components to mutate position?
|
||||
item.config.mutatePosition();
|
||||
},
|
||||
mutate(path, value) {
|
||||
this.openmct.objects.mutate(this.internalDomainObject, path, value);
|
||||
this.openmct.objects.mutate(this.newDomainObject, path, value);
|
||||
},
|
||||
handleDrop($event) {
|
||||
if (!$event.dataTransfer.types.includes('openmct/domain-object-path')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event.preventDefault();
|
||||
|
||||
let domainObject = JSON.parse($event.dataTransfer.getData('openmct/domain-object-path'))[0];
|
||||
let domainObject = JSON.parse($event.dataTransfer.getData('domainObject'));
|
||||
let elementRect = this.$el.getBoundingClientRect();
|
||||
let droppedObjectPosition = [
|
||||
this.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);
|
||||
this.addTelemetryObject(domainObject, this.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();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
this.checkForDuplicatePanel(domainObject);
|
||||
}
|
||||
},
|
||||
checkForDuplicatePanel(domainObject) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
let panels = this.newDomainObject.configuration.panels;
|
||||
|
||||
if (panels && panels[id]) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
},
|
||||
addItem(item) {
|
||||
let items = this.newDomainObject.configuration.items || [];
|
||||
item.index = items.push(item) - 1;
|
||||
this.mutate("configuration.items", items);
|
||||
this.makeLayoutItem(item, true);
|
||||
},
|
||||
addTelemetryObject(domainObject, position) {
|
||||
let item = TelemetryViewConfiguration.create(domainObject, position, this.openmct);
|
||||
this.addItem(item);
|
||||
this.telemetryViewsById.set(this.openmct.objects.makeKeyString(domainObject.identifier), true);
|
||||
},
|
||||
addElement(type) {
|
||||
Promise.resolve(ElementViewConfiguration.create(type, this.openmct))
|
||||
.then(item => {
|
||||
this.addItem(item);
|
||||
});
|
||||
},
|
||||
handleDragOver($event){
|
||||
$event.preventDefault();
|
||||
},
|
||||
@@ -275,136 +284,51 @@
|
||||
return false;
|
||||
}
|
||||
},
|
||||
addItem(itemType, ...options) {
|
||||
let item = getItemDefinition(itemType, this.openmct, this.gridSize, ...options);
|
||||
item.type = itemType;
|
||||
item.id = uuid();
|
||||
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.identifier) {
|
||||
return;
|
||||
}
|
||||
addSubobject(domainObject) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
|
||||
let keyString = this.openmct.objects.makeKeyString(item.identifier);
|
||||
|
||||
if (item.type === "telemetry-view") {
|
||||
let count = this.telemetryViewMap[keyString] || 0;
|
||||
this.telemetryViewMap[keyString] = ++count;
|
||||
} else if (item.type === "subobject-view") {
|
||||
this.objectViewMap[keyString] = true;
|
||||
}
|
||||
},
|
||||
removeItem(item, index) {
|
||||
this.initSelectIndex = -1;
|
||||
this.layoutItems.splice(index, 1);
|
||||
this.mutate("configuration.items", this.layoutItems);
|
||||
this.untrackItem(item);
|
||||
this.$el.click();
|
||||
},
|
||||
untrackItem(item) {
|
||||
if (!item.identifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keyString = this.openmct.objects.makeKeyString(item.identifier);
|
||||
|
||||
if (item.type === 'telemetry-view') {
|
||||
let count = --this.telemetryViewMap[keyString];
|
||||
|
||||
if (count === 0) {
|
||||
delete this.telemetryViewMap[keyString];
|
||||
this.removeFromComposition(keyString);
|
||||
if (this.isTelemetry(domainObject)) {
|
||||
if (!this.telemetryViewsById.has(id)) {
|
||||
this.addTelemetryObject(domainObject);
|
||||
}
|
||||
} else if (item.type === 'subobject-view') {
|
||||
delete this.objectViewMap[keyString];
|
||||
this.removeFromComposition(keyString);
|
||||
}
|
||||
},
|
||||
removeFromComposition(keyString) {
|
||||
let composition = _.get(this.internalDomainObject, 'composition');
|
||||
composition = composition.filter(identifier => {
|
||||
return this.openmct.objects.makeKeyString(identifier) !== keyString;
|
||||
});
|
||||
this.mutate("composition", composition);
|
||||
},
|
||||
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) {
|
||||
let keyString = this.openmct.objects.makeKeyString(identifier);
|
||||
|
||||
if (this.objectViewMap[keyString]) {
|
||||
delete this.objectViewMap[keyString];
|
||||
this.removeFromConfiguration(keyString);
|
||||
} else if (this.telemetryViewMap[keyString]) {
|
||||
delete this.telemetryViewMap[keyString];
|
||||
this.removeFromConfiguration(keyString);
|
||||
}
|
||||
},
|
||||
removeFromConfiguration(keyString) {
|
||||
let layoutItems = this.layoutItems.filter(item => {
|
||||
if (!item.identifier) {
|
||||
return true;
|
||||
} else {
|
||||
return this.openmct.objects.makeKeyString(item.identifier) !== keyString;
|
||||
}
|
||||
});
|
||||
this.mutate("configuration.items", layoutItems);
|
||||
this.$el.click();
|
||||
},
|
||||
orderItem(position, index) {
|
||||
let delta = ORDERS[position];
|
||||
let newIndex = Math.max(Math.min(index + delta, this.layoutItems.length - 1), 0);
|
||||
let item = this.layoutItems[index];
|
||||
|
||||
if (newIndex !== index) {
|
||||
this.layoutItems.splice(index, 1);
|
||||
this.layoutItems.splice(newIndex, 0, item);
|
||||
this.mutate('configuration.items', this.layoutItems);
|
||||
|
||||
if (this.removeSelectionListener) {
|
||||
this.removeSelectionListener();
|
||||
this.attachSelectionListener(newIndex);
|
||||
} else {
|
||||
if (!this.subobjectViewsById.has(id)) {
|
||||
let item = SubobjectViewConfiguration.create(domainObject, this.gridSize, this.droppedObjectPosition);
|
||||
this.subobjectViewsById.set(id, true);
|
||||
this.addItem(item, true);
|
||||
delete this.droppedObjectPosition;
|
||||
}
|
||||
}
|
||||
},
|
||||
removeSubobject() {
|
||||
// Not yet implemented
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) {
|
||||
this.internalDomainObject = JSON.parse(JSON.stringify(obj));
|
||||
this.newDomainObject = this.domainObject;
|
||||
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;
|
||||
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));
|
||||
|
||||
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.getConfigurationItems();
|
||||
|
||||
this.composition = this.openmct.composition.get(this.newDomainObject);
|
||||
this.composition.on('add', this.addSubobject);
|
||||
this.composition.on('remove', this.removeSubobject);
|
||||
this.composition.load();
|
||||
},
|
||||
destroyed: function () {
|
||||
this.openmct.off('change', this.setSelection);
|
||||
this.composition.off('add', this.addChild);
|
||||
this.composition.off('remove', this.removeChild);
|
||||
this.composition.off('add', this.addSubobject);
|
||||
this.composition.off('remove', this.removeSubobject);
|
||||
this.unlisten();
|
||||
|
||||
if (this.removeSelectionListener) {
|
||||
this.removeSelectionListener();
|
||||
}
|
||||
delete this.subobjectViewsById;
|
||||
delete this.telemetryViewsById;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -21,13 +21,9 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<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>
|
||||
<div class="c-image-view"
|
||||
:style="styleObject">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -46,59 +42,24 @@
|
||||
</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,
|
||||
useGrid: true
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
index: Number,
|
||||
initSelect: Boolean
|
||||
},
|
||||
components: {
|
||||
LayoutFrame
|
||||
item: Object
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
styleObject() {
|
||||
let element = this.item.config.element;
|
||||
return {
|
||||
backgroundImage: 'url(' + this.item.url + ')',
|
||||
border: '1px solid ' + this.item.stroke
|
||||
backgroundImage: 'url(' + element.url + ')',
|
||||
border: '1px solid ' + element.stroke
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
index(newIndex) {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.index = newIndex;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.context = {
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context, this.initSelect);
|
||||
this.item.config.attachListeners();
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
this.item.config.removeListeners();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,300 +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__frame c-frame"
|
||||
:class="{
|
||||
'no-frame': !item.hasFrame,
|
||||
'u-inspectable': inspectable,
|
||||
'is-resizing': isResizing
|
||||
}"
|
||||
:style="style">
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<!-- Drag handles -->
|
||||
<div class="c-frame-edit">
|
||||
<div class="c-frame-edit__move"
|
||||
@mousedown="startDrag([1,1], [0,0], $event, 'move')"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--nw"
|
||||
@mousedown="startDrag([1,1], [-1,-1], $event, 'resize')"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--ne"
|
||||
@mousedown="startDrag([0,1], [1,-1], $event, 'resize')"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--sw"
|
||||
@mousedown="startDrag([1,0], [-1,1], $event, 'resize')"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--se"
|
||||
@mousedown="startDrag([0,0], [1,1], $event, 'resize')"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* FRAME */
|
||||
.c-frame {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// Whatever is placed into the slot, make it fill the entirety of the space, obeying padding
|
||||
> *:first-child {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&:not(.no-frame) {
|
||||
background: $colorBodyBg;
|
||||
border: $browseFrameBorder;
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.c-frame-edit {
|
||||
// In Layouts, this is the editing rect and handles
|
||||
// In Fixed Position, this is a wrapper element
|
||||
@include abs();
|
||||
display: none;
|
||||
|
||||
&__move {
|
||||
@include abs();
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
&__handle {
|
||||
$d: 6px;
|
||||
$o: floor($d * -0.5);
|
||||
background: $editFrameColorHandleFg;
|
||||
box-shadow: $editFrameColorHandleBg 0 0 0 2px;
|
||||
display: none; // Set to block via s-selected selector
|
||||
position: absolute;
|
||||
width: $d; height: $d;
|
||||
top: auto; right: auto; bottom: auto; left: auto;
|
||||
|
||||
&:before {
|
||||
// Extended hit area
|
||||
@include abs(-10px);
|
||||
content: '';
|
||||
display: block;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $editUIColor;
|
||||
}
|
||||
|
||||
&--nwse {
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
&--nw {
|
||||
cursor: nw-resize;
|
||||
left: $o; top: $o;
|
||||
}
|
||||
|
||||
&--ne {
|
||||
cursor: ne-resize;
|
||||
right: $o; top: $o;
|
||||
}
|
||||
|
||||
&--se {
|
||||
cursor: se-resize;
|
||||
right: $o; bottom: $o;
|
||||
}
|
||||
|
||||
&--sw {
|
||||
cursor: sw-resize;
|
||||
left: $o; bottom: $o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-so-view.has-complex-content + .c-frame-edit {
|
||||
// Target frames that hold domain objects that include header elements, as opposed to drawing and alpha objects
|
||||
// Make the __move element a more affordable drag UI element
|
||||
.c-frame-edit__move {
|
||||
@include userSelectNone();
|
||||
background: $editFrameMovebarColorBg;
|
||||
box-shadow: rgba(black, 0.2) 0 1px;
|
||||
bottom: auto;
|
||||
height: 0; // Height is set on hover on s-selected.c-frame
|
||||
opacity: 0.8;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
|
||||
&:before {
|
||||
// Grippy
|
||||
$h: 4px;
|
||||
$tbOffset: ($editFrameMovebarH - $h) / 2;
|
||||
$lrOffset: 25%;
|
||||
@include grippy($editFrameMovebarColorFg);
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: $tbOffset; right: $lrOffset; bottom: $tbOffset; left: $lrOffset;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $editFrameHovMovebarColorBg;
|
||||
&:before { @include grippy($editFrameHovMovebarColorFg); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-editing {
|
||||
.c-frame {
|
||||
$moveBarOutDelay: 500ms;
|
||||
&.no-frame {
|
||||
border: $editFrameBorder; // Base border style for a frame element while editing.
|
||||
}
|
||||
|
||||
&-edit {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
&-edit__move,
|
||||
.c-so-view {
|
||||
transition: $transOut;
|
||||
transition-delay: $moveBarOutDelay;
|
||||
}
|
||||
|
||||
&:not([s-selected]) {
|
||||
&:hover {
|
||||
border: $editFrameBorderHov;
|
||||
}
|
||||
}
|
||||
|
||||
&[s-selected] {
|
||||
// All frames selected while editing
|
||||
border: $editFrameSelectedBorder;
|
||||
box-shadow: $editFrameSelectedShdw;
|
||||
|
||||
> .c-frame-edit {
|
||||
[class*='__handle'] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l-layout__frame:not(.is-resizing) {
|
||||
// Show and animate the __move bar for sub-object views with complex content
|
||||
&:hover > .c-so-view.has-complex-content {
|
||||
// Move content down so the __move bar doesn't cover it.
|
||||
padding-top: $editFrameMovebarH;
|
||||
transition: $transIn;
|
||||
|
||||
&.c-so-view--no-frame {
|
||||
// Move content down with a bit more space
|
||||
padding-top: $editFrameMovebarH + $interiorMarginSm;
|
||||
}
|
||||
|
||||
// Show the move bar
|
||||
+ .c-frame-edit .c-frame-edit__move {
|
||||
height: $editFrameMovebarH;
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import LayoutDrag from './../LayoutDrag'
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragPosition: undefined,
|
||||
isResizing: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
let {x, y, width, height} = this.item;
|
||||
|
||||
if (this.dragPosition) {
|
||||
[x, y] = this.dragPosition.position;
|
||||
[width, height] = this.dragPosition.dimensions;
|
||||
}
|
||||
|
||||
return {
|
||||
left: (this.gridSize[0] * x) + 'px',
|
||||
top: (this.gridSize[1] * y) + 'px',
|
||||
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: {
|
||||
updatePosition(event) {
|
||||
let currentPosition = [event.pageX, event.pageY];
|
||||
this.initialPosition = this.initialPosition || currentPosition;
|
||||
this.delta = currentPosition.map(function (value, index) {
|
||||
return value - this.initialPosition[index];
|
||||
}.bind(this));
|
||||
},
|
||||
startDrag(posFactor, dimFactor, event, type) {
|
||||
document.body.addEventListener('mousemove', this.continueDrag);
|
||||
document.body.addEventListener('mouseup', this.endDrag);
|
||||
|
||||
this.dragPosition = {
|
||||
position: [this.item.x, this.item.y],
|
||||
dimensions: [this.item.width, this.item.height]
|
||||
};
|
||||
this.updatePosition(event);
|
||||
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
|
||||
this.isResizing = type === 'resize';
|
||||
event.preventDefault();
|
||||
},
|
||||
continueDrag(event) {
|
||||
event.preventDefault();
|
||||
this.updatePosition(event);
|
||||
this.dragPosition = this.activeDrag.getAdjustedPosition(this.delta);
|
||||
},
|
||||
endDrag(event) {
|
||||
document.body.removeEventListener('mousemove', this.continueDrag);
|
||||
document.body.removeEventListener('mouseup', this.endDrag);
|
||||
this.continueDrag(event);
|
||||
let [x, y] = this.dragPosition.position;
|
||||
let [width, height] = this.dragPosition.dimensions;
|
||||
this.$emit('endDrag', this.item, {x, y, width, height});
|
||||
this.dragPosition = undefined;
|
||||
this.initialPosition = undefined;
|
||||
this.delta = undefined;
|
||||
this.isResizing = undefined;
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
188
src/plugins/displayLayout/components/LayoutItem.vue
Normal file
188
src/plugins/displayLayout/components/LayoutItem.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
/*****************************************************************************
|
||||
* 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-frame has-local-controls"
|
||||
:style="item.config.style"
|
||||
:class="[classObject, {
|
||||
'u-inspectable': item.config.inspectable()
|
||||
}]"
|
||||
@dblclick="drill(item.id, $event)">
|
||||
|
||||
<component :is="item.type" :item="item" :gridSize="gridSize"></component>
|
||||
|
||||
<!-- Drag handles -->
|
||||
<div class="c-frame-edit">
|
||||
<div class="c-frame-edit__move"
|
||||
@mousedown="startDrag([1,1], [0,0], $event)"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--nw"
|
||||
@mousedown="startDrag([1,1], [-1,-1], $event)"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--ne"
|
||||
@mousedown="startDrag([0,1], [1,-1], $event)"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--sw"
|
||||
@mousedown="startDrag([1,0], [-1,1], $event)"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--se"
|
||||
@mousedown="startDrag([0,0], [1,1], $event)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* FRAME */
|
||||
.c-frame {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid transparent;
|
||||
|
||||
/*************************** NO-FRAME */
|
||||
&.no-frame {
|
||||
> [class*="contents"] > [class*="__header"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.no-frame) {
|
||||
background: $colorBodyBg;
|
||||
border: 1px solid $colorInteriorBorder;
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
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'
|
||||
import LayoutDrag from './../LayoutDrag'
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array
|
||||
},
|
||||
components: {
|
||||
SubobjectView,
|
||||
TelemetryView,
|
||||
BoxView,
|
||||
TextView,
|
||||
LineView,
|
||||
ImageView
|
||||
},
|
||||
computed: {
|
||||
classObject: function () {
|
||||
return {
|
||||
'is-drilled-in': this.item.drilledIn,
|
||||
'no-frame': !this.item.config.hasFrame()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
drill(id, $event) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
if (!this.openmct.editor.isEditing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.item.domainObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.openmct.composition.get(this.item.domainObject) === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable for fixed position.
|
||||
if (this.item.domainObject.type === 'telemetry.fixed') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('drilledIn', id);
|
||||
},
|
||||
updatePosition(event) {
|
||||
let currentPosition = [event.pageX, event.pageY];
|
||||
this.initialPosition = this.initialPosition || currentPosition;
|
||||
this.delta = currentPosition.map(function (value, index) {
|
||||
return value - this.initialPosition[index];
|
||||
}.bind(this));
|
||||
},
|
||||
startDrag(posFactor, dimFactor, event) {
|
||||
document.body.addEventListener('mousemove', this.continueDrag);
|
||||
document.body.addEventListener('mouseup', this.endDrag);
|
||||
|
||||
this.updatePosition(event);
|
||||
this.activeDrag = new LayoutDrag(
|
||||
this.item.config.position(),
|
||||
posFactor,
|
||||
dimFactor,
|
||||
this.gridSize
|
||||
);
|
||||
event.preventDefault();
|
||||
},
|
||||
continueDrag(event) {
|
||||
event.preventDefault();
|
||||
this.updatePosition(event);
|
||||
|
||||
if (this.activeDrag) {
|
||||
this.$emit(
|
||||
'dragInProgress',
|
||||
this.item,
|
||||
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);
|
||||
this.initialPosition = undefined;
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let context = {
|
||||
item: this.item.domainObject,
|
||||
layoutItem: this.item,
|
||||
addElement: this.$parent.addElement
|
||||
};
|
||||
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el,
|
||||
context,
|
||||
this.item.initSelect
|
||||
);
|
||||
},
|
||||
destroyed() {
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -20,214 +20,37 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="l-layout__frame c-frame 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>
|
||||
<template>
|
||||
<div>
|
||||
<svg :width="gridSize[0] * element.width"
|
||||
:height=" gridSize[1] * element.height">
|
||||
<line :x1=" gridSize[0] * element.x1 + 1"
|
||||
:y1="gridSize[1] * element.y1 + 1 "
|
||||
:x2="gridSize[0] * element.x2 + 1"
|
||||
:y2=" gridSize[1] * element.y2 + 1 "
|
||||
:stroke="element.stroke"
|
||||
stroke-width="2">
|
||||
</line>
|
||||
</svg>
|
||||
</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',
|
||||
useGrid: true
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
initSelect: Boolean,
|
||||
index: Number,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragPosition: undefined
|
||||
};
|
||||
gridSize: Array
|
||||
},
|
||||
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`,
|
||||
};
|
||||
},
|
||||
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;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
index(newIndex) {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.index = newIndex;
|
||||
element() {
|
||||
return this.item.config.element;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.context = {
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context, this.initSelect);
|
||||
this.item.config.attachListeners();
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
this.item.config.removeListeners();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -20,101 +20,99 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||
<object-frame v-if="domainObject"
|
||||
:domain-object="domainObject"
|
||||
:object-path="objectPath"
|
||||
:has-frame="item.hasFrame">
|
||||
</object-frame>
|
||||
</layout-frame>
|
||||
<div class="u-contents">
|
||||
<div class="c-so-view__header">
|
||||
<div class="c-so-view__header__start">
|
||||
<div class="c-so-view__name icon-object">{{ item.domainObject.name }}</div>
|
||||
<div class="c-so-view__context-actions c-disclosure-button"></div>
|
||||
</div>
|
||||
<div class="c-so-view__header__end">
|
||||
<div class="c-button icon-expand local-controls--hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<object-view class="c-so-view__object-view"
|
||||
:object="item.domainObject"></object-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ObjectFrame from '../../../ui/components/ObjectFrame.vue'
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
const MINIMUM_FRAME_SIZE = [320, 180],
|
||||
DEFAULT_DIMENSIONS = [10, 10],
|
||||
DEFAULT_POSITION = [1, 1],
|
||||
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
|
||||
.c-so-view {
|
||||
/*************************** HEADER */
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: $interiorMargin;
|
||||
|
||||
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),
|
||||
useGrid: true
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
initSelect: Boolean,
|
||||
index: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainObject: undefined,
|
||||
objectPath: []
|
||||
> [class*="__"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ObjectFrame,
|
||||
LayoutFrame
|
||||
},
|
||||
watch: {
|
||||
index(newIndex) {
|
||||
if (!this.context) {
|
||||
return;
|
||||
|
||||
> * + * {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.context.index = newIndex;
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
flex: 0 1 auto;
|
||||
font-size: 1.2em;
|
||||
|
||||
&:before {
|
||||
// Object type icon
|
||||
flex: 0 0 auto;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setObject(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.objectPath = [this.domainObject].concat(this.openmct.router.path);
|
||||
this.context = {
|
||||
item: domainObject,
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context, this.initSelect);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.objects.get(this.item.identifier)
|
||||
.then(this.setObject);
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
|
||||
/*************************** 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ObjectView from '../../../ui/components/layout/ObjectView.vue'
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object
|
||||
},
|
||||
components: {
|
||||
ObjectView,
|
||||
},
|
||||
mounted() {
|
||||
this.item.config.attachListeners();
|
||||
},
|
||||
destroyed() {
|
||||
this.item.config.removeListeners();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -21,25 +21,20 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<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 class="c-telemetry-view"
|
||||
:style="styleObject">
|
||||
<div v-if="showLabel"
|
||||
class="c-telemetry-view__label">
|
||||
<div class="c-telemetry-view__label-text">{{ item.domainObject.name }}</div>
|
||||
</div>
|
||||
</layout-frame>
|
||||
|
||||
<div v-if="showValue"
|
||||
:title="fieldName"
|
||||
class="c-telemetry-view__value"
|
||||
:class="[telemetryClass]">
|
||||
<div class="c-telemetry-view__value-text">{{ telemetryValue }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -77,66 +72,37 @@
|
||||
</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",
|
||||
useGrid: true
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
initSelect: Boolean,
|
||||
index: Number
|
||||
},
|
||||
components: {
|
||||
LayoutFrame
|
||||
item: Object
|
||||
},
|
||||
computed: {
|
||||
showLabel() {
|
||||
let displayMode = this.item.displayMode;
|
||||
let displayMode = this.item.config.alphanumeric.displayMode;
|
||||
return displayMode === 'all' || displayMode === 'label';
|
||||
},
|
||||
showValue() {
|
||||
let displayMode = this.item.displayMode;
|
||||
let displayMode = this.item.config.alphanumeric.displayMode;
|
||||
return displayMode === 'all' || displayMode === 'value';
|
||||
},
|
||||
styleObject() {
|
||||
let alphanumeric = this.item.config.alphanumeric;
|
||||
return {
|
||||
backgroundColor: this.item.fill,
|
||||
borderColor: this.item.stroke,
|
||||
color: this.item.color,
|
||||
fontSize: this.item.size
|
||||
backgroundColor: alphanumeric.fill,
|
||||
borderColor: alphanumeric.stroke,
|
||||
color: alphanumeric.color,
|
||||
fontSize: alphanumeric.size
|
||||
}
|
||||
},
|
||||
fieldName() {
|
||||
return this.valueMetadata && this.valueMetadata.name;
|
||||
return this.valueMetadata.name;
|
||||
},
|
||||
valueMetadata() {
|
||||
return this.datum && this.metadata.value(this.item.value);
|
||||
return this.metadata.value(this.item.config.alphanumeric.value);
|
||||
},
|
||||
valueFormatter() {
|
||||
return this.formats[this.item.value];
|
||||
return this.formats[this.item.config.alphanumeric.value];
|
||||
},
|
||||
telemetryValue() {
|
||||
if (!this.datum) {
|
||||
@@ -156,18 +122,8 @@
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
datum: undefined,
|
||||
formats: undefined,
|
||||
domainObject: undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
index(newIndex) {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.index = newIndex;
|
||||
datum: {},
|
||||
formats: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -178,7 +134,7 @@
|
||||
end: bounds.end,
|
||||
size: 1
|
||||
};
|
||||
this.openmct.telemetry.request(this.domainObject, options)
|
||||
this.openmct.telemetry.request(this.item.domainObject, options)
|
||||
.then(data => {
|
||||
if (data.length > 0) {
|
||||
this.updateView(data[data.length - 1]);
|
||||
@@ -186,7 +142,7 @@
|
||||
});
|
||||
},
|
||||
subscribeToObject() {
|
||||
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
|
||||
this.subscription = this.openmct.telemetry.subscribe(this.item.domainObject, function (datum) {
|
||||
if (this.openmct.time.clock() !== undefined) {
|
||||
this.updateView(datum);
|
||||
}
|
||||
@@ -204,38 +160,26 @@
|
||||
refreshData(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
this.datum = undefined;
|
||||
this.requestHistoricalData(this.domainObject);
|
||||
this.requestHistoricalData(this.item.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();
|
||||
|
||||
this.context = {
|
||||
item: domainObject,
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context, this.initSelect);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.metadata = this.openmct.telemetry.getMetadata(this.item.domainObject);
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.objects.get(this.item.identifier)
|
||||
.then(this.setObject);
|
||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.item.domainObject);
|
||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||
|
||||
this.requestHistoricalData();
|
||||
this.subscribeToObject();
|
||||
|
||||
this.item.config.attachListeners();
|
||||
this.openmct.time.on("bounds", this.refreshData);
|
||||
},
|
||||
destroyed() {
|
||||
this.removeSubscription();
|
||||
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
|
||||
this.item.config.removeListeners();
|
||||
this.openmct.time.off("bounds", this.refreshData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,10 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<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>
|
||||
<div class="c-text-view"
|
||||
:style="styleObject">
|
||||
{{ item.config.element.text }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -46,65 +42,26 @@
|
||||
</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,
|
||||
useGrid: true
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
index: Number,
|
||||
initSelect: Boolean
|
||||
},
|
||||
components: {
|
||||
LayoutFrame
|
||||
item: Object
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
styleObject() {
|
||||
let element = this.item.config.element;
|
||||
return {
|
||||
backgroundColor: this.item.fill,
|
||||
borderColor: this.item.stroke,
|
||||
color: this.item.color,
|
||||
fontSize: this.item.size
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
index(newIndex) {
|
||||
if (!this.context) {
|
||||
return;
|
||||
backgroundColor: element.fill,
|
||||
borderColor: element.stroke,
|
||||
color: element.color,
|
||||
fontSize: element.size
|
||||
}
|
||||
|
||||
this.context.index = newIndex;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.context = {
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context, this.initSelect);
|
||||
this.item.config.attachListeners();
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
this.item.config.removeListeners();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -33,9 +33,6 @@ export default function () {
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'layout';
|
||||
},
|
||||
canEdit: function (domainObject) {
|
||||
return domainObject.type === 'layout';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
return {
|
||||
@@ -60,9 +57,7 @@ export default function () {
|
||||
getSelectionContext() {
|
||||
return {
|
||||
item: domainObject,
|
||||
addElement: component && component.$refs.displayLayout.addElement,
|
||||
removeItem: component && component.$refs.displayLayout.removeItem,
|
||||
orderItem: component && component.$refs.displayLayout.orderItem
|
||||
addElement: component && component.$refs.displayLayout.addElement
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
|
||||
@@ -22,52 +22,47 @@
|
||||
|
||||
<template>
|
||||
<div class="c-fl-container"
|
||||
:style="[{'flex-basis': sizeString}]"
|
||||
:class="{'is-empty': !frames.length}">
|
||||
<div class="c-fl-container__header"
|
||||
:style="[{'flex-basis': size}]"
|
||||
:class="{'is-empty': frames.length === 1}">
|
||||
<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>
|
||||
@dragstart="startContainerDrag"
|
||||
@dragend="stopContainerDrag">
|
||||
<span class="c-fl-container__size-indicator">{{ size }}</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">
|
||||
<div class="u-contents"
|
||||
v-for="(frame, i) in frames"
|
||||
:key="i">
|
||||
|
||||
<frame-component
|
||||
class="c-fl-container__frame"
|
||||
:key="frame.id"
|
||||
:style="{
|
||||
'flex-basis': `${frame.height}%`
|
||||
}"
|
||||
:frame="frame"
|
||||
:size="frame.height"
|
||||
:index="i"
|
||||
:containerIndex="index">
|
||||
:containerIndex="index"
|
||||
:isEditing="isEditing"
|
||||
:isDragging="isDragging"
|
||||
@frame-drag-from="frameDragFrom"
|
||||
@frame-drop-to="frameDropTo"
|
||||
@delete-frame="promptBeforeDeletingFrame"
|
||||
@add-container="addContainer">
|
||||
</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"
|
||||
v-if="i !== 0 && (i !== frames.length - 1)"
|
||||
v-show="isEditing"
|
||||
:index="i"
|
||||
:orientation="rowsLayout ? 'horizontal' : 'vertical'"
|
||||
@init-move="startFrameResizing"
|
||||
@move="frameResizing"
|
||||
@end-move="endFrameResizing">
|
||||
</resize-handle>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -76,98 +71,61 @@
|
||||
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 SNAP_TO_PERCENTAGE = 1;
|
||||
const MIN_FRAME_SIZE = 5;
|
||||
|
||||
export default {
|
||||
inject:['openmct'],
|
||||
props: ['container', 'index', 'rowsLayout'],
|
||||
mixins: [isEditingMixin],
|
||||
inject:['openmct', 'domainObject'],
|
||||
props: ['size', 'frames', 'index', 'isEditing', 'isDragging', 'rowsLayout'],
|
||||
components: {
|
||||
FrameComponent,
|
||||
ResizeHandle,
|
||||
DropHint
|
||||
ResizeHandle
|
||||
},
|
||||
computed: {
|
||||
frames() {
|
||||
return this.container.frames;
|
||||
},
|
||||
sizeString() {
|
||||
return `${Math.round(this.container.size)}%`
|
||||
data() {
|
||||
return {
|
||||
initialPos: 0,
|
||||
frameIndex: 0,
|
||||
maxMoveSize: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
allowDrop(event, index) {
|
||||
if (event.dataTransfer.types.includes('openmct/domain-object-path')) {
|
||||
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;
|
||||
}
|
||||
frameDragFrom(frameIndex) {
|
||||
this.$emit('frame-drag-from', this.index, frameIndex);
|
||||
},
|
||||
moveOrCreateFrame(insertIndex, event) {
|
||||
if (event.dataTransfer.types.includes('openmct/domain-object-path')) {
|
||||
// create frame using domain object
|
||||
let domainObject = JSON.parse(event.dataTransfer.getData('openmct/domain-object-path'))[0];
|
||||
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
|
||||
);
|
||||
frameDropTo(frameIndex, event) {
|
||||
let domainObject = event.dataTransfer.getData('domainObject'),
|
||||
frameObject;
|
||||
|
||||
if (domainObject) {
|
||||
frameObject = new Frame(JSON.parse(domainObject));
|
||||
}
|
||||
|
||||
this.$emit('frame-drop-to', this.index, frameIndex, frameObject);
|
||||
},
|
||||
startFrameResizing(index) {
|
||||
let beforeFrame = this.frames[index],
|
||||
afterFrame = this.frames[index + 1];
|
||||
|
||||
this.maxMoveSize = beforeFrame.size + afterFrame.size;
|
||||
this.maxMoveSize = beforeFrame.height + afterFrame.height;
|
||||
},
|
||||
frameResizing(index, delta, event) {
|
||||
let percentageMoved = Math.round(delta / this.getElSize() * 100),
|
||||
|
||||
let percentageMoved = (delta / this.getElSize(this.$el))*100,
|
||||
beforeFrame = this.frames[index],
|
||||
afterFrame = this.frames[index + 1];
|
||||
|
||||
beforeFrame.size = this.getFrameSize(beforeFrame.size + percentageMoved);
|
||||
afterFrame.size = this.getFrameSize(afterFrame.size - percentageMoved);
|
||||
beforeFrame.height = this.snapToPercentage(beforeFrame.height + percentageMoved);
|
||||
afterFrame.height = this.snapToPercentage(afterFrame.height - percentageMoved);
|
||||
},
|
||||
endFrameResizing(index, event) {
|
||||
this.persist();
|
||||
},
|
||||
getElSize() {
|
||||
getElSize(el) {
|
||||
if (this.rowsLayout) {
|
||||
return this.$el.offsetWidth;
|
||||
return el.offsetWidth;
|
||||
} else {
|
||||
return this.$el.offsetHeight;
|
||||
return el.offsetHeight;
|
||||
}
|
||||
},
|
||||
getFrameSize(size) {
|
||||
@@ -179,19 +137,72 @@ export default {
|
||||
return size;
|
||||
}
|
||||
},
|
||||
snapToPercentage(value){
|
||||
let rem = value % SNAP_TO_PERCENTAGE,
|
||||
roundedValue;
|
||||
|
||||
if (rem < 0.5) {
|
||||
roundedValue = Math.floor(value/SNAP_TO_PERCENTAGE)*SNAP_TO_PERCENTAGE;
|
||||
} else {
|
||||
roundedValue = Math.ceil(value/SNAP_TO_PERCENTAGE)*SNAP_TO_PERCENTAGE;
|
||||
}
|
||||
|
||||
return this.getFrameSize(roundedValue);
|
||||
},
|
||||
persist() {
|
||||
this.$emit('persist', this.index);
|
||||
},
|
||||
promptBeforeDeletingFrame(frameIndex) {
|
||||
let deleteFrame = this.deleteFrame;
|
||||
|
||||
let prompt = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: `This action will remove ${this.frames[frameIndex].domainObject.name} from this Flexible Layout. Do you want to continue?`,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Ok',
|
||||
emphasis: 'true',
|
||||
callback: function () {
|
||||
deleteFrame(frameIndex);
|
||||
prompt.dismiss();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
prompt.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
deleteFrame(frameIndex) {
|
||||
this.frames.splice(frameIndex, 1);
|
||||
this.$parent.recalculateOldFrameSize(this.frames);
|
||||
this.persist();
|
||||
},
|
||||
deleteContainer() {
|
||||
this.$emit('delete-container', this.index);
|
||||
},
|
||||
addContainer() {
|
||||
this.$emit('add-container', this.index);
|
||||
},
|
||||
startContainerDrag(event) {
|
||||
event.dataTransfer.setData('containerid', this.container.id);
|
||||
event.stopPropagation();
|
||||
this.$emit('start-container-drag', this.index);
|
||||
},
|
||||
stopContainerDrag(event) {
|
||||
event.stopPropagation();
|
||||
this.$emit('stop-container-drag');
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let context = {
|
||||
item: this.$parent.domainObject,
|
||||
item: this.domainObject,
|
||||
method: this.deleteContainer,
|
||||
addContainer: this.addContainer,
|
||||
type: 'container',
|
||||
containerId: this.container.id
|
||||
index: this.index,
|
||||
type: 'container'
|
||||
}
|
||||
|
||||
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, false);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div v-show="isValidTarget">
|
||||
<div>
|
||||
<div class="c-drop-hint c-drop-hint--always-show"
|
||||
:class="{'is-mouse-over': isMouseOver}"
|
||||
@dragenter="dragenter"
|
||||
@@ -37,17 +37,10 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props:{
|
||||
index: Number,
|
||||
allowDrop: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
props:['index'],
|
||||
data() {
|
||||
return {
|
||||
isMouseOver: false,
|
||||
isValidTarget: false
|
||||
isMouseOver: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -58,23 +51,8 @@ export default {
|
||||
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;
|
||||
this.$emit('object-drop-to', event, this.index);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('dragstart', this.dragstart);
|
||||
document.addEventListener('dragend', this.dragend);
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('dragstart', this.dragstart);
|
||||
document.removeEventListener('dragend', this.dragend);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
/*****************************************************************************
|
||||
* 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"
|
||||
@@ -32,31 +10,40 @@
|
||||
'c-fl--rows': rowsLayout === true
|
||||
}">
|
||||
|
||||
<template v-for="(container, index) in containers">
|
||||
<div class="u-contents"
|
||||
v-for="(container, index) in containers"
|
||||
:key="index">
|
||||
|
||||
<drop-hint
|
||||
class="c-fl-frame__drop-hint"
|
||||
style="flex-basis: 15px;"
|
||||
v-if="index === 0 && containers.length > 1"
|
||||
:key="index"
|
||||
v-show="isContainerDragging"
|
||||
:index="-1"
|
||||
:allow-drop="allowContainerDrop"
|
||||
@object-drop-to="moveContainer">
|
||||
@object-drop-to="containerDropTo">
|
||||
</drop-hint>
|
||||
|
||||
<container-component
|
||||
class="c-fl__container"
|
||||
:key="container.id"
|
||||
ref="containerComponent"
|
||||
:index="index"
|
||||
:container="container"
|
||||
:size="`${Math.round(container.width)}%`"
|
||||
:frames="container.frames"
|
||||
:isEditing="isEditing"
|
||||
:isDragging="isDragging"
|
||||
:rowsLayout="rowsLayout"
|
||||
@move-frame="moveFrame"
|
||||
@create-frame="createFrame"
|
||||
@persist="persist">
|
||||
@addFrame="addFrame"
|
||||
@frame-drag-from="frameDragFromHandler"
|
||||
@frame-drop-to="frameDropToHandler"
|
||||
@persist="persist"
|
||||
@delete-container="promptBeforeDeletingContainer"
|
||||
@add-container="addContainer"
|
||||
@start-container-drag="startContainerDrag"
|
||||
@stop-container-drag="stopContainerDrag">
|
||||
</container-component>
|
||||
|
||||
<resize-handle
|
||||
v-if="index !== (containers.length - 1)"
|
||||
:key="index"
|
||||
v-show="isEditing"
|
||||
:index="index"
|
||||
:orientation="rowsLayout ? 'vertical' : 'horizontal'"
|
||||
@init-move="startContainerResizing"
|
||||
@@ -65,14 +52,13 @@
|
||||
</resize-handle>
|
||||
|
||||
<drop-hint
|
||||
class="c-fl-frame__drop-hint"
|
||||
style="flex-basis: 15px;"
|
||||
v-if="containers.length > 1"
|
||||
:key="index"
|
||||
v-show="isContainerDragging"
|
||||
:index="index"
|
||||
:allowDrop="allowContainerDrop"
|
||||
@object-drop-to="moveContainer">
|
||||
@object-drop-to="containerDropTo">
|
||||
</drop-hint>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -80,22 +66,6 @@
|
||||
<style lang="scss">
|
||||
@import '~styles/sass-base';
|
||||
|
||||
@mixin containerGrippy($headerSize, $dir) {
|
||||
position: absolute;
|
||||
$h: 6px;
|
||||
$minorOffset: ($headerSize - $h) / 2;
|
||||
$majorOffset: 35%;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
@include grippy($c: $editFrameSelectedMovebarColorFg, $dir: $dir);
|
||||
@if $dir == 'x' {
|
||||
top: $minorOffset; right: $majorOffset; bottom: $minorOffset; left: $majorOffset;
|
||||
} @else {
|
||||
top: $majorOffset; right: $minorOffset; bottom: $majorOffset; left: $minorOffset;
|
||||
}
|
||||
}
|
||||
|
||||
.c-fl {
|
||||
@include abs();
|
||||
display: flex;
|
||||
@@ -116,6 +86,7 @@
|
||||
> * + * { margin-left: 1px; }
|
||||
|
||||
&[class*='--rows'] {
|
||||
//@include test(blue, 0.1);
|
||||
flex-direction: column;
|
||||
> * + * {
|
||||
margin-left: 0;
|
||||
@@ -143,6 +114,7 @@
|
||||
/***************************************************** CONTAINERS */
|
||||
$headerSize: 16px;
|
||||
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
@@ -152,9 +124,9 @@
|
||||
flex-shrink: 1;
|
||||
|
||||
&__header {
|
||||
// Only displayed when editing, controlled via JS
|
||||
background: $editFrameMovebarColorBg;
|
||||
color: $editFrameMovebarColorFg;
|
||||
// Only displayed when editing
|
||||
background: $editSelectableColor;
|
||||
color: $editSelectableColorFg;
|
||||
cursor: move;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -162,8 +134,12 @@
|
||||
|
||||
&:before {
|
||||
// Drag grippy
|
||||
@include containerGrippy($headerSize, 'x');
|
||||
font-size: 0.8em;
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
left: 50%; top: 50%;
|
||||
transform-origin: center;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,27 +159,19 @@
|
||||
}
|
||||
|
||||
.is-editing & {
|
||||
&:hover {
|
||||
.c-fl-container__header {
|
||||
background: $editFrameHovMovebarColorBg;
|
||||
color: $editFrameHovMovebarColorFg;
|
||||
//background: $editCanvasColorBg;
|
||||
border-color: $editSelectableColor;
|
||||
|
||||
&:before {
|
||||
opacity: .75;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border-color: $editSelectableColorHov;
|
||||
}
|
||||
|
||||
&[s-selected] {
|
||||
border: $editFrameSelectedBorder;
|
||||
border-color: $editSelectableColorSelected;
|
||||
|
||||
.c-fl-container__header {
|
||||
background:$editFrameSelectedMovebarColorBg;
|
||||
color: $editFrameSelectedMovebarColorFg;
|
||||
&:before {
|
||||
// Grippy
|
||||
opacity: 1;
|
||||
}
|
||||
background: $editSelectableColorSelected;
|
||||
color: $editSelectableColorSelectedFg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,6 +180,11 @@
|
||||
// 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 */
|
||||
@@ -224,8 +197,8 @@
|
||||
overflow: hidden;
|
||||
|
||||
&:before {
|
||||
// Grippy
|
||||
@include containerGrippy($headerSize, 'y');
|
||||
// Drag grippy
|
||||
transform: rotate(90deg) translate(-50%, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +221,8 @@
|
||||
$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
|
||||
@@ -286,6 +261,7 @@
|
||||
pointer-events: none;
|
||||
text-align: center;
|
||||
width: $size;
|
||||
z-index: 2;
|
||||
|
||||
// Changed when layout is different, see below
|
||||
border-top-right-radius: $controlCr;
|
||||
@@ -314,16 +290,37 @@
|
||||
|
||||
&:before {
|
||||
// The visible resize line
|
||||
background: $editUIColor;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -331,15 +328,20 @@
|
||||
&.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: $editUIColorHov;
|
||||
background: $editColorHov;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,7 +383,7 @@
|
||||
}
|
||||
|
||||
&__drop-hint {
|
||||
flex: 1 0 100%;
|
||||
flex: 1 1 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -395,63 +397,31 @@
|
||||
<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;
|
||||
}
|
||||
const SNAP_TO_PERCENTAGE = 1,
|
||||
MIN_CONTAINER_SIZE = 5;
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'layoutObject'],
|
||||
mixins: [isEditingMixin],
|
||||
inject: ['openmct', 'domainObject'],
|
||||
components: {
|
||||
ContainerComponent,
|
||||
ResizeHandle,
|
||||
DropHint
|
||||
},
|
||||
data() {
|
||||
let containers = this.domainObject.configuration.containers,
|
||||
rowsLayout = this.domainObject.configuration.rowsLayout;
|
||||
|
||||
return {
|
||||
domainObject: this.layoutObject
|
||||
containers: containers,
|
||||
dragFrom: [],
|
||||
isEditing: false,
|
||||
isDragging: false,
|
||||
isContainerDragging: false,
|
||||
rowsLayout: rowsLayout,
|
||||
maxMoveSize: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -461,105 +431,144 @@ export default {
|
||||
} 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;
|
||||
return !!!this.containers.filter(container => container.frames.length > 1).length;
|
||||
},
|
||||
addContainer() {
|
||||
let container = new Container();
|
||||
let newSize = 100/(this.containers.length+1);
|
||||
|
||||
let container = new Container(newSize)
|
||||
|
||||
this.recalculateContainerSize(newSize);
|
||||
|
||||
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();
|
||||
recalculateContainerSize(newSize) {
|
||||
this.containers.forEach((container) => {
|
||||
container.width = newSize;
|
||||
});
|
||||
},
|
||||
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();
|
||||
recalculateNewFrameSize(multFactor, framesArray){
|
||||
framesArray.forEach((frame, index) => {
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
let frameSize = frame.height
|
||||
frame.height = this.snapToPercentage(multFactor * frameSize);
|
||||
});
|
||||
},
|
||||
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();
|
||||
recalculateOldFrameSize(framesArray) {
|
||||
let totalRemainingSum = framesArray.map((frame,i) => {
|
||||
if (i !== 0) {
|
||||
return frame.height
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}).reduce((a, c) => a + c);
|
||||
|
||||
framesArray.forEach((frame, index) => {
|
||||
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
if (framesArray.length === 2) {
|
||||
|
||||
frame.height = 100;
|
||||
} else {
|
||||
|
||||
let newSize = frame.height + ((frame.height / totalRemainingSum) * (100 - totalRemainingSum));
|
||||
frame.height = this.snapToPercentage(newSize);
|
||||
}
|
||||
});
|
||||
},
|
||||
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);
|
||||
addFrame(frame, index) {
|
||||
this.containers[index].addFrame(frame);
|
||||
},
|
||||
allowContainerDrop(event, index) {
|
||||
if (!event.dataTransfer.types.includes('containerid')) {
|
||||
return false;
|
||||
frameDragFromHandler(containerIndex, frameIndex) {
|
||||
this.dragFrom = [containerIndex, frameIndex];
|
||||
},
|
||||
frameDropToHandler(containerIndex, frameIndex, frameObject) {
|
||||
let newContainer = this.containers[containerIndex];
|
||||
|
||||
this.isDragging = false;
|
||||
|
||||
if (!frameObject) {
|
||||
frameObject = this.containers[this.dragFrom[0]].frames.splice(this.dragFrom[1], 1)[0];
|
||||
this.recalculateOldFrameSize(this.containers[this.dragFrom[0]].frames);
|
||||
}
|
||||
|
||||
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
|
||||
if (!frameObject.height) {
|
||||
frameObject.height = 100 / Math.max(newContainer.frames.length - 1, 1);
|
||||
}
|
||||
|
||||
newContainer.frames.splice((frameIndex + 1), 0, frameObject);
|
||||
|
||||
let newTotalHeight = newContainer.frames.reduce((total, frame) => {
|
||||
let num = Number(frame.height);
|
||||
|
||||
if(isNaN(num)) {
|
||||
return total;
|
||||
} else {
|
||||
return total + num;
|
||||
}
|
||||
},0);
|
||||
let newMultFactor = 100 / newTotalHeight;
|
||||
|
||||
this.recalculateNewFrameSize(newMultFactor, newContainer.frames);
|
||||
|
||||
this.persist();
|
||||
},
|
||||
persist(index){
|
||||
if (index) {
|
||||
this.openmct.objects.mutate(this.domainObject, `configuration.containers[${index}]`, this.containers[index]);
|
||||
this.openmct.objects.mutate(this.domainObject, `.configuration.containers[${index}]`, this.containers[index]);
|
||||
} else {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.containers', this.containers);
|
||||
this.openmct.objects.mutate(this.domainObject, '.configuration.containers', this.containers);
|
||||
}
|
||||
},
|
||||
isEditingHandler(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
|
||||
if (this.isEditing) {
|
||||
this.$el.click(); //force selection of flexible-layout for toolbar
|
||||
}
|
||||
|
||||
if (this.isDragging && isEditing === false) {
|
||||
this.isDragging = false;
|
||||
}
|
||||
},
|
||||
dragstartHandler() {
|
||||
if (this.isEditing) {
|
||||
this.isDragging = true;
|
||||
}
|
||||
},
|
||||
dragendHandler() {
|
||||
this.isDragging = false;
|
||||
},
|
||||
startContainerResizing(index) {
|
||||
let beforeContainer = this.containers[index],
|
||||
afterContainer = this.containers[index + 1];
|
||||
|
||||
this.maxMoveSize = beforeContainer.size + afterContainer.size;
|
||||
this.maxMoveSize = beforeContainer.width + afterContainer.width;
|
||||
},
|
||||
containerResizing(index, delta, event) {
|
||||
let percentageMoved = Math.round(delta / this.getElSize() * 100),
|
||||
let percentageMoved = (delta/this.getElSize(this.$el))*100,
|
||||
beforeContainer = this.containers[index],
|
||||
afterContainer = this.containers[index + 1];
|
||||
|
||||
beforeContainer.size = this.getContainerSize(beforeContainer.size + percentageMoved);
|
||||
afterContainer.size = this.getContainerSize(afterContainer.size - percentageMoved);
|
||||
beforeContainer.width = this.getContainerSize(this.snapToPercentage(beforeContainer.width + percentageMoved));
|
||||
afterContainer.width = this.getContainerSize(this.snapToPercentage(afterContainer.width - percentageMoved));
|
||||
},
|
||||
endContainerResizing(event) {
|
||||
this.persist();
|
||||
},
|
||||
getElSize() {
|
||||
getElSize(el) {
|
||||
if (this.rowsLayout) {
|
||||
return this.$el.offsetHeight;
|
||||
return el.offsetHeight;
|
||||
} else {
|
||||
return this.$el.offsetWidth;
|
||||
return el.offsetWidth;
|
||||
}
|
||||
},
|
||||
getContainerSize(size) {
|
||||
@@ -571,19 +580,80 @@ export default {
|
||||
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);
|
||||
snapToPercentage(value) {
|
||||
let rem = value % SNAP_TO_PERCENTAGE,
|
||||
roundedValue;
|
||||
|
||||
if (rem < 0.5) {
|
||||
roundedValue = Math.floor(value/SNAP_TO_PERCENTAGE)*SNAP_TO_PERCENTAGE;
|
||||
} else {
|
||||
this.containers.splice(toIndex, 0, container);
|
||||
roundedValue = Math.ceil(value/SNAP_TO_PERCENTAGE)*SNAP_TO_PERCENTAGE;
|
||||
}
|
||||
|
||||
return roundedValue;
|
||||
},
|
||||
toggleLayoutDirection(v) {
|
||||
this.rowsLayout = v;
|
||||
},
|
||||
promptBeforeDeletingContainer(containerIndex) {
|
||||
let deleteContainer = this.deleteContainer;
|
||||
|
||||
let prompt = this.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 () {
|
||||
deleteContainer(containerIndex);
|
||||
prompt.dismiss();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
prompt.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
deleteContainer(containerIndex) {
|
||||
this.containers.splice(containerIndex, 1);
|
||||
|
||||
this.recalculateContainerSize(100/this.containers.length);
|
||||
this.persist();
|
||||
},
|
||||
addContainer(containerIndex) {
|
||||
let newContainer = new Container();
|
||||
|
||||
if (typeof containerIndex === 'number') {
|
||||
this.containers.splice(containerIndex+1, 0, newContainer);
|
||||
} else {
|
||||
|
||||
this.containers.push(newContainer);
|
||||
}
|
||||
|
||||
this.recalculateContainerSize(100/this.containers.length);
|
||||
this.persist();
|
||||
},
|
||||
startContainerDrag(index) {
|
||||
this.isContainerDragging = true;
|
||||
this.containerDragFrom = index;
|
||||
},
|
||||
stopContainerDrag() {
|
||||
this.isContainerDragging = false;
|
||||
},
|
||||
containerDropTo(event, index) {
|
||||
let fromContainer = this.containers.splice(this.containerDragFrom, 1)[0];
|
||||
|
||||
if (index === -1) {
|
||||
this.containers.unshift(fromContainer);
|
||||
} else {
|
||||
this.containers.splice(index, 0, fromContainer);
|
||||
}
|
||||
|
||||
this.persist();
|
||||
}
|
||||
},
|
||||
@@ -592,17 +662,23 @@ export default {
|
||||
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);
|
||||
|
||||
this.openmct.objects.observe(this.domainObject, 'configuration.rowsLayout', this.toggleLayoutDirection);
|
||||
this.openmct.editor.on('isEditing', this.isEditingHandler);
|
||||
|
||||
document.addEventListener('dragstart', this.dragstartHandler);
|
||||
document.addEventListener('dragend', this.dragendHandler);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unsubscribeSelection();
|
||||
this.unobserve();
|
||||
|
||||
this.openmct.editor.off('isEditing', this.isEditingHandler);
|
||||
document.removeEventListener('dragstart', this.dragstartHandler);
|
||||
document.removeEventListener('dragend', this.dragendHandler);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -22,82 +22,103 @@
|
||||
|
||||
<template>
|
||||
<div class="c-fl-frame"
|
||||
:style="{
|
||||
'flex-basis': `${frame.size}%`
|
||||
}">
|
||||
:class="{
|
||||
'is-dragging': isDragging,
|
||||
[frame.cssClass]: true
|
||||
}"
|
||||
@dragstart="initDrag"
|
||||
@drag="continueDrag">
|
||||
|
||||
<div class="c-frame c-fl-frame__drag-wrapper is-selectable u-inspectable is-moveable"
|
||||
<div class="c-frame c-fl-frame__drag-wrapper is-selectable is-moveable"
|
||||
:class="{'no-frame': noFrame}"
|
||||
draggable="true"
|
||||
@dragstart="initDrag"
|
||||
ref="frame">
|
||||
ref="frame"
|
||||
v-if="frame.domainObject">
|
||||
|
||||
<object-frame
|
||||
v-if="domainObject"
|
||||
:domain-object="domainObject"
|
||||
:object-path="objectPath"
|
||||
:has-frame="hasFrame">
|
||||
</object-frame>
|
||||
<frame-header
|
||||
v-if="index !== 0"
|
||||
ref="dragObject"
|
||||
class="c-fl-frame__header"
|
||||
:domainObject="frame.domainObject">
|
||||
</frame-header>
|
||||
|
||||
<object-view
|
||||
class="c-fl-frame__object-view"
|
||||
:object="frame.domainObject">
|
||||
</object-view>
|
||||
|
||||
<div class="c-fl-frame__size-indicator"
|
||||
v-if="isEditing"
|
||||
v-show="frame.size && frame.size < 100">
|
||||
{{frame.size}}%
|
||||
v-show="frame.height && frame.height < 100">
|
||||
{{frame.height}}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<drop-hint
|
||||
v-show="isEditing && isDragging"
|
||||
class="c-fl-frame__drop-hint"
|
||||
:class="{'is-dragging': isDragging}"
|
||||
@object-drop-to="dropHandler">
|
||||
</drop-hint>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ObjectView from '../../../ui/components/layout/ObjectView.vue';
|
||||
import DropHint from './dropHint.vue';
|
||||
import ResizeHandle from './resizeHandle.vue';
|
||||
import ObjectFrame from '../../../ui/components/ObjectFrame.vue';
|
||||
import isEditingMixin from '../mixins/isEditing';
|
||||
import FrameHeader from '../../../ui/components/utils/frameHeader.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: ['frame', 'index', 'containerIndex'],
|
||||
mixins: [isEditingMixin],
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: ['frame', 'index', 'containerIndex', 'isEditing', 'isDragging'],
|
||||
data() {
|
||||
return {
|
||||
domainObject: undefined,
|
||||
objectPath: undefined
|
||||
noFrame: this.frame.noFrame
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ObjectView,
|
||||
DropHint,
|
||||
ResizeHandle,
|
||||
ObjectFrame
|
||||
},
|
||||
computed: {
|
||||
hasFrame() {
|
||||
return !this.frame.noFrame;
|
||||
}
|
||||
FrameHeader
|
||||
},
|
||||
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);
|
||||
this.$emit('frame-drag-from', this.index);
|
||||
},
|
||||
dropHandler(event) {
|
||||
this.$emit('frame-drop-to', this.index, event);
|
||||
},
|
||||
continueDrag(event) {
|
||||
if (!this.isDragging) {
|
||||
this.isDragging = true;
|
||||
}
|
||||
},
|
||||
deleteFrame() {
|
||||
this.$emit('delete-frame', this.index);
|
||||
},
|
||||
addContainer() {
|
||||
this.$emit('add-container');
|
||||
},
|
||||
toggleFrame(v) {
|
||||
this.noFrame = v;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.frame.domainObjectIdentifier) {
|
||||
this.openmct.objects.get(this.frame.domainObjectIdentifier).then((object)=>{
|
||||
this.setDomainObject(object);
|
||||
});
|
||||
|
||||
if (this.frame.domainObject.identifier) {
|
||||
let context = {
|
||||
item: this.frame.domainObject,
|
||||
method: this.deleteFrame,
|
||||
addContainer: this.addContainer,
|
||||
type: 'frame',
|
||||
index: this.index
|
||||
}
|
||||
|
||||
this.unsubscribeSelection = this.openmct.selection.selectable(this.$refs.frame, context, false);
|
||||
|
||||
this.openmct.objects.observe(this.domainObject, `configuration.containers[${this.containerIndex}].frames[${this.index}].noFrame`, this.toggleFrame);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
||||
@@ -23,21 +23,16 @@
|
||||
<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,
|
||||
initialPos: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -52,18 +47,7 @@ export default {
|
||||
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;
|
||||
|
||||
let delta = this.getMousePosition(event) - this.getElSizeFromRect(this.$el);
|
||||
this.$emit('move', this.index, delta, event);
|
||||
},
|
||||
mouseup(event) {
|
||||
@@ -72,20 +56,20 @@ export default {
|
||||
document.body.removeEventListener('mousemove', this.mousemove);
|
||||
document.body.removeEventListener('mouseup', this.mouseup);
|
||||
},
|
||||
setDragging(event) {
|
||||
this.isDragging = true;
|
||||
getMousePosition(event) {
|
||||
if (this.orientation === 'horizontal') {
|
||||
return event.clientX;
|
||||
} else {
|
||||
return event.clientY;
|
||||
}
|
||||
},
|
||||
getElSizeFromRect(el) {
|
||||
if (this.orientation === 'horizontal') {
|
||||
return el.getBoundingClientRect().x;
|
||||
} else {
|
||||
return el.getBoundingClientRect().y;
|
||||
}
|
||||
},
|
||||
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>
|
||||
|
||||
@@ -35,9 +35,6 @@ define([
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'flexible-layout';
|
||||
},
|
||||
canEdit: function (domainObject) {
|
||||
return domainObject.type === 'flexible-layout';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
@@ -49,7 +46,7 @@ define([
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
layoutObject: domainObject
|
||||
domainObject
|
||||
},
|
||||
el: element,
|
||||
template: '<flexible-layout-component></flexible-layout-component>'
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -22,12 +22,10 @@
|
||||
|
||||
define([
|
||||
'./flexibleLayoutViewProvider',
|
||||
'./utils/container',
|
||||
'./toolbarProvider'
|
||||
'./utils/container'
|
||||
], function (
|
||||
FlexibleLayoutViewProvider,
|
||||
Container,
|
||||
ToolBarProvider
|
||||
Container
|
||||
) {
|
||||
return function plugin() {
|
||||
|
||||
@@ -44,13 +42,122 @@ define([
|
||||
containers: [new Container.default(50), new Container.default(50)],
|
||||
rowsLayout: false
|
||||
};
|
||||
domainObject.composition = [];
|
||||
}
|
||||
});
|
||||
|
||||
let toolbar = ToolBarProvider.default(openmct);
|
||||
openmct.toolbars.addProvider({
|
||||
name: "Flexible Layout Toolbar",
|
||||
key: "flex-layout",
|
||||
description: "A toolbar for objects inside a Flexible Layout.",
|
||||
forSelection: function (selection) {
|
||||
let context = selection[0].context;
|
||||
|
||||
openmct.toolbars.addProvider(toolbar);
|
||||
return (openmct.editor.isEditing() && context && context.type &&
|
||||
(context.type === 'flexible-layout' || context.type === 'container' || context.type === 'frame'));
|
||||
},
|
||||
toolbar: function (selection) {
|
||||
|
||||
let primary = selection[0],
|
||||
parent = selection[1],
|
||||
deleteFrame,
|
||||
toggleContainer,
|
||||
deleteContainer,
|
||||
addContainer,
|
||||
toggleFrame,
|
||||
separator;
|
||||
|
||||
addContainer = {
|
||||
control: "button",
|
||||
domainObject: parent ? parent.context.item : primary.context.item,
|
||||
method: parent ? parent.context.addContainer : primary.context.addContainer,
|
||||
key: "add",
|
||||
icon: "icon-plus-in-rect",
|
||||
title: 'Add Container'
|
||||
};
|
||||
|
||||
separator = {
|
||||
control: "separator",
|
||||
domainObject: selection[0].context.item,
|
||||
key: "separator"
|
||||
};
|
||||
|
||||
toggleContainer = {
|
||||
control: 'toggle-button',
|
||||
key: 'toggle-layout',
|
||||
domainObject: parent ? parent.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') {
|
||||
|
||||
deleteFrame = {
|
||||
control: "button",
|
||||
domainObject: primary.context.item,
|
||||
method: primary.context.method,
|
||||
key: "remove",
|
||||
icon: "icon-trash",
|
||||
title: "Remove Frame"
|
||||
};
|
||||
toggleFrame = {
|
||||
control: "toggle-button",
|
||||
domainObject: parent.context.item,
|
||||
property: `configuration.containers[${parent.context.index}].frames[${primary.context.index}].noFrame`,
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-frame-hide',
|
||||
title: "Hide frame"
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-frame-show',
|
||||
title: "Show frame"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
} else if (primary.context.type === 'container') {
|
||||
|
||||
deleteContainer = {
|
||||
control: "button",
|
||||
domainObject: primary.context.item,
|
||||
method: primary.context.method,
|
||||
key: "remove",
|
||||
icon: "icon-trash",
|
||||
title: "Remove 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, deleteFrame, deleteContainer];
|
||||
|
||||
return toolbar.filter(button => button !== undefined);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,215 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
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: true,
|
||||
icon: 'icon-columns',
|
||||
title: 'Columns layout'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-rows',
|
||||
title: 'Rows layout'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
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: false,
|
||||
icon: 'icon-frame-hide',
|
||||
title: "Frame hidden"
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-frame-show',
|
||||
title: "Frame visible"
|
||||
}
|
||||
]
|
||||
};
|
||||
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;
|
||||
@@ -1,10 +1,13 @@
|
||||
import uuid from 'uuid';
|
||||
import Frame from './frame';
|
||||
|
||||
class Container {
|
||||
constructor (size) {
|
||||
this.id = uuid();
|
||||
this.frames = [];
|
||||
this.size = size;
|
||||
constructor (width) {
|
||||
this.frames = [new Frame({}, '', 'c-fl-frame--first-in-container')];
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
addFrame(frameObject) {
|
||||
this.frames.push(frameObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
class Frame {
|
||||
constructor(domainObjectIdentifier, size) {
|
||||
this.id = uuid();
|
||||
this.domainObjectIdentifier = domainObjectIdentifier;
|
||||
this.size = size;
|
||||
|
||||
constructor(domainObject, height, cssClass) {
|
||||
this.domainObject = domainObject;
|
||||
this.height = height;
|
||||
this.cssClass = cssClass ? cssClass : '';
|
||||
this.noFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ define([
|
||||
function FolderGridView(openmct) {
|
||||
return {
|
||||
key: 'grid',
|
||||
name: 'Grid View',
|
||||
name: 'Grid Vue',
|
||||
cssClass: 'icon-thumbs-strip',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'folder';
|
||||
|
||||
@@ -32,7 +32,7 @@ define([
|
||||
function FolderListView(openmct) {
|
||||
return {
|
||||
key: 'list-view',
|
||||
name: 'List View',
|
||||
name: 'List Vue',
|
||||
cssClass: 'icon-list-view',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'folder';
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
color: $colorItemFg;
|
||||
@include headerFont(1.2em);
|
||||
font-size: 1.2em;
|
||||
font-weight: 400;
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
@@ -149,11 +150,11 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
||||
import objectLink from '../../../ui/mixins/object-link';
|
||||
import contextMenu from '../../../ui/components/mixins/context-menu';
|
||||
import objectLink from '../../../ui/components/mixins/object-link';
|
||||
|
||||
export default {
|
||||
mixins: [contextMenuGesture, objectLink],
|
||||
mixins: [contextMenu, objectLink],
|
||||
props: ['item']
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
@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>
|
||||
<div class="c-list-item__name-value">{{item.model.name}}</div>
|
||||
<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>
|
||||
@@ -19,24 +20,17 @@
|
||||
|
||||
/******************************* LIST ITEM */
|
||||
.c-list-item {
|
||||
&__name a {
|
||||
display: flex;
|
||||
|
||||
> * + * { margin-left: $interiorMarginSm; }
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
}
|
||||
|
||||
&__type-icon {
|
||||
// Have to do it this way instead of using icon-* class, due to need to apply alias to the icon
|
||||
color: $colorKey;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
margin-right:$interiorMarginSm;
|
||||
}
|
||||
|
||||
&__name-value {
|
||||
@include ellipsize();
|
||||
}
|
||||
|
||||
&.is-alias {
|
||||
// Object is an alias to an original.
|
||||
[class*='__type-icon'] {
|
||||
@@ -54,16 +48,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import moment from 'moment';
|
||||
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
||||
import objectLink from '../../../ui/mixins/object-link';
|
||||
import contextMenu from '../../../ui/components/mixins/context-menu';
|
||||
import objectLink from '../../../ui/components/mixins/object-link';
|
||||
|
||||
export default {
|
||||
mixins: [contextMenuGesture, objectLink],
|
||||
mixins: [contextMenu, objectLink],
|
||||
props: ['item'],
|
||||
methods: {
|
||||
formatTime(timestamp, format) {
|
||||
|
||||
@@ -42,8 +42,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<list-item v-for="item in sortedItems"
|
||||
:key="item.objectKeyString"
|
||||
<list-item v-for="(item,index) in sortedItems"
|
||||
:item="item"
|
||||
:object-path="item.objectPath">
|
||||
</list-item>
|
||||
@@ -78,12 +77,9 @@
|
||||
|
||||
td {
|
||||
$p: floor($interiorMargin * 1.5);
|
||||
@include ellipsize();
|
||||
line-height: 120%; // Needed for icon alignment
|
||||
max-width: 0;
|
||||
font-size: 1.1em;
|
||||
padding-top: $p;
|
||||
padding-bottom: $p;
|
||||
width: 25%;
|
||||
|
||||
&:not(.c-list-item__name) {
|
||||
color: $colorItemFgDetails;
|
||||
@@ -103,20 +99,9 @@ export default {
|
||||
mixins: [compositionLoader],
|
||||
inject: ['domainObject', 'openmct'],
|
||||
data() {
|
||||
let sortBy = 'model.name',
|
||||
ascending = true,
|
||||
persistedSortOrder = window.localStorage.getItem('openmct-listview-sort-order');
|
||||
|
||||
if (persistedSortOrder) {
|
||||
let parsed = JSON.parse(persistedSortOrder);
|
||||
|
||||
sortBy = parsed.sortBy;
|
||||
ascending = parsed.ascending;
|
||||
}
|
||||
|
||||
return {
|
||||
sortBy,
|
||||
ascending
|
||||
sortBy: 'model.name',
|
||||
ascending: true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -136,17 +121,6 @@ export default {
|
||||
this.sortBy = field;
|
||||
this.ascending = defaultDirection;
|
||||
}
|
||||
|
||||
window.localStorage
|
||||
.setItem(
|
||||
'openmct-listview-sort-order',
|
||||
JSON.stringify(
|
||||
{
|
||||
sortBy: this.sortBy,
|
||||
ascending: this.ascending
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ export default {
|
||||
model: child,
|
||||
type: type.definition,
|
||||
isAlias: this.domainObject.identifier.key !== child.location,
|
||||
objectPath: [child].concat(this.openmct.router.path),
|
||||
objectKeyString: this.openmct.objects.makeKeyString(child.identifier)
|
||||
objectPath: [child].concat(openmct.router.path)
|
||||
});
|
||||
},
|
||||
remove(identifier) {
|
||||
|
||||
@@ -21,9 +21,29 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/controllers/NotebookController"
|
||||
"./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"
|
||||
], function (
|
||||
NotebookController
|
||||
NotebookController,
|
||||
NewEntryController,
|
||||
SelectSnapshotController,
|
||||
newEntryAction,
|
||||
AnnotateSnapshotAction,
|
||||
MCTSnapshotDirective,
|
||||
EntryDndDirective,
|
||||
snapSelectTemplate,
|
||||
embedControlTemplate,
|
||||
annotationTemplate,
|
||||
draggedEntryTemplate
|
||||
) {
|
||||
var installed = false;
|
||||
|
||||
@@ -47,8 +67,9 @@ define([
|
||||
features: 'creation',
|
||||
model: {
|
||||
entries: [],
|
||||
composition: [],
|
||||
entryTypes: [],
|
||||
defaultSort: 'oldest'
|
||||
defaultSort: '-createdOn'
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
@@ -58,17 +79,112 @@ define([
|
||||
options: [
|
||||
{
|
||||
name: 'Newest First',
|
||||
value: "newest"
|
||||
value: "-createdOn",
|
||||
},
|
||||
{
|
||||
name: 'Oldest First',
|
||||
value: "oldest"
|
||||
value: "createdOn"
|
||||
}
|
||||
],
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
2
src/plugins/notebook/res/templates/annotation.html
Normal file
2
src/plugins/notebook/res/templates/annotation.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<div class="snap-annotation" id="snap-annotation" ng-controller="ngModel.controller">
|
||||
</div>
|
||||
@@ -0,0 +1,51 @@
|
||||
<!--
|
||||
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>
|
||||
29
src/plugins/notebook/res/templates/controls/snapSelect.html
Normal file
29
src/plugins/notebook/res/templates/controls/snapSelect.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!--
|
||||
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>
|
||||
38
src/plugins/notebook/res/templates/draggedEntry.html
Normal file
38
src/plugins/notebook/res/templates/draggedEntry.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!--
|
||||
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, $event)"
|
||||
v-on:drop="dropOnEntry(entry.id)"
|
||||
v-on:dragover="dragoverOnEntry"
|
||||
>
|
||||
<div class="c-ne__time-and-content">
|
||||
@@ -19,16 +19,15 @@
|
||||
<div class="c-ne__embeds">
|
||||
<notebook-embed
|
||||
v-for="(embed, index) in entry.embeds"
|
||||
:key="index"
|
||||
:embed="embed"
|
||||
:entry="entry"
|
||||
v-bind:embed="embed"
|
||||
v-bind:entry="entry"
|
||||
></notebook-embed>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-ne__local-controls--hidden">
|
||||
<button class="c-click-icon c-click-icon--major icon-trash"
|
||||
<button class="c-click-icon icon-trash"
|
||||
title="Delete this entry"
|
||||
v-on:click="deleteEntry"></button>
|
||||
</div>
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
<div class="c-notebook">
|
||||
<div class="c-notebook__head">
|
||||
<search class="c-notebook__search"
|
||||
:value="entrySearch"
|
||||
@input="search"
|
||||
@clear="search">
|
||||
</search>
|
||||
<div class="c-notebook__controls ">
|
||||
<select class="c-notebook__controls__time" v-model="showTime">
|
||||
<option value="0" selected="selected">Show all</option>
|
||||
<option value="1">Last hour</option>
|
||||
<option value="8">Last 8 hours</option>
|
||||
<option value="24">Last 24 hours</option>
|
||||
</select>
|
||||
<select class="c-notebook__controls__time" v-model="sortEntries">
|
||||
<option value="newest" :selected="sortEntries === 'newest'">Newest first</option>
|
||||
<option value="oldest" :selected="sortEntries === 'oldest'">Oldest first</option>
|
||||
</select>
|
||||
v-model="entrySearch"
|
||||
v-on:input="search($event)"
|
||||
v-on:clear="entrySearch = ''; search($event)"></search>
|
||||
<div class="c-notebook__controls">
|
||||
<div class="select c-notebook__controls__time">
|
||||
<select v-model="showTime">
|
||||
<option value="0" selected="selected">Show all</option>
|
||||
<option value="1">Last hour</option>
|
||||
<option value="8">Last 8 hours</option>
|
||||
<option value="24">Last 24 hours</option>
|
||||
</select>
|
||||
</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>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-notebook__drag-area icon-plus"
|
||||
@click="newEntry($event)"
|
||||
@drop="newEntry($event)"
|
||||
id="newEntry">
|
||||
v-on:click="newEntry($event)"
|
||||
id="newEntry" mct-entry-dnd>
|
||||
<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,index) in filteredAndSortedEntries"
|
||||
:key="entry.key"
|
||||
:entry="entry">
|
||||
</notebook-entry>
|
||||
<notebook-entry
|
||||
v-for="entry in filterBySearch(entries, entrySearch)"
|
||||
v-bind:entry="entry"
|
||||
></notebook-entry>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
162
src/plugins/notebook/src/actions/AnnotateSnapshot.js
Normal file
162
src/plugins/notebook/src/actions/AnnotateSnapshot.js
Normal file
@@ -0,0 +1,162 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
);
|
||||
204
src/plugins/notebook/src/actions/NewEntryContextual.js
Normal file
204
src/plugins/notebook/src/actions/NewEntryContextual.js
Normal file
@@ -0,0 +1,204 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
);
|
||||
130
src/plugins/notebook/src/actions/snapshotAction.js
Normal file
130
src/plugins/notebook/src/actions/snapshotAction.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/*****************************************************************************
|
||||
* 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,6 +275,7 @@ function (
|
||||
|
||||
function menuClickHandler(e) {
|
||||
e.stopPropagation();
|
||||
window.setTimeout(dismiss, 300);
|
||||
}
|
||||
|
||||
// Dismiss any menu which was already showing
|
||||
|
||||
@@ -105,27 +105,23 @@ function (
|
||||
}
|
||||
};
|
||||
|
||||
EntryController.prototype.dropOnEntry = function (entryid, event) {
|
||||
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: ''
|
||||
};
|
||||
|
||||
var data = event.dataTransfer.getData('openmct/domain-object-path');
|
||||
|
||||
if (data) {
|
||||
var selectedObject = JSON.parse(data)[0],
|
||||
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);
|
||||
}
|
||||
currentEntryEmbeds.push(newEmbed);
|
||||
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].embeds', currentEntryEmbeds);
|
||||
};
|
||||
|
||||
EntryController.prototype.dragoverOnEntry = function () {
|
||||
@@ -136,6 +132,7 @@ function (
|
||||
return {
|
||||
openmct: this.openmct,
|
||||
domainObject: this.domainObject,
|
||||
dndService: this.dndService,
|
||||
dialogService: this.dialogService,
|
||||
currentEntryValue: this.currentEntryValue
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user