mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
* feat(IndicatorAPI): accept Vue components - Adds a new property to Indicators, `component`, which is a synchronous or asynchronous Vue component. - Adds `wrapHtmlElement` utility function to create anonymous Vue components out of `HTMLElement`s (for backwards compatibility) - Refactors StatusIndicators.vue to use dynamic components, allowing us to dynamically render indicators (and keep it all within Vue's ecosystem). * refactor(indicators): use dynamic Vue components instead of `mount()` - Refactors some indicators to use Vue components directly as async components * refactor: use Vue reactivity for timestamps in clock indicator * fix(test): fix unit tests and remove some console logs * test(e2e): stabilize ladSet e2e test * test: mix in some Vue indicators in indicatorSpec * refactor: cleanup variable names * docs: update IndicatorAPI docs * fix(e2e): wait for async status bar components to load before snapshot * a11y(e2e): add aria-labels and wait for status bar to load * test(e2e): add exact: true * fix: initializing indicators * fix(typo): uhhh.. how did that get there? O_o * fix: use synchronous components for default indicators * test: clean up, remove unnecessary `nextTick()`s * test: remove more `nextTick()`s * refactor: lint:fix * fix: `on` -> `off` * test(e2e): stabilize tabs test * test(e2e): attempt to stabilize limit lines tests with `toHaveCount()` assertion
This commit is contained in:
parent
4cf63062c0
commit
114864429a
@ -260,9 +260,9 @@ async function assertLimitLinesExistAndAreVisible(page) {
|
||||
await waitForPlotsToRender(page);
|
||||
// Wait for limit lines to be created
|
||||
await page.waitForSelector('.js-limit-area', { state: 'attached' });
|
||||
const limitLineCount = await page.locator('.c-plot-limit-line').count();
|
||||
// There should be 10 limit lines created by default
|
||||
expect(await page.locator('.c-plot-limit-line').count()).toBe(10);
|
||||
await expect(page.locator('.c-plot-limit-line')).toHaveCount(10);
|
||||
const limitLineCount = await page.locator('.c-plot-limit-line').count();
|
||||
for (let i = 0; i < limitLineCount; i++) {
|
||||
await expect(page.locator('.c-plot-limit-line').nth(i)).toBeVisible();
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ test.describe('Tabs View', () => {
|
||||
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
||||
|
||||
// no canvas (i.e., sine wave generator) in the document should be visible
|
||||
await expect(page.locator('canvas')).toBeHidden();
|
||||
await expect(page.locator('canvas[id=webglContext]')).toBeHidden();
|
||||
|
||||
// select second tab
|
||||
await page.getByLabel(`${notebook.name} tab`).click();
|
||||
@ -64,7 +64,7 @@ test.describe('Tabs View', () => {
|
||||
await expect(page.locator('.c-notebook__drag-area')).toBeVisible();
|
||||
|
||||
// no canvas (i.e., sine wave generator) in the document should be visible
|
||||
await expect(page.locator('canvas')).toBeHidden();
|
||||
await expect(page.locator('canvas[id=webglContext]')).toBeHidden();
|
||||
|
||||
// select third tab
|
||||
await page.getByLabel(`${sineWaveGenerator.name} tab`).click();
|
||||
@ -83,6 +83,6 @@ test.describe('Tabs View', () => {
|
||||
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
||||
|
||||
// no canvas (i.e., sine wave generator) in the document should be visible
|
||||
await expect(page.locator('canvas')).toBeHidden();
|
||||
await expect(page.locator('canvas[id=webglContext]')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
@ -99,7 +99,7 @@ test.describe('Grand Search', () => {
|
||||
page.waitForNavigation(),
|
||||
page.getByLabel('OpenMCT Search').getByText('Clock A').click()
|
||||
]);
|
||||
await expect(page.getByRole('status', { name: 'Clock' })).toBeVisible();
|
||||
await expect(page.getByRole('status', { name: 'Clock', exact: true })).toBeVisible();
|
||||
|
||||
await grandSearchInput.fill('Disp');
|
||||
await expect(page.getByLabel('Object Search Result').first()).toContainText(
|
||||
|
@ -36,6 +36,22 @@ test.describe('Visual - Header @a11y', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Go to baseURL and Hide Tree
|
||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
||||
// Wait for status bar to load
|
||||
await expect(
|
||||
page.getByRole('status', {
|
||||
name: 'Clock Indicator'
|
||||
})
|
||||
).toBeInViewport();
|
||||
await expect(
|
||||
page.getByRole('status', {
|
||||
name: 'Global Clear Indicator'
|
||||
})
|
||||
).toBeInViewport();
|
||||
await expect(
|
||||
page.getByRole('status', {
|
||||
name: 'Snapshot Indicator'
|
||||
})
|
||||
).toBeInViewport();
|
||||
});
|
||||
|
||||
test('header sizing', async ({ page, theme }) => {
|
||||
|
@ -23,7 +23,7 @@ import percySnapshot from '@percy/playwright';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import * as utils from '../../helper/faultUtils.js';
|
||||
import { test } from '../../pluginFixtures.js';
|
||||
import { expect, test } from '../../pluginFixtures.js';
|
||||
|
||||
test.describe('Fault Management Visual Tests', () => {
|
||||
test('icon test', async ({ page, theme }) => {
|
||||
@ -32,6 +32,23 @@ test.describe('Fault Management Visual Tests', () => {
|
||||
});
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
// Wait for status bar to load
|
||||
await expect(
|
||||
page.getByRole('status', {
|
||||
name: 'Clock Indicator'
|
||||
})
|
||||
).toBeInViewport();
|
||||
await expect(
|
||||
page.getByRole('status', {
|
||||
name: 'Global Clear Indicator'
|
||||
})
|
||||
).toBeInViewport();
|
||||
await expect(
|
||||
page.getByRole('status', {
|
||||
name: 'Snapshot Indicator'
|
||||
})
|
||||
).toBeInViewport();
|
||||
|
||||
await percySnapshot(page, `Fault Management icon appears in tree (theme: '${theme}')`);
|
||||
});
|
||||
|
||||
|
@ -22,9 +22,12 @@
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
import vueWrapHtmlElement from '../../utils/vueWrapHtmlElement.js';
|
||||
import SimpleIndicator from './SimpleIndicator.js';
|
||||
|
||||
class IndicatorAPI extends EventEmitter {
|
||||
/** @type {import('../../../openmct.js').OpenMCT} */
|
||||
openmct;
|
||||
constructor(openmct) {
|
||||
super();
|
||||
|
||||
@ -42,6 +45,18 @@ class IndicatorAPI extends EventEmitter {
|
||||
return new SimpleIndicator(this.openmct);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('vue').Component} VueComponent
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Indicator
|
||||
* @property {HTMLElement} [element]
|
||||
* @property {VueComponent|Promise<VueComponent>} [vueComponent]
|
||||
* @property {string} key
|
||||
* @property {number} priority
|
||||
*/
|
||||
|
||||
/**
|
||||
* Accepts an indicator object, which is a simple object
|
||||
* with a two attributes: 'element' which has an HTMLElement
|
||||
@ -62,11 +77,20 @@ class IndicatorAPI extends EventEmitter {
|
||||
* myIndicator.text("Hello World!");
|
||||
* myIndicator.iconClass("icon-info");
|
||||
*
|
||||
* If you would like to use a Vue component, you can pass it in
|
||||
* directly as the 'vueComponent' attribute of the indicator object.
|
||||
* This accepts a Vue component or a promise that resolves to a Vue component (for asynchronous
|
||||
* rendering).
|
||||
*
|
||||
* @param {Indicator} indicator
|
||||
*/
|
||||
add(indicator) {
|
||||
if (!indicator.priority) {
|
||||
indicator.priority = this.openmct.priority.DEFAULT;
|
||||
}
|
||||
if (!indicator.vueComponent) {
|
||||
indicator.vueComponent = vueWrapHtmlElement(indicator.element);
|
||||
}
|
||||
|
||||
this.indicatorObjects.push(indicator);
|
||||
|
||||
|
@ -19,6 +19,8 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import { createOpenMct, resetApplicationState } from '../../utils/testing.js';
|
||||
import SimpleIndicator from './SimpleIndicator.js';
|
||||
|
||||
@ -33,7 +35,7 @@ describe('The Indicator API', () => {
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
function generateIndicator(className, label, priority) {
|
||||
function generateHTMLIndicator(className, label, priority) {
|
||||
const element = document.createElement('div');
|
||||
element.classList.add(className);
|
||||
const textNode = document.createTextNode(label);
|
||||
@ -46,8 +48,25 @@ describe('The Indicator API', () => {
|
||||
return testIndicator;
|
||||
}
|
||||
|
||||
it('can register an indicator', () => {
|
||||
const testIndicator = generateIndicator('test-indicator', 'This is a test indicator', 2);
|
||||
function generateVueIndicator(priority) {
|
||||
return {
|
||||
vueComponent: defineComponent({
|
||||
template: '<div class="test-indicator">This is a test indicator</div>'
|
||||
}),
|
||||
priority
|
||||
};
|
||||
}
|
||||
|
||||
it('can register an HTML indicator', () => {
|
||||
const testIndicator = generateHTMLIndicator('test-indicator', 'This is a test indicator', 2);
|
||||
openmct.indicators.add(testIndicator);
|
||||
expect(openmct.indicators.indicatorObjects).toBeDefined();
|
||||
// notifier indicator is installed by default
|
||||
expect(openmct.indicators.indicatorObjects.length).toBe(2);
|
||||
});
|
||||
|
||||
it('can register a Vue indicator', () => {
|
||||
const testIndicator = generateVueIndicator(2);
|
||||
openmct.indicators.add(testIndicator);
|
||||
expect(openmct.indicators.indicatorObjects).toBeDefined();
|
||||
// notifier indicator is installed by default
|
||||
@ -55,37 +74,40 @@ describe('The Indicator API', () => {
|
||||
});
|
||||
|
||||
it('can order indicators based on priority', () => {
|
||||
const testIndicator1 = generateIndicator(
|
||||
const testIndicator1 = generateHTMLIndicator(
|
||||
'test-indicator-1',
|
||||
'This is a test indicator',
|
||||
openmct.priority.LOW
|
||||
);
|
||||
openmct.indicators.add(testIndicator1);
|
||||
|
||||
const testIndicator2 = generateIndicator(
|
||||
const testIndicator2 = generateHTMLIndicator(
|
||||
'test-indicator-2',
|
||||
'This is another test indicator',
|
||||
openmct.priority.DEFAULT
|
||||
);
|
||||
openmct.indicators.add(testIndicator2);
|
||||
|
||||
const testIndicator3 = generateIndicator(
|
||||
const testIndicator3 = generateHTMLIndicator(
|
||||
'test-indicator-3',
|
||||
'This is yet another test indicator',
|
||||
openmct.priority.LOW
|
||||
);
|
||||
openmct.indicators.add(testIndicator3);
|
||||
|
||||
const testIndicator4 = generateIndicator(
|
||||
const testIndicator4 = generateHTMLIndicator(
|
||||
'test-indicator-4',
|
||||
'This is yet another test indicator',
|
||||
openmct.priority.HIGH
|
||||
);
|
||||
openmct.indicators.add(testIndicator4);
|
||||
|
||||
expect(openmct.indicators.indicatorObjects.length).toBe(5);
|
||||
const testIndicator5 = generateVueIndicator(openmct.priority.DEFAULT);
|
||||
openmct.indicators.add(testIndicator5);
|
||||
|
||||
expect(openmct.indicators.indicatorObjects.length).toBe(6);
|
||||
const indicatorObjectsByPriority = openmct.indicators.getIndicatorObjectsByPriority();
|
||||
expect(indicatorObjectsByPriority.length).toBe(5);
|
||||
expect(indicatorObjectsByPriority.length).toBe(6);
|
||||
expect(indicatorObjectsByPriority[2].priority).toBe(openmct.priority.DEFAULT);
|
||||
});
|
||||
|
||||
|
@ -20,7 +20,10 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<template>
|
||||
<div class="c-indicator c-indicator--clickable icon-clear-data s-status-caution">
|
||||
<div
|
||||
aria-label="Global Clear Indicator"
|
||||
class="c-indicator c-indicator--clickable icon-clear-data s-status-caution"
|
||||
>
|
||||
<span class="label c-indicator__label">
|
||||
<button @click="globalClearEmit">Clear Data</button>
|
||||
</span>
|
||||
|
@ -19,8 +19,6 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import mount from 'utils/mount';
|
||||
|
||||
import ClearDataAction from './ClearDataAction.js';
|
||||
import GlobalClearIndicator from './components/GlobalClearIndicator.vue';
|
||||
|
||||
@ -31,27 +29,10 @@ export default function plugin(appliesToObjects, options = { indicator: true })
|
||||
|
||||
return function install(openmct) {
|
||||
if (installIndicator) {
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
components: {
|
||||
GlobalClearIndicator
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
template: '<GlobalClearIndicator></GlobalClearIndicator>'
|
||||
},
|
||||
{
|
||||
app: openmct.app,
|
||||
element: document.createElement('div')
|
||||
}
|
||||
);
|
||||
|
||||
let indicator = {
|
||||
element: vNode.el,
|
||||
vueComponent: GlobalClearIndicator,
|
||||
key: 'global-clear-indicator',
|
||||
priority: openmct.priority.DEFAULT,
|
||||
destroy: destroy
|
||||
priority: openmct.priority.DEFAULT
|
||||
};
|
||||
|
||||
openmct.indicators.add(indicator);
|
||||
|
@ -21,7 +21,6 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import { createMouseEvent, createOpenMct, resetApplicationState } from 'utils/testing';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
import ClearDataPlugin from './plugin.js';
|
||||
|
||||
@ -208,12 +207,11 @@ describe('The Clear Data Plugin:', () => {
|
||||
it('installs', () => {
|
||||
const globalClearIndicator = openmct.indicators.indicatorObjects.find(
|
||||
(indicator) => indicator.key === 'global-clear-indicator'
|
||||
).element;
|
||||
).vueComponent;
|
||||
expect(globalClearIndicator).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders its major elements', async () => {
|
||||
await nextTick();
|
||||
it('renders its major elements', () => {
|
||||
const indicatorClass = appHolder.querySelector('.c-indicator');
|
||||
const iconClass = appHolder.querySelector('.icon-clear-data');
|
||||
const indicatorLabel = appHolder.querySelector('.c-indicator__label');
|
||||
@ -228,10 +226,7 @@ describe('The Clear Data Plugin:', () => {
|
||||
const indicatorLabel = appHolder.querySelector('.c-indicator__label');
|
||||
const buttonElement = indicatorLabel.querySelector('button');
|
||||
const clickEvent = createMouseEvent('click');
|
||||
openmct.objectViews.on('clearData', () => {
|
||||
// when we click the button, this event should fire
|
||||
done();
|
||||
});
|
||||
openmct.objectViews.on('clearData', done);
|
||||
buttonElement.dispatchEvent(clickEvent);
|
||||
});
|
||||
});
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
<template>
|
||||
<div
|
||||
aria-label="Clock Indicator"
|
||||
class="c-indicator t-indicator-clock icon-clock no-minify c-indicator--not-clickable"
|
||||
role="complementary"
|
||||
>
|
||||
@ -40,27 +41,32 @@ export default {
|
||||
props: {
|
||||
indicatorFormat: {
|
||||
type: String,
|
||||
required: true
|
||||
default: 'YYYY/MM/DD HH:mm:ss'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timeTextValue: this.openmct.time.getClock() ? this.openmct.time.now() : undefined
|
||||
timestamp: this.openmct.time.getClock() ? this.openmct.time.now() : undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
timeTextValue() {
|
||||
return `${moment.utc(this.timestamp).format(this.indicatorFormat)} ${
|
||||
this.openmct.time.getTimeSystem().name
|
||||
}`;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.tick = raf(this.tick);
|
||||
this.openmct.time.on('tick', this.tick);
|
||||
this.tick(this.timeTextValue);
|
||||
this.tick(this.timestamp);
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.openmct.time.off('tick', this.tick);
|
||||
},
|
||||
methods: {
|
||||
tick(timestamp) {
|
||||
this.timeTextValue = `${moment.utc(timestamp).format(this.indicatorFormat)} ${
|
||||
this.openmct.time.getTimeSystem().name
|
||||
}`;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -21,14 +21,12 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import momentTimezone from 'moment-timezone';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
import ClockViewProvider from './ClockViewProvider.js';
|
||||
import ClockIndicator from './components/ClockIndicator.vue';
|
||||
|
||||
export default function ClockPlugin(options) {
|
||||
return function install(openmct) {
|
||||
const CLOCK_INDICATOR_FORMAT = 'YYYY/MM/DD HH:mm:ss';
|
||||
openmct.types.addType('clock', {
|
||||
name: 'Clock',
|
||||
description:
|
||||
@ -92,31 +90,9 @@ export default function ClockPlugin(options) {
|
||||
});
|
||||
openmct.objectViews.addProvider(new ClockViewProvider(openmct));
|
||||
|
||||
if (options && options.enableClockIndicator === true) {
|
||||
const element = document.createElement('div');
|
||||
|
||||
const { vNode } = mount(
|
||||
{
|
||||
components: {
|
||||
ClockIndicator
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
indicatorFormat: CLOCK_INDICATOR_FORMAT
|
||||
};
|
||||
},
|
||||
template: '<ClockIndicator :indicator-format="indicatorFormat" />'
|
||||
},
|
||||
{
|
||||
app: openmct.app,
|
||||
element
|
||||
}
|
||||
);
|
||||
if (options?.enableClockIndicator === true) {
|
||||
const indicator = {
|
||||
element: vNode.el,
|
||||
vueComponent: ClockIndicator,
|
||||
key: 'clock-indicator',
|
||||
priority: openmct.priority.LOW
|
||||
};
|
||||
|
@ -195,10 +195,6 @@ describe('Clock plugin:', () => {
|
||||
let clockIndicator;
|
||||
|
||||
afterEach(() => {
|
||||
if (clockIndicator) {
|
||||
clockIndicator.remove();
|
||||
}
|
||||
|
||||
clockIndicator = undefined;
|
||||
if (appHolder) {
|
||||
appHolder.remove();
|
||||
@ -223,7 +219,7 @@ describe('Clock plugin:', () => {
|
||||
|
||||
clockIndicator = openmct.indicators.indicatorObjects.find(
|
||||
(indicator) => indicator.key === 'clock-indicator'
|
||||
).element;
|
||||
).vueComponent;
|
||||
|
||||
const hasClockIndicator = clockIndicator !== null && clockIndicator !== undefined;
|
||||
expect(hasClockIndicator).toBe(true);
|
||||
@ -231,14 +227,16 @@ describe('Clock plugin:', () => {
|
||||
|
||||
it('contains text', async () => {
|
||||
await setupClock(true);
|
||||
|
||||
await nextTick();
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
|
||||
clockIndicator = openmct.indicators.indicatorObjects.find(
|
||||
(indicator) => indicator.key === 'clock-indicator'
|
||||
).element;
|
||||
const clockIndicatorText = clockIndicator.textContent.trim();
|
||||
).vueComponent;
|
||||
const hasClockIndicator = clockIndicator !== null && clockIndicator !== undefined;
|
||||
expect(hasClockIndicator).toBe(true);
|
||||
const clockIndicatorText = appHolder
|
||||
.querySelector('.t-indicator-clock .c-indicator__label')
|
||||
.textContent.trim();
|
||||
const textIncludesUTC = clockIndicatorText.includes('UTC');
|
||||
|
||||
expect(textIncludesUTC).toBe(true);
|
||||
|
@ -106,7 +106,6 @@ describe('the plugin', function () {
|
||||
flexibleView.show(child, false);
|
||||
|
||||
await nextTick();
|
||||
console.log(child);
|
||||
const flexTitle = child.querySelector('.c-fl');
|
||||
|
||||
expect(flexTitle).not.toBeNull();
|
||||
|
@ -48,11 +48,12 @@
|
||||
import mount from 'utils/mount';
|
||||
|
||||
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants.js';
|
||||
import { getSnapshotContainer } from '../plugin.js';
|
||||
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container.js';
|
||||
import SnapshotContainerComponent from './NotebookSnapshotContainer.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'snapshotContainer'],
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
@ -62,6 +63,9 @@ export default {
|
||||
flashIndicator: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.snapshotContainer = getSnapshotContainer(this.openmct);
|
||||
},
|
||||
mounted() {
|
||||
this.snapshotContainer.on(EVENT_SNAPSHOTS_UPDATED, this.snapshotsUpdated);
|
||||
this.updateSnapshotIndicatorTitle();
|
||||
|
@ -20,8 +20,6 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import mount from 'utils/mount';
|
||||
|
||||
import { notebookImageMigration } from '../notebook/utils/notebook-migration.js';
|
||||
import CopyToNotebookAction from './actions/CopyToNotebookAction.js';
|
||||
import ExportNotebookAsTextAction from './actions/ExportNotebookAsTextAction.js';
|
||||
@ -39,7 +37,7 @@ import NotebookViewProvider from './NotebookViewProvider.js';
|
||||
import SnapshotContainer from './snapshot-container.js';
|
||||
|
||||
let notebookSnapshotContainer;
|
||||
function getSnapshotContainer(openmct) {
|
||||
export function getSnapshotContainer(openmct) {
|
||||
if (!notebookSnapshotContainer) {
|
||||
notebookSnapshotContainer = new SnapshotContainer(openmct);
|
||||
}
|
||||
@ -66,7 +64,6 @@ function installBaseNotebookFunctionality(openmct) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshotContainer = getSnapshotContainer(openmct);
|
||||
const notebookSnapshotImageType = {
|
||||
name: 'Notebook Snapshot Image Storage',
|
||||
description: 'Notebook Snapshot Image Storage object',
|
||||
@ -82,27 +79,10 @@ function installBaseNotebookFunctionality(openmct) {
|
||||
openmct.actions.register(new CopyToNotebookAction(openmct));
|
||||
openmct.actions.register(new ExportNotebookAsTextAction(openmct));
|
||||
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
components: {
|
||||
NotebookSnapshotIndicator
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
snapshotContainer
|
||||
},
|
||||
template: '<NotebookSnapshotIndicator></NotebookSnapshotIndicator>'
|
||||
},
|
||||
{
|
||||
app: openmct.app
|
||||
}
|
||||
);
|
||||
|
||||
const indicator = {
|
||||
element: vNode.el,
|
||||
vueComponent: NotebookSnapshotIndicator,
|
||||
key: 'notebook-snapshot-indicator',
|
||||
priority: openmct.priority.DEFAULT,
|
||||
destroy: destroy
|
||||
priority: openmct.priority.DEFAULT
|
||||
};
|
||||
|
||||
openmct.indicators.add(indicator);
|
||||
|
@ -336,24 +336,23 @@ describe('Notebook plugin:', () => {
|
||||
let snapshotIndicator;
|
||||
let drawerElement;
|
||||
|
||||
function clickSnapshotIndicator() {
|
||||
const indicator = element.querySelector('.icon-camera');
|
||||
const button = indicator.querySelector('button');
|
||||
async function clickSnapshotIndicator() {
|
||||
const button =
|
||||
appHolder.querySelector('[aria-label="Show Snapshots"]') ??
|
||||
appHolder.querySelector('[aria-label="Hide Snapshots"]');
|
||||
const clickEvent = createMouseEvent('click');
|
||||
|
||||
button.dispatchEvent(clickEvent);
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
snapshotIndicator = openmct.indicators.indicatorObjects.find(
|
||||
(indicator) => indicator.key === 'notebook-snapshot-indicator'
|
||||
).element;
|
||||
).vueComponent;
|
||||
|
||||
element.append(snapshotIndicator);
|
||||
|
||||
return nextTick().then(() => {
|
||||
drawerElement = document.querySelector('.l-shell__drawer');
|
||||
});
|
||||
await nextTick();
|
||||
drawerElement = document.querySelector('.l-shell__drawer');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -361,7 +360,6 @@ describe('Notebook plugin:', () => {
|
||||
drawerElement.classList.remove('is-expanded');
|
||||
}
|
||||
|
||||
snapshotIndicator.remove();
|
||||
snapshotIndicator = undefined;
|
||||
|
||||
if (drawerElement) {
|
||||
@ -375,11 +373,11 @@ describe('Notebook plugin:', () => {
|
||||
expect(hasSnapshotIndicator).toBe(true);
|
||||
});
|
||||
|
||||
it('snapshots container has class isExpanded', () => {
|
||||
it('snapshots container has class isExpanded', async () => {
|
||||
let classes = drawerElement.classList;
|
||||
const isExpandedBefore = classes.contains('is-expanded');
|
||||
|
||||
clickSnapshotIndicator();
|
||||
await clickSnapshotIndicator();
|
||||
classes = drawerElement.classList;
|
||||
const isExpandedAfterFirstClick = classes.contains('is-expanded');
|
||||
|
||||
@ -387,15 +385,15 @@ describe('Notebook plugin:', () => {
|
||||
expect(isExpandedAfterFirstClick).toBeTrue();
|
||||
});
|
||||
|
||||
it('snapshots container does not have class isExpanded', () => {
|
||||
it('snapshots container does not have class isExpanded', async () => {
|
||||
let classes = drawerElement.classList;
|
||||
const isExpandedBefore = classes.contains('is-expanded');
|
||||
|
||||
clickSnapshotIndicator();
|
||||
await clickSnapshotIndicator();
|
||||
classes = drawerElement.classList;
|
||||
const isExpandedAfterFirstClick = classes.contains('is-expanded');
|
||||
|
||||
clickSnapshotIndicator();
|
||||
await clickSnapshotIndicator();
|
||||
classes = drawerElement.classList;
|
||||
const isExpandedAfterSecondClick = classes.contains('is-expanded');
|
||||
|
||||
@ -404,8 +402,8 @@ describe('Notebook plugin:', () => {
|
||||
expect(isExpandedAfterSecondClick).toBeFalse();
|
||||
});
|
||||
|
||||
it('show notebook snapshots container text', () => {
|
||||
clickSnapshotIndicator();
|
||||
it('show notebook snapshots container text', async () => {
|
||||
await clickSnapshotIndicator();
|
||||
|
||||
const notebookSnapshots = drawerElement.querySelector('.l-browse-bar__object-name');
|
||||
const snapshotsText = notebookSnapshots.textContent.trim();
|
||||
|
@ -72,8 +72,8 @@ export default {
|
||||
this.openmct.notifications.on('dismiss-all', this.updateNotifications);
|
||||
},
|
||||
unmounted() {
|
||||
this.openmct.notifications.of('notification', this.updateNotifications);
|
||||
this.openmct.notifications.of('dismiss-all', this.updateNotifications);
|
||||
this.openmct.notifications.off('notification', this.updateNotifications);
|
||||
this.openmct.notifications.off('dismiss-all', this.updateNotifications);
|
||||
},
|
||||
methods: {
|
||||
dismissAllNotifications() {
|
||||
|
@ -19,32 +19,14 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import mount from 'utils/mount';
|
||||
|
||||
import NotificationIndicator from './components/NotificationIndicator.vue';
|
||||
|
||||
export default function plugin() {
|
||||
return function install(openmct) {
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
components: {
|
||||
NotificationIndicator
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
template: '<NotificationIndicator></NotificationIndicator>'
|
||||
},
|
||||
{
|
||||
app: openmct.app
|
||||
}
|
||||
);
|
||||
|
||||
let indicator = {
|
||||
key: 'notifications-indicator',
|
||||
element: vNode.el,
|
||||
priority: openmct.priority.DEFAULT,
|
||||
destroy: destroy
|
||||
vueComponent: NotificationIndicator,
|
||||
priority: openmct.priority.DEFAULT
|
||||
};
|
||||
openmct.indicators.add(indicator);
|
||||
};
|
||||
|
@ -37,7 +37,7 @@
|
||||
>
|
||||
<CreateButton class="l-shell__create-button" />
|
||||
<GrandSearch ref="grand-search" />
|
||||
<StatusIndicators class="l-shell__head-section l-shell__indicators" />
|
||||
<StatusIndicators />
|
||||
<button
|
||||
class="l-shell__head__collapse-button c-icon-button"
|
||||
:class="
|
||||
|
@ -17,24 +17,43 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<template>
|
||||
<div></div>
|
||||
<div class="l-shell__head-section l-shell__indicators">
|
||||
<component
|
||||
:is="indicator.value.vueComponent"
|
||||
v-for="indicator in sortedIndicators"
|
||||
:key="indicator.value.key"
|
||||
role="status"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { shallowRef } from 'vue';
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
return {
|
||||
indicators: this.openmct.indicators.getIndicatorObjectsByPriority().map(shallowRef)
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
sortedIndicators() {
|
||||
if (this.indicators.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [...this.indicators].sort((a, b) => b.value.priority - a.value.priority);
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.openmct.indicators.off('addIndicator', this.addIndicator);
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.indicators.getIndicatorObjectsByPriority().forEach(this.addIndicator);
|
||||
|
||||
created() {
|
||||
this.openmct.indicators.on('addIndicator', this.addIndicator);
|
||||
},
|
||||
methods: {
|
||||
addIndicator(indicator) {
|
||||
this.$el.appendChild(indicator.element);
|
||||
this.indicators.push(shallowRef(indicator));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
24
src/utils/vueWrapHtmlElement.js
Normal file
24
src/utils/vueWrapHtmlElement.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { defineComponent, h, onMounted, ref } from 'vue';
|
||||
|
||||
/**
|
||||
* Compatibility wrapper for wrapping an HTMLElement in a Vue component.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @returns {import('vue').Component}
|
||||
*/
|
||||
export default function vueWrapHtmlElement(element) {
|
||||
return defineComponent({
|
||||
setup() {
|
||||
const wrapper = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
if (wrapper.value) {
|
||||
wrapper.value.appendChild(element);
|
||||
}
|
||||
});
|
||||
|
||||
// Render function returning the wrapper div
|
||||
return () => h('div', { ref: wrapper });
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user