Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tooltips] Add tooltips on hover #6756

Merged
merged 23 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
90ee655
Add tooltip api, extend object api to add telemetry composition looku…
khalidadil Jun 22, 2023
37f5c1c
Merge branch 'release/2.2.5' into feature/issue-6656
khalidadil Jun 22, 2023
78c293b
Add tooltips to telemetry/lad tables
khalidadil Jun 23, 2023
b133fe4
Clean up docs
khalidadil Jun 23, 2023
a9d4ead
Merge branch 'master' into feature/issue-6656
khalidadil Jun 23, 2023
92b7f99
Closes #6656
charlesh88 Jul 6, 2023
aa3d987
Add tooltips for Conditional widgets and Tab Views and tests
khalidadil Jul 13, 2023
1eaea39
Add tests
khalidadil Jul 13, 2023
1f165fb
Linting
khalidadil Jul 13, 2023
7830064
Merge branch 'master' into feature/issue-6656
khalidadil Jul 13, 2023
16ab9c4
Fix bad merge
khalidadil Jul 13, 2023
57d4120
Comment out test for telemetry tables
khalidadil Jul 13, 2023
41759c4
Linting
khalidadil Jul 13, 2023
b7b8356
Merge branch 'master' into feature/issue-6656
khalidadil Jul 13, 2023
a9ab584
Switch to using enum-ish consts for tooltip locations
khalidadil Jul 13, 2023
07ff673
Cleanup
khalidadil Jul 13, 2023
c2436c7
Trim LAD table row name to account for spacing required by linting rules
khalidadil Jul 13, 2023
83d81b8
Move tooltips tests to e2e
khalidadil Jul 13, 2023
6dfebb3
Fix comment
khalidadil Jul 13, 2023
fa278af
Switch from indexOf to includes
khalidadil Jul 14, 2023
c66fc84
Cleanup appActions test safety check since we're removing pointer-eve…
khalidadil Jul 14, 2023
86a2384
Rename tooltip mixin from getTelemetryPath to getTelemetryPathString
khalidadil Jul 14, 2023
aa699cb
Fixing JSDoc for tooltip API
khalidadil Jul 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions openmct.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ if (document.currentScript) {
* @property {import('./src/api/notifications/NotificationAPI').default} notifications
* @property {import('./src/api/Editor').default} editor
* @property {import('./src/api/overlays/OverlayAPI')} overlays
* @property {import('./src/api/tooltips/ToolTipAPI')} tooltips
* @property {import('./src/api/menu/MenuAPI').default} menus
* @property {import('./src/api/actions/ActionsAPI').default} actions
* @property {import('./src/api/status/StatusAPI').default} status
Expand Down
4 changes: 4 additions & 0 deletions src/MCT.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ define([
'EventEmitter',
'./api/api',
'./api/overlays/OverlayAPI',
'./api/tooltips/ToolTipAPI',
'./selection/Selection',
'./plugins/plugins',
'./ui/registries/ViewRegistry',
Expand All @@ -48,6 +49,7 @@ define([
EventEmitter,
api,
OverlayAPI,
ToolTipAPI,
Selection,
plugins,
ViewRegistry,
Expand Down Expand Up @@ -220,6 +222,8 @@ define([

['overlays', () => new OverlayAPI.default()],

['tooltips', () => new ToolTipAPI.default()],

['menus', () => new api.MenuAPI(this)],

['actions', () => new api.ActionsAPI(this)],
Expand Down
34 changes: 34 additions & 0 deletions src/api/objects/ObjectAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,40 @@
.join('/');
}

/**
* Return path of telemetry objects in the object composition
* @param {object} identifier the identifier for the domain object to query for
* @param {object} [telemetryIdentifier] the specific identifier for the telemetry
* to look for in the composition, uses first object in composition otherwise
* @returns {Array} path of telemetry object in object composition
*/
async getTelemetryPath(identifier, telemetryIdentifier) {
const objectDetails = await this.get(identifier);
const telemetryPath = [];
if (objectDetails.composition && ['folder'].indexOf(objectDetails.type) === -1) {
khalidadil marked this conversation as resolved.
Show resolved Hide resolved
let sourceTelemetry = objectDetails.composition[0];
if (telemetryIdentifier) {
sourceTelemetry = objectDetails.composition.find(

Check warning on line 556 in src/api/objects/ObjectAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/objects/ObjectAPI.js#L551-L556

Added lines #L551 - L556 were not covered by tests
(telemetrySource) =>
this.makeKeyString(telemetrySource) === this.makeKeyString(telemetryIdentifier)

Check warning on line 558 in src/api/objects/ObjectAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/objects/ObjectAPI.js#L558

Added line #L558 was not covered by tests
);
}
const compositionElement = await this.get(sourceTelemetry);
if (!['yamcs.telemetry', 'generator'].includes(compositionElement.type)) {
return telemetryPath;

Check warning on line 563 in src/api/objects/ObjectAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/objects/ObjectAPI.js#L561-L563

Added lines #L561 - L563 were not covered by tests
}
const telemetryKey = compositionElement.identifier.key;
const telemetryPathObjects = await this.getOriginalPath(telemetryKey);
telemetryPathObjects.forEach((pathObject) => {
if (pathObject.type === 'root') {
return;

Check warning on line 569 in src/api/objects/ObjectAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/objects/ObjectAPI.js#L565-L569

Added lines #L565 - L569 were not covered by tests
}
telemetryPath.unshift(pathObject.name);

Check warning on line 571 in src/api/objects/ObjectAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/objects/ObjectAPI.js#L571

Added line #L571 was not covered by tests
});
}
return telemetryPath;

Check warning on line 574 in src/api/objects/ObjectAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/objects/ObjectAPI.js#L574

Added line #L574 was not covered by tests
}

/**
* Modify a domain object. Internal to ObjectAPI, won't call save after.
* @private
Expand Down
51 changes: 51 additions & 0 deletions src/api/tooltips/ToolTip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import TooltipComponent from './components/TooltipComponent.vue';
import EventEmitter from 'EventEmitter';
import Vue from 'vue';

class Tooltip extends EventEmitter {
constructor(
{ toolTipText, toolTipLocation, parentElement } = {
tooltipText: '',
toolTipLocation: 'below',
parentElement: null
}
) {
super();

Check warning on line 13 in src/api/tooltips/ToolTip.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTip.js#L13

Added line #L13 was not covered by tests

this.container = document.createElement('div');

Check warning on line 15 in src/api/tooltips/ToolTip.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTip.js#L15

Added line #L15 was not covered by tests

this.component = new Vue({

Check warning on line 17 in src/api/tooltips/ToolTip.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTip.js#L17

Added line #L17 was not covered by tests
components: {
TooltipComponent: TooltipComponent
},
provide: {
toolTipText,
toolTipLocation,
parentElement
},
template: '<tooltip-component toolTipText="toolTipText"></tooltip-component>'
});

this.isActive = null;

Check warning on line 29 in src/api/tooltips/ToolTip.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTip.js#L29

Added line #L29 was not covered by tests
}

destroy() {
if (!this.isActive) {
return;

Check warning on line 34 in src/api/tooltips/ToolTip.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTip.js#L33-L34

Added lines #L33 - L34 were not covered by tests
}
document.body.removeChild(this.container);
this.component.$destroy();
this.isActive = false;

Check warning on line 38 in src/api/tooltips/ToolTip.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTip.js#L36-L38

Added lines #L36 - L38 were not covered by tests
}

/**
* @private
**/
show() {
document.body.appendChild(this.container);
this.container.appendChild(this.component.$mount().$el);
this.isActive = true;

Check warning on line 47 in src/api/tooltips/ToolTip.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTip.js#L45-L47

Added lines #L45 - L47 were not covered by tests
}
}

export default Tooltip;
46 changes: 46 additions & 0 deletions src/api/tooltips/ToolTipAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Tooltip from './ToolTip';

/**
* The TooltipAPI is responsible for adding custom tooltips to
* the desired elements on the screen
*
* @memberof api/tooltips
* @constructor
*/

class TooltipAPI {
constructor() {
this.activeToolTips = [];
}

/**
* private
khalidadil marked this conversation as resolved.
Show resolved Hide resolved
*/
showTooltip(tooltip) {
for (let i = this.activeToolTips.length - 1; i > -1; i--) {
this.activeToolTips[i].destroy();
this.activeToolTips.splice(i, 1);

Check warning on line 22 in src/api/tooltips/ToolTipAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTipAPI.js#L20-L22

Added lines #L20 - L22 were not covered by tests
}
this.activeToolTips.push(tooltip);

Check warning on line 24 in src/api/tooltips/ToolTipAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTipAPI.js#L24

Added line #L24 was not covered by tests

tooltip.show();

Check warning on line 26 in src/api/tooltips/ToolTipAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTipAPI.js#L26

Added line #L26 was not covered by tests
}

/**
* A description of option properties that can be passed into the overlay
khalidadil marked this conversation as resolved.
Show resolved Hide resolved
* @typedef options
* @property {string} tooltipText text to show in the tooltip
* @property {string} tooltipLocation location to show the tooltip relative to the parentElement
khalidadil marked this conversation as resolved.
Show resolved Hide resolved
* (above, below, right, left, center)
* @property {HTMLElement} parentElement reference to the DOM node we're adding the tooltip to
*/
tooltip(options) {
let tooltip = new Tooltip(options);

Check warning on line 38 in src/api/tooltips/ToolTipAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTipAPI.js#L38

Added line #L38 was not covered by tests

this.showTooltip(tooltip);

Check warning on line 40 in src/api/tooltips/ToolTipAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTipAPI.js#L40

Added line #L40 was not covered by tests

return tooltip;

Check warning on line 42 in src/api/tooltips/ToolTipAPI.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/ToolTipAPI.js#L42

Added line #L42 was not covered by tests
}
}

export default TooltipAPI;
61 changes: 61 additions & 0 deletions src/api/tooltips/components/TooltipComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!--
Open MCT, Copyright (c) 2014-2023, 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="tooltip-wrapper" class="l-tooltip-wrapper" :style="toolTipLocationStyle">
<div class="tooltip">
{{ toolTipText }}
</div>
</div>
</template>

<script>
export default {
inject: ['toolTipText', 'toolTipLocation', 'parentElement'],
computed: {
toolTipCoordinates() {
return this.parentElement.getBoundingClientRect();
},
toolTipLocationStyle() {
const { top, left, height, width } = this.toolTipCoordinates;
let toolTipLocationStyle = {};

if (this.toolTipLocation === 'above') {
toolTipLocationStyle = { top: `${top - 5}px`, left: `${left}px` };
}
if (this.toolTipLocation === 'below') {
toolTipLocationStyle = { top: `${top + height}px`, left: `${left}px` };
}
if (this.toolTipLocation === 'right') {
toolTipLocationStyle = { top: `${top}px`, left: `${left + width}px` };
}
if (this.toolTipLocation === 'left') {
toolTipLocationStyle = { top: `${top}px`, left: `${left - width}px` };
}
if (this.toolTipLocation === 'center') {
toolTipLocationStyle = { top: `${top + height / 2}px`, left: `${left + width / 2}px` };
}

return toolTipLocationStyle;
}
}
};
</script>
12 changes: 12 additions & 0 deletions src/api/tooltips/components/tooltip-component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.l-tooltip-wrapper {
@include themedButton();
background: $colorBodyBg;
color: $colorBodyFg;
border: 2px solid $colorInteriorBorder;
display: inline-block;
max-width: 200px;
border-radius: 3px;
position: absolute;
padding: 5px 8px;
z-index: 60;
}
46 changes: 46 additions & 0 deletions src/api/tooltips/tooltipMixins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const tooltipHelpers = {
methods: {
async getTelemetryPath(telemetryIdentifier) {
khalidadil marked this conversation as resolved.
Show resolved Hide resolved
let telemetryPathString = '';
if (!this.domainObject?.identifier) {
return;

Check warning on line 6 in src/api/tooltips/tooltipMixins.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/tooltipMixins.js#L4-L6

Added lines #L4 - L6 were not covered by tests
}
const telemetryPath = await this.openmct.objects.getTelemetryPath(

Check warning on line 8 in src/api/tooltips/tooltipMixins.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/tooltipMixins.js#L8

Added line #L8 was not covered by tests
this.domainObject.identifier,
telemetryIdentifier
);
if (telemetryPath.length) {
telemetryPathString = telemetryPath.join(' / ');

Check warning on line 13 in src/api/tooltips/tooltipMixins.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/tooltipMixins.js#L12-L13

Added lines #L12 - L13 were not covered by tests
}
return telemetryPathString;

Check warning on line 15 in src/api/tooltips/tooltipMixins.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/tooltipMixins.js#L15

Added line #L15 was not covered by tests
},
async getObjectPath(objectIdentifier) {
if (!objectIdentifier && !this.domainObject) {
return;

Check warning on line 19 in src/api/tooltips/tooltipMixins.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/tooltipMixins.js#L18-L19

Added lines #L18 - L19 were not covered by tests
}
const domainObjectIdentifier = objectIdentifier || this.domainObject.identifier;
const objectPathList = await this.openmct.objects.getOriginalPath(domainObjectIdentifier);
objectPathList.pop();
return objectPathList
.map((pathItem) => pathItem.name)

Check warning on line 25 in src/api/tooltips/tooltipMixins.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/tooltipMixins.js#L21-L25

Added lines #L21 - L25 were not covered by tests
.reverse()
.join(' / ');
},
buildToolTip(tooltipText, tooltipLocation, elementRef) {
if (!tooltipText || tooltipText.length < 1) {
return;

Check warning on line 31 in src/api/tooltips/tooltipMixins.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/tooltipMixins.js#L30-L31

Added lines #L30 - L31 were not covered by tests
}
this.tooltip = this.openmct.tooltips.tooltip({

Check warning on line 33 in src/api/tooltips/tooltipMixins.js

View check run for this annotation

Codecov / codecov/patch

src/api/tooltips/tooltipMixins.js#L33

Added line #L33 was not covered by tests
toolTipText: tooltipText,
toolTipLocation: tooltipLocation,
parentElement: this.$refs[elementRef]
});
},
hideToolTip() {
this.tooltip?.destroy();
this.tooltip = null;
}
}
};

export default tooltipHelpers;
14 changes: 13 additions & 1 deletion src/plugins/LADTable/components/LADRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@
@click="clickedRow"
@contextmenu.prevent="showContextMenu"
>
<td class="js-first-data">{{ domainObject.name }}</td>
<td
ref="tableCell"
class="js-first-data"
@mouseover.ctrl="showToolTip"
@mouseleave="hideToolTip"
>
{{ domainObject.name }}
ozyx marked this conversation as resolved.
Show resolved Hide resolved
</td>
<td v-if="showTimestamp" class="js-second-data">{{ formattedTimestamp }}</td>
<td class="js-third-data" :class="valueClasses">{{ value }}</td>
<td v-if="hasUnits" class="js-units">
Expand All @@ -42,8 +49,10 @@ const BLANK_VALUE = '---';

import identifierToString from '/src/tools/url';
import PreviewAction from '@/ui/preview/PreviewAction.js';
import tooltipHelpers from '../../../api/tooltips/tooltipMixins';

export default {
mixins: [tooltipHelpers],
inject: ['openmct', 'currentView'],
props: {
domainObject: {
Expand Down Expand Up @@ -259,6 +268,9 @@ export default {
return metadata
.values()
.find((metadatum) => metadatum.hints.domain === undefined && metadatum.key !== 'name');
},
async showToolTip() {
this.buildToolTip(await this.getObjectPath(), 'below', 'tableCell');
}
}
};
Expand Down
1 change: 0 additions & 1 deletion src/plugins/displayLayout/components/SubobjectView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
<layout-frame
:item="item"
:grid-size="gridSize"
:title="domainObject && domainObject.name"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
Expand Down
9 changes: 8 additions & 1 deletion src/plugins/displayLayout/components/TelemetryView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@
>
<div
v-if="domainObject"
ref="telemetryViewWrapper"
class="c-telemetry-view u-style-receiver"
:class="[itemClasses]"
:style="styleObject"
:data-font-size="item.fontSize"
:data-font="item.font"
@contextmenu.prevent="showContextMenu"
@mouseover.ctrl="showToolTip"
@mouseleave="hideToolTip"
>
<div class="is-status__indicator" :title="`This item is ${status}`"></div>
<div v-if="showLabel" class="c-telemetry-view__label">
Expand Down Expand Up @@ -69,6 +72,7 @@ import {
getDefaultNotebook,
getNotebookSectionAndPage
} from '@/plugins/notebook/utils/notebook-storage.js';
import tooltipHelpers from '../../../api/tooltips/tooltipMixins';

const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
const DEFAULT_POSITION = [1, 1];
Expand Down Expand Up @@ -97,7 +101,7 @@ export default {
components: {
LayoutFrame
},
mixins: [conditionalStylesMixin, stalenessMixin],
mixins: [conditionalStylesMixin, stalenessMixin, tooltipHelpers],
inject: ['openmct', 'objectPath', 'currentView'],
props: {
item: {
Expand Down Expand Up @@ -379,6 +383,9 @@ export default {
},
setStatus(status) {
this.status = status;
},
async showToolTip() {
this.buildToolTip(await this.getObjectPath(), 'below', 'telemetryViewWrapper');
}
}
};
Expand Down