mirror of
https://github.com/nasa/openmct.git
synced 2025-01-08 22:12:42 +00:00
Merge branch 'master' into remove-action-tests
Merging master
This commit is contained in:
commit
6bd0246db6
@ -28,6 +28,16 @@ define([
|
||||
domain: 2
|
||||
}
|
||||
},
|
||||
// Need to enable "LocalTimeSystem" plugin to make use of this
|
||||
// {
|
||||
// key: "local",
|
||||
// name: "Time",
|
||||
// format: "local-format",
|
||||
// source: "utc",
|
||||
// hints: {
|
||||
// domain: 3
|
||||
// }
|
||||
// },
|
||||
{
|
||||
key: "sin",
|
||||
name: "Sine",
|
||||
@ -61,6 +71,15 @@ define([
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "local",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
source: "utc",
|
||||
hints: {
|
||||
domain: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "state",
|
||||
source: "value",
|
||||
|
40
index.html
40
index.html
@ -34,8 +34,8 @@
|
||||
<body>
|
||||
</body>
|
||||
<script>
|
||||
const FIVE_MINUTES = 5 * 60 * 1000;
|
||||
const THIRTY_MINUTES = 30 * 60 * 1000;
|
||||
const THIRTY_SECONDS = 30 * 1000;
|
||||
const THIRTY_MINUTES = THIRTY_SECONDS * 60;
|
||||
|
||||
[
|
||||
'example/eventGenerator'
|
||||
@ -63,7 +63,39 @@
|
||||
bounds: {
|
||||
start: Date.now() - THIRTY_MINUTES,
|
||||
end: Date.now()
|
||||
}
|
||||
},
|
||||
// commonly used bounds can be stored in history
|
||||
// bounds (start and end) can accept either a milliseconds number
|
||||
// or a callback function returning a milliseconds number
|
||||
// a function is useful for invoking Date.now() at exact moment of preset selection
|
||||
presets: [
|
||||
{
|
||||
label: 'Last Day',
|
||||
bounds: {
|
||||
start: () => Date.now() - 1000 * 60 * 60 * 24,
|
||||
end: () => Date.now()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Last 2 hours',
|
||||
bounds: {
|
||||
start: () => Date.now() - 1000 * 60 * 60 * 2,
|
||||
end: () => Date.now()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Last hour',
|
||||
bounds: {
|
||||
start: () => Date.now() - 1000 * 60 * 60,
|
||||
end: () => Date.now()
|
||||
}
|
||||
}
|
||||
],
|
||||
// maximum recent bounds to retain in conductor history
|
||||
records: 10,
|
||||
// maximum duration between start and end bounds
|
||||
// for utc-based time systems this is in milliseconds
|
||||
limit: 1000 * 60 * 60 * 24
|
||||
},
|
||||
{
|
||||
name: "Realtime",
|
||||
@ -71,7 +103,7 @@
|
||||
clock: 'local',
|
||||
clockOffsets: {
|
||||
start: - THIRTY_MINUTES,
|
||||
end: FIVE_MINUTES
|
||||
end: THIRTY_SECONDS
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -81,10 +81,15 @@ define(
|
||||
* context.
|
||||
*/
|
||||
PropertiesAction.appliesTo = function (context) {
|
||||
|
||||
var domainObject = (context || {}).domainObject,
|
||||
type = domainObject && domainObject.getCapability('type'),
|
||||
creatable = type && type.hasFeature('creation');
|
||||
|
||||
if (domainObject && domainObject.model && domainObject.model.locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only allow creatable types to be edited
|
||||
return domainObject && creatable;
|
||||
};
|
||||
|
@ -40,7 +40,18 @@ define(
|
||||
}
|
||||
|
||||
MoveAction.prototype = Object.create(AbstractComposeAction.prototype);
|
||||
MoveAction.appliesTo = AbstractComposeAction.appliesTo;
|
||||
|
||||
MoveAction.appliesTo = function (context) {
|
||||
var applicableObject =
|
||||
context.selectedObject || context.domainObject;
|
||||
|
||||
if (applicableObject && applicableObject.model.locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(applicableObject &&
|
||||
applicableObject.hasCapability('context'));
|
||||
};
|
||||
|
||||
return MoveAction;
|
||||
}
|
||||
|
@ -216,8 +216,14 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
|
||||
};
|
||||
|
||||
ImportAsJSONAction.appliesTo = function (context) {
|
||||
return context.domainObject !== undefined &&
|
||||
context.domainObject.hasCapability("composition");
|
||||
let domainObject = context.domainObject;
|
||||
|
||||
if (domainObject && domainObject.model.locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return domainObject !== undefined &&
|
||||
domainObject.hasCapability("composition");
|
||||
};
|
||||
|
||||
return ImportAsJSONAction;
|
||||
|
@ -64,7 +64,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
formattedTimestamp() {
|
||||
return this.timestamp !== undefined ? this.formats[this.timestampKey].format(this.timestamp) : '---';
|
||||
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -110,11 +110,11 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
updateValues(datum) {
|
||||
let newTimestamp = this.formats[this.timestampKey].parse(datum),
|
||||
let newTimestamp = this.getParsedTimestamp(datum),
|
||||
limit;
|
||||
|
||||
if(this.shouldUpdate(newTimestamp)) {
|
||||
this.timestamp = this.formats[this.timestampKey].parse(datum);
|
||||
this.timestamp = newTimestamp;
|
||||
this.value = this.formats[this.valueKey].format(datum);
|
||||
limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
|
||||
if (limit) {
|
||||
@ -125,9 +125,12 @@ export default {
|
||||
}
|
||||
},
|
||||
shouldUpdate(newTimestamp) {
|
||||
return (this.timestamp === undefined) ||
|
||||
(this.inBounds(newTimestamp) &&
|
||||
newTimestamp > this.timestamp);
|
||||
let newTimestampInBounds = this.inBounds(newTimestamp),
|
||||
noExistingTimestamp = this.timestamp === undefined,
|
||||
newTimestampIsLatest = newTimestamp > this.timestamp;
|
||||
|
||||
return newTimestampInBounds &&
|
||||
(noExistingTimestamp || newTimestampIsLatest);
|
||||
},
|
||||
requestHistory() {
|
||||
this.openmct
|
||||
@ -146,6 +149,7 @@ export default {
|
||||
updateBounds(bounds, isTick) {
|
||||
this.bounds = bounds;
|
||||
if(!isTick) {
|
||||
this.resetValues();
|
||||
this.requestHistory();
|
||||
}
|
||||
},
|
||||
@ -153,13 +157,34 @@ export default {
|
||||
return timestamp >= this.bounds.start && timestamp <= this.bounds.end;
|
||||
},
|
||||
updateTimeSystem(timeSystem) {
|
||||
this.value = '---';
|
||||
this.timestamp = '---';
|
||||
this.valueClass = '';
|
||||
this.resetValues();
|
||||
this.timestampKey = timeSystem.key;
|
||||
},
|
||||
showContextMenu(event) {
|
||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
|
||||
},
|
||||
resetValues() {
|
||||
this.value = '---';
|
||||
this.timestamp = undefined;
|
||||
this.valueClass = '';
|
||||
},
|
||||
getParsedTimestamp(timestamp) {
|
||||
if(this.timeSystemFormat()) {
|
||||
return this.formats[this.timestampKey].parse(timestamp);
|
||||
}
|
||||
},
|
||||
getFormattedTimestamp(timestamp) {
|
||||
if(this.timeSystemFormat()) {
|
||||
return this.formats[this.timestampKey].format(timestamp);
|
||||
}
|
||||
},
|
||||
timeSystemFormat() {
|
||||
if(this.formats[this.timestampKey]) {
|
||||
return true;
|
||||
} else {
|
||||
console.warn(`No formatter for ${this.timestampKey} time system for ${this.domainObject.name}.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,6 +150,7 @@ export default class Condition extends EventEmitter {
|
||||
criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct);
|
||||
}
|
||||
criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
||||
criterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
|
||||
if (!this.criteria) {
|
||||
this.criteria = [];
|
||||
}
|
||||
@ -178,10 +179,12 @@ export default class Condition extends EventEmitter {
|
||||
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
|
||||
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
|
||||
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
||||
newCriterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
|
||||
|
||||
let criterion = found.item;
|
||||
criterion.unsubscribe();
|
||||
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
||||
criterion.off('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
|
||||
this.criteria.splice(found.index, 1, newCriterion);
|
||||
this.updateDescription();
|
||||
}
|
||||
@ -194,6 +197,9 @@ export default class Condition extends EventEmitter {
|
||||
criterion.off('criterionUpdated', (obj) => {
|
||||
this.handleCriterionUpdated(obj);
|
||||
});
|
||||
criterion.off('telemetryIsStale', (obj) => {
|
||||
this.handleStaleCriterion(obj);
|
||||
});
|
||||
criterion.destroy();
|
||||
this.criteria.splice(found.index, 1);
|
||||
this.updateDescription();
|
||||
@ -211,6 +217,18 @@ export default class Condition extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
handleStaleCriterion(updatedCriterion) {
|
||||
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
|
||||
let latestTimestamp = {};
|
||||
latestTimestamp = getLatestTimestamp(
|
||||
latestTimestamp,
|
||||
updatedCriterion.data,
|
||||
this.timeSystems,
|
||||
this.openmct.time.timeSystem()
|
||||
);
|
||||
this.conditionManager.updateCurrentCondition(latestTimestamp);
|
||||
}
|
||||
|
||||
updateDescription() {
|
||||
const triggerDescription = this.getTriggerDescription();
|
||||
let description = '';
|
||||
|
@ -103,6 +103,8 @@ export default class ConditionManager extends EventEmitter {
|
||||
criterion.operation = '';
|
||||
conditionChanged = true;
|
||||
}
|
||||
} else {
|
||||
conditionChanged = true;
|
||||
}
|
||||
});
|
||||
if (conditionChanged) {
|
||||
@ -315,6 +317,10 @@ export default class ConditionManager extends EventEmitter {
|
||||
condition.getResult(normalizedDatum);
|
||||
});
|
||||
|
||||
this.updateCurrentCondition(timestamp);
|
||||
}
|
||||
|
||||
updateCurrentCondition(timestamp) {
|
||||
const currentCondition = this.getCurrentCondition();
|
||||
|
||||
this.emit('conditionSetResultUpdated',
|
||||
|
@ -27,13 +27,13 @@
|
||||
>
|
||||
{{ condition.configuration.name }}
|
||||
</span>
|
||||
<span class="c-style__condition-desc__text"
|
||||
v-if="!condition.isDefault"
|
||||
<span v-if="!condition.isDefault"
|
||||
class="c-style__condition-desc__text"
|
||||
>
|
||||
{{ description }}
|
||||
</span>
|
||||
<span class="c-style__condition-desc__text"
|
||||
v-else
|
||||
<span v-else
|
||||
class="c-style__condition-desc__text"
|
||||
>
|
||||
Match if no other condition is matched
|
||||
</span>
|
||||
|
@ -55,6 +55,7 @@
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
<option value="dataReceived">any data received</option>
|
||||
</select>
|
||||
</span>
|
||||
<span v-if="criterion.telemetry && criterion.metadata"
|
||||
@ -83,6 +84,7 @@
|
||||
>
|
||||
<span v-if="inputIndex < inputCount-1">and</span>
|
||||
</span>
|
||||
<span v-if="criterion.metadata === 'dataReceived'">seconds</span>
|
||||
</template>
|
||||
<span v-else>
|
||||
<span v-if="inputCount && criterion.operation"
|
||||
@ -148,7 +150,11 @@ export default {
|
||||
return (this.index !== 0 ? operator : '') + ' when';
|
||||
},
|
||||
filteredOps: function () {
|
||||
return this.operations.filter(op => op.appliesTo.indexOf(this.operationFormat) !== -1);
|
||||
if (this.criterion.metadata === 'dataReceived') {
|
||||
return this.operations.filter(op => op.name === 'isStale');
|
||||
} else {
|
||||
return this.operations.filter(op => op.appliesTo.indexOf(this.operationFormat) !== -1);
|
||||
}
|
||||
},
|
||||
setInputType: function () {
|
||||
let type = '';
|
||||
@ -214,6 +220,8 @@ export default {
|
||||
} else {
|
||||
this.operationFormat = 'number';
|
||||
}
|
||||
} else if (this.criterion.metadata === 'dataReceived') {
|
||||
this.operationFormat = 'number';
|
||||
}
|
||||
this.updateInputVisibilityAndValues();
|
||||
},
|
||||
|
@ -37,12 +37,13 @@
|
||||
>
|
||||
<style-editor class="c-inspect-styles__editor"
|
||||
:style-item="staticStyle"
|
||||
:is-editing="isEditing"
|
||||
:is-editing="allowEditing"
|
||||
:mixed-styles="mixedStyles"
|
||||
@persist="updateStaticStyle"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
v-if="allowEditing"
|
||||
id="addConditionSet"
|
||||
class="c-button c-button--major c-toggle-styling-button labeled"
|
||||
@click="addConditionSet"
|
||||
@ -63,7 +64,7 @@
|
||||
>
|
||||
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
||||
</a>
|
||||
<template v-if="isEditing">
|
||||
<template v-if="allowEditing">
|
||||
<button
|
||||
id="changeConditionSet"
|
||||
class="c-button labeled"
|
||||
@ -96,7 +97,7 @@
|
||||
/>
|
||||
<style-editor class="c-inspect-styles__editor"
|
||||
:style-item="conditionStyle"
|
||||
:is-editing="isEditing"
|
||||
:is-editing="allowEditing"
|
||||
@persist="updateConditionalStyle"
|
||||
/>
|
||||
</div>
|
||||
@ -137,7 +138,13 @@ export default {
|
||||
conditions: undefined,
|
||||
conditionsLoaded: false,
|
||||
navigateToPath: '',
|
||||
selectedConditionId: ''
|
||||
selectedConditionId: '',
|
||||
locked: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
allowEditing() {
|
||||
return this.isEditing && !this.locked;
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
@ -224,7 +231,13 @@ export default {
|
||||
this.selection.forEach((selectionItem) => {
|
||||
const item = selectionItem[0].context.item;
|
||||
const layoutItem = selectionItem[0].context.layoutItem;
|
||||
const layoutDomainObject = selectionItem[0].context.item;
|
||||
const isChildItem = selectionItem.length > 1;
|
||||
|
||||
if (layoutDomainObject && layoutDomainObject.locked) {
|
||||
this.locked = true;
|
||||
}
|
||||
|
||||
if (!isChildItem) {
|
||||
domainObject = item;
|
||||
itemStyle = getApplicableStylesForItem(item);
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
import TelemetryCriterion from './TelemetryCriterion';
|
||||
import { evaluateResults } from "../utils/evaluator";
|
||||
import { getLatestTimestamp } from '../utils/time';
|
||||
import {getLatestTimestamp, subscribeForStaleness} from '../utils/time';
|
||||
import { getOperatorText } from "@/plugins/condition/utils/operations";
|
||||
|
||||
export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
@ -41,6 +41,32 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
initialize() {
|
||||
this.telemetryObjects = { ...this.telemetryDomainObjectDefinition.telemetryObjects };
|
||||
this.telemetryDataCache = {};
|
||||
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
|
||||
this.subscribeForStaleData(this.telemetryObjects || {});
|
||||
}
|
||||
}
|
||||
|
||||
subscribeForStaleData(telemetryObjects) {
|
||||
|
||||
if (!this.stalenessSubscription) {
|
||||
this.stalenessSubscription = {};
|
||||
}
|
||||
Object.values(telemetryObjects).forEach((telemetryObject) => {
|
||||
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
if (!this.stalenessSubscription[id]) {
|
||||
this.stalenessSubscription[id] = subscribeForStaleness((data) => {
|
||||
this.handleStaleTelemetry(id, data);
|
||||
}, this.input[0]*1000);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleStaleTelemetry(id, data) {
|
||||
if (this.telemetryDataCache) {
|
||||
this.telemetryDataCache[id] = true;
|
||||
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
|
||||
}
|
||||
this.emitEvent('telemetryIsStale', data);
|
||||
}
|
||||
|
||||
isValid() {
|
||||
@ -50,6 +76,9 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
updateTelemetryObjects(telemetryObjects) {
|
||||
this.telemetryObjects = { ...telemetryObjects };
|
||||
this.removeTelemetryDataCache();
|
||||
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
|
||||
this.subscribeForStaleData(this.telemetryObjects || {});
|
||||
}
|
||||
}
|
||||
|
||||
removeTelemetryDataCache() {
|
||||
@ -63,6 +92,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
});
|
||||
telemetryCacheIds.forEach(id => {
|
||||
delete (this.telemetryDataCache[id]);
|
||||
delete (this.stalenessSubscription[id]);
|
||||
});
|
||||
}
|
||||
|
||||
@ -96,7 +126,14 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
const validatedData = this.isValid() ? data : {};
|
||||
|
||||
if (validatedData) {
|
||||
this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
|
||||
if (this.isStalenessCheck()) {
|
||||
if (this.stalenessSubscription[validatedData.id]) {
|
||||
this.stalenessSubscription[validatedData.id].update(validatedData);
|
||||
}
|
||||
this.telemetryDataCache[validatedData.id] = false;
|
||||
} else {
|
||||
this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
|
||||
}
|
||||
}
|
||||
|
||||
Object.values(telemetryObjects).forEach(telemetryObject => {
|
||||
@ -162,7 +199,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
|
||||
getDescription() {
|
||||
const telemetryDescription = this.telemetry === 'all' ? 'all telemetry' : 'any telemetry';
|
||||
let metadataValue = this.metadata;
|
||||
let metadataValue = (this.metadata === 'dataReceived' ? '' : this.metadata);
|
||||
let inputValue = this.input;
|
||||
if (this.metadata) {
|
||||
const telemetryObjects = Object.values(this.telemetryObjects);
|
||||
@ -182,5 +219,9 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
destroy() {
|
||||
delete this.telemetryObjects;
|
||||
delete this.telemetryDataCache;
|
||||
if (this.stalenessSubscription) {
|
||||
Object.values(this.stalenessSubscription).forEach((subscription) => subscription.clear);
|
||||
delete this.stalenessSubscription;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import { OPERATIONS, getOperatorText } from '../utils/operations';
|
||||
import { subscribeForStaleness } from "../utils/time";
|
||||
|
||||
export default class TelemetryCriterion extends EventEmitter {
|
||||
|
||||
@ -43,6 +44,7 @@ export default class TelemetryCriterion extends EventEmitter {
|
||||
this.input = telemetryDomainObjectDefinition.input;
|
||||
this.metadata = telemetryDomainObjectDefinition.metadata;
|
||||
this.result = undefined;
|
||||
this.stalenessSubscription = undefined;
|
||||
|
||||
this.initialize();
|
||||
this.emitEvent('criterionUpdated', this);
|
||||
@ -51,14 +53,40 @@ export default class TelemetryCriterion extends EventEmitter {
|
||||
initialize() {
|
||||
this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry);
|
||||
this.updateTelemetryObjects(this.telemetryDomainObjectDefinition.telemetryObjects);
|
||||
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
|
||||
this.subscribeForStaleData()
|
||||
}
|
||||
}
|
||||
|
||||
subscribeForStaleData() {
|
||||
if (this.stalenessSubscription) {
|
||||
this.stalenessSubscription.clear();
|
||||
}
|
||||
this.stalenessSubscription = subscribeForStaleness(this.handleStaleTelemetry.bind(this), this.input[0]*1000);
|
||||
}
|
||||
|
||||
handleStaleTelemetry(data) {
|
||||
this.result = true;
|
||||
this.emitEvent('telemetryIsStale', data);
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return this.telemetryObject && this.metadata && this.operation;
|
||||
}
|
||||
|
||||
isStalenessCheck() {
|
||||
return this.metadata && this.metadata === 'dataReceived';
|
||||
}
|
||||
|
||||
isValidInput() {
|
||||
return this.input instanceof Array && this.input.length;
|
||||
}
|
||||
|
||||
updateTelemetryObjects(telemetryObjects) {
|
||||
this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
|
||||
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
|
||||
this.subscribeForStaleData()
|
||||
}
|
||||
}
|
||||
|
||||
createNormalizedDatum(telemetryDatum, endpoint) {
|
||||
@ -91,7 +119,14 @@ export default class TelemetryCriterion extends EventEmitter {
|
||||
|
||||
getResult(data) {
|
||||
const validatedData = this.isValid() ? data : {};
|
||||
this.result = this.computeResult(validatedData);
|
||||
if (this.isStalenessCheck()) {
|
||||
if (this.stalenessSubscription) {
|
||||
this.stalenessSubscription.update(validatedData);
|
||||
}
|
||||
this.result = false;
|
||||
} else {
|
||||
this.result = this.computeResult(validatedData);
|
||||
}
|
||||
}
|
||||
|
||||
requestLAD() {
|
||||
@ -136,7 +171,7 @@ export default class TelemetryCriterion extends EventEmitter {
|
||||
let comparator = this.findOperation(this.operation);
|
||||
let params = [];
|
||||
params.push(data[this.metadata]);
|
||||
if (this.input instanceof Array && this.input.length) {
|
||||
if (this.isValidInput()) {
|
||||
this.input.forEach(input => params.push(input));
|
||||
}
|
||||
if (typeof comparator === 'function') {
|
||||
@ -191,7 +226,7 @@ export default class TelemetryCriterion extends EventEmitter {
|
||||
description = `Unknown ${this.metadata} ${getOperatorText(this.operation, this.input)}`;
|
||||
} else {
|
||||
const metadataObject = this.getMetaDataObject(this.telemetryObject, this.metadata);
|
||||
const metadataValue = this.getMetadataValueFromMetaData(metadataObject) || this.metadata;
|
||||
const metadataValue = this.getMetadataValueFromMetaData(metadataObject) || (this.metadata === 'dataReceived' ? '' : this.metadata);
|
||||
const inputValue = this.getInputValueFromMetaData(metadataObject, this.input) || this.input;
|
||||
description = `${this.telemetryObject.name} ${metadataValue} ${getOperatorText(this.operation, inputValue)}`;
|
||||
}
|
||||
@ -202,5 +237,8 @@ export default class TelemetryCriterion extends EventEmitter {
|
||||
destroy() {
|
||||
delete this.telemetryObject;
|
||||
delete this.telemetryObjectIdAsString;
|
||||
if (this.stalenessSubscription) {
|
||||
delete this.stalenessSubscription;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,19 +25,50 @@ import ConditionPlugin from "./plugin";
|
||||
import StylesView from "./components/inspector/StylesView.vue";
|
||||
import Vue from 'vue';
|
||||
import {getApplicableStylesForItem} from "./utils/styleUtils";
|
||||
import ConditionManager from "@/plugins/condition/ConditionManager";
|
||||
|
||||
describe('the plugin', function () {
|
||||
let conditionSetDefinition;
|
||||
let mockConditionSetDomainObject;
|
||||
let mockListener;
|
||||
let element;
|
||||
let child;
|
||||
let openmct;
|
||||
let testTelemetryObject;
|
||||
|
||||
beforeAll(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
testTelemetryObject = {
|
||||
identifier:{ namespace: "", key: "test-object"},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
valueMetadatas: [{
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "testSource",
|
||||
source: "value",
|
||||
name: "Test",
|
||||
format: "string"
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
openmct = createOpenMct();
|
||||
openmct.install(new ConditionPlugin());
|
||||
|
||||
@ -55,6 +86,8 @@ describe('the plugin', function () {
|
||||
type: 'conditionSet'
|
||||
};
|
||||
|
||||
mockListener = jasmine.createSpy('mockListener');
|
||||
|
||||
conditionSetDefinition.initialize(mockConditionSetDomainObject);
|
||||
|
||||
openmct.on('start', done);
|
||||
@ -356,4 +389,113 @@ describe('the plugin', function () {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('the condition check for staleness', () => {
|
||||
let conditionSetDomainObject;
|
||||
|
||||
beforeEach(()=>{
|
||||
conditionSetDomainObject = {
|
||||
"configuration":{
|
||||
"conditionTestData":[
|
||||
{
|
||||
"telemetry":"",
|
||||
"metadata":"",
|
||||
"input":""
|
||||
}
|
||||
],
|
||||
"conditionCollection":[
|
||||
{
|
||||
"id":"39584410-cbf9-499e-96dc-76f27e69885d",
|
||||
"configuration":{
|
||||
"name":"Unnamed Condition",
|
||||
"output":"Any stale telemetry",
|
||||
"trigger":"all",
|
||||
"criteria":[
|
||||
{
|
||||
"id":"35400132-63b0-425c-ac30-8197df7d5862",
|
||||
"telemetry":"any",
|
||||
"operation":"isStale",
|
||||
"input":[
|
||||
"1"
|
||||
],
|
||||
"metadata":"dataReceived"
|
||||
}
|
||||
]
|
||||
},
|
||||
"summary":"Match if all criteria are met: Any telemetry is stale after 5 seconds"
|
||||
},
|
||||
{
|
||||
"isDefault":true,
|
||||
"id":"2532d90a-e0d6-4935-b546-3123522da2de",
|
||||
"configuration":{
|
||||
"name":"Default",
|
||||
"output":"Default",
|
||||
"trigger":"all",
|
||||
"criteria":[
|
||||
]
|
||||
},
|
||||
"summary":""
|
||||
}
|
||||
]
|
||||
},
|
||||
"composition":[
|
||||
{
|
||||
"namespace":"",
|
||||
"key":"test-object"
|
||||
}
|
||||
],
|
||||
"telemetry":{
|
||||
},
|
||||
"name":"Condition Set",
|
||||
"type":"conditionSet",
|
||||
"identifier":{
|
||||
"namespace":"",
|
||||
"key":"cf4456a9-296a-4e6b-b182-62ed29cd15b9"
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
it('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
|
||||
|
||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||
conditionMgr.telemetryObjects = {
|
||||
"test-object": testTelemetryObject
|
||||
};
|
||||
conditionMgr.updateConditionTelemetryObjects();
|
||||
setTimeout(() => {
|
||||
expect(mockListener).toHaveBeenCalledWith({
|
||||
output: 'Any stale telemetry',
|
||||
id: { namespace: '', key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9' },
|
||||
conditionId: '39584410-cbf9-499e-96dc-76f27e69885d',
|
||||
utc: undefined
|
||||
});
|
||||
done();
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
it('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
|
||||
const date = Date.now();
|
||||
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["2"];
|
||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||
conditionMgr.telemetryObjects = {
|
||||
"test-object": testTelemetryObject
|
||||
};
|
||||
conditionMgr.updateConditionTelemetryObjects();
|
||||
conditionMgr.telemetryReceived(testTelemetryObject, {
|
||||
utc: date
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(mockListener).toHaveBeenCalledWith({
|
||||
output: 'Default',
|
||||
id: { namespace: '', key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9' },
|
||||
conditionId: '2532d90a-e0d6-4935-b546-3123522da2de',
|
||||
utc: undefined
|
||||
});
|
||||
done();
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -283,6 +283,18 @@ export const OPERATIONS = [
|
||||
getDescription: function (values) {
|
||||
return ' is not one of ' + values[0];
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'isStale',
|
||||
operation: function () {
|
||||
return false;
|
||||
},
|
||||
text: 'is older than',
|
||||
appliesTo: ["number"],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ` is older than ${values[0] || ''} seconds`;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -50,3 +50,26 @@ function updateLatestTimeStamp(timestamp, timeSystems) {
|
||||
|
||||
return latest;
|
||||
}
|
||||
|
||||
export const subscribeForStaleness = (callback, timeout) => {
|
||||
let stalenessTimer = setTimeout(() => {
|
||||
clearTimeout(stalenessTimer);
|
||||
callback();
|
||||
}, timeout);
|
||||
return {
|
||||
update: (data) => {
|
||||
if (stalenessTimer) {
|
||||
clearTimeout(stalenessTimer);
|
||||
}
|
||||
stalenessTimer = setTimeout(() => {
|
||||
clearTimeout(stalenessTimer);
|
||||
callback(data);
|
||||
}, timeout);
|
||||
},
|
||||
clear: () => {
|
||||
if (stalenessTimer) {
|
||||
clearTimeout(stalenessTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
64
src/plugins/condition/utils/timeSpec.js
Normal file
64
src/plugins/condition/utils/timeSpec.js
Normal file
@ -0,0 +1,64 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, 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 { subscribeForStaleness } from "./time";
|
||||
|
||||
describe('time related utils', () => {
|
||||
let subscription;
|
||||
let mockListener;
|
||||
|
||||
beforeEach(() => {
|
||||
mockListener = jasmine.createSpy('listener');
|
||||
subscription = subscribeForStaleness(mockListener, 100);
|
||||
});
|
||||
|
||||
describe('subscribe for staleness', () => {
|
||||
it('should call listeners when stale', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(mockListener).toHaveBeenCalled();
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it('should update the subscription', (done) => {
|
||||
function updated() {
|
||||
setTimeout(() => {
|
||||
expect(mockListener).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, 50);
|
||||
}
|
||||
setTimeout(() => {
|
||||
subscription.update();
|
||||
updated();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
it('should clear the subscription', (done) => {
|
||||
subscription.clear();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(mockListener).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -596,7 +596,7 @@ define(['lodash'], function (_) {
|
||||
let selectedParent = selectionPath[1].context.item;
|
||||
let layoutItem = selectionPath[0].context.layoutItem;
|
||||
|
||||
if (!layoutItem) {
|
||||
if (!layoutItem || selectedParent.locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
<layout-frame
|
||||
:item="item"
|
||||
:grid-size="gridSize"
|
||||
:is-editing="isEditing"
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')"
|
||||
>
|
||||
@ -70,7 +71,11 @@ export default {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
initSelect: Boolean
|
||||
initSelect: Boolean,
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
|
@ -24,14 +24,18 @@
|
||||
<div
|
||||
class="l-layout"
|
||||
:class="{
|
||||
'is-multi-selected': selectedLayoutItems.length > 1
|
||||
'is-multi-selected': selectedLayoutItems.length > 1,
|
||||
'allow-editing': isEditing
|
||||
}"
|
||||
@dragover="handleDragOver"
|
||||
@click.capture="bypassSelection"
|
||||
@drop="handleDrop"
|
||||
>
|
||||
<!-- Background grid -->
|
||||
<div class="l-layout__grid-holder c-grid">
|
||||
<div
|
||||
v-if="isEditing"
|
||||
class="l-layout__grid-holder c-grid"
|
||||
>
|
||||
<div
|
||||
v-if="gridSize[0] >= 3"
|
||||
class="c-grid__x l-grid l-grid-x"
|
||||
@ -53,6 +57,7 @@
|
||||
:init-select="initSelectIndex === index"
|
||||
:index="index"
|
||||
:multi-select="selectedLayoutItems.length > 1"
|
||||
:is-editing="isEditing"
|
||||
@move="move"
|
||||
@endMove="endMove"
|
||||
@endLineResize="endLineResize"
|
||||
@ -138,6 +143,10 @@ export default {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@ -164,7 +173,7 @@ export default {
|
||||
let selectionPath = this.selection[0];
|
||||
let singleSelectedLine = this.selection.length === 1 &&
|
||||
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view';
|
||||
return selectionPath && selectionPath.length > 1 && !singleSelectedLine;
|
||||
return this.isEditing && selectionPath && selectionPath.length > 1 && !singleSelectedLine;
|
||||
}
|
||||
},
|
||||
inject: ['openmct', 'options', 'objectPath'],
|
||||
@ -352,6 +361,9 @@ export default {
|
||||
.some(childId => this.openmct.objects.areIdsEqual(childId, identifier));
|
||||
},
|
||||
handleDragOver($event) {
|
||||
if (this.internalDomainObject.locked) {
|
||||
return;
|
||||
}
|
||||
// Get the ID of the dragged object
|
||||
let draggedKeyString = $event.dataTransfer.types
|
||||
.filter(type => type.startsWith(DRAG_OBJECT_TRANSFER_PREFIX))
|
||||
|
@ -24,6 +24,7 @@
|
||||
<layout-frame
|
||||
:item="item"
|
||||
:grid-size="gridSize"
|
||||
:is-editing="isEditing"
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')"
|
||||
>
|
||||
@ -70,7 +71,11 @@ export default {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
initSelect: Boolean
|
||||
initSelect: Boolean,
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
|
@ -33,7 +33,7 @@
|
||||
|
||||
<div
|
||||
class="c-frame-edit__move"
|
||||
@mousedown="startMove([1,1], [0,0], $event)"
|
||||
@mousedown="isEditing ? startMove([1,1], [0,0], $event) : null"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
@ -54,6 +54,10 @@ export default {
|
||||
required: true,
|
||||
validator: (arr) => arr && arr.length === 2
|
||||
&& arr.every(el => typeof el === 'number')
|
||||
},
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -24,6 +24,7 @@
|
||||
:item="item"
|
||||
:grid-size="gridSize"
|
||||
:title="domainObject && domainObject.name"
|
||||
:is-editing="isEditing"
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')"
|
||||
>
|
||||
@ -95,6 +96,10 @@ export default {
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -24,6 +24,7 @@
|
||||
<layout-frame
|
||||
:item="item"
|
||||
:grid-size="gridSize"
|
||||
:is-editing="isEditing"
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')"
|
||||
>
|
||||
@ -105,6 +106,10 @@ export default {
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -24,6 +24,7 @@
|
||||
<layout-frame
|
||||
:item="item"
|
||||
:grid-size="gridSize"
|
||||
:is-editing="isEditing"
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')"
|
||||
>
|
||||
@ -75,7 +76,11 @@ export default {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
initSelect: Boolean
|
||||
initSelect: Boolean,
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
|
@ -45,8 +45,7 @@
|
||||
&[s-selected],
|
||||
&[s-selected-parent] {
|
||||
// Display grid and allow edit marquee to display in nested layouts when editing
|
||||
> * > * > .l-layout {
|
||||
background: $editUIGridColorBg;
|
||||
> * > * > .l-layout + .allow-editing {
|
||||
box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
|
||||
|
||||
> [class*='grid-holder'] {
|
||||
|
@ -54,10 +54,11 @@ export default function DisplayLayoutPlugin(options) {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainObject: domainObject
|
||||
domainObject: domainObject,
|
||||
isEditing: openmct.editor.isEditing()
|
||||
};
|
||||
},
|
||||
template: '<layout ref="displayLayout" :domain-object="domainObject"></layout>'
|
||||
template: '<layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></layout>'
|
||||
});
|
||||
},
|
||||
getSelectionContext() {
|
||||
@ -73,6 +74,9 @@ export default function DisplayLayoutPlugin(options) {
|
||||
mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots
|
||||
};
|
||||
},
|
||||
onEditModeChange: function (isEditing) {
|
||||
component.isEditing = isEditing;
|
||||
},
|
||||
destroy() {
|
||||
component.$destroy();
|
||||
}
|
||||
|
@ -53,6 +53,7 @@
|
||||
:index="i"
|
||||
:container-index="index"
|
||||
:is-editing="isEditing"
|
||||
:object-path="objectPath"
|
||||
/>
|
||||
|
||||
<drop-hint
|
||||
@ -105,6 +106,14 @@ export default {
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
locked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
objectPath: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -130,6 +139,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
allowDrop(event, index) {
|
||||
if (this.locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.dataTransfer.types.includes('openmct/domain-object-path')) {
|
||||
return true;
|
||||
}
|
||||
|
@ -57,6 +57,8 @@
|
||||
:container="container"
|
||||
:rows-layout="rowsLayout"
|
||||
:is-editing="isEditing"
|
||||
:locked="domainObject.locked"
|
||||
:object-path="objectPath"
|
||||
@move-frame="moveFrame"
|
||||
@new-frame="setFrameLocation"
|
||||
@persist="persist"
|
||||
@ -136,7 +138,7 @@ function sizeToFill(items) {
|
||||
}
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'layoutObject'],
|
||||
inject: ['openmct', 'objectPath', 'layoutObject'],
|
||||
components: {
|
||||
ContainerComponent,
|
||||
ResizeHandle,
|
||||
|
@ -37,7 +37,7 @@
|
||||
v-if="domainObject"
|
||||
ref="objectFrame"
|
||||
:domain-object="domainObject"
|
||||
:object-path="objectPath"
|
||||
:object-path="currentObjectPath"
|
||||
:has-frame="hasFrame"
|
||||
:show-edit-view="false"
|
||||
/>
|
||||
@ -77,12 +77,16 @@ export default {
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
objectPath: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainObject: undefined,
|
||||
objectPath: undefined
|
||||
currentObjectPath: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -107,7 +111,7 @@ export default {
|
||||
methods: {
|
||||
setDomainObject(object) {
|
||||
this.domainObject = object;
|
||||
this.objectPath = [object];
|
||||
this.currentObjectPath = [object].concat(this.objectPath);
|
||||
this.setSelection();
|
||||
},
|
||||
setSelection() {
|
||||
|
@ -38,7 +38,7 @@ define([
|
||||
canEdit: function (domainObject) {
|
||||
return domainObject.type === 'flexible-layout';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
view: function (domainObject, objectPath) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
@ -46,6 +46,7 @@ define([
|
||||
component = new Vue({
|
||||
provide: {
|
||||
openmct,
|
||||
objectPath,
|
||||
layoutObject: domainObject
|
||||
},
|
||||
el: element,
|
||||
|
@ -70,6 +70,10 @@ function ToolbarProvider(openmct) {
|
||||
}
|
||||
|
||||
if (primary.context.type === 'frame') {
|
||||
if (secondary.context.item.locked) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let frameId = primary.context.frameId;
|
||||
let layoutObject = tertiary.context.item;
|
||||
let containers = layoutObject
|
||||
@ -143,6 +147,9 @@ function ToolbarProvider(openmct) {
|
||||
toggleContainer.domainObject = secondary.context.item;
|
||||
|
||||
} else if (primary.context.type === 'container') {
|
||||
if (primary.context.item.locked) {
|
||||
return [];
|
||||
}
|
||||
|
||||
deleteContainer = {
|
||||
control: "button",
|
||||
@ -187,6 +194,9 @@ function ToolbarProvider(openmct) {
|
||||
};
|
||||
|
||||
} else if (primary.context.type === 'flexible-layout') {
|
||||
if (primary.context.item.locked) {
|
||||
return [];
|
||||
}
|
||||
|
||||
addContainer = {
|
||||
control: "button",
|
||||
|
@ -41,7 +41,7 @@ define([], function () {
|
||||
this.timeFormat = 'local-format';
|
||||
this.durationFormat = 'duration';
|
||||
|
||||
this.isUTCBased = false;
|
||||
this.isUTCBased = true;
|
||||
}
|
||||
|
||||
return LocalTimeSystem;
|
||||
|
@ -24,14 +24,14 @@ import uuid from 'uuid';
|
||||
|
||||
export default class NewFolderAction {
|
||||
constructor(openmct) {
|
||||
this.name = 'New Folder';
|
||||
this.name = 'Add New Folder';
|
||||
this.key = 'newFolder';
|
||||
this.description = 'Create a new folder';
|
||||
this.cssClass = 'icon-folder';
|
||||
this.cssClass = 'icon-folder-new';
|
||||
|
||||
this._openmct = openmct;
|
||||
this._dialogForm = {
|
||||
name: "New Folder Name",
|
||||
name: "Add New Folder",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
@ -39,7 +39,9 @@ export default class NewFolderAction {
|
||||
key: "name",
|
||||
control: "textfield",
|
||||
name: "Folder Name",
|
||||
required: false
|
||||
pattern: "\\S+",
|
||||
required: true,
|
||||
cssClass: "l-input-lg"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -53,7 +55,7 @@ export default class NewFolderAction {
|
||||
dialogService = this._openmct.$injector.get('dialogService'),
|
||||
folderType = this._openmct.types.get('folder');
|
||||
|
||||
dialogService.getUserInput(this._dialogForm, {}).then((userInput) => {
|
||||
dialogService.getUserInput(this._dialogForm, {name: 'Unnamed Folder'}).then((userInput) => {
|
||||
let name = userInput.name,
|
||||
identifier = {
|
||||
key: uuid(),
|
||||
|
@ -60,6 +60,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.addPopupMenuItems();
|
||||
this.exportImageService = this.openmct.$injector.get('exportImageService');
|
||||
},
|
||||
methods: {
|
||||
addPopupMenuItems() {
|
||||
@ -205,7 +206,7 @@ export default {
|
||||
},
|
||||
openSnapshot() {
|
||||
const self = this;
|
||||
const snapshot = new Vue({
|
||||
this.snapshot = new Vue({
|
||||
data: () => {
|
||||
return {
|
||||
embed: self.embed
|
||||
@ -213,14 +214,15 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
formatTime: self.formatTime,
|
||||
annotateSnapshot: self.annotateSnapshot
|
||||
annotateSnapshot: self.annotateSnapshot,
|
||||
exportImage: self.exportImage
|
||||
},
|
||||
template: SnapshotTemplate
|
||||
});
|
||||
|
||||
const snapshotOverlay = this.openmct.overlays.overlay({
|
||||
element: snapshot.$mount().$el,
|
||||
onDestroy: () => { snapshot.$destroy(true) },
|
||||
element: this.snapshot.$mount().$el,
|
||||
onDestroy: () => { this.snapshot.$destroy(true) },
|
||||
size: 'large',
|
||||
dismissable: true,
|
||||
buttons: [
|
||||
@ -234,6 +236,15 @@ export default {
|
||||
]
|
||||
});
|
||||
},
|
||||
exportImage(type) {
|
||||
let element = this.snapshot.$refs['snapshot-image'];
|
||||
|
||||
if (type === 'png') {
|
||||
this.exportImageService.exportPNG(element, this.embed.name);
|
||||
} else {
|
||||
this.exportImageService.exportJPG(element, this.embed.name);
|
||||
}
|
||||
},
|
||||
previewEmbed() {
|
||||
const self = this;
|
||||
const previewAction = new PreviewAction(self.openmct);
|
||||
|
@ -15,14 +15,32 @@
|
||||
<div class="l-browse-bar__snapshot-datetime">
|
||||
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
|
||||
</div>
|
||||
<span class="c-button-set c-button-set--strip-h">
|
||||
<button
|
||||
class="c-button icon-download"
|
||||
title="Export This View's Data as PNG"
|
||||
@click="exportImage('png')"
|
||||
>
|
||||
<span class="c-button__label">PNG</span>
|
||||
</button>
|
||||
<button
|
||||
class="c-button"
|
||||
title="Export This View's Data as JPG"
|
||||
@click="exportImage('jpg')"
|
||||
>
|
||||
<span class="c-button__label">JPG</span>
|
||||
</button>
|
||||
</span>
|
||||
<a class="l-browse-bar__annotate-button c-button icon-pencil" title="Annotate" @click="annotateSnapshot">
|
||||
<span class="title-label">Annotate</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-notebook-snapshot__image"
|
||||
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
|
||||
<div
|
||||
ref="snapshot-image"
|
||||
class="c-notebook-snapshot__image"
|
||||
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,12 +19,12 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div ng-if="domainObject.getCapability('editor').inEditContext()">
|
||||
<div ng-if="!domainObject.model.locked && domainObject.getCapability('editor').inEditContext()">
|
||||
<mct-representation key="'plot-options-edit'"
|
||||
mct-object="domainObject">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<div ng-if="!domainObject.getCapability('editor').inEditContext()">
|
||||
<div ng-if="domainObject.model.locked || !domainObject.getCapability('editor').inEditContext()">
|
||||
<mct-representation key="'plot-options-browse'"
|
||||
mct-object="domainObject">
|
||||
</mct-representation>
|
||||
|
@ -101,6 +101,12 @@ export default class RemoveAction {
|
||||
appliesTo(objectPath) {
|
||||
let parent = objectPath[1];
|
||||
let parentType = parent && this.openmct.types.get(parent.type);
|
||||
let child = objectPath[0];
|
||||
let locked = child.locked ? child.locked : parent && parent.locked;
|
||||
|
||||
if (locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parentType &&
|
||||
parentType.definition.creatable &&
|
||||
|
@ -22,7 +22,12 @@
|
||||
<template>
|
||||
<div
|
||||
class="c-conductor"
|
||||
:class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode']"
|
||||
:class="[
|
||||
{ 'is-zooming': isZooming },
|
||||
{ 'is-panning': isPanning },
|
||||
{ 'alt-pressed': altPressed },
|
||||
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
|
||||
]"
|
||||
>
|
||||
<form
|
||||
ref="conductorForm"
|
||||
@ -52,7 +57,7 @@
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
@change="validateAllBounds(); submitForm()"
|
||||
@change="validateAllBounds('startDate'); submitForm()"
|
||||
>
|
||||
<date-picker
|
||||
v-if="isFixed && isUTCBased"
|
||||
@ -92,7 +97,7 @@
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
:disabled="!isFixed"
|
||||
@change="validateAllBounds(); submitForm()"
|
||||
@change="validateAllBounds('endDate'); submitForm()"
|
||||
>
|
||||
<date-picker
|
||||
v-if="isFixed && isUTCBased"
|
||||
@ -122,14 +127,25 @@
|
||||
|
||||
<conductor-axis
|
||||
class="c-conductor__ticks"
|
||||
:bounds="rawBounds"
|
||||
@panAxis="setViewFromBounds"
|
||||
:view-bounds="viewBounds"
|
||||
:is-fixed="isFixed"
|
||||
:alt-pressed="altPressed"
|
||||
@endPan="endPan"
|
||||
@endZoom="endZoom"
|
||||
@panAxis="pan"
|
||||
@zoomAxis="zoom"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div class="c-conductor__controls">
|
||||
<!-- Mode, time system menu buttons and duration slider -->
|
||||
<ConductorMode class="c-conductor__mode-select" />
|
||||
<ConductorTimeSystem class="c-conductor__time-system-select" />
|
||||
<ConductorHistory
|
||||
v-if="isFixed"
|
||||
class="c-conductor__history-select"
|
||||
:bounds="openmct.time.bounds()"
|
||||
:time-system="timeSystem"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="submit"
|
||||
@ -145,6 +161,7 @@ import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
||||
import DatePicker from './DatePicker.vue';
|
||||
import ConductorAxis from './ConductorAxis.vue';
|
||||
import ConductorModeIcon from './ConductorModeIcon.vue';
|
||||
import ConductorHistory from './ConductorHistory.vue'
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
|
||||
@ -155,7 +172,8 @@ export default {
|
||||
ConductorTimeSystem,
|
||||
DatePicker,
|
||||
ConductorAxis,
|
||||
ConductorModeIcon
|
||||
ConductorModeIcon,
|
||||
ConductorHistory
|
||||
},
|
||||
data() {
|
||||
let bounds = this.openmct.time.bounds();
|
||||
@ -165,6 +183,7 @@ export default {
|
||||
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
|
||||
return {
|
||||
timeSystem: timeSystem,
|
||||
timeFormatter: timeFormatter,
|
||||
durationFormatter: durationFormatter,
|
||||
offsets: {
|
||||
@ -175,29 +194,68 @@ export default {
|
||||
start: timeFormatter.format(bounds.start),
|
||||
end: timeFormatter.format(bounds.end)
|
||||
},
|
||||
rawBounds: {
|
||||
viewBounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
},
|
||||
isFixed: this.openmct.time.clock() === undefined,
|
||||
isUTCBased: timeSystem.isUTCBased,
|
||||
showDatePicker: false
|
||||
showDatePicker: false,
|
||||
altPressed: false,
|
||||
isPanning: false,
|
||||
isZooming: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
document.addEventListener('keyup', this.handleKeyUp);
|
||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
||||
|
||||
this.openmct.time.on('bounds', this.setViewFromBounds);
|
||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
||||
this.openmct.time.on('clock', this.setViewFromClock);
|
||||
this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
document.removeEventListener('keyup', this.handleKeyUp);
|
||||
},
|
||||
methods: {
|
||||
handleKeyDown(event) {
|
||||
if (event.key === 'Alt') {
|
||||
this.altPressed = true;
|
||||
}
|
||||
},
|
||||
handleKeyUp(event) {
|
||||
if (event.key === 'Alt') {
|
||||
this.altPressed = false;
|
||||
}
|
||||
},
|
||||
pan(bounds) {
|
||||
this.isPanning = true;
|
||||
this.setViewFromBounds(bounds);
|
||||
},
|
||||
endPan(bounds) {
|
||||
this.isPanning = false;
|
||||
if (bounds) {
|
||||
this.openmct.time.bounds(bounds);
|
||||
}
|
||||
},
|
||||
zoom(bounds) {
|
||||
this.isZooming = true;
|
||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||
},
|
||||
endZoom(bounds) {
|
||||
const _bounds = bounds ? bounds : this.openmct.time.bounds();
|
||||
this.isZooming = false;
|
||||
|
||||
this.openmct.time.bounds(_bounds);
|
||||
},
|
||||
setTimeSystem(timeSystem) {
|
||||
this.timeSystem = timeSystem
|
||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
this.durationFormatter = this.getFormatter(
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
|
||||
this.isUTCBased = timeSystem.isUTCBased;
|
||||
},
|
||||
setOffsetsFromView($event) {
|
||||
@ -237,8 +295,8 @@ export default {
|
||||
setViewFromBounds(bounds) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||
this.rawBounds.start = bounds.start;
|
||||
this.rawBounds.end = bounds.end;
|
||||
this.viewBounds.start = bounds.start;
|
||||
this.viewBounds.end = bounds.end;
|
||||
},
|
||||
setViewFromOffsets(offsets) {
|
||||
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
|
||||
@ -251,6 +309,15 @@ export default {
|
||||
this.setOffsetsFromView();
|
||||
}
|
||||
},
|
||||
getBoundsLimit() {
|
||||
const configuration = this.configuration.menuOptions
|
||||
.filter(option => option.timeSystem === this.timeSystem.key)
|
||||
.find(option => option.limit);
|
||||
|
||||
const limit = configuration ? configuration.limit : undefined;
|
||||
|
||||
return limit;
|
||||
},
|
||||
clearAllValidation() {
|
||||
if (this.isFixed) {
|
||||
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
|
||||
@ -262,36 +329,52 @@ export default {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
},
|
||||
validateAllBounds() {
|
||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||
let validationResult = true;
|
||||
let formattedDate;
|
||||
validateAllBounds(ref) {
|
||||
if (!this.areBoundsFormatsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (input === this.$refs.startDate) {
|
||||
formattedDate = this.formattedBounds.start;
|
||||
let validationResult = true;
|
||||
const currentInput = this.$refs[ref];
|
||||
|
||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||
let boundsValues = {
|
||||
start: this.timeFormatter.parse(this.formattedBounds.start),
|
||||
end: this.timeFormatter.parse(this.formattedBounds.end)
|
||||
};
|
||||
const limit = this.getBoundsLimit();
|
||||
|
||||
if (
|
||||
this.timeSystem.isUTCBased
|
||||
&& limit
|
||||
&& boundsValues.end - boundsValues.start > limit
|
||||
) {
|
||||
if (input === currentInput) {
|
||||
validationResult = "Start and end difference exceeds allowable limit";
|
||||
}
|
||||
} else {
|
||||
formattedDate = this.formattedBounds.end;
|
||||
if (input === currentInput) {
|
||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||
}
|
||||
}
|
||||
|
||||
return this.handleValidationResults(input, validationResult);
|
||||
});
|
||||
},
|
||||
areBoundsFormatsValid() {
|
||||
let validationResult = true;
|
||||
|
||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||
const formattedDate = input === this.$refs.startDate
|
||||
? this.formattedBounds.start
|
||||
: this.formattedBounds.end
|
||||
;
|
||||
|
||||
if (!this.timeFormatter.validate(formattedDate)) {
|
||||
validationResult = 'Invalid date';
|
||||
} else {
|
||||
let boundsValues = {
|
||||
start: this.timeFormatter.parse(this.formattedBounds.start),
|
||||
end: this.timeFormatter.parse(this.formattedBounds.end)
|
||||
};
|
||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||
}
|
||||
|
||||
if (validationResult !== true) {
|
||||
input.setCustomValidity(validationResult);
|
||||
input.title = validationResult;
|
||||
return false;
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
return true;
|
||||
}
|
||||
return this.handleValidationResults(input, validationResult);
|
||||
});
|
||||
},
|
||||
validateAllOffsets(event) {
|
||||
@ -315,17 +398,20 @@ export default {
|
||||
validationResult = this.openmct.time.validateOffsets(offsetValues);
|
||||
}
|
||||
|
||||
if (validationResult !== true) {
|
||||
input.setCustomValidity(validationResult);
|
||||
input.title = validationResult;
|
||||
return false;
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
return true;
|
||||
}
|
||||
return this.handleValidationResults(input, validationResult);
|
||||
});
|
||||
},
|
||||
handleValidationResults(input, validationResult) {
|
||||
if (validationResult !== true) {
|
||||
input.setCustomValidity(validationResult);
|
||||
input.title = validationResult;
|
||||
return false;
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
return true;
|
||||
}
|
||||
},
|
||||
submitForm() {
|
||||
// Allow Vue model to catch up to user input.
|
||||
// Submitting form will cause validation messages to display (but only if triggered by button click)
|
||||
@ -338,12 +424,12 @@ export default {
|
||||
},
|
||||
startDateSelected(date) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(date);
|
||||
this.validateAllBounds();
|
||||
this.validateAllBounds('startDate');
|
||||
this.submitForm();
|
||||
},
|
||||
endDateSelected(date) {
|
||||
this.formattedBounds.end = this.timeFormatter.format(date);
|
||||
this.validateAllBounds();
|
||||
this.validateAllBounds('endDate');
|
||||
this.submitForm();
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,12 @@
|
||||
ref="axisHolder"
|
||||
class="c-conductor-axis"
|
||||
@mousedown="dragStart($event)"
|
||||
></div>
|
||||
>
|
||||
<div
|
||||
class="c-conductor-axis__zoom-indicator"
|
||||
:style="zoomStyle"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -43,52 +48,81 @@ const PIXELS_PER_TICK_WIDE = 200;
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
bounds: {
|
||||
viewBounds: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isFixed: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
altPressed: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inPanMode: false,
|
||||
dragStartX: undefined,
|
||||
dragX: undefined,
|
||||
zoomStyle: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
inZoomMode() {
|
||||
return !this.inPanMode;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
bounds: {
|
||||
handler(bounds) {
|
||||
viewBounds: {
|
||||
handler() {
|
||||
this.setScale();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let axisHolder = this.$refs.axisHolder;
|
||||
let height = axisHolder.offsetHeight;
|
||||
let vis = d3Selection.select(axisHolder)
|
||||
.append("svg:svg")
|
||||
.attr("width", "100%")
|
||||
.attr("height", height);
|
||||
let vis = d3Selection.select(this.$refs.axisHolder).append("svg:svg");
|
||||
|
||||
this.width = this.$refs.axisHolder.clientWidth;
|
||||
this.xAxis = d3Axis.axisTop();
|
||||
this.dragging = false;
|
||||
|
||||
// draw x axis with labels. CSS is used to position them.
|
||||
this.axisElement = vis.append("g");
|
||||
this.axisElement = vis.append("g")
|
||||
.attr("class", "axis");
|
||||
|
||||
this.setViewFromTimeSystem(this.openmct.time.timeSystem());
|
||||
this.setAxisDimensions();
|
||||
this.setScale();
|
||||
|
||||
//Respond to changes in conductor
|
||||
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
|
||||
setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||
},
|
||||
destroyed() {
|
||||
},
|
||||
methods: {
|
||||
setAxisDimensions() {
|
||||
const axisHolder = this.$refs.axisHolder;
|
||||
const rect = axisHolder.getBoundingClientRect();
|
||||
|
||||
this.left = Math.round(rect.left);
|
||||
this.width = axisHolder.clientWidth;
|
||||
},
|
||||
setScale() {
|
||||
if (!this.width) {
|
||||
return;
|
||||
}
|
||||
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let bounds = this.bounds;
|
||||
|
||||
if (timeSystem.isUTCBased) {
|
||||
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
|
||||
this.xScale.domain(
|
||||
[new Date(this.viewBounds.start), new Date(this.viewBounds.end)]
|
||||
);
|
||||
} else {
|
||||
this.xScale.domain([bounds.start, bounds.end]);
|
||||
this.xScale.domain(
|
||||
[this.viewBounds.start, this.viewBounds.end]
|
||||
);
|
||||
}
|
||||
|
||||
this.xAxis.scale(this.xScale);
|
||||
@ -102,7 +136,7 @@ export default {
|
||||
this.xAxis.ticks(this.width / PIXELS_PER_TICK);
|
||||
}
|
||||
|
||||
this.msPerPixel = (bounds.end - bounds.start) / this.width;
|
||||
this.msPerPixel = (this.viewBounds.end - this.viewBounds.start) / this.width;
|
||||
},
|
||||
setViewFromTimeSystem(timeSystem) {
|
||||
//The D3 scale used depends on the type of time system as d3
|
||||
@ -120,9 +154,8 @@ export default {
|
||||
},
|
||||
getActiveFormatter() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let isFixed = this.openmct.time.clock() === undefined;
|
||||
|
||||
if (isFixed) {
|
||||
if (this.isFixed) {
|
||||
return this.getFormatter(timeSystem.timeFormat);
|
||||
} else {
|
||||
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
@ -134,45 +167,131 @@ export default {
|
||||
}).formatter;
|
||||
},
|
||||
dragStart($event) {
|
||||
let isFixed = this.openmct.time.clock() === undefined;
|
||||
if (isFixed) {
|
||||
if (this.isFixed) {
|
||||
this.dragStartX = $event.clientX;
|
||||
|
||||
if (this.altPressed) {
|
||||
this.inPanMode = true;
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', this.drag);
|
||||
document.addEventListener('mouseup', this.dragEnd, {
|
||||
once: true
|
||||
});
|
||||
|
||||
if (this.inZoomMode) {
|
||||
this.startZoom();
|
||||
}
|
||||
}
|
||||
},
|
||||
drag($event) {
|
||||
if (!this.dragging) {
|
||||
this.dragging = true;
|
||||
requestAnimationFrame(()=>{
|
||||
let deltaX = $event.clientX - this.dragStartX;
|
||||
let percX = deltaX / this.width;
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let deltaTime = bounds.end - bounds.start;
|
||||
let newStart = bounds.start - percX * deltaTime;
|
||||
this.$emit('panAxis',{
|
||||
start: newStart,
|
||||
end: newStart + deltaTime
|
||||
});
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.dragX = $event.clientX;
|
||||
this.inPanMode ? this.pan() : this.zoom();
|
||||
this.dragging = false;
|
||||
})
|
||||
} else {
|
||||
console.log('Rejected drag due to RAF cap');
|
||||
});
|
||||
}
|
||||
},
|
||||
dragEnd() {
|
||||
this.inPanMode ? this.endPan() : this.endZoom();
|
||||
|
||||
document.removeEventListener('mousemove', this.drag);
|
||||
this.openmct.time.bounds({
|
||||
start: this.bounds.start,
|
||||
end: this.bounds.end
|
||||
this.dragStartX = undefined;
|
||||
this.dragX = undefined;
|
||||
},
|
||||
pan() {
|
||||
const panBounds = this.getPanBounds();
|
||||
this.$emit('panAxis', panBounds);
|
||||
},
|
||||
endPan() {
|
||||
const panBounds = this.dragStartX && this.dragX && this.dragStartX !== this.dragX
|
||||
? this.getPanBounds()
|
||||
: undefined;
|
||||
this.$emit('endPan', panBounds);
|
||||
this.inPanMode = false;
|
||||
},
|
||||
getPanBounds() {
|
||||
const bounds = this.openmct.time.bounds();
|
||||
const deltaTime = bounds.end - bounds.start;
|
||||
const deltaX = this.dragX - this.dragStartX;
|
||||
const percX = deltaX / this.width;
|
||||
const panStart = bounds.start - percX * deltaTime;
|
||||
|
||||
return {
|
||||
start: panStart,
|
||||
end: panStart + deltaTime
|
||||
};
|
||||
},
|
||||
startZoom() {
|
||||
const x = this.scaleToBounds(this.dragStartX);
|
||||
|
||||
this.zoomStyle = {
|
||||
left: `${this.dragStartX - this.left}px`
|
||||
};
|
||||
|
||||
this.$emit('zoomAxis', {
|
||||
start: x,
|
||||
end: x
|
||||
});
|
||||
},
|
||||
zoom() {
|
||||
const zoomRange = this.getZoomRange();
|
||||
|
||||
this.zoomStyle = {
|
||||
left: `${zoomRange.start - this.left}px`,
|
||||
width: `${zoomRange.end - zoomRange.start}px`
|
||||
};
|
||||
|
||||
this.$emit('zoomAxis', {
|
||||
start: this.scaleToBounds(zoomRange.start),
|
||||
end: this.scaleToBounds(zoomRange.end)
|
||||
});
|
||||
},
|
||||
endZoom() {
|
||||
const zoomRange = this.dragStartX && this.dragX && this.dragStartX !== this.dragX
|
||||
? this.getZoomRange()
|
||||
: undefined;
|
||||
|
||||
const zoomBounds = zoomRange
|
||||
? {
|
||||
start: this.scaleToBounds(zoomRange.start),
|
||||
end: this.scaleToBounds(zoomRange.end)
|
||||
}
|
||||
: this.openmct.time.bounds();
|
||||
|
||||
this.zoomStyle = {};
|
||||
this.$emit('endZoom', zoomBounds);
|
||||
},
|
||||
getZoomRange() {
|
||||
const leftBound = this.left;
|
||||
const rightBound = this.left + this.width;
|
||||
|
||||
const zoomStart = this.dragX < leftBound
|
||||
? leftBound
|
||||
: Math.min(this.dragX, this.dragStartX);
|
||||
|
||||
const zoomEnd = this.dragX > rightBound
|
||||
? rightBound
|
||||
: Math.max(this.dragX, this.dragStartX);
|
||||
|
||||
return {
|
||||
start: zoomStart,
|
||||
end: zoomEnd
|
||||
};
|
||||
},
|
||||
scaleToBounds(value) {
|
||||
const bounds = this.openmct.time.bounds();
|
||||
const timeDelta = bounds.end - bounds.start;
|
||||
const valueDelta = value - this.left;
|
||||
const offset = valueDelta / this.width * timeDelta;
|
||||
return bounds.start + offset;
|
||||
},
|
||||
resize() {
|
||||
if (this.$refs.axisHolder.clientWidth !== this.width) {
|
||||
this.width = this.$refs.axisHolder.clientWidth;
|
||||
this.setAxisDimensions();
|
||||
this.setScale();
|
||||
}
|
||||
}
|
||||
|
200
src/plugins/timeConductor/ConductorHistory.vue
Normal file
200
src/plugins/timeConductor/ConductorHistory.vue
Normal file
@ -0,0 +1,200 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
|
||||
<button class="c-button--menu c-history-button icon-history"
|
||||
@click.prevent="toggle"
|
||||
>
|
||||
<span class="c-button__label">History</span>
|
||||
</button>
|
||||
<div v-if="open"
|
||||
class="c-menu c-conductor__history-menu"
|
||||
>
|
||||
<ul v-if="hasHistoryPresets">
|
||||
<li
|
||||
v-for="preset in presets"
|
||||
:key="preset.label"
|
||||
class="icon-clock"
|
||||
@click="selectPresetBounds(preset.bounds)"
|
||||
>
|
||||
{{ preset.label }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
v-if="hasHistoryPresets"
|
||||
class="c-menu__section-separator"
|
||||
></div>
|
||||
|
||||
<div class="c-menu__section-hint">
|
||||
Past timeframes, ordered by latest first
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li
|
||||
v-for="(timespan, index) in historyForCurrentTimeSystem"
|
||||
:key="index"
|
||||
class="icon-history"
|
||||
@click="selectTimespan(timespan)"
|
||||
>
|
||||
{{ formatTime(timespan.start) }} - {{ formatTime(timespan.end) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import toggleMixin from '../../ui/mixins/toggle-mixin';
|
||||
|
||||
const LOCAL_STORAGE_HISTORY_KEY = 'tcHistory';
|
||||
const DEFAULT_RECORDS = 10;
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'configuration'],
|
||||
mixins: [toggleMixin],
|
||||
props: {
|
||||
bounds: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
timeSystem: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
history: {}, // contains arrays of timespans {start, end}, array key is time system key
|
||||
presets: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasHistoryPresets() {
|
||||
return this.timeSystem.isUTCBased && this.presets.length;
|
||||
},
|
||||
historyForCurrentTimeSystem() {
|
||||
const history = this.history[this.timeSystem.key];
|
||||
|
||||
return history;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
bounds: {
|
||||
handler() {
|
||||
this.addTimespan();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
timeSystem: {
|
||||
handler() {
|
||||
this.loadConfiguration();
|
||||
this.addTimespan();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
history: {
|
||||
handler() {
|
||||
this.persistHistoryToLocalStorage();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getHistoryFromLocalStorage();
|
||||
},
|
||||
methods: {
|
||||
getHistoryFromLocalStorage() {
|
||||
if (localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY)) {
|
||||
this.history = JSON.parse(localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY))
|
||||
} else {
|
||||
this.history = {};
|
||||
this.persistHistoryToLocalStorage();
|
||||
}
|
||||
},
|
||||
persistHistoryToLocalStorage() {
|
||||
localStorage.setItem(LOCAL_STORAGE_HISTORY_KEY, JSON.stringify(this.history));
|
||||
},
|
||||
addTimespan() {
|
||||
const key = this.timeSystem.key;
|
||||
let [...currentHistory] = this.history[key] || [];
|
||||
const timespan = {
|
||||
start: this.bounds.start,
|
||||
end: this.bounds.end
|
||||
};
|
||||
|
||||
const isNotEqual = function (entry) {
|
||||
const start = entry.start !== this.start;
|
||||
const end = entry.end !== this.end;
|
||||
|
||||
return start || end;
|
||||
};
|
||||
currentHistory = currentHistory.filter(isNotEqual, timespan);
|
||||
|
||||
while (currentHistory.length >= this.records) {
|
||||
currentHistory.pop();
|
||||
}
|
||||
|
||||
currentHistory.unshift(timespan);
|
||||
this.history[key] = currentHistory;
|
||||
},
|
||||
selectTimespan(timespan) {
|
||||
this.openmct.time.bounds(timespan);
|
||||
},
|
||||
selectPresetBounds(bounds) {
|
||||
const start = typeof bounds.start === 'function' ? bounds.start() : bounds.start;
|
||||
const end = typeof bounds.end === 'function' ? bounds.end() : bounds.end;
|
||||
|
||||
this.selectTimespan({
|
||||
start: start,
|
||||
end: end
|
||||
});
|
||||
},
|
||||
loadConfiguration() {
|
||||
const configurations = this.configuration.menuOptions
|
||||
.filter(option => option.timeSystem === this.timeSystem.key);
|
||||
|
||||
this.presets = this.loadPresets(configurations);
|
||||
this.records = this.loadRecords(configurations);
|
||||
},
|
||||
loadPresets(configurations) {
|
||||
const configuration = configurations.find(option => option.presets);
|
||||
const presets = configuration ? configuration.presets : [];
|
||||
|
||||
return presets;
|
||||
},
|
||||
loadRecords(configurations) {
|
||||
const configuration = configurations.find(option => option.records);
|
||||
const records = configuration ? configuration.records : DEFAULT_RECORDS;
|
||||
|
||||
return records;
|
||||
},
|
||||
formatTime(time) {
|
||||
const formatter = this.openmct.telemetry.getValueFormatter({
|
||||
format: this.timeSystem.timeFormat
|
||||
}).formatter;
|
||||
|
||||
return formatter.format(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -110,7 +110,7 @@ export default {
|
||||
if (clock === undefined) {
|
||||
return {
|
||||
key: 'fixed',
|
||||
name: 'Fixed Timespan Mode',
|
||||
name: 'Fixed Timespan',
|
||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
||||
cssClass: 'icon-tabular'
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
text-rendering: geometricPrecision;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
> g {
|
||||
> g.axis {
|
||||
// Overall Tick holder
|
||||
transform: translateY($tickYPos);
|
||||
path {
|
||||
@ -44,7 +44,6 @@
|
||||
}
|
||||
|
||||
body.desktop .is-fixed-mode & {
|
||||
@include cursorGrab();
|
||||
background-size: 3px 30%;
|
||||
background-color: $colorBodyBgSubtle;
|
||||
box-shadow: inset rgba(black, 0.4) 0 1px 1px;
|
||||
@ -55,17 +54,6 @@
|
||||
stroke: $colorBodyBgSubtle;
|
||||
transition: $transOut;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
$c: $colorKeySubtle;
|
||||
background-color: $c;
|
||||
transition: $transIn;
|
||||
svg text {
|
||||
stroke: $c;
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-realtime-mode & {
|
||||
|
@ -57,6 +57,65 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.is-fixed-mode {
|
||||
.c-conductor-axis {
|
||||
&__zoom-indicator {
|
||||
border: 1px solid transparent;
|
||||
display: none; // Hidden by default
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-panning),
|
||||
&:not(.is-zooming) {
|
||||
.c-conductor-axis {
|
||||
&:hover,
|
||||
&:active {
|
||||
cursor: col-resize;
|
||||
filter: $timeConductorAxisHoverFilter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-panning,
|
||||
&.is-zooming {
|
||||
.c-conductor-input input {
|
||||
// Styles for inputs while zooming or panning
|
||||
background: rgba($timeConductorActiveBg, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
&.alt-pressed {
|
||||
.c-conductor-axis:hover {
|
||||
// When alt is being pressed and user is hovering over the axis, set the cursor
|
||||
@include cursorGrab();
|
||||
}
|
||||
}
|
||||
|
||||
&.is-panning {
|
||||
.c-conductor-axis {
|
||||
@include cursorGrab();
|
||||
background-color: $timeConductorActivePanBg;
|
||||
transition: $transIn;
|
||||
|
||||
svg text {
|
||||
stroke: $timeConductorActivePanBg;
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-zooming {
|
||||
.c-conductor-axis__zoom-indicator {
|
||||
display: block;
|
||||
position: absolute;
|
||||
background: rgba($timeConductorActiveBg, 0.4);
|
||||
border-left-color: $timeConductorActiveBg;
|
||||
border-right-color: $timeConductorActiveBg;
|
||||
top: 0; bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-realtime-mode {
|
||||
.c-conductor__time-bounds {
|
||||
grid-template-columns: 20px auto 1fr auto auto;
|
||||
|
@ -142,6 +142,9 @@ $colorTimeHov: pullForward($colorTime, 10%);
|
||||
$colorTimeSubtle: pushBack($colorTime, 20%);
|
||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||
$timeConductorAxisHoverFilter: brightness(1.2);
|
||||
$timeConductorActiveBg: $colorKey;
|
||||
$timeConductorActivePanBg: #226074;
|
||||
|
||||
/************************************************** BROWSING */
|
||||
$browseFrameColor: pullForward($colorBodyBg, 10%);
|
||||
|
@ -146,6 +146,9 @@ $colorTimeHov: pullForward($colorTime, 10%);
|
||||
$colorTimeSubtle: pushBack($colorTime, 20%);
|
||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||
$timeConductorAxisHoverFilter: brightness(1.2);
|
||||
$timeConductorActiveBg: $colorKey;
|
||||
$timeConductorActivePanBg: #226074;
|
||||
|
||||
/************************************************** BROWSING */
|
||||
$browseFrameColor: pullForward($colorBodyBg, 10%);
|
||||
|
@ -132,7 +132,7 @@ $colorPausedFg: #fff;
|
||||
// Base variations
|
||||
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
|
||||
$colorBodyBgSubtleHov: pushBack($colorKey, 50%);
|
||||
$colorKeySubtle: pushBack($colorKey, 10%);
|
||||
$colorKeySubtle: pushBack($colorKey, 20%);
|
||||
|
||||
// Time Colors
|
||||
$colorTime: #618cff;
|
||||
@ -142,6 +142,9 @@ $colorTimeHov: pushBack($colorTime, 5%);
|
||||
$colorTimeSubtle: pushBack($colorTime, 20%);
|
||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||
$timeConductorAxisHoverFilter: brightness(0.8);
|
||||
$timeConductorActiveBg: $colorKey;
|
||||
$timeConductorActivePanBg: #A0CDE1;
|
||||
|
||||
/************************************************** BROWSING */
|
||||
$browseFrameColor: pullForward($colorBodyBg, 10%);
|
||||
|
@ -148,6 +148,7 @@ $glyph-icon-cursor-lock: '\e929';
|
||||
$glyph-icon-flag: '\e92a';
|
||||
$glyph-icon-eye-disabled: '\e92b';
|
||||
$glyph-icon-notebook-page: '\e92c';
|
||||
$glyph-icon-unlocked: '\e92d';
|
||||
$glyph-icon-arrows-right-left: '\ea00';
|
||||
$glyph-icon-arrows-up-down: '\ea01';
|
||||
$glyph-icon-bullet: '\ea02';
|
||||
@ -198,6 +199,11 @@ $glyph-icon-export: '\ea2e';
|
||||
$glyph-icon-font-size: '\ea2f';
|
||||
$glyph-icon-clear-data: '\ea30';
|
||||
$glyph-icon-history: '\ea31';
|
||||
$glyph-icon-arrow-nav-to-parent: '\ea32';
|
||||
$glyph-icon-crosshair-in-circle: '\ea33';
|
||||
$glyph-icon-target: '\ea34';
|
||||
$glyph-icon-items-collapse: '\ea35';
|
||||
$glyph-icon-items-expand: '\ea36';
|
||||
$glyph-icon-activity: '\eb00';
|
||||
$glyph-icon-activity-mode: '\eb01';
|
||||
$glyph-icon-autoflow-tabular: '\eb02';
|
||||
@ -240,6 +246,7 @@ $glyph-icon-command: '\eb26';
|
||||
$glyph-icon-conditional: '\eb27';
|
||||
$glyph-icon-condition-widget: '\eb28';
|
||||
$glyph-icon-alphanumeric: '\eb29';
|
||||
$glyph-icon-image-telemetry: '\eb2a';
|
||||
|
||||
/************************** GLYPHS AS DATA URI */
|
||||
// Only objects have been converted, for use in Create menu and folder views
|
||||
|
@ -462,9 +462,17 @@ select {
|
||||
text-shadow: $shdwMenuText;
|
||||
padding: $interiorMarginSm;
|
||||
box-shadow: $shdwMenu;
|
||||
display: block;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
|
||||
> * {
|
||||
flex: 0 0 auto;
|
||||
//+ * {
|
||||
// margin-top: $interiorMarginSm;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin menuInner() {
|
||||
@ -502,6 +510,23 @@ select {
|
||||
.c-menu {
|
||||
@include menuOuter();
|
||||
@include menuInner();
|
||||
|
||||
&__section-hint {
|
||||
$m: $interiorMargin;
|
||||
margin: $m 0;
|
||||
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
|
||||
|
||||
opacity: 0.6;
|
||||
font-size: 0.9em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
&__section-separator {
|
||||
$m: $interiorMargin;
|
||||
border-top: 1px solid $colorInteriorBorder;
|
||||
margin: $m 0;
|
||||
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
|
||||
}
|
||||
}
|
||||
|
||||
.c-super-menu {
|
||||
|
@ -84,6 +84,7 @@
|
||||
.icon-flag { @include glyphBefore($glyph-icon-flag); }
|
||||
.icon-eye-disabled { @include glyphBefore($glyph-icon-eye-disabled); }
|
||||
.icon-notebook-page { @include glyphBefore($glyph-icon-notebook-page); }
|
||||
.icon-unlocked { @include glyphBefore($glyph-icon-unlocked); }
|
||||
.icon-arrows-right-left { @include glyphBefore($glyph-icon-arrows-right-left); }
|
||||
.icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); }
|
||||
.icon-bullet { @include glyphBefore($glyph-icon-bullet); }
|
||||
@ -134,6 +135,11 @@
|
||||
.icon-font-size { @include glyphBefore($glyph-icon-font-size); }
|
||||
.icon-clear-data { @include glyphBefore($glyph-icon-clear-data); }
|
||||
.icon-history { @include glyphBefore($glyph-icon-history); }
|
||||
.icon-arrow-nav-to-parent { @include glyphBefore($glyph-icon-arrow-nav-to-parent); }
|
||||
.icon-crosshair-in-circle { @include glyphBefore($glyph-icon-crosshair-in-circle); }
|
||||
.icon-target { @include glyphBefore($glyph-icon-target); }
|
||||
.icon-items-collapse { @include glyphBefore($glyph-icon-items-collapse); }
|
||||
.icon-items-expand { @include glyphBefore($glyph-icon-items-expand); }
|
||||
.icon-activity { @include glyphBefore($glyph-icon-activity); }
|
||||
.icon-activity-mode { @include glyphBefore($glyph-icon-activity-mode); }
|
||||
.icon-autoflow-tabular { @include glyphBefore($glyph-icon-autoflow-tabular); }
|
||||
@ -176,6 +182,7 @@
|
||||
.icon-conditional { @include glyphBefore($glyph-icon-conditional); }
|
||||
.icon-condition-widget { @include glyphBefore($glyph-icon-condition-widget); }
|
||||
.icon-alphanumeric { @include glyphBefore($glyph-icon-alphanumeric); }
|
||||
.icon-image-telemetry { @include glyphBefore($glyph-icon-image-telemetry); }
|
||||
|
||||
/************************** 12 PX CLASSES */
|
||||
// TODO: sync with 16px redo as of 10/25/18
|
||||
|
@ -114,25 +114,6 @@ mct-plot {
|
||||
.plot-wrapper-axis-and-display-area {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
|
||||
.l-state-indicators {
|
||||
color: $colorPausedBg;
|
||||
position: absolute;
|
||||
display: block;
|
||||
font-size: 1.5em;
|
||||
pointer-events: none;
|
||||
top: $interiorMarginSm;
|
||||
left: $interiorMarginSm;
|
||||
z-index: 2;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
.t-alert-unsynced {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gl-plot-wrapper-display-area-and-x-axis {
|
||||
@ -294,6 +275,25 @@ mct-plot {
|
||||
right: $m;
|
||||
}
|
||||
}
|
||||
|
||||
.l-state-indicators {
|
||||
color: $colorPausedBg;
|
||||
position: absolute;
|
||||
display: block;
|
||||
font-size: 1.5em;
|
||||
pointer-events: none;
|
||||
top: $interiorMarginSm;
|
||||
left: $interiorMarginSm;
|
||||
z-index: 2;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
.t-alert-unsynced {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gl-plot-display-area,
|
||||
@ -432,6 +432,7 @@ mct-plot {
|
||||
&__wrapper {
|
||||
// Holds view-control and both collapsed and expanded legends
|
||||
flex: 1 1 auto;
|
||||
overflow: auto; // Prevents collapsed legend from forcing scrollbars on higher parent containers
|
||||
}
|
||||
|
||||
&__view-control {
|
||||
|
@ -893,7 +893,7 @@ body.desktop {
|
||||
.grid-row {
|
||||
.grid-cell {
|
||||
padding: 3px $interiorMarginLg 3px 0;
|
||||
&[title] {
|
||||
&[title]:not([title=""]) {
|
||||
// When a cell has a title, assume it's helpful text
|
||||
cursor: help;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -30,7 +30,7 @@
|
||||
<glyph unicode="" glyph-name="icon-hourglass" d="M1024 832h-1024c0-282.8 229.2-512 512-512s512 229.2 512 512zM512 448c-102.6 0-199 40-271.6 112.4-41.2 41.2-72 90.2-90.8 143.6h724.6c-18.8-53.4-49.6-102.4-90.8-143.6-72.4-72.4-168.8-112.4-271.4-112.4zM512 320c-282.8 0-512-229.2-512-512h1024c0 282.8-229.2 512-512 512z" />
|
||||
<glyph unicode="" glyph-name="icon-info" d="M512 832c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM512 704c70.6 0 128-57.4 128-128s-57.4-128-128-128c-70.6 0-128 57.4-128 128s57.4 128 128 128zM704 0h-384v128h64v256h256v-256h64v-128z" />
|
||||
<glyph unicode="" glyph-name="icon-link" d="M1024 320l-512 512v-307.2l-512-204.8v-256h512v-256z" />
|
||||
<glyph unicode="" glyph-name="icon-lock" d="M832 448h-32v96c0 158.8-129.2 288-288 288s-288-129.2-288-288v-96h-32c-70.4 0-128-57.6-128-128v-384c0-70.4 57.6-128 128-128h640c70.4 0 128 57.6 128 128v384c0 70.4-57.6 128-128 128zM416 544c0 53 43 96 96 96s96-43 96-96v-96h-192v96z" />
|
||||
<glyph unicode="" glyph-name="icon-lock" horiz-adv-x="768" d="M702 448h-62v128c0 141.385-114.615 256-256 256s-256-114.615-256-256v0-128h-64c-35.301-0.113-63.887-28.699-64-63.989v-512.011c0.113-35.301 28.699-63.887 63.989-64h638.011c35.301 0.113 63.887 28.699 64 63.989v512.011c-0.113 35.301-28.699 63.887-63.989 64h-0.011zM256 448v128c0 70.692 57.308 128 128 128s128-57.308 128-128v0-128z" />
|
||||
<glyph unicode="" glyph-name="icon-minus" d="M960 192c35.2 0 64 28.8 64 64v128c0 35.2-28.8 64-64 64h-896c-35.2 0-64-28.8-64-64v-128c0-35.2 28.8-64 64-64h896z" />
|
||||
<glyph unicode="" glyph-name="icon-people" d="M704 512h64c70.4 0 128 57.6 128 128v64c0 70.4-57.6 128-128 128h-64c-70.4 0-128-57.6-128-128v-64c0-70.4 57.6-128 128-128zM256 512h64c70.4 0 128 57.6 128 128v64c0 70.4-57.6 128-128 128h-64c-70.4 0-128-57.6-128-128v-64c0-70.4 57.6-128 128-128zM832 448h-192c-34.908 0-67.716-9.448-96-25.904 57.278-33.324 96-95.404 96-166.096v-448h384v448c0 105.6-86.4 192-192 192zM384 448h-192c-105.6 0-192-86.4-192-192v-448h576v448c0 105.6-86.4 192-192 192z" />
|
||||
<glyph unicode="" glyph-name="icon-person" d="M768 576c0-105.6-86.4-192-192-192h-128c-105.6 0-192 86.4-192 192v64c0 105.6 86.4 192 192 192h128c105.6 0 192-86.4 192-192v-64zM64-192v192c0 140.8 115.2 256 256 256h384c140.8 0 256-115.2 256-256v-192z" />
|
||||
@ -52,6 +52,7 @@
|
||||
<glyph unicode="" glyph-name="icon-flag" d="M192 192h832l-192 320 192 320h-896c-70.606-0.215-127.785-57.394-128-127.979v-896.021h192z" />
|
||||
<glyph unicode="" glyph-name="icon-eye-disabled" d="M209.46 223.32q-7.46 9.86-14.26 20.28c-14.737 21.984-27.741 47.184-37.759 73.847l-0.841 2.553c11.078 29.259 24.068 54.443 39.51 77.869l-0.91-1.469c23.221 34.963 50.705 64.8 82.207 89.793l0.793 0.607c57.663 45.719 130.179 75.053 209.311 79.947l1.069 0.053 114.48 140.88c-27.366 5.017-58.869 7.898-91.041 7.92h-0.019c-245.8 0-452.2-168-510.8-395.6 21.856-82.93 60.906-154.847 113.325-214.773l-0.525 0.613zM814.76 416.92q7.52-10 14.44-20.52c14.737-21.984 27.741-47.184 37.759-73.847l0.841-2.553c-10.859-29.216-23.863-54.416-39.447-77.748l0.847 1.348c-23.221-34.963-50.705-64.8-82.207-89.793l-0.793-0.607c-57.762-45.834-130.437-75.216-209.743-80.049l-1.057-0.051-114.46-140.86c27.346-4.988 58.817-7.84 90.955-7.84 0.037 0 0.074 0 0.111 0h-0.005c245.8 0 452.2 168 510.8 395.6-21.856 82.93-60.906 154.847-113.325 214.773l0.525-0.613zM832 832l-832-1024h192l832 1024h-192z" />
|
||||
<glyph unicode="" glyph-name="icon-notebook-page" d="M830 770h-830l-4-702c0-106.6 87.4-194 194-194h640c106.6 0 194 87.4 194 194v508c0 106.8-87.4 194-194 194zM832 386l-384-384-192 192v256l192-192 384 384v-256z" />
|
||||
<glyph unicode="" glyph-name="icon-unlocked" d="M768 832c-141.339-0.114-255.886-114.661-256-255.989v-128.011h-448c-35.301-0.113-63.887-28.699-64-63.989v-512.011c0.113-35.301 28.699-63.887 63.989-64h638.011c35.301 0.113 63.887 28.699 64 63.989v512.011c-0.113 35.301-28.699 63.887-63.989 64h-62.011v128c0 70.692 57.308 128 128 128s128-57.308 128-128v0-128h128v128c-0.114 141.339-114.661 255.886-255.989 256h-0.011z" />
|
||||
<glyph unicode="" glyph-name="icon-arrows-right-left" d="M1024 320l-448-512v1024zM448 832l-448-512 448-512z" />
|
||||
<glyph unicode="" glyph-name="icon-arrows-up-down" d="M512 832l512-448h-1024zM0 256l512-448 512 448z" />
|
||||
<glyph unicode="" glyph-name="icon-bullet" d="M832 80c0-44-36-80-80-80h-480c-44 0-80 36-80 80v480c0 44 36 80 80 80h480c44 0 80-36 80-80v-480z" />
|
||||
@ -102,6 +103,11 @@
|
||||
<glyph unicode="" glyph-name="icon-font-size" d="M842.841 451.952h-120.956l-52.382-139.676 52.918-141.12 59.942 159.84 62.361-166.314h-119.884l34.019-90.717h119.884l39.695-105.836h105.836l-181.434 483.823zM263.903 671.871l-263.903-703.742h153.944l57.729 153.944h280.397l57.729-153.944h153.944l-263.903 703.742zM261.154 254.024l90.717 241.911 90.717-241.911z" />
|
||||
<glyph unicode="" glyph-name="icon-clear-data" d="M632 520l-120-120-120 120-80-80 120-120-120-120 80-80 120 120 120-120 80 80-120 120 120 120-80 80zM512 832c-282.76 0-512-86-512-192v-640c0-106 229.24-192 512-192s512 86 512 192v640c0 106-229.24 192-512 192zM512 0c-176.731 0-320 143.269-320 320s143.269 320 320 320c176.731 0 320-143.269 320-320v0c0-176.731-143.269-320-320-320v0z" />
|
||||
<glyph unicode="" glyph-name="icon-history" d="M576 768c-247.4 0-448-200.6-448-448h-128l192-192 192 192h-128c0 85.4 33.2 165.8 93.8 226.2 60.4 60.6 140.8 93.8 226.2 93.8s165.8-33.2 226.2-93.8c60.6-60.4 93.8-140.8 93.8-226.2s-33.2-165.8-93.8-226.2c-60.4-60.6-140.8-93.8-226.2-93.8s-165.8 33.2-226.2 93.8l-90.6-90.6c81-81 193-131.2 316.8-131.2 247.4 0 448 200.6 448 448s-200.6 448-448 448zM576 560c-26.6 0-48-21.4-48-48v-211.8l142-142c9.4-9.4 21.6-14 34-14s24.6 4.6 34 14c18.8 18.8 18.8 49.2 0 67.8l-114 114v172c0 26.6-21.4 48-48 48z" />
|
||||
<glyph unicode="" glyph-name="icon-arrow-up-to-parent" horiz-adv-x="1056" d="M643.427 6.739c-81.955 0.697-148.179 67.065-148.642 149.010v395.872l296.871-247.393v197.914l-395.828 329.857-395.828-328.62v-197.502l296.871 246.156v-396.241c0-190.905 155.239-346.556 346.144-346.968l412.321-0.825 0.412 197.914z" />
|
||||
<glyph unicode="" glyph-name="icon-crosshair-in-circle" d="M512 832c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM783.6 48.4c-54.634-54.8-125.77-93.12-205.322-106.874l-2.278-0.326v250.8h-128v-250.8c-161.302 28.062-286.738 153.497-314.468 312.5l-0.332 2.3h250.8v128h-250.8c28.062 161.302 153.497 286.738 312.5 314.468l2.3 0.332v-250.8h128v250.8c161.302-28.062 286.738-153.497 314.468-312.5l0.332-2.3h-250.8v-128h250.8c-14.080-81.83-52.4-152.966-107.191-207.591l-0.009-0.009z" />
|
||||
<glyph unicode="" glyph-name="icon-target" d="M512 448c70.692 0 128-57.308 128-128s-57.308-128-128-128c-70.692 0-128 57.308-128 128v0c0.114 70.647 57.353 127.886 127.989 128h0.011zM512 576c-141.385 0-256-114.615-256-256s114.615-256 256-256c141.385 0 256 114.615 256 256v0c-0.114 141.339-114.661 255.886-255.989 256h-0.011zM512 704c211.87-0.128 383.575-171.912 383.575-383.8 0-211.967-171.833-383.8-383.8-383.8s-383.8 171.833-383.8 383.8c0 105.99 42.963 201.945 112.425 271.4v0c69.21 69.437 164.944 112.401 270.713 112.401 0.312 0 0.624 0 0.936-0.001h-0.048zM512 832c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512z" />
|
||||
<glyph unicode="" glyph-name="icon-items-collapse" d="M45.2 173.2h229.6l-274.8-274.6 90.6-90.6 274.6 274.8v-229.6h128v448h-448v-128zM1024 741.4l-90.6 90.6-274.6-274.8v229.6h-128v-448h448v128h-229.6l274.8 274.6z" />
|
||||
<glyph unicode="" glyph-name="icon-items-expand" d="M448-64h-229.4l274.6 274.8-90.4 90.4-274.8-274.6v229.4h-128v-448h448v128zM530.8 429.2l90.4-90.4 274.8 274.6v-229.4h128v448h-448v-128h229.4l-274.6-274.8z" />
|
||||
<glyph unicode="" glyph-name="icon-activity" d="M576 768h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" />
|
||||
<glyph unicode="" glyph-name="icon-activity-mode" d="M512 832c-214.8 0-398.8-132.4-474.8-320h90.8c56.8 0 108-24.8 143-64h241l-192 192h256l320-320-320-320h-256l192 192h-241c-35-39.2-86.2-64-143-64h-90.8c76-187.6 259.8-320 474.8-320 282.8 0 512 229.2 512 512s-229.2 512-512 512z" />
|
||||
<glyph unicode="" glyph-name="icon-autoflow-tabular" d="M192 832c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 832h256v-1024h-256v1024zM832 832h-64v-704h256v512c0 105.6-86.4 192-192 192z" />
|
||||
@ -144,4 +150,5 @@
|
||||
<glyph unicode="" glyph-name="icon-conditional" d="M512 832c-282.76 0-512-229.24-512-512s229.24-512 512-512 512 229.24 512 512-229.24 512-512 512zM512 64l-384 256 384 256 384-256z" />
|
||||
<glyph unicode="" glyph-name="icon-condition-widget" d="M832 832h-640c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM512 64l-384 256 384 256 384-256z" />
|
||||
<glyph unicode="" glyph-name="icon-alphanumeric" d="M535.6 301.4c-8.4-1.6-17.2-3-26.2-4s-18.2-2.4-27.2-4c-10.196-1.861-18.808-4.010-27.21-6.633l1.61 0.433c-8.609-2.674-16.105-6.348-22.89-10.987l0.29 0.187c-6.693-4.517-12.283-10.107-16.663-16.585l-0.137-0.215c-4.6-6.8-7.4-15.6-8.8-26s-0.4-18.4 2.4-25.2c2.746-6.688 7.224-12.195 12.881-16.122l0.119-0.078c5.967-4.053 13.057-6.94 20.704-8.161l0.296-0.039c7.592-1.527 16.319-2.4 25.25-2.4 0.123 0 0.246 0 0.369 0h-0.019c22.2 0 39.6 3.6 52.6 11s23.2 16.2 30.2 26.4c6.273 8.873 11.271 19.191 14.426 30.285l0.174 0.715c1.853 6.809 3.601 15.41 4.855 24.169l0.145 1.231 5.2 41.6c-5.4-4.217-11.723-7.564-18.583-9.689l-0.417-0.111c-6.489-2.241-14.362-4.255-22.444-5.662l-0.956-0.138zM1024 448v192h-152l24 192h-192l-24-192h-256l24 192h-192l-24-192h-232v-192h208l-32-256h-176v-192h152l-24-192h192l24 192h256l-24-192h192l24 192h232v192h-208l32 256zM702.8 420.2l-26.4-211.8c-2.231-15.809-3.537-34.122-3.6-52.727v-0.073c0-16.8 2.2-29.4 6.4-37.8h-113.4c-1.342 5.556-2.338 12.122-2.781 18.84l-0.019 0.36c-0.261 3.524-0.409 7.634-0.409 11.778 0 2.962 0.076 5.907 0.226 8.832l-0.017-0.41c-18.663-17.401-41.395-30.694-66.597-38.289l-1.203-0.311c-22.627-6.956-48.639-10.974-75.586-11h-0.014c-0.764-0.011-1.666-0.018-2.569-0.018-18.098 0-35.598 2.563-52.156 7.345l1.325-0.328c-15.991 4.512-29.851 12.090-41.545 22.122l0.145-0.122c-11.233 9.982-19.792 22.733-24.624 37.192l-0.176 0.608c-5.2 15.2-6.4 33.4-3.8 54.4s9.4 42.2 19.4 57.2c9.524 14.399 21.535 26.346 35.532 35.512l0.468 0.288c13.387 8.662 28.922 15.533 45.512 19.765l1.088 0.235c13.436 3.792 30.801 7.554 48.47 10.41l2.93 0.39c17 2.6 33.8 4.6 50.4 6.2 16.628 1.527 31.69 4.070 46.349 7.643l-2.149-0.443c13 3 23.6 7.6 31.6 13.6s12.6 15 13.6 26.4 0.8 21.8-2.4 28.8c-2.849 6.902-7.542 12.56-13.468 16.517l-0.132 0.083c-6.217 4.011-13.604 6.78-21.543 7.774l-0.257 0.026c-7.897 1.277-17 2.007-26.274 2.007-0.537 0-1.073-0.002-1.609-0.007l0.082 0.001c-22 0-40-4.6-53.8-14.2s-23-25.2-28-47.2h-111.8c4.8 26.2 14.2 48 27.8 65.4 13.475 16.978 29.89 30.968 48.574 41.377l0.826 0.423c18.192 10.038 39.297 17.806 61.619 22.175l1.381 0.225c20.488 4.162 44.053 6.563 68.171 6.6h0.029c21.8-0.005 43.239-1.532 64.222-4.479l-2.422 0.279c20.641-2.809 39.324-8.783 56.401-17.461l-1.001 0.461c15.909-8.108 28.858-20.031 37.967-34.601l0.233-0.399c9-15 12.2-34.8 9-59.6z" />
|
||||
<glyph unicode="" glyph-name="icon-image-telemetry" d="M512 832c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM783.6 48.4c-69.581-69.675-165.757-112.776-272-112.776-212.298 0-384.4 172.102-384.4 384.4s172.102 384.4 384.4 384.4c212.298 0 384.4-172.102 384.4-384.4 0-0.008 0-0.017 0-0.025v0.001c0.001-0.264 0.001-0.575 0.001-0.887 0-105.769-42.964-201.503-112.391-270.703l-0.010-0.010zM704 448l-128-128-192 192-192-192c0-176.731 143.269-320 320-320s320 143.269 320 320v0z" />
|
||||
</font></defs></svg>
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 57 KiB |
Binary file not shown.
Binary file not shown.
@ -90,7 +90,15 @@ export default {
|
||||
this.openmct.objectViews.off('clearData', this.clearData);
|
||||
},
|
||||
invokeEditModeHandler(editMode) {
|
||||
this.currentView.onEditModeChange(editMode);
|
||||
let edit;
|
||||
|
||||
if (this.currentObject.locked) {
|
||||
edit = false;
|
||||
} else {
|
||||
edit = editMode;
|
||||
}
|
||||
|
||||
this.currentView.onEditModeChange(edit);
|
||||
},
|
||||
toggleEditView(editMode) {
|
||||
this.clear();
|
||||
@ -227,7 +235,11 @@ export default {
|
||||
},
|
||||
onDragOver(event) {
|
||||
if (this.hasComposableDomainObject(event)) {
|
||||
event.preventDefault();
|
||||
if (this.isEditingAllowed()) {
|
||||
event.preventDefault();
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
addObjectToParent(event) {
|
||||
@ -283,6 +295,13 @@ export default {
|
||||
this.currentView.onClearData();
|
||||
}
|
||||
}
|
||||
},
|
||||
isEditingAllowed() {
|
||||
let browseObject = this.openmct.layout.$refs.browseObject.currentObject,
|
||||
objectPath= this.currentObjectPath || this.objectPath,
|
||||
parentObject = objectPath[1];
|
||||
|
||||
return [browseObject, parentObject, this.currentObject].every(object => !object.locked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,6 @@
|
||||
|
||||
&__label {
|
||||
margin-left: $interiorMarginSm;
|
||||
margin-right: $interiorMargin;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,6 @@ export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
StylesInspectorView,
|
||||
// StylesInspectorView,
|
||||
multipane,
|
||||
pane,
|
||||
Elements,
|
||||
|
@ -143,7 +143,7 @@
|
||||
&__label {
|
||||
color: $colorInspectorPropName;
|
||||
|
||||
&[title] {
|
||||
&[title]:not([title=""]) {
|
||||
// When a cell has a title, assume it's helpful text
|
||||
cursor: help;
|
||||
}
|
||||
|
@ -41,7 +41,17 @@
|
||||
/>
|
||||
<div class="l-browse-bar__actions">
|
||||
<button
|
||||
v-if="isViewEditable & !isEditing"
|
||||
v-if="isViewEditable && !isEditing"
|
||||
:title="lockedOrUnlocked"
|
||||
class="c-button"
|
||||
:class="{
|
||||
'icon-lock': domainObject.locked,
|
||||
'icon-unlocked': !domainObject.locked
|
||||
}"
|
||||
@click="toggleLock(!domainObject.locked)"
|
||||
></button>
|
||||
<button
|
||||
v-if="isViewEditable && !isEditing && !domainObject.locked"
|
||||
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
|
||||
title="Edit"
|
||||
@click="edit()"
|
||||
@ -161,6 +171,13 @@ export default {
|
||||
return currentViewProvider.canEdit && currentViewProvider.canEdit(this.domainObject);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
lockedOrUnlocked() {
|
||||
if (this.domainObject.locked) {
|
||||
return 'Locked for editing - click to unlock.';
|
||||
} else {
|
||||
return 'Unlocked for editing - click to lock.';
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -271,6 +288,9 @@ export default {
|
||||
},
|
||||
goToParent() {
|
||||
window.location.hash = this.parentUrl;
|
||||
},
|
||||
toggleLock(flag) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'locked', flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -314,7 +314,7 @@
|
||||
&__actions,
|
||||
&__end {
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user