mirror of
https://github.com/nasa/openmct.git
synced 2025-01-29 15:43:52 +00:00
Display layout alphanumeric (#2203)
* Support displaying and adding telemerty points in display layouts. * Create TelemetryView component. Also disable the toolbar frame button for telemetry objects. * Add 'components' directory and move the toolbar provider definition to a separate file. * Saving work * Saving work * Saving work * Fix telemetryClass * Fixes for .no-frame in new markup structure - CSS cleaned up and reorganized; - Added .c-telemetry-view classes; * Add computed properties for hiding label and value. * Filter value meta data based on the item config display mode. * Add drop down menus for display mode and value * Add toolbar controls for telemerty points * Set border and fill related styles on telemetry view instead of layout item * Refinements to telemetry view - Stoke and fill styling now work; - Internal element layout now much better when sizing in a Layout frame; - Tweaked color of frame border while editing; * Prevents adding a new (panel) object if it's already in the composition. * Fix for jumping edit area - Removed v-if from Toolbar.vue; - Refined c-toolbar styling; - TODO: don't include toolbar component when not editing, and for components that don't use a toolbar; * Add a separator toolbar control * Check for domainObject being on the toolbar item as not all controls have that property * Hide 'no fill' option from the text palette. Modify the color-picker component to say 'No border' for stroke palette. * Move the listener for hasFrame to the subobject view configuration * Fixes for toolbar-separator - New mixin; - Corrected markup for separator; - New class for .c-toolbar__separator; - Updated DisplayLayoutToolbar.js to include separators in the right spots; * Get type from item. * Include copyright notice. * Use arrow function for consistency and define a TEXT_SIZE constant. * Use composition API to add non-telemetry objects instead of relying on the drop handler. Display a blocking dialog if an existing non-telemetry object is dropped. * Fix text color picker icon * Address reviewer's feedback * Load the composition and update addObject() to render existing panels as well. Also, cache the telemetry value formatter. * Add listener for changes to time bounds. * Code cleanup * Use getFormatMap() to store formats. Reset telemetry value and class before fetching new data. * Fix a typo * Define telemetry class and value as computed properties. * Change context object definition * Look at the telemetry metadata to find a good default for the value key instead of defaulting to 'sin'. Also, make formats reactive. * Use let instead of var.
This commit is contained in:
parent
55d3ab5e8a
commit
d13d59bfa0
@ -1,312 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="l-layout"
|
||||
@dragover="handleDragOver"
|
||||
@click="bypassSelection"
|
||||
@drop="handleDrop">
|
||||
<div class="l-layout__object">
|
||||
<!-- Background grid -->
|
||||
<div class="l-layout__grid-holder c-grid"
|
||||
v-if="!drilledIn">
|
||||
<div class="c-grid__x l-grid l-grid-x"
|
||||
v-if="gridSize[0] >= 3"
|
||||
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
|
||||
</div>
|
||||
<div class="c-grid__y l-grid l-grid-y"
|
||||
v-if="gridSize[1] >= 3"
|
||||
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
||||
</div>
|
||||
<layout-frame v-for="item in frameItems"
|
||||
class="l-layout__frame"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:gridSize="gridSize"
|
||||
@drilledIn="updateDrilledInState"
|
||||
@dragInProgress="updatePosition"
|
||||
@endDrag="endDrag">
|
||||
</layout-frame>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.l-layout {
|
||||
@include abs();
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__grid-holder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__object {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__frame {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.l-shell__main-container {
|
||||
> .l-layout {
|
||||
[s-selected] {
|
||||
border: $browseBorderSelected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Styles moved to _global.scss;
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue';
|
||||
|
||||
const DEFAULT_GRID_SIZE = [32, 32],
|
||||
DEFAULT_DIMENSIONS = [12, 8],
|
||||
DEFAULT_POSITION = [0, 0],
|
||||
MINIMUM_FRAME_SIZE = [320, 180],
|
||||
DEFAULT_HIDDEN_FRAME_TYPES = [
|
||||
'hyperlink'
|
||||
];
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
gridSize: [],
|
||||
frameItems: [],
|
||||
frames: [],
|
||||
frameStyles: [],
|
||||
rawPositions: {},
|
||||
drilledIn: undefined
|
||||
}
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: ['domainObject'],
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
created: function () {
|
||||
this.newDomainObject = this.domainObject;
|
||||
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;
|
||||
this.composition = this.openmct.composition.get(this.newDomainObject);
|
||||
this.Listeners = [];
|
||||
let panels = (((this.newDomainObject.configuration || {}).layout || {}).panels || {});
|
||||
|
||||
if (this.composition !== undefined) {
|
||||
this.composition.load().then((composition) => {
|
||||
composition.forEach(function (domainObject) {
|
||||
this.readLayoutConfiguration(domainObject, panels);
|
||||
this.makeFrameItem(domainObject, false);
|
||||
}.bind(this));
|
||||
this.composition.on('add', this.onAddComposition);
|
||||
this.composition.on('remove', this.onRemoveComposition);
|
||||
});
|
||||
}
|
||||
|
||||
this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) {
|
||||
this.newDomainObject = JSON.parse(JSON.stringify(obj));
|
||||
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;;
|
||||
}.bind(this));
|
||||
},
|
||||
methods: {
|
||||
readLayoutConfiguration(domainObject, panels) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.rawPositions[id] = {
|
||||
position: panels[id].position || DEFAULT_POSITION,
|
||||
dimensions: panels[id].dimensions || this.defaultDimensions()
|
||||
};
|
||||
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
|
||||
this.frames[id] = panels[id].hasOwnProperty('hasFrame') ?
|
||||
panels[id].hasFrame :
|
||||
this.hasFrameByDefault(domainObject.type);
|
||||
},
|
||||
makeFrameItem(domainObject, initSelect) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.frameItems.push({
|
||||
id: id,
|
||||
hasFrame: this.frames[id],
|
||||
domainObject,
|
||||
style: this.frameStyles[id],
|
||||
drilledIn: this.isDrilledIn(id),
|
||||
initSelect: initSelect,
|
||||
rawPosition: this.rawPositions[id]
|
||||
});
|
||||
},
|
||||
onAddComposition(domainObject) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.rawPositions[id] = {
|
||||
position: [
|
||||
Math.floor(this.droppedObjectPosition.x / this.gridSize[0]),
|
||||
Math.floor(this.droppedObjectPosition.y / this.gridSize[1])
|
||||
],
|
||||
dimensions: this.defaultDimensions()
|
||||
};
|
||||
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
|
||||
this.frames[id] = this.hasFrameByDefault(domainObject.type);
|
||||
|
||||
let newPanel = this.rawPositions[id];
|
||||
newPanel.hasFrame = this.frames[id];
|
||||
this.mutate("configuration.layout.panels[" + id + "]", newPanel);
|
||||
this.makeFrameItem(domainObject, true);
|
||||
},
|
||||
onRemoveComposition(identifier) {
|
||||
// TODO: remove the object from frameItems
|
||||
},
|
||||
defaultDimensions() {
|
||||
let gridSize = this.gridSize;
|
||||
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
||||
return Math.max(
|
||||
Math.ceil(min / gridSize[i]),
|
||||
DEFAULT_DIMENSIONS[i]
|
||||
);
|
||||
});
|
||||
},
|
||||
convertPosition(raw) {
|
||||
return {
|
||||
left: (this.gridSize[0] * raw.position[0]) + 'px',
|
||||
top: (this.gridSize[1] * raw.position[1]) + 'px',
|
||||
width: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
height: (this.gridSize[1] * raw.dimensions[1]) + 'px',
|
||||
minWidth: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px'
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Checks if the frame should be hidden or not.
|
||||
*
|
||||
* @param type the domain object type
|
||||
* @return {boolean} true if the object should have
|
||||
* frame by default, false, otherwise
|
||||
*/
|
||||
hasFrameByDefault(type) {
|
||||
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
||||
},
|
||||
setSelection(selection) {
|
||||
if (selection.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeListeners();
|
||||
let domainObject = selection[0].context.item;
|
||||
|
||||
if (selection[1] && domainObject) {
|
||||
this.attachSelectionListeners(domainObject.identifier);
|
||||
}
|
||||
|
||||
this.updateDrilledInState();
|
||||
},
|
||||
attachSelectionListeners(identifier) {
|
||||
let id = this.openmct.objects.makeKeyString(identifier);
|
||||
let path = "configuration.layout.panels[" + id + "]";
|
||||
this.listeners.push(this.openmct.objects.observe(this.newDomainObject, path + ".hasFrame", function (newValue) {
|
||||
this.frameItems.forEach(function (item) {
|
||||
if (item.id === id) {
|
||||
item.hasFrame = newValue;
|
||||
}
|
||||
});
|
||||
this.frames[id] = newValue;
|
||||
}.bind(this)));
|
||||
},
|
||||
updateDrilledInState(id) {
|
||||
this.drilledIn = id;
|
||||
this.frameItems.forEach(function (item) {
|
||||
item.drilledIn = item.id === id;
|
||||
});
|
||||
},
|
||||
isDrilledIn(id) {
|
||||
return this.drilledIn === id;
|
||||
},
|
||||
updatePosition(id, newPosition) {
|
||||
let newStyle = this.convertPosition(newPosition);
|
||||
this.frameStyles[id] = newStyle;
|
||||
this.rawPositions[id] = newPosition;
|
||||
this.frameItems.forEach(function (item) {
|
||||
if (item.id === id) {
|
||||
item.style = newStyle;
|
||||
item.rawPosition = newPosition;
|
||||
}
|
||||
});
|
||||
},
|
||||
bypassSelection($event) {
|
||||
if (this.dragInProgress) {
|
||||
if ($event) {
|
||||
$event.stopImmediatePropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
endDrag(id) {
|
||||
this.dragInProgress = true;
|
||||
setTimeout(function () {
|
||||
this.dragInProgress = false;
|
||||
}.bind(this), 0);
|
||||
|
||||
let path = "configuration.layout.panels[" + id + "]";
|
||||
this.mutate(path + ".dimensions", this.rawPositions[id].dimensions);
|
||||
this.mutate(path + ".position", this.rawPositions[id].position);
|
||||
},
|
||||
mutate(path, value) {
|
||||
this.openmct.objects.mutate(this.newDomainObject, path, value);
|
||||
},
|
||||
handleDrop($event) {
|
||||
$event.preventDefault();
|
||||
|
||||
let child = JSON.parse($event.dataTransfer.getData('domainObject'));
|
||||
|
||||
let elementRect = this.$el.getBoundingClientRect();
|
||||
this.droppedObjectPosition = {
|
||||
x: $event.pageX - elementRect.left,
|
||||
y: $event.pageY - elementRect.top
|
||||
}
|
||||
},
|
||||
handleDragOver($event){
|
||||
$event.preventDefault();
|
||||
},
|
||||
removeListeners() {
|
||||
if (this.listeners) {
|
||||
this.listeners.forEach(function (l) {
|
||||
l();
|
||||
})
|
||||
}
|
||||
this.listeners = [];
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.selection.on('change', this.setSelection);
|
||||
},
|
||||
destroyed: function () {
|
||||
this.composition.off('add', this.onAddComposition);
|
||||
this.composition.off('remove', this.onRemoveComposition);
|
||||
this.openmct.off('change', this.selection);
|
||||
this.unlisten();
|
||||
this.removeListeners();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
151
src/plugins/displayLayout/DisplayLayoutToolbar.js
Normal file
151
src/plugins/displayLayout/DisplayLayoutToolbar.js
Normal file
@ -0,0 +1,151 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
|
||||
function DisplayLayoutToolbar(openmct) {
|
||||
return {
|
||||
name: "Display Layout Toolbar",
|
||||
key: "layout",
|
||||
description: "A toolbar for objects inside a display layout.",
|
||||
forSelection: function (selection) {
|
||||
// Apply the layout toolbar if the selected object is inside a layout,
|
||||
// and in edit mode.
|
||||
return (selection &&
|
||||
selection[1] &&
|
||||
selection[1].context.item &&
|
||||
selection[1].context.item.type === 'layout' &&
|
||||
openmct.editor.isEditing());
|
||||
},
|
||||
toolbar: function (selection) {
|
||||
let domainObject = selection[1].context.item;
|
||||
let layoutItem = selection[0].context.layoutItem;
|
||||
|
||||
if (layoutItem && layoutItem.type === 'telemetry-view') {
|
||||
let path = "configuration.alphanumerics[" + layoutItem.config.alphanumeric.index + "]";
|
||||
let metadata = openmct.telemetry.getMetadata(layoutItem.domainObject);
|
||||
const TEXT_SIZE = [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96];
|
||||
|
||||
return [
|
||||
{
|
||||
control: "select-menu",
|
||||
domainObject: domainObject,
|
||||
property: path + ".displayMode",
|
||||
title: "Set display mode",
|
||||
options: [
|
||||
{
|
||||
name: 'Label + Value',
|
||||
value: 'all'
|
||||
},
|
||||
{
|
||||
name: "Label only",
|
||||
value: "label"
|
||||
},
|
||||
{
|
||||
name: "Value only",
|
||||
value: "value"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
control: "separator"
|
||||
},
|
||||
{
|
||||
control: "select-menu",
|
||||
domainObject: domainObject,
|
||||
property: path + ".value",
|
||||
title: "Set value",
|
||||
options: metadata.values().map(value => {
|
||||
return {
|
||||
name: value.name,
|
||||
value: value.key
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
control: "separator"
|
||||
},
|
||||
{
|
||||
control: "color-picker",
|
||||
domainObject: domainObject,
|
||||
property: path + ".fill",
|
||||
icon: "icon-paint-bucket",
|
||||
title: "Set fill color"
|
||||
},
|
||||
{
|
||||
control: "color-picker",
|
||||
domainObject: domainObject,
|
||||
property: path + ".stroke",
|
||||
icon: "icon-line-horz",
|
||||
title: "Set border color"
|
||||
},
|
||||
{
|
||||
control: "color-picker",
|
||||
domainObject: domainObject,
|
||||
property: path + ".color",
|
||||
icon: "icon-font",
|
||||
mandatory: true,
|
||||
title: "Set text color",
|
||||
preventNone: true
|
||||
},
|
||||
{
|
||||
control: "separator"
|
||||
},
|
||||
{
|
||||
control: "select-menu",
|
||||
domainObject: domainObject,
|
||||
property: path + ".size",
|
||||
title: "Set text size",
|
||||
options: TEXT_SIZE.map(size => {
|
||||
return {
|
||||
value: size + "px"
|
||||
};
|
||||
})
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
control: "toggle-button",
|
||||
domainObject: domainObject,
|
||||
property: "configuration.panels[" + layoutItem.id + "].hasFrame",
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-frame-hide',
|
||||
title: "Hide frame"
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-frame-show',
|
||||
title: "Show frame"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DisplayLayoutToolbar;
|
||||
});
|
@ -29,9 +29,8 @@ define(function () {
|
||||
initialize(domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {
|
||||
layout: {
|
||||
panels: {}
|
||||
}
|
||||
panels: {},
|
||||
alphanumerics: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
74
src/plugins/displayLayout/SubobjectViewConfiguration.js
Normal file
74
src/plugins/displayLayout/SubobjectViewConfiguration.js
Normal file
@ -0,0 +1,74 @@
|
||||
/*****************************************************************************
|
||||
* 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 SubobjectViewConfiguration {
|
||||
/**
|
||||
*
|
||||
* @param domainObject the domain object to mutate.
|
||||
* @param id
|
||||
* @param rawPosition
|
||||
* @param openmct
|
||||
*/
|
||||
constructor(domainObject, id, hasFrame, rawPosition, openmct) {
|
||||
this.domainObject = domainObject;
|
||||
this.id = id;
|
||||
this.hasFrame = hasFrame;
|
||||
this.rawPosition = rawPosition;
|
||||
this.openmct = openmct;
|
||||
this.mutatePosition = this.mutatePosition.bind(this);
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
mutatePosition() {
|
||||
let path = "configuration.panels[" + this.id + "]";
|
||||
this.mutate(path + ".dimensions", this.rawPosition.dimensions);
|
||||
this.mutate(path + ".position", this.rawPosition.position);
|
||||
}
|
||||
|
||||
mutate(path, value) {
|
||||
this.openmct.objects.mutate(this.domainObject, path, value);
|
||||
}
|
||||
|
||||
attachListeners() {
|
||||
let path = "configuration.panels[" + this.id + "].hasFrame";
|
||||
this.listeners.push(this.openmct.objects.observe(this.domainObject, path, function (newValue) {
|
||||
this.hasFrame = newValue;
|
||||
}.bind(this)));
|
||||
|
||||
this.listeners.push(this.openmct.objects.observe(this.domainObject, '*', function (obj) {
|
||||
this.domainObject = JSON.parse(JSON.stringify(obj));
|
||||
}.bind(this)));
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
this.listeners.forEach(listener => {
|
||||
listener();
|
||||
});
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
|
||||
return SubobjectViewConfiguration;
|
||||
}
|
||||
);
|
84
src/plugins/displayLayout/TelemetryViewConfiguration.js
Normal file
84
src/plugins/displayLayout/TelemetryViewConfiguration.js
Normal file
@ -0,0 +1,84 @@
|
||||
/*****************************************************************************
|
||||
* 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 TelemetryViewConfiguration {
|
||||
/**
|
||||
*
|
||||
* @param domainObject the domain object to mutate.
|
||||
* @param alphanumeric
|
||||
* @param rawPosition
|
||||
* @param openmct
|
||||
*/
|
||||
constructor(domainObject, alphanumeric, rawPosition, openmct) {
|
||||
this.domainObject = domainObject;
|
||||
this.alphanumeric = alphanumeric;
|
||||
this.rawPosition = rawPosition;
|
||||
this.openmct = openmct;
|
||||
this.mutatePosition = this.mutatePosition.bind(this);
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
mutatePosition() {
|
||||
let path = "configuration.alphanumerics[" + this.alphanumeric.index + "]";
|
||||
this.mutate(path + ".dimensions", this.rawPosition.dimensions);
|
||||
this.mutate(path + ".position", this.rawPosition.position);
|
||||
}
|
||||
|
||||
attachListeners() {
|
||||
let path = "configuration.alphanumerics[" + this.alphanumeric.index + "]";
|
||||
[
|
||||
'displayMode',
|
||||
'value',
|
||||
'fill',
|
||||
'stroke',
|
||||
'color',
|
||||
'size'
|
||||
].forEach(property => {
|
||||
this.listeners.push(
|
||||
this.openmct.objects.observe(this.domainObject, path + "." + property, function (newValue) {
|
||||
this.alphanumeric[property] = newValue;
|
||||
}.bind(this))
|
||||
);
|
||||
});
|
||||
this.listeners.push(this.openmct.objects.observe(this.domainObject, '*', function (obj) {
|
||||
this.domainObject = JSON.parse(JSON.stringify(obj));
|
||||
}.bind(this)));
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
this.listeners.forEach(listener => {
|
||||
listener();
|
||||
});
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
mutate(path, value) {
|
||||
this.openmct.objects.mutate(this.domainObject, path, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return TelemetryViewConfiguration;
|
||||
}
|
||||
);
|
380
src/plugins/displayLayout/components/DisplayLayout.vue
Normal file
380
src/plugins/displayLayout/components/DisplayLayout.vue
Normal file
@ -0,0 +1,380 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="l-layout"
|
||||
@dragover="handleDragOver"
|
||||
@click="bypassSelection"
|
||||
@drop="handleDrop">
|
||||
<div class="l-layout__object">
|
||||
<!-- Background grid -->
|
||||
<div class="l-layout__grid-holder c-grid"
|
||||
v-if="!drilledIn">
|
||||
<div class="c-grid__x l-grid l-grid-x"
|
||||
v-if="gridSize[0] >= 3"
|
||||
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
|
||||
</div>
|
||||
<div class="c-grid__y l-grid l-grid-y"
|
||||
v-if="gridSize[1] >= 3"
|
||||
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
||||
</div>
|
||||
<layout-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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.l-layout {
|
||||
@include abs();
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__grid-holder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__object {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__frame {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.l-shell__main-container {
|
||||
> .l-layout {
|
||||
[s-selected] {
|
||||
border: $browseBorderSelected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Styles moved to _global.scss;
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import LayoutItem from './LayoutItem.vue';
|
||||
import TelemetryViewConfiguration from './../TelemetryViewConfiguration.js'
|
||||
import SubobjectViewConfiguration from './../SubobjectViewConfiguration.js'
|
||||
|
||||
const DEFAULT_GRID_SIZE = [32, 32],
|
||||
DEFAULT_DIMENSIONS = [12, 8],
|
||||
DEFAULT_TELEMETRY_DIMENSIONS = [2, 1],
|
||||
DEFAULT_POSITION = [0, 0],
|
||||
MINIMUM_FRAME_SIZE = [320, 180],
|
||||
DEFAULT_HIDDEN_FRAME_TYPES = [
|
||||
'hyperlink'
|
||||
];
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
gridSize: [],
|
||||
layoutItems: [],
|
||||
drilledIn: undefined
|
||||
}
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: ['domainObject'],
|
||||
components: {
|
||||
LayoutItem
|
||||
},
|
||||
methods: {
|
||||
getAlphanumerics() {
|
||||
let alphanumerics = this.newDomainObject.configuration.alphanumerics || [];
|
||||
alphanumerics.forEach((alphanumeric, index) => {
|
||||
alphanumeric.index = index;
|
||||
this.makeTelemetryItem(alphanumeric, false);
|
||||
});
|
||||
},
|
||||
makeFrameItem(panel, initSelect) {
|
||||
let rawPosition = {
|
||||
position: panel.position,
|
||||
dimensions: panel.dimensions
|
||||
};
|
||||
let style = this.convertPosition(rawPosition);
|
||||
let id = this.openmct.objects.makeKeyString(panel.domainObject.identifier);
|
||||
let config = new SubobjectViewConfiguration(
|
||||
this.newDomainObject, id, panel.hasFrame, rawPosition, openmct);
|
||||
|
||||
this.layoutItems.push({
|
||||
id: id,
|
||||
domainObject: panel.domainObject,
|
||||
style: style,
|
||||
drilledIn: this.isItemDrilledIn(id),
|
||||
initSelect: initSelect,
|
||||
type: 'subobject-view',
|
||||
config: config
|
||||
});
|
||||
},
|
||||
makeTelemetryItem(alphanumeric, initSelect) {
|
||||
let rawPosition = {
|
||||
position: alphanumeric.position,
|
||||
dimensions: alphanumeric.dimensions
|
||||
};
|
||||
let style = this.convertPosition(rawPosition);
|
||||
let id = this.openmct.objects.makeKeyString(alphanumeric.identifier);
|
||||
|
||||
this.openmct.objects.get(id).then(domainObject => {
|
||||
let config = new TelemetryViewConfiguration(
|
||||
this.newDomainObject, alphanumeric, rawPosition, openmct);
|
||||
|
||||
this.layoutItems.push({
|
||||
id: id,
|
||||
domainObject: domainObject,
|
||||
style: style,
|
||||
initSelect: initSelect,
|
||||
alphanumeric: alphanumeric,
|
||||
type: 'telemetry-view',
|
||||
config: config
|
||||
});
|
||||
});
|
||||
},
|
||||
getSubobjectDefaultDimensions() {
|
||||
let gridSize = this.gridSize;
|
||||
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
||||
return Math.max(
|
||||
Math.ceil(min / gridSize[i]),
|
||||
DEFAULT_DIMENSIONS[i]
|
||||
);
|
||||
});
|
||||
},
|
||||
convertPosition(raw) {
|
||||
return {
|
||||
left: (this.gridSize[0] * raw.position[0]) + 'px',
|
||||
top: (this.gridSize[1] * raw.position[1]) + 'px',
|
||||
width: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
height: (this.gridSize[1] * raw.dimensions[1]) + 'px',
|
||||
minWidth: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px'
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Checks if the frame should be hidden or not.
|
||||
*
|
||||
* @param type the domain object type
|
||||
* @return {boolean} true if the object should have
|
||||
* frame by default, false, otherwise
|
||||
*/
|
||||
hasFrameByDefault(type) {
|
||||
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
||||
},
|
||||
setSelection(selection) {
|
||||
if (selection.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateDrilledInState();
|
||||
},
|
||||
updateDrilledInState(id) {
|
||||
this.drilledIn = id;
|
||||
this.layoutItems.forEach(function (item) {
|
||||
if (item.type === 'subobject-view') {
|
||||
item.drilledIn = item.id === id;
|
||||
}
|
||||
});
|
||||
},
|
||||
isItemDrilledIn(id) {
|
||||
return this.drilledIn === id;
|
||||
},
|
||||
updatePosition(item, newPosition) {
|
||||
let newStyle = this.convertPosition(newPosition);
|
||||
item.config.rawPosition = newPosition;
|
||||
item.style = newStyle;
|
||||
},
|
||||
bypassSelection($event) {
|
||||
if (this.dragInProgress) {
|
||||
if ($event) {
|
||||
$event.stopImmediatePropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
endDrag(item) {
|
||||
this.dragInProgress = true;
|
||||
setTimeout(function () {
|
||||
this.dragInProgress = false;
|
||||
}.bind(this), 0);
|
||||
|
||||
item.config.mutatePosition();
|
||||
},
|
||||
mutate(path, value) {
|
||||
this.openmct.objects.mutate(this.newDomainObject, path, value);
|
||||
},
|
||||
handleDrop($event) {
|
||||
$event.preventDefault();
|
||||
|
||||
let domainObject = JSON.parse($event.dataTransfer.getData('domainObject'));
|
||||
let elementRect = this.$el.getBoundingClientRect();
|
||||
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.addAlphanumeric(domainObject, this.droppedObjectPosition);
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
},
|
||||
addAlphanumeric(domainObject, position) {
|
||||
let alphanumeric = {
|
||||
identifier: domainObject.identifier,
|
||||
position: position,
|
||||
dimensions: DEFAULT_TELEMETRY_DIMENSIONS,
|
||||
displayMode: 'all',
|
||||
value: this.getDefaultTelemetryValue(domainObject),
|
||||
stroke: "transparent",
|
||||
fill: "",
|
||||
color: "",
|
||||
size: "13px",
|
||||
};
|
||||
let alphanumerics = this.newDomainObject.configuration.alphanumerics || [];
|
||||
alphanumeric.index = alphanumerics.push(alphanumeric) - 1;
|
||||
|
||||
this.mutate("configuration.alphanumerics", alphanumerics);
|
||||
this.makeTelemetryItem(alphanumeric, true);
|
||||
},
|
||||
getDefaultTelemetryValue(domainObject) {
|
||||
let metadata = this.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;
|
||||
},
|
||||
handleDragOver($event){
|
||||
$event.preventDefault();
|
||||
},
|
||||
isTelemetry(domainObject) {
|
||||
if (this.openmct.telemetry.isTelemetryObject(domainObject)
|
||||
&& domainObject.type !== 'summary-widget') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
addObject(domainObject) {
|
||||
if (!this.isTelemetry(domainObject)) {
|
||||
let panels = this.newDomainObject.configuration.panels,
|
||||
id = this.openmct.objects.makeKeyString(domainObject.identifier),
|
||||
panel = panels[id],
|
||||
mutateObject = false,
|
||||
initSelect = false;
|
||||
|
||||
// If this is a new panel, select it and save the configuration.
|
||||
if (!panel) {
|
||||
panel = {};
|
||||
mutateObject = true;
|
||||
initSelect = true;
|
||||
}
|
||||
|
||||
panel.dimensions = panel.dimensions || this.getSubobjectDefaultDimensions();
|
||||
panel.hasFrame = panel.hasOwnProperty('hasFrame') ?
|
||||
panel.hasFrame :
|
||||
this.hasFrameByDefault(domainObject.type);
|
||||
|
||||
if (this.droppedObjectPosition) {
|
||||
panel.position = this.droppedObjectPosition;
|
||||
this.droppedObjectPosition = undefined;
|
||||
} else {
|
||||
panel.position = panel.position || DEFAULT_POSITION;
|
||||
}
|
||||
|
||||
if (mutateObject) {
|
||||
this.mutate("configuration.panels[" + id + "]", panel);
|
||||
}
|
||||
|
||||
panel.domainObject = domainObject;
|
||||
this.makeFrameItem(panel, initSelect);
|
||||
}
|
||||
},
|
||||
removeObject() {
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
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.composition = this.openmct.composition.get(this.newDomainObject);
|
||||
this.composition.on('add', this.addObject);
|
||||
this.composition.on('remove', this.removeObject);
|
||||
this.composition.load();
|
||||
this.getAlphanumerics();
|
||||
},
|
||||
destroyed: function () {
|
||||
this.openmct.off('change', this.setSelection);
|
||||
this.composition.off('add', this.addObject);
|
||||
this.composition.off('remove', this.removeObject);
|
||||
this.unlisten();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
@ -25,17 +25,8 @@
|
||||
:style="item.style"
|
||||
:class="classObject"
|
||||
@dblclick="drill(item.id, $event)">
|
||||
<div class="c-frame__header">
|
||||
<div class="c-frame__header__start">
|
||||
<div class="c-frame__name icon-object">{{ item.domainObject.name }}</div>
|
||||
<div class="c-frame__context-actions c-disclosure-button"></div>
|
||||
</div>
|
||||
<div class="c-frame__header__end">
|
||||
<div class="c-button icon-expand local-controls--hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<object-view class="c-frame__object-view"
|
||||
:object="item.domainObject"></object-view>
|
||||
|
||||
<component :is="item.type" :item="item"></component>
|
||||
|
||||
<!-- Drag handles -->
|
||||
<div class="c-frame-edit">
|
||||
@ -60,68 +51,11 @@
|
||||
.c-frame {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-width: 1px;
|
||||
border-color: transparent;
|
||||
|
||||
/*************************** HEADER */
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: $interiorMargin;
|
||||
|
||||
> [class*="__"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
[class*="__start"] {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[class*="__end"] {
|
||||
//justify-content: flex-end;
|
||||
flex: 0 0 auto;
|
||||
|
||||
[class*="button"] {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
flex: 0 1 auto;
|
||||
font-size: 1.2em;
|
||||
|
||||
&:before {
|
||||
// Object type icon
|
||||
flex: 0 0 auto;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************** OBJECT VIEW */
|
||||
&__object-view {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
.c-object-view {
|
||||
.u-fills-container {
|
||||
// Expand component types that fill a container
|
||||
@include abs();
|
||||
}
|
||||
}
|
||||
}
|
||||
border: 1px solid transparent;
|
||||
|
||||
/*************************** NO-FRAME */
|
||||
&.no-frame {
|
||||
> [class*="__header"] {
|
||||
> [class*="contents"] > [class*="__header"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -131,15 +65,14 @@
|
||||
border: 1px solid $colorInteriorBorder;
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
|
||||
// Styles moved to _global.scss;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import ObjectView from '../../ui/components/layout/ObjectView.vue'
|
||||
import LayoutDrag from './LayoutDrag'
|
||||
import SubobjectView from './SubobjectView.vue'
|
||||
import TelemetryView from './TelemetryView.vue'
|
||||
import LayoutDrag from './../LayoutDrag'
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
@ -148,13 +81,14 @@
|
||||
gridSize: Array
|
||||
},
|
||||
components: {
|
||||
ObjectView
|
||||
SubobjectView,
|
||||
TelemetryView
|
||||
},
|
||||
computed: {
|
||||
classObject: function () {
|
||||
return {
|
||||
'is-drilled-in': this.item.drilledIn,
|
||||
'no-frame': !this.item.hasFrame
|
||||
'no-frame': !this.item.config.hasFrame
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -164,7 +98,7 @@
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
if (!this.isBeingEdited(this.item.domainObject)) {
|
||||
if (!this.openmct.editor.isEditing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -179,10 +113,6 @@
|
||||
|
||||
this.$emit('drilledIn', id);
|
||||
},
|
||||
isBeingEdited(object) {
|
||||
// TODO: add logic when inEditContext() is implemented in Vue.
|
||||
return true;
|
||||
},
|
||||
updatePosition(event) {
|
||||
let currentPosition = [event.pageX, event.pageY];
|
||||
this.initialPosition = this.initialPosition || currentPosition;
|
||||
@ -196,7 +126,7 @@
|
||||
|
||||
this.updatePosition(event);
|
||||
this.activeDrag = new LayoutDrag(
|
||||
this.item.rawPosition,
|
||||
this.item.config.rawPosition,
|
||||
posFactor,
|
||||
dimFactor,
|
||||
this.gridSize
|
||||
@ -208,24 +138,31 @@
|
||||
this.updatePosition(event);
|
||||
|
||||
if (this.activeDrag) {
|
||||
this.$emit('dragInProgress', this.item.id, this.activeDrag.getAdjustedPosition(this.delta));
|
||||
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.id);
|
||||
this.$emit('endDrag', this.item);
|
||||
this.initialPosition = undefined;
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let context = {
|
||||
item: this.item.domainObject,
|
||||
layoutItem: this.item
|
||||
};
|
||||
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el,
|
||||
{
|
||||
item: this.item.domainObject
|
||||
},
|
||||
context,
|
||||
this.item.initSelect
|
||||
);
|
||||
},
|
118
src/plugins/displayLayout/components/SubobjectView.vue
Normal file
118
src/plugins/displayLayout/components/SubobjectView.vue
Normal file
@ -0,0 +1,118 @@
|
||||
/*****************************************************************************
|
||||
* 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="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>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.c-so-view {
|
||||
/*************************** HEADER */
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: $interiorMargin;
|
||||
|
||||
> [class*="__"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
[class*="__start"] {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[class*="__end"] {
|
||||
//justify-content: flex-end;
|
||||
flex: 0 0 auto;
|
||||
|
||||
[class*="button"] {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
flex: 0 1 auto;
|
||||
font-size: 1.2em;
|
||||
|
||||
&:before {
|
||||
// Object type icon
|
||||
flex: 0 0 auto;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************** OBJECT VIEW */
|
||||
&__object-view {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
.c-object-view {
|
||||
.u-fills-container {
|
||||
// Expand component types that fill a container
|
||||
@include abs();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</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>
|
181
src/plugins/displayLayout/components/TelemetryView.vue
Normal file
181
src/plugins/displayLayout/components/TelemetryView.vue
Normal file
@ -0,0 +1,181 @@
|
||||
/*****************************************************************************
|
||||
* 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-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>
|
||||
|
||||
<div v-if="showValue"
|
||||
class="c-telemetry-view__value"
|
||||
:class="[telemetryClass]">
|
||||
<div class="c-telemetry-view__value-text">{{ telemetryValue }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~styles/sass-base';
|
||||
|
||||
.c-telemetry-view {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
> * {
|
||||
// Label and value holders
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
// justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding: $interiorMargin;
|
||||
|
||||
> * {
|
||||
// Text elements
|
||||
@include ellipsize();
|
||||
}
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
.c-frame & {
|
||||
@include abs();
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object
|
||||
},
|
||||
computed: {
|
||||
showLabel() {
|
||||
let displayMode = this.item.config.alphanumeric.displayMode;
|
||||
return displayMode === 'all' || displayMode === 'label';
|
||||
},
|
||||
showValue() {
|
||||
let displayMode = this.item.config.alphanumeric.displayMode;
|
||||
return displayMode === 'all' || displayMode === 'value';
|
||||
},
|
||||
styleObject() {
|
||||
let alphanumeric = this.item.config.alphanumeric;
|
||||
return {
|
||||
backgroundColor: alphanumeric.fill,
|
||||
borderColor: alphanumeric.stroke,
|
||||
color: alphanumeric.color,
|
||||
fontSize: alphanumeric.size
|
||||
}
|
||||
},
|
||||
valueMetadata() {
|
||||
return this.metadata.value(this.item.config.alphanumeric.value);
|
||||
},
|
||||
valueFormatter() {
|
||||
return this.formats[this.item.config.alphanumeric.value];
|
||||
},
|
||||
telemetryValue() {
|
||||
if (!this.datum) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.valueFormatter && this.valueFormatter.format(this.datum);
|
||||
},
|
||||
telemetryClass() {
|
||||
if (!this.datum) {
|
||||
return;
|
||||
}
|
||||
|
||||
let alarm = this.limitEvaluator && this.limitEvaluator.evaluate(this.datum, this.valueMetadata);
|
||||
return alarm && alarm.cssClass;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
datum: {},
|
||||
formats: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
requestHistoricalData() {
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let options = {
|
||||
start: bounds.start,
|
||||
end: bounds.end,
|
||||
size: 1
|
||||
};
|
||||
this.openmct.telemetry.request(this.item.domainObject, options)
|
||||
.then(data => {
|
||||
if (data.length > 0) {
|
||||
this.updateView(data[data.length - 1]);
|
||||
}
|
||||
});
|
||||
},
|
||||
subscribeToObject() {
|
||||
this.subscription = this.openmct.telemetry.subscribe(this.item.domainObject, function (datum) {
|
||||
if (this.openmct.time.clock() !== undefined) {
|
||||
this.updateView(datum);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
updateView(datum) {
|
||||
this.datum = datum;
|
||||
},
|
||||
removeSubscription() {
|
||||
if (this.subscription) {
|
||||
this.subscription();
|
||||
this.subscription = undefined;
|
||||
}
|
||||
},
|
||||
refreshData(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
this.datum = undefined;
|
||||
this.requestHistoricalData(this.item.domainObject);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.item.domainObject);
|
||||
this.metadata = this.openmct.telemetry.getMetadata(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();
|
||||
this.item.config.removeListeners();
|
||||
this.openmct.time.off("bounds", this.refreshData);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
@ -20,10 +20,11 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import Layout from './DisplayLayout.vue'
|
||||
import Layout from './components/DisplayLayout.vue'
|
||||
import Vue from 'vue'
|
||||
import objectUtils from '../../api/objects/object-utils.js'
|
||||
import DisplayLayoutType from './DisplayLayoutType.js'
|
||||
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'
|
||||
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
@ -63,6 +64,7 @@ export default function () {
|
||||
}
|
||||
});
|
||||
openmct.types.addType('layout', DisplayLayoutType());
|
||||
openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct));
|
||||
openmct.composition.addPolicy((parent, child) => {
|
||||
if (parent.type === 'layout' && child.type === 'folder') {
|
||||
return false;
|
||||
@ -70,41 +72,5 @@ export default function () {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
openmct.toolbars.addProvider({
|
||||
name: "Display Layout Toolbar",
|
||||
key: "layout",
|
||||
description: "A toolbar for objects inside a display layout.",
|
||||
forSelection: function (selection) {
|
||||
// Apply the layout toolbar if the selected object is inside a layout,
|
||||
// and in edit mode.
|
||||
return (selection &&
|
||||
selection[1] &&
|
||||
selection[1].context.item &&
|
||||
selection[1].context.item.type === 'layout' &&
|
||||
openmct.editor.isEditing());
|
||||
},
|
||||
toolbar: function (selection) {
|
||||
let id = openmct.objects.makeKeyString(selection[0].context.item.identifier);
|
||||
return [
|
||||
{
|
||||
control: "toggle-button",
|
||||
domainObject: selection[1].context.item,
|
||||
property: "configuration.layout.panels[" + id + "].hasFrame",
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
icon: 'icon-frame-hide',
|
||||
title: "Hide frame"
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: 'icon-frame-show',
|
||||
title: "Show frame"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ $browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
|
||||
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
|
||||
$editBorderSelectable: 1px dotted rgba($editColor, 1);
|
||||
$editBorderSelectableHov: 1px dashed rgba($editColor, 1);
|
||||
$editBorderSelected: 1px solid $editColor;
|
||||
$editBorderSelected: 1px solid rgba($editColor, 0.7);
|
||||
$editBorderDrilledIn: 1px dashed #ff4d9a;
|
||||
$colorGridLines: rgba($editColor, 0.2);
|
||||
|
||||
|
@ -386,8 +386,24 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
@mixin cToolbarSeparator() {
|
||||
$m: $interiorMargin;
|
||||
$b: 1px;
|
||||
display: block;
|
||||
width: $m + $b; // Allow for border
|
||||
border-right: $b solid $colorInteriorBorder;
|
||||
margin-right: $m;
|
||||
}
|
||||
|
||||
.c-toolbar {
|
||||
height: 24px; // Need to standardize the height
|
||||
$p: $interiorMargin;
|
||||
border-top: 1px solid $colorInteriorBorder;
|
||||
height: $p + 24px; // Need to standardize the height
|
||||
padding-top: $p;
|
||||
|
||||
&__separator {
|
||||
@include cToolbarSeparator();
|
||||
}
|
||||
|
||||
.c-click-icon {
|
||||
@include cControl();
|
||||
@ -444,14 +460,9 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
}
|
||||
|
||||
+ .c-button-set {
|
||||
$m: $interiorMargin;
|
||||
$b: 1px;
|
||||
&:before {
|
||||
@include cToolbarSeparator();
|
||||
content: '';
|
||||
display: block;
|
||||
width: $m + $b; // Allow for border
|
||||
border-right: $b solid $colorInteriorBorder;
|
||||
margin-right: $m;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,6 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.c-object-view {
|
||||
display: contents;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import _ from "lodash"
|
||||
|
||||
@ -53,7 +47,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.viewContainer = document.createElement('div');
|
||||
this.viewContainer.classList.add('c-object-view');
|
||||
this.viewContainer.classList.add('c-object-view','u-contents');
|
||||
this.$el.append(this.viewContainer);
|
||||
let provider = this.openmct.objectViews.getByProviderKey(this.viewKey);
|
||||
if (!provider) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="c-toolbar" v-if="structure.length !== 0">
|
||||
<div class="c-toolbar">
|
||||
<component v-for="item in structure"
|
||||
:is="item.control"
|
||||
:options="item"
|
||||
@ -48,9 +48,14 @@
|
||||
let structure = this.openmct.toolbars.get(selection) || [];
|
||||
this.structure = structure.map(function (item) {
|
||||
let toolbarItem = {...item};
|
||||
let domainObject = toolbarItem.domainObject;
|
||||
toolbarItem.control = "toolbar-" + toolbarItem.control;
|
||||
toolbarItem.value = _.get(toolbarItem.domainObject, item.property);
|
||||
this.registerListener(toolbarItem.domainObject);
|
||||
|
||||
if (domainObject) {
|
||||
toolbarItem.value = _.get(domainObject, item.property);
|
||||
this.registerListener(domainObject);
|
||||
}
|
||||
|
||||
return toolbarItem;
|
||||
}.bind(this));
|
||||
},
|
||||
@ -82,15 +87,19 @@
|
||||
updateToolbarAfterMutation() {
|
||||
this.structure = this.structure.map((item) => {
|
||||
let toolbarItem = {...item};
|
||||
let id = this.openmct.objects.makeKeyString(toolbarItem.domainObject.identifier);
|
||||
let newObject = this.domainObjectsById[id].newObject;
|
||||
let domainObject = toolbarItem.domainObject;
|
||||
|
||||
if (newObject) {
|
||||
toolbarItem.domainObject = newObject;
|
||||
let newValue = _.get(newObject, item.property);
|
||||
if (domainObject) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
let newObject = this.domainObjectsById[id].newObject;
|
||||
|
||||
if (toolbarItem.value !== newValue) {
|
||||
toolbarItem.value = newValue;
|
||||
if (newObject) {
|
||||
toolbarItem.domainObject = newObject;
|
||||
let newValue = _.get(newObject, item.property);
|
||||
|
||||
if (toolbarItem.value !== newValue) {
|
||||
toolbarItem.value = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,10 +126,14 @@
|
||||
let changedItemId = this.openmct.objects.makeKeyString(item.domainObject.identifier);
|
||||
this.structure = this.structure.map((s) => {
|
||||
let toolbarItem = {...s};
|
||||
let id = this.openmct.objects.makeKeyString(toolbarItem.domainObject.identifier);
|
||||
let domainObject = toolbarItem.domainObject;
|
||||
|
||||
if (changedItemId === id && item.property === s.property) {
|
||||
toolbarItem.value = value;
|
||||
if (domainObject) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
|
||||
if (changedItemId === id && item.property === s.property) {
|
||||
toolbarItem.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
return toolbarItem;
|
||||
|
@ -14,7 +14,7 @@
|
||||
v-if="!this.options.preventNone"
|
||||
@click="select({value: 'transparent'})">
|
||||
<div class="c-palette__item"></div>
|
||||
No fill
|
||||
None
|
||||
</div>
|
||||
<div class="c-palette__items">
|
||||
<div class="c-palette__item"
|
||||
|
@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="c-button-set"></div>
|
||||
</div>
|
||||
<div class="c-toolbar__separator"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -27,7 +27,7 @@ const webpackConfig = {
|
||||
"bourbon": "bourbon.scss",
|
||||
"espresso": path.join(__dirname, "src/styles/theme-espresso.scss"),
|
||||
"snow": path.join(__dirname, "src/styles/theme-snow.scss"),
|
||||
"vue": path.join(__dirname, "node_modules/vue/dist/vue.min.js"),
|
||||
"vue": path.join(__dirname, "node_modules/vue/dist/vue.js"),
|
||||
"d3-scale": path.join(__dirname, "node_modules/d3-scale/build/d3-scale.min.js"),
|
||||
"styles": path.join(__dirname, "src/styles-new")
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user