Merge branch 'master' into remove-action-tests

Merging master
This commit is contained in:
Jamie Vigliotta 2020-07-02 12:53:43 -07:00
commit 6bd0246db6
62 changed files with 2394 additions and 561 deletions

View File

@ -28,6 +28,16 @@ define([
domain: 2 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", key: "sin",
name: "Sine", name: "Sine",
@ -61,6 +71,15 @@ define([
domain: 1 domain: 1
} }
}, },
{
key: "local",
name: "Time",
format: "utc",
source: "utc",
hints: {
domain: 2
}
},
{ {
key: "state", key: "state",
source: "value", source: "value",

View File

@ -34,8 +34,8 @@
<body> <body>
</body> </body>
<script> <script>
const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000;
const THIRTY_MINUTES = 30 * 60 * 1000; const THIRTY_MINUTES = THIRTY_SECONDS * 60;
[ [
'example/eventGenerator' 'example/eventGenerator'
@ -63,7 +63,39 @@
bounds: { bounds: {
start: Date.now() - THIRTY_MINUTES, start: Date.now() - THIRTY_MINUTES,
end: Date.now() 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", name: "Realtime",
@ -71,7 +103,7 @@
clock: 'local', clock: 'local',
clockOffsets: { clockOffsets: {
start: - THIRTY_MINUTES, start: - THIRTY_MINUTES,
end: FIVE_MINUTES end: THIRTY_SECONDS
} }
} }
] ]

View File

@ -81,10 +81,15 @@ define(
* context. * context.
*/ */
PropertiesAction.appliesTo = function (context) { PropertiesAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject, var domainObject = (context || {}).domainObject,
type = domainObject && domainObject.getCapability('type'), type = domainObject && domainObject.getCapability('type'),
creatable = type && type.hasFeature('creation'); creatable = type && type.hasFeature('creation');
if (domainObject && domainObject.model && domainObject.model.locked) {
return false;
}
// Only allow creatable types to be edited // Only allow creatable types to be edited
return domainObject && creatable; return domainObject && creatable;
}; };

View File

@ -40,7 +40,18 @@ define(
} }
MoveAction.prototype = Object.create(AbstractComposeAction.prototype); 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; return MoveAction;
} }

View File

@ -216,8 +216,14 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
}; };
ImportAsJSONAction.appliesTo = function (context) { ImportAsJSONAction.appliesTo = function (context) {
return context.domainObject !== undefined && let domainObject = context.domainObject;
context.domainObject.hasCapability("composition");
if (domainObject && domainObject.model.locked) {
return false;
}
return domainObject !== undefined &&
domainObject.hasCapability("composition");
}; };
return ImportAsJSONAction; return ImportAsJSONAction;

View File

@ -64,7 +64,7 @@ export default {
}, },
computed: { computed: {
formattedTimestamp() { formattedTimestamp() {
return this.timestamp !== undefined ? this.formats[this.timestampKey].format(this.timestamp) : '---'; return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
} }
}, },
mounted() { mounted() {
@ -110,11 +110,11 @@ export default {
}, },
methods: { methods: {
updateValues(datum) { updateValues(datum) {
let newTimestamp = this.formats[this.timestampKey].parse(datum), let newTimestamp = this.getParsedTimestamp(datum),
limit; limit;
if(this.shouldUpdate(newTimestamp)) { if(this.shouldUpdate(newTimestamp)) {
this.timestamp = this.formats[this.timestampKey].parse(datum); this.timestamp = newTimestamp;
this.value = this.formats[this.valueKey].format(datum); this.value = this.formats[this.valueKey].format(datum);
limit = this.limitEvaluator.evaluate(datum, this.valueMetadata); limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) { if (limit) {
@ -125,9 +125,12 @@ export default {
} }
}, },
shouldUpdate(newTimestamp) { shouldUpdate(newTimestamp) {
return (this.timestamp === undefined) || let newTimestampInBounds = this.inBounds(newTimestamp),
(this.inBounds(newTimestamp) && noExistingTimestamp = this.timestamp === undefined,
newTimestamp > this.timestamp); newTimestampIsLatest = newTimestamp > this.timestamp;
return newTimestampInBounds &&
(noExistingTimestamp || newTimestampIsLatest);
}, },
requestHistory() { requestHistory() {
this.openmct this.openmct
@ -146,6 +149,7 @@ export default {
updateBounds(bounds, isTick) { updateBounds(bounds, isTick) {
this.bounds = bounds; this.bounds = bounds;
if(!isTick) { if(!isTick) {
this.resetValues();
this.requestHistory(); this.requestHistory();
} }
}, },
@ -153,13 +157,34 @@ export default {
return timestamp >= this.bounds.start && timestamp <= this.bounds.end; return timestamp >= this.bounds.start && timestamp <= this.bounds.end;
}, },
updateTimeSystem(timeSystem) { updateTimeSystem(timeSystem) {
this.value = '---'; this.resetValues();
this.timestamp = '---';
this.valueClass = '';
this.timestampKey = timeSystem.key; this.timestampKey = timeSystem.key;
}, },
showContextMenu(event) { showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS); 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;
}
} }
} }
} }

View File

@ -150,6 +150,7 @@ export default class Condition extends EventEmitter {
criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct); criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct);
} }
criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
if (!this.criteria) { if (!this.criteria) {
this.criteria = []; this.criteria = [];
} }
@ -178,10 +179,12 @@ export default class Condition extends EventEmitter {
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration); const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct); let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
newCriterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
let criterion = found.item; let criterion = found.item;
criterion.unsubscribe(); criterion.unsubscribe();
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.off('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
this.criteria.splice(found.index, 1, newCriterion); this.criteria.splice(found.index, 1, newCriterion);
this.updateDescription(); this.updateDescription();
} }
@ -194,6 +197,9 @@ export default class Condition extends EventEmitter {
criterion.off('criterionUpdated', (obj) => { criterion.off('criterionUpdated', (obj) => {
this.handleCriterionUpdated(obj); this.handleCriterionUpdated(obj);
}); });
criterion.off('telemetryIsStale', (obj) => {
this.handleStaleCriterion(obj);
});
criterion.destroy(); criterion.destroy();
this.criteria.splice(found.index, 1); this.criteria.splice(found.index, 1);
this.updateDescription(); 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() { updateDescription() {
const triggerDescription = this.getTriggerDescription(); const triggerDescription = this.getTriggerDescription();
let description = ''; let description = '';

View File

@ -103,6 +103,8 @@ export default class ConditionManager extends EventEmitter {
criterion.operation = ''; criterion.operation = '';
conditionChanged = true; conditionChanged = true;
} }
} else {
conditionChanged = true;
} }
}); });
if (conditionChanged) { if (conditionChanged) {
@ -315,6 +317,10 @@ export default class ConditionManager extends EventEmitter {
condition.getResult(normalizedDatum); condition.getResult(normalizedDatum);
}); });
this.updateCurrentCondition(timestamp);
}
updateCurrentCondition(timestamp) {
const currentCondition = this.getCurrentCondition(); const currentCondition = this.getCurrentCondition();
this.emit('conditionSetResultUpdated', this.emit('conditionSetResultUpdated',

View File

@ -27,13 +27,13 @@
> >
{{ condition.configuration.name }} {{ condition.configuration.name }}
</span> </span>
<span class="c-style__condition-desc__text" <span v-if="!condition.isDefault"
v-if="!condition.isDefault" class="c-style__condition-desc__text"
> >
{{ description }} {{ description }}
</span> </span>
<span class="c-style__condition-desc__text" <span v-else
v-else class="c-style__condition-desc__text"
> >
Match if no other condition is matched Match if no other condition is matched
</span> </span>

View File

@ -55,6 +55,7 @@
> >
{{ option.name }} {{ option.name }}
</option> </option>
<option value="dataReceived">any data received</option>
</select> </select>
</span> </span>
<span v-if="criterion.telemetry && criterion.metadata" <span v-if="criterion.telemetry && criterion.metadata"
@ -83,6 +84,7 @@
> >
<span v-if="inputIndex < inputCount-1">and</span> <span v-if="inputIndex < inputCount-1">and</span>
</span> </span>
<span v-if="criterion.metadata === 'dataReceived'">seconds</span>
</template> </template>
<span v-else> <span v-else>
<span v-if="inputCount && criterion.operation" <span v-if="inputCount && criterion.operation"
@ -148,7 +150,11 @@ export default {
return (this.index !== 0 ? operator : '') + ' when'; return (this.index !== 0 ? operator : '') + ' when';
}, },
filteredOps: function () { 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 () { setInputType: function () {
let type = ''; let type = '';
@ -214,6 +220,8 @@ export default {
} else { } else {
this.operationFormat = 'number'; this.operationFormat = 'number';
} }
} else if (this.criterion.metadata === 'dataReceived') {
this.operationFormat = 'number';
} }
this.updateInputVisibilityAndValues(); this.updateInputVisibilityAndValues();
}, },

View File

@ -37,12 +37,13 @@
> >
<style-editor class="c-inspect-styles__editor" <style-editor class="c-inspect-styles__editor"
:style-item="staticStyle" :style-item="staticStyle"
:is-editing="isEditing" :is-editing="allowEditing"
:mixed-styles="mixedStyles" :mixed-styles="mixedStyles"
@persist="updateStaticStyle" @persist="updateStaticStyle"
/> />
</div> </div>
<button <button
v-if="allowEditing"
id="addConditionSet" id="addConditionSet"
class="c-button c-button--major c-toggle-styling-button labeled" class="c-button c-button--major c-toggle-styling-button labeled"
@click="addConditionSet" @click="addConditionSet"
@ -63,7 +64,7 @@
> >
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span> <span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
</a> </a>
<template v-if="isEditing"> <template v-if="allowEditing">
<button <button
id="changeConditionSet" id="changeConditionSet"
class="c-button labeled" class="c-button labeled"
@ -96,7 +97,7 @@
/> />
<style-editor class="c-inspect-styles__editor" <style-editor class="c-inspect-styles__editor"
:style-item="conditionStyle" :style-item="conditionStyle"
:is-editing="isEditing" :is-editing="allowEditing"
@persist="updateConditionalStyle" @persist="updateConditionalStyle"
/> />
</div> </div>
@ -137,7 +138,13 @@ export default {
conditions: undefined, conditions: undefined,
conditionsLoaded: false, conditionsLoaded: false,
navigateToPath: '', navigateToPath: '',
selectedConditionId: '' selectedConditionId: '',
locked: false
}
},
computed: {
allowEditing() {
return this.isEditing && !this.locked;
} }
}, },
destroyed() { destroyed() {
@ -224,7 +231,13 @@ export default {
this.selection.forEach((selectionItem) => { this.selection.forEach((selectionItem) => {
const item = selectionItem[0].context.item; const item = selectionItem[0].context.item;
const layoutItem = selectionItem[0].context.layoutItem; const layoutItem = selectionItem[0].context.layoutItem;
const layoutDomainObject = selectionItem[0].context.item;
const isChildItem = selectionItem.length > 1; const isChildItem = selectionItem.length > 1;
if (layoutDomainObject && layoutDomainObject.locked) {
this.locked = true;
}
if (!isChildItem) { if (!isChildItem) {
domainObject = item; domainObject = item;
itemStyle = getApplicableStylesForItem(item); itemStyle = getApplicableStylesForItem(item);

View File

@ -22,7 +22,7 @@
import TelemetryCriterion from './TelemetryCriterion'; import TelemetryCriterion from './TelemetryCriterion';
import { evaluateResults } from "../utils/evaluator"; import { evaluateResults } from "../utils/evaluator";
import { getLatestTimestamp } from '../utils/time'; import {getLatestTimestamp, subscribeForStaleness} from '../utils/time';
import { getOperatorText } from "@/plugins/condition/utils/operations"; import { getOperatorText } from "@/plugins/condition/utils/operations";
export default class AllTelemetryCriterion extends TelemetryCriterion { export default class AllTelemetryCriterion extends TelemetryCriterion {
@ -41,6 +41,32 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
initialize() { initialize() {
this.telemetryObjects = { ...this.telemetryDomainObjectDefinition.telemetryObjects }; this.telemetryObjects = { ...this.telemetryDomainObjectDefinition.telemetryObjects };
this.telemetryDataCache = {}; 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() { isValid() {
@ -50,6 +76,9 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
updateTelemetryObjects(telemetryObjects) { updateTelemetryObjects(telemetryObjects) {
this.telemetryObjects = { ...telemetryObjects }; this.telemetryObjects = { ...telemetryObjects };
this.removeTelemetryDataCache(); this.removeTelemetryDataCache();
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData(this.telemetryObjects || {});
}
} }
removeTelemetryDataCache() { removeTelemetryDataCache() {
@ -63,6 +92,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
}); });
telemetryCacheIds.forEach(id => { telemetryCacheIds.forEach(id => {
delete (this.telemetryDataCache[id]); delete (this.telemetryDataCache[id]);
delete (this.stalenessSubscription[id]);
}); });
} }
@ -96,7 +126,14 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
const validatedData = this.isValid() ? data : {}; const validatedData = this.isValid() ? data : {};
if (validatedData) { 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 => { Object.values(telemetryObjects).forEach(telemetryObject => {
@ -162,7 +199,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
getDescription() { getDescription() {
const telemetryDescription = this.telemetry === 'all' ? 'all telemetry' : 'any telemetry'; const telemetryDescription = this.telemetry === 'all' ? 'all telemetry' : 'any telemetry';
let metadataValue = this.metadata; let metadataValue = (this.metadata === 'dataReceived' ? '' : this.metadata);
let inputValue = this.input; let inputValue = this.input;
if (this.metadata) { if (this.metadata) {
const telemetryObjects = Object.values(this.telemetryObjects); const telemetryObjects = Object.values(this.telemetryObjects);
@ -182,5 +219,9 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
destroy() { destroy() {
delete this.telemetryObjects; delete this.telemetryObjects;
delete this.telemetryDataCache; delete this.telemetryDataCache;
if (this.stalenessSubscription) {
Object.values(this.stalenessSubscription).forEach((subscription) => subscription.clear);
delete this.stalenessSubscription;
}
} }
} }

View File

@ -22,6 +22,7 @@
import EventEmitter from 'EventEmitter'; import EventEmitter from 'EventEmitter';
import { OPERATIONS, getOperatorText } from '../utils/operations'; import { OPERATIONS, getOperatorText } from '../utils/operations';
import { subscribeForStaleness } from "../utils/time";
export default class TelemetryCriterion extends EventEmitter { export default class TelemetryCriterion extends EventEmitter {
@ -43,6 +44,7 @@ export default class TelemetryCriterion extends EventEmitter {
this.input = telemetryDomainObjectDefinition.input; this.input = telemetryDomainObjectDefinition.input;
this.metadata = telemetryDomainObjectDefinition.metadata; this.metadata = telemetryDomainObjectDefinition.metadata;
this.result = undefined; this.result = undefined;
this.stalenessSubscription = undefined;
this.initialize(); this.initialize();
this.emitEvent('criterionUpdated', this); this.emitEvent('criterionUpdated', this);
@ -51,14 +53,40 @@ export default class TelemetryCriterion extends EventEmitter {
initialize() { initialize() {
this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry); this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry);
this.updateTelemetryObjects(this.telemetryDomainObjectDefinition.telemetryObjects); 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() { isValid() {
return this.telemetryObject && this.metadata && this.operation; 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) { updateTelemetryObjects(telemetryObjects) {
this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString]; this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData()
}
} }
createNormalizedDatum(telemetryDatum, endpoint) { createNormalizedDatum(telemetryDatum, endpoint) {
@ -91,7 +119,14 @@ export default class TelemetryCriterion extends EventEmitter {
getResult(data) { getResult(data) {
const validatedData = this.isValid() ? 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() { requestLAD() {
@ -136,7 +171,7 @@ export default class TelemetryCriterion extends EventEmitter {
let comparator = this.findOperation(this.operation); let comparator = this.findOperation(this.operation);
let params = []; let params = [];
params.push(data[this.metadata]); params.push(data[this.metadata]);
if (this.input instanceof Array && this.input.length) { if (this.isValidInput()) {
this.input.forEach(input => params.push(input)); this.input.forEach(input => params.push(input));
} }
if (typeof comparator === 'function') { if (typeof comparator === 'function') {
@ -191,7 +226,7 @@ export default class TelemetryCriterion extends EventEmitter {
description = `Unknown ${this.metadata} ${getOperatorText(this.operation, this.input)}`; description = `Unknown ${this.metadata} ${getOperatorText(this.operation, this.input)}`;
} else { } else {
const metadataObject = this.getMetaDataObject(this.telemetryObject, this.metadata); 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; const inputValue = this.getInputValueFromMetaData(metadataObject, this.input) || this.input;
description = `${this.telemetryObject.name} ${metadataValue} ${getOperatorText(this.operation, inputValue)}`; description = `${this.telemetryObject.name} ${metadataValue} ${getOperatorText(this.operation, inputValue)}`;
} }
@ -202,5 +237,8 @@ export default class TelemetryCriterion extends EventEmitter {
destroy() { destroy() {
delete this.telemetryObject; delete this.telemetryObject;
delete this.telemetryObjectIdAsString; delete this.telemetryObjectIdAsString;
if (this.stalenessSubscription) {
delete this.stalenessSubscription;
}
} }
} }

View File

@ -25,19 +25,50 @@ import ConditionPlugin from "./plugin";
import StylesView from "./components/inspector/StylesView.vue"; import StylesView from "./components/inspector/StylesView.vue";
import Vue from 'vue'; import Vue from 'vue';
import {getApplicableStylesForItem} from "./utils/styleUtils"; import {getApplicableStylesForItem} from "./utils/styleUtils";
import ConditionManager from "@/plugins/condition/ConditionManager";
describe('the plugin', function () { describe('the plugin', function () {
let conditionSetDefinition; let conditionSetDefinition;
let mockConditionSetDomainObject; let mockConditionSetDomainObject;
let mockListener;
let element; let element;
let child; let child;
let openmct; let openmct;
let testTelemetryObject;
beforeAll(() => { beforeAll(() => {
resetApplicationState(openmct); resetApplicationState(openmct);
}); });
beforeEach((done) => { 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 = createOpenMct();
openmct.install(new ConditionPlugin()); openmct.install(new ConditionPlugin());
@ -55,6 +86,8 @@ describe('the plugin', function () {
type: 'conditionSet' type: 'conditionSet'
}; };
mockListener = jasmine.createSpy('mockListener');
conditionSetDefinition.initialize(mockConditionSetDomainObject); conditionSetDefinition.initialize(mockConditionSetDomainObject);
openmct.on('start', done); 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);
});
});
}); });

View File

@ -283,6 +283,18 @@ export const OPERATIONS = [
getDescription: function (values) { getDescription: function (values) {
return ' is not one of ' + values[0]; 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`;
}
} }
]; ];

View File

@ -50,3 +50,26 @@ function updateLatestTimeStamp(timestamp, timeSystems) {
return latest; 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);
}
}
}
};

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

View File

@ -596,7 +596,7 @@ define(['lodash'], function (_) {
let selectedParent = selectionPath[1].context.item; let selectedParent = selectionPath[1].context.item;
let layoutItem = selectionPath[0].context.layoutItem; let layoutItem = selectionPath[0].context.layoutItem;
if (!layoutItem) { if (!layoutItem || selectedParent.locked) {
return; return;
} }

View File

@ -24,6 +24,7 @@
<layout-frame <layout-frame
:item="item" :item="item"
:grid-size="gridSize" :grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)" @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')" @endMove="() => $emit('endMove')"
> >
@ -70,7 +71,11 @@ export default {
type: Number, type: Number,
required: true required: true
}, },
initSelect: Boolean initSelect: Boolean,
isEditing: {
type: Boolean,
required: true
}
}, },
computed: { computed: {
style() { style() {

View File

@ -24,14 +24,18 @@
<div <div
class="l-layout" class="l-layout"
:class="{ :class="{
'is-multi-selected': selectedLayoutItems.length > 1 'is-multi-selected': selectedLayoutItems.length > 1,
'allow-editing': isEditing
}" }"
@dragover="handleDragOver" @dragover="handleDragOver"
@click.capture="bypassSelection" @click.capture="bypassSelection"
@drop="handleDrop" @drop="handleDrop"
> >
<!-- Background grid --> <!-- Background grid -->
<div class="l-layout__grid-holder c-grid"> <div
v-if="isEditing"
class="l-layout__grid-holder c-grid"
>
<div <div
v-if="gridSize[0] >= 3" v-if="gridSize[0] >= 3"
class="c-grid__x l-grid l-grid-x" class="c-grid__x l-grid l-grid-x"
@ -53,6 +57,7 @@
:init-select="initSelectIndex === index" :init-select="initSelectIndex === index"
:index="index" :index="index"
:multi-select="selectedLayoutItems.length > 1" :multi-select="selectedLayoutItems.length > 1"
:is-editing="isEditing"
@move="move" @move="move"
@endMove="endMove" @endMove="endMove"
@endLineResize="endLineResize" @endLineResize="endLineResize"
@ -138,6 +143,10 @@ export default {
domainObject: { domainObject: {
type: Object, type: Object,
required: true required: true
},
isEditing: {
type: Boolean,
required: true
} }
}, },
data() { data() {
@ -164,7 +173,7 @@ export default {
let selectionPath = this.selection[0]; let selectionPath = this.selection[0];
let singleSelectedLine = this.selection.length === 1 && let singleSelectedLine = this.selection.length === 1 &&
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view'; 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'], inject: ['openmct', 'options', 'objectPath'],
@ -352,6 +361,9 @@ export default {
.some(childId => this.openmct.objects.areIdsEqual(childId, identifier)); .some(childId => this.openmct.objects.areIdsEqual(childId, identifier));
}, },
handleDragOver($event) { handleDragOver($event) {
if (this.internalDomainObject.locked) {
return;
}
// Get the ID of the dragged object // Get the ID of the dragged object
let draggedKeyString = $event.dataTransfer.types let draggedKeyString = $event.dataTransfer.types
.filter(type => type.startsWith(DRAG_OBJECT_TRANSFER_PREFIX)) .filter(type => type.startsWith(DRAG_OBJECT_TRANSFER_PREFIX))

View File

@ -24,6 +24,7 @@
<layout-frame <layout-frame
:item="item" :item="item"
:grid-size="gridSize" :grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)" @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')" @endMove="() => $emit('endMove')"
> >
@ -70,7 +71,11 @@ export default {
type: Number, type: Number,
required: true required: true
}, },
initSelect: Boolean initSelect: Boolean,
isEditing: {
type: Boolean,
required: true
}
}, },
computed: { computed: {
style() { style() {

View File

@ -33,7 +33,7 @@
<div <div
class="c-frame-edit__move" class="c-frame-edit__move"
@mousedown="startMove([1,1], [0,0], $event)" @mousedown="isEditing ? startMove([1,1], [0,0], $event) : null"
></div> ></div>
</div> </div>
</template> </template>
@ -54,6 +54,10 @@ export default {
required: true, required: true,
validator: (arr) => arr && arr.length === 2 validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number') && arr.every(el => typeof el === 'number')
},
isEditing: {
type: Boolean,
required: true
} }
}, },
computed: { computed: {

View File

@ -24,6 +24,7 @@
:item="item" :item="item"
:grid-size="gridSize" :grid-size="gridSize"
:title="domainObject && domainObject.name" :title="domainObject && domainObject.name"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)" @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')" @endMove="() => $emit('endMove')"
> >
@ -95,6 +96,10 @@ export default {
index: { index: {
type: Number, type: Number,
required: true required: true
},
isEditing: {
type: Boolean,
required: true
} }
}, },
data() { data() {

View File

@ -24,6 +24,7 @@
<layout-frame <layout-frame
:item="item" :item="item"
:grid-size="gridSize" :grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)" @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')" @endMove="() => $emit('endMove')"
> >
@ -105,6 +106,10 @@ export default {
index: { index: {
type: Number, type: Number,
required: true required: true
},
isEditing: {
type: Boolean,
required: true
} }
}, },
data() { data() {

View File

@ -24,6 +24,7 @@
<layout-frame <layout-frame
:item="item" :item="item"
:grid-size="gridSize" :grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)" @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')" @endMove="() => $emit('endMove')"
> >
@ -75,7 +76,11 @@ export default {
type: Number, type: Number,
required: true required: true
}, },
initSelect: Boolean initSelect: Boolean,
isEditing: {
type: Boolean,
required: true
}
}, },
computed: { computed: {
style() { style() {

View File

@ -45,8 +45,7 @@
&[s-selected], &[s-selected],
&[s-selected-parent] { &[s-selected-parent] {
// Display grid and allow edit marquee to display in nested layouts when editing // Display grid and allow edit marquee to display in nested layouts when editing
> * > * > .l-layout { > * > * > .l-layout + .allow-editing {
background: $editUIGridColorBg;
box-shadow: inset $editUIGridColorFg 0 0 2px 1px; box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
> [class*='grid-holder'] { > [class*='grid-holder'] {

View File

@ -54,10 +54,11 @@ export default function DisplayLayoutPlugin(options) {
}, },
data() { data() {
return { 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() { getSelectionContext() {
@ -73,6 +74,9 @@ export default function DisplayLayoutPlugin(options) {
mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots
}; };
}, },
onEditModeChange: function (isEditing) {
component.isEditing = isEditing;
},
destroy() { destroy() {
component.$destroy(); component.$destroy();
} }

View File

@ -53,6 +53,7 @@
:index="i" :index="i"
:container-index="index" :container-index="index"
:is-editing="isEditing" :is-editing="isEditing"
:object-path="objectPath"
/> />
<drop-hint <drop-hint
@ -105,6 +106,14 @@ export default {
isEditing: { isEditing: {
type: Boolean, type: Boolean,
default: false default: false
},
locked: {
type: Boolean,
default: false
},
objectPath: {
type: Array,
required: true
} }
}, },
computed: { computed: {
@ -130,6 +139,10 @@ export default {
}, },
methods: { methods: {
allowDrop(event, index) { allowDrop(event, index) {
if (this.locked) {
return false;
}
if (event.dataTransfer.types.includes('openmct/domain-object-path')) { if (event.dataTransfer.types.includes('openmct/domain-object-path')) {
return true; return true;
} }

View File

@ -57,6 +57,8 @@
:container="container" :container="container"
:rows-layout="rowsLayout" :rows-layout="rowsLayout"
:is-editing="isEditing" :is-editing="isEditing"
:locked="domainObject.locked"
:object-path="objectPath"
@move-frame="moveFrame" @move-frame="moveFrame"
@new-frame="setFrameLocation" @new-frame="setFrameLocation"
@persist="persist" @persist="persist"
@ -136,7 +138,7 @@ function sizeToFill(items) {
} }
export default { export default {
inject: ['openmct', 'layoutObject'], inject: ['openmct', 'objectPath', 'layoutObject'],
components: { components: {
ContainerComponent, ContainerComponent,
ResizeHandle, ResizeHandle,

View File

@ -37,7 +37,7 @@
v-if="domainObject" v-if="domainObject"
ref="objectFrame" ref="objectFrame"
:domain-object="domainObject" :domain-object="domainObject"
:object-path="objectPath" :object-path="currentObjectPath"
:has-frame="hasFrame" :has-frame="hasFrame"
:show-edit-view="false" :show-edit-view="false"
/> />
@ -77,12 +77,16 @@ export default {
isEditing: { isEditing: {
type: Boolean, type: Boolean,
default: false default: false
},
objectPath: {
type: Array,
required: true
} }
}, },
data() { data() {
return { return {
domainObject: undefined, domainObject: undefined,
objectPath: undefined currentObjectPath: undefined
} }
}, },
computed: { computed: {
@ -107,7 +111,7 @@ export default {
methods: { methods: {
setDomainObject(object) { setDomainObject(object) {
this.domainObject = object; this.domainObject = object;
this.objectPath = [object]; this.currentObjectPath = [object].concat(this.objectPath);
this.setSelection(); this.setSelection();
}, },
setSelection() { setSelection() {

View File

@ -38,7 +38,7 @@ define([
canEdit: function (domainObject) { canEdit: function (domainObject) {
return domainObject.type === 'flexible-layout'; return domainObject.type === 'flexible-layout';
}, },
view: function (domainObject) { view: function (domainObject, objectPath) {
let component; let component;
return { return {
@ -46,6 +46,7 @@ define([
component = new Vue({ component = new Vue({
provide: { provide: {
openmct, openmct,
objectPath,
layoutObject: domainObject layoutObject: domainObject
}, },
el: element, el: element,

View File

@ -70,6 +70,10 @@ function ToolbarProvider(openmct) {
} }
if (primary.context.type === 'frame') { if (primary.context.type === 'frame') {
if (secondary.context.item.locked) {
return [];
}
let frameId = primary.context.frameId; let frameId = primary.context.frameId;
let layoutObject = tertiary.context.item; let layoutObject = tertiary.context.item;
let containers = layoutObject let containers = layoutObject
@ -143,6 +147,9 @@ function ToolbarProvider(openmct) {
toggleContainer.domainObject = secondary.context.item; toggleContainer.domainObject = secondary.context.item;
} else if (primary.context.type === 'container') { } else if (primary.context.type === 'container') {
if (primary.context.item.locked) {
return [];
}
deleteContainer = { deleteContainer = {
control: "button", control: "button",
@ -187,6 +194,9 @@ function ToolbarProvider(openmct) {
}; };
} else if (primary.context.type === 'flexible-layout') { } else if (primary.context.type === 'flexible-layout') {
if (primary.context.item.locked) {
return [];
}
addContainer = { addContainer = {
control: "button", control: "button",

View File

@ -41,7 +41,7 @@ define([], function () {
this.timeFormat = 'local-format'; this.timeFormat = 'local-format';
this.durationFormat = 'duration'; this.durationFormat = 'duration';
this.isUTCBased = false; this.isUTCBased = true;
} }
return LocalTimeSystem; return LocalTimeSystem;

View File

@ -24,14 +24,14 @@ import uuid from 'uuid';
export default class NewFolderAction { export default class NewFolderAction {
constructor(openmct) { constructor(openmct) {
this.name = 'New Folder'; this.name = 'Add New Folder';
this.key = 'newFolder'; this.key = 'newFolder';
this.description = 'Create a new folder'; this.description = 'Create a new folder';
this.cssClass = 'icon-folder'; this.cssClass = 'icon-folder-new';
this._openmct = openmct; this._openmct = openmct;
this._dialogForm = { this._dialogForm = {
name: "New Folder Name", name: "Add New Folder",
sections: [ sections: [
{ {
rows: [ rows: [
@ -39,7 +39,9 @@ export default class NewFolderAction {
key: "name", key: "name",
control: "textfield", control: "textfield",
name: "Folder Name", 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'), dialogService = this._openmct.$injector.get('dialogService'),
folderType = this._openmct.types.get('folder'); 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, let name = userInput.name,
identifier = { identifier = {
key: uuid(), key: uuid(),

View File

@ -60,6 +60,7 @@ export default {
}, },
mounted() { mounted() {
this.addPopupMenuItems(); this.addPopupMenuItems();
this.exportImageService = this.openmct.$injector.get('exportImageService');
}, },
methods: { methods: {
addPopupMenuItems() { addPopupMenuItems() {
@ -205,7 +206,7 @@ export default {
}, },
openSnapshot() { openSnapshot() {
const self = this; const self = this;
const snapshot = new Vue({ this.snapshot = new Vue({
data: () => { data: () => {
return { return {
embed: self.embed embed: self.embed
@ -213,14 +214,15 @@ export default {
}, },
methods: { methods: {
formatTime: self.formatTime, formatTime: self.formatTime,
annotateSnapshot: self.annotateSnapshot annotateSnapshot: self.annotateSnapshot,
exportImage: self.exportImage
}, },
template: SnapshotTemplate template: SnapshotTemplate
}); });
const snapshotOverlay = this.openmct.overlays.overlay({ const snapshotOverlay = this.openmct.overlays.overlay({
element: snapshot.$mount().$el, element: this.snapshot.$mount().$el,
onDestroy: () => { snapshot.$destroy(true) }, onDestroy: () => { this.snapshot.$destroy(true) },
size: 'large', size: 'large',
dismissable: true, dismissable: true,
buttons: [ 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() { previewEmbed() {
const self = this; const self = this;
const previewAction = new PreviewAction(self.openmct); const previewAction = new PreviewAction(self.openmct);

View File

@ -15,14 +15,32 @@
<div class="l-browse-bar__snapshot-datetime"> <div class="l-browse-bar__snapshot-datetime">
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}} SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div> </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"> <a class="l-browse-bar__annotate-button c-button icon-pencil" title="Annotate" @click="annotateSnapshot">
<span class="title-label">Annotate</span> <span class="title-label">Annotate</span>
</a> </a>
</div> </div>
</div> </div>
<div class="c-notebook-snapshot__image" <div
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }" ref="snapshot-image"
class="c-notebook-snapshot__image"
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
> >
</div> </div>
</div> </div>

View File

@ -19,12 +19,12 @@
this source code distribution or the Licensing information page available this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information. 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-representation key="'plot-options-edit'"
mct-object="domainObject"> mct-object="domainObject">
</mct-representation> </mct-representation>
</div> </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-representation key="'plot-options-browse'"
mct-object="domainObject"> mct-object="domainObject">
</mct-representation> </mct-representation>

View File

@ -101,6 +101,12 @@ export default class RemoveAction {
appliesTo(objectPath) { appliesTo(objectPath) {
let parent = objectPath[1]; let parent = objectPath[1];
let parentType = parent && this.openmct.types.get(parent.type); 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 && return parentType &&
parentType.definition.creatable && parentType.definition.creatable &&

View File

@ -22,7 +22,12 @@
<template> <template>
<div <div
class="c-conductor" 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 <form
ref="conductorForm" ref="conductorForm"
@ -52,7 +57,7 @@
type="text" type="text"
autocorrect="off" autocorrect="off"
spellcheck="false" spellcheck="false"
@change="validateAllBounds(); submitForm()" @change="validateAllBounds('startDate'); submitForm()"
> >
<date-picker <date-picker
v-if="isFixed && isUTCBased" v-if="isFixed && isUTCBased"
@ -92,7 +97,7 @@
autocorrect="off" autocorrect="off"
spellcheck="false" spellcheck="false"
:disabled="!isFixed" :disabled="!isFixed"
@change="validateAllBounds(); submitForm()" @change="validateAllBounds('endDate'); submitForm()"
> >
<date-picker <date-picker
v-if="isFixed && isUTCBased" v-if="isFixed && isUTCBased"
@ -122,14 +127,25 @@
<conductor-axis <conductor-axis
class="c-conductor__ticks" class="c-conductor__ticks"
:bounds="rawBounds" :view-bounds="viewBounds"
@panAxis="setViewFromBounds" :is-fixed="isFixed"
:alt-pressed="altPressed"
@endPan="endPan"
@endZoom="endZoom"
@panAxis="pan"
@zoomAxis="zoom"
/> />
</div> </div>
<div class="c-conductor__controls"> <div class="c-conductor__controls">
<!-- Mode, time system menu buttons and duration slider -->
<ConductorMode class="c-conductor__mode-select" /> <ConductorMode class="c-conductor__mode-select" />
<ConductorTimeSystem class="c-conductor__time-system-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> </div>
<input <input
type="submit" type="submit"
@ -145,6 +161,7 @@ import ConductorTimeSystem from './ConductorTimeSystem.vue';
import DatePicker from './DatePicker.vue'; import DatePicker from './DatePicker.vue';
import ConductorAxis from './ConductorAxis.vue'; import ConductorAxis from './ConductorAxis.vue';
import ConductorModeIcon from './ConductorModeIcon.vue'; import ConductorModeIcon from './ConductorModeIcon.vue';
import ConductorHistory from './ConductorHistory.vue'
const DEFAULT_DURATION_FORMATTER = 'duration'; const DEFAULT_DURATION_FORMATTER = 'duration';
@ -155,7 +172,8 @@ export default {
ConductorTimeSystem, ConductorTimeSystem,
DatePicker, DatePicker,
ConductorAxis, ConductorAxis,
ConductorModeIcon ConductorModeIcon,
ConductorHistory
}, },
data() { data() {
let bounds = this.openmct.time.bounds(); let bounds = this.openmct.time.bounds();
@ -165,6 +183,7 @@ export default {
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
return { return {
timeSystem: timeSystem,
timeFormatter: timeFormatter, timeFormatter: timeFormatter,
durationFormatter: durationFormatter, durationFormatter: durationFormatter,
offsets: { offsets: {
@ -175,29 +194,68 @@ export default {
start: timeFormatter.format(bounds.start), start: timeFormatter.format(bounds.start),
end: timeFormatter.format(bounds.end) end: timeFormatter.format(bounds.end)
}, },
rawBounds: { viewBounds: {
start: bounds.start, start: bounds.start,
end: bounds.end end: bounds.end
}, },
isFixed: this.openmct.time.clock() === undefined, isFixed: this.openmct.time.clock() === undefined,
isUTCBased: timeSystem.isUTCBased, isUTCBased: timeSystem.isUTCBased,
showDatePicker: false showDatePicker: false,
altPressed: false,
isPanning: false,
isZooming: false
} }
}, },
mounted() { mounted() {
document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('keyup', this.handleKeyUp);
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem()))); this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
this.openmct.time.on('bounds', this.setViewFromBounds); this.openmct.time.on('bounds', this.setViewFromBounds);
this.openmct.time.on('timeSystem', this.setTimeSystem); this.openmct.time.on('timeSystem', this.setTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock); this.openmct.time.on('clock', this.setViewFromClock);
this.openmct.time.on('clockOffsets', this.setViewFromOffsets) this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
}, },
beforeDestroy() {
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp);
},
methods: { 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) { setTimeSystem(timeSystem) {
this.timeSystem = timeSystem
this.timeFormatter = this.getFormatter(timeSystem.timeFormat); this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
this.durationFormatter = this.getFormatter( this.durationFormatter = this.getFormatter(
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
this.isUTCBased = timeSystem.isUTCBased; this.isUTCBased = timeSystem.isUTCBased;
}, },
setOffsetsFromView($event) { setOffsetsFromView($event) {
@ -237,8 +295,8 @@ export default {
setViewFromBounds(bounds) { setViewFromBounds(bounds) {
this.formattedBounds.start = this.timeFormatter.format(bounds.start); this.formattedBounds.start = this.timeFormatter.format(bounds.start);
this.formattedBounds.end = this.timeFormatter.format(bounds.end); this.formattedBounds.end = this.timeFormatter.format(bounds.end);
this.rawBounds.start = bounds.start; this.viewBounds.start = bounds.start;
this.rawBounds.end = bounds.end; this.viewBounds.end = bounds.end;
}, },
setViewFromOffsets(offsets) { setViewFromOffsets(offsets) {
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start)); this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
@ -251,6 +309,15 @@ export default {
this.setOffsetsFromView(); 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() { clearAllValidation() {
if (this.isFixed) { if (this.isFixed) {
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput); [this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
@ -262,36 +329,52 @@ export default {
input.setCustomValidity(''); input.setCustomValidity('');
input.title = ''; input.title = '';
}, },
validateAllBounds() { validateAllBounds(ref) {
return [this.$refs.startDate, this.$refs.endDate].every((input) => { if (!this.areBoundsFormatsValid()) {
let validationResult = true; return false;
let formattedDate; }
if (input === this.$refs.startDate) { let validationResult = true;
formattedDate = this.formattedBounds.start; 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 { } 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)) { if (!this.timeFormatter.validate(formattedDate)) {
validationResult = 'Invalid date'; 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) { return this.handleValidationResults(input, validationResult);
input.setCustomValidity(validationResult);
input.title = validationResult;
return false;
} else {
input.setCustomValidity('');
input.title = '';
return true;
}
}); });
}, },
validateAllOffsets(event) { validateAllOffsets(event) {
@ -315,17 +398,20 @@ export default {
validationResult = this.openmct.time.validateOffsets(offsetValues); validationResult = this.openmct.time.validateOffsets(offsetValues);
} }
if (validationResult !== true) { return this.handleValidationResults(input, validationResult);
input.setCustomValidity(validationResult);
input.title = validationResult;
return false;
} else {
input.setCustomValidity('');
input.title = '';
return true;
}
}); });
}, },
handleValidationResults(input, validationResult) {
if (validationResult !== true) {
input.setCustomValidity(validationResult);
input.title = validationResult;
return false;
} else {
input.setCustomValidity('');
input.title = '';
return true;
}
},
submitForm() { submitForm() {
// Allow Vue model to catch up to user input. // Allow Vue model to catch up to user input.
// Submitting form will cause validation messages to display (but only if triggered by button click) // Submitting form will cause validation messages to display (but only if triggered by button click)
@ -338,12 +424,12 @@ export default {
}, },
startDateSelected(date) { startDateSelected(date) {
this.formattedBounds.start = this.timeFormatter.format(date); this.formattedBounds.start = this.timeFormatter.format(date);
this.validateAllBounds(); this.validateAllBounds('startDate');
this.submitForm(); this.submitForm();
}, },
endDateSelected(date) { endDateSelected(date) {
this.formattedBounds.end = this.timeFormatter.format(date); this.formattedBounds.end = this.timeFormatter.format(date);
this.validateAllBounds(); this.validateAllBounds('endDate');
this.submitForm(); this.submitForm();
} }
} }

View File

@ -24,7 +24,12 @@
ref="axisHolder" ref="axisHolder"
class="c-conductor-axis" class="c-conductor-axis"
@mousedown="dragStart($event)" @mousedown="dragStart($event)"
></div> >
<div
class="c-conductor-axis__zoom-indicator"
:style="zoomStyle"
></div>
</div>
</template> </template>
<script> <script>
@ -43,52 +48,81 @@ const PIXELS_PER_TICK_WIDE = 200;
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
bounds: { viewBounds: {
type: Object, type: Object,
required: true 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: { watch: {
bounds: { viewBounds: {
handler(bounds) { handler() {
this.setScale(); this.setScale();
}, },
deep: true deep: true
} }
}, },
mounted() { mounted() {
let axisHolder = this.$refs.axisHolder; let vis = d3Selection.select(this.$refs.axisHolder).append("svg:svg");
let height = axisHolder.offsetHeight;
let vis = d3Selection.select(axisHolder)
.append("svg:svg")
.attr("width", "100%")
.attr("height", height);
this.width = this.$refs.axisHolder.clientWidth;
this.xAxis = d3Axis.axisTop(); this.xAxis = d3Axis.axisTop();
this.dragging = false; this.dragging = false;
// draw x axis with labels. CSS is used to position them. // 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.setViewFromTimeSystem(this.openmct.time.timeSystem());
this.setAxisDimensions();
this.setScale(); this.setScale();
//Respond to changes in conductor //Respond to changes in conductor
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem); this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
setInterval(this.resize, RESIZE_POLL_INTERVAL); setInterval(this.resize, RESIZE_POLL_INTERVAL);
}, },
destroyed() {
},
methods: { methods: {
setAxisDimensions() {
const axisHolder = this.$refs.axisHolder;
const rect = axisHolder.getBoundingClientRect();
this.left = Math.round(rect.left);
this.width = axisHolder.clientWidth;
},
setScale() { setScale() {
if (!this.width) {
return;
}
let timeSystem = this.openmct.time.timeSystem(); let timeSystem = this.openmct.time.timeSystem();
let bounds = this.bounds;
if (timeSystem.isUTCBased) { 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 { } else {
this.xScale.domain([bounds.start, bounds.end]); this.xScale.domain(
[this.viewBounds.start, this.viewBounds.end]
);
} }
this.xAxis.scale(this.xScale); this.xAxis.scale(this.xScale);
@ -102,7 +136,7 @@ export default {
this.xAxis.ticks(this.width / PIXELS_PER_TICK); 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) { setViewFromTimeSystem(timeSystem) {
//The D3 scale used depends on the type of time system as d3 //The D3 scale used depends on the type of time system as d3
@ -120,9 +154,8 @@ export default {
}, },
getActiveFormatter() { getActiveFormatter() {
let timeSystem = this.openmct.time.timeSystem(); let timeSystem = this.openmct.time.timeSystem();
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed) { if (this.isFixed) {
return this.getFormatter(timeSystem.timeFormat); return this.getFormatter(timeSystem.timeFormat);
} else { } else {
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
@ -134,45 +167,131 @@ export default {
}).formatter; }).formatter;
}, },
dragStart($event) { dragStart($event) {
let isFixed = this.openmct.time.clock() === undefined; if (this.isFixed) {
if (isFixed) {
this.dragStartX = $event.clientX; this.dragStartX = $event.clientX;
if (this.altPressed) {
this.inPanMode = true;
}
document.addEventListener('mousemove', this.drag); document.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.dragEnd, { document.addEventListener('mouseup', this.dragEnd, {
once: true once: true
}); });
if (this.inZoomMode) {
this.startZoom();
}
} }
}, },
drag($event) { drag($event) {
if (!this.dragging) { if (!this.dragging) {
this.dragging = true; this.dragging = true;
requestAnimationFrame(()=>{
let deltaX = $event.clientX - this.dragStartX; requestAnimationFrame(() => {
let percX = deltaX / this.width; this.dragX = $event.clientX;
let bounds = this.openmct.time.bounds(); this.inPanMode ? this.pan() : this.zoom();
let deltaTime = bounds.end - bounds.start;
let newStart = bounds.start - percX * deltaTime;
this.$emit('panAxis',{
start: newStart,
end: newStart + deltaTime
});
this.dragging = false; this.dragging = false;
}) });
} else {
console.log('Rejected drag due to RAF cap');
} }
}, },
dragEnd() { dragEnd() {
this.inPanMode ? this.endPan() : this.endZoom();
document.removeEventListener('mousemove', this.drag); document.removeEventListener('mousemove', this.drag);
this.openmct.time.bounds({ this.dragStartX = undefined;
start: this.bounds.start, this.dragX = undefined;
end: this.bounds.end },
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() { resize() {
if (this.$refs.axisHolder.clientWidth !== this.width) { if (this.$refs.axisHolder.clientWidth !== this.width) {
this.width = this.$refs.axisHolder.clientWidth; this.setAxisDimensions();
this.setScale(); this.setScale();
} }
} }

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

View File

@ -110,7 +110,7 @@ export default {
if (clock === undefined) { if (clock === undefined) {
return { return {
key: 'fixed', key: 'fixed',
name: 'Fixed Timespan Mode', name: 'Fixed Timespan',
description: 'Query and explore data that falls between two fixed datetimes.', description: 'Query and explore data that falls between two fixed datetimes.',
cssClass: 'icon-tabular' cssClass: 'icon-tabular'
} }

View File

@ -13,7 +13,7 @@
text-rendering: geometricPrecision; text-rendering: geometricPrecision;
width: 100%; width: 100%;
height: 100%; height: 100%;
> g { > g.axis {
// Overall Tick holder // Overall Tick holder
transform: translateY($tickYPos); transform: translateY($tickYPos);
path { path {
@ -44,7 +44,6 @@
} }
body.desktop .is-fixed-mode & { body.desktop .is-fixed-mode & {
@include cursorGrab();
background-size: 3px 30%; background-size: 3px 30%;
background-color: $colorBodyBgSubtle; background-color: $colorBodyBgSubtle;
box-shadow: inset rgba(black, 0.4) 0 1px 1px; box-shadow: inset rgba(black, 0.4) 0 1px 1px;
@ -55,17 +54,6 @@
stroke: $colorBodyBgSubtle; stroke: $colorBodyBgSubtle;
transition: $transOut; transition: $transOut;
} }
&:hover,
&:active {
$c: $colorKeySubtle;
background-color: $c;
transition: $transIn;
svg text {
stroke: $c;
transition: $transIn;
}
}
} }
.is-realtime-mode & { .is-realtime-mode & {

View File

@ -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 { &.is-realtime-mode {
.c-conductor__time-bounds { .c-conductor__time-bounds {
grid-template-columns: 20px auto 1fr auto auto; grid-template-columns: 20px auto 1fr auto auto;

View File

@ -142,6 +142,9 @@ $colorTimeHov: pullForward($colorTime, 10%);
$colorTimeSubtle: pushBack($colorTime, 20%); $colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor $colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(1.2);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #226074;
/************************************************** BROWSING */ /************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%); $browseFrameColor: pullForward($colorBodyBg, 10%);

View File

@ -146,6 +146,9 @@ $colorTimeHov: pullForward($colorTime, 10%);
$colorTimeSubtle: pushBack($colorTime, 20%); $colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor $colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(1.2);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #226074;
/************************************************** BROWSING */ /************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%); $browseFrameColor: pullForward($colorBodyBg, 10%);

View File

@ -132,7 +132,7 @@ $colorPausedFg: #fff;
// Base variations // Base variations
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%); $colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
$colorBodyBgSubtleHov: pushBack($colorKey, 50%); $colorBodyBgSubtleHov: pushBack($colorKey, 50%);
$colorKeySubtle: pushBack($colorKey, 10%); $colorKeySubtle: pushBack($colorKey, 20%);
// Time Colors // Time Colors
$colorTime: #618cff; $colorTime: #618cff;
@ -142,6 +142,9 @@ $colorTimeHov: pushBack($colorTime, 5%);
$colorTimeSubtle: pushBack($colorTime, 20%); $colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor $colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(0.8);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #A0CDE1;
/************************************************** BROWSING */ /************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%); $browseFrameColor: pullForward($colorBodyBg, 10%);

View File

@ -148,6 +148,7 @@ $glyph-icon-cursor-lock: '\e929';
$glyph-icon-flag: '\e92a'; $glyph-icon-flag: '\e92a';
$glyph-icon-eye-disabled: '\e92b'; $glyph-icon-eye-disabled: '\e92b';
$glyph-icon-notebook-page: '\e92c'; $glyph-icon-notebook-page: '\e92c';
$glyph-icon-unlocked: '\e92d';
$glyph-icon-arrows-right-left: '\ea00'; $glyph-icon-arrows-right-left: '\ea00';
$glyph-icon-arrows-up-down: '\ea01'; $glyph-icon-arrows-up-down: '\ea01';
$glyph-icon-bullet: '\ea02'; $glyph-icon-bullet: '\ea02';
@ -198,6 +199,11 @@ $glyph-icon-export: '\ea2e';
$glyph-icon-font-size: '\ea2f'; $glyph-icon-font-size: '\ea2f';
$glyph-icon-clear-data: '\ea30'; $glyph-icon-clear-data: '\ea30';
$glyph-icon-history: '\ea31'; $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: '\eb00';
$glyph-icon-activity-mode: '\eb01'; $glyph-icon-activity-mode: '\eb01';
$glyph-icon-autoflow-tabular: '\eb02'; $glyph-icon-autoflow-tabular: '\eb02';
@ -240,6 +246,7 @@ $glyph-icon-command: '\eb26';
$glyph-icon-conditional: '\eb27'; $glyph-icon-conditional: '\eb27';
$glyph-icon-condition-widget: '\eb28'; $glyph-icon-condition-widget: '\eb28';
$glyph-icon-alphanumeric: '\eb29'; $glyph-icon-alphanumeric: '\eb29';
$glyph-icon-image-telemetry: '\eb2a';
/************************** GLYPHS AS DATA URI */ /************************** GLYPHS AS DATA URI */
// Only objects have been converted, for use in Create menu and folder views // Only objects have been converted, for use in Create menu and folder views

View File

@ -462,9 +462,17 @@ select {
text-shadow: $shdwMenuText; text-shadow: $shdwMenuText;
padding: $interiorMarginSm; padding: $interiorMarginSm;
box-shadow: $shdwMenu; box-shadow: $shdwMenu;
display: block; display: flex;
flex-direction: column;
position: absolute; position: absolute;
z-index: 100; z-index: 100;
> * {
flex: 0 0 auto;
//+ * {
// margin-top: $interiorMarginSm;
//}
}
} }
@mixin menuInner() { @mixin menuInner() {
@ -502,6 +510,23 @@ select {
.c-menu { .c-menu {
@include menuOuter(); @include menuOuter();
@include menuInner(); @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 { .c-super-menu {

View File

@ -84,6 +84,7 @@
.icon-flag { @include glyphBefore($glyph-icon-flag); } .icon-flag { @include glyphBefore($glyph-icon-flag); }
.icon-eye-disabled { @include glyphBefore($glyph-icon-eye-disabled); } .icon-eye-disabled { @include glyphBefore($glyph-icon-eye-disabled); }
.icon-notebook-page { @include glyphBefore($glyph-icon-notebook-page); } .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-right-left { @include glyphBefore($glyph-icon-arrows-right-left); }
.icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); } .icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); }
.icon-bullet { @include glyphBefore($glyph-icon-bullet); } .icon-bullet { @include glyphBefore($glyph-icon-bullet); }
@ -134,6 +135,11 @@
.icon-font-size { @include glyphBefore($glyph-icon-font-size); } .icon-font-size { @include glyphBefore($glyph-icon-font-size); }
.icon-clear-data { @include glyphBefore($glyph-icon-clear-data); } .icon-clear-data { @include glyphBefore($glyph-icon-clear-data); }
.icon-history { @include glyphBefore($glyph-icon-history); } .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 { @include glyphBefore($glyph-icon-activity); }
.icon-activity-mode { @include glyphBefore($glyph-icon-activity-mode); } .icon-activity-mode { @include glyphBefore($glyph-icon-activity-mode); }
.icon-autoflow-tabular { @include glyphBefore($glyph-icon-autoflow-tabular); } .icon-autoflow-tabular { @include glyphBefore($glyph-icon-autoflow-tabular); }
@ -176,6 +182,7 @@
.icon-conditional { @include glyphBefore($glyph-icon-conditional); } .icon-conditional { @include glyphBefore($glyph-icon-conditional); }
.icon-condition-widget { @include glyphBefore($glyph-icon-condition-widget); } .icon-condition-widget { @include glyphBefore($glyph-icon-condition-widget); }
.icon-alphanumeric { @include glyphBefore($glyph-icon-alphanumeric); } .icon-alphanumeric { @include glyphBefore($glyph-icon-alphanumeric); }
.icon-image-telemetry { @include glyphBefore($glyph-icon-image-telemetry); }
/************************** 12 PX CLASSES */ /************************** 12 PX CLASSES */
// TODO: sync with 16px redo as of 10/25/18 // TODO: sync with 16px redo as of 10/25/18

View File

@ -114,25 +114,6 @@ mct-plot {
.plot-wrapper-axis-and-display-area { .plot-wrapper-axis-and-display-area {
position: relative; position: relative;
flex: 1 1 auto; 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 { .gl-plot-wrapper-display-area-and-x-axis {
@ -294,6 +275,25 @@ mct-plot {
right: $m; 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, .gl-plot-display-area,
@ -432,6 +432,7 @@ mct-plot {
&__wrapper { &__wrapper {
// Holds view-control and both collapsed and expanded legends // Holds view-control and both collapsed and expanded legends
flex: 1 1 auto; flex: 1 1 auto;
overflow: auto; // Prevents collapsed legend from forcing scrollbars on higher parent containers
} }
&__view-control { &__view-control {

View File

@ -893,7 +893,7 @@ body.desktop {
.grid-row { .grid-row {
.grid-cell { .grid-cell {
padding: 3px $interiorMarginLg 3px 0; padding: 3px $interiorMarginLg 3px 0;
&[title] { &[title]:not([title=""]) {
// When a cell has a title, assume it's helpful text // When a cell has a title, assume it's helpful text
cursor: help; cursor: help;
} }

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@
<glyph unicode="&#xe914;" 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="&#xe914;" 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="&#xe915;" 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="&#xe915;" 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="&#xe916;" glyph-name="icon-link" d="M1024 320l-512 512v-307.2l-512-204.8v-256h512v-256z" /> <glyph unicode="&#xe916;" glyph-name="icon-link" d="M1024 320l-512 512v-307.2l-512-204.8v-256h512v-256z" />
<glyph unicode="&#xe917;" 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="&#xe917;" 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="&#xe918;" 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="&#xe918;" 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="&#xe919;" 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="&#xe919;" 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="&#xe91a;" 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" /> <glyph unicode="&#xe91a;" 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="&#xe92a;" 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="&#xe92a;" 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="&#xe92b;" 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="&#xe92b;" 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="&#xe92c;" 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="&#xe92c;" 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="&#xe92d;" 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="&#xea00;" glyph-name="icon-arrows-right-left" d="M1024 320l-448-512v1024zM448 832l-448-512 448-512z" /> <glyph unicode="&#xea00;" glyph-name="icon-arrows-right-left" d="M1024 320l-448-512v1024zM448 832l-448-512 448-512z" />
<glyph unicode="&#xea01;" glyph-name="icon-arrows-up-down" d="M512 832l512-448h-1024zM0 256l512-448 512 448z" /> <glyph unicode="&#xea01;" glyph-name="icon-arrows-up-down" d="M512 832l512-448h-1024zM0 256l512-448 512 448z" />
<glyph unicode="&#xea02;" 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" /> <glyph unicode="&#xea02;" 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="&#xea2f;" 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="&#xea2f;" 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="&#xea30;" 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="&#xea30;" 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="&#xea31;" 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="&#xea31;" 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="&#xea32;" 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="&#xea33;" 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="&#xea34;" 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="&#xea35;" 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="&#xea36;" 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="&#xeb00;" 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="&#xeb00;" 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="&#xeb01;" 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="&#xeb01;" 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="&#xeb02;" 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" /> <glyph unicode="&#xeb02;" 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="&#xeb27;" 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="&#xeb27;" 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="&#xeb28;" 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="&#xeb28;" 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="&#xeb29;" 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="&#xeb29;" 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="&#xeb2a;" 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> </font></defs></svg>

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -90,7 +90,15 @@ export default {
this.openmct.objectViews.off('clearData', this.clearData); this.openmct.objectViews.off('clearData', this.clearData);
}, },
invokeEditModeHandler(editMode) { invokeEditModeHandler(editMode) {
this.currentView.onEditModeChange(editMode); let edit;
if (this.currentObject.locked) {
edit = false;
} else {
edit = editMode;
}
this.currentView.onEditModeChange(edit);
}, },
toggleEditView(editMode) { toggleEditView(editMode) {
this.clear(); this.clear();
@ -227,7 +235,11 @@ export default {
}, },
onDragOver(event) { onDragOver(event) {
if (this.hasComposableDomainObject(event)) { if (this.hasComposableDomainObject(event)) {
event.preventDefault(); if (this.isEditingAllowed()) {
event.preventDefault();
} else {
event.stopPropagation();
}
} }
}, },
addObjectToParent(event) { addObjectToParent(event) {
@ -283,6 +295,13 @@ export default {
this.currentView.onClearData(); 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);
} }
} }
} }

View File

@ -58,7 +58,6 @@
&__label { &__label {
margin-left: $interiorMarginSm; margin-left: $interiorMarginSm;
margin-right: $interiorMargin;
white-space: nowrap; white-space: nowrap;
} }
} }

View File

@ -54,7 +54,6 @@ export default {
inject: ['openmct'], inject: ['openmct'],
components: { components: {
StylesInspectorView, StylesInspectorView,
// StylesInspectorView,
multipane, multipane,
pane, pane,
Elements, Elements,

View File

@ -143,7 +143,7 @@
&__label { &__label {
color: $colorInspectorPropName; color: $colorInspectorPropName;
&[title] { &[title]:not([title=""]) {
// When a cell has a title, assume it's helpful text // When a cell has a title, assume it's helpful text
cursor: help; cursor: help;
} }

View File

@ -41,7 +41,17 @@
/> />
<div class="l-browse-bar__actions"> <div class="l-browse-bar__actions">
<button <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" class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
title="Edit" title="Edit"
@click="edit()" @click="edit()"
@ -161,6 +171,13 @@ export default {
return currentViewProvider.canEdit && currentViewProvider.canEdit(this.domainObject); return currentViewProvider.canEdit && currentViewProvider.canEdit(this.domainObject);
} }
return false; return false;
},
lockedOrUnlocked() {
if (this.domainObject.locked) {
return 'Locked for editing - click to unlock.';
} else {
return 'Unlocked for editing - click to lock.';
}
} }
}, },
watch: { watch: {
@ -271,6 +288,9 @@ export default {
}, },
goToParent() { goToParent() {
window.location.hash = this.parentUrl; window.location.hash = this.parentUrl;
},
toggleLock(flag) {
this.openmct.objects.mutate(this.domainObject, 'locked', flag);
} }
} }
} }

View File

@ -314,7 +314,7 @@
&__actions, &__actions,
&__end { &__end {
> * + * { > * + * {
margin-left: $interiorMarginSm; margin-left: $interiorMargin;
} }
} }