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:
Pegah Sarram 2018-11-08 17:09:17 -08:00 committed by Pete Richards
parent 55d3ab5e8a
commit d13d59bfa0
17 changed files with 1066 additions and 472 deletions

View File

@ -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>

View 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;
});

View File

@ -29,9 +29,8 @@ define(function () {
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
layout: {
panels: {}
}
panels: {},
alphanumerics: []
};
}
}

View 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;
}
);

View 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;
}
);

View 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>

View File

@ -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
);
},

View 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>

View 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>

View File

@ -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"
}
]
}
];
}
});
}
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -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"

View File

@ -1,7 +1,5 @@
<template>
<div>
<div class="c-button-set"></div>
</div>
<div class="c-toolbar__separator"></div>
</template>
<script>

View File

@ -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")
}