Compare commits
16 Commits
select-tab
...
tree-pagin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9015028f81 | ||
|
|
3dc6dac12d | ||
|
|
aafe524454 | ||
|
|
e84ade1752 | ||
|
|
3b094e43e3 | ||
|
|
e6a7b4ed6c | ||
|
|
97230bb21f | ||
|
|
768d99d928 | ||
|
|
c760190a29 | ||
|
|
38d78990da | ||
|
|
e0d2c85740 | ||
|
|
29ab9bdf02 | ||
|
|
0b433ccb50 | ||
|
|
e2ecd106f6 | ||
|
|
7debe89f8b | ||
|
|
a396f50acd |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "0.14.0-SNAPSHOT",
|
"version": "1.0.0-beta",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ define([
|
|||||||
"./res/templates/search.html",
|
"./res/templates/search.html",
|
||||||
"./res/templates/search-menu.html",
|
"./res/templates/search-menu.html",
|
||||||
"raw-loader!./src/services/GenericSearchWorker.js",
|
"raw-loader!./src/services/GenericSearchWorker.js",
|
||||||
|
"raw-loader!./src/services/BareBonesSearchWorker.js",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
SearchController,
|
SearchController,
|
||||||
@@ -39,6 +40,7 @@ define([
|
|||||||
searchTemplate,
|
searchTemplate,
|
||||||
searchMenuTemplate,
|
searchMenuTemplate,
|
||||||
searchWorkerText,
|
searchWorkerText,
|
||||||
|
BareBonesSearchWorkerText,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -115,6 +117,10 @@ define([
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"workers": [
|
"workers": [
|
||||||
|
{
|
||||||
|
"key": "bareBonesSearchWorker",
|
||||||
|
"scriptText": BareBonesSearchWorkerText
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "genericSearchWorker",
|
"key": "genericSearchWorker",
|
||||||
"scriptText": searchWorkerText
|
"scriptText": searchWorkerText
|
||||||
|
|||||||
80
platform/search/src/services/BareBonesSearchWorker.js
Normal file
80
platform/search/src/services/BareBonesSearchWorker.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*global self*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining GenericSearchWorker. Created by shale on 07/21/2015.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
// An array of objects composed of domain object IDs and models
|
||||||
|
// {id: domainObject's ID, model: domainObject's model}
|
||||||
|
var indexedItems = [];
|
||||||
|
|
||||||
|
function indexItem(id, model) {
|
||||||
|
indexedItems.push({
|
||||||
|
id: id,
|
||||||
|
name: model.name.toLowerCase()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets search results from the indexedItems based on provided search
|
||||||
|
* input. Returns matching results from indexedItems
|
||||||
|
*
|
||||||
|
* @param data An object which contains:
|
||||||
|
* * input: The original string which we are searching with
|
||||||
|
* * maxResults: The maximum number of search results desired
|
||||||
|
* * queryId: an id identifying this query, will be returned.
|
||||||
|
*/
|
||||||
|
function search(data) {
|
||||||
|
// This results dictionary will have domain object ID keys which
|
||||||
|
// point to the value the domain object's score.
|
||||||
|
var results,
|
||||||
|
input = data.input.trim().toLowerCase(),
|
||||||
|
message = {
|
||||||
|
request: 'search',
|
||||||
|
results: {},
|
||||||
|
total: 0,
|
||||||
|
queryId: data.queryId
|
||||||
|
};
|
||||||
|
|
||||||
|
results = indexedItems.filter((indexedItem) => {
|
||||||
|
return indexedItem.name.includes(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
message.total = results.length;
|
||||||
|
message.results = results
|
||||||
|
.slice(0, data.maxResults);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onmessage = function (event) {
|
||||||
|
if (event.data.request === 'index') {
|
||||||
|
indexItem(event.data.id, event.data.model);
|
||||||
|
} else if (event.data.request === 'search') {
|
||||||
|
self.postMessage(search(event.data));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}());
|
||||||
@@ -58,6 +58,8 @@ define([
|
|||||||
|
|
||||||
this.pendingQueries = {};
|
this.pendingQueries = {};
|
||||||
|
|
||||||
|
this.useBareBones = true;
|
||||||
|
|
||||||
this.worker = this.startWorker(workerService);
|
this.worker = this.startWorker(workerService);
|
||||||
this.indexOnMutation(topic);
|
this.indexOnMutation(topic);
|
||||||
|
|
||||||
@@ -101,8 +103,14 @@ define([
|
|||||||
* @returns worker the created search worker.
|
* @returns worker the created search worker.
|
||||||
*/
|
*/
|
||||||
GenericSearchProvider.prototype.startWorker = function (workerService) {
|
GenericSearchProvider.prototype.startWorker = function (workerService) {
|
||||||
var worker = workerService.run('genericSearchWorker'),
|
var provider = this,
|
||||||
provider = this;
|
worker;
|
||||||
|
|
||||||
|
if (this.useBareBones) {
|
||||||
|
worker = workerService.run('bareBonesSearchWorker');
|
||||||
|
} else {
|
||||||
|
worker = workerService.run('genericSearchWorker');
|
||||||
|
}
|
||||||
|
|
||||||
worker.addEventListener('message', function (messageEvent) {
|
worker.addEventListener('message', function (messageEvent) {
|
||||||
provider.onWorkerMessage(messageEvent);
|
provider.onWorkerMessage(messageEvent);
|
||||||
@@ -242,7 +250,22 @@ define([
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pendingQuery = this.pendingQueries[event.data.queryId],
|
var pendingQuery,
|
||||||
|
modelResults;
|
||||||
|
|
||||||
|
if (this.useBareBones) {
|
||||||
|
pendingQuery = this.pendingQueries[event.data.queryId];
|
||||||
|
modelResults = {
|
||||||
|
total: event.data.total
|
||||||
|
};
|
||||||
|
|
||||||
|
modelResults.hits = event.data.results.map(function (hit) {
|
||||||
|
return {
|
||||||
|
id: hit.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pendingQuery = this.pendingQueries[event.data.queryId];
|
||||||
modelResults = {
|
modelResults = {
|
||||||
total: event.data.total
|
total: event.data.total
|
||||||
};
|
};
|
||||||
@@ -254,6 +277,7 @@ define([
|
|||||||
score: hit.matchCount
|
score: hit.matchCount
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pendingQuery.resolve(modelResults);
|
pendingQuery.resolve(modelResults);
|
||||||
delete this.pendingQueries[event.data.queryId];
|
delete this.pendingQueries[event.data.queryId];
|
||||||
|
|||||||
@@ -144,6 +144,8 @@
|
|||||||
message.results = results
|
message.results = results
|
||||||
.slice(0, data.maxResults);
|
.slice(0, data.maxResults);
|
||||||
|
|
||||||
|
console.log(message);
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const OUTSIDE_EDIT_PATH_BLACKLIST = ["copy", "follow", "properties", "move", "li
|
|||||||
export default class LegacyContextMenuAction {
|
export default class LegacyContextMenuAction {
|
||||||
constructor(openmct, LegacyAction) {
|
constructor(openmct, LegacyAction) {
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
|
this.key = LegacyAction.definition.key;
|
||||||
this.name = LegacyAction.definition.name;
|
this.name = LegacyAction.definition.name;
|
||||||
this.description = LegacyAction.definition.description;
|
this.description = LegacyAction.definition.description;
|
||||||
this.cssClass = LegacyAction.definition.cssClass;
|
this.cssClass = LegacyAction.definition.cssClass;
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ class ContextMenuAPI {
|
|||||||
* a single sentence or short paragraph) of this kind of view
|
* a single sentence or short paragraph) of this kind of view
|
||||||
* @property {string} cssClass the CSS class to apply to labels for this
|
* @property {string} cssClass the CSS class to apply to labels for this
|
||||||
* view (to add icons, for instance)
|
* view (to add icons, for instance)
|
||||||
|
* @property {string} key unique key to identify the context menu action
|
||||||
|
* (used in custom context menu eg table rows, to identify which actions to include)
|
||||||
|
* @property {boolean} hideInDefaultMenu optional flag to hide action from showing in the default context menu (tree item)
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* @method appliesTo
|
* @method appliesTo
|
||||||
@@ -72,12 +75,21 @@ class ContextMenuAPI {
|
|||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_showContextMenuForObjectPath(objectPath, x, y) {
|
_showContextMenuForObjectPath(objectPath, x, y, actionsToBeIncluded) {
|
||||||
|
|
||||||
let applicableActions = this._allActions.filter((action) => {
|
let applicableActions = this._allActions.filter((action) => {
|
||||||
|
|
||||||
|
if (actionsToBeIncluded) {
|
||||||
|
if (action.appliesTo === undefined && actionsToBeIncluded.includes(action.key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return action.appliesTo(objectPath, actionsToBeIncluded) && actionsToBeIncluded.includes(action.key);
|
||||||
|
} else {
|
||||||
if (action.appliesTo === undefined) {
|
if (action.appliesTo === undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return action.appliesTo(objectPath);
|
return action.appliesTo(objectPath) && !action.hideInDefaultMenu;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this._activeContextMenu) {
|
if (this._activeContextMenu) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ define([
|
|||||||
canEdit: function (domainObject) {
|
canEdit: function (domainObject) {
|
||||||
return domainObject.type === 'LadTableSet';
|
return domainObject.type === 'LadTableSet';
|
||||||
},
|
},
|
||||||
view: function (domainObject) {
|
view: function (domainObject, isEditing, objectPath) {
|
||||||
let component;
|
let component;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -49,7 +49,8 @@ define([
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject
|
domainObject,
|
||||||
|
objectPath
|
||||||
},
|
},
|
||||||
el: element,
|
el: element,
|
||||||
template: '<lad-table-set></lad-table-set>'
|
template: '<lad-table-set></lad-table-set>'
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ define([
|
|||||||
canEdit: function (domainObject) {
|
canEdit: function (domainObject) {
|
||||||
return domainObject.type === 'LadTable';
|
return domainObject.type === 'LadTable';
|
||||||
},
|
},
|
||||||
view: function (domainObject) {
|
view: function (domainObject, isEditing, objectPath) {
|
||||||
let component;
|
let component;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -49,7 +49,8 @@ define([
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject
|
domainObject,
|
||||||
|
objectPath
|
||||||
},
|
},
|
||||||
el: element,
|
el: element,
|
||||||
template: '<lad-table-component></lad-table-component>'
|
template: '<lad-table-component></lad-table-component>'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<tr>
|
<tr @contextmenu.prevent="showContextMenu">
|
||||||
<td>{{name}}</td>
|
<td>{{name}}</td>
|
||||||
<td>{{timestamp}}</td>
|
<td>{{timestamp}}</td>
|
||||||
<td :class="valueClass">
|
<td :class="valueClass">
|
||||||
@@ -35,15 +36,25 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
const CONTEXT_MENU_ACTIONS = [
|
||||||
|
'viewHistoricalData',
|
||||||
|
'remove'
|
||||||
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct', 'objectPath'],
|
||||||
props: ['domainObject'],
|
props: ['domainObject'],
|
||||||
data() {
|
data() {
|
||||||
|
let currentObjectPath = this.objectPath.slice();
|
||||||
|
currentObjectPath.unshift(this.domainObject);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: this.domainObject.name,
|
name: this.domainObject.name,
|
||||||
timestamp: '---',
|
timestamp: '---',
|
||||||
value: '---',
|
value: '---',
|
||||||
valueClass: ''
|
valueClass: '',
|
||||||
|
currentObjectPath
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -73,11 +84,15 @@ export default {
|
|||||||
.request(this.domainObject, {strategy: 'latest'})
|
.request(this.domainObject, {strategy: 'latest'})
|
||||||
.then((array) => this.updateValues(array[array.length - 1]));
|
.then((array) => this.updateValues(array[array.length - 1]));
|
||||||
|
|
||||||
|
},
|
||||||
|
showContextMenu(event) {
|
||||||
|
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||||
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
|
||||||
this.limitEvaluator = openmct
|
this.limitEvaluator = openmct
|
||||||
.telemetry
|
.telemetry
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import lodash from 'lodash';
|
|||||||
import LadRow from './LADRow.vue';
|
import LadRow from './LADRow.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||||
components: {
|
components: {
|
||||||
LadRow
|
LadRow
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,13 +46,14 @@ define([
|
|||||||
|
|
||||||
return selection.every(isTelemetryObject);
|
return selection.every(isTelemetryObject);
|
||||||
},
|
},
|
||||||
view: function (selection) {
|
view: function (domainObject, isEditing, objectPath) {
|
||||||
let component;
|
let component;
|
||||||
return {
|
return {
|
||||||
show: function (element) {
|
show: function (element) {
|
||||||
component = new Vue({
|
component = new Vue({
|
||||||
provide: {
|
provide: {
|
||||||
openmct
|
openmct,
|
||||||
|
objectPath
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
AlphanumericFormatView: AlphanumericFormatView.default
|
AlphanumericFormatView: AlphanumericFormatView.default
|
||||||
|
|||||||
@@ -202,7 +202,7 @@
|
|||||||
return selectionPath && selectionPath.length > 1 && !singleSelectedLine;
|
return selectionPath && selectionPath.length > 1 && !singleSelectedLine;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'options'],
|
inject: ['openmct', 'options', 'objectPath'],
|
||||||
props: ['domainObject'],
|
props: ['domainObject'],
|
||||||
components: components,
|
components: components,
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
@endMove="() => $emit('endMove')">
|
@endMove="() => $emit('endMove')">
|
||||||
<object-frame v-if="domainObject"
|
<object-frame v-if="domainObject"
|
||||||
:domain-object="domainObject"
|
:domain-object="domainObject"
|
||||||
:object-path="objectPath"
|
:object-path="currentObjectPath"
|
||||||
:has-frame="item.hasFrame"
|
:has-frame="item.hasFrame"
|
||||||
:show-edit-view="false"
|
:show-edit-view="false"
|
||||||
ref="objectFrame">
|
ref="objectFrame">
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
hasFrame: hasFrameByDefault(domainObject.type)
|
hasFrame: hasFrameByDefault(domainObject.type)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct', 'objectPath'],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
gridSize: Array,
|
gridSize: Array,
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
domainObject: undefined,
|
domainObject: undefined,
|
||||||
objectPath: []
|
currentObjectPath: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
methods: {
|
methods: {
|
||||||
setObject(domainObject) {
|
setObject(domainObject) {
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.objectPath = [this.domainObject].concat(this.openmct.router.path);
|
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
let childContext = this.$refs.objectFrame.getSelectionContext();
|
let childContext = this.$refs.objectFrame.getSelectionContext();
|
||||||
childContext.item = domainObject;
|
childContext.item = domainObject;
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
@endMove="() => $emit('endMove')">
|
@endMove="() => $emit('endMove')">
|
||||||
<div class="c-telemetry-view"
|
<div class="c-telemetry-view"
|
||||||
:style="styleObject"
|
:style="styleObject"
|
||||||
v-if="domainObject">
|
v-if="domainObject"
|
||||||
|
@contextmenu.prevent="showContextMenu">
|
||||||
<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">{{ domainObject.name }}</div>
|
<div class="c-telemetry-view__label-text">{{ domainObject.name }}</div>
|
||||||
@@ -82,7 +83,8 @@
|
|||||||
import printj from 'printj'
|
import printj from 'printj'
|
||||||
|
|
||||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
|
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
|
||||||
DEFAULT_POSITION = [1, 1];
|
DEFAULT_POSITION = [1, 1],
|
||||||
|
CONTEXT_MENU_ACTIONS = ['viewHistoricalData'];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
makeDefinition(openmct, gridSize, domainObject, position) {
|
makeDefinition(openmct, gridSize, domainObject, position) {
|
||||||
@@ -103,7 +105,7 @@
|
|||||||
size: "13px"
|
size: "13px"
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct', 'objectPath'],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
gridSize: Array,
|
gridSize: Array,
|
||||||
@@ -163,7 +165,8 @@
|
|||||||
return {
|
return {
|
||||||
datum: undefined,
|
datum: undefined,
|
||||||
formats: undefined,
|
formats: undefined,
|
||||||
domainObject: undefined
|
domainObject: undefined,
|
||||||
|
currentObjectPath: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -218,12 +221,16 @@
|
|||||||
},
|
},
|
||||||
setObject(domainObject) {
|
setObject(domainObject) {
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
|
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
|
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.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.currentObjectPath = this.objectPath.slice();
|
||||||
|
this.currentObjectPath.unshift(this.domainObject);
|
||||||
|
|
||||||
this.context = {
|
this.context = {
|
||||||
item: domainObject,
|
item: domainObject,
|
||||||
layoutItem: this.item,
|
layoutItem: this.item,
|
||||||
@@ -235,6 +242,9 @@
|
|||||||
},
|
},
|
||||||
updateTelemetryFormat(format) {
|
updateTelemetryFormat(format) {
|
||||||
this.$emit('formatChanged', this.item, format);
|
this.$emit('formatChanged', this.item, format);
|
||||||
|
},
|
||||||
|
showContextMenu(event) {
|
||||||
|
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function DisplayLayoutPlugin(options) {
|
|||||||
canEdit: function (domainObject) {
|
canEdit: function (domainObject) {
|
||||||
return domainObject.type === 'layout';
|
return domainObject.type === 'layout';
|
||||||
},
|
},
|
||||||
view: function (domainObject) {
|
view: function (domainObject, isEditing, objectPath) {
|
||||||
let component;
|
let component;
|
||||||
return {
|
return {
|
||||||
show(container) {
|
show(container) {
|
||||||
@@ -49,13 +49,14 @@ export default function DisplayLayoutPlugin(options) {
|
|||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
objectUtils,
|
objectUtils,
|
||||||
options
|
options,
|
||||||
|
objectPath
|
||||||
},
|
},
|
||||||
el: container,
|
el: container,
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
domainObject: domainObject
|
domainObject: domainObject
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="u-contents c-filter-settings">
|
<div class="c-properties__section c-filter-settings">
|
||||||
<li class="grid-row c-filter-settings__setting"
|
<li class="c-properties__row c-filter-settings__setting"
|
||||||
v-for="(filter, index) in filterField.filters"
|
v-for="(filter, index) in filterField.filters"
|
||||||
:key="index">
|
:key="index">
|
||||||
<div class="grid-cell label">
|
<div class="c-properties__label label"
|
||||||
|
:disabled="useGlobal">
|
||||||
{{ filterField.name }} =
|
{{ filterField.name }} =
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-cell value">
|
<div class="c-properties__value value">
|
||||||
<!-- EDITING -->
|
<!-- EDITING -->
|
||||||
<!-- String input, editing -->
|
<!-- String input, editing -->
|
||||||
<template v-if="!filter.possibleValues && isEditing">
|
<template v-if="!filter.possibleValues && isEditing">
|
||||||
<input class="c-input--flex"
|
<input class="c-input--flex"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter Value"
|
|
||||||
:id="`${filter}filterControl`"
|
:id="`${filter}filterControl`"
|
||||||
|
:disabled="useGlobal"
|
||||||
:value="persistedValue(filter)"
|
:value="persistedValue(filter)"
|
||||||
@change="updateFilterValue($event, filter)">
|
@change="updateFilterValue($event, filter)">
|
||||||
</template>
|
</template>
|
||||||
@@ -21,15 +22,16 @@
|
|||||||
<!-- Checkbox list, editing -->
|
<!-- Checkbox list, editing -->
|
||||||
<template v-if="filter.possibleValues && isEditing">
|
<template v-if="filter.possibleValues && isEditing">
|
||||||
<div class="c-checkbox-list__row"
|
<div class="c-checkbox-list__row"
|
||||||
v-for="value in filter.possibleValues"
|
v-for="option in filter.possibleValues"
|
||||||
:key="value">
|
:key="option.value">
|
||||||
<input class="c-checkbox-list__input"
|
<input class="c-checkbox-list__input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:id="`${value}filterControl`"
|
:id="`${option.value}filterControl`"
|
||||||
@change="onUserSelect($event, filter.comparator, value)"
|
:disabled="useGlobal"
|
||||||
:checked="isChecked(filter.comparator, value)">
|
@change="updateFilterValue($event, filter.comparator, option.value)"
|
||||||
|
:checked="isChecked(filter.comparator, option.value)">
|
||||||
<span class="c-checkbox-list__value">
|
<span class="c-checkbox-list__value">
|
||||||
{{ value }}
|
{{ option.label }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -42,9 +44,8 @@
|
|||||||
|
|
||||||
<!-- Checkbox list, NOT editing -->
|
<!-- Checkbox list, NOT editing -->
|
||||||
<template v-if="filter.possibleValues && !isEditing">
|
<template v-if="filter.possibleValues && !isEditing">
|
||||||
<span
|
<span v-if="persistedFilters[filter.comparator]">
|
||||||
v-if="persistedFilters[filter.comparator]">
|
{{ getFilterLabels(filter) }}
|
||||||
{{persistedFilters[filter.comparator].join(', ')}}
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,19 +53,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import "~styles/sass-base";
|
|
||||||
|
|
||||||
.c-filter-settings {
|
|
||||||
&__setting {
|
|
||||||
.grid-cell.label {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
inject: [
|
inject: [
|
||||||
@@ -72,6 +60,7 @@ export default {
|
|||||||
],
|
],
|
||||||
props: {
|
props: {
|
||||||
filterField: Object,
|
filterField: Object,
|
||||||
|
useGlobal: Boolean,
|
||||||
persistedFilters: {
|
persistedFilters: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
@@ -81,7 +70,6 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
expanded: false,
|
|
||||||
isEditing: this.openmct.editor.isEditing()
|
isEditing: this.openmct.editor.isEditing()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -89,9 +77,6 @@ export default {
|
|||||||
toggleIsEditing(isEditing) {
|
toggleIsEditing(isEditing) {
|
||||||
this.isEditing = isEditing;
|
this.isEditing = isEditing;
|
||||||
},
|
},
|
||||||
onUserSelect(event, comparator, value){
|
|
||||||
this.$emit('onUserSelect', this.filterField.key, comparator, value, event.target.checked);
|
|
||||||
},
|
|
||||||
isChecked(comparator, value) {
|
isChecked(comparator, value) {
|
||||||
if (this.persistedFilters[comparator] && this.persistedFilters[comparator].includes(value)) {
|
if (this.persistedFilters[comparator] && this.persistedFilters[comparator].includes(value)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -102,8 +87,25 @@ export default {
|
|||||||
persistedValue(comparator) {
|
persistedValue(comparator) {
|
||||||
return this.persistedFilters && this.persistedFilters[comparator];
|
return this.persistedFilters && this.persistedFilters[comparator];
|
||||||
},
|
},
|
||||||
updateFilterValue(event, comparator) {
|
updateFilterValue(event, comparator, value) {
|
||||||
this.$emit('onTextEnter', this.filterField.key, comparator, event.target.value);
|
if (value !== undefined) {
|
||||||
|
this.$emit('filterSelected', this.filterField.key, comparator, value, event.target.checked);
|
||||||
|
} else {
|
||||||
|
this.$emit('filterTextValueChanged', this.filterField.key, comparator, event.target.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFilterLabels(filter) {
|
||||||
|
return this.persistedFilters[filter.comparator].reduce((accum, filterValue) => {
|
||||||
|
accum.push(filter.possibleValues.reduce((label, possibleValue) => {
|
||||||
|
if (filterValue === possibleValue.value) {
|
||||||
|
label = possibleValue.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
}, ''));
|
||||||
|
|
||||||
|
return accum;
|
||||||
|
}, []).join(', ');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<li>
|
<li class="c-tree__item-h">
|
||||||
<div class="c-tree__item menus-to-left"
|
<div class="c-tree__item menus-to-left"
|
||||||
@click="toggleExpanded">
|
@click="toggleExpanded">
|
||||||
|
<div class="c-filter-tree-item__filter-indicator"
|
||||||
|
:class="{'icon-filter': hasActiveFilters }"></div>
|
||||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||||
:class="{'c-disclosure-triangle--expanded': expanded}"></span>
|
:class="{'c-disclosure-triangle--expanded': expanded}"></span>
|
||||||
<div class="c-tree__item__label">
|
<div class="c-tree__item__label c-object-label">
|
||||||
<div class="c-object-label">
|
<div class="c-object-label">
|
||||||
<div class="c-object-label__type-icon"
|
<div class="c-object-label__type-icon"
|
||||||
:class="objectCssClass">
|
:class="objectCssClass">
|
||||||
@@ -13,30 +15,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="grid-properties" v-if="expanded">
|
|
||||||
|
<div v-if="expanded">
|
||||||
|
<ul class="c-properties">
|
||||||
|
<div class="c-properties__label span-all"
|
||||||
|
v-if="!isEditing && persistedFilters.useGlobal">
|
||||||
|
Uses global filter
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="c-properties__label span-all"
|
||||||
|
v-if="isEditing">
|
||||||
|
<toggle-switch
|
||||||
|
:id="keyString"
|
||||||
|
@change="useGlobalFilter"
|
||||||
|
:checked="persistedFilters.useGlobal">
|
||||||
|
</toggle-switch>
|
||||||
|
Use global filter
|
||||||
|
</div>
|
||||||
<filter-field
|
<filter-field
|
||||||
v-for="field in filterObject.valuesWithFilters"
|
v-if="(!persistedFilters.useGlobal && !isEditing) || isEditing"
|
||||||
:key="field.key"
|
v-for="metadatum in filterObject.metadataWithFilters"
|
||||||
:filterField="field"
|
:key="metadatum.key"
|
||||||
:persistedFilters="persistedFilters[field.key]"
|
:filterField="metadatum"
|
||||||
@onUserSelect="collectUserSelects"
|
:useGlobal="persistedFilters.useGlobal"
|
||||||
@onTextEnter="updateTextFilter">
|
:persistedFilters="updatedFilters[metadatum.key]"
|
||||||
|
@filterSelected="updateFiltersWithSelectedValue"
|
||||||
|
@filterTextValueChanged="updateFiltersWithTextValue">
|
||||||
</filter-field>
|
</filter-field>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FilterField from './FilterField.vue';
|
import FilterField from './FilterField.vue';
|
||||||
|
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
FilterField
|
FilterField,
|
||||||
|
ToggleSwitch
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
filterObject: Object,
|
filterObject: Object,
|
||||||
@@ -51,58 +70,74 @@ export default {
|
|||||||
return {
|
return {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
objectCssClass: undefined,
|
objectCssClass: undefined,
|
||||||
updatedFilters: this.persistedFilters
|
updatedFilters: JSON.parse(JSON.stringify(this.persistedFilters)),
|
||||||
|
isEditing: this.openmct.editor.isEditing()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
persistedFilters: {
|
||||||
|
handler: function checkFilters(newpersistedFilters) {
|
||||||
|
this.updatedFilters = JSON.parse(JSON.stringify(newpersistedFilters));
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasActiveFilters() {
|
||||||
|
// Should be true when the user has entered any filter values.
|
||||||
|
return Object.values(this.persistedFilters).some(comparator => {
|
||||||
|
return (typeof(comparator) === 'object' && !_.isEmpty(comparator));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleExpanded() {
|
toggleExpanded() {
|
||||||
this.expanded = !this.expanded;
|
this.expanded = !this.expanded;
|
||||||
},
|
},
|
||||||
collectUserSelects(key, comparator, valueName, value) {
|
updateFiltersWithSelectedValue(key, comparator, valueName, value) {
|
||||||
let filterValue = this.updatedFilters[key];
|
let filterValue = this.updatedFilters[key];
|
||||||
|
|
||||||
if (filterValue && filterValue[comparator]) {
|
if (filterValue[comparator]) {
|
||||||
if (value === false) {
|
if (value === true) {
|
||||||
let filteredValueName = filterValue[comparator].filter(v => v !== valueName);
|
|
||||||
|
|
||||||
if (filteredValueName.length === 0) {
|
|
||||||
delete this.updatedFilters[key];
|
|
||||||
} else {
|
|
||||||
filterValue[comparator] = filteredValueName;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
filterValue[comparator].push(valueName);
|
filterValue[comparator].push(valueName);
|
||||||
|
} else {
|
||||||
|
if (filterValue[comparator].length === 1) {
|
||||||
|
this.$set(this.updatedFilters, key, {});
|
||||||
|
} else {
|
||||||
|
filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!this.updatedFilters[key]) {
|
this.$set(this.updatedFilters[key], comparator, [valueName]);
|
||||||
this.$set(this.updatedFilters, key, {});
|
|
||||||
}
|
|
||||||
this.$set(this.updatedFilters[key], comparator, [value ? valueName : undefined]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('updateFilters', this.keyString, this.updatedFilters);
|
this.$emit('updateFilters', this.keyString, this.updatedFilters);
|
||||||
},
|
},
|
||||||
updateTextFilter(key, comparator, value) {
|
updateFiltersWithTextValue(key, comparator, value) {
|
||||||
if (value.trim() === '') {
|
if (value.trim() === '') {
|
||||||
if (this.updatedFilters[key]) {
|
this.$set(this.updatedFilters, key, {});
|
||||||
delete this.updatedFilters[key];
|
} else {
|
||||||
this.$emit('updateFilters', this.keyString, this.updatedFilters);
|
this.$set(this.updatedFilters[key], comparator, value);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.updatedFilters[key]) {
|
|
||||||
this.$set(this.updatedFilters, key, {});
|
|
||||||
this.$set(this.updatedFilters[key], comparator, '');
|
|
||||||
}
|
|
||||||
this.updatedFilters[key][comparator] = value;
|
|
||||||
this.$emit('updateFilters', this.keyString, this.updatedFilters);
|
this.$emit('updateFilters', this.keyString, this.updatedFilters);
|
||||||
}
|
},
|
||||||
|
useGlobalFilter(checked) {
|
||||||
|
this.updatedFilters.useGlobal = checked;
|
||||||
|
this.$emit('updateFilters', this.keyString, this.updatedFilters, checked);
|
||||||
|
},
|
||||||
|
toggleIsEditing(isEditing) {
|
||||||
|
this.isEditing = isEditing;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
let type = this.openmct.types.get(this.filterObject.domainObject.type) || {};
|
let type = this.openmct.types.get(this.filterObject.domainObject.type) || {};
|
||||||
this.keyString = this.openmct.objects.makeKeyString(this.filterObject.domainObject.identifier);
|
this.keyString = this.openmct.objects.makeKeyString(this.filterObject.domainObject.identifier);
|
||||||
this.objectCssClass = type.definition.cssClass;
|
this.objectCssClass = type.definition.cssClass;
|
||||||
|
this.openmct.editor.on('isEditing', this.toggleIsEditing);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.openmct.editor.off('isEditing', this.toggleIsEditing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul class="tree c-tree c-properties__section" v-if="Object.keys(children).length">
|
<ul class="c-tree c-filter-tree" v-if="Object.keys(children).length">
|
||||||
<h2 class="c-properties__header">Filters</h2>
|
<h2>Data Filters</h2>
|
||||||
|
<div class="c-filter-indication"
|
||||||
|
v-if="hasActiveFilters">{{ label }}
|
||||||
|
</div>
|
||||||
|
<global-filters
|
||||||
|
:globalFilters="globalFilters"
|
||||||
|
:globalMetadata="globalMetadata"
|
||||||
|
@persistGlobalFilters="persistGlobalFilters">
|
||||||
|
</global-filters>
|
||||||
<filter-object
|
<filter-object
|
||||||
v-for="(child, key) in children"
|
v-for="(child, key) in children"
|
||||||
:key="key"
|
:key="key"
|
||||||
@@ -12,65 +20,213 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
.c-inspector {
|
||||||
|
.c-filter-indication {
|
||||||
|
border-radius: $smallCr;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: $interiorMarginSm $interiorMargin;
|
||||||
|
text-transform: inherit;
|
||||||
|
}
|
||||||
|
.c-filter-tree {
|
||||||
|
// Filters UI uses a tree-based structure
|
||||||
|
.c-properties {
|
||||||
|
// Add extra margin to account for filter-indicator
|
||||||
|
margin-left: 38px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FilterObject from './FilterObject.vue';
|
import FilterObject from './FilterObject.vue';
|
||||||
|
import GlobalFilters from './GlobalFilters.vue'
|
||||||
|
|
||||||
|
const FILTER_VIEW_TITLE = 'Filters applied';
|
||||||
|
const FILTER_VIEW_TITLE_MIXED = 'Mixed filters applied';
|
||||||
|
const USE_GLOBAL = 'useGlobal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
FilterObject
|
FilterObject,
|
||||||
|
GlobalFilters
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
'openmct'
|
'openmct'
|
||||||
],
|
],
|
||||||
data() {
|
data() {
|
||||||
let providedObject = this.openmct.selection.get()[0][0].context.item;
|
let providedObject = this.openmct.selection.get()[0][0].context.item;
|
||||||
let persistedFilters = {};
|
let configuration = providedObject.configuration;
|
||||||
|
|
||||||
if (providedObject.configuration && providedObject.configuration.filters) {
|
|
||||||
persistedFilters = providedObject.configuration.filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
persistedFilters: (configuration && configuration.filters) || {},
|
||||||
|
globalFilters: (configuration && configuration.globalFilters) || {},
|
||||||
|
globalMetadata: {},
|
||||||
providedObject,
|
providedObject,
|
||||||
persistedFilters,
|
|
||||||
children: {}
|
children: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
hasActiveFilters() {
|
||||||
|
// Should be true when the user has entered any filter values.
|
||||||
|
return Object.values(this.persistedFilters).some(filters => {
|
||||||
|
return Object.values(filters).some(comparator => {
|
||||||
|
return (typeof(comparator) === 'object' && !_.isEmpty(comparator));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hasMixedFilters() {
|
||||||
|
// Should be true when filter values are mixed.
|
||||||
|
let filtersToCompare = _.omit(this.persistedFilters[Object.keys(this.persistedFilters)[0]], [USE_GLOBAL]);
|
||||||
|
return Object.values(this.persistedFilters).some(filters => {
|
||||||
|
return !_.isEqual(filtersToCompare, _.omit(filters, [USE_GLOBAL]));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
label() {
|
||||||
|
if (this.hasActiveFilters) {
|
||||||
|
if (this.hasMixedFilters) {
|
||||||
|
return FILTER_VIEW_TITLE_MIXED;
|
||||||
|
} else {
|
||||||
|
return FILTER_VIEW_TITLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addChildren(child) {
|
addChildren(domainObject) {
|
||||||
let keyString = this.openmct.objects.makeKeyString(child.identifier),
|
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
metadata = this.openmct.telemetry.getMetadata(child),
|
let metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||||
valuesWithFilters = metadata.valueMetadatas.filter((value) => value.filters),
|
let metadataWithFilters = metadata.valueMetadatas.filter(value => value.filters);
|
||||||
childObject = {
|
let hasFiltersWithKeyString = this.persistedFilters[keyString] !== undefined;
|
||||||
name: child.name,
|
let mutateFilters = false;
|
||||||
domainObject: child,
|
let childObject = {
|
||||||
valuesWithFilters
|
name: domainObject.name,
|
||||||
|
domainObject: domainObject,
|
||||||
|
metadataWithFilters
|
||||||
};
|
};
|
||||||
|
|
||||||
if (childObject.valuesWithFilters.length) {
|
if (metadataWithFilters.length) {
|
||||||
this.$set(this.children, keyString, childObject);
|
this.$set(this.children, keyString, childObject);
|
||||||
} else {
|
|
||||||
return;
|
metadataWithFilters.forEach(metadatum => {
|
||||||
|
if (!this.globalFilters[metadatum.key]) {
|
||||||
|
this.$set(this.globalFilters, metadatum.key, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.globalMetadata[metadatum.key]) {
|
||||||
|
this.$set(this.globalMetadata, metadatum.key, metadatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasFiltersWithKeyString) {
|
||||||
|
if (!this.persistedFilters[keyString]) {
|
||||||
|
this.$set(this.persistedFilters, keyString, {});
|
||||||
|
this.$set(this.persistedFilters[keyString], 'useGlobal', true);
|
||||||
|
mutateFilters = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$set(this.persistedFilters[keyString], metadatum.key, this.globalFilters[metadatum.key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mutateFilters) {
|
||||||
|
this.mutateConfigurationFilters();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeChildren(identifier) {
|
removeChildren(identifier) {
|
||||||
let keyString = this.openmct.objects.makeKeyString(identifier);
|
let keyString = this.openmct.objects.makeKeyString(identifier);
|
||||||
|
let globalFiltersToRemove = this.getGlobalFiltersToRemove(keyString);
|
||||||
|
|
||||||
|
if (globalFiltersToRemove.length > 0) {
|
||||||
|
globalFiltersToRemove.forEach(key => {
|
||||||
|
this.$delete(this.globalFilters, key);
|
||||||
|
this.$delete(this.globalMetadata, key);
|
||||||
|
});
|
||||||
|
this.mutateConfigurationGlobalFilters();
|
||||||
|
}
|
||||||
|
|
||||||
this.$delete(this.children, keyString);
|
this.$delete(this.children, keyString);
|
||||||
delete this.persistedFilters[keyString];
|
this.$delete(this.persistedFilters, keyString);
|
||||||
this.mutateConfigurationFilters();
|
this.mutateConfigurationFilters();
|
||||||
},
|
},
|
||||||
persistFilters(keyString, userSelects) {
|
getGlobalFiltersToRemove(keyString) {
|
||||||
this.persistedFilters[keyString] = userSelects;
|
let filtersToRemove = new Set();
|
||||||
|
|
||||||
|
this.children[keyString].metadataWithFilters.forEach(metadatum => {
|
||||||
|
let keepFilter = false
|
||||||
|
Object.keys(this.children).forEach(childKeyString => {
|
||||||
|
if (childKeyString !== keyString) {
|
||||||
|
let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key);
|
||||||
|
|
||||||
|
if (filterMatched) {
|
||||||
|
keepFilter = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!keepFilter) {
|
||||||
|
filtersToRemove.add(metadatum.key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(filtersToRemove);
|
||||||
|
},
|
||||||
|
persistFilters(keyString, updatedFilters, useGlobalValues) {
|
||||||
|
this.persistedFilters[keyString] = updatedFilters;
|
||||||
|
|
||||||
|
if (useGlobalValues) {
|
||||||
|
Object.keys(this.persistedFilters[keyString]).forEach(key => {
|
||||||
|
if (typeof(this.persistedFilters[keyString][key]) === 'object') {
|
||||||
|
this.persistedFilters[keyString][key] = this.globalFilters[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.mutateConfigurationFilters();
|
this.mutateConfigurationFilters();
|
||||||
},
|
},
|
||||||
updatePersistedFilters(filters) {
|
updatePersistedFilters(filters) {
|
||||||
this.persistedFilters = filters;
|
this.persistedFilters = filters;
|
||||||
},
|
},
|
||||||
|
persistGlobalFilters(key, filters) {
|
||||||
|
this.globalFilters[key] = filters[key];
|
||||||
|
this.mutateConfigurationGlobalFilters();
|
||||||
|
let mutateFilters = false;
|
||||||
|
|
||||||
|
Object.keys(this.children).forEach(keyString => {
|
||||||
|
if (this.persistedFilters[keyString].useGlobal !== false && this.containsField(keyString, key)) {
|
||||||
|
if (!this.persistedFilters[keyString][key]) {
|
||||||
|
this.$set(this.persistedFilters[keyString], key, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$set(this.persistedFilters[keyString], key, filters[key]);
|
||||||
|
mutateFilters = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mutateFilters) {
|
||||||
|
this.mutateConfigurationFilters();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateGlobalFilters(filters) {
|
||||||
|
this.globalFilters = filters;
|
||||||
|
},
|
||||||
|
containsField(keyString, field) {
|
||||||
|
let hasField = false;
|
||||||
|
this.children[keyString].metadataWithFilters.forEach(metadatum => {
|
||||||
|
if (metadatum.key === field) {
|
||||||
|
hasField = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return hasField;
|
||||||
|
},
|
||||||
mutateConfigurationFilters() {
|
mutateConfigurationFilters() {
|
||||||
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters);
|
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters);
|
||||||
|
},
|
||||||
|
mutateConfigurationGlobalFilters() {
|
||||||
|
this.openmct.objects.mutate(this.providedObject, 'configuration.globalFilters', this.globalFilters);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted(){
|
mounted(){
|
||||||
@@ -79,12 +235,14 @@ export default {
|
|||||||
this.composition.on('remove', this.removeChildren);
|
this.composition.on('remove', this.removeChildren);
|
||||||
this.composition.load();
|
this.composition.load();
|
||||||
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
|
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
|
||||||
|
this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
|
||||||
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
|
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.composition.off('add', this.addChildren);
|
this.composition.off('add', this.addChildren);
|
||||||
this.composition.off('remove', this.removeChildren);
|
this.composition.off('remove', this.removeChildren);
|
||||||
this.unobserve();
|
this.unobserve();
|
||||||
|
this.unobserveGlobalFilters();
|
||||||
this.unobserveAllMutation();
|
this.unobserveAllMutation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
134
src/plugins/filters/components/GlobalFilters.vue
Normal file
134
src/plugins/filters/components/GlobalFilters.vue
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<template>
|
||||||
|
<li class="c-tree__item-h">
|
||||||
|
<div class="c-tree__item menus-to-left"
|
||||||
|
@click="toggleExpanded">
|
||||||
|
<div class="c-filter-tree-item__filter-indicator"
|
||||||
|
:class="{'icon-filter': hasActiveGlobalFilters }"></div>
|
||||||
|
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||||
|
:class="{'c-disclosure-triangle--expanded': expanded}"></span>
|
||||||
|
<div class="c-tree__item__label c-object-label">
|
||||||
|
<div class="c-object-label">
|
||||||
|
<div class="c-object-label__type-icon icon-gear"></div>
|
||||||
|
<div class="c-object-label__name flex-elem grows">Global Filtering</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="c-properties" v-if="expanded">
|
||||||
|
<filter-field
|
||||||
|
v-for="metadatum in globalMetadata"
|
||||||
|
:key="metadatum.key"
|
||||||
|
:filterField="metadatum"
|
||||||
|
:persistedFilters="updatedFilters[metadatum.key]"
|
||||||
|
@filterSelected="updateFiltersWithSelectedValue"
|
||||||
|
@filterTextValueChanged="updateFiltersWithTextValue">
|
||||||
|
</filter-field>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
.c-filter-indication {
|
||||||
|
// Appears as a block element beneath tables
|
||||||
|
@include userSelectNone();
|
||||||
|
background: $colorFilterBg;
|
||||||
|
color: $colorFilterFg;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-top: $interiorMarginSm;
|
||||||
|
padding: 2px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
font-family: symbolsfont-12px;
|
||||||
|
content: $glyph-icon-filter;
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-filter-tree-item {
|
||||||
|
&__filter-indicator {
|
||||||
|
color: $colorFilter;
|
||||||
|
width: 1.2em; // Set width explicitly for layout reasons: will either have class icon-filter, or none.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FilterField from './FilterField.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
components: {
|
||||||
|
FilterField
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
globalMetadata: Object,
|
||||||
|
globalFilters: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
expanded: false,
|
||||||
|
updatedFilters: JSON.parse(JSON.stringify(this.globalFilters))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasActiveGlobalFilters() {
|
||||||
|
return Object.values(this.globalFilters).some(field => {
|
||||||
|
return Object.values(field).some(comparator => {
|
||||||
|
return (comparator && (comparator !== '' || comparator.length > 0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
globalFilters: {
|
||||||
|
handler: function checkFilters(newGlobalFilters) {
|
||||||
|
this.updatedFilters = JSON.parse(JSON.stringify(newGlobalFilters));
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleExpanded() {
|
||||||
|
this.expanded = !this.expanded;
|
||||||
|
},
|
||||||
|
updateFiltersWithSelectedValue(key, comparator, valueName, value) {
|
||||||
|
let filterValue = this.updatedFilters[key];
|
||||||
|
|
||||||
|
if (filterValue[comparator]) {
|
||||||
|
if (value === true) {
|
||||||
|
filterValue[comparator].push(valueName);
|
||||||
|
} else {
|
||||||
|
if (filterValue[comparator].length === 1) {
|
||||||
|
this.$set(this.updatedFilters, key, {});
|
||||||
|
} else {
|
||||||
|
filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$set(this.updatedFilters[key], comparator, [valueName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('persistGlobalFilters', key, this.updatedFilters);
|
||||||
|
},
|
||||||
|
updateFiltersWithTextValue(key, comparator, value) {
|
||||||
|
if (value.trim() === '') {
|
||||||
|
this.$set(this.updatedFilters, key, {});
|
||||||
|
} else {
|
||||||
|
this.$set(this.updatedFilters[key], comparator, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('persistGlobalFilters', key, this.updatedFilters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
define([
|
||||||
'./filtersInspectorViewProvider'
|
'./FiltersInspectorViewProvider'
|
||||||
], function (
|
], function (
|
||||||
FiltersInspectorViewProvider
|
FiltersInspectorViewProvider
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
export default class GoToOriginalAction {
|
export default class GoToOriginalAction {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
this.name = 'Go To Original';
|
this.name = 'Go To Original';
|
||||||
|
this.key = 'goToOriginal';
|
||||||
this.description = 'Go to the original unlinked instance of this object';
|
this.description = 'Go to the original unlinked instance of this object';
|
||||||
|
|
||||||
this._openmct = openmct;
|
this._openmct = openmct;
|
||||||
|
|||||||
@@ -115,10 +115,22 @@
|
|||||||
width: (tickWidth + 30) + 'px'
|
width: (tickWidth + 30) + 'px'
|
||||||
}">
|
}">
|
||||||
|
|
||||||
<div class="gl-plot-label gl-plot-y-label">
|
<div class="gl-plot-label gl-plot-y-label" ng-if="!yKeyOptions">
|
||||||
{{ yAxis.get('label') }}
|
{{ yAxis.get('label') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="gl-plot-label gl-plot-y-label" ng-if="yKeyOptions.length > 1 && series.length === 1">
|
||||||
|
<select class="gl-plot-y-label__select"
|
||||||
|
ng-model="yAxisLabel" ng-change="plot.toggleYAxisLabel(yAxisLabel, yKeyOptions, series[0])">
|
||||||
|
<option ng-repeat="option in yKeyOptions"
|
||||||
|
value="{{option.name}}"
|
||||||
|
ng-selected="option.name === yAxisLabel">
|
||||||
|
{{option.name}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<mct-ticks axis="yAxis">
|
<mct-ticks axis="yAxis">
|
||||||
<div ng-repeat="tick in ticks track by tick.text"
|
<div ng-repeat="tick in ticks track by tick.text"
|
||||||
class="gl-plot-tick gl-plot-y-tick-label"
|
class="gl-plot-tick gl-plot-y-tick-label"
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ define([
|
|||||||
this.$scope.series = this.config.series.models;
|
this.$scope.series = this.config.series.models;
|
||||||
this.$scope.legend = this.config.legend;
|
this.$scope.legend = this.config.legend;
|
||||||
|
|
||||||
|
this.$scope.yAxisLabel = this.config.yAxis.get('label');
|
||||||
|
|
||||||
this.cursorGuideVertical = this.$element[0].querySelector('.js-cursor-guide--v');
|
this.cursorGuideVertical = this.$element[0].querySelector('.js-cursor-guide--v');
|
||||||
this.cursorGuideHorizontal = this.$element[0].querySelector('.js-cursor-guide--h');
|
this.cursorGuideHorizontal = this.$element[0].querySelector('.js-cursor-guide--h');
|
||||||
this.cursorGuide = false;
|
this.cursorGuide = false;
|
||||||
@@ -103,9 +105,35 @@ define([
|
|||||||
this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this);
|
this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this);
|
||||||
this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this);
|
this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this);
|
||||||
this.listenTo(this.$scope, 'plot:reinitializeCanvas', this.initCanvas, this);
|
this.listenTo(this.$scope, 'plot:reinitializeCanvas', this.initCanvas, this);
|
||||||
|
|
||||||
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
||||||
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
|
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
|
||||||
|
|
||||||
|
this.setUpYAxisOptions();
|
||||||
|
};
|
||||||
|
|
||||||
|
MCTPlotController.prototype.setUpYAxisOptions = function () {
|
||||||
|
if (this.$scope.series.length === 1) {
|
||||||
|
let metadata = this.$scope.series[0].metadata;
|
||||||
|
|
||||||
|
this.$scope.yKeyOptions = metadata
|
||||||
|
.valuesForHints(['range'])
|
||||||
|
.map(function (o) {
|
||||||
|
return {
|
||||||
|
name: o.name,
|
||||||
|
key: o.key
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// set yAxisLabel if none is set yet
|
||||||
|
if (this.$scope.yAxisLabel === 'none') {
|
||||||
|
let yKey = this.$scope.series[0].model.yKey,
|
||||||
|
yKeyModel = this.$scope.yKeyOptions.filter(o => o.key === yKey)[0];
|
||||||
|
|
||||||
|
this.$scope.yAxisLabel = yKeyModel.name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$scope.yKeyOptions = undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MCTPlotController.prototype.onXAxisChange = function (displayBounds) {
|
MCTPlotController.prototype.onXAxisChange = function (displayBounds) {
|
||||||
@@ -493,5 +521,13 @@ define([
|
|||||||
this.cursorGuide = !this.cursorGuide;
|
this.cursorGuide = !this.cursorGuide;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MCTPlotController.prototype.toggleYAxisLabel = function (label, options, series) {
|
||||||
|
let yAxisObject = options.filter(o => o.name === label)[0];
|
||||||
|
|
||||||
|
if (yAxisObject) {
|
||||||
|
series.emit('change:yKey', yAxisObject.key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return MCTPlotController;
|
return MCTPlotController;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
export default class RemoveAction {
|
export default class RemoveAction {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
this.name = 'Remove';
|
this.name = 'Remove';
|
||||||
|
this.key = 'remove';
|
||||||
this.description = 'Remove this object from its containing object.';
|
this.description = 'Remove this object from its containing object.';
|
||||||
this.cssClass = "icon-trash";
|
this.cssClass = "icon-trash";
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ define([
|
|||||||
this.telemetryObjects = [];
|
this.telemetryObjects = [];
|
||||||
this.outstandingRequests = 0;
|
this.outstandingRequests = 0;
|
||||||
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||||
|
this.paused = false;
|
||||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
|
||||||
this.addTelemetryObject = this.addTelemetryObject.bind(this);
|
this.addTelemetryObject = this.addTelemetryObject.bind(this);
|
||||||
@@ -219,7 +220,10 @@ define([
|
|||||||
if (!this.telemetryObjects.includes(telemetryObject)) {
|
if (!this.telemetryObjects.includes(telemetryObject)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.paused) {
|
||||||
this.processRealtimeDatum(datum, columnMap, keyString, limitEvaluator);
|
this.processRealtimeDatum(datum, columnMap, keyString, limitEvaluator);
|
||||||
|
}
|
||||||
}, subscribeOptions);
|
}, subscribeOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,6 +259,17 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pause() {
|
||||||
|
this.paused = true;
|
||||||
|
this.boundedRows.unsubscribeFromBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
unpause() {
|
||||||
|
this.paused = false;
|
||||||
|
this.boundedRows.subscribeToBounds();
|
||||||
|
this.refreshData();
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.boundedRows.destroy();
|
this.boundedRows.destroy();
|
||||||
this.filteredRows.destroy();
|
this.filteredRows.destroy();
|
||||||
|
|||||||
@@ -21,10 +21,11 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
define(function () {
|
define(function () {
|
||||||
class TelemetryTableColumn {
|
class TelemetryTableColumn {
|
||||||
constructor (openmct, metadatum) {
|
constructor (openmct, metadatum, options = {selectable: false}) {
|
||||||
this.metadatum = metadatum;
|
this.metadatum = metadatum;
|
||||||
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
|
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
|
||||||
this.titleValue = this.metadatum.name;
|
this.titleValue = this.metadatum.name;
|
||||||
|
this.selectable = options.selectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
getKey() {
|
getKey() {
|
||||||
@@ -55,8 +56,7 @@ define(function () {
|
|||||||
return formattedValue;
|
return formattedValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
|
||||||
|
|
||||||
return TelemetryTableColumn;
|
return TelemetryTableColumn;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ define([], function () {
|
|||||||
}
|
}
|
||||||
return this.cellLimitClasses;
|
return this.cellLimitClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getContextMenuActions() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ define([
|
|||||||
canEdit(domainObject) {
|
canEdit(domainObject) {
|
||||||
return domainObject.type === 'table';
|
return domainObject.type === 'table';
|
||||||
},
|
},
|
||||||
view(domainObject) {
|
view(domainObject, isEditing, objectPath) {
|
||||||
let table = new TelemetryTable(domainObject, openmct);
|
let table = new TelemetryTable(domainObject, openmct);
|
||||||
let component;
|
let component;
|
||||||
return {
|
return {
|
||||||
@@ -64,10 +64,11 @@ define([
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
table
|
table,
|
||||||
|
objectPath
|
||||||
},
|
},
|
||||||
el: element,
|
el: element,
|
||||||
template: '<table-component :isEditing="isEditing"></table-component>'
|
template: '<table-component :isEditing="isEditing" :enableMarking="true"></table-component>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onEditModeChange(isEditing) {
|
onEditModeChange(isEditing) {
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ define(
|
|||||||
this.sortByTimeSystem(openmct.time.timeSystem());
|
this.sortByTimeSystem(openmct.time.timeSystem());
|
||||||
|
|
||||||
this.lastBounds = openmct.time.bounds();
|
this.lastBounds = openmct.time.bounds();
|
||||||
openmct.time.on('bounds', this.bounds);
|
|
||||||
|
this.subscribeToBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
addOne(item) {
|
addOne(item) {
|
||||||
@@ -140,9 +141,17 @@ define(
|
|||||||
return this.parseTime(row.datum[this.sortOptions.key]);
|
return this.parseTime(row.datum[this.sortOptions.key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
unsubscribeFromBounds() {
|
||||||
this.openmct.time.off('bounds', this.bounds);
|
this.openmct.time.off('bounds', this.bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscribeToBounds() {
|
||||||
|
this.openmct.time.on('bounds', this.bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.unsubscribeFromBounds();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return BoundedTableRowCollection;
|
return BoundedTableRowCollection;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div v-if="filterNames.length > 0"
|
<div v-if="filterNames.length > 0"
|
||||||
:title=title
|
:title=title
|
||||||
class="c-filter-indication"
|
class="c-filter-indication"
|
||||||
:class="{ 'c-filter-indication--mixed': mixed }">
|
:class="{ 'c-filter-indication--mixed': hasMixedFilters }">
|
||||||
<span class="c-filter-indication__mixed">{{ label }}</span>
|
<span class="c-filter-indication__mixed">{{ label }}</span>
|
||||||
<span v-for="(name, index) in filterNames"
|
<span v-for="(name, index) in filterNames"
|
||||||
class="c-filter-indication__label">
|
class="c-filter-indication__label">
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__mixed {
|
&__mixed {
|
||||||
font-weight: bold;
|
|
||||||
margin-right: $interiorMarginSm;
|
margin-right: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,119 +57,109 @@
|
|||||||
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
|
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
|
||||||
const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.';
|
const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.';
|
||||||
const FILTER_INDICATOR_TITLE_MIXED = 'A mix of data filter values are being applied to this view.';
|
const FILTER_INDICATOR_TITLE_MIXED = 'A mix of data filter values are being applied to this view.';
|
||||||
|
const USE_GLOBAL = 'useGlobal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'table'],
|
inject: ['openmct', 'table'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
filterNames: [],
|
filterNames: [],
|
||||||
filteredTelemetry: {},
|
filteredTelemetry: {}
|
||||||
mixed: false,
|
}
|
||||||
label: '',
|
},
|
||||||
title: ''
|
computed: {
|
||||||
|
hasMixedFilters() {
|
||||||
|
let filtersToCompare = _.omit(this.filteredTelemetry[Object.keys(this.filteredTelemetry)[0]], [USE_GLOBAL]);
|
||||||
|
return Object.values(this.filteredTelemetry).some(filters => {
|
||||||
|
return !_.isEqual(filtersToCompare, _.omit(filters, [USE_GLOBAL]));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
label() {
|
||||||
|
if (this.hasMixedFilters) {
|
||||||
|
return FILTER_INDICATOR_LABEL_MIXED;
|
||||||
|
} else {
|
||||||
|
return FILTER_INDICATOR_LABEL;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
if (this.hasMixedFilters) {
|
||||||
|
return FILTER_INDICATOR_TITLE_MIXED;
|
||||||
|
} else {
|
||||||
|
return FILTER_INDICATOR_TITLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isTelemetryObject(domainObject) {
|
|
||||||
return domainObject.hasOwnProperty('telemetry');
|
|
||||||
},
|
|
||||||
setFilterNames() {
|
setFilterNames() {
|
||||||
let names = [];
|
let names = [];
|
||||||
|
let composition = this.openmct.composition.get(this.table.configuration.domainObject);
|
||||||
|
|
||||||
this.composition && this.composition.load().then((domainObjects) => {
|
composition && composition.load().then((domainObjects) => {
|
||||||
domainObjects.forEach(telemetryObject => {
|
domainObjects.forEach(telemetryObject => {
|
||||||
let keyString= this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
let keyString= this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
|
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
|
||||||
let filters = this.filteredTelemetry[keyString];
|
let filters = this.filteredTelemetry[keyString];
|
||||||
this.telemetryKeyStrings.add(keyString);
|
|
||||||
|
|
||||||
if (filters !== undefined) {
|
if (filters !== undefined) {
|
||||||
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
|
names.push(this.getFilterNamesFromMetadata(filters, metadataValues));
|
||||||
Object.keys(filters).forEach(key => {
|
}
|
||||||
metadataValues.forEach(metadaum => {
|
});
|
||||||
|
|
||||||
if (key === metadaum.key) {
|
names = _.flatten(names);
|
||||||
names.push(metadaum.name);
|
this.filterNames = names.length === 0 ? names : Array.from(new Set(names));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
getFilterNamesFromMetadata(filters, metadataValues) {
|
||||||
|
let filterNames = [];
|
||||||
|
filters = _.omit(filters, [USE_GLOBAL]);
|
||||||
|
|
||||||
|
Object.keys(filters).forEach(key => {
|
||||||
|
if (!_.isEmpty(filters[key])) {
|
||||||
|
metadataValues.forEach(metadatum => {
|
||||||
|
if (key === metadatum.key) {
|
||||||
|
if (typeof metadatum.filters[0] === "object") {
|
||||||
|
filterNames.push(this.getFilterLabels(filters[key], metadatum));
|
||||||
|
} else {
|
||||||
|
filterNames.push(metadatum.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.filterNames = Array.from(new Set(names));
|
|
||||||
|
return _.flatten(filterNames);
|
||||||
|
},
|
||||||
|
getFilterLabels(filterObject, metadatum, ) {
|
||||||
|
let filterLabels = [];
|
||||||
|
Object.values(filterObject).forEach(comparator => {
|
||||||
|
comparator.forEach(filterValue => {
|
||||||
|
metadatum.filters[0].possibleValues.forEach(option => {
|
||||||
|
if (option.value === filterValue) {
|
||||||
|
filterLabels.push(option.label);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return filterLabels;
|
||||||
},
|
},
|
||||||
handleConfigurationChanges(configuration) {
|
handleConfigurationChanges(configuration) {
|
||||||
if (!_.eq(this.filteredTelemetry, configuration.filters)) {
|
if (!_.eq(this.filteredTelemetry, configuration.filters)) {
|
||||||
this.updateFilters(configuration.filters || {});
|
this.updateFilters(configuration.filters || {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkFiltersForMixedValues() {
|
|
||||||
let valueToCompare = this.filteredTelemetry[Object.keys(this.filteredTelemetry)[0]];
|
|
||||||
let mixed = false;
|
|
||||||
|
|
||||||
Object.values(this.filteredTelemetry).forEach(value => {
|
|
||||||
if (!_.isEqual(valueToCompare, value)) {
|
|
||||||
mixed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the filtered telemetry is not mixed at this point, check the number of available objects
|
|
||||||
// with the number of filtered telemetry. If they are not equal, the filters must be mixed.
|
|
||||||
if (mixed === false && _.size(this.filteredTelemetry) !== this.telemetryKeyStrings.size) {
|
|
||||||
mixed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.mixed = mixed;
|
|
||||||
},
|
|
||||||
setLabels() {
|
|
||||||
if (this.mixed) {
|
|
||||||
this.label = FILTER_INDICATOR_LABEL_MIXED;
|
|
||||||
this.title = FILTER_INDICATOR_TITLE_MIXED;
|
|
||||||
} else {
|
|
||||||
this.label = FILTER_INDICATOR_LABEL;
|
|
||||||
this.title = FILTER_INDICATOR_TITLE;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateFilters(filters) {
|
updateFilters(filters) {
|
||||||
this.filteredTelemetry = JSON.parse(JSON.stringify(filters));
|
this.filteredTelemetry = JSON.parse(JSON.stringify(filters));
|
||||||
this.setFilterNames();
|
this.setFilterNames();
|
||||||
this.updateIndicatorLabel();
|
|
||||||
},
|
|
||||||
addChildren(child) {
|
|
||||||
let keyString = this.openmct.objects.makeKeyString(child.identifier);
|
|
||||||
this.telemetryKeyStrings.add(keyString);
|
|
||||||
this.updateIndicatorLabel();
|
|
||||||
},
|
|
||||||
removeChildren(identifier) {
|
|
||||||
let keyString = this.openmct.objects.makeKeyString(identifier);
|
|
||||||
this.telemetryKeyStrings.delete(keyString);
|
|
||||||
this.updateIndicatorLabel();
|
|
||||||
},
|
|
||||||
updateIndicatorLabel() {
|
|
||||||
this.checkFiltersForMixedValues();
|
|
||||||
this.setLabels();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
let filters = this.table.configuration.getConfiguration().filters || {};
|
let filters = this.table.configuration.getConfiguration().filters || {};
|
||||||
this.telemetryKeyStrings = new Set();
|
|
||||||
this.composition = this.openmct.composition.get(this.table.configuration.domainObject);
|
|
||||||
|
|
||||||
if (this.composition) {
|
|
||||||
this.composition.on('add', this.addChildren);
|
|
||||||
this.composition.on('remove', this.removeChildren);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.table.configuration.on('change', this.handleConfigurationChanges);
|
this.table.configuration.on('change', this.handleConfigurationChanges);
|
||||||
this.updateFilters(filters);
|
this.updateFilters(filters);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.table.configuration.off('change', this.handleConfigurationChanges);
|
this.table.configuration.off('change', this.handleConfigurationChanges);
|
||||||
|
|
||||||
if (this.composition) {
|
|
||||||
this.composition.off('add', this.addChildren);
|
|
||||||
this.composition.off('remove', this.removeChildren);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
<template>
|
<template>
|
||||||
<td>{{formattedValue}}</td>
|
<td @click="selectCell($event.currentTarget, columnKey)" :title="formattedValue">{{formattedValue}}</td>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
@@ -33,11 +33,38 @@ export default {
|
|||||||
columnKey: {
|
columnKey: {
|
||||||
type: String,
|
type: String,
|
||||||
require: true
|
require: true
|
||||||
|
},
|
||||||
|
objectPath: {
|
||||||
|
type: Array,
|
||||||
|
require: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
selectCell(element, columnKey) {
|
||||||
|
if (this.isSelectable) {
|
||||||
|
this.openmct.selection.select([{
|
||||||
|
element: element,
|
||||||
|
context: {
|
||||||
|
type: 'table-cell',
|
||||||
|
row: this.row.objectKeyString,
|
||||||
|
column: columnKey
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
element: this.openmct.layout.$refs.browseObject.$el,
|
||||||
|
context: {
|
||||||
|
item: this.objectPath[0]
|
||||||
|
}
|
||||||
|
}], false);
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
formattedValue() {
|
formattedValue() {
|
||||||
return this.row.getFormattedValue(this.columnKey);
|
return this.row.getFormattedValue(this.columnKey);
|
||||||
|
},
|
||||||
|
isSelectable() {
|
||||||
|
return this.row.columns[this.columnKey].selectable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
<div class="c-properties__header">Table Column Size</div>
|
<div class="c-properties__header">Table Column Size</div>
|
||||||
<ul class="c-properties__section">
|
<ul class="c-properties__section">
|
||||||
<li class="c-properties__row">
|
<li class="c-properties__row">
|
||||||
<div class="c-properties__label" title="Show or Hide Column"><label for="AutoSizeControl">Auto-size</label></div>
|
<div class="c-properties__label" title="Auto-size table"><label for="AutoSizeControl">Auto-size</label></div>
|
||||||
<div class="c-properties__value"><input type="checkbox" id="AutoSizeControl" :checked="configuration.autosize !== false" @change="toggleAutosize()"></div>
|
<div class="c-properties__value"><input type="checkbox" id="AutoSizeControl" :checked="configuration.autosize !== false" @change="toggleAutosize()"></div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="c-properties__header">Table Column Visibility</div>
|
<div class="c-properties__header">Table Column Visibility</div>
|
||||||
<ul class="c-properties__section">
|
<ul class="c-properties__section">
|
||||||
<li class="c-properties__row" v-for="(title, key) in headers">
|
<li class="c-properties__row" v-for="(title, key) in headers">
|
||||||
<div class="c-properties__label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
|
<div class="c-properties__label" title="Show or hide column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
|
||||||
<div class="c-properties__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
|
<div class="c-properties__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -20,37 +20,54 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
<template>
|
<template>
|
||||||
<tr :style="{ top: rowTop }" :class="rowClass">
|
<tr :style="{ top: rowTop }"
|
||||||
<component
|
class="noselect"
|
||||||
v-for="(title, key) in headers"
|
:class="[
|
||||||
|
rowClass,
|
||||||
|
{'is-selected': marked}
|
||||||
|
]"
|
||||||
|
v-on="listeners">
|
||||||
|
<component v-for="(title, key) in headers"
|
||||||
:key="key"
|
:key="key"
|
||||||
:is="componentList[key]"
|
:is="componentList[key]"
|
||||||
:columnKey="key"
|
:columnKey="key"
|
||||||
:style="columnWidths[key] === undefined ? {} : { width: columnWidths[key] + 'px', 'max-width': columnWidths[key] + 'px'}"
|
:style="columnWidths[key] === undefined ? {} : { width: columnWidths[key] + 'px', 'max-width': columnWidths[key] + 'px'}"
|
||||||
:title="formattedRow[key]"
|
:class="[cellLimitClasses[key], selectableColumns[key] ? 'is-selectable' : '']"
|
||||||
:class="cellLimitClasses[key]"
|
:objectPath="objectPath"
|
||||||
class="is-selectable"
|
:row="row">
|
||||||
@click="selectCell($event.currentTarget, key)"
|
</component>
|
||||||
:row="row"></component>
|
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.noselect {
|
||||||
|
-webkit-touch-callout: none; /* iOS Safari */
|
||||||
|
-webkit-user-select: none; /* Safari */
|
||||||
|
-khtml-user-select: none; /* Konqueror HTML */
|
||||||
|
-moz-user-select: none; /* Firefox */
|
||||||
|
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||||
|
user-select: none; /* Non-prefixed version, currently
|
||||||
|
supported by Chrome and Opera */
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TableCell from './table-cell.vue';
|
import TableCell from './table-cell.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct', 'objectPath'],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
|
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
|
||||||
formattedRow: this.row.getFormattedDatum(this.headers),
|
|
||||||
rowClass: this.row.getRowClass(),
|
rowClass: this.row.getRowClass(),
|
||||||
cellLimitClasses: this.row.getCellLimitClasses(),
|
cellLimitClasses: this.row.getCellLimitClasses(),
|
||||||
componentList: Object.keys(this.headers).reduce((components, header) => {
|
componentList: Object.keys(this.headers).reduce((components, header) => {
|
||||||
components[header] = this.row.getCellComponentName(header) || 'table-cell';
|
components[header] = this.row.getCellComponentName(header) || 'table-cell';
|
||||||
return components
|
return components
|
||||||
|
}, {}),
|
||||||
|
selectableColumns : Object.keys(this.row.columns).reduce((selectable, columnKeys) => {
|
||||||
|
selectable[columnKeys] = this.row.columns[columnKeys].selectable;
|
||||||
|
return selectable;
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -67,6 +84,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
objectPath: {
|
||||||
|
type: Array,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
rowIndex: {
|
rowIndex: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -81,6 +102,11 @@ export default {
|
|||||||
type: Number,
|
type: Number,
|
||||||
required: false,
|
required: false,
|
||||||
default: 0
|
default: 0
|
||||||
|
},
|
||||||
|
marked: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -88,11 +114,28 @@ export default {
|
|||||||
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
|
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
|
||||||
},
|
},
|
||||||
formatRow: function (row) {
|
formatRow: function (row) {
|
||||||
this.formattedRow = row.getFormattedDatum(this.headers);
|
|
||||||
this.rowClass = row.getRowClass();
|
this.rowClass = row.getRowClass();
|
||||||
this.cellLimitClasses = row.getCellLimitClasses();
|
this.cellLimitClasses = row.getCellLimitClasses();
|
||||||
},
|
},
|
||||||
|
markRow: function (event) {
|
||||||
|
let keyCtrlModifier = false;
|
||||||
|
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
keyCtrlModifier = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
this.$emit('markMultipleConcurrent', this.rowIndex);
|
||||||
|
} else {
|
||||||
|
if (this.marked) {
|
||||||
|
this.$emit('unmark', this.rowIndex, keyCtrlModifier);
|
||||||
|
} else {
|
||||||
|
this.$emit('mark', this.rowIndex, keyCtrlModifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
selectCell(element, columnKey) {
|
selectCell(element, columnKey) {
|
||||||
|
if (this.selectableColumns[columnKey]) {
|
||||||
//TODO: This is a hack. Cannot get parent this way.
|
//TODO: This is a hack. Cannot get parent this way.
|
||||||
this.openmct.selection.select([{
|
this.openmct.selection.select([{
|
||||||
element: element,
|
element: element,
|
||||||
@@ -109,6 +152,17 @@ export default {
|
|||||||
}], false);
|
}], false);
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
showContextMenu: function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.openmct.objects.get(this.row.objectKeyString).then((domainObject) => {
|
||||||
|
let contextualObjectPath = this.objectPath.slice();
|
||||||
|
contextualObjectPath.unshift(domainObject);
|
||||||
|
|
||||||
|
this.openmct.contextMenu._showContextMenuForObjectPath(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions());
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// TODO: use computed properties
|
// TODO: use computed properties
|
||||||
watch: {
|
watch: {
|
||||||
@@ -120,6 +174,19 @@ export default {
|
|||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
TableCell
|
TableCell
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
listeners() {
|
||||||
|
let listenersObject = {
|
||||||
|
click: this.markRow
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.row.getContextMenuActions().length) {
|
||||||
|
listenersObject.contextmenu = this.showContextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
return listenersObject;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -20,17 +20,50 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
<template>
|
<template>
|
||||||
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
|
<div class="c-table-wrapper">
|
||||||
:class="{'loading': loading}">
|
<div class="c-table-control-bar c-control-bar">
|
||||||
<div :style="{ 'max-width': widthWithScroll, 'min-width': '150px'}"><slot></slot></div>
|
|
||||||
<div v-if="allowExport" class="c-table__control-bar c-control-bar">
|
|
||||||
<button class="c-button icon-download labeled"
|
<button class="c-button icon-download labeled"
|
||||||
v-on:click="exportAsCSV()"
|
v-if="allowExport"
|
||||||
|
v-on:click="exportAllDataAsCSV()"
|
||||||
title="Export This View's Data">
|
title="Export This View's Data">
|
||||||
<span class="c-button__label">Export As CSV</span>
|
<span class="c-button__label">Export Table Data</span>
|
||||||
|
</button>
|
||||||
|
<button class="c-button icon-download labeled"
|
||||||
|
v-if="allowExport"
|
||||||
|
v-show="markedRows.length"
|
||||||
|
v-on:click="exportMarkedDataAsCSV()"
|
||||||
|
title="Export Marked Rows As CSV">
|
||||||
|
<span class="c-button__label">Export Marked Rows</span>
|
||||||
|
</button>
|
||||||
|
<button class="c-button icon-x labeled"
|
||||||
|
v-show="markedRows.length"
|
||||||
|
v-on:click="unmarkAllRows()"
|
||||||
|
title="Unmark All Rows">
|
||||||
|
<span class="c-button__label">Unmark All Rows</span>
|
||||||
|
</button>
|
||||||
|
<div v-if="enableMarking"
|
||||||
|
class="c-separator">
|
||||||
|
</div>
|
||||||
|
<button v-if="enableMarking"
|
||||||
|
class="c-button icon-pause pause-play labeled"
|
||||||
|
:class=" paused ? 'icon-play is-paused' : 'icon-pause'"
|
||||||
|
v-on:click="togglePauseByButton()"
|
||||||
|
:title="paused ? 'Continue Data Flow' : 'Pause Data Flow'">
|
||||||
|
<span class="c-button__label">
|
||||||
|
{{paused ? 'Play' : 'Pause'}}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<slot name="buttons"></slot>
|
<slot name="buttons"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
|
||||||
|
:class="{
|
||||||
|
'loading': loading,
|
||||||
|
'paused' : paused
|
||||||
|
}">
|
||||||
|
|
||||||
|
<div :style="{ 'max-width': widthWithScroll, 'min-width': '150px'}"><slot></slot></div>
|
||||||
|
|
||||||
<div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div>
|
<div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div>
|
||||||
<!-- Headers table -->
|
<!-- Headers table -->
|
||||||
<div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}">
|
<div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}">
|
||||||
@@ -87,9 +120,14 @@
|
|||||||
:headers="headers"
|
:headers="headers"
|
||||||
:columnWidths="columnWidths"
|
:columnWidths="columnWidths"
|
||||||
:rowIndex="rowIndex"
|
:rowIndex="rowIndex"
|
||||||
|
:objectPath="objectPath"
|
||||||
:rowOffset="rowOffset"
|
:rowOffset="rowOffset"
|
||||||
:rowHeight="rowHeight"
|
:rowHeight="rowHeight"
|
||||||
:row="row">
|
:row="row"
|
||||||
|
:marked="row.marked"
|
||||||
|
@mark="markRow"
|
||||||
|
@unmark="unmarkRow"
|
||||||
|
@markMultipleConcurrent="markMultipleConcurrentRows">
|
||||||
</telemetry-table-row>
|
</telemetry-table-row>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -102,6 +140,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
|
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
|
||||||
|
:key="objectKeyString"
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
:columnWidths="configuredColumnWidths"
|
:columnWidths="configuredColumnWidths"
|
||||||
:row="sizingRowData">
|
:row="sizingRowData">
|
||||||
@@ -109,6 +148,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<telemetry-filter-indicator></telemetry-filter-indicator>
|
<telemetry-filter-indicator></telemetry-filter-indicator>
|
||||||
</div>
|
</div>
|
||||||
|
</div><!-- closes c-table-wrapper -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -134,7 +174,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default
|
vertical-align: middle; // This is crucial to hiding 4px height injected by browser by default
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
@@ -219,6 +259,10 @@
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 18px; // Needed when a row has empty values in its cells
|
height: 18px; // Needed when a row has empty values in its cells
|
||||||
|
|
||||||
|
&.is-selected {
|
||||||
|
background-color: $colorSelectedBg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
@@ -269,6 +313,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paused {
|
||||||
|
border: 1px solid #ff9900;
|
||||||
|
}
|
||||||
|
|
||||||
/******************************* LEGACY */
|
/******************************* LEGACY */
|
||||||
.s-status-taking-snapshot,
|
.s-status-taking-snapshot,
|
||||||
.overlay.snapshot {
|
.overlay.snapshot {
|
||||||
@@ -301,7 +349,7 @@ export default {
|
|||||||
search,
|
search,
|
||||||
TelemetryFilterIndicator
|
TelemetryFilterIndicator
|
||||||
},
|
},
|
||||||
inject: ['table', 'openmct'],
|
inject: ['table', 'openmct', 'objectPath'],
|
||||||
props: {
|
props: {
|
||||||
isEditing: {
|
isEditing: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -318,6 +366,10 @@ export default {
|
|||||||
allowSorting: {
|
allowSorting: {
|
||||||
'type': Boolean,
|
'type': Boolean,
|
||||||
'default': true
|
'default': true
|
||||||
|
},
|
||||||
|
enableMarking: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -346,7 +398,10 @@ export default {
|
|||||||
dropOffsetLeft: undefined,
|
dropOffsetLeft: undefined,
|
||||||
isDropTargetActive: false,
|
isDropTargetActive: false,
|
||||||
isAutosizeEnabled: configuration.autosize,
|
isAutosizeEnabled: configuration.autosize,
|
||||||
scrollW: 0
|
scrollW: 0,
|
||||||
|
markCounter: 0,
|
||||||
|
paused: false,
|
||||||
|
markedRows: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -532,15 +587,27 @@ export default {
|
|||||||
// which causes subsequent scroll to use an out of date height.
|
// which causes subsequent scroll to use an out of date height.
|
||||||
this.contentTable.style.height = this.totalHeight + 'px';
|
this.contentTable.style.height = this.totalHeight + 'px';
|
||||||
},
|
},
|
||||||
exportAsCSV() {
|
exportAsCSV(data) {
|
||||||
const headerKeys = Object.keys(this.headers);
|
const headerKeys = Object.keys(this.headers);
|
||||||
const justTheData = this.table.filteredRows.getRows()
|
|
||||||
.map(row => row.getFormattedDatum(this.headers));
|
this.csvExporter.export(data, {
|
||||||
this.csvExporter.export(justTheData, {
|
|
||||||
filename: this.table.domainObject.name + '.csv',
|
filename: this.table.domainObject.name + '.csv',
|
||||||
headers: headerKeys
|
headers: headerKeys
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
exportAllDataAsCSV() {
|
||||||
|
const justTheData = this.table.filteredRows.getRows()
|
||||||
|
.map(row => row.getFormattedDatum(this.headers));
|
||||||
|
|
||||||
|
this.exportAsCSV(justTheData);
|
||||||
|
},
|
||||||
|
exportMarkedDataAsCSV() {
|
||||||
|
const data = this.table.filteredRows.getRows()
|
||||||
|
.filter(row => row.marked === true)
|
||||||
|
.map(row => row.getFormattedDatum(this.headers));
|
||||||
|
|
||||||
|
this.exportAsCSV(data);
|
||||||
|
},
|
||||||
outstandingRequests(loading) {
|
outstandingRequests(loading) {
|
||||||
this.loading = loading;
|
this.loading = loading;
|
||||||
},
|
},
|
||||||
@@ -632,9 +699,106 @@ export default {
|
|||||||
clearRowsAndRerender() {
|
clearRowsAndRerender() {
|
||||||
this.visibleRows = [];
|
this.visibleRows = [];
|
||||||
this.$nextTick().then(this.updateVisibleRows);
|
this.$nextTick().then(this.updateVisibleRows);
|
||||||
|
},
|
||||||
|
pause(pausedByButton) {
|
||||||
|
if (pausedByButton) {
|
||||||
|
this.pausedByButton = true;
|
||||||
|
}
|
||||||
|
this.paused = true;
|
||||||
|
this.table.pause();
|
||||||
|
},
|
||||||
|
unpause(unpausedByButton) {
|
||||||
|
if (unpausedByButton) {
|
||||||
|
this.paused = false;
|
||||||
|
this.table.unpause();
|
||||||
|
this.markedRows = [];
|
||||||
|
this.pausedByButton = false;
|
||||||
|
} else {
|
||||||
|
if (!this.pausedByButton) {
|
||||||
|
this.paused = false;
|
||||||
|
this.table.unpause();
|
||||||
|
this.markedRows = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
togglePauseByButton() {
|
||||||
|
if (this.paused) {
|
||||||
|
this.unpause(true);
|
||||||
|
} else {
|
||||||
|
this.pause(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
undoMarkedRows(unpause) {
|
||||||
|
this.markedRows.forEach(r => r.marked = false);
|
||||||
|
this.markedRows = [];
|
||||||
|
},
|
||||||
|
unmarkRow(rowIndex) {
|
||||||
|
this.undoMarkedRows();
|
||||||
|
this.unpause();
|
||||||
|
},
|
||||||
|
markRow(rowIndex, keyModifier) {
|
||||||
|
if (!this.enableMarking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let insertMethod = 'unshift';
|
||||||
|
|
||||||
|
if (this.markedRows.length && !keyModifier) {
|
||||||
|
this.undoMarkedRows();
|
||||||
|
insertMethod = 'push';
|
||||||
|
}
|
||||||
|
|
||||||
|
let markedRow = this.visibleRows[rowIndex];
|
||||||
|
|
||||||
|
this.$set(markedRow, 'marked', true);
|
||||||
|
this.pause();
|
||||||
|
|
||||||
|
this.markedRows[insertMethod](markedRow);
|
||||||
|
},
|
||||||
|
unmarkAllRows(skipUnpause) {
|
||||||
|
this.markedRows.forEach(row => row.marked = false);
|
||||||
|
this.markedRows = [];
|
||||||
|
this.unpause();
|
||||||
|
},
|
||||||
|
markMultipleConcurrentRows(rowIndex) {
|
||||||
|
if (!this.enableMarking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.markedRows.length) {
|
||||||
|
this.markRow(rowIndex);
|
||||||
|
} else {
|
||||||
|
if (this.markedRows.length > 1) {
|
||||||
|
this.markedRows.forEach((r,i) => {
|
||||||
|
if (i !== 0) {
|
||||||
|
r.marked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.markedRows.splice(1);
|
||||||
|
}
|
||||||
|
let lastRowToBeMarked = this.visibleRows[rowIndex];
|
||||||
|
|
||||||
|
let allRows = this.table.filteredRows.getRows(),
|
||||||
|
firstRowIndex = allRows.indexOf(this.markedRows[0]),
|
||||||
|
lastRowIndex = allRows.indexOf(lastRowToBeMarked);
|
||||||
|
|
||||||
|
//supports backward selection
|
||||||
|
if (lastRowIndex < firstRowIndex) {
|
||||||
|
let temp = lastRowIndex;
|
||||||
|
|
||||||
|
lastRowIndex = firstRowIndex;
|
||||||
|
firstRowIndex = temp - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = firstRowIndex + 1; i <= lastRowIndex; i++) {
|
||||||
|
let row = allRows[i];
|
||||||
|
row.marked = true;
|
||||||
|
this.markedRows.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.filterChanged = _.debounce(this.filterChanged, 500);
|
this.filterChanged = _.debounce(this.filterChanged, 500);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ export default {
|
|||||||
this.xAxis.scale(this.xScale);
|
this.xAxis.scale(this.xScale);
|
||||||
this.xAxis.tickFormat(utcMultiTimeFormat);
|
this.xAxis.tickFormat(utcMultiTimeFormat);
|
||||||
this.axisElement.call(this.xAxis);
|
this.axisElement.call(this.xAxis);
|
||||||
|
this.setScale();
|
||||||
},
|
},
|
||||||
getActiveFormatter() {
|
getActiveFormatter() {
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ export default {
|
|||||||
.filter(menuOption => menuOption.clock === (clock && clock.key))
|
.filter(menuOption => menuOption.clock === (clock && clock.key))
|
||||||
.map(menuOption => JSON.parse(JSON.stringify(this.openmct.time.timeSystems.get(menuOption.timeSystem))));
|
.map(menuOption => JSON.parse(JSON.stringify(this.openmct.time.timeSystems.get(menuOption.timeSystem))));
|
||||||
},
|
},
|
||||||
|
|
||||||
setTimeSystemFromView(timeSystem) {
|
setTimeSystemFromView(timeSystem) {
|
||||||
if (timeSystem.key !== this.selectedTimeSystem.key) {
|
if (timeSystem.key !== this.selectedTimeSystem.key) {
|
||||||
let activeClock = this.openmct.time.clock();
|
let activeClock = this.openmct.time.clock();
|
||||||
@@ -69,7 +68,15 @@ export default {
|
|||||||
timeSystem: timeSystem.key
|
timeSystem: timeSystem.key
|
||||||
});
|
});
|
||||||
if (activeClock === undefined) {
|
if (activeClock === undefined) {
|
||||||
this.openmct.time.timeSystem(timeSystem.key, configuration.bounds);
|
let bounds;
|
||||||
|
|
||||||
|
if (this.selectedTimeSystem.isUTCBased && timeSystem.isUTCBased) {
|
||||||
|
bounds = this.openmct.time.bounds();
|
||||||
|
} else {
|
||||||
|
bounds = configuration.bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openmct.time.timeSystem(timeSystem.key, bounds);
|
||||||
} else {
|
} else {
|
||||||
this.openmct.time.timeSystem(timeSystem.key);
|
this.openmct.time.timeSystem(timeSystem.key);
|
||||||
this.openmct.time.clockOffsets(configuration.clockOffsets);
|
this.openmct.time.clockOffsets(configuration.clockOffsets);
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ $colorOk: #33cc33;
|
|||||||
$colorOkFg: #fff;
|
$colorOkFg: #fff;
|
||||||
$colorFilterBg: #44449c;
|
$colorFilterBg: #44449c;
|
||||||
$colorFilterFg: #8984e9;
|
$colorFilterFg: #8984e9;
|
||||||
|
$colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
|
||||||
|
|
||||||
// States
|
// States
|
||||||
$colorPausedBg: #ff9900;
|
$colorPausedBg: #ff9900;
|
||||||
@@ -211,6 +212,10 @@ $btnStdH: 24px;
|
|||||||
$colorCursorGuide: rgba(white, 0.6);
|
$colorCursorGuide: rgba(white, 0.6);
|
||||||
$shdwCursorGuide: rgba(black, 0.4) 0 0 2px;
|
$shdwCursorGuide: rgba(black, 0.4) 0 0 2px;
|
||||||
$colorLocalControlOvrBg: rgba($colorBodyBg, 0.8);
|
$colorLocalControlOvrBg: rgba($colorBodyBg, 0.8);
|
||||||
|
$colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due to usage of SVG bg in selects
|
||||||
|
$colorSelectFg: $colorBtnFg;
|
||||||
|
$colorSelectArw: lighten($colorBtnBg, 20%);
|
||||||
|
$shdwSelect: rgba(black, 0.5) 0 0.5px 3px;
|
||||||
|
|
||||||
// Menus
|
// Menus
|
||||||
$colorMenuBg: pullForward($colorBodyBg, 15%);
|
$colorMenuBg: pullForward($colorBodyBg, 15%);
|
||||||
@@ -425,7 +430,3 @@ $createBtnTextTransform: uppercase;
|
|||||||
background: linear-gradient(pullForward($c, 5%), $c);
|
background: linear-gradient(pullForward($c, 5%), $c);
|
||||||
box-shadow: rgba(black, 0.5) 0 0.5px 2px;
|
box-shadow: rgba(black, 0.5) 0 0.5px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin themedSelect($bg: $colorBtnBg, $fg: $colorBtnFg) {
|
|
||||||
@include cSelect(linear-gradient(lighten($bg, 5%), $bg), $fg, lighten($bg, 20%), rgba(black, 0.5) 0 0.5px 3px);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ $colorOk: #33cc33;
|
|||||||
$colorOkFg: #fff;
|
$colorOkFg: #fff;
|
||||||
$colorFilterBg: #44449c;
|
$colorFilterBg: #44449c;
|
||||||
$colorFilterFg: #8984e9;
|
$colorFilterFg: #8984e9;
|
||||||
|
$colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
|
||||||
|
|
||||||
// States
|
// States
|
||||||
$colorPausedBg: #ff9900;
|
$colorPausedBg: #ff9900;
|
||||||
@@ -215,6 +216,10 @@ $btnStdH: 24px;
|
|||||||
$colorCursorGuide: rgba(white, 0.6);
|
$colorCursorGuide: rgba(white, 0.6);
|
||||||
$shdwCursorGuide: rgba(black, 0.4) 0 0 2px;
|
$shdwCursorGuide: rgba(black, 0.4) 0 0 2px;
|
||||||
$colorLocalControlOvrBg: rgba($colorBodyBg, 0.8);
|
$colorLocalControlOvrBg: rgba($colorBodyBg, 0.8);
|
||||||
|
$colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due to usage of SVG bg in selects
|
||||||
|
$colorSelectFg: $colorBtnFg;
|
||||||
|
$colorSelectArw: lighten($colorBtnBg, 20%);
|
||||||
|
$shdwSelect: rgba(black, 0.5) 0 0.5px 3px;
|
||||||
|
|
||||||
// Menus
|
// Menus
|
||||||
$colorMenuBg: pullForward($colorBodyBg, 15%);
|
$colorMenuBg: pullForward($colorBodyBg, 15%);
|
||||||
@@ -430,10 +435,6 @@ $createBtnTextTransform: uppercase;
|
|||||||
box-shadow: rgba(black, 0.5) 0 0.5px 2px;
|
box-shadow: rgba(black, 0.5) 0 0.5px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin themedSelect($bg: $colorBtnBg, $fg: $colorBtnFg) {
|
|
||||||
@include cSelect(linear-gradient(lighten($bg, 5%), $bg), $fg, lighten($bg, 20%), rgba(black, 0.5) 0 0.5px 3px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************** OVERRIDES */
|
/**************************************************** OVERRIDES */
|
||||||
.c-frame {
|
.c-frame {
|
||||||
&:not(.no-frame) {
|
&:not(.no-frame) {
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ $colorOk: #33cc33;
|
|||||||
$colorOkFg: #fff;
|
$colorOkFg: #fff;
|
||||||
$colorFilterBg: #a29fe2;
|
$colorFilterBg: #a29fe2;
|
||||||
$colorFilterFg: #fff;
|
$colorFilterFg: #fff;
|
||||||
|
$colorFilter: $colorFilterBg; // Standalone against $colorBodyBg
|
||||||
|
|
||||||
// States
|
// States
|
||||||
$colorPausedBg: #ff9900;
|
$colorPausedBg: #ff9900;
|
||||||
@@ -211,6 +212,10 @@ $btnStdH: 24px;
|
|||||||
$colorCursorGuide: rgba(black, 0.6);
|
$colorCursorGuide: rgba(black, 0.6);
|
||||||
$shdwCursorGuide: rgba(white, 0.4) 0 0 2px;
|
$shdwCursorGuide: rgba(white, 0.4) 0 0 2px;
|
||||||
$colorLocalControlOvrBg: rgba($colorBodyFg, 0.8);
|
$colorLocalControlOvrBg: rgba($colorBodyFg, 0.8);
|
||||||
|
$colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due to usage of SVG bg in selects
|
||||||
|
$colorSelectFg: $colorBtnFg;
|
||||||
|
$colorSelectArw: lighten($colorBtnBg, 20%);
|
||||||
|
$shdwSelect: none;
|
||||||
|
|
||||||
// Menus
|
// Menus
|
||||||
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
||||||
@@ -424,7 +429,3 @@ $createBtnTextTransform: uppercase;
|
|||||||
@mixin themedButton($c: $colorBtnBg) {
|
@mixin themedButton($c: $colorBtnBg) {
|
||||||
background: $c;
|
background: $c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin themedSelect($bg: $colorBtnBg, $fg: $colorBtnFg) {
|
|
||||||
@include cSelect($bg, $fg, lighten($bg, 20%), none);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ $mobileMenuIconD: 24px; // Used
|
|||||||
$mobileTreeItemH: 35px; // Used
|
$mobileTreeItemH: 35px; // Used
|
||||||
|
|
||||||
/************************** VISUAL */
|
/************************** VISUAL */
|
||||||
$controlDisabledOpacity: 0.3;
|
$controlDisabledOpacity: 0.5;
|
||||||
|
|
||||||
/************************** UI ELEMENTS */
|
/************************** UI ELEMENTS */
|
||||||
/*************** Progress Bar */
|
/*************** Progress Bar */
|
||||||
|
|||||||
@@ -279,7 +279,10 @@ input[type=number]::-webkit-outer-spin-button {
|
|||||||
// SELECTS
|
// SELECTS
|
||||||
select {
|
select {
|
||||||
@include appearanceNone();
|
@include appearanceNone();
|
||||||
@include themedSelect();
|
background-color: $colorSelectBg;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3e%3cpath fill='%23#{svgColorFromHex($colorSelectArw)}' d='M5 5l5-5H0z'/%3e%3c/svg%3e");
|
||||||
|
color: $colorSelectFg;
|
||||||
|
box-shadow: $shdwSelect;
|
||||||
background-repeat: no-repeat, no-repeat;
|
background-repeat: no-repeat, no-repeat;
|
||||||
background-position: right .4em top 80%, 0 0;
|
background-position: right .4em top 80%, 0 0;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -600,15 +603,15 @@ select {
|
|||||||
margin-right: $m;
|
margin-right: $m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-separator {
|
||||||
|
@include cToolbarSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
.c-toolbar {
|
.c-toolbar {
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__button {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
&__separator {
|
&__separator {
|
||||||
@include cToolbarSeparator();
|
@include cToolbarSeparator();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,6 +202,11 @@ body.desktop .has-local-controls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::placeholder {
|
||||||
|
opacity: 0.5;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************** STATES */
|
/******************************************************** STATES */
|
||||||
@mixin spinner($b: 5px, $c: $colorKey) {
|
@mixin spinner($b: 5px, $c: $colorKey) {
|
||||||
animation-name: rotation-centered;
|
animation-name: rotation-centered;
|
||||||
|
|||||||
@@ -161,8 +161,7 @@ mct-plot {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.gl-plot-y-label,
|
&.gl-plot-y-label {
|
||||||
&.l-plot-y-label {
|
|
||||||
$x: -50%;
|
$x: -50%;
|
||||||
$r: -90deg;
|
$r: -90deg;
|
||||||
transform-origin: 50% 0;
|
transform-origin: 50% 0;
|
||||||
@@ -172,6 +171,12 @@ mct-plot {
|
|||||||
left: 0;
|
left: 0;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
select {
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3e%3cpath fill='%23#{svgColorFromHex($colorSelectArw)}' d='M0 5l5 5V0L0 5z'/%3e%3c/svg%3e");
|
||||||
|
background-position: left .4em top 50%, 0 0;
|
||||||
|
padding: 1px $interiorMargin 1px 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -585,6 +585,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@function svgColorFromHex($hexColor) {
|
||||||
|
// Remove initial # in color value
|
||||||
|
@return str-slice(inspect($hexColor), 2, str-length(inspect($hexColor)));
|
||||||
|
}
|
||||||
|
|
||||||
@mixin test($c: deeppink, $a: 0.3) {
|
@mixin test($c: deeppink, $a: 0.3) {
|
||||||
background: rgba($c, $a) !important;
|
background: rgba($c, $a) !important;
|
||||||
background-color: rgba($c, $a) !important;
|
background-color: rgba($c, $a) !important;
|
||||||
|
|||||||
@@ -76,23 +76,43 @@ div.c-table {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-table-wrapper {
|
||||||
|
// Wraps .c-control-bar and .c-table
|
||||||
|
@include abs();
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> .c-table {
|
||||||
|
height: auto;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-top: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-table-control-bar {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.c-table {
|
.c-table {
|
||||||
// Can be used by any type of table, scrolling, LAD, etc.
|
// Can be used by any type of table, scrolling, LAD, etc.
|
||||||
$min-w: 50px;
|
$min-w: 50px;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&__control-bar,
|
|
||||||
&__headers-w {
|
&__headers-w {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************* ELEMENTS */
|
/******************************* ELEMENTS */
|
||||||
|
|
||||||
&__control-bar {
|
|
||||||
margin-bottom: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead tr,
|
thead tr,
|
||||||
&.c-table__headers {
|
&.c-table__headers {
|
||||||
background: $colorTabHeaderBg;
|
background: $colorTabHeaderBg;
|
||||||
|
|||||||
@@ -44,7 +44,8 @@
|
|||||||
class="c-so-view__object-view"
|
class="c-so-view__object-view"
|
||||||
ref="objectView"
|
ref="objectView"
|
||||||
:object="domainObject"
|
:object="domainObject"
|
||||||
:show-edit-view="showEditView">
|
:show-edit-view="showEditView"
|
||||||
|
:object-path="objectPath">
|
||||||
</object-view>
|
</object-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -123,9 +124,14 @@
|
|||||||
.c-click-icon,
|
.c-click-icon,
|
||||||
.c-button {
|
.c-button {
|
||||||
// Shrink buttons a bit when they appear in a frame
|
// Shrink buttons a bit when they appear in a frame
|
||||||
|
align-items: baseline;
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: 3px 5px;
|
||||||
|
&:before {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: $interiorMarginSm;
|
padding: $interiorMarginSm 1px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
view: String,
|
view: String,
|
||||||
object: Object,
|
object: Object,
|
||||||
showEditView: Boolean
|
showEditView: Boolean,
|
||||||
|
objectPath: Array
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.clear();
|
this.clear();
|
||||||
@@ -91,17 +92,19 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let objectPath = this.currentObjectPath || this.objectPath;
|
||||||
|
|
||||||
if (provider.edit && this.showEditView) {
|
if (provider.edit && this.showEditView) {
|
||||||
if (this.openmct.editor.isEditing()) {
|
if (this.openmct.editor.isEditing()) {
|
||||||
this.currentView = provider.edit(this.currentObject);
|
this.currentView = provider.edit(this.currentObject, true, objectPath);
|
||||||
} else {
|
} else {
|
||||||
this.currentView = provider.view(this.currentObject, false);
|
this.currentView = provider.view(this.currentObject, false, objectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openmct.editor.on('isEditing', this.toggleEditView);
|
this.openmct.editor.on('isEditing', this.toggleEditView);
|
||||||
this.releaseEditModeHandler = () => this.openmct.editor.off('isEditing', this.toggleEditView);
|
this.releaseEditModeHandler = () => this.openmct.editor.off('isEditing', this.toggleEditView);
|
||||||
} else {
|
} else {
|
||||||
this.currentView = provider.view(this.currentObject, this.openmct.editor.isEditing());
|
this.currentView = provider.view(this.currentObject, this.openmct.editor.isEditing(), objectPath);
|
||||||
|
|
||||||
if (this.currentView.onEditModeChange) {
|
if (this.currentView.onEditModeChange) {
|
||||||
this.openmct.editor.on('isEditing', this.invokeEditModeHandler);
|
this.openmct.editor.on('isEditing', this.invokeEditModeHandler);
|
||||||
@@ -117,7 +120,7 @@ export default {
|
|||||||
|
|
||||||
this.openmct.objectViews.on('clearData', this.clearData);
|
this.openmct.objectViews.on('clearData', this.clearData);
|
||||||
},
|
},
|
||||||
show(object, viewKey, immediatelySelect) {
|
show(object, viewKey, immediatelySelect, currentObjectPath) {
|
||||||
if (this.unlisten) {
|
if (this.unlisten) {
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
}
|
}
|
||||||
@@ -132,6 +135,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.currentObject = object;
|
this.currentObject = object;
|
||||||
|
|
||||||
|
if (currentObjectPath) {
|
||||||
|
this.currentObjectPath = currentObjectPath;
|
||||||
|
}
|
||||||
|
|
||||||
this.unlisten = this.openmct.objects.observe(this.currentObject, '*', (mutatedObject) => {
|
this.unlisten = this.openmct.objects.observe(this.currentObject, '*', (mutatedObject) => {
|
||||||
this.currentObject = mutatedObject;
|
this.currentObject = mutatedObject;
|
||||||
});
|
});
|
||||||
|
|||||||
78
src/ui/components/ToggleSwitch.vue
Normal file
78
src/ui/components/ToggleSwitch.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<label class="c-toggle-switch">
|
||||||
|
<input type="checkbox"
|
||||||
|
:id="id"
|
||||||
|
:checked="checked"
|
||||||
|
@change="onUserSelect($event)"/>
|
||||||
|
<span class="c-toggle-switch__slider"></span>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
.c-toggle-switch {
|
||||||
|
$d: 12px;
|
||||||
|
$m: 2px;
|
||||||
|
$br: $d/1.5;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
&__slider {
|
||||||
|
background: $colorBtnBg; // TODO: make discrete theme constants for these colors
|
||||||
|
border-radius: $br;
|
||||||
|
//box-shadow: inset rgba($colorBtnFg, 0.4) 0 0 0 1px;
|
||||||
|
display: inline-block;
|
||||||
|
height: $d + ($m*2);
|
||||||
|
position: relative;
|
||||||
|
transform: translateY(2px); // TODO: get this to work without this kind of hack!
|
||||||
|
width: $d*2 + $m*2;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
// Knob
|
||||||
|
background: $colorBtnFg; // TODO: make discrete theme constants for these colors
|
||||||
|
border-radius: floor($br * 0.8);
|
||||||
|
box-shadow: rgba(black, 0.4) 0 0 2px;
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
height: $d; width: $d;
|
||||||
|
top: $m; left: $m; right: auto;
|
||||||
|
transition: transform 100ms ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
+ .c-toggle-switch__slider {
|
||||||
|
background: $colorKey; // TODO: make discrete theme constants for these colors
|
||||||
|
&:before {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
id: String,
|
||||||
|
checked: Boolean
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onUserSelect(event) {
|
||||||
|
this.$emit('change', event.target.checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -106,8 +106,10 @@
|
|||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__row + &__row {
|
&__row + &__row,
|
||||||
> [class*="__"] {
|
&__section + &__section {
|
||||||
|
[class*="__label"],
|
||||||
|
[class*="__value"] {
|
||||||
// Row borders, effected via border-top on child elements of the row
|
// Row borders, effected via border-top on child elements of the row
|
||||||
border-top: 1px solid $colorInspectorSectionHeaderBg;
|
border-top: 1px solid $colorInspectorSectionHeaderBg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,28 +19,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="l-browse-bar__end">
|
<div class="l-browse-bar__end">
|
||||||
<div class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left"
|
<view-switcher
|
||||||
v-if="views.length > 1">
|
:currentView="currentView"
|
||||||
<button class="c-button--menu"
|
:views="views"
|
||||||
:class="currentView.cssClass"
|
@setView="setView">
|
||||||
title="Switch view type"
|
</view-switcher>
|
||||||
@click.stop="toggleViewMenu">
|
|
||||||
<span class="c-button__label">
|
|
||||||
{{ currentView.name }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<div class="c-menu" v-show="showViewMenu">
|
|
||||||
<ul>
|
|
||||||
<li v-for="(view, index) in views"
|
|
||||||
@click="setView(view)"
|
|
||||||
:key="index"
|
|
||||||
:class="view.cssClass"
|
|
||||||
:title="view.name">
|
|
||||||
{{ view.name }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Action buttons -->
|
<!-- Action buttons -->
|
||||||
<div class="l-browse-bar__actions">
|
<div class="l-browse-bar__actions">
|
||||||
<button v-if="notebookEnabled"
|
<button v-if="notebookEnabled"
|
||||||
@@ -77,14 +60,15 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NotebookSnapshot from '../utils/notebook-snapshot';
|
import NotebookSnapshot from '../utils/notebook-snapshot';
|
||||||
|
import ViewSwitcher from './ViewSwitcher.vue';
|
||||||
const PLACEHOLDER_OBJECT = {};
|
const PLACEHOLDER_OBJECT = {};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
methods: {
|
components: {
|
||||||
toggleViewMenu() {
|
ViewSwitcher
|
||||||
this.showViewMenu = !this.showViewMenu;
|
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
toggleSaveMenu() {
|
toggleSaveMenu() {
|
||||||
this.showSaveMenu = !this.showSaveMenu;
|
this.showSaveMenu = !this.showSaveMenu;
|
||||||
},
|
},
|
||||||
|
|||||||
55
src/ui/layout/ViewSwitcher.vue
Normal file
55
src/ui/layout/ViewSwitcher.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left"
|
||||||
|
v-if="views.length > 1">
|
||||||
|
<button class="c-button--menu"
|
||||||
|
:class="currentView.cssClass"
|
||||||
|
title="Switch view type"
|
||||||
|
@click.stop="toggleViewMenu">
|
||||||
|
<span class="c-button__label">
|
||||||
|
{{ currentView.name }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div class="c-menu" v-show="showViewMenu">
|
||||||
|
<ul>
|
||||||
|
<li v-for="(view, index) in views"
|
||||||
|
@click="setView(view)"
|
||||||
|
:key="index"
|
||||||
|
:class="view.cssClass"
|
||||||
|
:title="view.name">
|
||||||
|
{{ view.name }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: [
|
||||||
|
'currentView',
|
||||||
|
'views'
|
||||||
|
],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showViewMenu: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setView(view) {
|
||||||
|
this.$emit('setView', view);
|
||||||
|
},
|
||||||
|
toggleViewMenu() {
|
||||||
|
this.showViewMenu = !this.showViewMenu;
|
||||||
|
},
|
||||||
|
hideViewMenu() {
|
||||||
|
this.showViewMenu = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
document.addEventListener('click', this.hideViewMenu);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
document.removeEventListener('click', this.hideViewMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -81,6 +81,10 @@
|
|||||||
padding: $interiorMargin - $aPad;
|
padding: $interiorMargin - $aPad;
|
||||||
transition: background 150ms ease;
|
transition: background 150ms ease;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $colorItemTreeHoverBg;
|
background: $colorItemTreeHoverBg;
|
||||||
.c-tree__item__type-icon:before {
|
.c-tree__item__type-icon:before {
|
||||||
@@ -116,10 +120,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__view-control {
|
|
||||||
margin-right: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object labels in trees
|
// Object labels in trees
|
||||||
&__label {
|
&__label {
|
||||||
// <a> tag that holds type icon and name.
|
// <a> tag that holds type icon and name.
|
||||||
|
|||||||
@@ -18,10 +18,47 @@
|
|||||||
<span class="c-tree__item__label">Loading...</span>
|
<span class="c-tree__item__label">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<tree-item v-for="child in children"
|
|
||||||
|
<template v-if="children.length">
|
||||||
|
|
||||||
|
<template v-if="children.length > page_threshold">
|
||||||
|
<li v-show="!showSearchComponent"
|
||||||
|
@click="toggleSearchComponent"
|
||||||
|
class="c-tree__item-h"
|
||||||
|
style="font-size: 0.5em;">
|
||||||
|
<div class="c-tree__item icon-magnify">
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li v-show="showSearchComponent"
|
||||||
|
class="c-tree__item-h"
|
||||||
|
style="font-size: 0.7em">
|
||||||
|
<div class="c-tree__item">
|
||||||
|
<a class="c-tree__item__label c-object-label">
|
||||||
|
<search
|
||||||
|
:value="searchValue"
|
||||||
|
@input="searchChildren"
|
||||||
|
@clear="searchChildren"
|
||||||
|
style="min-width: 80%;">
|
||||||
|
</search>
|
||||||
|
<div style="padding: 2px; margin-left: 10%;"
|
||||||
|
class="icon-x"
|
||||||
|
@click="toggleSearchComponent">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div :style="style"
|
||||||
|
@scroll="scrollPage"
|
||||||
|
ref="scrollParent">
|
||||||
|
<tree-item v-for="child in filteredAndPagedChildren"
|
||||||
:key="child.id"
|
:key="child.id"
|
||||||
:node="child">
|
:node="child">
|
||||||
</tree-item>
|
</tree-item>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
@@ -29,6 +66,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import viewControl from '../components/viewControl.vue';
|
import viewControl from '../components/viewControl.vue';
|
||||||
import ObjectLabel from '../components/ObjectLabel.vue';
|
import ObjectLabel from '../components/ObjectLabel.vue';
|
||||||
|
import Search from '../components/search.vue';
|
||||||
|
|
||||||
|
const PAGE_THRESHOLD = 50;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'tree-item',
|
name: 'tree-item',
|
||||||
@@ -44,7 +84,13 @@
|
|||||||
loaded: false,
|
loaded: false,
|
||||||
isNavigated: this.navigateToPath === this.openmct.router.currentLocation.path,
|
isNavigated: this.navigateToPath === this.openmct.router.currentLocation.path,
|
||||||
children: [],
|
children: [],
|
||||||
expanded: false
|
expanded: false,
|
||||||
|
page: 1,
|
||||||
|
page_threshold: PAGE_THRESHOLD,
|
||||||
|
searchValue: '',
|
||||||
|
filteredChildren: [],
|
||||||
|
scrollTop: 0,
|
||||||
|
showSearchComponent: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -55,6 +101,44 @@
|
|||||||
}
|
}
|
||||||
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
|
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
|
||||||
return parentKeyString !== this.node.object.location;
|
return parentKeyString !== this.node.object.location;
|
||||||
|
},
|
||||||
|
filteredAndPagedChildren() {
|
||||||
|
if (this.searchValue) {
|
||||||
|
this.filteredChildren = this.children.filter((child) => {
|
||||||
|
let searchLowCase = this.searchValue.toLowerCase(),
|
||||||
|
nameLowerCase = child.object.name.toLowerCase();
|
||||||
|
|
||||||
|
return nameLowerCase.includes(searchLowCase);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.filteredChildren = this.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filteredChildren.length > this.page_threshold) {
|
||||||
|
let maxIndex = this.page * this.page_threshold,
|
||||||
|
minIndex = maxIndex - this.page_threshold;
|
||||||
|
|
||||||
|
return this.filteredChildren.slice(minIndex, maxIndex);
|
||||||
|
} else {
|
||||||
|
return this.filteredChildren;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lastPage() {
|
||||||
|
return Math.floor(this.filteredChildren.length / this.page_threshold);
|
||||||
|
},
|
||||||
|
style() {
|
||||||
|
let numChildren = this.filteredChildren.length;
|
||||||
|
|
||||||
|
if (!this.$refs.scrollParent || numChildren === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((numChildren * 20) > this.$refs.scrollParent.offsetHeight) {
|
||||||
|
return {
|
||||||
|
"overflow-y": 'scroll',
|
||||||
|
"max-height": (this.page_threshold * 10) + 'px'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -97,6 +181,11 @@
|
|||||||
this.composition.load().then(this.finishLoading);
|
this.composition.load().then(this.finishLoading);
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isExpanded) {
|
||||||
|
this.page = 1;
|
||||||
|
this.showSearchComponent = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -126,11 +215,53 @@
|
|||||||
} else if (oldPath === this.navigateToPath) {
|
} else if (oldPath === this.navigateToPath) {
|
||||||
this.isNavigated = false;
|
this.isNavigated = false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
nextPage() {
|
||||||
|
if (this.page < this.lastPage) {
|
||||||
|
this.page += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
previousPage() {
|
||||||
|
if (this.page >= 1) {
|
||||||
|
this.page -= 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchChildren(input) {
|
||||||
|
this.searchValue = input;
|
||||||
|
this.page = 1;
|
||||||
|
},
|
||||||
|
scrollPage(event) {
|
||||||
|
let offsetHeight = event.target.offsetHeight,
|
||||||
|
scrollTop = event.target.scrollTop,
|
||||||
|
changePage = true;
|
||||||
|
|
||||||
|
window.clearTimeout(this.scrollLoading);
|
||||||
|
|
||||||
|
if (scrollTop > this.scrollTop && scrollTop > offsetHeight) {
|
||||||
|
this.scrollLoading = window.setTimeout(() => {
|
||||||
|
if (this.page < this.lastPage) {
|
||||||
|
this.nextPage();
|
||||||
|
event.target.scrollTop = 1;
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
} else if (this.scrollTop <= this.scrollTop && scrollTop <= 0) {
|
||||||
|
this.scrollLoading = window.setTimeout(() => {
|
||||||
|
if (this.page > 1) {
|
||||||
|
this.previousPage();
|
||||||
|
event.target.scrollTop = offsetHeight - 1;
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
this.scrollTop = scrollTop;
|
||||||
|
},
|
||||||
|
toggleSearchComponent() {
|
||||||
|
this.showSearchComponent = !this.showSearchComponent;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
viewControl,
|
viewControl,
|
||||||
ObjectLabel
|
ObjectLabel,
|
||||||
|
Search
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -33,6 +33,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="l-browse-bar__end">
|
<div class="l-browse-bar__end">
|
||||||
<div class="l-browse-bar__actions">
|
<div class="l-browse-bar__actions">
|
||||||
|
<view-switcher
|
||||||
|
:views="views"
|
||||||
|
:currentView="currentView"
|
||||||
|
@setView="setView">
|
||||||
|
</view-switcher>
|
||||||
<button v-if="notebookEnabled"
|
<button v-if="notebookEnabled"
|
||||||
class="l-browse-bar__actions__edit c-button icon-notebook"
|
class="l-browse-bar__actions__edit c-button icon-notebook"
|
||||||
title="New Notebook entry"
|
title="New Notebook entry"
|
||||||
@@ -80,20 +85,52 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
|
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
|
||||||
|
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
|
||||||
import NotebookSnapshot from '../utils/notebook-snapshot';
|
import NotebookSnapshot from '../utils/notebook-snapshot';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ContextMenuDropDown
|
ContextMenuDropDown,
|
||||||
|
ViewSwitcher
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
'openmct',
|
'openmct',
|
||||||
'objectPath'
|
'objectPath'
|
||||||
],
|
],
|
||||||
|
computed: {
|
||||||
|
views() {
|
||||||
|
return this
|
||||||
|
.openmct
|
||||||
|
.objectViews
|
||||||
|
.get(this.domainObject);
|
||||||
|
},
|
||||||
|
currentView() {
|
||||||
|
return this.views.filter(v => v.key === this.viewKey)[0] || {};
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
snapshot() {
|
snapshot() {
|
||||||
let element = document.getElementsByClassName("l-preview-window__object-view")[0];
|
let element = document.getElementsByClassName("l-preview-window__object-view")[0];
|
||||||
this.notebookSnapshot.capture(this.domainObject, element);
|
this.notebookSnapshot.capture(this.domainObject, element);
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
if (this.view) {
|
||||||
|
this.view.destroy();
|
||||||
|
this.$refs.objectView.innerHTML = '';
|
||||||
|
}
|
||||||
|
delete this.view;
|
||||||
|
delete this.viewContainer;
|
||||||
|
},
|
||||||
|
setView(view) {
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
this.viewKey = view.key;
|
||||||
|
this.viewContainer = document.createElement('div');
|
||||||
|
this.viewContainer.classList.add('c-object-view','u-contents');
|
||||||
|
this.$refs.objectView.append(this.viewContainer);
|
||||||
|
|
||||||
|
this.view = this.currentView.view(this.domainObject, false, this.objectPath);
|
||||||
|
this.view.show(this.viewContainer, false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -103,13 +140,13 @@
|
|||||||
return {
|
return {
|
||||||
domainObject: domainObject,
|
domainObject: domainObject,
|
||||||
type: type,
|
type: type,
|
||||||
notebookEnabled: false
|
notebookEnabled: false,
|
||||||
|
viewKey: undefined
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
let viewProvider = this.openmct.objectViews.get(this.domainObject)[0];
|
let view = this.openmct.objectViews.get(this.domainObject)[0];
|
||||||
this.view = viewProvider.view(this.domainObject);
|
this.setView(view);
|
||||||
this.view.show(this.$refs.objectView, false);
|
|
||||||
|
|
||||||
if (this.openmct.types.get('notebook')) {
|
if (this.openmct.types.get('notebook')) {
|
||||||
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
|
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export default class PreviewAction {
|
|||||||
* Metadata
|
* Metadata
|
||||||
*/
|
*/
|
||||||
this.name = 'Preview';
|
this.name = 'Preview';
|
||||||
|
this.key = 'preview';
|
||||||
this.description = 'Preview in large dialog';
|
this.description = 'Preview in large dialog';
|
||||||
this.cssClass = 'icon-eye-open';
|
this.cssClass = 'icon-eye-open';
|
||||||
|
|
||||||
|
|||||||
35
src/ui/preview/ViewHistoricalDataAction.js
Normal file
35
src/ui/preview/ViewHistoricalDataAction.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import PreviewAction from './PreviewAction';
|
||||||
|
|
||||||
|
export default class ViewHistoricalDataAction extends PreviewAction {
|
||||||
|
constructor(openmct) {
|
||||||
|
super(openmct);
|
||||||
|
|
||||||
|
this.name = 'View Historical Data';
|
||||||
|
this.key = 'viewHistoricalData';
|
||||||
|
this.description = 'View Historical Data in a Table or Plot';
|
||||||
|
this.cssClass = 'icon-eye-open';
|
||||||
|
this.hideInDefaultMenu = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,9 +20,11 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import PreviewAction from './PreviewAction.js';
|
import PreviewAction from './PreviewAction.js';
|
||||||
|
import ViewHistoricalDataAction from './ViewHistoricalDataAction';
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
openmct.contextMenu.registerAction(new PreviewAction(openmct));
|
openmct.contextMenu.registerAction(new PreviewAction(openmct));
|
||||||
}
|
openmct.contextMenu.registerAction(new ViewHistoricalDataAction(openmct));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,11 +223,11 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
/**
|
/**
|
||||||
* Provide a view of this object.
|
* Provide a view of this object.
|
||||||
*
|
*
|
||||||
* When called by Open MCT, this may include additional arguments
|
* When called by Open MCT, the following arguments will be passed to it:
|
||||||
* which are on the path to the object to be viewed; for instance,
|
* @param {object} domainObject - the domainObject that the view is provided for
|
||||||
* when viewing "A Folder" within "My Items", this method will be
|
* @param {boolean} isEditing - A boolean value indicating wether openmct is in a global edit mode
|
||||||
* invoked with "A Folder" (as a domain object) as the first argument,
|
* @param {array} objectPath - The current contextual object path of the view object
|
||||||
* and "My Items" as the second argument.
|
* eg current domainObject is located under MyItems which is under Root
|
||||||
*
|
*
|
||||||
* @method view
|
* @method view
|
||||||
* @memberof module:openmct.ViewProvider#
|
* @memberof module:openmct.ViewProvider#
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ define([
|
|||||||
let navigateCall = 0;
|
let navigateCall = 0;
|
||||||
let browseObject;
|
let browseObject;
|
||||||
let unobserve = undefined;
|
let unobserve = undefined;
|
||||||
|
let currentObjectPath;
|
||||||
|
|
||||||
openmct.router.route(/^\/browse\/?$/, navigateToFirstChildOfRoot);
|
openmct.router.route(/^\/browse\/?$/, navigateToFirstChildOfRoot);
|
||||||
|
|
||||||
@@ -26,7 +27,9 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
function viewObject(object, viewProvider) {
|
function viewObject(object, viewProvider) {
|
||||||
openmct.layout.$refs.browseObject.show(object, viewProvider.key, true);
|
currentObjectPath = openmct.router.path;
|
||||||
|
|
||||||
|
openmct.layout.$refs.browseObject.show(object, viewProvider.key, true, currentObjectPath);
|
||||||
openmct.layout.$refs.browseBar.domainObject = object;
|
openmct.layout.$refs.browseBar.domainObject = object;
|
||||||
openmct.layout.$refs.browseBar.viewKey = viewProvider.key;
|
openmct.layout.$refs.browseBar.viewKey = viewProvider.key;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user