mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
test(e2e): stabilize flaky imagery tests (#7765)
This commit is contained in:
parent
1fae0a6ad5
commit
689f7cc815
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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 =
|
||||
|
@ -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())) {
|
||||
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())) {
|
||||
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 });
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<mct-tree
|
||||
<MctTree
|
||||
:is-selector-tree="true"
|
||||
:initial-selection="model.parent"
|
||||
@tree-item-selection="handleItemSelection"
|
||||
|
@ -36,7 +36,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<lad-row
|
||||
<LadRow
|
||||
v-for="ladRow in items"
|
||||
:key="ladRow.key"
|
||||
:domain-object="ladRow.domainObject"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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)"
|
||||
/>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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'"
|
||||
|
@ -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"
|
||||
|
@ -35,7 +35,7 @@
|
||||
role="group"
|
||||
@dragstart="initDrag"
|
||||
>
|
||||
<object-frame
|
||||
<ObjectFrame
|
||||
v-if="domainObject"
|
||||
ref="objectFrame"
|
||||
:domain-object="domainObject"
|
||||
|
@ -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"
|
||||
|
@ -73,7 +73,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<list-item
|
||||
<ListItem
|
||||
v-for="item in sortedItems"
|
||||
:key="item.objectKeyString"
|
||||
:item="item"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -33,7 +33,7 @@
|
||||
:key="pathObject.key"
|
||||
class="c-location__item"
|
||||
>
|
||||
<object-label
|
||||
<ObjectLabel
|
||||
:domain-object="pathObject.domainObject"
|
||||
:object-path="pathObject.objectPath"
|
||||
/>
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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 }"
|
||||
|
@ -38,7 +38,7 @@
|
||||
</span>
|
||||
<span class="c-indicator__count">{{ notifications.length }}</span>
|
||||
|
||||
<notifications-list
|
||||
<NotificationsList
|
||||
v-if="showNotificationsOverlay"
|
||||
:notifications="notifications"
|
||||
@close="toggleNotificationsList"
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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]"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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()"
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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' }"
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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="
|
||||
|
@ -39,7 +39,7 @@
|
||||
>{{ getLinkProps.text }}</span
|
||||
>
|
||||
|
||||
<progress-bar
|
||||
<ProgressBar
|
||||
v-if="activeModel.progressPerc"
|
||||
class="c-message-banner__progress-bar"
|
||||
:model="activeModel"
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user