diff options
author | Shefali Joshi <simplyrender@gmail.com> | 2021-06-29 19:10:37 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-29 19:10:37 +0300 |
commit | 8422add614c0e31164bf1cfac0aaef7ae66161a1 (patch) | |
tree | cf57c0faab4546d9ed0864f0cd741778bac6e96d | |
parent | 2114697d6fdcbe0efcf9659926f2aaed5474cf3d (diff) | |
parent | e3bf72e77f602c803a68915a85633be116c88775 (diff) |
Merge branch 'master' into 1.7.41.7.4
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; +} |