mirror of
https://github.com/nasa/openmct.git
synced 2025-06-04 00:20:50 +00:00
merge display layout view config with components, add line view.
* Hacky WIP * WIP * check for 'domainobject' in data transfer * Metadata manager can return default display value * Refactor subobject and telemetry views to use layout frame * Use data domainObject * Get metadata for selected object. * Don't inject lodash via vue * move selection to specific layout types * restore toolbar functionality * Support creating line * Add controls for setting x, y, x2 and y2 for lines from the toolbar. * Initial attempt at resizing lines * Check for duplicate panel * line resize handles working * Get Text and Box elements working. * Refactor image view to use layout frame * Fix drill in * Check for object before accessing the identifier to avoid type error. * Add inspectable class if item is subobject or telemetry view. * Delete unused files * remove unused imports * Fix typos * Fix cssClass and objectPath * object can be undefined so check for it not being undefined before adding a listener. * Set cssClass when domain object is available * Get user input for text and image in the toolbar when adding element.
This commit is contained in:
parent
c6a181a2e7
commit
c1ef701eb2
@ -124,6 +124,22 @@ define([
|
|||||||
return sortedMetadata;
|
return sortedMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TelemetryMetadataManager.prototype.getDefaultDisplayValue = function () {
|
||||||
|
let valueMetadata = this.valuesForHints(['range'])[0];
|
||||||
|
|
||||||
|
if (valueMetadata === undefined) {
|
||||||
|
valueMetadata = this.values().filter(values => {
|
||||||
|
return !(values.hints.domain);
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueMetadata === undefined) {
|
||||||
|
valueMetadata = this.values()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return valueMetadata.key;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return TelemetryMetadataManager;
|
return TelemetryMetadataManager;
|
||||||
|
|
||||||
|
@ -35,9 +35,48 @@ define([], function () {
|
|||||||
(selection[0].context.item && selection[0].context.item.type === 'layout')));
|
(selection[0].context.item && selection[0].context.item.type === 'layout')));
|
||||||
},
|
},
|
||||||
toolbar: function (selection) {
|
toolbar: function (selection) {
|
||||||
|
const DIALOG_FORM = {
|
||||||
|
'text': {
|
||||||
|
name: "Text Element Properties",
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
key: "text",
|
||||||
|
control: "textfield",
|
||||||
|
name: "Text",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'image': {
|
||||||
|
name: "Image Properties",
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
key: "url",
|
||||||
|
control: "textfield",
|
||||||
|
name: "Image URL",
|
||||||
|
"cssClass": "l-input-lg",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getUserInput(form) {
|
||||||
|
return openmct.$injector.get('dialogService').getUserInput(form, {});
|
||||||
|
}
|
||||||
|
|
||||||
let selectedParent = selection[1] && selection[1].context.item,
|
let selectedParent = selection[1] && selection[1].context.item,
|
||||||
selectedObject = selection[0].context.item,
|
selectedObject = selection[0].context.item,
|
||||||
layoutItem = selection[0].context.layoutItem,
|
layoutItem = selection[0].context.layoutItem,
|
||||||
|
layoutItemIndex = selection[0].context.index,
|
||||||
toolbar = [];
|
toolbar = [];
|
||||||
|
|
||||||
if (selectedObject && selectedObject.type === 'layout') {
|
if (selectedObject && selectedObject.type === 'layout') {
|
||||||
@ -45,7 +84,14 @@ define([], function () {
|
|||||||
control: "menu",
|
control: "menu",
|
||||||
domainObject: selectedObject,
|
domainObject: selectedObject,
|
||||||
method: function (option) {
|
method: function (option) {
|
||||||
selection[0].context.addElement(option.name.toLowerCase());
|
let name = option.name.toLowerCase();
|
||||||
|
let form = DIALOG_FORM[name];
|
||||||
|
if (form) {
|
||||||
|
getUserInput(form)
|
||||||
|
.then(element => selection[0].context.addElement(name, element));
|
||||||
|
} else {
|
||||||
|
selection[0].context.addElement(name);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
key: "add",
|
key: "add",
|
||||||
icon: "icon-plus",
|
icon: "icon-plus",
|
||||||
@ -75,16 +121,19 @@ define([], function () {
|
|||||||
return toolbar;
|
return toolbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let path = `configuration.items[${layoutItemIndex}]`;
|
||||||
|
let separator = {
|
||||||
|
control: "separator"
|
||||||
|
};
|
||||||
|
|
||||||
if (layoutItem.type === 'subobject-view') {
|
if (layoutItem.type === 'subobject-view') {
|
||||||
if (toolbar.length > 0) {
|
if (toolbar.length > 0) {
|
||||||
toolbar.push({
|
toolbar.push(separator);
|
||||||
control: "separator"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
toolbar.push({
|
toolbar.push({
|
||||||
control: "toggle-button",
|
control: "toggle-button",
|
||||||
domainObject: selectedParent,
|
domainObject: selectedParent,
|
||||||
property: "configuration.panels[" + layoutItem.id + "].hasFrame",
|
property: path + ".hasFrame",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: false,
|
value: false,
|
||||||
@ -100,19 +149,7 @@ define([], function () {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const TEXT_SIZE = [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96];
|
const TEXT_SIZE = [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96];
|
||||||
let path;
|
let fill = {
|
||||||
// TODO: get the path from the view configuration
|
|
||||||
// let path = layoutItem.config.path();
|
|
||||||
if (layoutItem.type === 'telemetry-view') {
|
|
||||||
path = "configuration.alphanumerics[" + layoutItem.config.alphanumeric.index + "]";
|
|
||||||
} else {
|
|
||||||
path = "configuration.elements[" + layoutItem.config.element.index + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
let separator = {
|
|
||||||
control: "separator"
|
|
||||||
},
|
|
||||||
fill = {
|
|
||||||
control: "color-picker",
|
control: "color-picker",
|
||||||
domainObject: selectedParent,
|
domainObject: selectedParent,
|
||||||
property: path + ".fill",
|
property: path + ".fill",
|
||||||
@ -180,9 +217,7 @@ define([], function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (layoutItem.type === 'telemetry-view') {
|
if (layoutItem.type === 'telemetry-view') {
|
||||||
// TODO: add "remove", "order", "useGrid"
|
let displayMode = {
|
||||||
let metadata = openmct.telemetry.getMetadata(layoutItem.domainObject),
|
|
||||||
displayMode = {
|
|
||||||
control: "select-menu",
|
control: "select-menu",
|
||||||
domainObject: selectedParent,
|
domainObject: selectedParent,
|
||||||
property: path + ".displayMode",
|
property: path + ".displayMode",
|
||||||
@ -207,7 +242,7 @@ define([], function () {
|
|||||||
domainObject: selectedParent,
|
domainObject: selectedParent,
|
||||||
property: path + ".value",
|
property: path + ".value",
|
||||||
title: "Set value",
|
title: "Set value",
|
||||||
options: metadata.values().map(value => {
|
options: openmct.telemetry.getMetadata(selectedObject).values().map(value => {
|
||||||
return {
|
return {
|
||||||
name: value.name,
|
name: value.name,
|
||||||
value: value.key
|
value: value.key
|
||||||
@ -231,28 +266,13 @@ define([], function () {
|
|||||||
width
|
width
|
||||||
];
|
];
|
||||||
} else if (layoutItem.type === 'text-view' ) {
|
} else if (layoutItem.type === 'text-view' ) {
|
||||||
// TODO: Add "remove", "order", "useGrid"
|
|
||||||
let text = {
|
let text = {
|
||||||
control: "button",
|
control: "button",
|
||||||
domainObject: selectedParent,
|
domainObject: selectedParent,
|
||||||
property: path,
|
property: path,
|
||||||
icon: "icon-gear",
|
icon: "icon-gear",
|
||||||
title: "Edit text properties",
|
title: "Edit text properties",
|
||||||
dialog: {
|
dialog: DIALOG_FORM['text']
|
||||||
name: "Text Element Properties",
|
|
||||||
sections: [
|
|
||||||
{
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
key: "text",
|
|
||||||
control: "textfield",
|
|
||||||
name: "Text",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
toolbar = [
|
toolbar = [
|
||||||
fill,
|
fill,
|
||||||
@ -269,7 +289,6 @@ define([], function () {
|
|||||||
text
|
text
|
||||||
];
|
];
|
||||||
} else if (layoutItem.type === 'box-view') {
|
} else if (layoutItem.type === 'box-view') {
|
||||||
// TODO: Add "remove", "order", "useGrid"
|
|
||||||
toolbar = [
|
toolbar = [
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
@ -280,29 +299,13 @@ define([], function () {
|
|||||||
width
|
width
|
||||||
];
|
];
|
||||||
} else if (layoutItem.type === 'image-view') {
|
} else if (layoutItem.type === 'image-view') {
|
||||||
// TODO: Add "remove", "order", "useGrid"
|
|
||||||
let url = {
|
let url = {
|
||||||
control: "button",
|
control: "button",
|
||||||
domainObject: selectedParent,
|
domainObject: selectedParent,
|
||||||
property: path,
|
property: path,
|
||||||
icon: "icon-image",
|
icon: "icon-image",
|
||||||
title: "Edit image properties",
|
title: "Edit image properties",
|
||||||
dialog: {
|
dialog: DIALOG_FORM['image']
|
||||||
name: "Image Properties",
|
|
||||||
sections: [
|
|
||||||
{
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
key: "url",
|
|
||||||
control: "textfield",
|
|
||||||
name: "Image URL",
|
|
||||||
"cssClass": "l-input-lg",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
toolbar = [
|
toolbar = [
|
||||||
stroke,
|
stroke,
|
||||||
@ -315,8 +318,30 @@ define([], function () {
|
|||||||
url
|
url
|
||||||
];
|
];
|
||||||
} else if (layoutItem.type === 'line-view') {
|
} else if (layoutItem.type === 'line-view') {
|
||||||
// TODO: Add "remove", "order", "useGrid", "x1", "y1", x2", "y2"
|
let x2 = {
|
||||||
toolbar = [stroke];
|
control: "input",
|
||||||
|
type: "number",
|
||||||
|
domainObject: selectedParent,
|
||||||
|
property: path + ".x2",
|
||||||
|
label: "X2:",
|
||||||
|
title: "X2 position"
|
||||||
|
},
|
||||||
|
y2 = {
|
||||||
|
control: "input",
|
||||||
|
type: "number",
|
||||||
|
domainObject: selectedParent,
|
||||||
|
property: path + ".y2",
|
||||||
|
label: "Y2:",
|
||||||
|
title: "Y2 position",
|
||||||
|
};
|
||||||
|
toolbar = [
|
||||||
|
stroke,
|
||||||
|
separator,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
x2,
|
||||||
|
y2
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,9 +29,8 @@ define(function () {
|
|||||||
initialize(domainObject) {
|
initialize(domainObject) {
|
||||||
domainObject.composition = [];
|
domainObject.composition = [];
|
||||||
domainObject.configuration = {
|
domainObject.configuration = {
|
||||||
panels: {},
|
items: [],
|
||||||
alphanumerics: [],
|
layoutGrid: [10, 10],
|
||||||
elements: []
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,171 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
['./ViewConfiguration'],
|
|
||||||
function (ViewConfiguration) {
|
|
||||||
class ElementViewConfiguration extends ViewConfiguration {
|
|
||||||
|
|
||||||
static create(type, openmct) {
|
|
||||||
const DEFAULT_WIDTH = 10,
|
|
||||||
DEFAULT_HEIGHT = 5,
|
|
||||||
DEFAULT_X = 1,
|
|
||||||
DEFAULT_Y = 1;
|
|
||||||
const INITIAL_STATES = {
|
|
||||||
"image": {
|
|
||||||
stroke: "transparent"
|
|
||||||
},
|
|
||||||
"box": {
|
|
||||||
fill: "#717171",
|
|
||||||
stroke: "transparent"
|
|
||||||
},
|
|
||||||
"line": {
|
|
||||||
x: 5,
|
|
||||||
y: 3,
|
|
||||||
x2: 6,
|
|
||||||
y2: 6,
|
|
||||||
stroke: "#717171"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
fill: "transparent",
|
|
||||||
stroke: "transparent",
|
|
||||||
size: "13px",
|
|
||||||
color: ""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const DIALOGS = {
|
|
||||||
"image": {
|
|
||||||
name: "Image Properties",
|
|
||||||
sections: [
|
|
||||||
{
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
key: "url",
|
|
||||||
control: "textfield",
|
|
||||||
name: "Image URL",
|
|
||||||
"cssClass": "l-input-lg",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
name: "Text Element Properties",
|
|
||||||
sections: [
|
|
||||||
{
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
key: "text",
|
|
||||||
control: "textfield",
|
|
||||||
name: "Text",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let element = INITIAL_STATES[type] || {};
|
|
||||||
element = JSON.parse(JSON.stringify(element));
|
|
||||||
element.x = element.x || DEFAULT_X;
|
|
||||||
element.y = element.y || DEFAULT_Y;
|
|
||||||
element.width = DEFAULT_WIDTH;
|
|
||||||
element.height = DEFAULT_HEIGHT;
|
|
||||||
element.type = type;
|
|
||||||
|
|
||||||
return DIALOGS[type] ?
|
|
||||||
openmct.$injector.get('dialogService').getUserInput(DIALOGS[type], element) :
|
|
||||||
element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} configuration the element (line, box, text or image) view configuration
|
|
||||||
* @param {Object} configuration.element
|
|
||||||
* @param {Object} configuration.domainObject the telemetry domain object
|
|
||||||
* @param {Object} configuration.openmct the openmct object
|
|
||||||
*/
|
|
||||||
constructor({element, ...rest}) {
|
|
||||||
super(rest);
|
|
||||||
this.element = element;
|
|
||||||
this.updateStyle(this.position());
|
|
||||||
}
|
|
||||||
|
|
||||||
path() {
|
|
||||||
return "configuration.elements[" + this.element.index + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
x() {
|
|
||||||
return this.element.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
y() {
|
|
||||||
return this.element.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
width() {
|
|
||||||
return this.element.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
height() {
|
|
||||||
return this.element.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
observeProperties() {
|
|
||||||
[
|
|
||||||
"width",
|
|
||||||
"height",
|
|
||||||
"stroke",
|
|
||||||
"fill",
|
|
||||||
"x",
|
|
||||||
"y",
|
|
||||||
"x1",
|
|
||||||
"y1",
|
|
||||||
"x2",
|
|
||||||
"y2",
|
|
||||||
"color",
|
|
||||||
"size",
|
|
||||||
"text",
|
|
||||||
"url"
|
|
||||||
].forEach(property => {
|
|
||||||
this.attachListener(property, newValue => {
|
|
||||||
this.element[property] = newValue;
|
|
||||||
|
|
||||||
if (property === 'width' || property === 'height' ||
|
|
||||||
property === 'x' || property === 'y') {
|
|
||||||
this.updateStyle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: attach listener for useGrid
|
|
||||||
}
|
|
||||||
|
|
||||||
inspectable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ElementViewConfiguration;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,122 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
['./ViewConfiguration'],
|
|
||||||
function (ViewConfiguration) {
|
|
||||||
class SubobjectViewConfiguration extends ViewConfiguration {
|
|
||||||
|
|
||||||
static create(domainObject, gridSize, position) {
|
|
||||||
const MINIMUM_FRAME_SIZE = [320, 180],
|
|
||||||
DEFAULT_DIMENSIONS = [10, 10],
|
|
||||||
DEFAULT_POSITION = [0, 0],
|
|
||||||
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
|
|
||||||
|
|
||||||
function getDefaultDimensions() {
|
|
||||||
return MINIMUM_FRAME_SIZE.map((min, index) => {
|
|
||||||
return Math.max(
|
|
||||||
Math.ceil(min / gridSize[index]),
|
|
||||||
DEFAULT_DIMENSIONS[index]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasFrameByDefault(type) {
|
|
||||||
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
position = position || DEFAULT_POSITION;
|
|
||||||
let defaultDimensions = getDefaultDimensions();
|
|
||||||
let panel = {
|
|
||||||
width: defaultDimensions[0],
|
|
||||||
height: defaultDimensions[1],
|
|
||||||
x: position[0],
|
|
||||||
y: position[1],
|
|
||||||
hasFrame: hasFrameByDefault(domainObject.type)
|
|
||||||
};
|
|
||||||
|
|
||||||
return panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Object} configuration the subobject view configuration
|
|
||||||
* @param {String} configuration.id the domain object keystring identifier
|
|
||||||
* @param {Boolean} configuration.panel
|
|
||||||
* @param {Object} configuration.domainObject the domain object to observe the changes on
|
|
||||||
* @param {Object} configuration.openmct the openmct object
|
|
||||||
*/
|
|
||||||
constructor({panel, id, ...rest}) {
|
|
||||||
super(rest);
|
|
||||||
this.id = id;
|
|
||||||
this.panel = panel;
|
|
||||||
this.hasFrame = this.hasFrame.bind(this);
|
|
||||||
this.updateStyle(this.position());
|
|
||||||
}
|
|
||||||
|
|
||||||
path() {
|
|
||||||
return "configuration.panels[" + this.id + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
x() {
|
|
||||||
return this.panel.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
y() {
|
|
||||||
return this.panel.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
width() {
|
|
||||||
return this.panel.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
height() {
|
|
||||||
return this.panel.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasFrame() {
|
|
||||||
return this.panel.hasFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
observeProperties() {
|
|
||||||
[
|
|
||||||
'hasFrame',
|
|
||||||
'x',
|
|
||||||
'y',
|
|
||||||
'width',
|
|
||||||
'height'
|
|
||||||
].forEach(property => {
|
|
||||||
this.attachListener(property, newValue => {
|
|
||||||
this.panel[property] = newValue;
|
|
||||||
|
|
||||||
if (property === 'width' || property === 'height' ||
|
|
||||||
property === 'x' || property === 'y') {
|
|
||||||
this.updateStyle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SubobjectViewConfiguration;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,124 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
['./ViewConfiguration'],
|
|
||||||
function (ViewConfiguration) {
|
|
||||||
|
|
||||||
class TelemetryViewConfiguration extends ViewConfiguration {
|
|
||||||
static create(domainObject, position, openmct) {
|
|
||||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
|
|
||||||
|
|
||||||
function getDefaultTelemetryValue(domainObject, openmct) {
|
|
||||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
|
||||||
let valueMetadata = metadata.valuesForHints(['range'])[0];
|
|
||||||
|
|
||||||
if (valueMetadata === undefined) {
|
|
||||||
valueMetadata = metadata.values().filter(values => {
|
|
||||||
return !(values.hints.domain);
|
|
||||||
})[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueMetadata === undefined) {
|
|
||||||
valueMetadata = metadata.values()[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return valueMetadata.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
let alphanumeric = {
|
|
||||||
identifier: domainObject.identifier,
|
|
||||||
x: position[0],
|
|
||||||
y: position[1],
|
|
||||||
width: DEFAULT_TELEMETRY_DIMENSIONS[0],
|
|
||||||
height: DEFAULT_TELEMETRY_DIMENSIONS[1],
|
|
||||||
displayMode: 'all',
|
|
||||||
value: getDefaultTelemetryValue(domainObject, openmct),
|
|
||||||
stroke: "transparent",
|
|
||||||
fill: "",
|
|
||||||
color: "",
|
|
||||||
size: "13px",
|
|
||||||
};
|
|
||||||
|
|
||||||
return alphanumeric;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} configuration the telemetry object view configuration
|
|
||||||
* @param {Object} configuration.alphanumeric
|
|
||||||
* @param {Object} configuration.domainObject the domain object to observe the changes on
|
|
||||||
* @param {Object} configuration.openmct the openmct object
|
|
||||||
*/
|
|
||||||
constructor({alphanumeric, ...rest}) {
|
|
||||||
super(rest);
|
|
||||||
this.alphanumeric = alphanumeric;
|
|
||||||
this.updateStyle(this.position());
|
|
||||||
}
|
|
||||||
|
|
||||||
path() {
|
|
||||||
return "configuration.alphanumerics[" + this.alphanumeric.index + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
x() {
|
|
||||||
return this.alphanumeric.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
y() {
|
|
||||||
return this.alphanumeric.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
width() {
|
|
||||||
return this.alphanumeric.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
height() {
|
|
||||||
return this.alphanumeric.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
observeProperties() {
|
|
||||||
[
|
|
||||||
'displayMode',
|
|
||||||
'value',
|
|
||||||
'fill',
|
|
||||||
'stroke',
|
|
||||||
'color',
|
|
||||||
'size',
|
|
||||||
'x',
|
|
||||||
'y',
|
|
||||||
'width',
|
|
||||||
'height'
|
|
||||||
].forEach(property => {
|
|
||||||
this.attachListener(property, newValue => {
|
|
||||||
this.alphanumeric[property] = newValue;
|
|
||||||
|
|
||||||
if (property === 'width' || property === 'height' ||
|
|
||||||
property === 'x' || property === 'y') {
|
|
||||||
this.updateStyle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return TelemetryViewConfiguration;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,121 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([],
|
|
||||||
function () {
|
|
||||||
class ViewConfiguration {
|
|
||||||
|
|
||||||
constructor({domainObject, openmct, gridSize}) {
|
|
||||||
this.domainObject = domainObject;
|
|
||||||
this.gridSize = gridSize;
|
|
||||||
this.mutatePosition = this.mutatePosition.bind(this);
|
|
||||||
this.listeners = [];
|
|
||||||
this.observe = openmct.objects.observe.bind(openmct.objects);
|
|
||||||
this.mutate = function (path, value) {
|
|
||||||
openmct.objects.mutate(this.domainObject, path, value);
|
|
||||||
}.bind(this);
|
|
||||||
this.newPosition = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
mutatePosition() {
|
|
||||||
this.mutate(this.path() + ".x", this.newPosition.position[0]);
|
|
||||||
this.mutate(this.path() + ".y", this.newPosition.position[1]);
|
|
||||||
this.mutate(this.path() + ".width", this.newPosition.dimensions[0]);
|
|
||||||
this.mutate(this.path() + ".height", this.newPosition.dimensions[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
attachListener(property, callback) {
|
|
||||||
this.listeners.push(this.observe(this.domainObject, this.path() + "." + property, callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
attachListeners() {
|
|
||||||
this.observeProperties();
|
|
||||||
this.listeners.push(this.observe(this.domainObject, '*', function (obj) {
|
|
||||||
this.domainObject = JSON.parse(JSON.stringify(obj));
|
|
||||||
}.bind(this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
removeListeners() {
|
|
||||||
this.listeners.forEach(listener => {
|
|
||||||
listener();
|
|
||||||
});
|
|
||||||
this.listeners = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
position() {
|
|
||||||
return {
|
|
||||||
position: [this.x(), this.y()],
|
|
||||||
dimensions: [this.width(), this.height()]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
path() {
|
|
||||||
throw "NOT IMPLEMENTED;"
|
|
||||||
}
|
|
||||||
|
|
||||||
inspectable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStyle(raw) {
|
|
||||||
if (!raw) {
|
|
||||||
raw = this.position();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.style = {
|
|
||||||
left: (this.gridSize[0] * raw.position[0]) + 'px',
|
|
||||||
top: (this.gridSize[1] * raw.position[1]) + 'px',
|
|
||||||
width: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
|
||||||
height: (this.gridSize[1] * raw.dimensions[1]) + 'px',
|
|
||||||
minWidth: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
|
||||||
minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
observeProperties() {
|
|
||||||
// Not implemented
|
|
||||||
}
|
|
||||||
|
|
||||||
x() {
|
|
||||||
// Not implemented
|
|
||||||
}
|
|
||||||
|
|
||||||
y() {
|
|
||||||
// Not implemented
|
|
||||||
}
|
|
||||||
|
|
||||||
width() {
|
|
||||||
// Not implemented
|
|
||||||
}
|
|
||||||
|
|
||||||
height() {
|
|
||||||
// Not implemented
|
|
||||||
}
|
|
||||||
|
|
||||||
hasFrame() {
|
|
||||||
// Not implemented
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ViewConfiguration;
|
|
||||||
}
|
|
||||||
);
|
|
@ -21,9 +21,13 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<layout-frame :item="item"
|
||||||
|
:grid-size="gridSize"
|
||||||
|
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||||
<div class="c-box-view"
|
<div class="c-box-view"
|
||||||
:style="styleObject">
|
:style="style">
|
||||||
</div>
|
</div>
|
||||||
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -40,24 +44,53 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import LayoutFrame from './LayoutFrame.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
makeDefinition() {
|
||||||
|
return {
|
||||||
|
fill: '#717171',
|
||||||
|
stroke: 'transparent',
|
||||||
|
x: 1,
|
||||||
|
y: 1,
|
||||||
|
width: 10,
|
||||||
|
height: 5
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
|
components: {
|
||||||
|
LayoutFrame
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
item: Object
|
item: Object,
|
||||||
|
gridSize: Array,
|
||||||
|
index: Number,
|
||||||
|
initSelect: Boolean
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
styleObject() {
|
style() {
|
||||||
let element = this.item.config.element;
|
|
||||||
return {
|
return {
|
||||||
backgroundColor: element.fill,
|
backgroundColor: this.item.fill,
|
||||||
border: '1px solid ' + element.stroke
|
border: '1px solid ' + this.item.stroke
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.item.config.attachListeners();
|
let context = {
|
||||||
|
layoutItem: this.item,
|
||||||
|
index: this.index
|
||||||
|
};
|
||||||
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
|
this.$el,
|
||||||
|
context,
|
||||||
|
this.initSelect
|
||||||
|
);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.item.config.removeListeners();
|
if (this.removeSelectable) {
|
||||||
|
this.removeSelectable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -37,15 +37,16 @@
|
|||||||
v-if="gridSize[1] >= 3"
|
v-if="gridSize[1] >= 3"
|
||||||
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
||||||
</div>
|
</div>
|
||||||
<layout-item v-for="(item, index) in layoutItems"
|
<component v-for="(item, index) in layoutItems"
|
||||||
class="l-layout__frame"
|
:is="item.type"
|
||||||
:key="index"
|
|
||||||
:item="item"
|
:item="item"
|
||||||
:gridSize="gridSize"
|
:gridSize="gridSize"
|
||||||
@drilledIn="updateDrilledInState"
|
:initSelect="initSelectIndex === index"
|
||||||
@dragInProgress="updatePosition"
|
:index="index"
|
||||||
@endDrag="endDrag">
|
@drilledIn="updateDrilledIn"
|
||||||
</layout-item>
|
@endDrag="endDrag"
|
||||||
|
>
|
||||||
|
</component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -85,112 +86,74 @@
|
|||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import LayoutItem from './LayoutItem.vue';
|
import uuid from 'uuid';
|
||||||
import TelemetryViewConfiguration from './../TelemetryViewConfiguration.js'
|
|
||||||
import SubobjectViewConfiguration from './../SubobjectViewConfiguration.js'
|
|
||||||
import ElementViewConfiguration from './../ElementViewConfiguration.js'
|
|
||||||
|
|
||||||
const DEFAULT_GRID_SIZE = [10, 10];
|
import SubobjectView from './SubobjectView.vue'
|
||||||
|
import TelemetryView from './TelemetryView.vue'
|
||||||
|
import BoxView from './BoxView.vue'
|
||||||
|
import TextView from './TextView.vue'
|
||||||
|
import LineView from './LineView.vue'
|
||||||
|
import ImageView from './ImageView.vue'
|
||||||
|
|
||||||
|
const ITEM_TYPE_VIEW_MAP = {
|
||||||
|
'subobject-view': SubobjectView,
|
||||||
|
'telemetry-view': TelemetryView,
|
||||||
|
'box-view': BoxView,
|
||||||
|
'line-view': LineView,
|
||||||
|
'text-view': TextView,
|
||||||
|
'image-view': ImageView
|
||||||
|
};
|
||||||
|
|
||||||
|
function getItemDefinition(itemType, ...options) {
|
||||||
|
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
|
||||||
|
|
||||||
|
if (!itemView) {
|
||||||
|
throw `Invalid itemType: ${itemType}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemView.makeDefinition(...options);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
|
||||||
return {
|
return {
|
||||||
gridSize: [],
|
drilledIn: undefined,
|
||||||
layoutItems: [],
|
internalDomainObject: domainObject,
|
||||||
drilledIn: undefined
|
initSelectIndex: undefined
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
gridSize() {
|
||||||
|
return this.internalDomainObject.configuration.layoutGrid;
|
||||||
|
},
|
||||||
|
layoutItems() {
|
||||||
|
return this.internalDomainObject.configuration.items;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
props: ['domainObject'],
|
props: ['domainObject'],
|
||||||
components: {
|
components: ITEM_TYPE_VIEW_MAP,
|
||||||
LayoutItem
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
getAlphanumerics() {
|
addElement(itemType, element) {
|
||||||
let alphanumerics = this.newDomainObject.configuration.alphanumerics || [];
|
this.addItem(itemType + '-view', element);
|
||||||
alphanumerics.forEach((alphanumeric, index) => {
|
|
||||||
alphanumeric.index = index;
|
|
||||||
this.makeTelemetryItem(alphanumeric, false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getElements() {
|
|
||||||
let elements = this.newDomainObject.configuration.elements || [];
|
|
||||||
elements.forEach((element, index) => {
|
|
||||||
element.index = index;
|
|
||||||
this.makeElementItem(element, false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
makeSubobjectItem(panel, initSelect) {
|
|
||||||
let id = this.openmct.objects.makeKeyString(panel.domainObject.identifier);
|
|
||||||
let config = new SubobjectViewConfiguration({
|
|
||||||
domainObject: this.newDomainObject,
|
|
||||||
panel: panel,
|
|
||||||
id: id,
|
|
||||||
openmct: openmct,
|
|
||||||
gridSize: this.gridSize
|
|
||||||
});
|
|
||||||
this.layoutItems.push({
|
|
||||||
id: id,
|
|
||||||
domainObject: panel.domainObject,
|
|
||||||
drilledIn: this.isItemDrilledIn(id),
|
|
||||||
initSelect: initSelect,
|
|
||||||
type: 'subobject-view',
|
|
||||||
config: config
|
|
||||||
});
|
|
||||||
},
|
|
||||||
makeTelemetryItem(alphanumeric, initSelect) {
|
|
||||||
let id = this.openmct.objects.makeKeyString(alphanumeric.identifier);
|
|
||||||
this.openmct.objects.get(id).then(domainObject => {
|
|
||||||
let config = new TelemetryViewConfiguration({
|
|
||||||
domainObject: this.newDomainObject,
|
|
||||||
alphanumeric: alphanumeric,
|
|
||||||
openmct: openmct,
|
|
||||||
gridSize: this.gridSize
|
|
||||||
});
|
|
||||||
this.layoutItems.push({
|
|
||||||
id: id,
|
|
||||||
domainObject: domainObject,
|
|
||||||
initSelect: initSelect,
|
|
||||||
type: 'telemetry-view',
|
|
||||||
config: config
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
makeElementItem(element, initSelect) {
|
|
||||||
let config = new ElementViewConfiguration({
|
|
||||||
domainObject: this.newDomainObject,
|
|
||||||
element: element,
|
|
||||||
openmct: openmct,
|
|
||||||
gridSize: this.gridSize
|
|
||||||
});
|
|
||||||
this.layoutItems.push({
|
|
||||||
initSelect: initSelect,
|
|
||||||
type: element.type + '-view',
|
|
||||||
config: config
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
setSelection(selection) {
|
setSelection(selection) {
|
||||||
if (selection.length === 0) {
|
if (selection.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateDrilledInState();
|
this.updateDrilledIn();
|
||||||
},
|
},
|
||||||
updateDrilledInState(id) {
|
updateDrilledIn(drilledInItem) {
|
||||||
this.drilledIn = id;
|
let identifier = drilledInItem && this.openmct.objects.makeKeyString(drilledInItem.identifier);
|
||||||
this.layoutItems.forEach(function (item) {
|
this.drilledIn = identifier;
|
||||||
|
this.layoutItems.forEach(item => {
|
||||||
if (item.type === 'subobject-view') {
|
if (item.type === 'subobject-view') {
|
||||||
item.drilledIn = item.id === id;
|
item.drilledIn = this.openmct.objects.makeKeyString(item.identifier) === identifier;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isItemDrilledIn(id) {
|
|
||||||
return this.drilledIn === id;
|
|
||||||
},
|
|
||||||
updatePosition(item, newPosition) {
|
|
||||||
item.config.newPosition = newPosition;
|
|
||||||
item.config.updateStyle(newPosition);
|
|
||||||
},
|
|
||||||
bypassSelection($event) {
|
bypassSelection($event) {
|
||||||
if (this.dragInProgress) {
|
if (this.dragInProgress) {
|
||||||
if ($event) {
|
if ($event) {
|
||||||
@ -199,38 +162,41 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
endDrag(item) {
|
endDrag(item, updates) {
|
||||||
this.dragInProgress = true;
|
this.dragInProgress = true;
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
this.dragInProgress = false;
|
this.dragInProgress = false;
|
||||||
}.bind(this), 0);
|
}.bind(this), 0);
|
||||||
// TODO: emit "finishResizing" for view components to mutate position?
|
|
||||||
item.config.mutatePosition();
|
let index = this.layoutItems.indexOf(item);
|
||||||
|
Object.assign(item, updates);
|
||||||
|
this.mutate(`configuration.items[${index}]`, item);
|
||||||
},
|
},
|
||||||
mutate(path, value) {
|
mutate(path, value) {
|
||||||
this.openmct.objects.mutate(this.newDomainObject, path, value);
|
this.openmct.objects.mutate(this.internalDomainObject, path, value);
|
||||||
},
|
},
|
||||||
handleDrop($event) {
|
handleDrop($event) {
|
||||||
|
if (!$event.dataTransfer.types.includes('domainobject')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
|
|
||||||
let domainObject = JSON.parse($event.dataTransfer.getData('domainObject'));
|
let domainObject = JSON.parse($event.dataTransfer.getData('domainobject'));
|
||||||
let elementRect = this.$el.getBoundingClientRect();
|
let elementRect = this.$el.getBoundingClientRect();
|
||||||
this.droppedObjectPosition = [
|
let droppedObjectPosition = [
|
||||||
Math.floor(($event.pageX - elementRect.left) / this.gridSize[0]),
|
Math.floor(($event.pageX - elementRect.left) / this.gridSize[0]),
|
||||||
Math.floor(($event.pageY - elementRect.top) / this.gridSize[1])
|
Math.floor(($event.pageY - elementRect.top) / this.gridSize[1])
|
||||||
];
|
];
|
||||||
|
|
||||||
if (this.isTelemetry(domainObject)) {
|
if (this.isTelemetry(domainObject)) {
|
||||||
this.addAlphanumeric(domainObject, this.droppedObjectPosition);
|
this.addItem('telemetry-view', domainObject, droppedObjectPosition);
|
||||||
} else {
|
} else {
|
||||||
this.checkForDuplicatePanel(domainObject);
|
let identifier = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
}
|
|
||||||
},
|
|
||||||
checkForDuplicatePanel(domainObject) {
|
|
||||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
|
||||||
let panels = this.newDomainObject.configuration.panels;
|
|
||||||
|
|
||||||
if (panels && panels[id]) {
|
if (!this.objectViewMap[identifier]) {
|
||||||
|
this.addItem('subobject-view', domainObject, droppedObjectPosition);
|
||||||
|
} else {
|
||||||
let prompt = this.openmct.overlays.dialog({
|
let prompt = this.openmct.overlays.dialog({
|
||||||
iconClass: 'alert',
|
iconClass: 'alert',
|
||||||
message: "This item is already in layout and will not be added again.",
|
message: "This item is already in layout and will not be added again.",
|
||||||
@ -244,13 +210,7 @@
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
addAlphanumeric(domainObject, position) {
|
|
||||||
let alphanumerics = this.newDomainObject.configuration.alphanumerics || [];
|
|
||||||
let alphanumeric = TelemetryViewConfiguration.create(domainObject, position, this.openmct);
|
|
||||||
alphanumeric.index = alphanumerics.push(alphanumeric) - 1;
|
|
||||||
this.mutate("configuration.alphanumerics", alphanumerics);
|
|
||||||
this.makeTelemetryItem(alphanumeric, true);
|
|
||||||
},
|
},
|
||||||
handleDragOver($event){
|
handleDragOver($event){
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
@ -263,61 +223,56 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addSubobject(domainObject) {
|
addItem(itemType, ...options) {
|
||||||
if (!this.isTelemetry(domainObject)) {
|
let item = getItemDefinition(itemType, this.openmct, this.gridSize, ...options);
|
||||||
let panels = this.newDomainObject.configuration.panels,
|
item.type = itemType;
|
||||||
id = this.openmct.objects.makeKeyString(domainObject.identifier),
|
this.trackItem(item);
|
||||||
panel = panels[id],
|
this.layoutItems.push(item);
|
||||||
mutateObject = false,
|
this.openmct.objects.mutate(this.internalDomainObject, "configuration.items", this.layoutItems);
|
||||||
initSelect = false;
|
this.initSelectIndex = this.layoutItems.length - 1;
|
||||||
|
},
|
||||||
// If the panel doesn't exist, create one and mutate the configuration
|
trackItem(item) {
|
||||||
if (!panel) {
|
if (item.type === "telemetry-view") {
|
||||||
panel = SubobjectViewConfiguration.create(domainObject, this.gridSize, this.droppedObjectPosition);
|
this.telemetryViewMap[this.openmct.objects.makeKeyString(item.identifier)] = true;
|
||||||
initSelect = true;
|
} else if (item.type === "subobject-view") {
|
||||||
this.mutate("configuration.panels[" + id + "]", panel);
|
this.objectViewMap[this.openmct.objects.makeKeyString(item.identifier)] = true;
|
||||||
delete this.droppedObjectPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
panel.domainObject = domainObject;
|
|
||||||
this.makeSubobjectItem(panel, initSelect);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeSubobject() {
|
initializeItems() {
|
||||||
// Not yet implemented
|
this.telemetryViewMap = {};
|
||||||
|
this.objectViewMap = {};
|
||||||
|
this.layoutItems.forEach(this.trackItem);
|
||||||
},
|
},
|
||||||
addElement(type) {
|
addChild(child) {
|
||||||
let elements = this.newDomainObject.configuration.elements || [];
|
let identifier = this.openmct.objects.makeKeyString(child.identifier);
|
||||||
Promise.resolve(ElementViewConfiguration.create(type, this.openmct))
|
if (this.isTelemetry(child)) {
|
||||||
.then(element => {
|
if (!this.telemetryViewMap[identifier]) {
|
||||||
element.index = elements.push(element) - 1;
|
this.addItem('telemetry-view', child);
|
||||||
this.mutate("configuration.elements", elements);
|
}
|
||||||
this.makeElementItem(element, true);
|
} else if (!this.objectViewMap[identifier]) {
|
||||||
});
|
this.addItem('subobject-view', child);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeChild(identifier) {
|
||||||
|
// TODO: implement
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.newDomainObject = this.domainObject;
|
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) {
|
||||||
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;
|
this.internalDomainObject = JSON.parse(JSON.stringify(obj));
|
||||||
|
|
||||||
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));
|
}.bind(this));
|
||||||
|
|
||||||
this.openmct.selection.on('change', this.setSelection);
|
this.openmct.selection.on('change', this.setSelection);
|
||||||
|
this.initializeItems();
|
||||||
this.composition = this.openmct.composition.get(this.newDomainObject);
|
this.composition = this.openmct.composition.get(this.internalDomainObject);
|
||||||
this.composition.on('add', this.addSubobject);
|
this.composition.on('add', this.addChild);
|
||||||
this.composition.on('remove', this.removeSubobject);
|
this.composition.on('remove', this.removeChild);
|
||||||
this.composition.load();
|
this.composition.load();
|
||||||
this.getAlphanumerics();
|
|
||||||
this.getElements();
|
|
||||||
},
|
},
|
||||||
destroyed: function () {
|
destroyed: function () {
|
||||||
this.openmct.off('change', this.setSelection);
|
this.openmct.off('change', this.setSelection);
|
||||||
this.composition.off('add', this.addSubobject);
|
this.composition.off('add', this.addChild);
|
||||||
this.composition.off('remove', this.removeSubobject);
|
this.composition.off('remove', this.removeChild);
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,13 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<layout-frame :item="item"
|
||||||
|
:grid-size="gridSize"
|
||||||
|
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||||
<div class="c-image-view"
|
<div class="c-image-view"
|
||||||
:style="styleObject">
|
:style="style">
|
||||||
</div>
|
</div>
|
||||||
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -42,24 +46,49 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import LayoutFrame from './LayoutFrame.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
makeDefinition(openmct, gridSize, element) {
|
||||||
|
return {
|
||||||
|
stroke: 'transparent',
|
||||||
|
x: 1,
|
||||||
|
y: 1,
|
||||||
|
width: 10,
|
||||||
|
height: 5,
|
||||||
|
url: element.url
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
item: Object
|
item: Object,
|
||||||
|
gridSize: Array,
|
||||||
|
index: Number,
|
||||||
|
initSelect: Boolean
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
LayoutFrame
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
styleObject() {
|
style() {
|
||||||
let element = this.item.config.element;
|
|
||||||
return {
|
return {
|
||||||
backgroundImage: 'url(' + element.url + ')',
|
backgroundImage: 'url(' + this.item.url + ')',
|
||||||
border: '1px solid ' + element.stroke
|
border: '1px solid ' + this.item.stroke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.item.config.attachListeners();
|
let context = {
|
||||||
|
layoutItem: this.item,
|
||||||
|
index: this.index
|
||||||
|
};
|
||||||
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
|
this.$el, context, this.initSelect);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.item.config.removeListeners();
|
if (this.removeSelectable) {
|
||||||
|
this.removeSelectable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -21,14 +21,16 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-frame has-local-controls"
|
<div class="l-layout__frame c-frame has-local-controls"
|
||||||
:style="item.config.style"
|
:class="{
|
||||||
:class="[classObject, {
|
'no-frame': !item.hasFrame,
|
||||||
'u-inspectable': item.config.inspectable()
|
'u-inspectable': inspectable,
|
||||||
}]"
|
'is-drilled-in': item.drilledIn
|
||||||
@dblclick="drill(item.id, $event)">
|
}"
|
||||||
|
:style="style"
|
||||||
|
@dblclick="drill($event)">
|
||||||
|
|
||||||
<component :is="item.type" :item="item" :gridSize="gridSize"></component>
|
<slot></slot>
|
||||||
|
|
||||||
<!-- Drag handles -->
|
<!-- Drag handles -->
|
||||||
<div class="c-frame-edit">
|
<div class="c-frame-edit">
|
||||||
@ -72,12 +74,6 @@
|
|||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SubobjectView from './SubobjectView.vue'
|
|
||||||
import TelemetryView from './TelemetryView.vue'
|
|
||||||
import BoxView from './BoxView.vue'
|
|
||||||
import TextView from './TextView.vue'
|
|
||||||
import LineView from './LineView.vue'
|
|
||||||
import ImageView from './ImageView.vue'
|
|
||||||
import LayoutDrag from './../LayoutDrag'
|
import LayoutDrag from './../LayoutDrag'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -86,46 +82,51 @@
|
|||||||
item: Object,
|
item: Object,
|
||||||
gridSize: Array
|
gridSize: Array
|
||||||
},
|
},
|
||||||
components: {
|
data() {
|
||||||
SubobjectView,
|
return {
|
||||||
TelemetryView,
|
dragPosition: undefined
|
||||||
BoxView,
|
}
|
||||||
TextView,
|
|
||||||
LineView,
|
|
||||||
ImageView
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
classObject: function () {
|
style() {
|
||||||
return {
|
let {x, y, width, height} = this.item;
|
||||||
'is-drilled-in': this.item.drilledIn,
|
|
||||||
'no-frame': !this.item.config.hasFrame()
|
if (this.dragPosition) {
|
||||||
|
[x, y] = this.dragPosition.position;
|
||||||
|
[width, height] = this.dragPosition.dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
left: (this.gridSize[0] * x) + 'px',
|
||||||
|
top: (this.gridSize[1] * y) + 'px',
|
||||||
|
width: (this.gridSize[0] * width) + 'px',
|
||||||
|
height: (this.gridSize[1] * height) + 'px',
|
||||||
|
minWidth: (this.gridSize[0] * width) + 'px',
|
||||||
|
minHeight: (this.gridSize[1] * height) + 'px'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inspectable() {
|
||||||
|
return this.item.type === 'subobject-view' || this.item.type === 'telemetry-view';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
drill(id, $event) {
|
drill($event) {
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.openmct.editor.isEditing()) {
|
if (!this.openmct.editor.isEditing() || !this.item.identifier) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.item.domainObject) {
|
this.openmct.objects.get(this.item.identifier)
|
||||||
|
.then(domainObject => {
|
||||||
|
if (this.openmct.composition.get(domainObject) === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.openmct.composition.get(this.item.domainObject) === undefined) {
|
this.$emit('drilledIn', this.item);
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Disable for fixed position.
|
|
||||||
if (this.item.domainObject.type === 'telemetry.fixed') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('drilledIn', id);
|
|
||||||
},
|
},
|
||||||
updatePosition(event) {
|
updatePosition(event) {
|
||||||
let currentPosition = [event.pageX, event.pageY];
|
let currentPosition = [event.pageX, event.pageY];
|
||||||
@ -138,51 +139,31 @@
|
|||||||
document.body.addEventListener('mousemove', this.continueDrag);
|
document.body.addEventListener('mousemove', this.continueDrag);
|
||||||
document.body.addEventListener('mouseup', this.endDrag);
|
document.body.addEventListener('mouseup', this.endDrag);
|
||||||
|
|
||||||
|
this.dragPosition = {
|
||||||
|
position: [this.item.x, this.item.y],
|
||||||
|
dimensions: [this.item.width, this.item.height]
|
||||||
|
};
|
||||||
this.updatePosition(event);
|
this.updatePosition(event);
|
||||||
this.activeDrag = new LayoutDrag(
|
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
|
||||||
this.item.config.position(),
|
|
||||||
posFactor,
|
|
||||||
dimFactor,
|
|
||||||
this.gridSize
|
|
||||||
);
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
continueDrag(event) {
|
continueDrag(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.updatePosition(event);
|
this.updatePosition(event);
|
||||||
|
this.dragPosition = this.activeDrag.getAdjustedPosition(this.delta);
|
||||||
if (this.activeDrag) {
|
|
||||||
this.$emit(
|
|
||||||
'dragInProgress',
|
|
||||||
this.item,
|
|
||||||
this.activeDrag.getAdjustedPosition(this.delta)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
endDrag(event) {
|
endDrag(event) {
|
||||||
document.body.removeEventListener('mousemove', this.continueDrag);
|
document.body.removeEventListener('mousemove', this.continueDrag);
|
||||||
document.body.removeEventListener('mouseup', this.endDrag);
|
document.body.removeEventListener('mouseup', this.endDrag);
|
||||||
this.continueDrag(event);
|
this.continueDrag(event);
|
||||||
this.$emit('endDrag', this.item);
|
let [x, y] = this.dragPosition.position;
|
||||||
|
let [width, height] = this.dragPosition.dimensions;
|
||||||
|
this.$emit('endDrag', this.item, {x, y, width, height});
|
||||||
|
this.dragPosition = undefined;
|
||||||
this.initialPosition = undefined;
|
this.initialPosition = undefined;
|
||||||
|
this.delta = undefined;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
let context = {
|
|
||||||
item: this.item.domainObject,
|
|
||||||
layoutItem: this.item,
|
|
||||||
addElement: this.$parent.addElement
|
|
||||||
};
|
|
||||||
|
|
||||||
this.removeSelectable = this.openmct.selection.selectable(
|
|
||||||
this.$el,
|
|
||||||
context,
|
|
||||||
this.item.initSelect
|
|
||||||
);
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
this.removeSelectable();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -20,37 +20,212 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="l-layout__frame c-frame has-local-controls no-frame"
|
||||||
<svg :width="gridSize[0] * element.width"
|
:style="style">
|
||||||
:height=" gridSize[1] * element.height">
|
<svg width="100%"
|
||||||
<line :x1=" gridSize[0] * element.x1 + 1"
|
height="100%">
|
||||||
:y1="gridSize[1] * element.y1 + 1 "
|
<line v-bind="linePosition"
|
||||||
:x2="gridSize[0] * element.x2 + 1"
|
:stroke="item.stroke"
|
||||||
:y2=" gridSize[1] * element.y2 + 1 "
|
|
||||||
:stroke="element.stroke"
|
|
||||||
stroke-width="2">
|
stroke-width="2">
|
||||||
</line>
|
</line>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
|
<div class="c-frame-edit">
|
||||||
|
<div class="c-frame-edit__move"
|
||||||
|
@mousedown="startDrag($event)"></div>
|
||||||
|
<div class="c-frame-edit__handle"
|
||||||
|
:class="startHandleClass"
|
||||||
|
@mousedown="startDrag($event, 'start')"></div>
|
||||||
|
<div class="c-frame-edit__handle"
|
||||||
|
:class="endHandleClass"
|
||||||
|
@mousedown="startDrag($event, 'end')"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
const START_HANDLE_QUADRANTS = {
|
||||||
|
1: 'c-frame-edit__handle--sw',
|
||||||
|
2: 'c-frame-edit__handle--se',
|
||||||
|
3: 'c-frame-edit__handle--ne',
|
||||||
|
4: 'c-frame-edit__handle--nw'
|
||||||
|
};
|
||||||
|
|
||||||
|
const END_HANDLE_QUADRANTS = {
|
||||||
|
1: 'c-frame-edit__handle--ne',
|
||||||
|
2: 'c-frame-edit__handle--nw',
|
||||||
|
3: 'c-frame-edit__handle--sw',
|
||||||
|
4: 'c-frame-edit__handle--se'
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
makeDefinition() {
|
||||||
|
return {
|
||||||
|
x: 5,
|
||||||
|
y: 10,
|
||||||
|
x2: 10,
|
||||||
|
y2: 5,
|
||||||
|
stroke: '#717171'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
gridSize: Array
|
gridSize: Array,
|
||||||
|
initSelect: Boolean,
|
||||||
|
index: Number,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dragPosition: undefined
|
||||||
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
element() {
|
position() {
|
||||||
return this.item.config.element;
|
let {x, y, x2, y2} = this.item;
|
||||||
|
if (this.dragPosition) {
|
||||||
|
({x, y, x2, y2} = this.dragPosition);
|
||||||
|
}
|
||||||
|
return {x, y, x2, y2};
|
||||||
|
},
|
||||||
|
style() {
|
||||||
|
let {x, y, x2, y2} = this.position;
|
||||||
|
let width = this.gridSize[0] * Math.abs(x - x2);
|
||||||
|
let height = this.gridSize[1] * Math.abs(y - y2);
|
||||||
|
let left = this.gridSize[0] * Math.min(x, x2);
|
||||||
|
let top = this.gridSize[1] * Math.min(y, y2);
|
||||||
|
return {
|
||||||
|
left: `${left}px`,
|
||||||
|
top: `${top}px`,
|
||||||
|
width: `${width}px`,
|
||||||
|
height: `${height}px`,
|
||||||
|
minWidth: `${width}px`,
|
||||||
|
minHeight: `${height}px`,
|
||||||
|
position: 'absolute'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
startHandleClass() {
|
||||||
|
return START_HANDLE_QUADRANTS[this.vectorQuadrant];
|
||||||
|
},
|
||||||
|
endHandleClass() {
|
||||||
|
return END_HANDLE_QUADRANTS[this.vectorQuadrant];
|
||||||
|
},
|
||||||
|
vectorQuadrant() {
|
||||||
|
let {x, y, x2, y2} = this.position;
|
||||||
|
if (x2 > x) {
|
||||||
|
if (y2 < y) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (y2 < y) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 3;
|
||||||
|
},
|
||||||
|
linePosition() {
|
||||||
|
if (this.vectorQuadrant === 1) {
|
||||||
|
return {
|
||||||
|
x1: '0%',
|
||||||
|
y1: '100%',
|
||||||
|
x2: '100%',
|
||||||
|
y2: '0%'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (this.vectorQuadrant === 4) {
|
||||||
|
return {
|
||||||
|
x1: '0%',
|
||||||
|
y1: '0%',
|
||||||
|
x2: '100%',
|
||||||
|
y2: '100%'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (this.vectorQuadrant === 2) {
|
||||||
|
return {
|
||||||
|
x1: '0%',
|
||||||
|
y1: '0%',
|
||||||
|
x2: '100%',
|
||||||
|
y2: '100%'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (this.vectorQuadrant === 3) {
|
||||||
|
return {
|
||||||
|
x1: '100%',
|
||||||
|
y1: '0%',
|
||||||
|
x2: '0%',
|
||||||
|
y2: '100%'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
startDrag(event, position) {
|
||||||
|
this.dragging = position;
|
||||||
|
document.body.addEventListener('mousemove', this.continueDrag);
|
||||||
|
document.body.addEventListener('mouseup', this.endDrag);
|
||||||
|
this.startPosition = [event.pageX, event.pageY];
|
||||||
|
this.dragPosition = {
|
||||||
|
x: this.item.x,
|
||||||
|
y: this.item.y,
|
||||||
|
x2: this.item.x2,
|
||||||
|
y2: this.item.y2
|
||||||
|
};
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
continueDrag(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
let pxDeltaX = this.startPosition[0] - event.pageX;
|
||||||
|
let pxDeltaY = this.startPosition[1] - event.pageY;
|
||||||
|
this.dragPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
|
||||||
|
},
|
||||||
|
endDrag(event) {
|
||||||
|
document.body.removeEventListener('mousemove', this.continueDrag);
|
||||||
|
document.body.removeEventListener('mouseup', this.endDrag);
|
||||||
|
let {x, y, x2, y2} = this.dragPosition;
|
||||||
|
this.$emit('endDrag', this.item, {x, y, x2, y2});
|
||||||
|
this.dragPosition = undefined;
|
||||||
|
this.dragging = undefined;
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
calculateDragPosition(pxDeltaX, pxDeltaY) {
|
||||||
|
let gridDeltaX = Math.round(pxDeltaX / this.gridSize[0]);
|
||||||
|
let gridDeltaY = Math.round(pxDeltaY / this.gridSize[0]); // TODO: should this be gridSize[1]?
|
||||||
|
let {x, y, x2, y2} = this.item;
|
||||||
|
let dragPosition = {x, y, x2, y2};
|
||||||
|
if (this.dragging === 'start') {
|
||||||
|
dragPosition.x -= gridDeltaX;
|
||||||
|
dragPosition.y -= gridDeltaY;
|
||||||
|
} else if (this.dragging === 'end') {
|
||||||
|
dragPosition.x2 -= gridDeltaX;
|
||||||
|
dragPosition.y2 -= gridDeltaY;
|
||||||
|
} else {
|
||||||
|
// dragging entire line.
|
||||||
|
dragPosition.x -= gridDeltaX;
|
||||||
|
dragPosition.y -= gridDeltaY;
|
||||||
|
dragPosition.x2 -= gridDeltaX;
|
||||||
|
dragPosition.y2 -= gridDeltaY;
|
||||||
|
}
|
||||||
|
return dragPosition;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.item.config.attachListeners();
|
let context = {
|
||||||
|
layoutItem: this.item,
|
||||||
|
index: this.index
|
||||||
|
};
|
||||||
|
|
||||||
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
|
this.$el,
|
||||||
|
context,
|
||||||
|
this.initSelect
|
||||||
|
);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.item.config.removeListeners();
|
if (this.removeSelectable) {
|
||||||
|
this.removeSelectable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -20,12 +20,16 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
<template>
|
<template>
|
||||||
|
<layout-frame :item="item"
|
||||||
|
:grid-size="gridSize"
|
||||||
|
@endDrag="(item, updates) => $emit('endDrag', item, updates)"
|
||||||
|
@drilledIn="item => $emit('drilledIn', item)">
|
||||||
<div class="u-contents">
|
<div class="u-contents">
|
||||||
<div class="c-so-view__header">
|
<div class="c-so-view__header">
|
||||||
<div class="c-so-view__header__start">
|
<div class="c-so-view__header__start">
|
||||||
<div class="c-so-view__header__name"
|
<div class="c-so-view__header__name"
|
||||||
:class="cssClass">
|
:class="cssClass">
|
||||||
{{ item.domainObject.name }}
|
{{ domainObject && domainObject.name }}
|
||||||
</div>
|
</div>
|
||||||
<context-menu-drop-down
|
<context-menu-drop-down
|
||||||
:object-path="objectPath">
|
:object-path="objectPath">
|
||||||
@ -36,8 +40,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<object-view class="c-so-view__object-view"
|
<object-view class="c-so-view__object-view"
|
||||||
:object="item.domainObject"></object-view>
|
:object="domainObject"></object-view>
|
||||||
</div>
|
</div>
|
||||||
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -101,34 +106,82 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ObjectView from '../../../ui/components/layout/ObjectView.vue';
|
import ObjectView from '../../../ui/components/layout/ObjectView.vue'
|
||||||
import contextMenuDropDown from './contextMenuDropDown.vue';
|
import ContextMenuDropDown from './contextMenuDropDown.vue';
|
||||||
|
import LayoutFrame from './LayoutFrame.vue'
|
||||||
|
|
||||||
|
const MINIMUM_FRAME_SIZE = [320, 180],
|
||||||
|
DEFAULT_DIMENSIONS = [10, 10],
|
||||||
|
DEFAULT_POSITION = [1, 1],
|
||||||
|
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
|
||||||
|
|
||||||
|
function getDefaultDimensions(gridSize) {
|
||||||
|
return MINIMUM_FRAME_SIZE.map((min, index) => {
|
||||||
|
return Math.max(
|
||||||
|
Math.ceil(min / gridSize[index]),
|
||||||
|
DEFAULT_DIMENSIONS[index]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasFrameByDefault(type) {
|
||||||
|
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
makeDefinition(openmct, gridSize, domainObject, position) {
|
||||||
|
let defaultDimensions = getDefaultDimensions(gridSize);
|
||||||
|
position = position || DEFAULT_POSITION;
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: defaultDimensions[0],
|
||||||
|
height: defaultDimensions[1],
|
||||||
|
x: position[0],
|
||||||
|
y: position[1],
|
||||||
|
identifier: domainObject.identifier,
|
||||||
|
hasFrame: hasFrameByDefault(domainObject.type)
|
||||||
|
};
|
||||||
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
item: Object
|
item: Object,
|
||||||
|
gridSize: Array,
|
||||||
|
initSelect: Boolean,
|
||||||
|
index: Number
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
domainObject: undefined,
|
||||||
|
cssClass: undefined,
|
||||||
|
objectPath: []
|
||||||
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ObjectView,
|
ObjectView,
|
||||||
contextMenuDropDown
|
ContextMenuDropDown,
|
||||||
|
LayoutFrame
|
||||||
},
|
},
|
||||||
data() {
|
methods: {
|
||||||
let type = this.openmct.types.get(this.item.domainObject.type);
|
setObject(domainObject) {
|
||||||
|
this.domainObject = domainObject;
|
||||||
return {
|
this.objectPath = [this.domainObject].concat(this.openmct.router.path);
|
||||||
cssClass: type.definition.cssClass,
|
this.cssClass = this.openmct.types.get(this.domainObject.type).definition.cssClass;
|
||||||
objectPath: [this.item.domainObject].concat(this.openmct.router.path)
|
let context = {
|
||||||
|
item: domainObject,
|
||||||
|
layoutItem: this.item,
|
||||||
|
index: this.index
|
||||||
|
};
|
||||||
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
|
this.$el, context, this.initSelect);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.item.config) {
|
this.openmct.objects.get(this.item.identifier)
|
||||||
this.item.config.attachListeners();
|
.then(this.setObject);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
if (this.item.config) {
|
if (this.removeSelectable) {
|
||||||
this.item.config.removeListeners();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,11 +21,15 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<layout-frame :item="item"
|
||||||
|
:grid-size="gridSize"
|
||||||
|
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||||
<div class="c-telemetry-view"
|
<div class="c-telemetry-view"
|
||||||
:style="styleObject">
|
:style="styleObject"
|
||||||
|
v-if="domainObject">
|
||||||
<div v-if="showLabel"
|
<div v-if="showLabel"
|
||||||
class="c-telemetry-view__label">
|
class="c-telemetry-view__label">
|
||||||
<div class="c-telemetry-view__label-text">{{ item.domainObject.name }}</div>
|
<div class="c-telemetry-view__label-text">{{ domainObject.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showValue"
|
<div v-if="showValue"
|
||||||
@ -35,6 +39,7 @@
|
|||||||
<div class="c-telemetry-view__value-text">{{ telemetryValue }}</div>
|
<div class="c-telemetry-view__value-text">{{ telemetryValue }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -72,37 +77,65 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import LayoutFrame from './LayoutFrame.vue'
|
||||||
|
|
||||||
|
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
|
||||||
|
DEFAULT_POSITION = [1, 1];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
makeDefinition(openmct, gridSize, domainObject, position) {
|
||||||
|
let metadata = openmct.telemetry.getMetadata(domainObject);
|
||||||
|
position = position || DEFAULT_POSITION;
|
||||||
|
|
||||||
|
return {
|
||||||
|
identifier: domainObject.identifier,
|
||||||
|
x: position[0],
|
||||||
|
y: position[1],
|
||||||
|
width: DEFAULT_TELEMETRY_DIMENSIONS[0],
|
||||||
|
height: DEFAULT_TELEMETRY_DIMENSIONS[1],
|
||||||
|
displayMode: 'all',
|
||||||
|
value: metadata.getDefaultDisplayValue(),
|
||||||
|
stroke: "transparent",
|
||||||
|
fill: "",
|
||||||
|
color: "",
|
||||||
|
size: "13px",
|
||||||
|
};
|
||||||
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
item: Object
|
item: Object,
|
||||||
|
gridSize: Array,
|
||||||
|
initSelect: Boolean,
|
||||||
|
index: Number
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
LayoutFrame
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showLabel() {
|
showLabel() {
|
||||||
let displayMode = this.item.config.alphanumeric.displayMode;
|
let displayMode = this.item.displayMode;
|
||||||
return displayMode === 'all' || displayMode === 'label';
|
return displayMode === 'all' || displayMode === 'label';
|
||||||
},
|
},
|
||||||
showValue() {
|
showValue() {
|
||||||
let displayMode = this.item.config.alphanumeric.displayMode;
|
let displayMode = this.item.displayMode;
|
||||||
return displayMode === 'all' || displayMode === 'value';
|
return displayMode === 'all' || displayMode === 'value';
|
||||||
},
|
},
|
||||||
styleObject() {
|
styleObject() {
|
||||||
let alphanumeric = this.item.config.alphanumeric;
|
|
||||||
return {
|
return {
|
||||||
backgroundColor: alphanumeric.fill,
|
backgroundColor: this.item.fill,
|
||||||
borderColor: alphanumeric.stroke,
|
borderColor: this.item.stroke,
|
||||||
color: alphanumeric.color,
|
color: this.item.color,
|
||||||
fontSize: alphanumeric.size
|
fontSize: this.item.size
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fieldName() {
|
fieldName() {
|
||||||
return this.valueMetadata.name;
|
return this.valueMetadata && this.valueMetadata.name;
|
||||||
},
|
},
|
||||||
valueMetadata() {
|
valueMetadata() {
|
||||||
return this.metadata.value(this.item.config.alphanumeric.value);
|
return this.datum && this.metadata.value(this.item.value);
|
||||||
},
|
},
|
||||||
valueFormatter() {
|
valueFormatter() {
|
||||||
return this.formats[this.item.config.alphanumeric.value];
|
return this.formats[this.item.value];
|
||||||
},
|
},
|
||||||
telemetryValue() {
|
telemetryValue() {
|
||||||
if (!this.datum) {
|
if (!this.datum) {
|
||||||
@ -122,8 +155,9 @@
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
datum: {},
|
datum: undefined,
|
||||||
formats: {}
|
formats: undefined,
|
||||||
|
domainObject: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -134,7 +168,7 @@
|
|||||||
end: bounds.end,
|
end: bounds.end,
|
||||||
size: 1
|
size: 1
|
||||||
};
|
};
|
||||||
this.openmct.telemetry.request(this.item.domainObject, options)
|
this.openmct.telemetry.request(this.domainObject, options)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
this.updateView(data[data.length - 1]);
|
this.updateView(data[data.length - 1]);
|
||||||
@ -142,7 +176,7 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
subscribeToObject() {
|
subscribeToObject() {
|
||||||
this.subscription = this.openmct.telemetry.subscribe(this.item.domainObject, function (datum) {
|
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
|
||||||
if (this.openmct.time.clock() !== undefined) {
|
if (this.openmct.time.clock() !== undefined) {
|
||||||
this.updateView(datum);
|
this.updateView(datum);
|
||||||
}
|
}
|
||||||
@ -160,26 +194,38 @@
|
|||||||
refreshData(bounds, isTick) {
|
refreshData(bounds, isTick) {
|
||||||
if (!isTick) {
|
if (!isTick) {
|
||||||
this.datum = undefined;
|
this.datum = undefined;
|
||||||
this.requestHistoricalData(this.item.domainObject);
|
this.requestHistoricalData(this.domainObject);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
setObject(domainObject) {
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(this.item.domainObject);
|
this.domainObject = domainObject;
|
||||||
},
|
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
mounted() {
|
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
|
||||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.item.domainObject);
|
|
||||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||||
|
|
||||||
this.requestHistoricalData();
|
this.requestHistoricalData();
|
||||||
this.subscribeToObject();
|
this.subscribeToObject();
|
||||||
|
|
||||||
this.item.config.attachListeners();
|
let context = {
|
||||||
|
item: domainObject,
|
||||||
|
layoutItem: this.item,
|
||||||
|
index: this.index
|
||||||
|
};
|
||||||
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
|
this.$el, context, this.initSelect);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.openmct.objects.get(this.item.identifier)
|
||||||
|
.then(this.setObject);
|
||||||
this.openmct.time.on("bounds", this.refreshData);
|
this.openmct.time.on("bounds", this.refreshData);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.removeSubscription();
|
this.removeSubscription();
|
||||||
this.item.config.removeListeners();
|
|
||||||
|
if (this.removeSelectable) {
|
||||||
|
this.removeSelectable();
|
||||||
|
}
|
||||||
|
|
||||||
this.openmct.time.off("bounds", this.refreshData);
|
this.openmct.time.off("bounds", this.refreshData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,14 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<layout-frame :item="item"
|
||||||
|
:grid-size="gridSize"
|
||||||
|
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||||
<div class="c-text-view"
|
<div class="c-text-view"
|
||||||
:style="styleObject">
|
:style="style">
|
||||||
{{ item.config.element.text }}
|
{{ item.text }}
|
||||||
</div>
|
</div>
|
||||||
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -42,26 +46,55 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import LayoutFrame from './LayoutFrame.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
makeDefinition(openmct, gridSize, element) {
|
||||||
|
return {
|
||||||
|
fill: 'transparent',
|
||||||
|
stroke: 'transparent',
|
||||||
|
size: '13px',
|
||||||
|
color: '',
|
||||||
|
x: 1,
|
||||||
|
y: 1,
|
||||||
|
width: 10,
|
||||||
|
height: 5,
|
||||||
|
text: element.text
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
item: Object
|
item: Object,
|
||||||
|
gridSize: Array,
|
||||||
|
index: Number,
|
||||||
|
initSelect: Boolean
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
LayoutFrame
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
styleObject() {
|
style() {
|
||||||
let element = this.item.config.element;
|
|
||||||
return {
|
return {
|
||||||
backgroundColor: element.fill,
|
backgroundColor: this.item.fill,
|
||||||
borderColor: element.stroke,
|
borderColor: this.item.stroke,
|
||||||
color: element.color,
|
color: this.item.color,
|
||||||
fontSize: element.size
|
fontSize: this.item.size
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.item.config.attachListeners();
|
let context = {
|
||||||
|
layoutItem: this.item,
|
||||||
|
index: this.index
|
||||||
|
};
|
||||||
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
|
this.$el, context, this.initSelect);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.item.config.removeListeners();
|
if (this.removeSelectable) {
|
||||||
|
this.removeSelectable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -16,8 +16,12 @@ export default {
|
|||||||
Object.assign(oldObject, newObject);
|
Object.assign(oldObject, newObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.objectPath.forEach(object => this.$once('hook:destroy',
|
this.objectPath.forEach(object => {
|
||||||
|
if (object) {
|
||||||
|
this.$once('hook:destroy',
|
||||||
this.openmct.objects.observe(object, '*', updateObject.bind(this, object)))
|
this.openmct.objects.observe(object, '*', updateObject.bind(this, object)))
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
|
@ -14,7 +14,7 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return '#/browse/' + this.objectPath
|
return '#/browse/' + this.objectPath
|
||||||
.map(o => this.openmct.objects.makeKeyString(o.identifier))
|
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
|
||||||
.reverse()
|
.reverse()
|
||||||
.join('/');
|
.join('/');
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
import toolbarSeparator from './components/toolbar-separator.vue';
|
import toolbarSeparator from './components/toolbar-separator.vue';
|
||||||
import toolbarToggleButton from './components/toolbar-toggle-button.vue';
|
import toolbarToggleButton from './components/toolbar-toggle-button.vue';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'lodash'],
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
toolbarButton,
|
toolbarButton,
|
||||||
toolbarColorPicker,
|
toolbarColorPicker,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user