Refine display options and add Independent Time Conductor option for Time List view (#7161)

* Apply sort settings immediately - even when in edit mode.

* Adds test for sort order

* Enable independent time conductor for time list view

* Remove time frame duration options.

* Remove immediate sorting in edit mode.

* Closes #7113
- Color of current events changed to bring more in-line with color conventions.
- Changed Time List rgba colors to solids.
- Removed bolding on current events text.

* Fix tests to include new changes

---------

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
This commit is contained in:
Shefali Joshi 2023-11-01 08:47:43 -07:00 committed by GitHub
parent a0fd1f0171
commit ae22920576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 99 additions and 166 deletions

View File

@ -116,8 +116,10 @@ export default {
}, },
mounted() { mounted() {
this.isEditing = this.openmct.editor.isEditing(); this.isEditing = this.openmct.editor.isEditing();
this.timestamp = this.openmct.time.now(); this.updateTimestamp = _.throttle(this.updateTimestamp, 1000);
this.openmct.time.on(TIME_CONTEXT_EVENTS.modeChanged, this.setFixedTime);
this.setTimeContext();
this.timestamp = this.timeContext.now();
this.getPlanDataAndSetConfig(this.domainObject); this.getPlanDataAndSetConfig(this.domainObject);
@ -137,8 +139,6 @@ export default {
); );
this.status = this.openmct.status.get(this.domainObject.identifier); this.status = this.openmct.status.get(this.domainObject.identifier);
this.updateTimestamp = _.throttle(this.updateTimestamp, 1000);
this.openmct.time.on('tick', this.updateTimestamp);
this.openmct.editor.on('isEditing', this.setEditState); this.openmct.editor.on('isEditing', this.setEditState);
this.deferAutoScroll = _.debounce(this.deferAutoScroll, 500); this.deferAutoScroll = _.debounce(this.deferAutoScroll, 500);
@ -150,7 +150,7 @@ export default {
this.composition.load(); this.composition.load();
} }
this.setFixedTime(this.openmct.time.getMode()); this.setFixedTime(this.timeContext.getMode());
}, },
beforeUnmount() { beforeUnmount() {
if (this.unlisten) { if (this.unlisten) {
@ -166,8 +166,7 @@ export default {
} }
this.openmct.editor.off('isEditing', this.setEditState); this.openmct.editor.off('isEditing', this.setEditState);
this.openmct.time.off('tick', this.updateTimestamp); this.stopFollowingTimeContext();
this.openmct.time.off(TIME_CONTEXT_EVENTS.modeChanged, this.setFixedTime);
this.$el.parentElement?.removeEventListener('scroll', this.deferAutoScroll, true); this.$el.parentElement?.removeEventListener('scroll', this.deferAutoScroll, true);
if (this.clearAutoScrollDisabledTimer) { if (this.clearAutoScrollDisabledTimer) {
@ -180,6 +179,21 @@ export default {
} }
}, },
methods: { methods: {
setTimeContext() {
this.stopFollowingTimeContext();
this.timeContext = this.openmct.time.getContextForView(this.path);
this.followTimeContext();
},
followTimeContext() {
this.timeContext.on(TIME_CONTEXT_EVENTS.modeChanged, this.setFixedTime);
this.timeContext.on(TIME_CONTEXT_EVENTS.tick, this.updateTimestamp);
},
stopFollowingTimeContext() {
if (this.timeContext) {
this.timeContext.off(TIME_CONTEXT_EVENTS.modeChanged, this.setFixedTime);
this.timeContext.off(TIME_CONTEXT_EVENTS.tick, this.updateTimestamp);
}
},
planFileUpdated(selectFile) { planFileUpdated(selectFile) {
this.getPlanData({ this.getPlanData({
selectFile, selectFile,
@ -198,7 +212,6 @@ export default {
} else { } else {
this.filterValue = configuration.filter; this.filterValue = configuration.filter;
this.setSort(); this.setSort();
this.setViewBounds();
this.listActivities(); this.listActivities();
} }
}, },
@ -208,7 +221,7 @@ export default {
}, },
setFixedTime() { setFixedTime() {
this.filterValue = this.domainObject.configuration.filter; this.filterValue = this.domainObject.configuration.filter;
this.isFixedTime = !this.openmct.time.isRealTime(); this.isFixedTime = !this.timeContext.isRealTime();
if (this.isFixedTime) { if (this.isFixedTime) {
this.hideAll = false; this.hideAll = false;
} }
@ -269,71 +282,6 @@ export default {
getPlanData(domainObject) { getPlanData(domainObject) {
this.planData = getValidatedData(domainObject); this.planData = getValidatedData(domainObject);
}, },
setViewBounds() {
const pastEventsIndex = this.domainObject.configuration.pastEventsIndex;
const currentEventsIndex = this.domainObject.configuration.currentEventsIndex;
const futureEventsIndex = this.domainObject.configuration.futureEventsIndex;
const pastEventsDuration = this.domainObject.configuration.pastEventsDuration;
const pastEventsDurationIndex = this.domainObject.configuration.pastEventsDurationIndex;
const futureEventsDuration = this.domainObject.configuration.futureEventsDuration;
const futureEventsDurationIndex = this.domainObject.configuration.futureEventsDurationIndex;
if (pastEventsIndex === 0 && futureEventsIndex === 0 && currentEventsIndex === 0) {
this.viewBounds = undefined;
this.hideAll = true;
return;
}
this.hideAll = false;
if (pastEventsIndex === 1 && futureEventsIndex === 1 && currentEventsIndex === 1) {
this.viewBounds = undefined;
return;
}
this.viewBounds = {};
if (pastEventsIndex !== 1) {
const pastDurationInMS = this.getDurationInMilliSeconds(
pastEventsDuration,
pastEventsDurationIndex
);
this.viewBounds.pastEnd = (timestamp) => {
if (pastEventsIndex === 2) {
return timestamp - pastDurationInMS;
} else if (pastEventsIndex === 0) {
return timestamp + 1;
}
};
}
if (futureEventsIndex !== 1) {
const futureDurationInMS = this.getDurationInMilliSeconds(
futureEventsDuration,
futureEventsDurationIndex
);
this.viewBounds.futureStart = (timestamp) => {
if (futureEventsIndex === 2) {
return timestamp + futureDurationInMS;
} else if (futureEventsIndex === 0) {
return 0;
}
};
}
},
getDurationInMilliSeconds(duration, durationIndex) {
if (duration > 0) {
if (durationIndex === 0) {
return duration * 1000;
} else if (durationIndex === 1) {
return duration * 60 * 1000;
} else if (durationIndex === 2) {
return duration * 60 * 60 * 1000;
}
}
},
listActivities() { listActivities() {
let groups = Object.keys(this.planData); let groups = Object.keys(this.planData);
let activities = []; let activities = [];
@ -356,18 +304,18 @@ export default {
}, },
isActivityInBounds(activity) { isActivityInBounds(activity) {
const startInBounds = const startInBounds =
activity.start >= this.openmct.time.bounds()?.start && activity.start >= this.timeContext.bounds()?.start &&
activity.start <= this.openmct.time.bounds()?.end; activity.start <= this.timeContext.bounds()?.end;
const endInBounds = const endInBounds =
activity.end >= this.openmct.time.bounds()?.start && activity.end >= this.timeContext.bounds()?.start &&
activity.end <= this.openmct.time.bounds()?.end; activity.end <= this.timeContext.bounds()?.end;
const middleInBounds = const middleInBounds =
activity.start <= this.openmct.time.bounds()?.start && activity.start <= this.timeContext.bounds()?.start &&
activity.end >= this.openmct.time.bounds()?.end; activity.end >= this.timeContext.bounds()?.end;
return startInBounds || endInBounds || middleInBounds; return startInBounds || endInBounds || middleInBounds;
}, },
filterActivities(activity, index) { filterActivities(activity) {
if (this.isEditing) { if (this.isEditing) {
return true; return true;
} }
@ -381,15 +329,12 @@ export default {
return false; return false;
} }
//current event or future start event or past end event //current event or future start event or past end event
const isCurrent = this.timestamp >= activity.start && this.timestamp <= activity.end; const showCurrentEvents = this.domainObject.configuration.currentEventsIndex > 0;
const isPast =
this.timestamp > activity.end && const isCurrent =
(this.viewBounds?.pastEnd === undefined || showCurrentEvents && this.timestamp >= activity.start && this.timestamp <= activity.end;
activity.end >= this.viewBounds?.pastEnd(this.timestamp)); const isPast = this.timestamp > activity.end;
const isFuture = const isFuture = this.timestamp < activity.start;
this.timestamp < activity.start &&
(this.viewBounds?.futureStart === undefined ||
activity.start <= this.viewBounds?.futureStart(this.timestamp));
return isCurrent || isPast || isFuture; return isCurrent || isPast || isFuture;
}, },

View File

@ -32,34 +32,15 @@
{{ activityOption }} {{ activityOption }}
</option> </option>
</select> </select>
<input
v-if="index === 2"
v-model="duration"
class="c-input c-input--sm"
type="text"
@change="updateForm('duration')"
/>
<select v-if="index === 2" v-model="durationIndex" @change="updateForm('durationIndex')">
<option
v-for="(durationOption, durationKey) in durationOptions"
:key="durationKey"
:value="durationKey"
>
{{ durationOption }}
</option>
</select>
</div> </div>
<div v-else class="c-inspect-properties__value"> <div v-else class="c-inspect-properties__value">
{{ activitiesOptions[index] }} {{ activitiesOptions[index] }}
<span v-if="index > 1">{{ duration }} {{ durationOptions[durationIndex] }}</span>
</div> </div>
</li> </li>
</template> </template>
<script> <script>
const ACTIVITIES_OPTIONS = ["Don't show", 'Show all', 'Show starts within', 'Show after end for']; const ACTIVITIES_OPTIONS = ["Don't show", 'Show all'];
const DURATION_OPTIONS = ['seconds', 'minutes', 'hours'];
export default { export default {
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject'],
@ -76,11 +57,8 @@ export default {
emits: ['updated'], emits: ['updated'],
data() { data() {
return { return {
index: this.domainObject.configuration[`${this.prefix}Index`], index: this.domainObject.configuration[`${this.prefix}Index`] % 2, //this is modulo since we previously had more options and index could have been > 1
durationIndex: this.domainObject.configuration[`${this.prefix}DurationIndex`],
duration: this.domainObject.configuration[`${this.prefix}Duration`],
activitiesOptions: ACTIVITIES_OPTIONS, activitiesOptions: ACTIVITIES_OPTIONS,
durationOptions: DURATION_OPTIONS,
isEditing: this.openmct.editor.isEditing() isEditing: this.openmct.editor.isEditing()
}; };
}, },
@ -116,7 +94,7 @@ export default {
}); });
}, },
isValid() { isValid() {
return this.index < 2 || (this.durationIndex >= 0 && this.duration > 0); return this.index <= 1;
}, },
setEditState(isEditing) { setEditState(isEditing) {
this.isEditing = isEditing; this.isEditing = isEditing;

View File

@ -24,7 +24,7 @@
<div class="c-timelist-properties"> <div class="c-timelist-properties">
<div class="c-inspect-properties"> <div class="c-inspect-properties">
<ul class="c-inspect-properties__section"> <ul class="c-inspect-properties__section">
<div class="c-inspect-properties_header" title="'Timeframe options'">Timeframe</div> <div class="c-inspect-properties_header" title="'Display options'">Display Options</div>
<li class="c-inspect-properties__row"> <li class="c-inspect-properties__row">
<div v-if="canEdit" class="c-inspect-properties__hint span-all"> <div v-if="canEdit" class="c-inspect-properties__hint span-all">
These settings don't affect the view while editing, but will be applied after editing is These settings don't affect the view while editing, but will be applied after editing is
@ -72,17 +72,9 @@ import EventProperties from './EventProperties.vue';
import Filtering from './FilteringComponent.vue'; import Filtering from './FilteringComponent.vue';
const EVENT_TYPES = [ const EVENT_TYPES = [
{
label: 'Future Events',
prefix: 'futureEvents'
},
{ {
label: 'Current Events', label: 'Current Events',
prefix: 'currentEvents' prefix: 'currentEvents'
},
{
label: 'Past Events',
prefix: 'pastEvents'
} }
]; ];

View File

@ -46,6 +46,7 @@ describe('the plugin', function () {
let twoHoursPast = now - 1000 * 60 * 60 * 2; let twoHoursPast = now - 1000 * 60 * 60 * 2;
let oneHourPast = now - 1000 * 60 * 60; let oneHourPast = now - 1000 * 60 * 60;
let twoHoursFuture = now + 1000 * 60 * 60 * 2; let twoHoursFuture = now + 1000 * 60 * 60 * 2;
let threeHoursFuture = now + 1000 * 60 * 60 * 3;
let planObject = { let planObject = {
identifier: { identifier: {
key: 'test-plan-object', key: 'test-plan-object',
@ -71,6 +72,14 @@ describe('the plugin', function () {
type: 'TEST-GROUP', type: 'TEST-GROUP',
color: 'fuchsia', color: 'fuchsia',
textColor: 'black' textColor: 'black'
},
{
name: 'Sed ut perspiciatis two',
start: now,
end: threeHoursFuture,
type: 'TEST-GROUP',
color: 'fuchsia',
textColor: 'black'
} }
] ]
}) })
@ -85,13 +94,13 @@ describe('the plugin', function () {
openmct = createOpenMct({ openmct = createOpenMct({
timeSystemKey: 'utc', timeSystemKey: 'utc',
bounds: { bounds: {
start: twoHoursPast, start: twoHoursFuture,
end: twoHoursFuture end: threeHoursFuture
} }
}); });
openmct.time.setMode(FIXED_MODE_KEY, { openmct.time.setMode(FIXED_MODE_KEY, {
start: twoHoursPast, start: twoHoursFuture,
end: twoHoursFuture end: threeHoursFuture
}); });
openmct.install(new TimelistPlugin()); openmct.install(new TimelistPlugin());
@ -215,7 +224,7 @@ describe('the plugin', function () {
it('displays the activities', () => { it('displays the activities', () => {
const items = element.querySelectorAll(LIST_ITEM_CLASS); const items = element.querySelectorAll(LIST_ITEM_CLASS);
expect(items.length).toEqual(2); expect(items.length).toEqual(1);
}); });
it('displays the activity headers', () => { it('displays the activity headers', () => {
@ -230,14 +239,10 @@ describe('the plugin', function () {
const itemEls = element.querySelectorAll(LIST_ITEM_CLASS); const itemEls = element.querySelectorAll(LIST_ITEM_CLASS);
const itemValues = itemEls[0].querySelectorAll(LIST_ITEM_VALUE_CLASS); const itemValues = itemEls[0].querySelectorAll(LIST_ITEM_VALUE_CLASS);
expect(itemValues.length).toEqual(4); expect(itemValues.length).toEqual(4);
expect(itemValues[3].innerHTML.trim()).toEqual( expect(itemValues[3].innerHTML.trim()).toEqual('Sed ut perspiciatis');
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua' expect(itemValues[0].innerHTML.trim()).toEqual(timeFormatter.format(now, TIME_FORMAT));
);
expect(itemValues[0].innerHTML.trim()).toEqual(
timeFormatter.format(twoHoursPast, TIME_FORMAT)
);
expect(itemValues[1].innerHTML.trim()).toEqual( expect(itemValues[1].innerHTML.trim()).toEqual(
timeFormatter.format(oneHourPast, TIME_FORMAT) timeFormatter.format(twoHoursFuture, TIME_FORMAT)
); );
done(); done();
@ -313,7 +318,7 @@ describe('the plugin', function () {
type: TIMELIST_TYPE, type: TIMELIST_TYPE,
id: 'test-object', id: 'test-object',
configuration: { configuration: {
sortOrderIndex: 0, sortOrderIndex: 2,
futureEventsIndex: 1, futureEventsIndex: 1,
futureEventsDurationIndex: 0, futureEventsDurationIndex: 0,
futureEventsDuration: 0, futureEventsDuration: 0,
@ -350,7 +355,26 @@ describe('the plugin', function () {
return nextTick(() => { return nextTick(() => {
const items = element.querySelectorAll(LIST_ITEM_CLASS); const items = element.querySelectorAll(LIST_ITEM_CLASS);
expect(items.length).toEqual(1); expect(items.length).toEqual(2);
});
});
it('activities and sorts them correctly', () => {
mockComposition.emit('add', planObject);
return nextTick(() => {
const timeFormat = openmct.time.timeSystem().timeFormat;
const timeFormatter = openmct.telemetry.getValueFormatter({ format: timeFormat }).formatter;
const items = element.querySelectorAll(LIST_ITEM_CLASS);
expect(items.length).toEqual(2);
const itemValues = items[1].querySelectorAll(LIST_ITEM_VALUE_CLASS);
expect(itemValues[0].innerHTML.trim()).toEqual(timeFormatter.format(now, TIME_FORMAT));
expect(itemValues[1].innerHTML.trim()).toEqual(
timeFormatter.format(threeHoursFuture, TIME_FORMAT)
);
expect(itemValues[3].innerHTML.trim()).toEqual('Sed ut perspiciatis two');
}); });
}); });
}); });
@ -405,7 +429,7 @@ describe('the plugin', function () {
return nextTick(() => { return nextTick(() => {
const items = element.querySelectorAll(LIST_ITEM_CLASS); const items = element.querySelectorAll(LIST_ITEM_CLASS);
expect(items.length).toEqual(1); expect(items.length).toEqual(2);
}); });
}); });
}); });

View File

@ -42,7 +42,6 @@
background-color: $colorCurrentBg; background-color: $colorCurrentBg;
border-top: 1px solid $colorCurrentBorder !important; border-top: 1px solid $colorCurrentBorder !important;
color: $colorCurrentFg; color: $colorCurrentFg;
font-weight: bold;
} }
&.--is-future { &.--is-future {

View File

@ -421,10 +421,10 @@ $colorGaugeLimitLow: $colorGaugeLimitHigh;
$transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions $transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions
$marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges $marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges
// Time Strip and Lists // Time Strip and Lists
$colorCurrentBg: rgba($colorStatusAlert, 0.3); $colorCurrentBg: $colorTimeRealtimeBg;
$colorCurrentFg: pullForward($colorBodyFg, 20%); $colorCurrentFg: $colorTimeRealtimeFg;
$colorCurrentBorder: $colorBodyBg; $colorCurrentBorder: $colorBodyBg;
$colorFutureBg: rgba($colorKey, 0.2); $colorFutureBg: #1b5263;
$colorFutureFg: $colorCurrentFg; $colorFutureFg: $colorCurrentFg;
$colorFutureBorder: $colorCurrentBorder; $colorFutureBorder: $colorCurrentBorder;
$colorGanttSelectedBorder: rgba(#fff, 0.3); $colorGanttSelectedBorder: rgba(#fff, 0.3);

View File

@ -424,10 +424,10 @@ $colorGaugeLimitLow: $colorGaugeLimitHigh;
$transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions $transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions
$marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges $marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges
// Time Strip and Lists // Time Strip and Lists
$colorCurrentBg: rgba($colorStatusAlert, 0.3); $colorCurrentBg: $colorTimeRealtimeBg;
$colorCurrentFg: pullForward($colorBodyFg, 20%); $colorCurrentFg: $colorTimeRealtimeFg;
$colorCurrentBorder: #fff; $colorCurrentBorder: $colorBodyBg;
$colorFutureBg: rgba($colorKey, 0.2); $colorFutureBg: #1b5263;
$colorFutureFg: $colorCurrentFg; $colorFutureFg: $colorCurrentFg;
$colorFutureBorder: $colorCurrentBorder; $colorFutureBorder: $colorCurrentBorder;
$colorGanttSelectedBorder: #fff; $colorGanttSelectedBorder: #fff;

View File

@ -421,11 +421,11 @@ $colorGaugeLimitLow: $colorGaugeLimitHigh;
$transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions $transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions
$marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges $marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges
// Time Strip and Lists // Time Strip and Lists
$colorCurrentBg: rgba($colorStatusAlert, 0.3); $colorCurrentBg: #5872bd;
$colorCurrentFg: pullForward($colorBodyFg, 20%); $colorCurrentFg: #eee;
$colorCurrentBorder: #fff; $colorCurrentBorder: #fff;
$colorFutureBg: rgba($colorKey, 0.2); $colorFutureBg: #c6f0ff;
$colorFutureFg: $colorCurrentFg; $colorFutureFg: $colorBodyFg;
$colorFutureBorder: $colorCurrentBorder; $colorFutureBorder: $colorCurrentBorder;
$colorGanttSelectedBorder: #fff; $colorGanttSelectedBorder: #fff;

View File

@ -101,18 +101,11 @@ import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwit
import IndependentTimeConductor from '@/plugins/timeConductor/independent/IndependentTimeConductor.vue'; import IndependentTimeConductor from '@/plugins/timeConductor/independent/IndependentTimeConductor.vue';
import tooltipHelpers from '../../api/tooltips/tooltipMixins'; import tooltipHelpers from '../../api/tooltips/tooltipMixins';
import { SupportedViewTypes } from '../../utils/constants.js';
import ObjectView from './ObjectView.vue'; import ObjectView from './ObjectView.vue';
const SIMPLE_CONTENT_TYPES = ['clock', 'timer', 'summary-widget', 'hyperlink', 'conditionWidget']; const SIMPLE_CONTENT_TYPES = ['clock', 'timer', 'summary-widget', 'hyperlink', 'conditionWidget'];
const CSS_WIDTH_LESS_STR = '--width-less-than-'; const CSS_WIDTH_LESS_STR = '--width-less-than-';
const SupportedViewTypes = [
'plot-stacked',
'plot-overlay',
'bar-graph.view',
'scatter-plot.view',
'time-strip.view',
'example.imagery'
];
export default { export default {
components: { components: {

View File

@ -143,15 +143,9 @@ import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwit
import IndependentTimeConductor from '@/plugins/timeConductor/independent/IndependentTimeConductor.vue'; import IndependentTimeConductor from '@/plugins/timeConductor/independent/IndependentTimeConductor.vue';
import tooltipHelpers from '../../api/tooltips/tooltipMixins'; import tooltipHelpers from '../../api/tooltips/tooltipMixins';
import { SupportedViewTypes } from '../../utils/constants.js';
import ViewSwitcher from './ViewSwitcher.vue'; import ViewSwitcher from './ViewSwitcher.vue';
const SupportedViewTypes = [
'plot-stacked',
'plot-overlay',
'bar-graph.view',
'time-strip.view',
'example.imagery'
];
const PLACEHOLDER_OBJECT = {}; const PLACEHOLDER_OBJECT = {};
export default { export default {

8
src/utils/constants.js Normal file
View File

@ -0,0 +1,8 @@
export const SupportedViewTypes = [
'plot-stacked',
'plot-overlay',
'bar-graph.view',
'time-strip.view',
'example.imagery',
'timelist.view'
];