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",
|
||||
"version": "0.14.0-SNAPSHOT",
|
||||
"version": "1.0.0-beta",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -29,6 +29,7 @@ define([
|
||||
"./res/templates/search.html",
|
||||
"./res/templates/search-menu.html",
|
||||
"raw-loader!./src/services/GenericSearchWorker.js",
|
||||
"raw-loader!./src/services/BareBonesSearchWorker.js",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
SearchController,
|
||||
@@ -39,6 +40,7 @@ define([
|
||||
searchTemplate,
|
||||
searchMenuTemplate,
|
||||
searchWorkerText,
|
||||
BareBonesSearchWorkerText,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
@@ -115,6 +117,10 @@ define([
|
||||
}
|
||||
],
|
||||
"workers": [
|
||||
{
|
||||
"key": "bareBonesSearchWorker",
|
||||
"scriptText": BareBonesSearchWorkerText
|
||||
},
|
||||
{
|
||||
"key": "genericSearchWorker",
|
||||
"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.useBareBones = true;
|
||||
|
||||
this.worker = this.startWorker(workerService);
|
||||
this.indexOnMutation(topic);
|
||||
|
||||
@@ -101,8 +103,14 @@ define([
|
||||
* @returns worker the created search worker.
|
||||
*/
|
||||
GenericSearchProvider.prototype.startWorker = function (workerService) {
|
||||
var worker = workerService.run('genericSearchWorker'),
|
||||
provider = this;
|
||||
var provider = this,
|
||||
worker;
|
||||
|
||||
if (this.useBareBones) {
|
||||
worker = workerService.run('bareBonesSearchWorker');
|
||||
} else {
|
||||
worker = workerService.run('genericSearchWorker');
|
||||
}
|
||||
|
||||
worker.addEventListener('message', function (messageEvent) {
|
||||
provider.onWorkerMessage(messageEvent);
|
||||
@@ -242,18 +250,34 @@ define([
|
||||
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.item.id,
|
||||
model: hit.item.model,
|
||||
score: hit.matchCount
|
||||
modelResults.hits = event.data.results.map(function (hit) {
|
||||
return {
|
||||
id: hit.id
|
||||
};
|
||||
});
|
||||
} else {
|
||||
pendingQuery = this.pendingQueries[event.data.queryId];
|
||||
modelResults = {
|
||||
total: event.data.total
|
||||
};
|
||||
});
|
||||
|
||||
modelResults.hits = event.data.results.map(function (hit) {
|
||||
return {
|
||||
id: hit.item.id,
|
||||
model: hit.item.model,
|
||||
score: hit.matchCount
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
pendingQuery.resolve(modelResults);
|
||||
delete this.pendingQueries[event.data.queryId];
|
||||
|
||||
@@ -144,6 +144,8 @@
|
||||
message.results = results
|
||||
.slice(0, data.maxResults);
|
||||
|
||||
console.log(message);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ const OUTSIDE_EDIT_PATH_BLACKLIST = ["copy", "follow", "properties", "move", "li
|
||||
export default class LegacyContextMenuAction {
|
||||
constructor(openmct, LegacyAction) {
|
||||
this.openmct = openmct;
|
||||
this.key = LegacyAction.definition.key;
|
||||
this.name = LegacyAction.definition.name;
|
||||
this.description = LegacyAction.definition.description;
|
||||
this.cssClass = LegacyAction.definition.cssClass;
|
||||
|
||||
@@ -49,6 +49,9 @@ class ContextMenuAPI {
|
||||
* a single sentence or short paragraph) of this kind of view
|
||||
* @property {string} cssClass the CSS class to apply to labels for this
|
||||
* 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
|
||||
@@ -72,12 +75,21 @@ class ContextMenuAPI {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_showContextMenuForObjectPath(objectPath, x, y) {
|
||||
_showContextMenuForObjectPath(objectPath, x, y, actionsToBeIncluded) {
|
||||
|
||||
let applicableActions = this._allActions.filter((action) => {
|
||||
if (action.appliesTo === undefined) {
|
||||
return true;
|
||||
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
return action.appliesTo(objectPath) && !action.hideInDefaultMenu;
|
||||
}
|
||||
return action.appliesTo(objectPath);
|
||||
});
|
||||
|
||||
if (this._activeContextMenu) {
|
||||
|
||||
@@ -38,7 +38,7 @@ define([
|
||||
canEdit: function (domainObject) {
|
||||
return domainObject.type === 'LadTableSet';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
view: function (domainObject, isEditing, objectPath) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
@@ -49,7 +49,8 @@ define([
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
domainObject,
|
||||
objectPath
|
||||
},
|
||||
el: element,
|
||||
template: '<lad-table-set></lad-table-set>'
|
||||
|
||||
@@ -38,7 +38,7 @@ define([
|
||||
canEdit: function (domainObject) {
|
||||
return domainObject.type === 'LadTable';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
view: function (domainObject, isEditing, objectPath) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
@@ -49,7 +49,8 @@ define([
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
domainObject,
|
||||
objectPath
|
||||
},
|
||||
el: element,
|
||||
template: '<lad-table-component></lad-table-component>'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
@@ -21,7 +22,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<tr>
|
||||
<tr @contextmenu.prevent="showContextMenu">
|
||||
<td>{{name}}</td>
|
||||
<td>{{timestamp}}</td>
|
||||
<td :class="valueClass">
|
||||
@@ -35,15 +36,25 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
const CONTEXT_MENU_ACTIONS = [
|
||||
'viewHistoricalData',
|
||||
'remove'
|
||||
];
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
inject: ['openmct', 'objectPath'],
|
||||
props: ['domainObject'],
|
||||
data() {
|
||||
let currentObjectPath = this.objectPath.slice();
|
||||
currentObjectPath.unshift(this.domainObject);
|
||||
|
||||
return {
|
||||
name: this.domainObject.name,
|
||||
timestamp: '---',
|
||||
value: '---',
|
||||
valueClass: ''
|
||||
valueClass: '',
|
||||
currentObjectPath
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -73,11 +84,15 @@ export default {
|
||||
.request(this.domainObject, {strategy: 'latest'})
|
||||
.then((array) => this.updateValues(array[array.length - 1]));
|
||||
|
||||
},
|
||||
showContextMenu(event) {
|
||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
this.limitEvaluator = openmct
|
||||
.telemetry
|
||||
|
||||
@@ -44,7 +44,7 @@ import lodash from 'lodash';
|
||||
import LadRow from './LADRow.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
components: {
|
||||
LadRow
|
||||
},
|
||||
|
||||
@@ -46,13 +46,14 @@ define([
|
||||
|
||||
return selection.every(isTelemetryObject);
|
||||
},
|
||||
view: function (selection) {
|
||||
view: function (domainObject, isEditing, objectPath) {
|
||||
let component;
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
provide: {
|
||||
openmct
|
||||
openmct,
|
||||
objectPath
|
||||
},
|
||||
components: {
|
||||
AlphanumericFormatView: AlphanumericFormatView.default
|
||||
|
||||
@@ -202,7 +202,7 @@
|
||||
return selectionPath && selectionPath.length > 1 && !singleSelectedLine;
|
||||
}
|
||||
},
|
||||
inject: ['openmct', 'options'],
|
||||
inject: ['openmct', 'options', 'objectPath'],
|
||||
props: ['domainObject'],
|
||||
components: components,
|
||||
methods: {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
@endMove="() => $emit('endMove')">
|
||||
<object-frame v-if="domainObject"
|
||||
:domain-object="domainObject"
|
||||
:object-path="objectPath"
|
||||
:object-path="currentObjectPath"
|
||||
:has-frame="item.hasFrame"
|
||||
:show-edit-view="false"
|
||||
ref="objectFrame">
|
||||
@@ -71,7 +71,7 @@
|
||||
hasFrame: hasFrameByDefault(domainObject.type)
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
inject: ['openmct', 'objectPath'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
@@ -81,7 +81,7 @@
|
||||
data() {
|
||||
return {
|
||||
domainObject: undefined,
|
||||
objectPath: []
|
||||
currentObjectPath: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -100,7 +100,7 @@
|
||||
methods: {
|
||||
setObject(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.objectPath = [this.domainObject].concat(this.openmct.router.path);
|
||||
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
|
||||
this.$nextTick(function () {
|
||||
let childContext = this.$refs.objectFrame.getSelectionContext();
|
||||
childContext.item = domainObject;
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
@endMove="() => $emit('endMove')">
|
||||
<div class="c-telemetry-view"
|
||||
:style="styleObject"
|
||||
v-if="domainObject">
|
||||
v-if="domainObject"
|
||||
@contextmenu.prevent="showContextMenu">
|
||||
<div v-if="showLabel"
|
||||
class="c-telemetry-view__label">
|
||||
<div class="c-telemetry-view__label-text">{{ domainObject.name }}</div>
|
||||
@@ -82,7 +83,8 @@
|
||||
import printj from 'printj'
|
||||
|
||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
|
||||
DEFAULT_POSITION = [1, 1];
|
||||
DEFAULT_POSITION = [1, 1],
|
||||
CONTEXT_MENU_ACTIONS = ['viewHistoricalData'];
|
||||
|
||||
export default {
|
||||
makeDefinition(openmct, gridSize, domainObject, position) {
|
||||
@@ -103,7 +105,7 @@
|
||||
size: "13px"
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
inject: ['openmct', 'objectPath'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array,
|
||||
@@ -163,7 +165,8 @@
|
||||
return {
|
||||
datum: undefined,
|
||||
formats: undefined,
|
||||
domainObject: undefined
|
||||
domainObject: undefined,
|
||||
currentObjectPath: undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -218,12 +221,16 @@
|
||||
},
|
||||
setObject(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
|
||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||
this.requestHistoricalData();
|
||||
this.subscribeToObject();
|
||||
|
||||
this.currentObjectPath = this.objectPath.slice();
|
||||
this.currentObjectPath.unshift(this.domainObject);
|
||||
|
||||
this.context = {
|
||||
item: domainObject,
|
||||
layoutItem: this.item,
|
||||
@@ -235,6 +242,9 @@
|
||||
},
|
||||
updateTelemetryFormat(format) {
|
||||
this.$emit('formatChanged', this.item, format);
|
||||
},
|
||||
showContextMenu(event) {
|
||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function DisplayLayoutPlugin(options) {
|
||||
canEdit: function (domainObject) {
|
||||
return domainObject.type === 'layout';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
view: function (domainObject, isEditing, objectPath) {
|
||||
let component;
|
||||
return {
|
||||
show(container) {
|
||||
@@ -49,13 +49,14 @@ export default function DisplayLayoutPlugin(options) {
|
||||
provide: {
|
||||
openmct,
|
||||
objectUtils,
|
||||
options
|
||||
options,
|
||||
objectPath
|
||||
},
|
||||
el: container,
|
||||
data () {
|
||||
return {
|
||||
domainObject: domainObject
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<template>
|
||||
<div class="u-contents c-filter-settings">
|
||||
<li class="grid-row c-filter-settings__setting"
|
||||
<div class="c-properties__section c-filter-settings">
|
||||
<li class="c-properties__row c-filter-settings__setting"
|
||||
v-for="(filter, index) in filterField.filters"
|
||||
:key="index">
|
||||
<div class="grid-cell label">
|
||||
<div class="c-properties__label label"
|
||||
:disabled="useGlobal">
|
||||
{{ filterField.name }} =
|
||||
</div>
|
||||
<div class="grid-cell value">
|
||||
<div class="c-properties__value value">
|
||||
<!-- EDITING -->
|
||||
<!-- String input, editing -->
|
||||
<template v-if="!filter.possibleValues && isEditing">
|
||||
<input class="c-input--flex"
|
||||
type="text"
|
||||
placeholder="Enter Value"
|
||||
:id="`${filter}filterControl`"
|
||||
:disabled="useGlobal"
|
||||
:value="persistedValue(filter)"
|
||||
@change="updateFilterValue($event, filter)">
|
||||
</template>
|
||||
@@ -21,15 +22,16 @@
|
||||
<!-- Checkbox list, editing -->
|
||||
<template v-if="filter.possibleValues && isEditing">
|
||||
<div class="c-checkbox-list__row"
|
||||
v-for="value in filter.possibleValues"
|
||||
:key="value">
|
||||
v-for="option in filter.possibleValues"
|
||||
:key="option.value">
|
||||
<input class="c-checkbox-list__input"
|
||||
type="checkbox"
|
||||
:id="`${value}filterControl`"
|
||||
@change="onUserSelect($event, filter.comparator, value)"
|
||||
:checked="isChecked(filter.comparator, value)">
|
||||
:id="`${option.value}filterControl`"
|
||||
:disabled="useGlobal"
|
||||
@change="updateFilterValue($event, filter.comparator, option.value)"
|
||||
:checked="isChecked(filter.comparator, option.value)">
|
||||
<span class="c-checkbox-list__value">
|
||||
{{ value }}
|
||||
{{ option.label }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -42,9 +44,8 @@
|
||||
|
||||
<!-- Checkbox list, NOT editing -->
|
||||
<template v-if="filter.possibleValues && !isEditing">
|
||||
<span
|
||||
v-if="persistedFilters[filter.comparator]">
|
||||
{{persistedFilters[filter.comparator].join(', ')}}
|
||||
<span v-if="persistedFilters[filter.comparator]">
|
||||
{{ getFilterLabels(filter) }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
@@ -52,26 +53,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.c-filter-settings {
|
||||
&__setting {
|
||||
.grid-cell.label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: [
|
||||
'openmct'
|
||||
],
|
||||
props: {
|
||||
filterField: Object,
|
||||
filterField: Object,
|
||||
useGlobal: Boolean,
|
||||
persistedFilters: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
@@ -81,7 +70,6 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
isEditing: this.openmct.editor.isEditing()
|
||||
}
|
||||
},
|
||||
@@ -89,9 +77,6 @@ export default {
|
||||
toggleIsEditing(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
},
|
||||
onUserSelect(event, comparator, value){
|
||||
this.$emit('onUserSelect', this.filterField.key, comparator, value, event.target.checked);
|
||||
},
|
||||
isChecked(comparator, value) {
|
||||
if (this.persistedFilters[comparator] && this.persistedFilters[comparator].includes(value)) {
|
||||
return true;
|
||||
@@ -102,8 +87,25 @@ export default {
|
||||
persistedValue(comparator) {
|
||||
return this.persistedFilters && this.persistedFilters[comparator];
|
||||
},
|
||||
updateFilterValue(event, comparator) {
|
||||
this.$emit('onTextEnter', this.filterField.key, comparator, event.target.value);
|
||||
updateFilterValue(event, comparator, 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() {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<template>
|
||||
<li>
|
||||
<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': hasActiveFilters }"></div>
|
||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||
: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__type-icon"
|
||||
:class="objectCssClass">
|
||||
@@ -13,30 +15,47 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="grid-properties" v-if="expanded">
|
||||
<filter-field
|
||||
v-for="field in filterObject.valuesWithFilters"
|
||||
:key="field.key"
|
||||
:filterField="field"
|
||||
:persistedFilters="persistedFilters[field.key]"
|
||||
@onUserSelect="collectUserSelects"
|
||||
@onTextEnter="updateTextFilter">
|
||||
</filter-field>
|
||||
</ul>
|
||||
|
||||
<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
|
||||
v-if="(!persistedFilters.useGlobal && !isEditing) || isEditing"
|
||||
v-for="metadatum in filterObject.metadataWithFilters"
|
||||
:key="metadatum.key"
|
||||
:filterField="metadatum"
|
||||
:useGlobal="persistedFilters.useGlobal"
|
||||
:persistedFilters="updatedFilters[metadatum.key]"
|
||||
@filterSelected="updateFiltersWithSelectedValue"
|
||||
@filterTextValueChanged="updateFiltersWithTextValue">
|
||||
</filter-field>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import FilterField from './FilterField.vue';
|
||||
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
FilterField
|
||||
FilterField,
|
||||
ToggleSwitch
|
||||
},
|
||||
props: {
|
||||
filterObject: Object,
|
||||
@@ -51,58 +70,74 @@ export default {
|
||||
return {
|
||||
expanded: false,
|
||||
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: {
|
||||
toggleExpanded() {
|
||||
this.expanded = !this.expanded;
|
||||
},
|
||||
collectUserSelects(key, comparator, valueName, value) {
|
||||
updateFiltersWithSelectedValue(key, comparator, valueName, value) {
|
||||
let filterValue = this.updatedFilters[key];
|
||||
|
||||
if (filterValue && filterValue[comparator]) {
|
||||
if (value === false) {
|
||||
let filteredValueName = filterValue[comparator].filter(v => v !== valueName);
|
||||
|
||||
if (filteredValueName.length === 0) {
|
||||
delete this.updatedFilters[key];
|
||||
} else {
|
||||
filterValue[comparator] = filteredValueName;
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
if (!this.updatedFilters[key]) {
|
||||
this.$set(this.updatedFilters, key, {});
|
||||
}
|
||||
this.$set(this.updatedFilters[key], comparator, [value ? valueName : undefined]);
|
||||
this.$set(this.updatedFilters[key], comparator, [valueName]);
|
||||
}
|
||||
|
||||
this.$emit('updateFilters', this.keyString, this.updatedFilters);
|
||||
},
|
||||
updateTextFilter(key, comparator, value) {
|
||||
updateFiltersWithTextValue(key, comparator, value) {
|
||||
if (value.trim() === '') {
|
||||
if (this.updatedFilters[key]) {
|
||||
delete this.updatedFilters[key];
|
||||
this.$emit('updateFilters', this.keyString, this.updatedFilters);
|
||||
}
|
||||
return;
|
||||
this.$set(this.updatedFilters, key, {});
|
||||
} else {
|
||||
this.$set(this.updatedFilters[key], comparator, value);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
useGlobalFilter(checked) {
|
||||
this.updatedFilters.useGlobal = checked;
|
||||
this.$emit('updateFilters', this.keyString, this.updatedFilters, checked);
|
||||
},
|
||||
toggleIsEditing(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
let type = this.openmct.types.get(this.filterObject.domainObject.type) || {};
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.filterObject.domainObject.identifier);
|
||||
this.objectCssClass = type.definition.cssClass;
|
||||
this.openmct.editor.on('isEditing', this.toggleIsEditing);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.openmct.editor.off('isEditing', this.toggleIsEditing);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<template>
|
||||
<ul class="tree c-tree c-properties__section" v-if="Object.keys(children).length">
|
||||
<h2 class="c-properties__header">Filters</h2>
|
||||
<ul class="c-tree c-filter-tree" v-if="Object.keys(children).length">
|
||||
<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
|
||||
v-for="(child, key) in children"
|
||||
:key="key"
|
||||
@@ -12,80 +20,230 @@
|
||||
</template>
|
||||
|
||||
<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>
|
||||
|
||||
<script>
|
||||
import FilterObject from './FilterObject.vue';
|
||||
import FilterObject from './FilterObject.vue';
|
||||
import GlobalFilters from './GlobalFilters.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FilterObject
|
||||
},
|
||||
inject: [
|
||||
'openmct'
|
||||
],
|
||||
data() {
|
||||
let providedObject = this.openmct.selection.get()[0][0].context.item;
|
||||
let persistedFilters = {};
|
||||
const FILTER_VIEW_TITLE = 'Filters applied';
|
||||
const FILTER_VIEW_TITLE_MIXED = 'Mixed filters applied';
|
||||
const USE_GLOBAL = 'useGlobal';
|
||||
|
||||
if (providedObject.configuration && providedObject.configuration.filters) {
|
||||
persistedFilters = providedObject.configuration.filters;
|
||||
}
|
||||
export default {
|
||||
components: {
|
||||
FilterObject,
|
||||
GlobalFilters
|
||||
},
|
||||
inject: [
|
||||
'openmct'
|
||||
],
|
||||
data() {
|
||||
let providedObject = this.openmct.selection.get()[0][0].context.item;
|
||||
let configuration = providedObject.configuration;
|
||||
|
||||
return {
|
||||
providedObject,
|
||||
persistedFilters,
|
||||
children: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addChildren(child) {
|
||||
let keyString = this.openmct.objects.makeKeyString(child.identifier),
|
||||
metadata = this.openmct.telemetry.getMetadata(child),
|
||||
valuesWithFilters = metadata.valueMetadatas.filter((value) => value.filters),
|
||||
childObject = {
|
||||
name: child.name,
|
||||
domainObject: child,
|
||||
valuesWithFilters
|
||||
};
|
||||
|
||||
if (childObject.valuesWithFilters.length) {
|
||||
this.$set(this.children, keyString, childObject);
|
||||
} else {
|
||||
return;
|
||||
return {
|
||||
persistedFilters: (configuration && configuration.filters) || {},
|
||||
globalFilters: (configuration && configuration.globalFilters) || {},
|
||||
globalMetadata: {},
|
||||
providedObject,
|
||||
children: {}
|
||||
}
|
||||
},
|
||||
removeChildren(identifier) {
|
||||
let keyString = this.openmct.objects.makeKeyString(identifier);
|
||||
this.$delete(this.children, keyString);
|
||||
delete this.persistedFilters[keyString];
|
||||
this.mutateConfigurationFilters();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
persistFilters(keyString, userSelects) {
|
||||
this.persistedFilters[keyString] = userSelects;
|
||||
this.mutateConfigurationFilters();
|
||||
methods: {
|
||||
addChildren(domainObject) {
|
||||
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
let metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||
let metadataWithFilters = metadata.valueMetadatas.filter(value => value.filters);
|
||||
let hasFiltersWithKeyString = this.persistedFilters[keyString] !== undefined;
|
||||
let mutateFilters = false;
|
||||
let childObject = {
|
||||
name: domainObject.name,
|
||||
domainObject: domainObject,
|
||||
metadataWithFilters
|
||||
};
|
||||
|
||||
if (metadataWithFilters.length) {
|
||||
this.$set(this.children, keyString, childObject);
|
||||
|
||||
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) {
|
||||
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.persistedFilters, keyString);
|
||||
this.mutateConfigurationFilters();
|
||||
},
|
||||
getGlobalFiltersToRemove(keyString) {
|
||||
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();
|
||||
},
|
||||
updatePersistedFilters(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() {
|
||||
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters);
|
||||
},
|
||||
mutateConfigurationGlobalFilters() {
|
||||
this.openmct.objects.mutate(this.providedObject, 'configuration.globalFilters', this.globalFilters);
|
||||
}
|
||||
},
|
||||
updatePersistedFilters(filters) {
|
||||
this.persistedFilters = filters;
|
||||
mounted(){
|
||||
this.composition = this.openmct.composition.get(this.providedObject);
|
||||
this.composition.on('add', this.addChildren);
|
||||
this.composition.on('remove', this.removeChildren);
|
||||
this.composition.load();
|
||||
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);
|
||||
},
|
||||
mutateConfigurationFilters() {
|
||||
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters);
|
||||
beforeDestroy() {
|
||||
this.composition.off('add', this.addChildren);
|
||||
this.composition.off('remove', this.removeChildren);
|
||||
this.unobserve();
|
||||
this.unobserveGlobalFilters();
|
||||
this.unobserveAllMutation();
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.composition = this.openmct.composition.get(this.providedObject);
|
||||
this.composition.on('add', this.addChildren);
|
||||
this.composition.on('remove', this.removeChildren);
|
||||
this.composition.load();
|
||||
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
|
||||
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.composition.off('add', this.addChildren);
|
||||
this.composition.off('remove', this.removeChildren);
|
||||
this.unobserve();
|
||||
this.unobserveAllMutation();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
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([
|
||||
'./filtersInspectorViewProvider'
|
||||
'./FiltersInspectorViewProvider'
|
||||
], function (
|
||||
FiltersInspectorViewProvider
|
||||
) {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
export default class GoToOriginalAction {
|
||||
constructor(openmct) {
|
||||
this.name = 'Go To Original';
|
||||
this.key = 'goToOriginal';
|
||||
this.description = 'Go to the original unlinked instance of this object';
|
||||
|
||||
this._openmct = openmct;
|
||||
|
||||
@@ -115,10 +115,22 @@
|
||||
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') }}
|
||||
</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">
|
||||
<div ng-repeat="tick in ticks track by tick.text"
|
||||
class="gl-plot-tick gl-plot-y-tick-label"
|
||||
|
||||
@@ -93,6 +93,8 @@ define([
|
||||
this.$scope.series = this.config.series.models;
|
||||
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.cursorGuideHorizontal = this.$element[0].querySelector('.js-cursor-guide--h');
|
||||
this.cursorGuide = false;
|
||||
@@ -103,9 +105,35 @@ define([
|
||||
this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this);
|
||||
this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this);
|
||||
this.listenTo(this.$scope, 'plot:reinitializeCanvas', this.initCanvas, this);
|
||||
|
||||
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, 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) {
|
||||
@@ -493,5 +521,13 @@ define([
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
export default class RemoveAction {
|
||||
constructor(openmct) {
|
||||
this.name = 'Remove';
|
||||
this.key = 'remove';
|
||||
this.description = 'Remove this object from its containing object.';
|
||||
this.cssClass = "icon-trash";
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ define([
|
||||
this.telemetryObjects = [];
|
||||
this.outstandingRequests = 0;
|
||||
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||
this.paused = false;
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
this.addTelemetryObject = this.addTelemetryObject.bind(this);
|
||||
@@ -219,7 +220,10 @@ define([
|
||||
if (!this.telemetryObjects.includes(telemetryObject)) {
|
||||
return;
|
||||
}
|
||||
this.processRealtimeDatum(datum, columnMap, keyString, limitEvaluator);
|
||||
|
||||
if (!this.paused) {
|
||||
this.processRealtimeDatum(datum, columnMap, keyString, limitEvaluator);
|
||||
}
|
||||
}, subscribeOptions);
|
||||
}
|
||||
|
||||
@@ -255,6 +259,17 @@ define([
|
||||
}
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.paused = true;
|
||||
this.boundedRows.unsubscribeFromBounds();
|
||||
}
|
||||
|
||||
unpause() {
|
||||
this.paused = false;
|
||||
this.boundedRows.subscribeToBounds();
|
||||
this.refreshData();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.boundedRows.destroy();
|
||||
this.filteredRows.destroy();
|
||||
|
||||
@@ -21,10 +21,11 @@
|
||||
*****************************************************************************/
|
||||
define(function () {
|
||||
class TelemetryTableColumn {
|
||||
constructor (openmct, metadatum) {
|
||||
constructor (openmct, metadatum, options = {selectable: false}) {
|
||||
this.metadatum = metadatum;
|
||||
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
|
||||
this.titleValue = this.metadatum.name;
|
||||
this.selectable = options.selectable;
|
||||
}
|
||||
|
||||
getKey() {
|
||||
@@ -55,8 +56,7 @@ define(function () {
|
||||
return formattedValue;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
return TelemetryTableColumn;
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ define([], function () {
|
||||
this.limitEvaluator = limitEvaluator;
|
||||
this.objectKeyString = objectKeyString;
|
||||
}
|
||||
|
||||
|
||||
getFormattedDatum(headers) {
|
||||
return Object.keys(headers).reduce((formattedDatum, columnKey) => {
|
||||
formattedDatum[columnKey] = this.getFormattedValue(columnKey);
|
||||
@@ -62,12 +62,16 @@ define([], function () {
|
||||
this.cellLimitClasses = Object.values(this.columns).reduce((alarmStateMap, column) => {
|
||||
let limitEvaluation = this.limitEvaluator.evaluate(this.datum, column.getMetadatum());
|
||||
alarmStateMap[column.getKey()] = limitEvaluation && limitEvaluation.cssClass;
|
||||
|
||||
|
||||
return alarmStateMap;
|
||||
}, {});
|
||||
}
|
||||
return this.cellLimitClasses;
|
||||
}
|
||||
|
||||
getContextMenuActions() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,4 +89,4 @@ define([], function () {
|
||||
}
|
||||
|
||||
return TelemetryTableRow;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ define([
|
||||
canEdit(domainObject) {
|
||||
return domainObject.type === 'table';
|
||||
},
|
||||
view(domainObject) {
|
||||
view(domainObject, isEditing, objectPath) {
|
||||
let table = new TelemetryTable(domainObject, openmct);
|
||||
let component;
|
||||
return {
|
||||
@@ -64,10 +64,11 @@ define([
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
table
|
||||
table,
|
||||
objectPath
|
||||
},
|
||||
el: element,
|
||||
template: '<table-component :isEditing="isEditing"></table-component>'
|
||||
template: '<table-component :isEditing="isEditing" :enableMarking="true"></table-component>'
|
||||
});
|
||||
},
|
||||
onEditModeChange(isEditing) {
|
||||
|
||||
@@ -43,7 +43,8 @@ define(
|
||||
this.sortByTimeSystem(openmct.time.timeSystem());
|
||||
|
||||
this.lastBounds = openmct.time.bounds();
|
||||
openmct.time.on('bounds', this.bounds);
|
||||
|
||||
this.subscribeToBounds();
|
||||
}
|
||||
|
||||
addOne(item) {
|
||||
@@ -140,9 +141,17 @@ define(
|
||||
return this.parseTime(row.datum[this.sortOptions.key]);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
unsubscribeFromBounds() {
|
||||
this.openmct.time.off('bounds', this.bounds);
|
||||
}
|
||||
|
||||
subscribeToBounds() {
|
||||
this.openmct.time.on('bounds', this.bounds);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.unsubscribeFromBounds();
|
||||
}
|
||||
}
|
||||
return BoundedTableRowCollection;
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div v-if="filterNames.length > 0"
|
||||
:title=title
|
||||
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 v-for="(name, index) in filterNames"
|
||||
class="c-filter-indication__label">
|
||||
@@ -33,7 +33,6 @@
|
||||
}
|
||||
|
||||
&__mixed {
|
||||
font-weight: bold;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
|
||||
@@ -58,119 +57,109 @@
|
||||
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
|
||||
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 USE_GLOBAL = 'useGlobal';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'table'],
|
||||
data() {
|
||||
return {
|
||||
filterNames: [],
|
||||
filteredTelemetry: {},
|
||||
mixed: false,
|
||||
label: '',
|
||||
title: ''
|
||||
filteredTelemetry: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isTelemetryObject(domainObject) {
|
||||
return domainObject.hasOwnProperty('telemetry');
|
||||
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: {
|
||||
setFilterNames() {
|
||||
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 => {
|
||||
let keyString= this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
|
||||
let filters = this.filteredTelemetry[keyString];
|
||||
this.telemetryKeyStrings.add(keyString);
|
||||
|
||||
if (filters !== undefined) {
|
||||
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
|
||||
Object.keys(filters).forEach(key => {
|
||||
metadataValues.forEach(metadaum => {
|
||||
|
||||
if (key === metadaum.key) {
|
||||
names.push(metadaum.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
names.push(this.getFilterNamesFromMetadata(filters, metadataValues));
|
||||
}
|
||||
});
|
||||
this.filterNames = Array.from(new Set(names));
|
||||
|
||||
names = _.flatten(names);
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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) {
|
||||
if (!_.eq(this.filteredTelemetry, 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) {
|
||||
this.filteredTelemetry = JSON.parse(JSON.stringify(filters));
|
||||
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() {
|
||||
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.updateFilters(filters);
|
||||
},
|
||||
destroyed() {
|
||||
this.table.configuration.off('change', this.handleConfigurationChanges);
|
||||
|
||||
if (this.composition) {
|
||||
this.composition.off('add', this.addChildren);
|
||||
this.composition.off('remove', this.removeChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<td>{{formattedValue}}</td>
|
||||
<td @click="selectCell($event.currentTarget, columnKey)" :title="formattedValue">{{formattedValue}}</td>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
@@ -33,11 +33,38 @@ export default {
|
||||
columnKey: {
|
||||
type: String,
|
||||
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: {
|
||||
formattedValue() {
|
||||
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>
|
||||
<ul class="c-properties__section">
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="c-properties__header">Table Column Visibility</div>
|
||||
<ul class="c-properties__section">
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -20,37 +20,54 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<tr :style="{ top: rowTop }" :class="rowClass">
|
||||
<component
|
||||
v-for="(title, key) in headers"
|
||||
<tr :style="{ top: rowTop }"
|
||||
class="noselect"
|
||||
:class="[
|
||||
rowClass,
|
||||
{'is-selected': marked}
|
||||
]"
|
||||
v-on="listeners">
|
||||
<component v-for="(title, key) in headers"
|
||||
:key="key"
|
||||
:is="componentList[key]"
|
||||
:columnKey="key"
|
||||
:style="columnWidths[key] === undefined ? {} : { width: columnWidths[key] + 'px', 'max-width': columnWidths[key] + 'px'}"
|
||||
:title="formattedRow[key]"
|
||||
:class="cellLimitClasses[key]"
|
||||
class="is-selectable"
|
||||
@click="selectCell($event.currentTarget, key)"
|
||||
:row="row"></component>
|
||||
:class="[cellLimitClasses[key], selectableColumns[key] ? 'is-selectable' : '']"
|
||||
:objectPath="objectPath"
|
||||
:row="row">
|
||||
</component>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
|
||||
<script>
|
||||
import TableCell from './table-cell.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'objectPath'],
|
||||
data: function () {
|
||||
return {
|
||||
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
|
||||
formattedRow: this.row.getFormattedDatum(this.headers),
|
||||
rowClass: this.row.getRowClass(),
|
||||
cellLimitClasses: this.row.getCellLimitClasses(),
|
||||
componentList: Object.keys(this.headers).reduce((components, header) => {
|
||||
components[header] = this.row.getCellComponentName(header) || 'table-cell';
|
||||
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,
|
||||
required: true
|
||||
},
|
||||
objectPath: {
|
||||
type: Array,
|
||||
required: false
|
||||
},
|
||||
rowIndex: {
|
||||
type: Number,
|
||||
required: false,
|
||||
@@ -81,6 +102,11 @@ export default {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
marked: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -88,26 +114,54 @@ export default {
|
||||
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
|
||||
},
|
||||
formatRow: function (row) {
|
||||
this.formattedRow = row.getFormattedDatum(this.headers);
|
||||
this.rowClass = row.getRowClass();
|
||||
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) {
|
||||
//TODO: This is a hack. Cannot get parent this way.
|
||||
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.openmct.router.path[0]
|
||||
}
|
||||
}], false);
|
||||
event.stopPropagation();
|
||||
if (this.selectableColumns[columnKey]) {
|
||||
//TODO: This is a hack. Cannot get parent this way.
|
||||
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.openmct.router.path[0]
|
||||
}
|
||||
}], false);
|
||||
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
|
||||
@@ -120,6 +174,19 @@ export default {
|
||||
},
|
||||
components: {
|
||||
TableCell
|
||||
},
|
||||
computed: {
|
||||
listeners() {
|
||||
let listenersObject = {
|
||||
click: this.markRow
|
||||
}
|
||||
|
||||
if (this.row.getContextMenuActions().length) {
|
||||
listenersObject.contextmenu = this.showContextMenu;
|
||||
}
|
||||
|
||||
return listenersObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -20,95 +20,135 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
|
||||
:class="{'loading': loading}">
|
||||
<div :style="{ 'max-width': widthWithScroll, 'min-width': '150px'}"><slot></slot></div>
|
||||
<div v-if="allowExport" class="c-table__control-bar c-control-bar">
|
||||
<div class="c-table-wrapper">
|
||||
<div class="c-table-control-bar c-control-bar">
|
||||
<button class="c-button icon-download labeled"
|
||||
v-on:click="exportAsCSV()"
|
||||
title="Export This View's Data">
|
||||
<span class="c-button__label">Export As CSV</span>
|
||||
v-if="allowExport"
|
||||
v-on:click="exportAllDataAsCSV()"
|
||||
title="Export This View's Data">
|
||||
<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>
|
||||
<slot name="buttons"></slot>
|
||||
</div>
|
||||
<div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div>
|
||||
<!-- Headers table -->
|
||||
<div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}">
|
||||
<table class="c-table__headers c-telemetry-table__headers">
|
||||
<thead>
|
||||
<tr class="c-telemetry-table__headers__labels">
|
||||
<table-column-header
|
||||
v-for="(title, key, headerIndex) in headers"
|
||||
:key="key"
|
||||
:headerKey="key"
|
||||
:headerIndex="headerIndex"
|
||||
@sort="allowSorting && sortBy(key)"
|
||||
@resizeColumn="resizeColumn"
|
||||
@dropTargetOffsetChanged="setDropTargetOffset"
|
||||
@dropTargetActive="dropTargetActive"
|
||||
@reorderColumn="reorderColumn"
|
||||
@resizeColumnEnd="updateConfiguredColumnWidths"
|
||||
:columnWidth="columnWidths[key]"
|
||||
:sortOptions="sortOptions"
|
||||
:isEditing="isEditing"
|
||||
><span class="c-telemetry-table__headers__label">{{title}}</span>
|
||||
</table-column-header>
|
||||
</tr>
|
||||
<tr class="c-telemetry-table__headers__filter">
|
||||
<table-column-header
|
||||
v-for="(title, key, headerIndex) in headers"
|
||||
:key="key"
|
||||
:headerKey="key"
|
||||
:headerIndex="headerIndex"
|
||||
@resizeColumn="resizeColumn"
|
||||
@dropTargetOffsetChanged="setDropTargetOffset"
|
||||
@dropTargetActive="dropTargetActive"
|
||||
@reorderColumn="reorderColumn"
|
||||
@resizeColumnEnd="updateConfiguredColumnWidths"
|
||||
:columnWidth="columnWidths[key]"
|
||||
:isEditing="isEditing"
|
||||
>
|
||||
<search class="c-table__search"
|
||||
v-model="filters[key]"
|
||||
v-on:input="filterChanged(key)"
|
||||
v-on:clear="clearFilter(key)" />
|
||||
</table-column-header>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<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>
|
||||
<!-- Headers table -->
|
||||
<div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}">
|
||||
<table class="c-table__headers c-telemetry-table__headers">
|
||||
<thead>
|
||||
<tr class="c-telemetry-table__headers__labels">
|
||||
<table-column-header
|
||||
v-for="(title, key, headerIndex) in headers"
|
||||
:key="key"
|
||||
:headerKey="key"
|
||||
:headerIndex="headerIndex"
|
||||
@sort="allowSorting && sortBy(key)"
|
||||
@resizeColumn="resizeColumn"
|
||||
@dropTargetOffsetChanged="setDropTargetOffset"
|
||||
@dropTargetActive="dropTargetActive"
|
||||
@reorderColumn="reorderColumn"
|
||||
@resizeColumnEnd="updateConfiguredColumnWidths"
|
||||
:columnWidth="columnWidths[key]"
|
||||
:sortOptions="sortOptions"
|
||||
:isEditing="isEditing"
|
||||
><span class="c-telemetry-table__headers__label">{{title}}</span>
|
||||
</table-column-header>
|
||||
</tr>
|
||||
<tr class="c-telemetry-table__headers__filter">
|
||||
<table-column-header
|
||||
v-for="(title, key, headerIndex) in headers"
|
||||
:key="key"
|
||||
:headerKey="key"
|
||||
:headerIndex="headerIndex"
|
||||
@resizeColumn="resizeColumn"
|
||||
@dropTargetOffsetChanged="setDropTargetOffset"
|
||||
@dropTargetActive="dropTargetActive"
|
||||
@reorderColumn="reorderColumn"
|
||||
@resizeColumnEnd="updateConfiguredColumnWidths"
|
||||
:columnWidth="columnWidths[key]"
|
||||
:isEditing="isEditing"
|
||||
>
|
||||
<search class="c-table__search"
|
||||
v-model="filters[key]"
|
||||
v-on:input="filterChanged(key)"
|
||||
v-on:clear="clearFilter(key)" />
|
||||
</table-column-header>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Content table -->
|
||||
<div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll" :style="{ 'max-width': widthWithScroll}">
|
||||
<div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth + 'px' }"></div>
|
||||
<table class="c-table__body c-telemetry-table__body js-telemetry-table__content"
|
||||
:style="{ height: totalHeight + 'px'}">
|
||||
<tbody>
|
||||
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
|
||||
:headers="headers"
|
||||
:columnWidths="columnWidths"
|
||||
:rowIndex="rowIndex"
|
||||
:objectPath="objectPath"
|
||||
:rowOffset="rowOffset"
|
||||
:rowHeight="rowHeight"
|
||||
:row="row"
|
||||
:marked="row.marked"
|
||||
@mark="markRow"
|
||||
@unmark="unmarkRow"
|
||||
@markMultipleConcurrent="markMultipleConcurrentRows">
|
||||
</telemetry-table-row>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Sizing table -->
|
||||
<table class="c-telemetry-table__sizing js-telemetry-table__sizing" :style="sizingTableWidth">
|
||||
<tr>
|
||||
<template v-for="(title, key) in headers">
|
||||
<th :key="key" :style="{ width: configuredColumnWidths[key] + 'px', 'max-width': configuredColumnWidths[key] + 'px'}">{{title}}</th>
|
||||
</template>
|
||||
</tr>
|
||||
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
|
||||
:key="objectKeyString"
|
||||
:headers="headers"
|
||||
:columnWidths="configuredColumnWidths"
|
||||
:row="sizingRowData">
|
||||
</telemetry-table-row>
|
||||
</table>
|
||||
<telemetry-filter-indicator></telemetry-filter-indicator>
|
||||
</div>
|
||||
<!-- Content table -->
|
||||
<div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll" :style="{ 'max-width': widthWithScroll}">
|
||||
<div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth + 'px' }"></div>
|
||||
<table class="c-table__body c-telemetry-table__body js-telemetry-table__content"
|
||||
:style="{ height: totalHeight + 'px'}">
|
||||
<tbody>
|
||||
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
|
||||
:headers="headers"
|
||||
:columnWidths="columnWidths"
|
||||
:rowIndex="rowIndex"
|
||||
:rowOffset="rowOffset"
|
||||
:rowHeight="rowHeight"
|
||||
:row="row">
|
||||
</telemetry-table-row>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Sizing table -->
|
||||
<table class="c-telemetry-table__sizing js-telemetry-table__sizing" :style="sizingTableWidth">
|
||||
<tr>
|
||||
<template v-for="(title, key) in headers">
|
||||
<th :key="key" :style="{ width: configuredColumnWidths[key] + 'px', 'max-width': configuredColumnWidths[key] + 'px'}">{{title}}</th>
|
||||
</template>
|
||||
</tr>
|
||||
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
|
||||
:headers="headers"
|
||||
:columnWidths="configuredColumnWidths"
|
||||
:row="sizingRowData">
|
||||
</telemetry-table-row>
|
||||
</table>
|
||||
<telemetry-filter-indicator></telemetry-filter-indicator>
|
||||
</div>
|
||||
</div><!-- closes c-table-wrapper -->
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -134,7 +174,7 @@
|
||||
display: block;
|
||||
flex: 1 0 auto;
|
||||
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 {
|
||||
@@ -219,6 +259,10 @@
|
||||
align-items: stretch;
|
||||
position: absolute;
|
||||
height: 18px; // Needed when a row has empty values in its cells
|
||||
|
||||
&.is-selected {
|
||||
background-color: $colorSelectedBg;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
@@ -269,6 +313,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.paused {
|
||||
border: 1px solid #ff9900;
|
||||
}
|
||||
|
||||
/******************************* LEGACY */
|
||||
.s-status-taking-snapshot,
|
||||
.overlay.snapshot {
|
||||
@@ -301,7 +349,7 @@ export default {
|
||||
search,
|
||||
TelemetryFilterIndicator
|
||||
},
|
||||
inject: ['table', 'openmct'],
|
||||
inject: ['table', 'openmct', 'objectPath'],
|
||||
props: {
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
@@ -318,6 +366,10 @@ export default {
|
||||
allowSorting: {
|
||||
'type': Boolean,
|
||||
'default': true
|
||||
},
|
||||
enableMarking: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -346,7 +398,10 @@ export default {
|
||||
dropOffsetLeft: undefined,
|
||||
isDropTargetActive: false,
|
||||
isAutosizeEnabled: configuration.autosize,
|
||||
scrollW: 0
|
||||
scrollW: 0,
|
||||
markCounter: 0,
|
||||
paused: false,
|
||||
markedRows: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -532,15 +587,27 @@ export default {
|
||||
// which causes subsequent scroll to use an out of date height.
|
||||
this.contentTable.style.height = this.totalHeight + 'px';
|
||||
},
|
||||
exportAsCSV() {
|
||||
exportAsCSV(data) {
|
||||
const headerKeys = Object.keys(this.headers);
|
||||
const justTheData = this.table.filteredRows.getRows()
|
||||
.map(row => row.getFormattedDatum(this.headers));
|
||||
this.csvExporter.export(justTheData, {
|
||||
|
||||
this.csvExporter.export(data, {
|
||||
filename: this.table.domainObject.name + '.csv',
|
||||
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) {
|
||||
this.loading = loading;
|
||||
},
|
||||
@@ -632,8 +699,105 @@ export default {
|
||||
clearRowsAndRerender() {
|
||||
this.visibleRows = [];
|
||||
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() {
|
||||
this.filterChanged = _.debounce(this.filterChanged, 500);
|
||||
|
||||
@@ -167,6 +167,7 @@ export default {
|
||||
this.xAxis.scale(this.xScale);
|
||||
this.xAxis.tickFormat(utcMultiTimeFormat);
|
||||
this.axisElement.call(this.xAxis);
|
||||
this.setScale();
|
||||
},
|
||||
getActiveFormatter() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
|
||||
@@ -60,7 +60,6 @@ export default {
|
||||
.filter(menuOption => menuOption.clock === (clock && clock.key))
|
||||
.map(menuOption => JSON.parse(JSON.stringify(this.openmct.time.timeSystems.get(menuOption.timeSystem))));
|
||||
},
|
||||
|
||||
setTimeSystemFromView(timeSystem) {
|
||||
if (timeSystem.key !== this.selectedTimeSystem.key) {
|
||||
let activeClock = this.openmct.time.clock();
|
||||
@@ -69,7 +68,15 @@ export default {
|
||||
timeSystem: timeSystem.key
|
||||
});
|
||||
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 {
|
||||
this.openmct.time.timeSystem(timeSystem.key);
|
||||
this.openmct.time.clockOffsets(configuration.clockOffsets);
|
||||
|
||||
@@ -116,6 +116,7 @@ $colorOk: #33cc33;
|
||||
$colorOkFg: #fff;
|
||||
$colorFilterBg: #44449c;
|
||||
$colorFilterFg: #8984e9;
|
||||
$colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
|
||||
|
||||
// States
|
||||
$colorPausedBg: #ff9900;
|
||||
@@ -211,6 +212,10 @@ $btnStdH: 24px;
|
||||
$colorCursorGuide: rgba(white, 0.6);
|
||||
$shdwCursorGuide: rgba(black, 0.4) 0 0 2px;
|
||||
$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
|
||||
$colorMenuBg: pullForward($colorBodyBg, 15%);
|
||||
@@ -425,7 +430,3 @@ $createBtnTextTransform: uppercase;
|
||||
background: linear-gradient(pullForward($c, 5%), $c);
|
||||
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;
|
||||
$colorFilterBg: #44449c;
|
||||
$colorFilterFg: #8984e9;
|
||||
$colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
|
||||
|
||||
// States
|
||||
$colorPausedBg: #ff9900;
|
||||
@@ -215,6 +216,10 @@ $btnStdH: 24px;
|
||||
$colorCursorGuide: rgba(white, 0.6);
|
||||
$shdwCursorGuide: rgba(black, 0.4) 0 0 2px;
|
||||
$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
|
||||
$colorMenuBg: pullForward($colorBodyBg, 15%);
|
||||
@@ -430,10 +435,6 @@ $createBtnTextTransform: uppercase;
|
||||
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 */
|
||||
.c-frame {
|
||||
&:not(.no-frame) {
|
||||
|
||||
@@ -116,6 +116,7 @@ $colorOk: #33cc33;
|
||||
$colorOkFg: #fff;
|
||||
$colorFilterBg: #a29fe2;
|
||||
$colorFilterFg: #fff;
|
||||
$colorFilter: $colorFilterBg; // Standalone against $colorBodyBg
|
||||
|
||||
// States
|
||||
$colorPausedBg: #ff9900;
|
||||
@@ -211,6 +212,10 @@ $btnStdH: 24px;
|
||||
$colorCursorGuide: rgba(black, 0.6);
|
||||
$shdwCursorGuide: rgba(white, 0.4) 0 0 2px;
|
||||
$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
|
||||
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
||||
@@ -424,7 +429,3 @@ $createBtnTextTransform: uppercase;
|
||||
@mixin themedButton($c: $colorBtnBg) {
|
||||
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
|
||||
|
||||
/************************** VISUAL */
|
||||
$controlDisabledOpacity: 0.3;
|
||||
$controlDisabledOpacity: 0.5;
|
||||
|
||||
/************************** UI ELEMENTS */
|
||||
/*************** Progress Bar */
|
||||
|
||||
@@ -279,7 +279,10 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
// SELECTS
|
||||
select {
|
||||
@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-position: right .4em top 80%, 0 0;
|
||||
border: none;
|
||||
@@ -600,15 +603,15 @@ select {
|
||||
margin-right: $m;
|
||||
}
|
||||
|
||||
.c-separator {
|
||||
@include cToolbarSeparator();
|
||||
}
|
||||
|
||||
.c-toolbar {
|
||||
> * + * {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
&__button {
|
||||
|
||||
}
|
||||
|
||||
&__separator {
|
||||
@include cToolbarSeparator();
|
||||
}
|
||||
|
||||
@@ -202,6 +202,11 @@ body.desktop .has-local-controls {
|
||||
}
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
opacity: 0.5;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/******************************************************** STATES */
|
||||
@mixin spinner($b: 5px, $c: $colorKey) {
|
||||
animation-name: rotation-centered;
|
||||
|
||||
@@ -161,8 +161,7 @@ mct-plot {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
&.gl-plot-y-label,
|
||||
&.l-plot-y-label {
|
||||
&.gl-plot-y-label {
|
||||
$x: -50%;
|
||||
$r: -90deg;
|
||||
transform-origin: 50% 0;
|
||||
@@ -172,6 +171,12 @@ mct-plot {
|
||||
left: 0;
|
||||
top: 50%;
|
||||
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) {
|
||||
background: rgba($c, $a) !important;
|
||||
background-color: rgba($c, $a) !important;
|
||||
|
||||
@@ -76,23 +76,43 @@ div.c-table {
|
||||
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 {
|
||||
// Can be used by any type of table, scrolling, LAD, etc.
|
||||
$min-w: 50px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
&__control-bar,
|
||||
&__headers-w {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/******************************* ELEMENTS */
|
||||
|
||||
&__control-bar {
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
thead tr,
|
||||
&.c-table__headers {
|
||||
background: $colorTabHeaderBg;
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
class="c-so-view__object-view"
|
||||
ref="objectView"
|
||||
:object="domainObject"
|
||||
:show-edit-view="showEditView">
|
||||
:show-edit-view="showEditView"
|
||||
:object-path="objectPath">
|
||||
</object-view>
|
||||
</div>
|
||||
</template>
|
||||
@@ -123,7 +124,12 @@
|
||||
.c-click-icon,
|
||||
.c-button {
|
||||
// Shrink buttons a bit when they appear in a frame
|
||||
font-size: 0.8em;
|
||||
align-items: baseline;
|
||||
font-size: 0.85em;
|
||||
padding: 3px 5px;
|
||||
&:before {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
align-items: center;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
padding: $interiorMarginSm;
|
||||
padding: $interiorMarginSm 1px;
|
||||
white-space: nowrap;
|
||||
|
||||
&__name {
|
||||
|
||||
@@ -9,7 +9,8 @@ export default {
|
||||
props: {
|
||||
view: String,
|
||||
object: Object,
|
||||
showEditView: Boolean
|
||||
showEditView: Boolean,
|
||||
objectPath: Array
|
||||
},
|
||||
destroyed() {
|
||||
this.clear();
|
||||
@@ -91,17 +92,19 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
let objectPath = this.currentObjectPath || this.objectPath;
|
||||
|
||||
if (provider.edit && this.showEditView) {
|
||||
if (this.openmct.editor.isEditing()) {
|
||||
this.currentView = provider.edit(this.currentObject);
|
||||
this.currentView = provider.edit(this.currentObject, true, objectPath);
|
||||
} else {
|
||||
this.currentView = provider.view(this.currentObject, false);
|
||||
this.currentView = provider.view(this.currentObject, false, objectPath);
|
||||
}
|
||||
|
||||
this.openmct.editor.on('isEditing', this.toggleEditView);
|
||||
this.releaseEditModeHandler = () => this.openmct.editor.off('isEditing', this.toggleEditView);
|
||||
} 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) {
|
||||
this.openmct.editor.on('isEditing', this.invokeEditModeHandler);
|
||||
@@ -117,7 +120,7 @@ export default {
|
||||
|
||||
this.openmct.objectViews.on('clearData', this.clearData);
|
||||
},
|
||||
show(object, viewKey, immediatelySelect) {
|
||||
show(object, viewKey, immediatelySelect, currentObjectPath) {
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
@@ -132,6 +135,11 @@ export default {
|
||||
}
|
||||
|
||||
this.currentObject = object;
|
||||
|
||||
if (currentObjectPath) {
|
||||
this.currentObjectPath = currentObjectPath;
|
||||
}
|
||||
|
||||
this.unlisten = this.openmct.objects.observe(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;
|
||||
}
|
||||
|
||||
&__row + &__row {
|
||||
> [class*="__"] {
|
||||
&__row + &__row,
|
||||
&__section + &__section {
|
||||
[class*="__label"],
|
||||
[class*="__value"] {
|
||||
// Row borders, effected via border-top on child elements of the row
|
||||
border-top: 1px solid $colorInspectorSectionHeaderBg;
|
||||
}
|
||||
|
||||
@@ -19,28 +19,11 @@
|
||||
</div>
|
||||
|
||||
<div class="l-browse-bar__end">
|
||||
<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>
|
||||
<view-switcher
|
||||
:currentView="currentView"
|
||||
:views="views"
|
||||
@setView="setView">
|
||||
</view-switcher>
|
||||
<!-- Action buttons -->
|
||||
<div class="l-browse-bar__actions">
|
||||
<button v-if="notebookEnabled"
|
||||
@@ -77,14 +60,15 @@
|
||||
|
||||
<script>
|
||||
import NotebookSnapshot from '../utils/notebook-snapshot';
|
||||
import ViewSwitcher from './ViewSwitcher.vue';
|
||||
const PLACEHOLDER_OBJECT = {};
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
ViewSwitcher
|
||||
},
|
||||
methods: {
|
||||
toggleViewMenu() {
|
||||
this.showViewMenu = !this.showViewMenu;
|
||||
},
|
||||
toggleSaveMenu() {
|
||||
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;
|
||||
transition: background 150ms ease;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $colorItemTreeHoverBg;
|
||||
.c-tree__item__type-icon:before {
|
||||
@@ -116,10 +120,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__view-control {
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
|
||||
// Object labels in trees
|
||||
&__label {
|
||||
// <a> tag that holds type icon and name.
|
||||
|
||||
@@ -18,10 +18,47 @@
|
||||
<span class="c-tree__item__label">Loading...</span>
|
||||
</div>
|
||||
</li>
|
||||
<tree-item v-for="child in children"
|
||||
:key="child.id"
|
||||
:node="child">
|
||||
</tree-item>
|
||||
|
||||
<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"
|
||||
:node="child">
|
||||
</tree-item>
|
||||
</div>
|
||||
</template>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
@@ -29,6 +66,9 @@
|
||||
<script>
|
||||
import viewControl from '../components/viewControl.vue';
|
||||
import ObjectLabel from '../components/ObjectLabel.vue';
|
||||
import Search from '../components/search.vue';
|
||||
|
||||
const PAGE_THRESHOLD = 50;
|
||||
|
||||
export default {
|
||||
name: 'tree-item',
|
||||
@@ -44,7 +84,13 @@
|
||||
loaded: false,
|
||||
isNavigated: this.navigateToPath === this.openmct.router.currentLocation.path,
|
||||
children: [],
|
||||
expanded: false
|
||||
expanded: false,
|
||||
page: 1,
|
||||
page_threshold: PAGE_THRESHOLD,
|
||||
searchValue: '',
|
||||
filteredChildren: [],
|
||||
scrollTop: 0,
|
||||
showSearchComponent: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -55,6 +101,44 @@
|
||||
}
|
||||
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
|
||||
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() {
|
||||
@@ -97,6 +181,11 @@
|
||||
this.composition.load().then(this.finishLoading);
|
||||
this.isLoading = true;
|
||||
}
|
||||
|
||||
if (!isExpanded) {
|
||||
this.page = 1;
|
||||
this.showSearchComponent = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -126,11 +215,53 @@
|
||||
} else if (oldPath === this.navigateToPath) {
|
||||
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: {
|
||||
viewControl,
|
||||
ObjectLabel
|
||||
ObjectLabel,
|
||||
Search
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -33,6 +33,11 @@
|
||||
</div>
|
||||
<div class="l-browse-bar__end">
|
||||
<div class="l-browse-bar__actions">
|
||||
<view-switcher
|
||||
:views="views"
|
||||
:currentView="currentView"
|
||||
@setView="setView">
|
||||
</view-switcher>
|
||||
<button v-if="notebookEnabled"
|
||||
class="l-browse-bar__actions__edit c-button icon-notebook"
|
||||
title="New Notebook entry"
|
||||
@@ -80,20 +85,52 @@
|
||||
|
||||
<script>
|
||||
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
|
||||
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
|
||||
import NotebookSnapshot from '../utils/notebook-snapshot';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContextMenuDropDown
|
||||
ContextMenuDropDown,
|
||||
ViewSwitcher
|
||||
},
|
||||
inject: [
|
||||
'openmct',
|
||||
'objectPath'
|
||||
],
|
||||
computed: {
|
||||
views() {
|
||||
return this
|
||||
.openmct
|
||||
.objectViews
|
||||
.get(this.domainObject);
|
||||
},
|
||||
currentView() {
|
||||
return this.views.filter(v => v.key === this.viewKey)[0] || {};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
snapshot() {
|
||||
let element = document.getElementsByClassName("l-preview-window__object-view")[0];
|
||||
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() {
|
||||
@@ -103,13 +140,13 @@
|
||||
return {
|
||||
domainObject: domainObject,
|
||||
type: type,
|
||||
notebookEnabled: false
|
||||
notebookEnabled: false,
|
||||
viewKey: undefined
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
let viewProvider = this.openmct.objectViews.get(this.domainObject)[0];
|
||||
this.view = viewProvider.view(this.domainObject);
|
||||
this.view.show(this.$refs.objectView, false);
|
||||
let view = this.openmct.objectViews.get(this.domainObject)[0];
|
||||
this.setView(view);
|
||||
|
||||
if (this.openmct.types.get('notebook')) {
|
||||
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
|
||||
|
||||
@@ -28,6 +28,7 @@ export default class PreviewAction {
|
||||
* Metadata
|
||||
*/
|
||||
this.name = 'Preview';
|
||||
this.key = 'preview';
|
||||
this.description = 'Preview in large dialog';
|
||||
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.
|
||||
*****************************************************************************/
|
||||
import PreviewAction from './PreviewAction.js';
|
||||
import ViewHistoricalDataAction from './ViewHistoricalDataAction';
|
||||
|
||||
export default function () {
|
||||
return function (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.
|
||||
*
|
||||
* When called by Open MCT, this may include additional arguments
|
||||
* which are on the path to the object to be viewed; for instance,
|
||||
* when viewing "A Folder" within "My Items", this method will be
|
||||
* invoked with "A Folder" (as a domain object) as the first argument,
|
||||
* and "My Items" as the second argument.
|
||||
* When called by Open MCT, the following arguments will be passed to it:
|
||||
* @param {object} domainObject - the domainObject that the view is provided for
|
||||
* @param {boolean} isEditing - A boolean value indicating wether openmct is in a global edit mode
|
||||
* @param {array} objectPath - The current contextual object path of the view object
|
||||
* eg current domainObject is located under MyItems which is under Root
|
||||
*
|
||||
* @method view
|
||||
* @memberof module:openmct.ViewProvider#
|
||||
|
||||
@@ -8,6 +8,7 @@ define([
|
||||
let navigateCall = 0;
|
||||
let browseObject;
|
||||
let unobserve = undefined;
|
||||
let currentObjectPath;
|
||||
|
||||
openmct.router.route(/^\/browse\/?$/, navigateToFirstChildOfRoot);
|
||||
|
||||
@@ -26,7 +27,9 @@ define([
|
||||
});
|
||||
|
||||
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.viewKey = viewProvider.key;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user