Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nasa/openmct.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShefali Joshi <simplyrender@gmail.com>2021-06-29 19:10:37 +0300
committerGitHub <noreply@github.com>2021-06-29 19:10:37 +0300
commit8422add614c0e31164bf1cfac0aaef7ae66161a1 (patch)
treecf57c0faab4546d9ed0864f0cd741778bac6e96d
parent2114697d6fdcbe0efcf9659926f2aaed5474cf3d (diff)
parente3bf72e77f602c803a68915a85633be116c88775 (diff)
Merge branch 'master' into 1.7.41.7.4
-rw-r--r--src/api/overlays/components/overlay-component.scss16
-rw-r--r--src/plugins/folderView/components/grid-view.scss5
-rw-r--r--src/plugins/folderView/components/list-item.scss5
-rw-r--r--src/plugins/imagery/ImageryViewProvider.js3
-rw-r--r--src/plugins/imagery/components/ImageryViewLayout.vue99
-rw-r--r--src/plugins/imagery/components/imagery-view-layout.scss31
-rw-r--r--src/plugins/imagery/pluginSpec.js21
-rw-r--r--src/plugins/plan/Plan.vue57
-rw-r--r--src/plugins/plan/inspector/ActivityProperty.vue52
-rw-r--r--src/plugins/plan/inspector/PlanActivitiesView.vue206
-rw-r--r--src/plugins/plan/inspector/PlanActivityView.vue84
-rw-r--r--src/plugins/plan/inspector/PlanInspectorViewProvider.js69
-rw-r--r--src/plugins/plan/plan.scss22
-rw-r--r--src/plugins/plan/plugin.js2
-rw-r--r--src/plugins/plan/util.js22
-rw-r--r--src/plugins/timeConductor/ConductorHistory.vue42
-rw-r--r--src/plugins/timeConductor/timePopup.vue2
-rw-r--r--src/styles/_about.scss7
-rw-r--r--src/styles/_constants-espresso.scss7
-rw-r--r--src/styles/_constants-maelstrom.scss5
-rw-r--r--src/styles/_constants-snow.scss5
-rwxr-xr-xsrc/styles/_constants.scss3
-rw-r--r--src/ui/inspector/ObjectName.vue14
-rw-r--r--src/ui/inspector/Properties.vue6
-rw-r--r--src/utils/duration.js85
25 files changed, 755 insertions, 115 deletions
diff --git a/src/api/overlays/components/overlay-component.scss b/src/api/overlays/components/overlay-component.scss
index b0c0a7055..419d22048 100644
--- a/src/api/overlays/components/overlay-component.scss
+++ b/src/api/overlays/components/overlay-component.scss
@@ -21,8 +21,7 @@
&__outer {
@include abs();
- background: $overlayColorBg;
- color: $overlayColorFg;
+ background: $colorBodyBg;
display: flex;
flex-direction: column;
padding: $overlayInnerMargin;
@@ -30,7 +29,6 @@
&__close-button {
$p: $interiorMargin + 2px;
- color: $overlayColorFg;
font-size: 1.5em;
position: absolute;
top: $p; right: $p;
@@ -82,11 +80,6 @@
}
}
- .c-button,
- .c-click-icon {
- filter: $overlayBrightnessAdjust;
- }
-
.c-object-label__name {
filter: $objectLabelNameFilter;
}
@@ -103,6 +96,7 @@ body.desktop {
}
// Overlay types, styling for desktop. Appended to .l-overlay-wrapper element.
+ .l-overlay-large,
.l-overlay-small,
.l-overlay-fit {
.c-overlay__outer {
@@ -124,12 +118,8 @@ body.desktop {
$tbPad: floor($pad * 0.8);
$lrPad: $pad;
.c-overlay {
- &__blocker {
- display: none;
- }
-
&__outer {
- @include overlaySizing($overlayOuterMarginFullscreen);
+ @include overlaySizing($overlayOuterMarginLarge);
padding: $tbPad $lrPad;
}
diff --git a/src/plugins/folderView/components/grid-view.scss b/src/plugins/folderView/components/grid-view.scss
index 72a402894..65c019a19 100644
--- a/src/plugins/folderView/components/grid-view.scss
+++ b/src/plugins/folderView/components/grid-view.scss
@@ -23,6 +23,11 @@
body.mobile & {
flex: 1 0 auto;
}
+
+ [class*='l-overlay'] & {
+ // When this view is in an overlay, prevent navigation
+ pointer-events: none;
+ }
}
/******************************* GRID ITEMS */
diff --git a/src/plugins/folderView/components/list-item.scss b/src/plugins/folderView/components/list-item.scss
index 6e4c12d43..8e99c4a64 100644
--- a/src/plugins/folderView/components/list-item.scss
+++ b/src/plugins/folderView/components/list-item.scss
@@ -22,4 +22,9 @@
@include isAlias();
}
}
+
+ [class*='l-overlay'] & {
+ // When this view is in an overlay, prevent navigation
+ pointer-events: none;
+ }
}
diff --git a/src/plugins/imagery/ImageryViewProvider.js b/src/plugins/imagery/ImageryViewProvider.js
index 141999002..2fbc57f36 100644
--- a/src/plugins/imagery/ImageryViewProvider.js
+++ b/src/plugins/imagery/ImageryViewProvider.js
@@ -62,6 +62,9 @@ export default function ImageryViewProvider(openmct) {
destroy: function () {
component.$destroy();
component = undefined;
+ },
+ _getInstance: function () {
+ return component;
}
};
}
diff --git a/src/plugins/imagery/components/ImageryViewLayout.vue b/src/plugins/imagery/components/ImageryViewLayout.vue
index e480824c1..b39f3dbd2 100644
--- a/src/plugins/imagery/components/ImageryViewLayout.vue
+++ b/src/plugins/imagery/components/ImageryViewLayout.vue
@@ -124,27 +124,40 @@
</div>
</div>
<div
- ref="thumbsWrapper"
class="c-imagery__thumbs-wrapper"
- :class="{'is-paused': isPaused}"
- @scroll="handleScroll"
+ :class="[
+ { 'is-paused': isPaused },
+ { 'is-autoscroll-off': !resizingWindow && !autoScroll && !isPaused }
+ ]"
>
- <div v-for="(image, index) in imageHistory"
- :key="image.url + image.time"
- class="c-imagery__thumb c-thumb"
- :class="{ selected: focusedImageIndex === index && isPaused }"
- @click="setFocusedImage(index, thumbnailClick)"
+ <div
+ ref="thumbsWrapper"
+ class="c-imagery__thumbs-scroll-area"
+ @scroll="handleScroll"
>
- <a href=""
- :download="image.imageDownloadName"
- @click.prevent
+ <div v-for="(image, index) in imageHistory"
+ :key="image.url + image.time"
+ class="c-imagery__thumb c-thumb"
+ :class="{ selected: focusedImageIndex === index && isPaused }"
+ @click="setFocusedImage(index, thumbnailClick)"
>
- <img class="c-thumb__image"
- :src="image.url"
+ <a href=""
+ :download="image.imageDownloadName"
+ @click.prevent
>
- </a>
- <div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
+ <img class="c-thumb__image"
+ :src="image.url"
+ >
+ </a>
+ <div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
+ </div>
</div>
+
+ <button
+ class="c-imagery__auto-scroll-resume-button c-icon-button icon-play"
+ title="Resume automatic scrolling of image thumbnails"
+ @click="scrollToRight('reset')"
+ ></button>
</div>
</div>
</template>
@@ -171,6 +184,8 @@ const TWENTYFOUR_HOURS = EIGHT_HOURS * 3;
const ARROW_RIGHT = 39;
const ARROW_LEFT = 37;
+const SCROLL_LATENCY = 250;
+
export default {
components: {
Compass
@@ -204,7 +219,8 @@ export default {
focusedImageNaturalAspectRatio: undefined,
imageContainerWidth: undefined,
imageContainerHeight: undefined,
- lockCompass: true
+ lockCompass: true,
+ resizingWindow: false
};
},
computed: {
@@ -380,9 +396,13 @@ export default {
this.imageContainerResizeObserver = new ResizeObserver(this.resizeImageContainer);
this.imageContainerResizeObserver.observe(this.$refs.focusedImage);
- },
- updated() {
- this.scrollToRight();
+
+ // For adjusting scroll bar size and position when resizing thumbs wrapper
+ this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY);
+ this.handleThumbWindowResizeEnded = _.debounce(this.handleThumbWindowResizeEnded, SCROLL_LATENCY);
+
+ this.thumbWrapperResizeObserver = new ResizeObserver(this.handleThumbWindowResizeStart);
+ this.thumbWrapperResizeObserver.observe(this.$refs.thumbsWrapper);
},
beforeDestroy() {
if (this.unsubscribe) {
@@ -394,6 +414,10 @@ export default {
this.imageContainerResizeObserver.disconnect();
}
+ if (this.thumbWrapperResizeObserver) {
+ this.thumbWrapperResizeObserver.disconnect();
+ }
+
if (this.relatedTelemetry.hasRelatedTelemetry) {
this.relatedTelemetry.destroy();
}
@@ -561,17 +585,15 @@ export default {
},
handleScroll() {
const thumbsWrapper = this.$refs.thumbsWrapper;
- if (!thumbsWrapper) {
+ if (!thumbsWrapper || this.resizingWindow) {
return;
}
- const { scrollLeft, scrollWidth, clientWidth, scrollTop, scrollHeight, clientHeight } = thumbsWrapper;
- const disableScroll = (scrollWidth - scrollLeft) > 2 * clientWidth
- || (scrollHeight - scrollTop) > 2 * clientHeight;
+ const { scrollLeft, scrollWidth, clientWidth } = thumbsWrapper;
+ const disableScroll = scrollWidth > Math.ceil(scrollLeft + clientWidth);
this.autoScroll = !disableScroll;
},
paused(state, type) {
-
this.isPaused = state;
if (type === 'button') {
@@ -584,6 +606,7 @@ export default {
}
this.autoScroll = true;
+ this.scrollToRight();
},
scrollToFocused() {
const thumbsWrapper = this.$refs.thumbsWrapper;
@@ -600,8 +623,8 @@ export default {
});
}
},
- scrollToRight() {
- if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
+ scrollToRight(type) {
+ if (type !== 'reset' && (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll)) {
return;
}
@@ -610,7 +633,9 @@ export default {
return;
}
- setTimeout(() => this.$refs.thumbsWrapper.scrollLeft = scrollWidth, 0);
+ this.$nextTick(() => {
+ this.$refs.thumbsWrapper.scrollLeft = scrollWidth;
+ });
},
setFocusedImage(index, thumbnailClick = false) {
if (this.isPaused && !thumbnailClick) {
@@ -678,9 +703,9 @@ export default {
image.imageDownloadName = this.getImageDownloadName(datum);
this.imageHistory.push(image);
-
if (setFocused) {
this.setFocusedImage(this.imageHistory.length - 1);
+ this.scrollToRight();
}
},
getFormatter(key) {
@@ -816,6 +841,24 @@ export default {
this.imageContainerHeight = this.$refs.focusedImage.clientHeight;
}
},
+ handleThumbWindowResizeStart() {
+ if (!this.autoScroll) {
+ return;
+ }
+
+ // To hide resume button while scrolling
+ this.resizingWindow = true;
+ this.handleThumbWindowResizeEnded();
+ },
+ handleThumbWindowResizeEnded() {
+ if (!this.isPaused) {
+ this.scrollToRight('reset');
+ }
+
+ this.$nextTick(() => {
+ this.resizingWindow = false;
+ });
+ },
toggleLockCompass() {
this.lockCompass = !this.lockCompass;
}
diff --git a/src/plugins/imagery/components/imagery-view-layout.scss b/src/plugins/imagery/components/imagery-view-layout.scss
index 8f6fec9dc..7e45f2bd6 100644
--- a/src/plugins/imagery/components/imagery-view-layout.scss
+++ b/src/plugins/imagery/components/imagery-view-layout.scss
@@ -93,24 +93,43 @@
}
&__thumbs-wrapper {
- flex: 0 0 auto;
+ display: flex; // Uses row layout
+
+ &.is-autoscroll-off {
+ background: $colorInteriorBorder;
+ [class*='__auto-scroll-resume-button'] {
+ display: block;
+ }
+ }
+
+ &.is-paused {
+ background: rgba($colorPausedBg, 0.4);
+ }
+ }
+
+ &__thumbs-scroll-area {
+ flex: 0 1 auto;
display: flex;
flex-direction: row;
height: 135px;
overflow-x: auto;
overflow-y: hidden;
+ margin-bottom: 1px;
padding-bottom: $interiorMarginSm;
- &.is-paused {
- background: rgba($colorPausedBg, 0.4);
- }
-
.c-thumb:last-child {
// Hilite the lastest thumb
background: $colorBodyFg;
color: $colorBodyBg;
}
}
+
+ &__auto-scroll-resume-button {
+ display: none; // Set to block when __thumbs-wrapper has .is-autoscroll-off
+ flex: 0 0 auto;
+ font-size: 0.8em;
+ margin: $interiorMarginSm;
+ }
}
/*************************************** THUMBS */
@@ -142,7 +161,7 @@
.l-layout,
.c-fl {
- .c-imagery__thumbs-wrapper {
+ .c-imagery__thumbs-scroll-area {
// When Imagery is in a layout, hide the thumbs area
display: none;
}
diff --git a/src/plugins/imagery/pluginSpec.js b/src/plugins/imagery/pluginSpec.js
index 9179692d8..9a73c9ebd 100644
--- a/src/plugins/imagery/pluginSpec.js
+++ b/src/plugins/imagery/pluginSpec.js
@@ -92,6 +92,7 @@ describe("The Imagery View Layout", () => {
let resolveFunction;
let openmct;
+ let appHolder;
let parent;
let child;
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
@@ -195,7 +196,7 @@ describe("The Imagery View Layout", () => {
// this setups up the app
beforeEach((done) => {
- const appHolder = document.createElement('div');
+ appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
@@ -209,6 +210,8 @@ describe("The Imagery View Layout", () => {
child = document.createElement('div');
parent.appendChild(child);
+ // document.querySelector('body').append(parent);
+
spyOn(window, 'ResizeObserver').and.returnValue({
observe() {},
disconnect() {}
@@ -362,5 +365,21 @@ describe("The Imagery View Layout", () => {
done();
});
});
+ it ('shows an auto scroll button when scroll to left', async () => {
+ // to mock what a scroll would do
+ imageryView._getInstance().$refs.ImageryLayout.autoScroll = false;
+ await Vue.nextTick();
+ let autoScrollButton = parent.querySelector('.c-imagery__auto-scroll-resume-button');
+ expect(autoScrollButton).toBeTruthy();
+ });
+ it ('scrollToRight is called when clicking on auto scroll button', async () => {
+ // use spyon to spy the scroll function
+ spyOn(imageryView._getInstance().$refs.ImageryLayout, 'scrollToRight');
+ imageryView._getInstance().$refs.ImageryLayout.autoScroll = false;
+ await Vue.nextTick();
+ parent.querySelector('.c-imagery__auto-scroll-resume-button').click();
+ expect(imageryView._getInstance().$refs.ImageryLayout.scrollToRight).toHaveBeenCalledWith('reset');
+
+ });
});
});
diff --git a/src/plugins/plan/Plan.vue b/src/plugins/plan/Plan.vue
index b65b8a3a6..78342d12a 100644
--- a/src/plugins/plan/Plan.vue
+++ b/src/plugins/plan/Plan.vue
@@ -1,3 +1,25 @@
+<!--
+ Open MCT, Copyright (c) 2014-2020, United States Government
+ as represented by the Administrator of the National Aeronautics and Space
+ Administration. All rights reserved.
+
+ Open MCT is licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations
+ under the License.
+
+ Open MCT includes source code licensed under additional open source
+ licenses. See the Open Source Licenses file (LICENSES.md) included with
+ this source code distribution or the Licensing information page available
+ at runtime from the About dialog for additional information.
+-->
+
<template>
<div ref="plan"
class="c-plan c-timeline-holder"
@@ -28,7 +50,6 @@ import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
import { getValidatedPlan } from "./util";
import Vue from "vue";
-//TODO: UI direction needed for the following property values
const PADDING = 1;
const OUTER_TEXT_PADDING = 12;
const INNER_TEXT_PADDING = 17;
@@ -281,7 +302,9 @@ export default {
exceeds: {
start: this.xScale(this.viewBounds.start) > this.xScale(activity.start),
end: this.xScale(this.viewBounds.end) < this.xScale(activity.end)
- }
+ },
+ start: activity.start,
+ end: activity.end
},
textLines: textLines,
textStart: textStart,
@@ -339,6 +362,9 @@ export default {
components: {
SwimLane
},
+ provide: {
+ openmct: this.openmct
+ },
data() {
return {
heading,
@@ -376,7 +402,6 @@ export default {
activityRows.forEach((row) => {
const items = activitiesByRow[row];
items.forEach(item => {
- //TODO: Don't draw the left-border of the rectangle if the activity started before viewBounds.start
this.plotActivity(item, parseInt(row, 10), groupSVG);
});
});
@@ -399,6 +424,9 @@ export default {
element.setAttributeNS(null, key, attributes[key]);
});
},
+ getNSAttributesForElement(element, attribute) {
+ return element.getAttributeNS(null, attribute);
+ },
// Experimental for now - unused
addForeignElement(svgElement, label, x, y) {
let foreign = document.createElementNS('http://www.w3.org/2000/svg', "foreignObject");
@@ -443,6 +471,10 @@ export default {
fill: activity.color
});
+ rectElement.addEventListener('click', (event) => {
+ this.setSelectionForActivity(event.currentTarget, activity, event.metaKey);
+ });
+
svgElement.appendChild(rectElement);
item.textLines.forEach((line, index) => {
@@ -456,6 +488,9 @@ export default {
const textNode = document.createTextNode(line);
textElement.appendChild(textNode);
+ textElement.addEventListener('click', (event) => {
+ this.setSelectionForActivity(event.currentTarget, activity, event.metaKey);
+ });
svgElement.appendChild(textElement);
});
// this.addForeignElement(svgElement, activity.name, item.textStart, item.textY - LINE_HEIGHT);
@@ -482,6 +517,22 @@ export default {
const cBrightness = ((hR * 299) + (hG * 587) + (hB * 114)) / 1000;
return cBrightness > cThreshold ? "#000000" : "#ffffff";
+ },
+ setSelectionForActivity(element, activity, multiSelect) {
+ this.openmct.selection.select([{
+ element: element,
+ context: {
+ type: 'activity',
+ activity: activity
+ }
+ }, {
+ element: this.openmct.layout.$refs.browseObject.$el,
+ context: {
+ item: this.domainObject,
+ supportsMultiSelect: true
+ }
+ }], multiSelect);
+ event.stopPropagation();
}
}
};
diff --git a/src/plugins/plan/inspector/ActivityProperty.vue b/src/plugins/plan/inspector/ActivityProperty.vue
new file mode 100644
index 000000000..c21681039
--- /dev/null
+++ b/src/plugins/plan/inspector/ActivityProperty.vue
@@ -0,0 +1,52 @@
+<!--
+ Open MCT, Copyright (c) 2014-2020, United States Government
+ as represented by the Administrator of the National Aeronautics and Space
+ Administration. All rights reserved.
+
+ Open MCT is licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations
+ under the License.
+
+ Open MCT includes source code licensed under additional open source
+ licenses. See the Open Source Licenses file (LICENSES.md) included with
+ this source code distribution or the Licensing information page available
+ at runtime from the About dialog for additional information.
+-->
+
+<template>
+<li class="c-inspect-properties__row">
+ <div class="c-inspect-properties__label">
+ {{ label }}
+ </div>
+ <div class="c-inspect-properties__value">
+ {{ value }}
+ </div>
+</li>
+</template>
+
+<script>
+
+export default {
+ props: {
+ label: {
+ type: String,
+ default() {
+ return '';
+ }
+ },
+ value: {
+ type: String,
+ default() {
+ return '';
+ }
+ }
+ }
+};
+</script>
diff --git a/src/plugins/plan/inspector/PlanActivitiesView.vue b/src/plugins/plan/inspector/PlanActivitiesView.vue
new file mode 100644
index 000000000..1ed17fa6b
--- /dev/null
+++ b/src/plugins/plan/inspector/PlanActivitiesView.vue
@@ -0,0 +1,206 @@
+<!--
+ Open MCT, Copyright (c) 2014-2021, United States Government
+ as represented by the Administrator of the National Aeronautics and Space
+ Administration. All rights reserved.
+
+ Open MCT is licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations
+ under the License.
+
+ Open MCT includes source code licensed under additional open source
+ licenses. See the Open Source Licenses file (LICENSES.md) included with
+ this source code distribution or the Licensing information page available
+ at runtime from the About dialog for additional information.
+-->
+<template>
+<div class="c-inspector__properties c-inspect-properties">
+ <plan-activity-view v-for="activity in activities"
+ :key="activity.id"
+ :activity="activity"
+ :heading="heading"
+ />
+</div>
+</template>
+
+<script>
+import PlanActivityView from "./PlanActivityView.vue";
+import { getPreciseDuration } from "utils/duration";
+import uuid from 'uuid';
+
+const propertyLabels = {
+ 'start': 'Start DateTime',
+ 'end': 'End DateTime',
+ 'duration': 'Duration',
+ 'earliestStart': 'Earliest Start',
+ 'latestEnd': 'Latest End',
+ 'gap': 'Gap',
+ 'overlap': 'Overlap',
+ 'totalTime': 'Total Time'
+};
+
+export default {
+ components: {
+ PlanActivityView
+ },
+ inject: ['openmct', 'selection'],
+ data() {
+ return {
+ name: '',
+ activities: [],
+ heading: ''
+ };
+ },
+ mounted() {
+ this.setFormatters();
+ this.getPlanData(this.selection);
+ this.getActivities();
+ this.openmct.selection.on('change', this.updateSelection);
+ this.openmct.time.on('timeSystem', this.setFormatters);
+ },
+ beforeDestroy() {
+ this.openmct.selection.off('change', this.updateSelection);
+ this.openmct.time.off('timeSystem', this.setFormatters);
+ },
+ methods: {
+ setFormatters() {
+ let timeSystem = this.openmct.time.timeSystem();
+ this.timeFormatter = this.openmct.telemetry.getValueFormatter({
+ format: timeSystem.timeFormat
+ }).formatter;
+ },
+ updateSelection(newSelection) {
+ this.getPlanData(newSelection);
+ this.getActivities();
+ },
+ getPlanData(selection) {
+ this.selectedActivities = [];
+ selection.forEach((selectionItem) => {
+ if (selectionItem[0].context.type === 'activity') {
+ const activity = selectionItem[0].context.activity;
+ if (activity) {
+ this.selectedActivities.push(activity);
+ }
+ }
+ });
+ },
+ getActivities() {
+ if (this.selectedActivities.length <= 1) {
+ this.heading = 'Time';
+ this.setSingleActivityProperties();
+ } else {
+ this.heading = 'Convex Hull';
+ this.setMultipleActivityProperties();
+ }
+ },
+ setSingleActivityProperties() {
+ this.activities.splice(0);
+ this.selectedActivities.forEach((selectedActivity, index) => {
+ const activity = {
+ id: uuid(),
+ start: {
+ label: propertyLabels.start,
+ value: this.formatTime(selectedActivity.start)
+ },
+ end: {
+ label: propertyLabels.end,
+ value: this.formatTime(selectedActivity.end)
+ },
+ duration: {
+ label: propertyLabels.duration,
+ value: this.formatDuration(selectedActivity.end - selectedActivity.start)
+ }
+ };
+ this.$set(this.activities, index, activity);
+ });
+ },
+ sortFn(a, b) {
+ const numA = parseInt(a.start, 10);
+ const numB = parseInt(b.start, 10);
+ if (numA > numB) {
+ return 1;
+ }
+
+ if (numA < numB) {
+ return -1;
+ }
+
+ return 0;
+ },
+ setMultipleActivityProperties() {
+ this.activities.splice(0);
+
+ let earliestStart;
+ let latestEnd;
+ let gap;
+ let overlap;
+
+ //Sort by start time
+ let selectedActivities = this.selectedActivities.sort(this.sortFn);
+ selectedActivities.forEach((selectedActivity, index) => {
+ if (selectedActivities.length === 2 && index > 0) {
+ const previous = selectedActivities[index - 1];
+ //they're on different rows so there must be overlap
+ if (previous.end > selectedActivity.start) {
+ overlap = previous.end - selectedActivity.start;
+ } else if (previous.end < selectedActivity.start) {
+ gap = selectedActivity.start - previous.end;
+ }
+ }
+
+ if (index > 0) {
+ earliestStart = Math.min(earliestStart, selectedActivity.start);
+ latestEnd = Math.max(latestEnd, selectedActivity.end);
+ } else {
+ earliestStart = selectedActivity.start;
+ latestEnd = selectedActivity.end;
+ }
+ });
+ let totalTime = latestEnd - earliestStart;
+
+ const activity = {
+ id: uuid(),
+ 'earliestStart': {
+ label: propertyLabels.earliestStart,
+ value: this.formatTime(earliestStart)
+ },
+ 'latestEnd': {
+ label: propertyLabels.latestEnd,
+ value: this.formatTime(latestEnd)
+ }
+ };
+
+ if (gap) {
+ activity.gap = {
+ label: propertyLabels.gap,
+ value: this.formatDuration(gap)
+ };
+ } else if (overlap) {
+ activity.overlap = {
+ label: propertyLabels.overlap,
+ value: this.formatDuration(overlap)
+ };
+ }
+
+ activity.totalTime = {
+ label: propertyLabels.totalTime,
+ value: this.formatDuration(totalTime)
+ };
+
+ this.$set(this.activities, 0, activity);
+ },
+ formatDuration(duration) {
+ return getPreciseDuration(duration);
+ },
+ formatTime(time) {
+ return this.timeFormatter.format(time);
+ }
+ }
+};
+</script>
diff --git a/src/plugins/plan/inspector/PlanActivityView.vue b/src/plugins/plan/inspector/PlanActivityView.vue
new file mode 100644
index 000000000..c28eb8aad
--- /dev/null
+++ b/src/plugins/plan/inspector/PlanActivityView.vue
@@ -0,0 +1,84 @@
+<!--
+ Open MCT, Copyright (c) 2014-2020, United States Government
+ as represented by the Administrator of the National Aeronautics and Space
+ Administration. All rights reserved.
+
+ Open MCT is licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations
+ under the License.
+
+ Open MCT includes source code licensed under additional open source
+ licenses. See the Open Source Licenses file (LICENSES.md) included with
+ this source code distribution or the Licensing information page available
+ at runtime from the About dialog for additional information.
+-->
+
+<template>
+<div v-if="timeProperties.length"
+ class="u-contents"
+>
+ <div class="c-inspect-properties__header">
+ {{ heading }}
+ </div>
+ <ul v-for="timeProperty in timeProperties"
+ :key="timeProperty.id"
+ class="c-inspect-properties__section"
+ >
+ <activity-property :label="timeProperty.label"
+ :value="timeProperty.value"
+ />
+ </ul>
+</div>
+</template>
+
+<script>
+import ActivityProperty from './ActivityProperty.vue';
+import uuid from 'uuid';
+
+export default {
+ components: {
+ ActivityProperty
+ },
+ props: {
+ activity: {
+ type: Object,
+ required: true
+ },
+ heading: {
+ type: String,
+ required: true
+ }
+ },
+ data() {
+ return {
+ timeProperties: []
+ };
+ },
+ mounted() {
+ this.setProperties();
+ },
+ methods: {
+ setProperties() {
+ Object.keys(this.activity).forEach((key) => {
+ if (this.activity[key].label) {
+ const label = this.activity[key].label;
+ const value = String(this.activity[key].value);
+
+ this.$set(this.timeProperties, this.timeProperties.length, {
+ id: uuid(),
+ label,
+ value
+ });
+ }
+ });
+ }
+ }
+};
+</script>
diff --git a/src/plugins/plan/inspector/PlanInspectorViewProvider.js b/src/plugins/plan/inspector/PlanInspectorViewProvider.js
new file mode 100644
index 000000000..34156bbca
--- /dev/null
+++ b/src/plugins/plan/inspector/PlanInspectorViewProvider.js
@@ -0,0 +1,69 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import PlanActivitiesView from "./PlanActivitiesView.vue";
+import Vue from 'vue';
+
+export default function PlanInspectorViewProvider(openmct) {
+ return {
+ key: 'plan-inspector',
+ name: 'Plan Inspector View',
+ canView: function (selection) {
+ if (selection.length === 0 || selection[0].length === 0) {
+ return false;
+ }
+
+ let context = selection[0][0].context;
+
+ return context
+ && context.type === 'activity';
+ },
+ view: function (selection) {
+ let component;
+
+ return {
+ show: function (element) {
+ component = new Vue({
+ el: element,
+ components: {
+ PlanActivitiesView: PlanActivitiesView
+ },
+ provide: {
+ openmct,
+ selection: selection
+ },
+ template: '<plan-activities-view></plan-activities-view>'
+ });
+ },
+ destroy: function () {
+ if (component) {
+ component.$destroy();
+ component = undefined;
+ }
+ }
+ };
+ },
+ priority: function () {
+ return 1;
+ }
+ };
+}
diff --git a/src/plugins/plan/plan.scss b/src/plugins/plan/plan.scss
index 5b9457893..fe0bec78f 100644
--- a/src/plugins/plan/plan.scss
+++ b/src/plugins/plan/plan.scss
@@ -1,3 +1,25 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
.c-plan {
svg {
text-rendering: geometricPrecision;
diff --git a/src/plugins/plan/plugin.js b/src/plugins/plan/plugin.js
index 9f5f83d16..f2abb5faf 100644
--- a/src/plugins/plan/plugin.js
+++ b/src/plugins/plan/plugin.js
@@ -21,6 +21,7 @@
*****************************************************************************/
import PlanViewProvider from './PlanViewProvider';
+import PlanInspectorViewProvider from "./inspector/PlanInspectorViewProvider";
export default function () {
return function install(openmct) {
@@ -44,6 +45,7 @@ export default function () {
}
});
openmct.objectViews.addProvider(new PlanViewProvider(openmct));
+ openmct.inspectorViews.addProvider(new PlanInspectorViewProvider(openmct));
};
}
diff --git a/src/plugins/plan/util.js b/src/plugins/plan/util.js
index efa540905..fde072e57 100644
--- a/src/plugins/plan/util.js
+++ b/src/plugins/plan/util.js
@@ -1,3 +1,25 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
export function getValidatedPlan(domainObject) {
let body = domainObject.selectFile.body;
let json = {};
diff --git a/src/plugins/timeConductor/ConductorHistory.vue b/src/plugins/timeConductor/ConductorHistory.vue
index 76ea7a9fc..93219f366 100644
--- a/src/plugins/timeConductor/ConductorHistory.vue
+++ b/src/plugins/timeConductor/ConductorHistory.vue
@@ -39,9 +39,8 @@ const DEFAULT_DURATION_FORMATTER = 'duration';
const LOCAL_STORAGE_HISTORY_KEY_FIXED = 'tcHistory';
const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime';
const DEFAULT_RECORDS = 10;
-const ONE_MINUTE = 60 * 1000;
-const ONE_HOUR = ONE_MINUTE * 60;
-const ONE_DAY = ONE_HOUR * 24;
+
+import { getDuration } from "utils/duration";
export default {
inject: ['openmct', 'configuration'],
@@ -143,7 +142,7 @@ export default {
let description = `${startTime} - ${this.formatTime(timespan.end)}`;
if (this.timeSystem.isUTCBased && !this.openmct.time.clock()) {
- name = `${startTime} ${this.getDuration(timespan.end - timespan.start)}`;
+ name = `${startTime} ${getDuration(timespan.end - timespan.start)}`;
} else {
name = description;
}
@@ -176,41 +175,6 @@ export default {
};
});
},
- getDuration(numericDuration) {
- let result;
- let age;
-
- if (numericDuration > ONE_DAY - 1) {
- age = this.normalizeAge((numericDuration / ONE_DAY).toFixed(2));
- result = `+ ${age} day`;
-
- if (age !== 1) {
- result += 's';
- }
- } else if (numericDuration > ONE_HOUR - 1) {
- age = this.normalizeAge((numericDuration / ONE_HOUR).toFixed(2));
- result = `+ ${age} hour`;
-
- if (age !== 1) {
- result += 's';
- }
- } else {
- age = this.normalizeAge((numericDuration / ONE_MINUTE).toFixed(2));
- result = `+ ${age} min`;
-
- if (age !== 1) {
- result += 's';
- }
- }
-
- return result;
- },
- normalizeAge(num) {
- const hundredtized = num * 100;
- const isWhole = hundredtized % 100 === 0;
-
- return isWhole ? hundredtized / 100 : num;
- },
getHistoryFromLocalStorage() {
const localStorageHistory = localStorage.getItem(this.storageKey);
const history = localStorageHistory ? JSON.parse(localStorageHistory) : undefined;
diff --git a/src/plugins/timeConductor/timePopup.vue b/src/plugins/timeConductor/timePopup.vue
index 62015d691..d4ead5881 100644
--- a/src/plugins/timeConductor/timePopup.vue
+++ b/src/plugins/timeConductor/timePopup.vue
@@ -3,6 +3,8 @@
class="pr-tc-input-menu"
@keydown.enter.prevent
@keyup.enter.prevent="submit"
+ @keydown.esc.prevent
+ @keyup.esc.prevent="hide"
@click.stop
>
<div class="pr-time-label__hrs">Hrs</div>
diff --git a/src/styles/_about.scss b/src/styles/_about.scss
index 550fbbc4b..54fb15c21 100644
--- a/src/styles/_about.scss
+++ b/src/styles/_about.scss
@@ -26,6 +26,7 @@
background-repeat: no-repeat;
background-size: cover;
background-image: url('../ui/layout/assets/images/bg-splash.jpg');
+ margin-top: 30px; // Don't overlap with close "X" button
&:before,
&:after {
@@ -95,10 +96,6 @@
&--licenses {
padding: 0 10%;
.c-license {
- &__text {
- color: pushBack($overlayColorFg, 20%);
- }
-
+ .c-license {
border-top: 1px solid $colorInteriorBorder;
margin-top: 2em;
@@ -111,7 +108,7 @@
}
em {
- color: pushBack($overlayColorFg, 20%);
+ color: pushBack($colorBodyFg, 20%);
}
h1, h2, h3 {
diff --git a/src/styles/_constants-espresso.scss b/src/styles/_constants-espresso.scss
index 4d6d270dd..3ac9c0437 100644
--- a/src/styles/_constants-espresso.scss
+++ b/src/styles/_constants-espresso.scss
@@ -290,12 +290,7 @@ $colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
// Overlay
$colorOvrBlocker: rgba(black, 0.7);
-$overlayColorBg: $colorMenuBg;
-$overlayColorFg: $colorMenuFg;
-$colorOvrBtnBg: pullForward($overlayColorBg, 20%);
-$colorOvrBtnFg: #fff;
-$overlayCr: $interiorMarginLg;
-$overlayBrightnessAdjust: brightness(1.3); // Applied in a filter: property
+$overlayCr: $interiorMargin;
// Indicator colors
$colorIndicatorAvailable: $colorKey;
diff --git a/src/styles/_constants-maelstrom.scss b/src/styles/_constants-maelstrom.scss
index a3b6036e4..8564ef328 100644
--- a/src/styles/_constants-maelstrom.scss
+++ b/src/styles/_constants-maelstrom.scss
@@ -294,12 +294,7 @@ $colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
// Overlay
$colorOvrBlocker: rgba(black, 0.7);
-$overlayColorBg: $colorMenuBg;
-$overlayColorFg: $colorMenuFg;
-$colorOvrBtnBg: pullForward($overlayColorBg, 20%);
-$colorOvrBtnFg: #fff;
$overlayCr: $interiorMarginLg;
-$overlayBrightnessAdjust: brightness(1.3); // Applied in a filter: property
// Indicator colors
$colorIndicatorAvailable: $colorKey;
diff --git a/src/styles/_constants-snow.scss b/src/styles/_constants-snow.scss
index 8e104957c..14c25d697 100644
--- a/src/styles/_constants-snow.scss
+++ b/src/styles/_constants-snow.scss
@@ -290,12 +290,7 @@ $colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
// Overlay
$colorOvrBlocker: rgba(black, 0.7);
-$overlayColorBg: $colorMenuBg;
-$overlayColorFg: $colorMenuFg;
-$colorOvrBtnBg: pullForward($overlayColorBg, 40%);
-$colorOvrBtnFg: #fff;
$overlayCr: $interiorMarginLg;
-$overlayBrightnessAdjust: brightness(1); // Applied in a filter: property
// Indicator colors
$colorIndicatorAvailable: $colorKey;
diff --git a/src/styles/_constants.scss b/src/styles/_constants.scss
index 9fe2d78cd..b493ca058 100755
--- a/src/styles/_constants.scss
+++ b/src/styles/_constants.scss
@@ -40,7 +40,8 @@ $inputTextP: $inputTextPTopBtm $inputTextPLeftRight;
$menuLineH: 1.5rem;
$treeItemIndent: 16px;
$treeTypeIconW: 18px;
-$overlayOuterMarginFullscreen: 0%;
+$overlayOuterMarginFullscreen: 0;
+$overlayOuterMarginLarge: 10px;
$overlayOuterMarginDialog: 20%;
$overlayInnerMargin: 25px;
$mainViewPad: 0px;
diff --git a/src/ui/inspector/ObjectName.vue b/src/ui/inspector/ObjectName.vue
index ace020a2e..815b20512 100644
--- a/src/ui/inspector/ObjectName.vue
+++ b/src/ui/inspector/ObjectName.vue
@@ -20,7 +20,12 @@
<span class="c-object-label__type-icon"
:class="typeCssClass"
></span>
- <span class="c-object-label__name">Layout Object</span>
+ <span v-if="!activity"
+ class="c-object-label__name"
+ >Layout Object</span>
+ <span v-else
+ class="c-object-label__name"
+ >{{ activity.name }}</span>
</div>
</div>
<div v-if="multiSelect"
@@ -37,6 +42,7 @@ export default {
data() {
return {
domainObject: {},
+ activity: undefined,
keyString: undefined,
multiSelect: false,
itemsSelected: 0,
@@ -51,6 +57,10 @@ export default {
return this.openmct.types.get(this.item.type);
},
typeCssClass() {
+ if (this.activity) {
+ return 'icon-activity';
+ }
+
if (this.type.definition.cssClass === undefined) {
return 'icon-object';
}
@@ -97,7 +107,7 @@ export default {
} else {
this.multiSelect = false;
this.domainObject = selection[0][0].context.item;
-
+ this.activity = selection[0][0].context.activity;
if (this.domainObject) {
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.status = this.openmct.status.get(this.keyString);
diff --git a/src/ui/inspector/Properties.vue b/src/ui/inspector/Properties.vue
index a0bcb31a4..86fb195d8 100644
--- a/src/ui/inspector/Properties.vue
+++ b/src/ui/inspector/Properties.vue
@@ -1,5 +1,7 @@
<template>
-<div class="c-inspector__properties c-inspect-properties">
+<div v-if="!activity"
+ class="c-inspector__properties c-inspect-properties"
+>
<div class="c-inspect-properties__header">
Details
</div>
@@ -81,6 +83,7 @@ export default {
data() {
return {
domainObject: {},
+ activity: undefined,
multiSelect: false
};
},
@@ -157,6 +160,7 @@ export default {
} else {
this.multiSelect = false;
this.domainObject = selection[0][0].context.item;
+ this.activity = selection[0][0].context.activity;
}
},
formatTime(unixTime) {
diff --git a/src/utils/duration.js b/src/utils/duration.js
new file mode 100644
index 000000000..e69dd1dfb
--- /dev/null
+++ b/src/utils/duration.js
@@ -0,0 +1,85 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+const ONE_MINUTE = 60 * 1000;
+const ONE_HOUR = ONE_MINUTE * 60;
+const ONE_DAY = ONE_HOUR * 24;
+
+function normalizeAge(num) {
+ const hundredtized = num * 100;
+ const isWhole = hundredtized % 100 === 0;
+
+ return isWhole ? hundredtized / 100 : num;
+}
+
+function toDoubleDigits(num) {
+ if (num >= 10) {
+ return num;
+ } else {
+ return `0${num}`;
+ }
+}
+
+export function getDuration(numericDuration) {
+ let result;
+ let age;
+
+ if (numericDuration > ONE_DAY - 1) {
+ age = normalizeAge((numericDuration / ONE_DAY)).toFixed(2);
+ result = `+ ${age} day`;
+
+ if (age !== 1) {
+ result += 's';
+ }
+ } else if (numericDuration > ONE_HOUR - 1) {
+ age = normalizeAge((numericDuration / ONE_HOUR).toFixed(2));
+ result = `+ ${age} hour`;
+
+ if (age !== 1) {
+ result += 's';
+ }
+ } else {
+ age = normalizeAge((numericDuration / ONE_MINUTE).toFixed(2));
+ result = `+ ${age} min`;
+
+ if (age !== 1) {
+ result += 's';
+ }
+ }
+
+ return result;
+}
+
+export function getPreciseDuration(numericDuration) {
+ let result;
+
+ const days = toDoubleDigits(Math.floor((numericDuration) / (24 * 60 * 60 * 1000)));
+ let remaining = (numericDuration) % (24 * 60 * 60 * 1000);
+ const hours = toDoubleDigits(Math.floor((remaining) / (60 * 60 * 1000)));
+ remaining = (remaining) % (60 * 60 * 1000);
+ const minutes = toDoubleDigits(Math.floor((remaining) / (60 * 1000)));
+ remaining = (remaining) % (60 * 1000);
+ const seconds = toDoubleDigits(Math.floor((remaining) / (1000)));
+ result = `${days}:${hours}:${minutes}:${seconds}`;
+
+ return result;
+}