mirror of
https://github.com/nasa/openmct.git
synced 2025-06-26 11:09:22 +00:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
d5028eed98 | |||
480e327c63 | |||
e8b10f0193 | |||
1d64dc4653 | |||
08c1053a66 | |||
7508062aac | |||
2f58dfbfe7 | |||
166cb55945 | |||
83116257fc | |||
775f1048bc | |||
71a8b377bb | |||
c43d3fcfc9 |
40
index.html
40
index.html
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -53,9 +53,9 @@
|
|||||||
"marked": "^0.3.5",
|
"marked": "^0.3.5",
|
||||||
"mini-css-extract-plugin": "^0.4.1",
|
"mini-css-extract-plugin": "^0.4.1",
|
||||||
"minimist": "^1.1.1",
|
"minimist": "^1.1.1",
|
||||||
"moment": "^2.11.1",
|
"moment": "2.25.3",
|
||||||
"moment-duration-format": "^2.2.2",
|
"moment-duration-format": "^2.2.2",
|
||||||
"moment-timezone": "^0.5.21",
|
"moment-timezone": "0.5.28",
|
||||||
"node-bourbon": "^4.2.3",
|
"node-bourbon": "^4.2.3",
|
||||||
"node-sass": "^4.9.2",
|
"node-sass": "^4.9.2",
|
||||||
"painterro": "^0.2.65",
|
"painterro": "^0.2.65",
|
||||||
|
@ -204,7 +204,7 @@ export default class ConditionClass extends EventEmitter {
|
|||||||
let latestTimestamp;
|
let latestTimestamp;
|
||||||
let criteriaResults = {};
|
let criteriaResults = {};
|
||||||
const criteriaRequests = this.criteria
|
const criteriaRequests = this.criteria
|
||||||
.map(criterion => criterion.requestLAD({telemetryObjects: this.conditionManager.telemetryObjects}));
|
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects));
|
||||||
|
|
||||||
return Promise.all(criteriaRequests)
|
return Promise.all(criteriaRequests)
|
||||||
.then(results => {
|
.then(results => {
|
||||||
|
@ -55,7 +55,7 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
this.telemetryObjects[id] = Object.assign({}, endpoint, {telemetryMetaData: this.openmct.telemetry.getMetadata(endpoint).valueMetadatas});
|
this.telemetryObjects[id] = Object.assign({}, endpoint, {telemetryMetaData: this.openmct.telemetry.getMetadata(endpoint).valueMetadatas});
|
||||||
this.subscriptions[id] = this.openmct.telemetry.subscribe(
|
this.subscriptions[id] = this.openmct.telemetry.subscribe(
|
||||||
endpoint,
|
endpoint,
|
||||||
this.telemetryReceived.bind(this, id)
|
this.telemetryReceived.bind(this, endpoint)
|
||||||
);
|
);
|
||||||
this.updateConditionTelemetry();
|
this.updateConditionTelemetry();
|
||||||
}
|
}
|
||||||
@ -258,9 +258,13 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
this.openmct.time.timeSystem()
|
this.openmct.time.timeSystem()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const currentCondition = this.getCurrentConditionLAD(conditionResults);
|
|
||||||
|
|
||||||
return Object.assign(
|
if (!Object.values(latestTimestamp).some(timeSystem => timeSystem)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentCondition = this.getCurrentConditionLAD(conditionResults);
|
||||||
|
const currentOutput = Object.assign(
|
||||||
{
|
{
|
||||||
output: currentCondition.configuration.output,
|
output: currentCondition.configuration.output,
|
||||||
id: this.conditionSetDomainObject.identifier,
|
id: this.conditionSetDomainObject.identifier,
|
||||||
@ -268,11 +272,15 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
},
|
},
|
||||||
latestTimestamp
|
latestTimestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return [currentOutput];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isTelemetryUsed(id) {
|
isTelemetryUsed(endpoint) {
|
||||||
|
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||||
|
|
||||||
for(const condition of this.conditionClassCollection) {
|
for(const condition of this.conditionClassCollection) {
|
||||||
if (condition.isTelemetryUsed(id)) {
|
if (condition.isTelemetryUsed(id)) {
|
||||||
return true;
|
return true;
|
||||||
@ -282,12 +290,12 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
telemetryReceived(id, datum) {
|
telemetryReceived(endpoint, datum) {
|
||||||
if (!this.isTelemetryUsed(id)) {
|
if (!this.isTelemetryUsed(endpoint)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedDatum = this.createNormalizedDatum(datum, id);
|
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
||||||
const timeSystemKey = this.openmct.time.timeSystem().key;
|
const timeSystemKey = this.openmct.time.timeSystem().key;
|
||||||
let timestamp = {};
|
let timestamp = {};
|
||||||
timestamp[timeSystemKey] = normalizedDatum[timeSystemKey];
|
timestamp[timeSystemKey] = normalizedDatum[timeSystemKey];
|
||||||
@ -321,8 +329,11 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
createNormalizedDatum(telemetryDatum, id) {
|
createNormalizedDatum(telemetryDatum, endpoint) {
|
||||||
const normalizedDatum = Object.values(this.telemetryObjects[id].telemetryMetaData).reduce((datum, metadatum) => {
|
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||||
|
const metadata = this.openmct.telemetry.getMetadata(endpoint).valueMetadatas;
|
||||||
|
|
||||||
|
const normalizedDatum = Object.values(metadata).reduce((datum, metadatum) => {
|
||||||
const testValue = this.getTestData(metadatum);
|
const testValue = this.getTestData(metadatum);
|
||||||
const formatter = this.openmct.telemetry.getValueFormatter(metadatum);
|
const formatter = this.openmct.telemetry.getValueFormatter(metadatum);
|
||||||
datum[metadatum.key] = testValue !== undefined ? formatter.parse(testValue) : formatter.parse(telemetryDatum[metadatum.source]);
|
datum[metadatum.key] = testValue !== undefined ? formatter.parse(testValue) : formatter.parse(telemetryDatum[metadatum.source]);
|
||||||
|
@ -54,13 +54,22 @@ export default class ConditionSetMetadataProvider {
|
|||||||
return {
|
return {
|
||||||
values: this.getDomains().concat([
|
values: this.getDomains().concat([
|
||||||
{
|
{
|
||||||
name: 'Output',
|
key: "state",
|
||||||
key: 'output',
|
source: "output",
|
||||||
format: 'enum',
|
name: "State",
|
||||||
|
format: "enum",
|
||||||
enumerations: enumerations,
|
enumerations: enumerations,
|
||||||
hints: {
|
hints: {
|
||||||
range: 1
|
range: 1
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "output",
|
||||||
|
name: "Value",
|
||||||
|
format: "string",
|
||||||
|
hints: {
|
||||||
|
range: 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
};
|
};
|
||||||
|
@ -45,7 +45,7 @@ export default class ConditionSetTelemetryProvider {
|
|||||||
|
|
||||||
return conditionManager.requestLADConditionSetOutput()
|
return conditionManager.requestLADConditionSetOutput()
|
||||||
.then(latestOutput => {
|
.then(latestOutput => {
|
||||||
return latestOutput ? [latestOutput] : [];
|
return latestOutput;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +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';
|
||||||
|
|
||||||
export default class AllTelemetryCriterion extends TelemetryCriterion {
|
export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||||
|
|
||||||
@ -107,40 +108,53 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
|||||||
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
|
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLAD(options) {
|
requestLAD(telemetryObjects) {
|
||||||
options = Object.assign({},
|
const options = {
|
||||||
options,
|
strategy: 'latest',
|
||||||
{
|
size: 1
|
||||||
strategy: 'latest',
|
};
|
||||||
size: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!this.isValid()) {
|
if (!this.isValid()) {
|
||||||
return this.formatData({}, options.telemetryObjects);
|
return this.formatData({}, telemetryObjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
let keys = Object.keys(Object.assign({}, options.telemetryObjects));
|
let keys = Object.keys(Object.assign({}, telemetryObjects));
|
||||||
const telemetryRequests = keys
|
const telemetryRequests = keys
|
||||||
.map(key => this.openmct.telemetry.request(
|
.map(key => this.openmct.telemetry.request(
|
||||||
options.telemetryObjects[key],
|
telemetryObjects[key],
|
||||||
options
|
options
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let telemetryDataCache = {};
|
||||||
return Promise.all(telemetryRequests)
|
return Promise.all(telemetryRequests)
|
||||||
.then(telemetryRequestsResults => {
|
.then(telemetryRequestsResults => {
|
||||||
let latestDatum;
|
let latestTimestamp;
|
||||||
|
const timeSystems = this.openmct.time.getAllTimeSystems();
|
||||||
|
const timeSystem = this.openmct.time.timeSystem();
|
||||||
|
|
||||||
telemetryRequestsResults.forEach((results, index) => {
|
telemetryRequestsResults.forEach((results, index) => {
|
||||||
latestDatum = results.length ? results[results.length - 1] : {};
|
const latestDatum = results.length ? results[results.length - 1] : {};
|
||||||
if (index < telemetryRequestsResults.length-1) {
|
const datumId = keys[index];
|
||||||
if (latestDatum) {
|
const normalizedDatum = this.createNormalizedDatum(latestDatum, telemetryObjects[datumId]);
|
||||||
this.telemetryDataCache[latestDatum.id] = this.computeResult(latestDatum);
|
|
||||||
}
|
telemetryDataCache[datumId] = this.computeResult(normalizedDatum);
|
||||||
}
|
|
||||||
|
latestTimestamp = getLatestTimestamp(
|
||||||
|
latestTimestamp,
|
||||||
|
normalizedDatum,
|
||||||
|
timeSystems,
|
||||||
|
timeSystem
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const datum = {
|
||||||
|
result: evaluateResults(Object.values(telemetryDataCache), this.telemetry),
|
||||||
|
...latestTimestamp
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
data: this.formatData(latestDatum, options.telemetryObjects)
|
data: datum
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,21 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
|
this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createNormalizedDatum(telemetryDatum, endpoint) {
|
||||||
|
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||||
|
const metadata = this.openmct.telemetry.getMetadata(endpoint).valueMetadatas;
|
||||||
|
|
||||||
|
const normalizedDatum = Object.values(metadata).reduce((datum, metadatum) => {
|
||||||
|
const formatter = this.openmct.telemetry.getValueFormatter(metadatum);
|
||||||
|
datum[metadatum.key] = formatter.parse(telemetryDatum[metadatum.source]);
|
||||||
|
return datum;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
normalizedDatum.id = id;
|
||||||
|
|
||||||
|
return normalizedDatum;
|
||||||
|
}
|
||||||
|
|
||||||
formatData(data) {
|
formatData(data) {
|
||||||
const datum = {
|
const datum = {
|
||||||
result: this.computeResult(data)
|
result: this.computeResult(data)
|
||||||
@ -79,14 +94,11 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
this.result = this.computeResult(validatedData);
|
this.result = this.computeResult(validatedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLAD(options) {
|
requestLAD() {
|
||||||
options = Object.assign({},
|
const options = {
|
||||||
options,
|
strategy: 'latest',
|
||||||
{
|
size: 1
|
||||||
strategy: 'latest',
|
};
|
||||||
size: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!this.isValid()) {
|
if (!this.isValid()) {
|
||||||
return {
|
return {
|
||||||
@ -100,9 +112,11 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
options
|
options
|
||||||
).then(results => {
|
).then(results => {
|
||||||
const latestDatum = results.length ? results[results.length - 1] : {};
|
const latestDatum = results.length ? results[results.length - 1] : {};
|
||||||
|
const normalizedDatum = this.createNormalizedDatum(latestDatum, this.telemetryObject);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
data: this.formatData(latestDatum)
|
data: this.formatData(normalizedDatum)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -322,7 +322,15 @@ define([
|
|||||||
* a point to the end without dupe checking.
|
* a point to the end without dupe checking.
|
||||||
*/
|
*/
|
||||||
add: function (point, appendOnly) {
|
add: function (point, appendOnly) {
|
||||||
var insertIndex = this.data.length;
|
var insertIndex = this.data.length,
|
||||||
|
currentYVal = this.getYVal(point),
|
||||||
|
lastYVal = this.getYVal(this.data[insertIndex - 1]);
|
||||||
|
|
||||||
|
if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) {
|
||||||
|
console.warn('[Plot] Invalid Y Values detected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!appendOnly) {
|
if (!appendOnly) {
|
||||||
insertIndex = this.sortedIndex(point);
|
insertIndex = this.sortedIndex(point);
|
||||||
if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) {
|
if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) {
|
||||||
@ -332,11 +340,21 @@ define([
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateStats(point);
|
this.updateStats(point);
|
||||||
point.mctLimitState = this.evaluate(point);
|
point.mctLimitState = this.evaluate(point);
|
||||||
this.data.splice(insertIndex, 0, point);
|
this.data.splice(insertIndex, 0, point);
|
||||||
this.emit('add', point, insertIndex, this);
|
this.emit('add', point, insertIndex, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
isValueInvalid: function (val) {
|
||||||
|
return Number.isNaN(val) || val === undefined;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a point from the data array and notify listeners.
|
* Remove a point from the data array and notify listeners.
|
||||||
* @private
|
* @private
|
||||||
|
@ -182,21 +182,6 @@ define([
|
|||||||
this.set('format', yFormat.format.bind(yFormat));
|
this.set('format', yFormat.format.bind(yFormat));
|
||||||
this.set('values', yMetadata.values);
|
this.set('values', yMetadata.values);
|
||||||
if (!label) {
|
if (!label) {
|
||||||
var labelUnits = series.map(function (s) {
|
|
||||||
return s.metadata.value(s.get('yKey')).units;
|
|
||||||
}).reduce(function (a, b) {
|
|
||||||
if (a === undefined) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
if (a === b) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}, undefined);
|
|
||||||
if (labelUnits) {
|
|
||||||
this.set('label', labelUnits);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var labelName = series.map(function (s) {
|
var labelName = series.map(function (s) {
|
||||||
return s.metadata.value(s.get('yKey')).name;
|
return s.metadata.value(s.get('yKey')).name;
|
||||||
}).reduce(function (a, b) {
|
}).reduce(function (a, b) {
|
||||||
@ -208,7 +193,28 @@ define([
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}, undefined);
|
}, undefined);
|
||||||
this.set('label', labelName);
|
|
||||||
|
if (labelName) {
|
||||||
|
this.set('label', labelName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var labelUnits = series.map(function (s) {
|
||||||
|
return s.metadata.value(s.get('yKey')).units;
|
||||||
|
}).reduce(function (a, b) {
|
||||||
|
if (a === undefined) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
if (a === b) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}, undefined);
|
||||||
|
|
||||||
|
if (labelUnits) {
|
||||||
|
this.set('label', labelUnits);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaults: function (options) {
|
defaults: function (options) {
|
||||||
|
@ -65,10 +65,10 @@ define([
|
|||||||
}
|
}
|
||||||
this.$canvas = this.$element.find('canvas');
|
this.$canvas = this.$element.find('canvas');
|
||||||
|
|
||||||
this.listenTo(this.$canvas, 'click', this.onMouseClick, this);
|
|
||||||
this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this);
|
this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this);
|
||||||
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
|
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
|
||||||
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
|
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
|
||||||
|
this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this);
|
||||||
|
|
||||||
this.watchForMarquee();
|
this.watchForMarquee();
|
||||||
};
|
};
|
||||||
@ -76,7 +76,6 @@ define([
|
|||||||
MCTPlotController.prototype.initialize = function () {
|
MCTPlotController.prototype.initialize = function () {
|
||||||
this.$canvas = this.$element.find('canvas');
|
this.$canvas = this.$element.find('canvas');
|
||||||
|
|
||||||
this.listenTo(this.$canvas, 'click', this.onMouseClick, this);
|
|
||||||
this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this);
|
this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this);
|
||||||
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
|
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
|
||||||
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
|
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
|
||||||
@ -208,23 +207,6 @@ define([
|
|||||||
this.highlightValues(point);
|
this.highlightValues(point);
|
||||||
};
|
};
|
||||||
|
|
||||||
MCTPlotController.prototype.onMouseClick = function ($event) {
|
|
||||||
const isClick = this.isMouseClick();
|
|
||||||
if (this.pan) {
|
|
||||||
this.endPan($event);
|
|
||||||
}
|
|
||||||
if (this.marquee) {
|
|
||||||
this.endMarquee($event);
|
|
||||||
}
|
|
||||||
this.$scope.$apply();
|
|
||||||
|
|
||||||
if (!this.$scope.highlights.length || !isClick) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$scope.lockHighlightPoint = !this.$scope.lockHighlightPoint;
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.highlightValues = function (point) {
|
MCTPlotController.prototype.highlightValues = function (point) {
|
||||||
this.highlightPoint = point;
|
this.highlightPoint = point;
|
||||||
this.$scope.$emit('plot:highlight:update', point);
|
this.$scope.$emit('plot:highlight:update', point);
|
||||||
@ -272,11 +254,23 @@ define([
|
|||||||
MCTPlotController.prototype.onMouseUp = function ($event) {
|
MCTPlotController.prototype.onMouseUp = function ($event) {
|
||||||
this.stopListening(this.$window, 'mouseup', this.onMouseUp, this);
|
this.stopListening(this.$window, 'mouseup', this.onMouseUp, this);
|
||||||
this.stopListening(this.$window, 'mousemove', this.trackMousePosition, this);
|
this.stopListening(this.$window, 'mousemove', this.trackMousePosition, this);
|
||||||
|
|
||||||
|
if (this.isMouseClick()) {
|
||||||
|
this.$scope.lockHighlightPoint = !this.$scope.lockHighlightPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.allowPan) {
|
||||||
|
return this.endPan($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.allowMarquee) {
|
||||||
|
return this.endMarquee($event);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MCTPlotController.prototype.isMouseClick = function () {
|
MCTPlotController.prototype.isMouseClick = function () {
|
||||||
if (!this.marquee) {
|
if (!this.marquee) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { start, end } = this.marquee;
|
const { start, end } = this.marquee;
|
||||||
@ -329,7 +323,7 @@ define([
|
|||||||
} else {
|
} else {
|
||||||
// A history entry is created by startMarquee, need to remove
|
// A history entry is created by startMarquee, need to remove
|
||||||
// if marquee zoom doesn't occur.
|
// if marquee zoom doesn't occur.
|
||||||
this.back();
|
this.plotHistory.pop();
|
||||||
}
|
}
|
||||||
this.$scope.rectangles = [];
|
this.$scope.rectangles = [];
|
||||||
this.marquee = undefined;
|
this.marquee = undefined;
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
{'is-current': isCurrent(tab)},
|
{'is-current': isCurrent(tab)},
|
||||||
tab.type.definition.cssClass
|
tab.type.definition.cssClass
|
||||||
]"
|
]"
|
||||||
@click="showTab(tab)"
|
@click="showTab(tab, index)"
|
||||||
>
|
>
|
||||||
<span class="c-button__label">{{ tab.domainObject.name }}</span>
|
<span class="c-button__label">{{ tab.domainObject.name }}</span>
|
||||||
</button>
|
</button>
|
||||||
@ -48,6 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<object-view
|
<object-view
|
||||||
|
v-if="internalDomainObject.keep_alive ? currentTab : isCurrent(tab)"
|
||||||
class="c-tabs-view__object"
|
class="c-tabs-view__object"
|
||||||
:object="tab.domainObject"
|
:object="tab.domainObject"
|
||||||
/>
|
/>
|
||||||
@ -57,7 +58,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ObjectView from '../../../ui/components/ObjectView.vue';
|
import ObjectView from '../../../ui/components/ObjectView.vue';
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
var unknownObjectType = {
|
var unknownObjectType = {
|
||||||
definition: {
|
definition: {
|
||||||
@ -73,6 +73,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
internalDomainObject: this.domainObject,
|
||||||
currentTab: {},
|
currentTab: {},
|
||||||
tabsList: [],
|
tabsList: [],
|
||||||
setCurrentTab: true,
|
setCurrentTab: true,
|
||||||
@ -85,9 +86,17 @@ export default {
|
|||||||
this.composition.on('add', this.addItem);
|
this.composition.on('add', this.addItem);
|
||||||
this.composition.on('remove', this.removeItem);
|
this.composition.on('remove', this.removeItem);
|
||||||
this.composition.on('reorder', this.onReorder);
|
this.composition.on('reorder', this.onReorder);
|
||||||
this.composition.load();
|
this.composition.load().then(() => {
|
||||||
|
let currentTabIndex = this.domainObject.currentTabIndex;
|
||||||
|
|
||||||
|
if (currentTabIndex !== undefined && this.tabsList.length > currentTabIndex) {
|
||||||
|
this.currentTab = this.tabsList[currentTabIndex];
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.unsubscribe = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
|
||||||
|
|
||||||
document.addEventListener('dragstart', this.dragstart);
|
document.addEventListener('dragstart', this.dragstart);
|
||||||
document.addEventListener('dragend', this.dragend);
|
document.addEventListener('dragend', this.dragend);
|
||||||
},
|
},
|
||||||
@ -96,18 +105,25 @@ export default {
|
|||||||
this.composition.off('remove', this.removeItem);
|
this.composition.off('remove', this.removeItem);
|
||||||
this.composition.off('reorder', this.onReorder);
|
this.composition.off('reorder', this.onReorder);
|
||||||
|
|
||||||
|
this.unsubscribe();
|
||||||
|
|
||||||
document.removeEventListener('dragstart', this.dragstart);
|
document.removeEventListener('dragstart', this.dragstart);
|
||||||
document.removeEventListener('dragend', this.dragend);
|
document.removeEventListener('dragend', this.dragend);
|
||||||
},
|
},
|
||||||
methods:{
|
methods:{
|
||||||
showTab(tab) {
|
showTab(tab, index) {
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.storeCurrentTabIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
this.currentTab = tab;
|
this.currentTab = tab;
|
||||||
},
|
},
|
||||||
addItem(domainObject) {
|
addItem(domainObject) {
|
||||||
let type = this.openmct.types.get(domainObject.type) || unknownObjectType,
|
let type = this.openmct.types.get(domainObject.type) || unknownObjectType,
|
||||||
tabItem = {
|
tabItem = {
|
||||||
domainObject,
|
domainObject,
|
||||||
type: type
|
type: type,
|
||||||
|
key: this.openmct.objects.makeKeyString(domainObject.identifier)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.tabsList.push(tabItem);
|
this.tabsList.push(tabItem);
|
||||||
@ -126,7 +142,7 @@ export default {
|
|||||||
this.tabsList.splice(pos, 1);
|
this.tabsList.splice(pos, 1);
|
||||||
|
|
||||||
if (this.isCurrent(tabToBeRemoved)) {
|
if (this.isCurrent(tabToBeRemoved)) {
|
||||||
this.showTab(this.tabsList[this.tabsList.length - 1]);
|
this.showTab(this.tabsList[this.tabsList.length - 1], this.tabsList.length - 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onReorder(reorderPlan) {
|
onReorder(reorderPlan) {
|
||||||
@ -138,6 +154,7 @@ export default {
|
|||||||
},
|
},
|
||||||
onDrop(e) {
|
onDrop(e) {
|
||||||
this.setCurrentTab = true;
|
this.setCurrentTab = true;
|
||||||
|
this.storeCurrentTabIndex(this.tabsList.length);
|
||||||
},
|
},
|
||||||
dragstart(e) {
|
dragstart(e) {
|
||||||
if (e.dataTransfer.types.includes('openmct/domain-object-path')) {
|
if (e.dataTransfer.types.includes('openmct/domain-object-path')) {
|
||||||
@ -155,7 +172,13 @@ export default {
|
|||||||
this.allowDrop = false;
|
this.allowDrop = false;
|
||||||
},
|
},
|
||||||
isCurrent(tab) {
|
isCurrent(tab) {
|
||||||
return _.isEqual(this.currentTab, tab)
|
return this.currentTab.key === tab.key;
|
||||||
|
},
|
||||||
|
updateInternalDomainObject(domainObject) {
|
||||||
|
this.internalDomainObject = domainObject;
|
||||||
|
},
|
||||||
|
storeCurrentTabIndex(index) {
|
||||||
|
this.openmct.objects.mutate(this.internalDomainObject, 'currentTabIndex', index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,27 @@ define([
|
|||||||
cssClass: 'icon-tabs-view',
|
cssClass: 'icon-tabs-view',
|
||||||
initialize(domainObject) {
|
initialize(domainObject) {
|
||||||
domainObject.composition = [];
|
domainObject.composition = [];
|
||||||
}
|
domainObject.keep_alive = true;
|
||||||
|
},
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
"key": "keep_alive",
|
||||||
|
"name": "Keep Tabs Alive",
|
||||||
|
"control": "select",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
'name': 'True',
|
||||||
|
'value': true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'False',
|
||||||
|
'value': false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"required": true,
|
||||||
|
"cssClass": "l-input"
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -46,11 +46,23 @@ define(
|
|||||||
filter = filter.trim().toLowerCase();
|
filter = filter.trim().toLowerCase();
|
||||||
|
|
||||||
let rowsToFilter = this.getRowsToFilter(columnKey, filter);
|
let rowsToFilter = this.getRowsToFilter(columnKey, filter);
|
||||||
|
|
||||||
if (filter.length === 0) {
|
if (filter.length === 0) {
|
||||||
delete this.columnFilters[columnKey];
|
delete this.columnFilters[columnKey];
|
||||||
} else {
|
} else {
|
||||||
this.columnFilters[columnKey] = filter;
|
this.columnFilters[columnKey] = filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.rows = rowsToFilter.filter(this.matchesFilters, this);
|
||||||
|
this.emit('filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
setColumnRegexFilter(columnKey, filter) {
|
||||||
|
filter = filter.trim();
|
||||||
|
|
||||||
|
let rowsToFilter = this.masterCollection.getRows();
|
||||||
|
|
||||||
|
this.columnFilters[columnKey] = new RegExp(filter);
|
||||||
this.rows = rowsToFilter.filter(this.matchesFilters, this);
|
this.rows = rowsToFilter.filter(this.matchesFilters, this);
|
||||||
this.emit('filter');
|
this.emit('filter');
|
||||||
}
|
}
|
||||||
@ -70,6 +82,10 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
isSubsetOfCurrentFilter(columnKey, filter) {
|
isSubsetOfCurrentFilter(columnKey, filter) {
|
||||||
|
if (this.columnFilters[columnKey] instanceof RegExp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return this.columnFilters[columnKey] &&
|
return this.columnFilters[columnKey] &&
|
||||||
filter.startsWith(this.columnFilters[columnKey]) &&
|
filter.startsWith(this.columnFilters[columnKey]) &&
|
||||||
// startsWith check will otherwise fail when filter cleared
|
// startsWith check will otherwise fail when filter cleared
|
||||||
@ -96,7 +112,11 @@ define(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
|
if (this.columnFilters[key] instanceof RegExp) {
|
||||||
|
doesMatchFilters = this.columnFilters[key].test(formattedValue);
|
||||||
|
} else {
|
||||||
|
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return doesMatchFilters;
|
return doesMatchFilters;
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,17 @@
|
|||||||
class="c-table__search"
|
class="c-table__search"
|
||||||
@input="filterChanged(key)"
|
@input="filterChanged(key)"
|
||||||
@clear="clearFilter(key)"
|
@clear="clearFilter(key)"
|
||||||
/>
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="c-search__use-regex"
|
||||||
|
:class="{ 'is-active': enableRegexSearch[key] }"
|
||||||
|
title="Click to enable regex: enter a string with slashes, like this: /regex_exp/"
|
||||||
|
@click="toggleRegex(key)"
|
||||||
|
>
|
||||||
|
/R/
|
||||||
|
</button>
|
||||||
|
</search>
|
||||||
</table-column-header>
|
</table-column-header>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -336,7 +346,8 @@ export default {
|
|||||||
markCounter: 0,
|
markCounter: 0,
|
||||||
paused: false,
|
paused: false,
|
||||||
markedRows: [],
|
markedRows: [],
|
||||||
isShowingMarkedRowsOnly: false
|
isShowingMarkedRowsOnly: false,
|
||||||
|
enableRegexSearch: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -543,7 +554,16 @@ export default {
|
|||||||
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
||||||
},
|
},
|
||||||
filterChanged(columnKey) {
|
filterChanged(columnKey) {
|
||||||
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
if (this.enableRegexSearch[columnKey]) {
|
||||||
|
if (this.isCompleteRegex(this.filters[columnKey])) {
|
||||||
|
this.table.filteredRows.setColumnRegexFilter(columnKey, this.filters[columnKey].slice(1,-1));
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
||||||
|
}
|
||||||
|
|
||||||
this.setHeight();
|
this.setHeight();
|
||||||
},
|
},
|
||||||
clearFilter(columnKey) {
|
clearFilter(columnKey) {
|
||||||
@ -869,6 +889,18 @@ export default {
|
|||||||
this.isAutosizeEnabled = true;
|
this.isAutosizeEnabled = true;
|
||||||
|
|
||||||
this.$nextTick().then(this.calculateColumnWidths);
|
this.$nextTick().then(this.calculateColumnWidths);
|
||||||
|
},
|
||||||
|
toggleRegex(key) {
|
||||||
|
this.$set(this.filters, key, '');
|
||||||
|
|
||||||
|
if (this.enableRegexSearch[key] === undefined) {
|
||||||
|
this.$set(this.enableRegexSearch, key, true)
|
||||||
|
} else {
|
||||||
|
this.$set(this.enableRegexSearch, key, !this.enableRegexSearch[key]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isCompleteRegex(string) {
|
||||||
|
return (string.length > 2 && string[0] === '/' && string[string.length - 1] === '/')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
200
src/plugins/timeConductor/ConductorHistory.vue
Normal file
200
src/plugins/timeConductor/ConductorHistory.vue
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
|
||||||
|
<button class="c-button--menu c-history-button icon-history"
|
||||||
|
@click.prevent="toggle"
|
||||||
|
>
|
||||||
|
<span class="c-button__label">History</span>
|
||||||
|
</button>
|
||||||
|
<div v-if="open"
|
||||||
|
class="c-menu c-conductor__history-menu"
|
||||||
|
>
|
||||||
|
<ul v-if="hasHistoryPresets">
|
||||||
|
<li
|
||||||
|
v-for="preset in presets"
|
||||||
|
:key="preset.label"
|
||||||
|
class="icon-clock"
|
||||||
|
@click="selectPresetBounds(preset.bounds)"
|
||||||
|
>
|
||||||
|
{{ preset.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="hasHistoryPresets"
|
||||||
|
class="c-menu__section-separator"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div class="c-menu__section-hint">
|
||||||
|
Past timeframes, ordered by latest first
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="(timespan, index) in historyForCurrentTimeSystem"
|
||||||
|
:key="index"
|
||||||
|
class="icon-history"
|
||||||
|
@click="selectTimespan(timespan)"
|
||||||
|
>
|
||||||
|
{{ formatTime(timespan.start) }} - {{ formatTime(timespan.end) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import toggleMixin from '../../ui/mixins/toggle-mixin';
|
||||||
|
|
||||||
|
const LOCAL_STORAGE_HISTORY_KEY = 'tcHistory';
|
||||||
|
const DEFAULT_RECORDS = 10;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'configuration'],
|
||||||
|
mixins: [toggleMixin],
|
||||||
|
props: {
|
||||||
|
bounds: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
timeSystem: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
history: {}, // contains arrays of timespans {start, end}, array key is time system key
|
||||||
|
presets: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasHistoryPresets() {
|
||||||
|
return this.timeSystem.isUTCBased && this.presets.length;
|
||||||
|
},
|
||||||
|
historyForCurrentTimeSystem() {
|
||||||
|
const history = this.history[this.timeSystem.key];
|
||||||
|
|
||||||
|
return history;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
bounds: {
|
||||||
|
handler() {
|
||||||
|
this.addTimespan();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
timeSystem: {
|
||||||
|
handler() {
|
||||||
|
this.loadConfiguration();
|
||||||
|
this.addTimespan();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
history: {
|
||||||
|
handler() {
|
||||||
|
this.persistHistoryToLocalStorage();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getHistoryFromLocalStorage();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getHistoryFromLocalStorage() {
|
||||||
|
if (localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY)) {
|
||||||
|
this.history = JSON.parse(localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY))
|
||||||
|
} else {
|
||||||
|
this.history = {};
|
||||||
|
this.persistHistoryToLocalStorage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
persistHistoryToLocalStorage() {
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_HISTORY_KEY, JSON.stringify(this.history));
|
||||||
|
},
|
||||||
|
addTimespan() {
|
||||||
|
const key = this.timeSystem.key;
|
||||||
|
let [...currentHistory] = this.history[key] || [];
|
||||||
|
const timespan = {
|
||||||
|
start: this.bounds.start,
|
||||||
|
end: this.bounds.end
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNotEqual = function (entry) {
|
||||||
|
const start = entry.start !== this.start;
|
||||||
|
const end = entry.end !== this.end;
|
||||||
|
|
||||||
|
return start || end;
|
||||||
|
};
|
||||||
|
currentHistory = currentHistory.filter(isNotEqual, timespan);
|
||||||
|
|
||||||
|
while (currentHistory.length >= this.records) {
|
||||||
|
currentHistory.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentHistory.unshift(timespan);
|
||||||
|
this.history[key] = currentHistory;
|
||||||
|
},
|
||||||
|
selectTimespan(timespan) {
|
||||||
|
this.openmct.time.bounds(timespan);
|
||||||
|
},
|
||||||
|
selectPresetBounds(bounds) {
|
||||||
|
const start = typeof bounds.start === 'function' ? bounds.start() : bounds.start;
|
||||||
|
const end = typeof bounds.end === 'function' ? bounds.end() : bounds.end;
|
||||||
|
|
||||||
|
this.selectTimespan({
|
||||||
|
start: start,
|
||||||
|
end: end
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadConfiguration() {
|
||||||
|
const configurations = this.configuration.menuOptions
|
||||||
|
.filter(option => option.timeSystem === this.timeSystem.key);
|
||||||
|
|
||||||
|
this.presets = this.loadPresets(configurations);
|
||||||
|
this.records = this.loadRecords(configurations);
|
||||||
|
},
|
||||||
|
loadPresets(configurations) {
|
||||||
|
const configuration = configurations.find(option => option.presets);
|
||||||
|
const presets = configuration ? configuration.presets : [];
|
||||||
|
|
||||||
|
return presets;
|
||||||
|
},
|
||||||
|
loadRecords(configurations) {
|
||||||
|
const configuration = configurations.find(option => option.records);
|
||||||
|
const records = configuration ? configuration.records : DEFAULT_RECORDS;
|
||||||
|
|
||||||
|
return records;
|
||||||
|
},
|
||||||
|
formatTime(time) {
|
||||||
|
const formatter = this.openmct.telemetry.getValueFormatter({
|
||||||
|
format: this.timeSystem.timeFormat
|
||||||
|
}).formatter;
|
||||||
|
|
||||||
|
return formatter.format(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -110,7 +110,7 @@ export default {
|
|||||||
if (clock === undefined) {
|
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'
|
||||||
}
|
}
|
||||||
|
@ -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 & {
|
||||||
|
@ -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;
|
||||||
|
@ -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%);
|
||||||
|
@ -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%);
|
||||||
|
@ -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%);
|
||||||
|
@ -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 {
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
|
@mixin visibleRegexButton {
|
||||||
|
opacity: 1;
|
||||||
|
padding: 1px 3px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.c-search {
|
.c-search {
|
||||||
@include wrappedInput();
|
@include wrappedInput();
|
||||||
|
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
|
|
||||||
@ -9,11 +14,46 @@
|
|||||||
content: $glyph-icon-magnify;
|
content: $glyph-icon-magnify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__use-regex {
|
||||||
|
// Button
|
||||||
|
$c: $colorBodyFg;
|
||||||
|
background: rgba($c, 0.2);
|
||||||
|
border: 1px solid rgba($c, 0.3);
|
||||||
|
color: $c;
|
||||||
|
border-radius: $controlCr;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
min-width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
order: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 1px 0;
|
||||||
|
transform-origin: left;
|
||||||
|
transition: $transOut;
|
||||||
|
width: 0;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
$c: $colorBtnActiveBg;
|
||||||
|
@include visibleRegexButton();
|
||||||
|
background: rgba($c, 0.3);
|
||||||
|
border-color: $c;
|
||||||
|
color: $c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__clear-input {
|
&__clear-input {
|
||||||
display: none;
|
display: none;
|
||||||
|
order: 99;
|
||||||
|
padding: 1px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
|
.c-search__use-regex {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.c-search__clear-input {
|
.c-search__clear-input {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -21,6 +61,15 @@
|
|||||||
|
|
||||||
input[type='text'],
|
input[type='text'],
|
||||||
input[type='search'] {
|
input[type='search'] {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
order: 3;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.c-search__use-regex {
|
||||||
|
@include visibleRegexButton();
|
||||||
|
transition: $transIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
class="c-search__clear-input icon-x-in-circle"
|
class="c-search__clear-input icon-x-in-circle"
|
||||||
@click="clearInput"
|
@click="clearInput"
|
||||||
></a>
|
></a>
|
||||||
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ export default {
|
|||||||
let object = selection[0][0].context.item;
|
let object = selection[0][0].context.item;
|
||||||
if (object) {
|
if (object) {
|
||||||
let type = this.openmct.types.get(object.type);
|
let type = this.openmct.types.get(object.type);
|
||||||
this.showStyles = (this.excludeObjectTypes.indexOf(object.type) < 0) && type.definition.creatable;
|
this.showStyles = this.isLayoutObject(selection[0], object.type) || this.isCreatableObject(object, type);
|
||||||
}
|
}
|
||||||
if (!this.currentTabbedView.key || (!this.showStyles && this.currentTabbedView.key === this.tabbedViews[1].key))
|
if (!this.currentTabbedView.key || (!this.showStyles && this.currentTabbedView.key === this.tabbedViews[1].key))
|
||||||
{
|
{
|
||||||
@ -116,6 +116,14 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
isLayoutObject(selection, objectType) {
|
||||||
|
//we allow conditionSets to be styled if they're part of a layout
|
||||||
|
return selection.length > 1 &&
|
||||||
|
((objectType === 'conditionSet') || (this.excludeObjectTypes.indexOf(objectType) < 0));
|
||||||
|
},
|
||||||
|
isCreatableObject(object, type) {
|
||||||
|
return (this.excludeObjectTypes.indexOf(object.type) < 0) && type.definition.creatable;
|
||||||
|
},
|
||||||
updateCurrentTab(view) {
|
updateCurrentTab(view) {
|
||||||
this.currentTabbedView = view;
|
this.currentTabbedView = view;
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user