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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@
<template> <template>
<div class="c-tree-and-search l-shell__tree"> <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"> <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" v-for="recentObject in recentObjects"
:key="recentObject.navigationPath" :key="recentObject.navigationPath"
:object-path="recentObject.objectPath" :object-path="recentObject.objectPath"

View File

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

View File

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

View File

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

View File

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

View File

@ -30,7 +30,7 @@
</div> </div>
</div> </div>
<div class="l-browse-bar__end"> <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 <NotebookMenuSwitcher
:domain-object="domainObject" :domain-object="domainObject"
:object-path="objectPath" :object-path="objectPath"