test(e2e): stabilize flaky imagery tests (#7765)

This commit is contained in:
Jesse Mazzella 2024-07-23 20:41:07 -07:00 committed by GitHub
parent 1fae0a6ad5
commit 689f7cc815
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
77 changed files with 539 additions and 484 deletions

View File

@ -39,6 +39,7 @@ const config = {
'vue/no-deprecated-events-api': 'warn',
'vue/no-v-for-template-key': 'off',
'vue/no-v-for-template-key-on-child': 'error',
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
'prettier/prettier': 'error',
'you-dont-need-lodash-underscore/omit': 'off',
'you-dont-need-lodash-underscore/throttle': 'off',

View File

@ -39,7 +39,7 @@ export default merge(common, {
return shouldWrite;
}
},
watchFiles: ['**/*.css'],
watchFiles: ['src/**/*.css', 'example/**/*.css'],
static: {
directory: fileURLToPath(new URL('../dist', import.meta.url)),
publicPath: '/dist',

View File

@ -78,13 +78,13 @@ async function createDomainObjectWithDefaults(
// Navigate to the parent object. This is necessary to create the object
// in the correct location, such as a folder, layout, or plot.
await page.goto(`${parentUrl}`);
await page.goto(parentUrl);
//Click the Create button
await page.getByRole('button', { name: 'Create' }).click();
// Click the Create button
await page.getByRole('button', { name: 'Create', exact: true }).click();
// Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("${type}")`);
// Click the object specified by 'type'-- case insensitive
await page.getByRole('menuitem', { name: new RegExp(`^${type}$`, 'i') }).click();
// Modify the name input field of the domain object to accept 'name'
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
@ -353,7 +353,7 @@ async function getFocusedObjectUuid(page) {
* @returns {Promise<string>} the url of the object
*/
async function getHashUrlToDomainObject(page, identifier) {
await page.waitForLoadState('load');
await page.waitForLoadState('domcontentloaded');
const hashUrl = await page.evaluate(async (objectIdentifier) => {
const path = await window.openmct.objects.getOriginalPath(objectIdentifier);
let url =

View File

@ -25,14 +25,21 @@ This test suite is dedicated to tests which verify the basic operations surround
but only assume that example imagery is present.
*/
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../../../appActions.js';
import { waitForAnimations } from '../../../../baseFixtures.js';
import {
createDomainObjectWithDefaults,
navigateToObjectWithRealTime,
setRealTimeMode
} from '../../../../appActions.js';
import { MISSION_TIME } from '../../../../constants.js';
import { expect, test } from '../../../../pluginFixtures.js';
const backgroundImageSelector = '.c-imagery__main-image__background-image';
const panHotkey = process.platform === 'linux' ? ['Shift', 'Alt'] : ['Alt'];
const tagHotkey = ['Shift', 'Alt'];
const expectedAltText = process.platform === 'linux' ? 'Shift+Alt drag to pan' : 'Alt drag to pan';
const thumbnailUrlParamsRegexp = /\?w=100&h=100/;
const IMAGE_LOAD_DELAY = 5 * 1000;
const MOUSE_WHEEL_DELTA_Y = 120;
const FIVE_MINUTES = 1000 * 60 * 5;
const THIRTY_SECONDS = 1000 * 30;
//The following block of tests verifies the basic functionality of example imagery and serves as a template for Imagery objects embedded in other objects.
test.describe('Example Imagery Object', () => {
@ -45,8 +52,7 @@ test.describe('Example Imagery Object', () => {
// Verify that the created object is focused
await expect(page.locator('.l-browse-bar__object-name')).toContainText(exampleImagery.name);
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
await page.locator(backgroundImageSelector).waitFor();
await page.getByLabel('Focused Image Element').hover({ trial: true });
});
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
@ -63,7 +69,7 @@ test.describe('Example Imagery Object', () => {
test('Can right click on image and open it in a new tab @2p', async ({ page, context }) => {
// try to right click on image
const backgroundImage = await page.locator(backgroundImageSelector);
const backgroundImage = page.getByLabel('Focused Image Element');
await backgroundImage.click({
button: 'right',
// eslint-disable-next-line playwright/no-force-option
@ -80,7 +86,7 @@ test.describe('Example Imagery Object', () => {
const newPage = await pagePromise;
await newPage.waitForLoadState();
// expect new tab url to have jpg in it
await expect(newPage.url()).toContain('.jpg');
expect(newPage.url()).toContain('.jpg');
});
// this requires CORS to be enabled in some fashion
@ -105,27 +111,36 @@ test.describe('Example Imagery Object', () => {
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6821'
});
// Test independent fixed time with global fixed time
// flip on independent time conductor
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
await page.getByLabel('Enable Independent Time Conductor').click();
// Adding in delay to address flakiness of ITC test-- button event handlers not registering in time
await expect(page.locator('#independentTCToggle')).toBeChecked();
await expect(page.locator('.c-compact-tc').first()).toBeVisible();
await expect(
page.getByRole('button', { name: 'Independent Time Conductor Settings' })
).toBeEnabled();
await page.getByRole('button', { name: 'Independent Time Conductor Settings' }).click();
await expect(page.getByLabel('Time Conductor Options')).toBeVisible();
await page.getByLabel('Time Conductor Options').hover({ trial: true });
await page.getByRole('textbox', { name: 'Start date' }).hover({ trial: true });
await page.getByRole('textbox', { name: 'Start date' }).fill('2021-12-30');
await page.keyboard.press('Tab');
await page.getByRole('textbox', { name: 'Start time' }).hover({ trial: true });
await page.getByRole('textbox', { name: 'Start time' }).fill('01:01:00');
await page.keyboard.press('Tab');
await page.getByRole('textbox', { name: 'End date' }).hover({ trial: true });
await page.getByRole('textbox', { name: 'End date' }).fill('2021-12-30');
await page.keyboard.press('Tab');
await page.getByRole('textbox', { name: 'End time' }).hover({ trial: true });
await page.getByRole('textbox', { name: 'End time' }).fill('01:11:00');
await page.keyboard.press('Tab');
await page.keyboard.press('Enter');
await page.getByRole('textbox', { name: 'End time' }).fill('01:11:00');
await page.getByLabel('Submit time bounds').click();
// check image date
// wait for image thumbnails to stabilize
await page.getByLabel('Image Thumbnails', { exact: true }).hover({ trial: true });
await expect(page.getByText('2021-12-30 01:01:00.000Z').first()).toBeVisible();
// flip it off
@ -166,14 +181,11 @@ test.describe('Example Imagery Object', () => {
});
test('Can use alt+drag to move around image once zoomed in', async ({ page }) => {
const deltaYStep = 100; //equivalent to 1x zoom
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
// zoom in
await page.mouse.wheel(0, deltaYStep * 2);
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
await page.mouse.wheel(0, MOUSE_WHEEL_DELTA_Y * 2);
const zoomedBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
// move to the right
@ -195,7 +207,7 @@ test.describe('Example Imagery Object', () => {
await page.mouse.move(imageCenterX - 200, imageCenterY, 10);
await page.mouse.up();
await Promise.all(panHotkey.map((x) => page.keyboard.up(x)));
const afterRightPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const afterRightPanBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x);
// pan left
@ -204,7 +216,7 @@ test.describe('Example Imagery Object', () => {
await page.mouse.move(imageCenterX, imageCenterY, 10);
await page.mouse.up();
await Promise.all(panHotkey.map((x) => page.keyboard.up(x)));
const afterLeftPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const afterLeftPanBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x);
// pan up
@ -214,7 +226,7 @@ test.describe('Example Imagery Object', () => {
await page.mouse.move(imageCenterX, imageCenterY + 200, 10);
await page.mouse.up();
await Promise.all(panHotkey.map((x) => page.keyboard.up(x)));
const afterUpPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const afterUpPanBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(afterUpPanBoundingBox.y).toBeGreaterThan(afterLeftPanBoundingBox.y);
// pan down
@ -223,7 +235,7 @@ test.describe('Example Imagery Object', () => {
await page.mouse.move(imageCenterX, imageCenterY - 200, 10);
await page.mouse.up();
await Promise.all(panHotkey.map((x) => page.keyboard.up(x)));
const afterDownPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const afterDownPanBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y);
});
@ -282,26 +294,43 @@ test.describe('Example Imagery Object', () => {
await expect(page.getByText('Drilling')).toBeVisible();
});
test('Can use + - buttons to zoom on the image @unstable', async ({ page }) => {
test('Can use + - buttons to zoom on the image', async ({ page }) => {
await buttonZoomOnImageAndAssert(page);
});
test('Can use the reset button to reset the image @unstable', async ({ page }, testInfo) => {
test('Can use the reset button to reset the image', async ({ page }) => {
await expect(page.getByLabel('Focused Image Element')).toHaveJSProperty(
'style.transform',
'scale(1) translate(0px, 0px)'
);
// Get initial image dimensions
const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const initialBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
// Zoom in twice via button
await zoomIntoImageryByButton(page);
await expect(page.getByLabel('Focused Image Element')).toHaveJSProperty(
'style.transform',
'scale(2) translate(0px, 0px)'
);
await zoomIntoImageryByButton(page);
await expect(page.getByLabel('Focused Image Element')).toHaveJSProperty(
'style.transform',
'scale(3) translate(0px, 0px)'
);
// Get and assert zoomed in image dimensions
const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
const zoomedInBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
expect(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
// Reset pan and zoom and assert against initial image dimensions
await resetImageryPanAndZoom(page);
const finalBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
await expect(page.getByLabel('Focused Image Element')).toHaveJSProperty(
'style.transform',
'scale(1) translate(0px, 0px)'
);
const finalBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(finalBoundingBox).toEqual(initialBoundingBox);
});
@ -324,20 +353,25 @@ test.describe('Example Imagery Object', () => {
});
});
test.describe('Example Imagery in Display Layout', () => {
test.describe('Example Imagery in Display Layout @clock', () => {
let displayLayout;
test.beforeEach(async ({ page }) => {
// We mock the clock so that we don't need to wait for time driven events
// to verify functionality.
await page.clock.setSystemTime(MISSION_TIME);
await page.clock.resume();
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
displayLayout = await createDomainObjectWithDefaults(page, { type: 'Display Layout' });
await page.goto(displayLayout.url);
await createImageryView(page);
await expect(page.locator('.l-browse-bar__object-name')).toContainText(
'Unnamed Example Imagery'
);
// Create Example Imagery inside Display Layout
await createImageryViewWithShortDelay(page, {
name: 'Unnamed Example Imagery',
parent: displayLayout.uuid
});
await page.goto(displayLayout.url);
});
@ -390,7 +424,7 @@ test.describe('Example Imagery in Display Layout', () => {
await expect.soft(pausePlayButton).toHaveClass(/is-paused/);
});
test('Imagery View operations @unstable', async ({ page }) => {
test('Imagery View operations @clock', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5265'
@ -410,7 +444,7 @@ test.describe('Example Imagery in Display Layout', () => {
await page.locator('div[title="Resize object width"] > input').click();
await page.locator('div[title="Resize object width"] > input').fill('50');
await performImageryViewOperationsAndAssert(page);
await performImageryViewOperationsAndAssert(page, displayLayout);
});
test('Resizing the layout changes thumbnail visibility and size', async ({ page }) => {
@ -454,7 +488,10 @@ test.describe('Example Imagery in Display Layout', () => {
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6709'
});
await createImageryView(page);
await createImageryViewWithShortDelay(page, {
name: 'Unnamed Example Imagery',
parent: displayLayout.uuid
});
await page.goto(displayLayout.url);
const imageElements = page.locator('.c-imagery__main-image-wrapper');
@ -483,16 +520,21 @@ test.describe('Example Imagery in Display Layout', () => {
});
});
test.describe('Example Imagery in Flexible layout', () => {
test.describe('Example Imagery in Flexible layout @clock', () => {
let flexibleLayout;
test.beforeEach(async ({ page }) => {
// We mock the clock so that we don't need to wait for time driven events
// to verify functionality.
await page.clock.setSystemTime(MISSION_TIME);
await page.clock.resume();
await page.goto('./', { waitUntil: 'domcontentloaded' });
flexibleLayout = await createDomainObjectWithDefaults(page, { type: 'Flexible Layout' });
// Create Example Imagery inside the Flexible Layout
await createDomainObjectWithDefaults(page, {
type: 'Example Imagery',
await createImageryViewWithShortDelay(page, {
name: 'Unnamed Example Imagery',
parent: flexibleLayout.uuid
});
@ -502,61 +544,35 @@ test.describe('Example Imagery in Flexible layout', () => {
test('Can double-click on the image to view large image', async ({ page }) => {
// Double-click on the image to open large view
const imageElement = await page.getByRole('button', { name: 'Image Wrapper' });
const imageElement = page.getByRole('button', { name: 'Image Wrapper' });
await imageElement.dblclick();
// Check if the large view is visible
await page.getByRole('button', { name: 'Background Image', state: 'visible' });
page.getByRole('button', { name: 'Focused Image Element', state: 'visible' });
// Close the large view
await page.getByRole('button', { name: 'Close' }).click();
});
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
flexibleLayout = await createDomainObjectWithDefaults(page, { type: 'Flexible Layout' });
await page.goto(flexibleLayout.url);
/* Create Sine Wave Generator with minimum Image Load Delay */
// Click the Create button
await page.getByRole('button', { name: 'Create' }).click();
// Click text=Example Imagery
await page.click('li[role="menuitem"]:has-text("Example Imagery")');
// Clear and set Image load delay to minimum value
await page.locator('input[type="number"]').fill('');
await page.locator('input[type="number"]').fill('5000');
// Click text=OK
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.click('button:has-text("OK")'),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
await expect(page.locator('.l-browse-bar__object-name')).toContainText(
'Unnamed Example Imagery'
);
await page.goto(flexibleLayout.url);
});
test('Imagery View operations @unstable', async ({ page, browserName }) => {
test('Imagery View operations @clock', async ({ page, browserName }) => {
test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox');
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5326'
});
await performImageryViewOperationsAndAssert(page);
await performImageryViewOperationsAndAssert(page, flexibleLayout);
});
});
test.describe('Example Imagery in Tabs View', () => {
test.describe('Example Imagery in Tabs View @clock', () => {
let tabsView;
test.beforeEach(async ({ page }) => {
// We mock the clock so that we don't need to wait for time driven events
// to verify functionality.
await page.clock.setSystemTime(MISSION_TIME);
await page.clock.resume();
await page.goto('./', { waitUntil: 'domcontentloaded' });
tabsView = await createDomainObjectWithDefaults(page, { type: 'Tabs View' });
@ -570,8 +586,8 @@ test.describe('Example Imagery in Tabs View', () => {
await page.click('li[role="menuitem"]:has-text("Example Imagery")');
// Clear and set Image load delay to minimum value
await page.locator('input[type="number"]').fill('');
await page.locator('input[type="number"]').fill('5000');
await page.locator('input[type="number"]').clear();
await page.locator('input[type="number"]').fill(`${IMAGE_LOAD_DELAY}`);
// Click text=OK
await Promise.all([
@ -587,8 +603,8 @@ test.describe('Example Imagery in Tabs View', () => {
await page.goto(tabsView.url);
});
test('Imagery View operations @unstable', async ({ page }) => {
await performImageryViewOperationsAndAssert(page);
test('Imagery View operations @clock', async ({ page }) => {
await performImageryViewOperationsAndAssert(page, tabsView);
});
});
@ -652,20 +668,21 @@ test.describe('Example Imagery in Time Strip', () => {
* 7. Image brightness/contrast can be adjusted by dragging the sliders
* @param {import('@playwright/test').Page} page
*/
async function performImageryViewOperationsAndAssert(page) {
async function performImageryViewOperationsAndAssert(page, layoutObject) {
// Verify that imagery thumbnails use a thumbnail url
const thumbnailImages = page.locator('.c-thumb__image');
const thumbnailImages = page.getByLabel('Image thumbnail from').locator('.c-thumb__image');
const mainImage = page.locator('.c-imagery__main-image__image');
await expect(thumbnailImages.first()).toHaveAttribute('src', thumbnailUrlParamsRegexp);
await expect(mainImage).not.toHaveAttribute('src', thumbnailUrlParamsRegexp);
// Click previous image button
const previousImageButton = page.locator('.c-nav--prev');
await previousImageButton.click();
const previousImageButton = page.getByLabel('Previous image');
await expect(previousImageButton).toBeVisible();
await page.getByLabel('Image Wrapper').hover({ trial: true });
// Verify previous image
const selectedImage = page.locator('.selected');
await expect(selectedImage).toBeVisible();
// Need to force click as the annotation canvas lies on top of the image
// and fails the accessibility checks
// eslint-disable-next-line playwright/no-force-option
await previousImageButton.click({ force: true });
// Use the zoom buttons to zoom in and out
await buttonZoomOnImageAndAssert(page);
@ -680,42 +697,51 @@ async function performImageryViewOperationsAndAssert(page) {
await mouseZoomOnImageAndAssert(page, -2);
// Click next image button
const nextImageButton = page.locator('.c-nav--next');
await nextImageButton.click();
const nextImageButton = page.getByLabel('Next image');
await expect(nextImageButton).toBeVisible();
await page.getByLabel('Image Wrapper').hover({ trial: true });
// eslint-disable-next-line playwright/no-force-option
await nextImageButton.click({ force: true });
// set realtime mode
await setRealTimeMode(page);
await navigateToObjectWithRealTime(
page,
layoutObject.url,
`${FIVE_MINUTES}`,
`${THIRTY_SECONDS}`
);
// Verify previous image
await expect(previousImageButton).toBeVisible();
await page.getByLabel('Image Wrapper').hover({ trial: true });
// eslint-disable-next-line playwright/no-force-option
await previousImageButton.click({ force: true });
await page.locator('.active').click();
const selectedImage = page.locator('.selected');
await expect(selectedImage).toBeVisible();
// Zoom in on next image
await mouseZoomOnImageAndAssert(page, 2);
// Clicking on the left arrow should pause the imagery and go to previous image
await previousImageButton.click();
await expect(page.locator('.c-button.pause-play')).toHaveClass(/is-paused/);
await expect(page.getByLabel('Pause automatic scrolling of image thumbnails')).toBeVisible();
await expect(selectedImage).toBeVisible();
// The imagery view should be updated when new images come in
const imageCount = await page.locator('.c-imagery__thumb').count();
await expect
.poll(
async () => {
const newImageCount = await page.locator('.c-imagery__thumb').count();
return newImageCount;
},
{
message: 'verify that old images are discarded',
timeout: 7 * 1000
}
)
.toBe(imageCount);
// Verify selected image is still displayed
await expect(selectedImage).toBeVisible();
// Unpause imagery
await page.locator('.pause-play').click();
// verify that old images are discarded
const lastImageInBounds = page.getByLabel('Image thumbnail from').first();
const lastImageTimestamp = await lastImageInBounds.getAttribute('title');
expect(lastImageTimestamp).not.toBeNull();
// go forward in time to ensure old images are discarded
await page.clock.fastForward(IMAGE_LOAD_DELAY);
await page.clock.resume();
await expect(page.getByLabel(lastImageTimestamp)).toBeHidden();
//Get background-image url from background-image css prop
await assertBackgroundImageUrlFromBackgroundCss(page);
@ -789,38 +815,18 @@ async function assertBackgroundImageBrightness(page, expected) {
* @param {import('@playwright/test').Page} page
*/
async function assertBackgroundImageUrlFromBackgroundCss(page) {
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
let backgroundImageUrl = await backgroundImage.evaluate((el) => {
const backgroundImage = page.getByLabel('Focused Image Element');
const backgroundImageUrl = await backgroundImage.evaluate((el) => {
return window
.getComputedStyle(el)
.getPropertyValue('background-image')
.match(/url\(([^)]+)\)/)[1];
});
let backgroundImageUrl1 = backgroundImageUrl.slice(1, -1); //forgive me, padre
console.log('backgroundImageUrl1 ' + backgroundImageUrl1);
let backgroundImageUrl2;
await expect
.poll(
async () => {
// Verify next image has updated
let backgroundImageUrlNext = await backgroundImage.evaluate((el) => {
return window
.getComputedStyle(el)
.getPropertyValue('background-image')
.match(/url\(([^)]+)\)/)[1];
});
backgroundImageUrl2 = backgroundImageUrlNext.slice(1, -1); //forgive me, padre
return backgroundImageUrl2;
},
{
message: 'verify next image has updated',
timeout: 7 * 1000
}
)
.not.toBe(backgroundImageUrl1);
console.log('backgroundImageUrl2 ' + backgroundImageUrl2);
// go forward in time to ensure old images are discarded
await page.clock.fastForward(IMAGE_LOAD_DELAY);
await page.clock.resume();
await expect(backgroundImage).not.toHaveJSProperty('background-image', backgroundImageUrl);
}
/**
@ -829,7 +835,7 @@ async function assertBackgroundImageUrlFromBackgroundCss(page) {
async function panZoomAndAssertImageProperties(page) {
const imageryHintsText = await page.locator('.c-imagery__hints').innerText();
expect(expectedAltText).toEqual(imageryHintsText);
const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const zoomedBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
@ -839,7 +845,7 @@ async function panZoomAndAssertImageProperties(page) {
await page.mouse.move(imageCenterX - 200, imageCenterY, 10);
await page.mouse.up();
await Promise.all(panHotkey.map((x) => page.keyboard.up(x)));
const afterRightPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const afterRightPanBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x);
// Pan left
@ -848,7 +854,7 @@ async function panZoomAndAssertImageProperties(page) {
await page.mouse.move(imageCenterX, imageCenterY, 10);
await page.mouse.up();
await Promise.all(panHotkey.map((x) => page.keyboard.up(x)));
const afterLeftPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const afterLeftPanBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x);
// Pan up
@ -858,7 +864,7 @@ async function panZoomAndAssertImageProperties(page) {
await page.mouse.move(imageCenterX, imageCenterY + 200, 10);
await page.mouse.up();
await Promise.all(panHotkey.map((x) => page.keyboard.up(x)));
const afterUpPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const afterUpPanBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(afterUpPanBoundingBox.y).toBeGreaterThanOrEqual(afterLeftPanBoundingBox.y);
// Pan down
@ -867,7 +873,7 @@ async function panZoomAndAssertImageProperties(page) {
await page.mouse.move(imageCenterX, imageCenterY - 200, 10);
await page.mouse.up();
await Promise.all(panHotkey.map((x) => page.keyboard.up(x)));
const afterDownPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const afterDownPanBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(afterDownPanBoundingBox.y).toBeLessThanOrEqual(afterUpPanBoundingBox.y);
}
@ -879,19 +885,20 @@ async function panZoomAndAssertImageProperties(page) {
*/
async function mouseZoomOnImageAndAssert(page, factor = 2) {
// Zoom in
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
const deltaYStep = 100; // equivalent to 1x zoom
await page.mouse.wheel(0, deltaYStep * factor);
const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
await page.getByLabel('Focused Image Element').hover({ trial: true });
const originalImageDimensions = await page.getByLabel('Focused Image Element').boundingBox();
await page.mouse.wheel(0, MOUSE_WHEEL_DELTA_Y * factor);
await waitForZoomAndPanTransitions(page);
const zoomedBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
// center the mouse pointer
await page.mouse.move(imageCenterX, imageCenterY);
// Wait for zoom animation to finish
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
const imageMouseZoomed = await page.locator(backgroundImageSelector).boundingBox();
// Wait for zoom animation to finish and get the new image dimensions
const imageMouseZoomed = await page.getByLabel('Focused Image Element').boundingBox();
if (factor > 0) {
expect(imageMouseZoomed.height).toBeGreaterThan(originalImageDimensions.height);
@ -908,29 +915,61 @@ async function mouseZoomOnImageAndAssert(page, factor = 2) {
* @param {import('@playwright/test').Page} page
*/
async function buttonZoomOnImageAndAssert(page) {
// Lock the zoom and pan so it doesn't reset if a new image comes in
await page.getByLabel('Focused Image Element').hover({ trial: true });
const lockButton = page.getByRole('button', {
name: 'Lock current zoom and pan across all images'
});
if (!(await lockButton.isVisible())) {
await page.getByLabel('Focused Image Element').hover({ trial: true });
}
await lockButton.click();
await expect(page.getByLabel('Focused Image Element')).toHaveJSProperty(
'style.transform',
'scale(1) translate(0px, 0px)'
);
// Get initial image dimensions
const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const initialBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
// Zoom in twice via button
await zoomIntoImageryByButton(page);
await expect(page.getByLabel('Focused Image Element')).toHaveJSProperty(
'style.transform',
'scale(2) translate(0px, 0px)'
);
await zoomIntoImageryByButton(page);
await expect(page.getByLabel('Focused Image Element')).toHaveJSProperty(
'style.transform',
'scale(3) translate(0px, 0px)'
);
// Get and assert zoomed in image dimensions
const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const zoomedInBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
expect(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
// Zoom out once via button
await zoomOutOfImageryByButton(page);
await expect(page.getByLabel('Focused Image Element')).toHaveJSProperty(
'style.transform',
'scale(2) translate(0px, 0px)'
);
// Get and assert zoomed out image dimensions
const zoomedOutBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const zoomedOutBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(zoomedOutBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
expect(zoomedOutBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width);
// Zoom out again via button, assert against the initial image dimensions
await zoomOutOfImageryByButton(page);
const finalBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
await expect(page.getByLabel('Focused Image Element')).toHaveJSProperty(
'style.transform',
'scale(1) translate(0px, 0px)'
);
const finalBoundingBox = await page.getByLabel('Focused Image Element').boundingBox();
expect(finalBoundingBox).toEqual(initialBoundingBox);
}
@ -957,16 +996,11 @@ async function assertBackgroundImageContrast(page, expected) {
*/
async function zoomIntoImageryByButton(page) {
// FIXME: There should only be one set of imagery buttons, but there are two?
const zoomInBtn = page
.locator("[role='toolbar'][aria-label='Image controls'] .t-btn-zoom-in")
.nth(0);
const backgroundImage = page.locator(backgroundImageSelector);
if (!(await zoomInBtn.isVisible())) {
await backgroundImage.hover({ trial: true });
}
const zoomInBtn = page.getByRole('button', { name: 'Zoom in' });
const backgroundImage = page.getByLabel('Focused Image Element');
await backgroundImage.hover({ trial: true });
await zoomInBtn.click();
await waitForAnimations(backgroundImage);
await waitForZoomAndPanTransitions(page);
}
/**
@ -975,17 +1009,11 @@ async function zoomIntoImageryByButton(page) {
* @param {import('@playwright/test').Page} page
*/
async function zoomOutOfImageryByButton(page) {
// FIXME: There should only be one set of imagery buttons, but there are two?
const zoomOutBtn = page
.locator("[role='toolbar'][aria-label='Image controls'] .t-btn-zoom-out")
.nth(0);
const backgroundImage = page.locator(backgroundImageSelector);
if (!(await zoomOutBtn.isVisible())) {
await backgroundImage.hover({ trial: true });
}
const zoomOutBtn = page.getByRole('button', { name: 'Zoom out' });
const backgroundImage = page.getByLabel('Focused Image Element');
await backgroundImage.hover({ trial: true });
await zoomOutBtn.click();
await waitForAnimations(backgroundImage);
await waitForZoomAndPanTransitions(page);
}
/**
@ -994,38 +1022,43 @@ async function zoomOutOfImageryByButton(page) {
* @param {import('@playwright/test').Page} page
*/
async function resetImageryPanAndZoom(page) {
// FIXME: There should only be one set of imagery buttons, but there are two?
const panZoomResetBtn = page
.locator("[role='toolbar'][aria-label='Image controls'] .t-btn-zoom-reset")
.nth(0);
const backgroundImage = page.locator(backgroundImageSelector);
if (!(await panZoomResetBtn.isVisible())) {
await backgroundImage.hover({ trial: true });
}
const panZoomResetBtn = page.getByRole('button', { name: 'Remove zoom and pan' });
await expect(panZoomResetBtn).toBeVisible();
await panZoomResetBtn.hover({ trial: true });
await panZoomResetBtn.click();
await waitForAnimations(backgroundImage);
await waitForZoomAndPanTransitions(page);
await expect(page.getByText('Alt drag to pan')).toBeHidden();
await expect(page.locator('.c-thumb__viewable-area')).toBeHidden();
}
/**
* @param {import('@playwright/test').Page} page
*/
async function createImageryView(page) {
// Click the Create button
await page.getByRole('button', { name: 'Create' }).click();
// Click text=Example Imagery
await page.click('li[role="menuitem"]:has-text("Example Imagery")');
async function createImageryViewWithShortDelay(page, { name, parent }) {
await createDomainObjectWithDefaults(page, {
name,
type: 'Example Imagery',
parent
});
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
await page.getByLabel('More actions').click();
await page.getByLabel('Edit Properties').click();
// Clear and set Image load delay to minimum value
await page.locator('input[type="number"]').fill('');
await page.locator('input[type="number"]').fill('5000');
// Click text=OK
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.click('button:has-text("OK")'),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
await page.locator('input[type="number"]').fill(`${IMAGE_LOAD_DELAY}`);
await page.getByLabel('Save').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
// eslint-disable-next-line require-await
async function waitForZoomAndPanTransitions(page) {
// Wait for image to stabilize
await page.getByLabel('Focused Image Element').hover({ trial: true });
// Wait for zoom to end
await expect(page.getByLabel('Focused Image Element')).not.toHaveClass(/is-zooming|is-panning/);
// Wait for image to stabilize
await page.getByLabel('Focused Image Element').hover({ trial: true });
}

View File

@ -21,7 +21,7 @@
-->
<template>
<mct-tree
<MctTree
:is-selector-tree="true"
:initial-selection="model.parent"
@tree-item-selection="handleItemSelection"

View File

@ -36,7 +36,7 @@
</tr>
</thead>
<tbody>
<lad-row
<LadRow
v-for="ladRow in items"
:key="ladRow.key"
:domain-object="ladRow.domainObject"

View File

@ -39,7 +39,7 @@
{{ ladTable.domainObject.name }}
</td>
</tr>
<lad-row
<LadRow
v-for="ladRow in ladTelemetryObjects[ladTable.key]"
:key="combineKeys(ladTable.key, ladRow.key)"
:domain-object="ladRow.domainObject"

View File

@ -24,7 +24,7 @@
<ul class="c-tree">
<h2 title="Display properties for this object">Bar Graph Series</h2>
<li>
<series-options
<SeriesOptions
v-for="series in plotSeries"
:key="series.keyString"
:item="series"

View File

@ -22,10 +22,10 @@
<template>
<div>
<div v-if="canEdit">
<plot-options-edit />
<PlotOptionsEdit />
</div>
<div v-else>
<plot-options-browse />
<PlotOptionsBrowse />
</div>
</div>
</template>

View File

@ -57,7 +57,7 @@
<span class="c-condition__summary">
<template v-if="!condition.isDefault && !canEvaluateCriteria"> Define criteria </template>
<span v-else>
<condition-description :show-label="false" :condition="condition" />
<ConditionDescription :show-label="false" :condition="condition" />
</span>
</span>
@ -184,7 +184,7 @@
<span class="c-condition__output"> Output: {{ condition.configuration.output }} </span>
</div>
<div class="c-condition__summary">
<condition-description :show-label="false" :condition="condition" />
<ConditionDescription :show-label="false" :condition="condition" />
</div>
</div>
</div>

View File

@ -43,31 +43,31 @@
</span>
</div>
<toolbar-color-picker
<ToolbarColorPicker
v-if="hasProperty(styleItem.style.border)"
class="c-style__toolbar-button--border-color u-menu-to--center"
:options="borderColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker
<ToolbarColorPicker
v-if="hasProperty(styleItem.style.backgroundColor)"
class="c-style__toolbar-button--background-color u-menu-to--center"
:options="backgroundColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker
<ToolbarColorPicker
v-if="hasProperty(styleItem.style.color)"
class="c-style__toolbar-button--color u-menu-to--center"
:options="colorOption"
@change="updateStyleValue"
/>
<toolbar-button
<ToolbarButton
v-if="hasProperty(styleItem.style.imageUrl)"
class="c-style__toolbar-button--image-url"
:options="imageUrlOption"
@change="updateStyleValue"
/>
<toolbar-toggle-button
<ToolbarToggleButton
v-if="hasProperty(styleItem.style.isStyleInvisible)"
class="c-style__toolbar-button--toggle-visible"
:options="isStyleInvisibleOption"
@ -76,7 +76,7 @@
</div>
<!-- Save Styles -->
<toolbar-button
<ToolbarButton
v-if="canSaveStyle"
ref="saveStyleButton"
class="c-style__toolbar-button--save c-local-controls--show-on-hover c-icon-button c-icon-button--major"

View File

@ -112,11 +112,11 @@
:class="{ 'is-current': conditionStyle.conditionId === selectedConditionId }"
@click="applySelectedConditionStyle(conditionStyle.conditionId)"
>
<condition-error
<ConditionError
:show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<condition-description
<ConditionDescription
:show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>

View File

@ -21,7 +21,7 @@
-->
<template>
<layout-frame
<LayoutFrame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@ -38,7 +38,7 @@
aria-label="Box"
></div>
</template>
</layout-frame>
</LayoutFrame>
</template>
<script>

View File

@ -21,7 +21,7 @@
-->
<template>
<layout-frame
<LayoutFrame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@ -38,7 +38,7 @@
aria-label="Ellipse"
></div>
</template>
</layout-frame>
</LayoutFrame>
</template>
<script>

View File

@ -21,7 +21,7 @@
-->
<template>
<layout-frame
<LayoutFrame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@ -31,7 +31,7 @@
<template #content>
<div class="c-image-view" :style="style"></div>
</template>
</layout-frame>
</LayoutFrame>
</template>
<script>

View File

@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<template>
<layout-frame
<LayoutFrame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@ -39,7 +39,7 @@
:layout-font="item.font"
/>
</template>
</layout-frame>
</LayoutFrame>
</template>
<script>

View File

@ -21,7 +21,7 @@
-->
<template>
<layout-frame
<LayoutFrame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@ -68,7 +68,7 @@
</div>
</div>
</template>
</layout-frame>
</LayoutFrame>
</template>
<script>

View File

@ -21,7 +21,7 @@
-->
<template>
<layout-frame
<LayoutFrame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@ -42,7 +42,7 @@
<div class="c-text-view__text">{{ item.text }}</div>
</div>
</template>
</layout-frame>
</LayoutFrame>
</template>
<script>

View File

@ -50,14 +50,14 @@
</div>
<div v-if="isEditing" class="c-inspect-properties__label span-all">
<toggle-switch
<ToggleSwitch
:id="keyString"
:checked="persistedFilters.useGlobal"
@change="useGlobalFilter"
/>
Use global filter
</div>
<filter-field
<FilterField
v-for="metadatum in activeFilters"
:key="metadatum.key"
:filter-field="metadatum"

View File

@ -25,12 +25,12 @@
<div v-if="hasActiveFilters" class="c-filter-indication">
{{ label }}
</div>
<global-filters
<GlobalFilters
:global-filters="globalFilters"
:global-metadata="globalMetadata"
@persist-global-filters="persistGlobalFilters"
/>
<filter-object
<FilterObject
v-for="(child, key) in children"
:key="key"
:filter-object="child"

View File

@ -38,7 +38,7 @@
</div>
</div>
<ul v-if="expanded" class="c-inspect-properties">
<filter-field
<FilterField
v-for="metadatum in globalMetadata"
:key="metadatum.key"
:filter-field="metadatum"

View File

@ -38,7 +38,7 @@
<span class="c-fl-container__size-indicator">{{ sizeString }}</span>
</div>
<drop-hint
<DropHint
class="c-fl-frame__drop-hint"
:index="-1"
:allow-drop="allowDrop"
@ -47,7 +47,7 @@
<div role="row" class="c-fl-container__frames-holder" :class="flexLayoutCssClass">
<template v-for="(frame, i) in frames" :key="frame.id">
<frame-component
<FrameComponent
class="c-fl-container__frame"
:frame="frame"
:index="i"
@ -58,14 +58,14 @@
:object-path="objectPath"
/>
<drop-hint
<DropHint
class="c-fl-frame__drop-hint"
:index="i"
:allow-drop="allowDrop"
@object-drop-to="moveOrCreateNewFrame"
/>
<resize-handle
<ResizeHandle
v-if="i !== frames.length - 1"
:index="i"
:drag-orientation="rowsLayout ? 'horizontal' : 'vertical'"

View File

@ -34,7 +34,7 @@
:aria-label="`Flexible Layout ${rowsLayout ? 'Rows' : 'Columns'}`"
>
<template v-for="(container, index) in containers" :key="`component-${container.id}`">
<drop-hint
<DropHint
v-if="index === 0 && containers.length > 1"
class="c-fl-frame__drop-hint"
:index="-1"
@ -42,7 +42,7 @@
@object-drop-to="moveContainer"
/>
<container-component
<ContainerComponent
:index="index"
:container="container"
:rows-layout="rowsLayout"
@ -54,7 +54,7 @@
@persist="persist"
/>
<resize-handle
<ResizeHandle
v-if="index !== containers.length - 1"
:index="index"
:drag-orientation="rowsLayout ? 'vertical' : 'horizontal'"
@ -64,7 +64,7 @@
@end-move="endContainerResizing"
/>
<drop-hint
<DropHint
v-if="containers.length > 1"
class="c-fl-frame__drop-hint"
:index="index"

View File

@ -35,7 +35,7 @@
role="group"
@dragstart="initDrag"
>
<object-frame
<ObjectFrame
v-if="domainObject"
ref="objectFrame"
:domain-object="domainObject"

View File

@ -21,7 +21,7 @@
-->
<template>
<div class="l-grid-view" role="grid" :aria-label="`${domainObject.name} Grid View`">
<grid-item
<GridItem
v-for="(item, index) in items"
:key="index"
:item="item"

View File

@ -73,7 +73,7 @@
</tr>
</thead>
<tbody>
<list-item
<ListItem
v-for="item in sortedItems"
:key="item.objectKeyString"
:item="item"

View File

@ -22,7 +22,7 @@
<template>
<div class="c-compass" :style="`width: 100%; height: 100%`">
<compass-hud
<CompassHud
v-if="showCompassHUD"
:camera-angle-of-view="cameraAngleOfView"
:heading="heading"

View File

@ -26,50 +26,42 @@
role="toolbar"
aria-label="Image controls"
>
<imagery-view-menu-switcher
<ImageryViewMenuSwitcher
:icon-class="'icon-brightness'"
:aria-label="'Brightness and contrast'"
:title="'Brightness and contrast'"
>
<filter-settings @filter-changed="updateFilterValues" />
</imagery-view-menu-switcher>
<FilterSettings @filter-changed="updateFilterValues" />
</ImageryViewMenuSwitcher>
<imagery-view-menu-switcher
<ImageryViewMenuSwitcher
v-if="layers.length"
icon-class="icon-layers"
aria-label="Layers"
title="Layers"
>
<layer-settings :layers="layers" @toggle-layer-visibility="toggleLayerVisibility" />
</imagery-view-menu-switcher>
<LayerSettings :layers="layers" @toggle-layer-visibility="toggleLayerVisibility" />
</ImageryViewMenuSwitcher>
<zoom-settings
<ZoomSettings
class="--hide-if-less-than-220"
:pan-zoom-locked="panZoomLocked"
:zoom-factor="zoomFactor"
@zoom-out="zoomOut"
@zoom-in="zoomIn"
@toggle-zoom-lock="toggleZoomLock"
@handle-reset-image="handleResetImage"
/>
<imagery-view-menu-switcher
<ImageryViewMenuSwitcher
class="--show-if-less-than-220"
:icon-class="'icon-magnify'"
:aria-label="'Zoom settings'"
:title="'Zoom settings'"
>
<zoom-settings
<ZoomSettings
:pan-zoom-locked="panZoomLocked"
:class="'c-control-menu c-menu--has-close-btn'"
:zoom-factor="zoomFactor"
:is-menu="true"
@zoom-out="zoomOut"
@zoom-in="zoomIn"
@toggle-zoom-lock="toggleZoomLock"
@handle-reset-image="handleResetImage"
/>
</imagery-view-menu-switcher>
</ImageryViewMenuSwitcher>
</div>
</template>
@ -98,7 +90,15 @@ export default {
ImageryViewMenuSwitcher,
ZoomSettings
},
inject: ['openmct', 'domainObject'],
inject: ['openmct', 'domainObject', 'resetImage', 'handlePanZoomUpdate'],
provide() {
return {
resetImage: this.resetImage,
zoomIn: this.zoomIn,
zoomOut: this.zoomOut,
toggleZoomLock: this.toggleZoomLock
};
},
props: {
layers: {
type: Array,
@ -117,7 +117,6 @@ export default {
},
emits: [
'cursors-updated',
'reset-image',
'pan-zoom-updated',
'filters-updated',
'start-pan',
@ -155,7 +154,7 @@ export default {
imageUrl(newUrl, oldUrl) {
// reset image pan/zoom if newUrl only if not locked
if (newUrl && !this.panZoomLocked) {
this.handleResetImage();
this.resetImage();
}
},
cursorStates(states) {
@ -172,12 +171,6 @@ export default {
document.removeEventListener('keyup', this.handleKeyUp);
},
methods: {
handleResetImage() {
this.$emit('reset-image');
},
handleUpdatePanZoom(options) {
this.$emit('pan-zoom-updated', options);
},
toggleZoomLock() {
this.panZoomLocked = !this.panZoomLocked;
},
@ -208,10 +201,10 @@ export default {
}
if (newScaleFactor <= 0 || newScaleFactor <= ZOOM_LIMITS_MIN_DEFAULT) {
return this.handleResetImage();
return this.resetImage();
}
this.handleUpdatePanZoom({
this.handlePanZoomUpdate({
newScaleFactor,
screenClientX,
screenClientY

View File

@ -28,8 +28,10 @@
selected: selected,
'real-time': realTime
}"
:aria-label="image.formattedTime"
:aria-label="ariaLabel"
:title="image.formattedTime"
role="button"
tabindex="0"
@click="handleClick"
>
<a class="c-thumb__image-wrapper" href="" :download="image.imageDownloadName" @click.prevent>
@ -94,6 +96,9 @@ export default {
};
},
computed: {
ariaLabel() {
return `Image thumbnail from ${this.image.formattedTime}${this.showAnnotationIndicator ? ', has annotations' : ''}`;
},
viewableAreaStyle() {
if (!this.viewableArea || !this.imgWidth || !this.imgHeight) {
return null;

View File

@ -39,8 +39,6 @@
:zoom-factor="zoomFactor"
:image-url="imageUrl"
:layers="layers"
@reset-image="resetImage"
@pan-zoom-updated="handlePanZoomUpdate"
@filters-updated="setFilters"
@cursors-updated="setCursorStates"
@start-pan="startPan"
@ -87,9 +85,11 @@
fetchpriority="low"
/>
<div
v-if="imageUrl"
v-show="imageUrl"
ref="focusedImageElement"
aria-label="Focused Image Element"
class="c-imagery__main-image__background-image"
:class="{ 'is-zooming': isZooming, 'is-panning': isPanning }"
:draggable="!isSelectable"
:style="focusImageStyles"
></div>
@ -112,6 +112,7 @@
<button
class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--prev"
title="Previous image"
aria-label="Previous image"
:disabled="isPrevDisabled"
@click="prevImage()"
></button>
@ -119,6 +120,7 @@
<button
class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--next"
title="Next image"
aria-label="Next image"
:disabled="isNextDisabled"
@click="nextImage()"
></button>
@ -161,6 +163,7 @@
v-if="!isFixed"
class="c-button icon-pause pause-play"
:class="{ 'is-paused': isPaused }"
aria-label="Pause automatic scrolling of image thumbnails"
@click="handlePauseButton(!isPaused)"
></button>
</div>
@ -184,6 +187,7 @@
'animate-scroll': animateThumbScroll
}
]"
aria-label="Image Thumbnails"
@scroll="handleScroll"
>
<ImageThumbnail
@ -192,7 +196,7 @@
:image="image"
:active="focusedImageIndex === index"
:imagery-annotations="imageryAnnotations[image.time]"
:selected="focusedImageIndex === index && isPaused"
:selected="isSelected(index)"
:real-time="!isFixed"
:viewable-area="focusedImageIndex === index ? viewableArea : null"
@click="thumbnailClicked(index)"
@ -202,6 +206,7 @@
<button
class="c-imagery__auto-scroll-resume-button c-icon-button icon-play"
title="Resume automatic scrolling of image thumbnails"
aria-label="Resume automatic scrolling of image thumbnails"
@click="scrollToRight"
></button>
</div>
@ -267,6 +272,13 @@ export default {
'imageFreshnessOptions',
'showCompassHUD'
],
provide() {
return {
toggleZoomLock: this.toggleZoomLock,
resetImage: this.resetImage,
handlePanZoomUpdate: this.handlePanZoomUpdate
};
},
props: {
focusedImageTimestamp: {
type: Number,
@ -282,58 +294,62 @@ export default {
this.requestCount = 0;
return {
timeFormat: '',
layers: [],
visibleLayers: [],
durationFormatter: undefined,
imageHistory: [],
bounds: {},
timeSystem: timeSystem,
keyString: undefined,
animateThumbScroll: false,
animateZoom: true,
annotationsBeingMarqueed: false,
autoScroll: true,
thumbnailClick: THUMBNAIL_CLICKED,
isPaused: false,
isFixed: false,
bounds: {},
canTrackDuration: false,
refreshCSS: false,
focusedImageIndex: undefined,
focusedImageRelatedTelemetry: {},
numericDuration: undefined,
relatedTelemetry: {},
latestRelatedTelemetry: {},
focusedImageNaturalAspectRatio: undefined,
imageContainerWidth: undefined,
imageContainerHeight: undefined,
sizedImageWidth: 0,
sizedImageHeight: 0,
viewHeight: 0,
lockCompass: true,
resizingWindow: false,
zoomFactor: ZOOM_SCALE_DEFAULT,
cursorStates: {
isPannable: false,
modifierKeyPressed: false,
showCursorZoomIn: false,
showCursorZoomOut: false
},
durationFormatter: undefined,
filters: {
brightness: 100,
contrast: 100
},
cursorStates: {
isPannable: false,
showCursorZoomIn: false,
showCursorZoomOut: false,
modifierKeyPressed: false
},
focusedImageIndex: undefined,
focusedImageNaturalAspectRatio: undefined,
focusedImageRelatedTelemetry: {},
forceShowThumbnails: false,
imageContainerHeight: undefined,
imageContainerWidth: undefined,
imageHistory: [],
imagePanned: false,
imageTranslateX: 0,
imageTranslateY: 0,
imageViewportWidth: 0,
imageViewportHeight: 0,
pan: undefined,
animateZoom: true,
imagePanned: false,
forceShowThumbnails: false,
animateThumbScroll: false,
imageViewportWidth: 0,
imageryAnnotations: {},
annotationsBeingMarqueed: false
isFixed: false,
isPaused: false,
isZooming: false,
keyString: undefined,
latestRelatedTelemetry: {},
layers: [],
lockCompass: true,
numericDuration: undefined,
pan: null,
refreshCSS: false,
relatedTelemetry: {},
resizingWindow: false,
sizedImageHeight: 0,
sizedImageWidth: 0,
thumbnailClick: THUMBNAIL_CLICKED,
timeFormat: '',
timeSystem: timeSystem,
viewHeight: 0,
visibleLayers: [],
zoomFactor: ZOOM_SCALE_DEFAULT
};
},
computed: {
isPanning() {
return Boolean(this.pan);
},
displayThumbnails() {
return this.forceShowThumbnails || this.viewHeight >= SHOW_THUMBS_THRESHOLD_HEIGHT;
},
@ -667,6 +683,9 @@ export default {
this.focusedImageWrapper = this.$refs.focusedImageWrapper;
this.focusedImageElement = this.$refs.focusedImageElement;
this.focusedImageElement.addEventListener('transitionstart', this.handleZoomTransitionStart);
this.focusedImageElement.addEventListener('transitionend', this.handleZoomTransitionEnd);
//We only need to use this till the user focuses an image manually
if (this.focusedImageTimestamp !== undefined) {
this.isPaused = true;
@ -724,6 +743,9 @@ export default {
this.openmct.selection.on('change', this.updateSelection);
},
beforeUnmount() {
this.focusedImageElement.removeEventListener('transitionstart', this.handleZoomTransitionStart);
this.focusedImageElement.removeEventListener('transitionend', this.handleZoomTransitionEnd);
this.abortController.abort();
this.persistVisibleLayers();
this.stopFollowingTimeContext();
@ -886,6 +908,12 @@ export default {
return mostRecent[valueKey];
},
handleZoomTransitionStart() {
this.isZooming = true;
},
handleZoomTransitionEnd() {
this.isZooming = false;
},
loadVisibleLayers() {
const layersMetadata = this.imageMetadataValue.layers;
if (!layersMetadata) {
@ -1399,7 +1427,7 @@ export default {
this.updatePanZoom(this.zoomFactor, dX, dY);
},
endPan() {
this.pan = undefined;
this.pan = null;
this.animateZoom = true;
},
onMouseUp(event) {
@ -1420,6 +1448,9 @@ export default {
let isVisible = this.layers[index].visible === true;
this.layers[index].visible = !isVisible;
this.visibleLayers = this.layers.filter((layer) => layer.visible);
},
isSelected(index) {
return this.focusedImageIndex === index && this.isPaused;
}
}
};

View File

@ -26,14 +26,21 @@
<button
class="c-button t-btn-zoom-out icon-minus"
title="Zoom out"
aria-label="Zoom out"
@click="zoomOut"
></button>
<button class="c-button t-btn-zoom-in icon-plus" title="Zoom in" @click="zoomIn"></button>
<button
class="c-button t-btn-zoom-in icon-plus"
title="Zoom in"
aria-label="Zoom in"
@click="zoomIn"
></button>
<button
class="c-button t-btn-zoom-lock"
title="Lock current zoom and pan across all images"
aria-label="Lock current zoom and pan across all images"
:class="{ 'icon-unlocked': !panZoomLocked, 'icon-lock': panZoomLocked }"
@click="toggleZoomLock"
></button>
@ -41,7 +48,8 @@
<button
class="c-button icon-reset t-btn-zoom-reset"
title="Remove zoom and pan"
@click="handleResetImage"
aria-label="Remove zoom and pan"
@click="resetImage"
></button>
</div>
<div class="c-image-controls__zoom-factor">x{{ formattedZoomFactor }}</div>
@ -49,13 +57,14 @@
<button
v-if="isMenu"
class="c-click-icon icon-x t-btn-close c-switcher-menu__close-button"
aria-label="Close menu"
></button>
</div>
</template>
<script>
export default {
inject: ['openmct'],
inject: ['zoomIn', 'zoomOut', 'toggleZoomLock', 'resetImage'],
props: {
zoomFactor: {
type: Number,
@ -70,10 +79,6 @@ export default {
required: false
}
},
emits: ['handle-reset-image', 'toggle-zoom-lock', 'zoom-in', 'zoom-out'],
data() {
return {};
},
computed: {
formattedZoomFactor() {
return Number.parseFloat(this.zoomFactor).toPrecision(2);
@ -85,18 +90,6 @@ export default {
if (!closeButton) {
e.stopPropagation();
}
},
handleResetImage() {
this.$emit('handle-reset-image');
},
toggleZoomLock() {
this.$emit('toggle-zoom-lock');
},
zoomIn() {
this.$emit('zoom-in');
},
zoomOut() {
this.$emit('zoom-out');
}
}
};

View File

@ -40,7 +40,7 @@
}"
>
<span class="c-elements-pool__grippy c-grippy c-grippy--vertical-drag"></span>
<object-label
<ObjectLabel
:domain-object="elementObject"
:object-path="[elementObject, domainObject]"
@context-click-active="setContextClickState"

View File

@ -34,7 +34,7 @@
id="inspector-elements-tree"
class="c-tree c-elements-pool__tree"
>
<element-item
<ElementItem
v-for="(element, index) in elements"
:key="element.identifier.key"
:index="index"

View File

@ -37,7 +37,7 @@
<div class="c-elements-pool__instructions">
Select and drag an element to move it into a different axis.
</div>
<element-item-group
<ElementItemGroup
v-for="(yAxis, index) in yAxes"
:key="`element-group-yaxis-${yAxis.id}`"
:parent-object="parentObject"
@ -46,7 +46,7 @@
@drop-group="moveTo($event, 0, yAxis.id)"
>
<li class="js-first-place" @drop="moveTo($event, 0, yAxis.id)"></li>
<element-item
<ElementItem
v-for="(element, elemIndex) in yAxis.elements"
:key="element.identifier.key"
:index="elemIndex"
@ -61,7 +61,7 @@
class="js-last-place"
@drop="moveTo($event, yAxis.elements.length, yAxis.id)"
></li>
</element-item-group>
</ElementItemGroup>
</ul>
<div v-show="!hasElements">No contained elements</div>
</div>

View File

@ -33,7 +33,7 @@
:key="pathObject.key"
class="c-location__item"
>
<object-label
<ObjectLabel
:domain-object="pathObject.domainObject"
:object-path="pathObject.objectPath"
/>

View File

@ -24,7 +24,7 @@
<div class="c-inspector__saved-styles c-inspect-styles">
<div class="c-inspect-styles__content">
<div class="c-inspect-styles__saved-styles">
<saved-style-selector
<SavedStyleSelector
v-for="(savedStyle, index) in savedStyles"
:key="index"
class="c-inspect-styles__saved-style"

View File

@ -21,16 +21,16 @@
-->
<template>
<multipane type="vertical">
<pane class="c-inspector__styles">
<Multipane type="vertical">
<Pane class="c-inspector__styles">
<div class="u-contents">
<StylesView />
</div>
</pane>
<pane v-if="isEditing" class="c-inspector__saved-styles" handle="before" label="Saved Styles">
</Pane>
<Pane v-if="isEditing" class="c-inspector__saved-styles" handle="before" label="Saved Styles">
<SavedStylesInspectorView />
</pane>
</multipane>
</Pane>
</Multipane>
</template>
<script>

View File

@ -107,7 +107,7 @@
To start a new entry, click here or drag and drop any object
</span>
</div>
<progress-bar
<ProgressBar
v-if="savingTransaction"
class="c-telemetry-table__progress-bar"
:model="{ progressPerc: null }"

View File

@ -38,7 +38,7 @@
</span>
<span class="c-indicator__count">{{ notifications.length }}</span>
<notifications-list
<NotificationsList
v-if="showNotificationsOverlay"
:notifications="notifications"
@close="toggleNotificationsList"

View File

@ -31,7 +31,7 @@
<div class="c-message__title">{{ notification.model.message }}</div>
</div>
<div class="message-body">
<progress-bar v-if="isProgressNotification" :model="progressObject" />
<ProgressBar v-if="isProgressNotification" :model="progressObject" />
</div>
</div>
</div>

View File

@ -28,7 +28,7 @@
</div>
</div>
<div role="list" class="w-messages c-overlay__messages">
<notification-message
<NotificationMessage
v-for="(notification, notificationIndex) in notifications"
:key="notificationIndex"
:close-overlay="closeOverlay"

View File

@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<template>
<swim-lane :is-nested="isNested" :status="status">
<SwimLane :is-nested="isNested" :status="status">
<template #label>
{{ heading }}
</template>
@ -88,7 +88,7 @@
</text>
</svg>
</template>
</swim-lane>
</SwimLane>
</template>
<script>

View File

@ -23,17 +23,17 @@
<template>
<div ref="plan" class="c-plan c-timeline-holder">
<template v-if="viewBounds && !options.compact">
<swim-lane>
<SwimLane>
<template #label>{{ timeSystem.name }}</template>
<template #object>
<timeline-axis
<TimelineAxis
:bounds="viewBounds"
:time-system="timeSystem"
:content-height="height"
:rendering-engine="renderingEngine"
/>
</template>
</swim-lane>
</SwimLane>
</template>
<div class="c-plan__contents u-contents">
<ActivityTimeline

View File

@ -20,19 +20,19 @@
at runtime from the About dialog for additional information.
-->
<template>
<plan-activity-time-view
<PlanActivityTimeView
v-for="activity in activities"
:key="activity.key"
:activity="activity"
:heading="heading"
/>
<plan-activity-properties-view
<PlanActivityPropertiesView
v-for="activity in activities"
:key="activity.key"
heading="Properties"
:activity="activity"
/>
<plan-activity-status-view
<PlanActivityStatusView
v-if="canPersistState"
:key="activities[0].key"
:activity="activities[0]"

View File

@ -25,7 +25,7 @@
<div v-if="properties.length" class="u-contents">
<div class="c-inspect-properties__header">{{ heading }}</div>
<ul v-for="property in properties" :key="property.id" class="c-inspect-properties__section">
<activity-property :label="property.label" :value="property.value" />
<ActivityProperty :label="property.label" :value="property.value" />
</ul>
</div>
</div>

View File

@ -31,7 +31,7 @@
:key="timeProperty.id"
class="c-inspect-properties__section"
>
<activity-property :label="timeProperty.label" :value="timeProperty.value" />
<ActivityProperty :label="timeProperty.label" :value="timeProperty.value" />
</ul>
</div>
</div>

View File

@ -29,7 +29,7 @@
<slot></slot>
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
<div v-if="seriesModels.length" class="u-contents">
<y-axis
<YAxis
v-for="(yAxis, index) in yAxesIds"
:id="yAxis.id"
:key="`yAxis-${yAxis.id}-${index}`"
@ -68,7 +68,7 @@
class="gl-plot-chart-wrapper"
:class="[{ 'alt-pressed': altPressed }]"
>
<mct-chart
<MctChart
:rectangles="rectangles"
:highlights="highlights"
:show-limit-line-labels="limitLineLabels"
@ -159,10 +159,7 @@
class="c-cursor-guide--h js-cursor-guide--h"
></div>
</div>
<x-axis
v-if="seriesModels.length > 0 && !options.compact"
:series-model="seriesModels[0]"
/>
<XAxis v-if="seriesModels.length > 0 && !options.compact" :series-model="seriesModels[0]" />
</div>
</div>
</div>

View File

@ -33,12 +33,12 @@
's-status-timeconductor-unsynced': status === 'timeconductor-unsynced'
}"
>
<progress-bar
<ProgressBar
v-show="!!loading"
class="c-telemetry-table__progress-bar"
:model="{ progressPerc: null }"
/>
<mct-plot
<MctPlot
ref="mctPlot"
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
:init-grid-lines="gridLinesProp"
@ -54,7 +54,7 @@
@cursor-guide="onCursorGuideChange"
@grid-lines="onGridLinesChange"
>
<plot-legend
<PlotLegend
v-if="configReady && hideLegend === false"
:cursor-locked="lockHighlightPoint"
:highlights="highlights"
@ -62,7 +62,7 @@
@expanded="updateExpanded"
@position="updatePosition"
/>
</mct-plot>
</MctPlot>
</div>
</div>
</template>

View File

@ -37,18 +37,18 @@
aria-label="Plot Canvas"
></canvas>
<div ref="limitArea" class="js-limit-area" aria-hidden="true">
<limit-label
<LimitLabel
v-for="(limitLabel, index) in visibleLimitLabels"
:key="`limitLabel-${limitLabel.limit.seriesKey}-${index}`"
:point="limitLabel.point"
:limit="limitLabel.limit"
></limit-label>
<limit-line
></LimitLabel>
<LimitLine
v-for="(limitLine, index) in visibleLimitLines"
:key="`limitLine-${limitLine.limit.seriesKey}${index}`"
:point="limitLine.point"
:limit="limitLine.limit"
></limit-line>
></LimitLine>
</div>
</div>
</template>

View File

@ -22,10 +22,10 @@
<template>
<div>
<div v-if="canEdit">
<plot-options-edit />
<PlotOptionsEdit />
</div>
<div v-else>
<plot-options-browse />
<PlotOptionsBrowse />
</div>
</div>
</template>

View File

@ -24,10 +24,10 @@
<ul v-if="!isStackedPlotObject" class="c-tree" role="tree">
<h2 class="--first" title="Display properties for this Plot Series object">Plot Series</h2>
<li v-for="series in plotSeries" :key="series.keyString">
<series-form :series="series" @series-updated="updateSeriesConfigForObject" />
<SeriesForm :series="series" @series-updated="updateSeriesConfigForObject" />
</li>
</ul>
<y-axis-form
<YAxisForm
v-for="(yAxisId, index) in yAxesIds"
:id="yAxisId.id"
:key="`yAxis-${index}`"
@ -44,7 +44,7 @@
role="tree"
>
<h2 class="--first" title="Legend options">Legend</h2>
<legend-form role="treeitem" tabindex="0" class="grid-properties" :legend="config.legend" />
<LegendForm role="treeitem" tabindex="0" class="grid-properties" :legend="config.legend" />
</ul>
</div>
</template>

View File

@ -45,7 +45,7 @@
class="c-state-indicator__alert-cursor-lock icon-cursor-lock"
title="Cursor is point locked. Click anywhere in the plot to unlock."
></div>
<plot-legend-item-collapsed
<PlotLegendItemCollapsed
v-for="(seriesObject, seriesIndex) in seriesModels"
:key="`${seriesObject.keyString}-${seriesIndex}-collapsed`"
:highlights="highlights"
@ -79,7 +79,7 @@
</tr>
</thead>
<tbody>
<plot-legend-item-expanded
<PlotLegendItemExpanded
v-for="(seriesObject, seriesIndex) in seriesModels"
:key="`${seriesObject.keyString}-${seriesIndex}-expanded`"
:series-key-string="seriesObject.keyString"

View File

@ -27,7 +27,7 @@
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
aria-label="Stacked Plot Style Target"
>
<plot-legend
<PlotLegend
v-if="compositionObjectsConfigLoaded && showLegendsForChildren === false"
:cursor-locked="!!lockHighlightPoint"
:highlights="highlights"
@ -37,7 +37,7 @@
@position="updatePosition"
/>
<div class="l-view-section">
<stacked-plot-item
<StackedPlotItem
v-for="objectWrapper in compositionObjects"
ref="stackedPlotItems"
:key="objectWrapper.keyString"

View File

@ -76,7 +76,7 @@
class="c-tabs-view__object-holder"
:class="{ 'c-tabs-view__object-holder--hidden': !isCurrent(tab) }"
>
<object-view
<ObjectView
v-if="shouldLoadTab(tab)"
class="c-tabs-view__object"
:default-object="tab.domainObject"

View File

@ -100,7 +100,7 @@
}}
</div>
<toggle-switch
<ToggleSwitch
id="show-filtered-rows-toggle"
label="Show selected items only"
:checked="isShowingMarkedRowsOnly"
@ -139,7 +139,7 @@
:style="dropTargetStyle"
></div>
<progress-bar
<ProgressBar
v-if="loading"
class="c-telemetry-table__progress-bar"
:model="{ progressPerc: null }"
@ -155,7 +155,7 @@
<table class="c-table__headers c-telemetry-table__headers">
<thead>
<tr class="c-telemetry-table__headers__labels">
<table-column-header
<TableColumnHeader
v-for="(title, key, headerIndex) in headers"
:key="key"
:header-key="key"
@ -171,10 +171,10 @@
@resize-column-end="updateConfiguredColumnWidths"
>
<span class="c-telemetry-table__headers__label">{{ title }}</span>
</table-column-header>
</TableColumnHeader>
</tr>
<tr v-if="allowFiltering" class="c-telemetry-table__headers__filter">
<table-column-header
<TableColumnHeader
v-for="(title, key, headerIndex) in headers"
:key="key"
:header-key="key"
@ -188,7 +188,7 @@
@reorder-column="reorderColumn"
@resize-column-end="updateConfiguredColumnWidths"
>
<search
<Search
:value="filters[key]"
class="c-table__search"
:aria-label="`${key} filter input`"
@ -204,8 +204,8 @@
>
/R/
</button>
</search>
</table-column-header>
</Search>
</TableColumnHeader>
</tr>
</thead>
</table>
@ -225,7 +225,7 @@
:aria-label="`${table.domainObject.name} table content`"
>
<tbody>
<telemetry-table-row
<TelemetryTableRow
v-for="(row, rowIndex) in visibleRows"
:key="rowIndex"
:headers="headers"
@ -250,7 +250,7 @@
class="c-telemetry-table__sizing js-telemetry-table__sizing"
:style="sizingTableWidth"
>
<sizing-row :is-editing="isEditing" @change-height="setRowHeight" />
<SizingRow :is-editing="isEditing" @change-height="setRowHeight" />
<tr>
<template v-for="(title, key) in headers" :key="key">
<th
@ -263,7 +263,7 @@
</th>
</template>
</tr>
<telemetry-table-row
<TelemetryTableRow
v-for="(sizingRowData, objectKeyString) in sizingRows"
:key="objectKeyString"
:headers="headers"
@ -273,7 +273,7 @@
@row-context-click="updateViewContext"
/>
</table>
<table-footer-indicator
<TableFooterIndicator
class="c-telemetry-table__footer"
:marked-rows="markedRows.length"
:total-rows="totalNumberOfRows"

View File

@ -32,13 +32,13 @@
>
<ConductorModeIcon class="c-conductor__mode-icon" />
<div class="c-compact-tc__setting-value u-fade-truncate">
<conductor-mode :mode="mode" :read-only="true" />
<conductor-clock :read-only="true" />
<conductor-time-system :read-only="true" />
<ConductorMode :mode="mode" :read-only="true" />
<ConductorClock :read-only="true" />
<ConductorTimeSystem :read-only="true" />
</div>
<conductor-inputs-fixed v-if="isFixed" :input-bounds="viewBounds" :read-only="true" />
<conductor-inputs-realtime v-else :input-bounds="viewBounds" :read-only="true" />
<conductor-axis
<ConductorInputsFixed v-if="isFixed" :input-bounds="viewBounds" :read-only="true" />
<ConductorInputsRealtime v-else :input-bounds="viewBounds" :read-only="true" />
<ConductorAxis
v-if="isFixed"
class="c-conductor__ticks"
:view-bounds="viewBounds"
@ -55,7 +55,7 @@
aria-label="Time Conductor Settings"
></div>
<conductor-pop-up
<ConductorPopUp
v-if="showConductorPopup"
ref="conductorPopup"
:bottom="false"

View File

@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<template>
<time-popup-fixed
<TimePopupFixed
v-if="readOnly === false"
:input-bounds="bounds"
:input-time-system="timeSystem"

View File

@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<template>
<time-popup-realtime
<TimePopupRealtime
v-if="readOnly === false"
:offsets="offsets"
@focus="$event.target.select()"

View File

@ -1,6 +1,6 @@
<template>
<div class="c-tc-input-popup" :class="popupClasses" :style="position">
<div class="c-tc-input-popup__options">
<div class="c-tc-input-popup__options" aria-label="Time Conductor Options">
<IndependentMode
v-if="isIndependent"
class="c-conductor__mode-select"
@ -44,14 +44,14 @@
:button-css-class="'c-icon-button'"
/>
</div>
<conductor-inputs-fixed
<ConductorInputsFixed
v-if="isFixed"
:input-bounds="bounds"
:object-path="objectPath"
@bounds-updated="saveFixedBounds"
@dismiss-inputs-fixed="dismiss"
/>
<conductor-inputs-realtime
<ConductorInputsRealtime
v-else
:input-bounds="bounds"
:object-path="objectPath"

View File

@ -17,9 +17,9 @@
autocorrect="off"
spellcheck="false"
aria-label="Start date"
@change="validateAllBounds('startDate')"
@input="validateAllBounds('startDate')"
/>
<date-picker
<DatePicker
v-if="isUTCBased"
class="c-ctrl-wrapper--menus-right"
:default-date-time="formattedBounds.start"
@ -37,7 +37,7 @@
autocorrect="off"
spellcheck="false"
aria-label="Start time"
@change="validateAllBounds('startDate')"
@input="validateAllBounds('startDate')"
/>
</div>
@ -54,9 +54,9 @@
autocorrect="off"
spellcheck="false"
aria-label="End date"
@change="validateAllBounds('endDate')"
@input="validateAllBounds('endDate')"
/>
<date-picker
<DatePicker
v-if="isUTCBased"
class="c-ctrl-wrapper--menus-left"
:default-date-time="formattedBounds.end"
@ -74,7 +74,7 @@
autocorrect="off"
spellcheck="false"
aria-label="End time"
@change="validateAllBounds('endDate')"
@input="validateAllBounds('endDate')"
/>
</div>
@ -83,12 +83,12 @@
class="c-button c-button--major icon-check"
:disabled="isDisabled"
aria-label="Submit time bounds"
@click.prevent="submit"
@click.prevent="handleFormSubmission(true)"
></button>
<button
class="c-button icon-x"
aria-label="Discard changes and close time popup"
@click.prevent="hide"
@click.prevent="handleFormSubmission(true)"
></button>
</div>
</div>
@ -219,18 +219,21 @@ export default {
return false;
}
},
submit() {
handleFormSubmission(shouldDismiss) {
// Validate bounds before submission
this.validateAllBounds('startDate');
this.validateAllBounds('endDate');
this.submitForm(!this.isDisabled);
},
submitForm(dismiss) {
// Allow Vue model to catch up to user input.
// Submitting form will cause validation messages to display (but only if triggered by button click)
this.$nextTick(() => this.setBoundsFromView(dismiss));
// Submit the form if it's valid
if (!this.isDisabled) {
this.setBoundsFromView(shouldDismiss);
}
},
validateAllBounds(ref) {
this.isDisabled = false; // Reset isDisabled at the start of validation
if (!this.areBoundsFormatsValid()) {
this.isDisabled = true;
return false;
}
@ -305,7 +308,6 @@ export default {
} else {
input.setCustomValidity('');
input.title = '';
this.isDisabled = false;
}
this.$refs.fixedDeltaInput.reportValidity();

View File

@ -21,7 +21,7 @@
-->
<template>
<swim-lane
<SwimLane
:icon-class="item.type.definition.cssClass"
:status="status"
:min-height="item.height"
@ -33,7 +33,7 @@
{{ item.domainObject.name }}
</template>
<template #object>
<object-view
<ObjectView
ref="objectView"
class="u-contents"
:default-object="item.domainObject"
@ -41,7 +41,7 @@
@change-action-collection="setActionCollection"
/>
</template>
</swim-lane>
</SwimLane>
</template>
<script>

View File

@ -22,22 +22,22 @@
<template>
<div ref="timelineHolder" class="c-timeline-holder">
<swim-lane v-for="timeSystemItem in timeSystems" :key="timeSystemItem.timeSystem.key">
<SwimLane v-for="timeSystemItem in timeSystems" :key="timeSystemItem.timeSystem.key">
<template #label>
{{ timeSystemItem.timeSystem.name }}
</template>
<template #object>
<timeline-axis
<TimelineAxis
:bounds="timeSystemItem.bounds"
:time-system="timeSystemItem.timeSystem"
:content-height="height"
:rendering-engine="'svg'"
/>
</template>
</swim-lane>
</SwimLane>
<div ref="contentHolder" class="c-timeline__objects">
<timeline-object-view
<TimelineObjectView
v-for="item in items"
:key="item.keyString"
class="c-timeline__content js-timeline__content"

View File

@ -23,7 +23,7 @@
<template>
<div ref="timelistHolder" :class="listTypeClass">
<template v-if="isExpanded">
<expanded-view-item
<ExpandedViewItem
v-for="item in sortedItems"
:key="item.key"
:name="item.name"
@ -42,7 +42,7 @@
<table class="c-table__body js-table__body">
<thead class="c-table__header">
<tr>
<list-header
<ListHeader
v-for="headerItem in headerItems"
:key="headerItem.property"
:direction="getSortDirection(headerItem)"
@ -56,7 +56,7 @@
</tr>
</thead>
<tbody>
<list-item
<ListItem
v-for="item in sortedItems"
:key="item.key"
:class="{ '--is-in-progress': persistedActivityStates[item.id] === 'in-progress' }"

View File

@ -61,7 +61,7 @@
<span v-else>{{ sortOrderOptions[sortOrderIndex].label }}</span>
</div>
</li>
<event-properties
<EventProperties
v-for="type in eventTypes"
:key="type.prefix"
:label="type.label"
@ -73,7 +73,7 @@
<div class="c-inspect-properties">
<ul class="c-inspect-properties__section">
<div class="c-inspect-properties_header" title="'Filters'">Filtering</div>
<filtering @updated="eventPropertiesUpdated" />
<Filtering @updated="eventPropertiesUpdated" />
</ul>
</div>
</div>

View File

@ -24,7 +24,7 @@
<table class="c-table__body js-table__body">
<thead class="c-table__header">
<tr>
<list-header
<ListHeader
v-for="headerItem in headerItems"
:key="headerItem.property"
:direction="sortBy === headerItem.property ? ascending : headerItem.defaultDirection"
@ -38,7 +38,7 @@
</tr>
</thead>
<tbody>
<list-item
<ListItem
v-for="item in sortedItems"
:key="item.key"
:item="item"

View File

@ -64,7 +64,7 @@
:aria-label="`${ariaLabel} Controls`"
>
<div v-if="supportsIndependentTime" class="c-conductor-holder--compact">
<independent-time-conductor :domain-object="domainObject" :object-path="objectPath" />
<IndependentTimeConductor :domain-object="domainObject" :object-path="objectPath" />
</div>
<NotebookMenuSwitcher
v-if="notebookEnabled"
@ -94,7 +94,7 @@
</div>
</div>
<object-view
<ObjectView
ref="objectView"
class="c-so-view__object-view js-object-view js-notebook-snapshot-item"
:show-edit-view="showEditView"

View File

@ -28,7 +28,7 @@
role="navigation"
>
<li v-for="pathObject in orderedPath" :key="pathObject.key" class="c-location__item">
<object-label
<ObjectLabel
:domain-object="pathObject.domainObject"
:object-path="pathObject.objectPath"
:read-only="readOnly"

View File

@ -22,7 +22,7 @@
<template>
<div class="c-inspector js-inspector">
<object-name />
<ObjectName />
<InspectorTabs :is-editing="isEditing" @select-tab="selectTab" />
<InspectorViews :selected-tab="selectedTab" />
</div>

View File

@ -49,7 +49,7 @@
:title="`Click to ${headExpanded ? 'collapse' : 'expand'} items`"
@click="toggleShellHead"
></button>
<notification-banner />
<NotificationBanner />
<div class="l-shell__head-section l-shell__controls">
<button
class="c-icon-button c-icon-button--major icon-new-window"
@ -67,13 +67,13 @@
@click="fullScreenToggle"
></button>
</div>
<app-logo />
<AppLogo />
</div>
<div class="l-shell__drawer c-drawer c-drawer--push c-drawer--align-top"></div>
<multipane class="l-shell__main" :class="[resizingClass]" type="horizontal">
<pane
<Multipane class="l-shell__main" :class="[resizingClass]" type="horizontal">
<Pane
class="l-shell__pane-tree"
handle="after"
label="Browse"
@ -96,16 +96,16 @@
@click="handleSyncTreeNavigation"
></button>
</template>
<multipane type="vertical">
<pane>
<mct-tree
<Multipane type="vertical">
<Pane>
<MctTree
ref="mctTree"
:sync-tree-navigation="triggerSync"
:reset-tree-navigation="triggerReset"
class="l-shell__tree"
/>
</pane>
<pane
</Pane>
<Pane
handle="before"
label="Recently Viewed"
:persist-position="true"
@ -127,18 +127,18 @@
@click="handleClearRecentObjects"
></button>
</template>
</pane>
</multipane>
</pane>
<pane class="l-shell__pane-main" role="main">
<browse-bar
</Pane>
</Multipane>
</Pane>
<Pane class="l-shell__pane-main" role="main">
<BrowseBar
ref="browseBar"
class="l-shell__main-view-browse-bar"
:action-collection="actionCollection"
@sync-tree-navigation="handleSyncTreeNavigation"
/>
<toolbar v-if="toolbar" class="l-shell__toolbar" />
<object-view
<Toolbar v-if="toolbar" class="l-shell__toolbar" />
<ObjectView
ref="browseObject"
class="l-shell__main-container js-main-container js-notebook-snapshot-item"
data-selectable
@ -150,8 +150,8 @@
class="l-shell__time-conductor"
aria-label="Global Time Conductor"
/>
</pane>
<pane
</Pane>
<Pane
class="l-shell__pane-inspector l-pane--holds-multipane"
handle="before"
label="Inspect"
@ -161,8 +161,8 @@
@end-resizing="onEndResizing"
>
<Inspector ref="inspector" :is-editing="isEditing" />
</pane>
</multipane>
</Pane>
</Multipane>
</div>
</template>

View File

@ -53,7 +53,7 @@
v-if="supportsIndependentTime"
class="c-conductor-holder--compact l-shell__main-independent-time-conductor"
>
<independent-time-conductor
<IndependentTimeConductor
:domain-object="domainObject"
:object-path="openmct.router.path"
/>

View File

@ -28,7 +28,7 @@
}"
>
<div ref="search" class="c-tree-and-search__search">
<search
<Search
v-show="isSelectorTree"
ref="shell-search"
class="c-search"
@ -81,7 +81,7 @@
@scroll="updateVisibleItems()"
>
<div :style="childrenHeightStyles">
<tree-item
<TreeItem
v-for="(treeItem, index) in visibleItems"
:key="`${treeItem.navigationPath}-${index}-${treeItem.object.name}`"
:node="treeItem"

View File

@ -22,7 +22,7 @@
<template>
<div class="c-tree-and-search l-shell__tree">
<ul class="c-tree-and-search__tree c-tree c-tree__scrollable" aria-label="Recent Objects">
<recent-objects-list-item
<RecentObjectsListItem
v-for="recentObject in recentObjects"
:key="recentObject.navigationPath"
:object-path="recentObject.objectPath"

View File

@ -41,7 +41,7 @@
@click.capture="itemClick"
@contextmenu.capture="handleContextMenu"
>
<view-control
<ViewControl
ref="action"
class="c-tree__item__view-control"
:domain-object="node.object"
@ -49,7 +49,7 @@
:enabled="!activeSearch && hasComposition"
@input="itemAction()"
/>
<object-label
<ObjectLabel
ref="objectLabel"
:domain-object="node.object"
:object-path="node.objectPath"

View File

@ -23,7 +23,7 @@
<template>
<div ref="GrandSearch" aria-label="OpenMCT Search" class="c-gsearch" role="search">
<SearchResultsDropDown ref="searchResultsDropDown" />
<search
<Search
ref="shell-search"
class="c-gsearch__input"
:value="searchValue"

View File

@ -41,7 +41,7 @@
aria-label="Object Results"
>
<div class="c-gsearch__results-section-title">Object Results</div>
<object-search-result
<ObjectSearchResult
v-for="objectResult in objectResults"
:key="openmct.objects.makeKeyString(objectResult.identifier)"
:result="objectResult"
@ -56,7 +56,7 @@
aria-label="Annotation Results"
>
<div class="c-gsearch__results-section-title">Annotation Results</div>
<annotation-search-result
<AnnotationSearchResult
v-for="annotationResult in annotationResults"
:key="makeKeyForAnnotationResult(annotationResult)"
:result="annotationResult"
@ -65,7 +65,7 @@
</div>
<div v-if="searchLoading" class="c-gsearch__result-pane-msg">
<div class="hint">Searching...</div>
<progress-bar :model="{ progressPerc: null }" />
<ProgressBar :model="{ progressPerc: null }" />
</div>
<div
v-if="

View File

@ -39,7 +39,7 @@
>{{ getLinkProps.text }}</span
>
<progress-bar
<ProgressBar
v-if="activeModel.progressPerc"
class="c-message-banner__progress-bar"
:model="activeModel"

View File

@ -30,7 +30,7 @@
</div>
</div>
<div class="l-browse-bar__end">
<view-switcher :v-if="!hideViewSwitcher" :views="views" :current-view="currentView" />
<ViewSwitcher :v-if="!hideViewSwitcher" :views="views" :current-view="currentView" />
<NotebookMenuSwitcher
:domain-object="domainObject"
:object-path="objectPath"