mirror of
https://github.com/nasa/openmct.git
synced 2025-05-07 11:08:34 +00:00
Imagery layers (#4968)
* Moved imagery controls to a separate component * Zoom pan controls moved to component * Implement adjustments to encapsulate state into ImageryControls * Track modifier key pressed for layouts * image control popup open/close fix * Styling for imagery local controls Co-authored-by: Michael Rogers <contact@mhrogers.com> Co-authored-by: Shefali Joshi <simplyrender@gmail.com> Co-authored-by: Andrew Henry <akhenry@gmail.com> Co-authored-by: Scott Bell <scott@traclabs.com> Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com> Co-authored-by: unlikelyzero <jchill2@gmail.com> Co-authored-by: Jamie Vigliotta <jamie.j.vigliotta@nasa.gov> Co-authored-by: John Hill <john.c.hill@nasa.gov>
This commit is contained in:
parent
59c0da1b57
commit
111b0d0d68
@ -1 +1 @@
|
|||||||
{"openmct":{"21338566-d472-4377-aed1-21b79272c8de":{"identifier":{"key":"21338566-d472-4377-aed1-21b79272c8de","namespace":""},"name":"Performance Display Layout","type":"layout","composition":[{"key":"644c2e47-2903-475f-8a4a-6be1588ee02f","namespace":""}],"configuration":{"items":[{"width":32,"height":18,"x":1,"y":1,"identifier":{"key":"644c2e47-2903-475f-8a4a-6be1588ee02f","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"5aeb5a71-3149-41ed-9d8a-d34b0a18b053"}],"layoutGrid":[10,10]},"modified":1652228997384,"location":"mine","persisted":1652228997384},"644c2e47-2903-475f-8a4a-6be1588ee02f":{"identifier":{"key":"644c2e47-2903-475f-8a4a-6be1588ee02f","namespace":""},"name":"Performance Example Imagery","type":"example.imagery","configuration":{"imageLocation":"","imageLoadDelayInMilliSeconds":20000,"imageSamples":[]},"telemetry":{"values":[{"name":"Name","key":"name"},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2}},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1}},{"name":"Image","key":"url","format":"image","hints":{"image":1}},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1}}]},"modified":1652228997375,"location":"21338566-d472-4377-aed1-21b79272c8de","persisted":1652228997375}},"rootId":"21338566-d472-4377-aed1-21b79272c8de"}
|
{"openmct":{"b3cee102-86dd-4c0a-8eec-4d5d276f8691":{"identifier":{"key":"b3cee102-86dd-4c0a-8eec-4d5d276f8691","namespace":""},"name":"Performance Display Layout","type":"layout","composition":[{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""}],"configuration":{"items":[{"width":32,"height":18,"x":12,"y":9,"identifier":{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"23ca351d-a67d-46aa-a762-290eb742d2f1"}],"layoutGrid":[10,10]},"modified":1654299875432,"location":"mine","persisted":1654299878751},"9666e7b4-be0c-47a5-94b8-99accad7155e":{"identifier":{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""},"name":"Performance Example Imagery","type":"example.imagery","configuration":{"imageLocation":"","imageLoadDelayInMilliSeconds":20000,"imageSamples":[],"layers":[{"source":"dist/imagery/example-imagery-layer-16x9.png","name":"16:9","visible":false},{"source":"dist/imagery/example-imagery-layer-safe.png","name":"Safe","visible":false},{"source":"dist/imagery/example-imagery-layer-scale.png","name":"Scale","visible":false}]},"telemetry":{"values":[{"name":"Name","key":"name"},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2}},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1}},{"name":"Image","key":"url","format":"image","hints":{"image":1},"layers":[{"source":"dist/imagery/example-imagery-layer-16x9.png","name":"16:9"},{"source":"dist/imagery/example-imagery-layer-safe.png","name":"Safe"},{"source":"dist/imagery/example-imagery-layer-scale.png","name":"Scale"}]},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1}}]},"modified":1654299840077,"location":"b3cee102-86dd-4c0a-8eec-4d5d276f8691","persisted":1654299840078}},"rootId":"b3cee102-86dd-4c0a-8eec-4d5d276f8691"}
|
@ -164,7 +164,7 @@ test.describe('Performance tests', () => {
|
|||||||
console.log('jpgResourceTiming ' + JSON.stringify(jpgResourceTiming));
|
console.log('jpgResourceTiming ' + JSON.stringify(jpgResourceTiming));
|
||||||
|
|
||||||
// Click Close Icon
|
// Click Close Icon
|
||||||
await page.locator('.c-click-icon').click();
|
await page.locator('[aria-label="Close"]').click();
|
||||||
await page.evaluate(() => window.performance.mark("view-large-close-button"));
|
await page.evaluate(() => window.performance.mark("view-large-close-button"));
|
||||||
|
|
||||||
//await client.send('HeapProfiler.enable');
|
//await client.send('HeapProfiler.enable');
|
||||||
|
@ -32,7 +32,6 @@ const { expect } = require('@playwright/test');
|
|||||||
test.describe('Example Imagery', () => {
|
test.describe('Example Imagery', () => {
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
page.on('console', msg => console.log(msg.text()));
|
|
||||||
//Go to baseURL
|
//Go to baseURL
|
||||||
await page.goto('/', { waitUntil: 'networkidle' });
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
@ -61,19 +60,19 @@ test.describe('Example Imagery', () => {
|
|||||||
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 }) => {
|
||||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
const deltaYStep = 100; //equivalent to 1x zoom
|
const deltaYStep = 100; //equivalent to 1x zoom
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
|
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
// zoom in
|
// zoom in
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
await page.mouse.wheel(0, deltaYStep * 2);
|
await page.mouse.wheel(0, deltaYStep * 2);
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
// zoom out
|
// zoom out
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
await page.mouse.wheel(0, -deltaYStep);
|
await page.mouse.wheel(0, -deltaYStep);
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
const imageMouseZoomedOut = await page.locator(backgroundImageSelector).boundingBox();
|
const imageMouseZoomedOut = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
|
|
||||||
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
||||||
@ -88,11 +87,11 @@ test.describe('Example Imagery', () => {
|
|||||||
const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];
|
const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];
|
||||||
|
|
||||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
|
|
||||||
// zoom in
|
// zoom in
|
||||||
await page.mouse.wheel(0, deltaYStep * 2);
|
await page.mouse.wheel(0, deltaYStep * 2);
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
const zoomedBoundingBox = await bgImageLocator.boundingBox();
|
const zoomedBoundingBox = await bgImageLocator.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;
|
||||||
@ -151,22 +150,22 @@ test.describe('Example Imagery', () => {
|
|||||||
|
|
||||||
test('Can use + - buttons to zoom on the image', async ({ page }) => {
|
test('Can use + - buttons to zoom on the image', async ({ page }) => {
|
||||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
const zoomInBtn = page.locator('.t-btn-zoom-in');
|
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
|
||||||
const zoomOutBtn = page.locator('.t-btn-zoom-out');
|
const zoomOutBtn = page.locator('.t-btn-zoom-out').nth(0);
|
||||||
const initialBoundingBox = await bgImageLocator.boundingBox();
|
const initialBoundingBox = await bgImageLocator.boundingBox();
|
||||||
|
|
||||||
await zoomInBtn.click();
|
await zoomInBtn.click();
|
||||||
await zoomInBtn.click();
|
await zoomInBtn.click();
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
const zoomedInBoundingBox = await bgImageLocator.boundingBox();
|
const zoomedInBoundingBox = await bgImageLocator.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);
|
||||||
|
|
||||||
await zoomOutBtn.click();
|
await zoomOutBtn.click();
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
const zoomedOutBoundingBox = await bgImageLocator.boundingBox();
|
const zoomedOutBoundingBox = await bgImageLocator.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);
|
||||||
@ -176,18 +175,18 @@ test.describe('Example Imagery', () => {
|
|||||||
test('Can use the reset button to reset the image', async ({ page }) => {
|
test('Can use the reset button to reset the image', async ({ page }) => {
|
||||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
|
|
||||||
const zoomInBtn = page.locator('.t-btn-zoom-in');
|
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
|
||||||
const zoomResetBtn = page.locator('.t-btn-zoom-reset');
|
const zoomResetBtn = page.locator('.t-btn-zoom-reset').nth(0);
|
||||||
const initialBoundingBox = await bgImageLocator.boundingBox();
|
const initialBoundingBox = await bgImageLocator.boundingBox();
|
||||||
|
|
||||||
await zoomInBtn.click();
|
await zoomInBtn.click();
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
await zoomInBtn.click();
|
await zoomInBtn.click();
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
|
|
||||||
const zoomedInBoundingBox = await bgImageLocator.boundingBox();
|
const zoomedInBoundingBox = await bgImageLocator.boundingBox();
|
||||||
expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
|
expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
|
||||||
@ -195,7 +194,7 @@ test.describe('Example Imagery', () => {
|
|||||||
|
|
||||||
await zoomResetBtn.click();
|
await zoomResetBtn.click();
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
|
|
||||||
const resetBoundingBox = await bgImageLocator.boundingBox();
|
const resetBoundingBox = await bgImageLocator.boundingBox();
|
||||||
expect.soft(resetBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
|
expect.soft(resetBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
|
||||||
@ -209,18 +208,18 @@ test.describe('Example Imagery', () => {
|
|||||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
const pausePlayButton = page.locator('.c-button.pause-play');
|
const pausePlayButton = page.locator('.c-button.pause-play');
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
|
|
||||||
// open the time conductor drop down
|
// open the time conductor drop down
|
||||||
await page.locator('.c-conductor__controls button.c-mode-button').click();
|
await page.locator('button:has-text("Fixed Timespan")').click();
|
||||||
// Click local clock
|
// Click local clock
|
||||||
await page.locator('.icon-clock >> text=Local Clock').click();
|
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
|
||||||
|
|
||||||
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
|
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
|
||||||
const zoomInBtn = page.locator('.t-btn-zoom-in');
|
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
|
||||||
await zoomInBtn.click();
|
await zoomInBtn.click();
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
|
|
||||||
return expect(pausePlayButton).not.toHaveClass(/is-paused/);
|
return expect(pausePlayButton).not.toHaveClass(/is-paused/);
|
||||||
});
|
});
|
||||||
@ -267,7 +266,7 @@ test('Example Imagery in Display layout', async ({ page }) => {
|
|||||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||||
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
|
||||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
|
|
||||||
// Click previous image button
|
// Click previous image button
|
||||||
const previousImageButton = page.locator('.c-nav--prev');
|
const previousImageButton = page.locator('.c-nav--prev');
|
||||||
@ -279,7 +278,7 @@ test('Example Imagery in Display layout', async ({ page }) => {
|
|||||||
|
|
||||||
// Zoom in
|
// Zoom in
|
||||||
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
|
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
const deltaYStep = 100; // equivalent to 1x zoom
|
const deltaYStep = 100; // equivalent to 1x zoom
|
||||||
await page.mouse.wheel(0, deltaYStep * 2);
|
await page.mouse.wheel(0, deltaYStep * 2);
|
||||||
const zoomedBoundingBox = await bgImageLocator.boundingBox();
|
const zoomedBoundingBox = await bgImageLocator.boundingBox();
|
||||||
@ -287,7 +286,7 @@ test('Example Imagery in Display layout', async ({ page }) => {
|
|||||||
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
|
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
|
||||||
|
|
||||||
// Wait for zoom animation to finish
|
// Wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
||||||
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
|
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
|
||||||
@ -311,11 +310,11 @@ test('Example Imagery in Display layout', async ({ page }) => {
|
|||||||
await page.locator('[data-testid=conductor-modeOption-realtime]').click();
|
await page.locator('[data-testid=conductor-modeOption-realtime]').click();
|
||||||
|
|
||||||
// Zoom in on next image
|
// Zoom in on next image
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
await page.mouse.wheel(0, deltaYStep * 2);
|
await page.mouse.wheel(0, deltaYStep * 2);
|
||||||
|
|
||||||
// Wait for zoom animation to finish
|
// Wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover({trial: true});
|
||||||
const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
||||||
expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
|
expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
|
||||||
|
@ -59,7 +59,8 @@ export default function () {
|
|||||||
object.configuration = {
|
object.configuration = {
|
||||||
imageLocation: '',
|
imageLocation: '',
|
||||||
imageLoadDelayInMilliSeconds: DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS,
|
imageLoadDelayInMilliSeconds: DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS,
|
||||||
imageSamples: []
|
imageSamples: [],
|
||||||
|
layers: []
|
||||||
};
|
};
|
||||||
|
|
||||||
object.telemetry = {
|
object.telemetry = {
|
||||||
@ -90,7 +91,21 @@ export default function () {
|
|||||||
format: 'image',
|
format: 'image',
|
||||||
hints: {
|
hints: {
|
||||||
image: 1
|
image: 1
|
||||||
}
|
},
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
source: 'dist/imagery/example-imagery-layer-16x9.png',
|
||||||
|
name: '16:9'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: 'dist/imagery/example-imagery-layer-safe.png',
|
||||||
|
name: 'Safe'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: 'dist/imagery/example-imagery-layer-scale.png',
|
||||||
|
name: 'Scale'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Image Download Name',
|
name: 'Image Download Name',
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
<div class="c-overlay__outer">
|
<div class="c-overlay__outer">
|
||||||
<button
|
<button
|
||||||
v-if="dismissable"
|
v-if="dismissable"
|
||||||
|
aria-label="Close"
|
||||||
class="c-click-icon c-overlay__close-button icon-x"
|
class="c-click-icon c-overlay__close-button icon-x"
|
||||||
@click="destroy"
|
@click="destroy"
|
||||||
></button>
|
></button>
|
||||||
|
@ -25,8 +25,7 @@
|
|||||||
class="l-layout__frame c-frame"
|
class="l-layout__frame c-frame"
|
||||||
:class="{
|
:class="{
|
||||||
'no-frame': !item.hasFrame,
|
'no-frame': !item.hasFrame,
|
||||||
'u-inspectable': inspectable,
|
'u-inspectable': inspectable
|
||||||
'is-in-small-container': size.width < 600 || size.height < 600
|
|
||||||
}"
|
}"
|
||||||
:style="style"
|
:style="style"
|
||||||
>
|
>
|
||||||
|
@ -9,10 +9,6 @@
|
|||||||
> *:first-child {
|
> *:first-child {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-in-small-container {
|
|
||||||
//background: rgba(blue, 0.1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-frame__move-bar {
|
.c-frame__move-bar {
|
||||||
|
74
src/plugins/imagery/components/FilterSettings.vue
Normal file
74
src/plugins/imagery/components/FilterSettings.vue
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="c-control-menu c-menu--to-left c-menu--has-close-btn c-image-controls c-image-controls--filters"
|
||||||
|
@click="handleClose"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c-image-controls__controls"
|
||||||
|
@click="$event.stopPropagation()"
|
||||||
|
>
|
||||||
|
<span class="c-image-controls__sliders">
|
||||||
|
<div class="c-image-controls__slider-wrapper icon-brightness">
|
||||||
|
<input
|
||||||
|
v-model="filters.brightness"
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="500"
|
||||||
|
@change="notifyFiltersChanged"
|
||||||
|
@input="notifyFiltersChanged"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="c-image-controls__slider-wrapper icon-contrast">
|
||||||
|
<input
|
||||||
|
v-model="filters.contrast"
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="500"
|
||||||
|
@change="notifyFiltersChanged"
|
||||||
|
@input="notifyFiltersChanged"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span class="c-image-controls__reset-btn">
|
||||||
|
<a
|
||||||
|
class="s-icon-button icon-reset t-btn-reset"
|
||||||
|
@click="resetFilters"
|
||||||
|
></a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="c-click-icon icon-x t-btn-close c-switcher-menu__close-button"></button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
brightness: 100,
|
||||||
|
contrast: 100
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClose(e) {
|
||||||
|
const closeButton = e.target.classList.contains('c-switcher-menu__close-button');
|
||||||
|
if (!closeButton) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
notifyFiltersChanged() {
|
||||||
|
this.$emit('filterChanged', this.filters);
|
||||||
|
},
|
||||||
|
resetFilters() {
|
||||||
|
this.filters = {
|
||||||
|
brightness: 100,
|
||||||
|
contrast: 100
|
||||||
|
};
|
||||||
|
this.notifyFiltersChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -21,75 +21,62 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover c-image-controls__controls">
|
<div class="h-local-controls h-local-controls--overlay-content h-local-controls--menus-aligned c-local-controls--show-on-hover">
|
||||||
<div class="c-image-controls__control c-image-controls__zoom icon-magnify">
|
<imagery-view-menu-switcher
|
||||||
<div class="c-button-set c-button-set--strip-h">
|
:icon-class="'icon-brightness'"
|
||||||
<button
|
:title="'Brightness and contrast'"
|
||||||
class="c-button t-btn-zoom-out icon-minus"
|
>
|
||||||
title="Zoom out"
|
<filter-settings @filterChanged="updateFilterValues" />
|
||||||
@click="zoomOut"
|
</imagery-view-menu-switcher>
|
||||||
></button>
|
|
||||||
|
|
||||||
<button
|
<imagery-view-menu-switcher
|
||||||
class="c-button t-btn-zoom-in icon-plus"
|
v-if="layers.length"
|
||||||
title="Zoom in"
|
:icon-class="'icon-layers'"
|
||||||
@click="zoomIn"
|
:title="'Layers'"
|
||||||
></button>
|
>
|
||||||
</div>
|
<layer-settings
|
||||||
|
:layers="layers"
|
||||||
|
@toggleLayerVisibility="toggleLayerVisibility"
|
||||||
|
/>
|
||||||
|
</imagery-view-menu-switcher>
|
||||||
|
|
||||||
<button
|
<zoom-settings
|
||||||
class="c-button t-btn-zoom-lock"
|
class="--hide-if-less-than-220"
|
||||||
title="Lock current zoom and pan across all images"
|
:pan-zoom-locked="panZoomLocked"
|
||||||
:class="{'icon-unlocked': !panZoomLocked, 'icon-lock': panZoomLocked}"
|
:zoom-factor="zoomFactor"
|
||||||
@click="toggleZoomLock"
|
@zoomOut="zoomOut"
|
||||||
></button>
|
@zoomIn="zoomIn"
|
||||||
|
@toggleZoomLock="toggleZoomLock"
|
||||||
|
@handleResetImage="handleResetImage"
|
||||||
|
/>
|
||||||
|
|
||||||
<button
|
<imagery-view-menu-switcher
|
||||||
class="c-button icon-reset t-btn-zoom-reset"
|
class="--show-if-less-than-220"
|
||||||
title="Remove zoom and pan"
|
:icon-class="'icon-magnify'"
|
||||||
@click="handleResetImage"
|
:title="'Zoom settings'"
|
||||||
></button>
|
>
|
||||||
|
<zoom-settings
|
||||||
<span class="c-image-controls__zoom-factor">x{{ formattedZoomFactor }}</span>
|
:pan-zoom-locked="panZoomLocked"
|
||||||
</div>
|
:class="'c-control-menu c-menu--has-close-btn'"
|
||||||
<div class="c-image-controls__control c-image-controls__brightness-contrast">
|
:zoom-factor="zoomFactor"
|
||||||
<span
|
:is-menu="true"
|
||||||
class="c-image-controls__sliders"
|
@zoomOut="zoomOut"
|
||||||
draggable="true"
|
@zoomIn="zoomIn"
|
||||||
@dragstart.stop.prevent
|
@toggleZoomLock="toggleZoomLock"
|
||||||
>
|
@handleResetImage="handleResetImage"
|
||||||
<div class="c-image-controls__input icon-brightness">
|
/>
|
||||||
<input
|
</imagery-view-menu-switcher>
|
||||||
v-model="filters.contrast"
|
|
||||||
type="range"
|
|
||||||
min="0"
|
|
||||||
max="500"
|
|
||||||
@change="notifyFiltersChanged"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="c-image-controls__input icon-contrast">
|
|
||||||
<input
|
|
||||||
v-model="filters.brightness"
|
|
||||||
type="range"
|
|
||||||
min="0"
|
|
||||||
max="500"
|
|
||||||
@change="notifyFiltersChanged"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
<span class="t-reset-btn-holder c-imagery__lc__reset-btn c-image-controls__btn-reset">
|
|
||||||
<button
|
|
||||||
class="c-icon-link icon-reset t-btn-reset"
|
|
||||||
@click="handleResetFilters"
|
|
||||||
></button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import FilterSettings from "./FilterSettings.vue";
|
||||||
|
import LayerSettings from "./LayerSettings.vue";
|
||||||
|
import ZoomSettings from "./ZoomSettings.vue";
|
||||||
|
import ImageryViewMenuSwitcher from "./ImageryViewMenuSwitcher.vue";
|
||||||
|
|
||||||
const DEFAULT_FILTER_VALUES = {
|
const DEFAULT_FILTER_VALUES = {
|
||||||
brightness: '100',
|
brightness: '100',
|
||||||
contrast: '100'
|
contrast: '100'
|
||||||
@ -101,15 +88,27 @@ const ZOOM_STEP = 1;
|
|||||||
const ZOOM_WHEEL_SENSITIVITY_REDUCTION = 0.01;
|
const ZOOM_WHEEL_SENSITIVITY_REDUCTION = 0.01;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
FilterSettings,
|
||||||
|
LayerSettings,
|
||||||
|
ImageryViewMenuSwitcher,
|
||||||
|
ZoomSettings
|
||||||
|
},
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
props: {
|
props: {
|
||||||
|
layers: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
zoomFactor: {
|
zoomFactor: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
imageUrl: {
|
imageUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: () => {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -126,9 +125,6 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
formattedZoomFactor() {
|
|
||||||
return Number.parseFloat(this.zoomFactor).toPrecision(2);
|
|
||||||
},
|
|
||||||
cursorStates() {
|
cursorStates() {
|
||||||
const isPannable = this.altPressed && this.zoomFactor > 1;
|
const isPannable = this.altPressed && this.zoomFactor > 1;
|
||||||
const showCursorZoomIn = this.metaPressed && !this.shiftPressed;
|
const showCursorZoomIn = this.metaPressed && !this.shiftPressed;
|
||||||
@ -270,6 +266,13 @@ export default {
|
|||||||
|
|
||||||
const newScaleFactor = this.zoomFactor + (this.shiftPressed ? -ZOOM_STEP : ZOOM_STEP);
|
const newScaleFactor = this.zoomFactor + (this.shiftPressed ? -ZOOM_STEP : ZOOM_STEP);
|
||||||
this.zoomImage(newScaleFactor, e.clientX, e.clientY);
|
this.zoomImage(newScaleFactor, e.clientX, e.clientY);
|
||||||
|
},
|
||||||
|
toggleLayerVisibility(index) {
|
||||||
|
this.$emit('toggleLayerVisibility', index);
|
||||||
|
},
|
||||||
|
updateFilterValues(filters) {
|
||||||
|
this.filters = filters;
|
||||||
|
this.notifyFiltersChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -28,34 +28,34 @@
|
|||||||
@keydown="arrowDownHandler"
|
@keydown="arrowDownHandler"
|
||||||
@mouseover="focusElement"
|
@mouseover="focusElement"
|
||||||
>
|
>
|
||||||
<div class="c-imagery__main-image-wrapper has-local-controls">
|
<div
|
||||||
|
class="c-imagery__main-image-wrapper has-local-controls"
|
||||||
|
:class="imageWrapperStyle"
|
||||||
|
@mousedown="handlePanZoomClick"
|
||||||
|
>
|
||||||
<ImageControls
|
<ImageControls
|
||||||
ref="imageControls"
|
ref="imageControls"
|
||||||
:zoom-factor="zoomFactor"
|
:zoom-factor="zoomFactor"
|
||||||
:image-url="imageUrl"
|
:image-url="imageUrl"
|
||||||
|
:layers="layers"
|
||||||
@resetImage="resetImage"
|
@resetImage="resetImage"
|
||||||
@panZoomUpdated="handlePanZoomUpdate"
|
@panZoomUpdated="handlePanZoomUpdate"
|
||||||
@filtersUpdated="setFilters"
|
@filtersUpdated="setFilters"
|
||||||
@cursorsUpdated="setCursorStates"
|
@cursorsUpdated="setCursorStates"
|
||||||
@startPan="startPan"
|
@startPan="startPan"
|
||||||
|
@toggleLayerVisibility="toggleLayerVisibility"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref="imageBG"
|
ref="imageBG"
|
||||||
class="c-imagery__main-image__bg"
|
class="c-imagery__main-image__bg"
|
||||||
:class="{
|
|
||||||
'paused unnsynced': isPaused && !isFixed,
|
|
||||||
'stale': false,
|
|
||||||
'pannable': cursorStates.isPannable,
|
|
||||||
'cursor-zoom-in': cursorStates.showCursorZoomIn,
|
|
||||||
'cursor-zoom-out': cursorStates.showCursorZoomOut
|
|
||||||
}"
|
|
||||||
@click="expand"
|
@click="expand"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="zoomFactor > 1"
|
v-if="zoomFactor > 1"
|
||||||
class="c-imagery__hints"
|
class="c-imagery__hints"
|
||||||
>{{ formatImageAltText }}</div>
|
>
|
||||||
|
{{ formatImageAltText }}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="focusedImageWrapper"
|
ref="focusedImageWrapper"
|
||||||
class="image-wrapper"
|
class="image-wrapper"
|
||||||
@ -65,6 +65,13 @@
|
|||||||
}"
|
}"
|
||||||
@mousedown="handlePanZoomClick"
|
@mousedown="handlePanZoomClick"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-for="(layer, index) in visibleLayers"
|
||||||
|
:key="index"
|
||||||
|
class="layer-image s-image-layer c-imagery__layer-image js-layer-image"
|
||||||
|
:style="getVisibleLayerStyles(layer)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
<img
|
<img
|
||||||
ref="focusedImage"
|
ref="focusedImage"
|
||||||
class="c-imagery__main-image__image js-imageryView-image "
|
class="c-imagery__main-image__image js-imageryView-image "
|
||||||
@ -81,25 +88,7 @@
|
|||||||
ref="focusedImageElement"
|
ref="focusedImageElement"
|
||||||
class="c-imagery__main-image__background-image"
|
class="c-imagery__main-image__background-image"
|
||||||
:draggable="!isSelectable"
|
:draggable="!isSelectable"
|
||||||
:style="{
|
:style="focusImageStyles"
|
||||||
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`,
|
|
||||||
'background-image':
|
|
||||||
`${imageUrl ? (
|
|
||||||
`url(${imageUrl}),
|
|
||||||
repeating-linear-gradient(
|
|
||||||
45deg,
|
|
||||||
transparent,
|
|
||||||
transparent 4px,
|
|
||||||
rgba(125,125,125,.2) 4px,
|
|
||||||
rgba(125,125,125,.2) 8px
|
|
||||||
)`
|
|
||||||
) : ''}`,
|
|
||||||
'transform': `scale(${zoomFactor}) translate(${imageTranslateX}px, ${imageTranslateY}px)`,
|
|
||||||
'transition': `${!pan && animateZoom ? 'transform 250ms ease-in' : 'initial'}`,
|
|
||||||
'width': `${sizedImageWidth}px`,
|
|
||||||
'height': `${sizedImageHeight}px`,
|
|
||||||
|
|
||||||
}"
|
|
||||||
></div>
|
></div>
|
||||||
<Compass
|
<Compass
|
||||||
v-if="shouldDisplayCompass"
|
v-if="shouldDisplayCompass"
|
||||||
@ -260,6 +249,9 @@ export default {
|
|||||||
this.requestCount = 0;
|
this.requestCount = 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
timeFormat: '',
|
||||||
|
layers: [],
|
||||||
|
visibleLayers: [],
|
||||||
durationFormatter: undefined,
|
durationFormatter: undefined,
|
||||||
imageHistory: [],
|
imageHistory: [],
|
||||||
timeSystem: timeSystem,
|
timeSystem: timeSystem,
|
||||||
@ -323,12 +315,41 @@ export default {
|
|||||||
displayThumbnailsSmall() {
|
displayThumbnailsSmall() {
|
||||||
return this.viewHeight > SHOW_THUMBS_THRESHOLD_HEIGHT && this.viewHeight <= SHOW_THUMBS_FULLSIZE_THRESHOLD_HEIGHT;
|
return this.viewHeight > SHOW_THUMBS_THRESHOLD_HEIGHT && this.viewHeight <= SHOW_THUMBS_FULLSIZE_THRESHOLD_HEIGHT;
|
||||||
},
|
},
|
||||||
|
focusImageStyles() {
|
||||||
|
return {
|
||||||
|
'filter': `brightness(${this.filters.brightness}%) contrast(${this.filters.contrast}%)`,
|
||||||
|
'background-image':
|
||||||
|
`${this.imageUrl ? (
|
||||||
|
`url(${this.imageUrl}),
|
||||||
|
repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
transparent,
|
||||||
|
transparent 4px,
|
||||||
|
rgba(125,125,125,.2) 4px,
|
||||||
|
rgba(125,125,125,.2) 8px
|
||||||
|
)`
|
||||||
|
) : ''}`,
|
||||||
|
'transform': `scale(${this.zoomFactor}) translate(${this.imageTranslateX}px, ${this.imageTranslateY}px)`,
|
||||||
|
'transition': `${!this.pan && this.animateZoom ? 'transform 250ms ease-in' : 'initial'}`,
|
||||||
|
'width': `${this.sizedImageWidth}px`,
|
||||||
|
'height': `${this.sizedImageHeight}px`
|
||||||
|
};
|
||||||
|
},
|
||||||
time() {
|
time() {
|
||||||
return this.formatTime(this.focusedImage);
|
return this.formatTime(this.focusedImage);
|
||||||
},
|
},
|
||||||
imageUrl() {
|
imageUrl() {
|
||||||
return this.formatImageUrl(this.focusedImage);
|
return this.formatImageUrl(this.focusedImage);
|
||||||
},
|
},
|
||||||
|
imageWrapperStyle() {
|
||||||
|
return {
|
||||||
|
'cursor-zoom-in': this.cursorStates.showCursorZoomIn,
|
||||||
|
'cursor-zoom-out': this.cursorStates.showCursorZoomOut,
|
||||||
|
'pannable': this.cursorStates.isPannable,
|
||||||
|
'paused unnsynced': this.isPaused && !this.isFixed,
|
||||||
|
'stale': false
|
||||||
|
};
|
||||||
|
},
|
||||||
isImageNew() {
|
isImageNew() {
|
||||||
let cutoff = FIVE_MINUTES;
|
let cutoff = FIVE_MINUTES;
|
||||||
if (this.imageFreshnessOptions) {
|
if (this.imageFreshnessOptions) {
|
||||||
@ -593,8 +614,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.listenTo(this.focusedImageWrapper, 'wheel', this.wheelZoom, this);
|
this.listenTo(this.focusedImageWrapper, 'wheel', this.wheelZoom, this);
|
||||||
|
this.loadVisibleLayers();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
this.persistVisibleLayers();
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
|
|
||||||
if (this.thumbWrapperResizeObserver) {
|
if (this.thumbWrapperResizeObserver) {
|
||||||
@ -625,6 +648,13 @@ export default {
|
|||||||
calculateViewHeight() {
|
calculateViewHeight() {
|
||||||
this.viewHeight = this.$el.clientHeight;
|
this.viewHeight = this.$el.clientHeight;
|
||||||
},
|
},
|
||||||
|
getVisibleLayerStyles(layer) {
|
||||||
|
return {
|
||||||
|
'background-image': `url(${layer.source})`,
|
||||||
|
'transform': `scale(${this.zoomFactor}) translate(${this.imageTranslateX}px, ${this.imageTranslateY}px)`,
|
||||||
|
'transition': `${!this.pan && this.animateZoom ? 'transform 250ms ease-in' : 'initial'}`
|
||||||
|
};
|
||||||
|
},
|
||||||
setTimeContext() {
|
setTimeContext() {
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||||
@ -693,6 +723,37 @@ export default {
|
|||||||
|
|
||||||
return mostRecent[valueKey];
|
return mostRecent[valueKey];
|
||||||
},
|
},
|
||||||
|
loadVisibleLayers() {
|
||||||
|
const metaDataValues = this.metadata.valuesForHints(['image'])[0];
|
||||||
|
this.imageFormat = this.openmct.telemetry.getValueFormatter(metaDataValues);
|
||||||
|
let layersMetadata = metaDataValues.layers;
|
||||||
|
if (layersMetadata) {
|
||||||
|
this.layers = layersMetadata;
|
||||||
|
if (this.domainObject.configuration) {
|
||||||
|
let persistedLayers = this.domainObject.configuration.layers;
|
||||||
|
layersMetadata.forEach((layer) => {
|
||||||
|
const persistedLayer = persistedLayers.find(object => object.name === layer.name);
|
||||||
|
if (persistedLayer) {
|
||||||
|
layer.visible = persistedLayer.visible === true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.visibleLayers = this.layers.filter(layer => layer.visible);
|
||||||
|
} else {
|
||||||
|
this.visibleLayers = [];
|
||||||
|
this.layers.forEach((layer) => {
|
||||||
|
layer.visible = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
persistVisibleLayers() {
|
||||||
|
if (this.domainObject.configuration) {
|
||||||
|
this.openmct.objects.mutate(this.domainObject, 'configuration.layers', this.layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visibleLayers = [];
|
||||||
|
this.layers = [];
|
||||||
|
},
|
||||||
// will subscribe to data for this key if not already done
|
// will subscribe to data for this key if not already done
|
||||||
subscribeToDataForKey(key) {
|
subscribeToDataForKey(key) {
|
||||||
if (this.relatedTelemetry[key].isSubscribed) {
|
if (this.relatedTelemetry[key].isSubscribed) {
|
||||||
@ -1030,7 +1091,6 @@ export default {
|
|||||||
this.resizingWindow = false;
|
this.resizingWindow = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// debounced method
|
|
||||||
clearWheelZoom() {
|
clearWheelZoom() {
|
||||||
this.$refs.imageControls.clearWheelZoom();
|
this.$refs.imageControls.clearWheelZoom();
|
||||||
},
|
},
|
||||||
@ -1093,6 +1153,11 @@ export default {
|
|||||||
},
|
},
|
||||||
setCursorStates(states) {
|
setCursorStates(states) {
|
||||||
this.cursorStates = states;
|
this.cursorStates = states;
|
||||||
|
},
|
||||||
|
toggleLayerVisibility(index) {
|
||||||
|
let isVisible = this.layers[index].visible === true;
|
||||||
|
this.layers[index].visible = !isVisible;
|
||||||
|
this.visibleLayers = this.layers.filter(layer => layer.visible);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
65
src/plugins/imagery/components/ImageryViewMenuSwitcher.vue
Normal file
65
src/plugins/imagery/components/ImageryViewMenuSwitcher.vue
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div class="c-switcher-menu">
|
||||||
|
<button
|
||||||
|
:id="id"
|
||||||
|
class="c-button c-button--menu c-switcher-menu__button"
|
||||||
|
:class="iconClass"
|
||||||
|
:title="title"
|
||||||
|
@click="toggleMenu"
|
||||||
|
>
|
||||||
|
<span class="c-button__label"></span>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-show="showMenu"
|
||||||
|
class="c-switcher-menu__content"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {v4 as uuid} from 'uuid';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
iconClass: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: uuid(),
|
||||||
|
showMenu: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
document.addEventListener('click', this.hideMenu);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
document.removeEventListener('click', this.hideMenu);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleMenu() {
|
||||||
|
this.showMenu = !this.showMenu;
|
||||||
|
},
|
||||||
|
hideMenu(e) {
|
||||||
|
if (this.id === e.target.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showMenu = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
59
src/plugins/imagery/components/LayerSettings.vue
Normal file
59
src/plugins/imagery/components/LayerSettings.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="c-control-menu c-menu--to-left c-menu--has-close-btn c-image-controls"
|
||||||
|
@click="handleClose"
|
||||||
|
>
|
||||||
|
<div class="c-checkbox-list js-checkbox-menu c-menu--to-left c-menu--has-close-btn">
|
||||||
|
<ul
|
||||||
|
@click="$event.stopPropagation()"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="(layer, index) in layers"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-if="layer.visible"
|
||||||
|
:id="index + 'LayerControl'"
|
||||||
|
checked
|
||||||
|
type="checkbox"
|
||||||
|
@change="toggleLayerVisibility(index)"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-else
|
||||||
|
:id="index + 'LayerControl'"
|
||||||
|
type="checkbox"
|
||||||
|
@change="toggleLayerVisibility(index)"
|
||||||
|
>
|
||||||
|
<label :for="index + 'LayerControl'">{{ layer.name }}</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="c-click-icon icon-x t-btn-close c-switcher-menu__close-button"></button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
layers: {
|
||||||
|
type: Array,
|
||||||
|
default() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClose(e) {
|
||||||
|
const closeButton = e.target.classList.contains('c-switcher-menu__close-button');
|
||||||
|
if (!closeButton) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleLayerVisibility(index) {
|
||||||
|
this.$emit('toggleLayerVisibility', index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
89
src/plugins/imagery/components/ZoomSettings.vue
Normal file
89
src/plugins/imagery/components/ZoomSettings.vue
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="c-image-controls__controls-wrapper"
|
||||||
|
@click="handleClose"
|
||||||
|
>
|
||||||
|
<div class="c-image-controls__control c-image-controls__zoom">
|
||||||
|
<div class="c-button-set c-button-set--strip-h">
|
||||||
|
<button
|
||||||
|
class="c-button t-btn-zoom-out icon-minus"
|
||||||
|
title="Zoom out"
|
||||||
|
@click="zoomOut"
|
||||||
|
></button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="c-button t-btn-zoom-in icon-plus"
|
||||||
|
title="Zoom in"
|
||||||
|
@click="zoomIn"
|
||||||
|
></button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="c-button t-btn-zoom-lock"
|
||||||
|
title="Lock current zoom and pan across all images"
|
||||||
|
:class="{'icon-unlocked': !panZoomLocked, 'icon-lock': panZoomLocked}"
|
||||||
|
@click="toggleZoomLock"
|
||||||
|
></button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="c-button icon-reset t-btn-zoom-reset"
|
||||||
|
title="Remove zoom and pan"
|
||||||
|
@click="handleResetImage"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
<div class="c-image-controls__zoom-factor">x{{ formattedZoomFactor }}</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="isMenu"
|
||||||
|
class="c-click-icon icon-x t-btn-close c-switcher-menu__close-button"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
zoomFactor: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
panZoomLocked: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isMenu: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
formattedZoomFactor() {
|
||||||
|
return Number.parseFloat(this.zoomFactor).toPrecision(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClose(e) {
|
||||||
|
const closeButton = e.target.classList.contains('c-switcher-menu__close-button');
|
||||||
|
if (!closeButton) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleResetImage() {
|
||||||
|
this.$emit('handleResetImage');
|
||||||
|
},
|
||||||
|
toggleZoomLock() {
|
||||||
|
this.$emit('toggleZoomLock');
|
||||||
|
},
|
||||||
|
zoomIn() {
|
||||||
|
this.$emit('zoomIn');
|
||||||
|
},
|
||||||
|
zoomOut() {
|
||||||
|
this.$emit('zoomOut');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -28,6 +28,27 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|
||||||
|
&.unnsynced{
|
||||||
|
@include sUnsynced();
|
||||||
|
}
|
||||||
|
|
||||||
|
&.cursor-zoom-in {
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.cursor-zoom-out {
|
||||||
|
cursor: zoom-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pannable {
|
||||||
|
@include cursorGrab();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper {
|
||||||
|
overflow: visible clip;
|
||||||
|
background-image: repeating-linear-gradient(45deg, transparent, transparent 4px, rgba(125, 125, 125, 0.2) 4px, rgba(125, 125, 125, 0.2) 8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-wrapper {
|
.image-wrapper {
|
||||||
@ -45,19 +66,6 @@
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
&.unnsynced{
|
|
||||||
@include sUnsynced();
|
|
||||||
}
|
|
||||||
&.cursor-zoom-in {
|
|
||||||
cursor: zoom-in;
|
|
||||||
}
|
|
||||||
&.cursor-zoom-out {
|
|
||||||
cursor: zoom-out;
|
|
||||||
}
|
|
||||||
&.pannable {
|
|
||||||
@include cursorGrab();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&__background-image {
|
&__background-image {
|
||||||
background-position: center;
|
background-position: center;
|
||||||
@ -77,6 +85,7 @@
|
|||||||
background: rgba(black, 0.2);
|
background: rgba(black, 0.2);
|
||||||
border-radius: $smallCr;
|
border-radius: $smallCr;
|
||||||
padding: 2px $interiorMargin;
|
padding: 2px $interiorMargin;
|
||||||
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: $m;
|
right: $m;
|
||||||
top: $m;
|
top: $m;
|
||||||
@ -146,6 +155,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&__layer-image {
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&__thumbs-wrapper {
|
&__thumbs-wrapper {
|
||||||
display: flex; // Uses row layout
|
display: flex; // Uses row layout
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@ -179,6 +193,50 @@
|
|||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
margin: $interiorMarginSm;
|
margin: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-control-menu {
|
||||||
|
// Controls on left of flex column layout, close btn on right
|
||||||
|
@include menuOuter();
|
||||||
|
|
||||||
|
border-radius: $controlCr;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: $interiorMargin;
|
||||||
|
width: min-content;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-switcher-menu {
|
||||||
|
display: contents;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
// Menu panel
|
||||||
|
top: 28px;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
.c-so-view & {
|
||||||
|
top: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.--width-less-than-220 .--show-if-less-than-220.c-switcher-menu {
|
||||||
|
display: contents !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-image-layer {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
opacity: 0.5;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************** THUMBS */
|
/*************************************** THUMBS */
|
||||||
@ -229,70 +287,36 @@
|
|||||||
/*************************************** IMAGERY LOCAL CONTROLS*/
|
/*************************************** IMAGERY LOCAL CONTROLS*/
|
||||||
.c-imagery {
|
.c-imagery {
|
||||||
.h-local-controls--overlay-content {
|
.h-local-controls--overlay-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: $interiorMargin; top: $interiorMargin;
|
left: $interiorMargin; top: $interiorMargin;
|
||||||
z-index: 70;
|
z-index: 70;
|
||||||
background: $colorLocalControlOvrBg;
|
background: $colorLocalControlOvrBg;
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
max-width: 250px;
|
|
||||||
min-width: 170px;
|
|
||||||
width: 35%;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: $interiorMargin $interiorMarginLg;
|
padding: $interiorMargin $interiorMargin;
|
||||||
|
|
||||||
input[type="range"] {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
&:not(:first-child) {
|
|
||||||
margin-top: $interiorMarginLg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
margin-right: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.s-status-taking-snapshot & {
|
.s-status-taking-snapshot & {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
[class*='--menus-aligned'] {
|
||||||
&__lc {
|
> * + * {
|
||||||
&__reset-btn {
|
button { margin-left: $interiorMarginSm; }
|
||||||
// Span that holds bracket graphics and button
|
|
||||||
$bc: $scrollbarTrackColorBg;
|
|
||||||
|
|
||||||
&:before,
|
|
||||||
&:after {
|
|
||||||
border-right: 1px solid $bc;
|
|
||||||
content:'';
|
|
||||||
display: block;
|
|
||||||
width: 5px;
|
|
||||||
height: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
border-top: 1px solid $bc;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
border-bottom: 1px solid $bc;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-icon-link {
|
|
||||||
color: $colorBtnFg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-image-controls {
|
.c-image-controls {
|
||||||
|
&__controls-wrapper {
|
||||||
|
// Wraps __controls and __close-btn
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
&__controls {
|
&__controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-top: $interiorMargin;
|
margin-top: $interiorMargin;
|
||||||
@ -314,31 +338,67 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__input {
|
|
||||||
// A wrapper is needed to add the type icon to left of each control
|
|
||||||
|
|
||||||
input[type='range'] {
|
|
||||||
//width: 100%; // Do we need this?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__zoom {
|
&__zoom {
|
||||||
> * + * { margin-left: $interiorMargin; }
|
> * + * { margin-left: $interiorMargin; } // Is this used?
|
||||||
}
|
}
|
||||||
|
|
||||||
&__sliders {
|
&--filters {
|
||||||
display: flex;
|
// Styles specific to the brightness and contrast controls
|
||||||
flex: 1 1 auto;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
> * + * {
|
.c-image-controls {
|
||||||
margin-top: 11px;
|
&__sliders {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 80px;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-top: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__slider-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:before { margin-right: $interiorMargin; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&__reset-btn {
|
||||||
|
// Span that holds bracket graphics and button
|
||||||
|
$bc: $scrollbarTrackColorBg;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
border-right: 1px solid $bc;
|
||||||
|
content:'';
|
||||||
|
display: block;
|
||||||
|
width: 5px;
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
border-top: 1px solid $bc;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
border-bottom: 1px solid $bc;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-icon-link {
|
||||||
|
color: $colorBtnFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__btn-reset {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************** BUTTONS */
|
/*************************************** BUTTONS */
|
||||||
@ -383,7 +443,7 @@
|
|||||||
@include cArrowButtonSizing($dimOuter: 48px);
|
@include cArrowButtonSizing($dimOuter: 48px);
|
||||||
border-radius: $controlCr;
|
border-radius: $controlCr;
|
||||||
|
|
||||||
.is-in-small-container & {
|
.--width-less-than-600 & {
|
||||||
@include cArrowButtonSizing($dimOuter: 32px);
|
@include cArrowButtonSizing($dimOuter: 32px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -409,10 +469,6 @@
|
|||||||
background-color: $colorBodyFg;
|
background-color: $colorBodyFg;
|
||||||
}
|
}
|
||||||
|
|
||||||
//[class*='__image-placeholder'] {
|
|
||||||
// display: none;
|
|
||||||
//}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
BIN
src/plugins/imagery/layers/example-imagery-layer-16x9.png
Normal file
BIN
src/plugins/imagery/layers/example-imagery-layer-16x9.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.4 KiB |
BIN
src/plugins/imagery/layers/example-imagery-layer-safe.png
Normal file
BIN
src/plugins/imagery/layers/example-imagery-layer-safe.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
BIN
src/plugins/imagery/layers/example-imagery-layer-scale.png
Normal file
BIN
src/plugins/imagery/layers/example-imagery-layer-scale.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -100,12 +100,24 @@ describe("The Imagery View Layouts", () => {
|
|||||||
location: "parentId",
|
location: "parentId",
|
||||||
modified: 0,
|
modified: 0,
|
||||||
persisted: 0,
|
persisted: 0,
|
||||||
|
configuration: {
|
||||||
|
layers: [{
|
||||||
|
name: '16:9',
|
||||||
|
visible: true
|
||||||
|
}]
|
||||||
|
},
|
||||||
telemetry: {
|
telemetry: {
|
||||||
values: [
|
values: [
|
||||||
{
|
{
|
||||||
"name": "Image",
|
"name": "Image",
|
||||||
"key": "url",
|
"key": "url",
|
||||||
"format": "image",
|
"format": "image",
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
source: location.host + '/images/bg-splash.jpg',
|
||||||
|
name: '16:9'
|
||||||
|
}
|
||||||
|
],
|
||||||
"hints": {
|
"hints": {
|
||||||
"image": 1,
|
"image": 1,
|
||||||
"priority": 3
|
"priority": 3
|
||||||
@ -366,6 +378,18 @@ describe("The Imagery View Layouts", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("on mount should show the any image layers", (done) => {
|
||||||
|
//Looks like we need Vue.nextTick here so that computed properties settle down
|
||||||
|
Vue.nextTick().then(() => {
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
const layerEls = parent.querySelectorAll('.js-layer-image');
|
||||||
|
console.log(layerEls);
|
||||||
|
expect(layerEls.length).toEqual(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should show the clicked thumbnail as the main image", (done) => {
|
it("should show the clicked thumbnail as the main image", (done) => {
|
||||||
//Looks like we need Vue.nextTick here so that computed properties settle down
|
//Looks like we need Vue.nextTick here so that computed properties settle down
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
|
@ -63,8 +63,9 @@
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
.is-in-small-container & {
|
|
||||||
display: none;
|
.--width-less-than-600 & {
|
||||||
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin menuPositioning() {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@mixin menuInner() {
|
@mixin menuInner() {
|
||||||
li {
|
li {
|
||||||
@include cControl();
|
@include cControl();
|
||||||
@ -479,6 +490,10 @@ select {
|
|||||||
&__row {
|
&__row {
|
||||||
> * + * { margin-left: $interiorMargin; }
|
> * + * { margin-left: $interiorMargin; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************** TABS */
|
/******************************************************** TABS */
|
||||||
@ -567,6 +582,7 @@ select {
|
|||||||
/******************************************************** MENUS */
|
/******************************************************** MENUS */
|
||||||
.c-menu {
|
.c-menu {
|
||||||
@include menuOuter();
|
@include menuOuter();
|
||||||
|
@include menuPositioning();
|
||||||
@include menuInner();
|
@include menuInner();
|
||||||
|
|
||||||
&__section-hint {
|
&__section-hint {
|
||||||
@ -590,6 +606,7 @@ select {
|
|||||||
.c-super-menu {
|
.c-super-menu {
|
||||||
// Two column layout, menu items on left with detail of hover element on right
|
// Two column layout, menu items on left with detail of hover element on right
|
||||||
@include menuOuter();
|
@include menuOuter();
|
||||||
|
@include menuPositioning();
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: $interiorMarginLg;
|
padding: $interiorMarginLg;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -1035,6 +1052,14 @@ input[type="range"] {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[class*='--menus-aligned'] {
|
||||||
|
// Contains top level elements that hold dropdown menus
|
||||||
|
// Top level elements use display: contents to allow their menus to compactly align
|
||||||
|
// 03-18-22: used in ImageControls.vue
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-local-controls {
|
.c-local-controls {
|
||||||
|
@ -349,3 +349,22 @@ body.desktop .has-local-controls {
|
|||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************************************************** RESPONSIVE CONTAINERS */
|
||||||
|
@mixin responsiveContainerWidths($dimension) {
|
||||||
|
// 3/21/22: `--width-less-than*` classes set in ObjectView.vue
|
||||||
|
.--show-if-less-than-#{$dimension} {
|
||||||
|
// Hide anything that displays within a given width by default.
|
||||||
|
// `display` property must be set within a more specific class
|
||||||
|
// for the particular item to be displayed.
|
||||||
|
display: none !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.--width-less-than-#{$dimension} {
|
||||||
|
.--hide-if-less-than-#{$dimension} { display: none; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//.--hide-by-default { display: none !important; }
|
||||||
|
@include responsiveContainerWidths('220');
|
||||||
|
@include responsiveContainerWidths('600');
|
||||||
|
@ -118,7 +118,7 @@ mct-plot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-in-small-container & {
|
.--width-less-than-600 & {
|
||||||
.c-control-bar {
|
.c-control-bar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -498,7 +498,7 @@ mct-plot {
|
|||||||
margin-bottom: $interiorMarginSm;
|
margin-bottom: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-in-small-container & {
|
.--width-less-than-600 & {
|
||||||
&.is-legend-hidden {
|
&.is-legend-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ div.c-table {
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-in-small-container & {
|
.--width-less-than-600 & {
|
||||||
&:not(.is-paused) {
|
&:not(.is-paused) {
|
||||||
.c-table-control-bar {
|
.c-table-control-bar {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -21,9 +21,11 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
ref="soView"
|
||||||
class="c-so-view js-notebook-snapshot-item-wrapper"
|
class="c-so-view js-notebook-snapshot-item-wrapper"
|
||||||
:class="[
|
:class="[
|
||||||
statusClass,
|
statusClass,
|
||||||
|
widthClass,
|
||||||
'c-so-view--' + domainObject.type,
|
'c-so-view--' + domainObject.type,
|
||||||
{
|
{
|
||||||
'c-so-view--no-frame': !hasFrame,
|
'c-so-view--no-frame': !hasFrame,
|
||||||
@ -111,6 +113,7 @@ const SIMPLE_CONTENT_TYPES = [
|
|||||||
'hyperlink',
|
'hyperlink',
|
||||||
'conditionWidget'
|
'conditionWidget'
|
||||||
];
|
];
|
||||||
|
const CSS_WIDTH_LESS_STR = '--width-less-than-';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -150,6 +153,7 @@ export default {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
cssClass,
|
cssClass,
|
||||||
|
widthClass: '',
|
||||||
complexContent,
|
complexContent,
|
||||||
notebookEnabled: this.openmct.types.get('notebook'),
|
notebookEnabled: this.openmct.types.get('notebook'),
|
||||||
statusBarItems: [],
|
statusBarItems: [],
|
||||||
@ -168,6 +172,11 @@ export default {
|
|||||||
if (provider) {
|
if (provider) {
|
||||||
this.$refs.objectView.show(this.domainObject, provider.key, false, this.objectPath);
|
this.$refs.objectView.show(this.domainObject, provider.key, false, this.objectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.$refs.soView) {
|
||||||
|
this.soViewResizeObserver = new ResizeObserver(this.resizeSoView);
|
||||||
|
this.soViewResizeObserver.observe(this.$refs.soView);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.removeStatusListener();
|
this.removeStatusListener();
|
||||||
@ -175,6 +184,10 @@ export default {
|
|||||||
if (this.actionCollection) {
|
if (this.actionCollection) {
|
||||||
this.unlistenToActionCollection();
|
this.unlistenToActionCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.soViewResizeObserver) {
|
||||||
|
this.soViewResizeObserver.disconnect();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getSelectionContext() {
|
getSelectionContext() {
|
||||||
@ -207,6 +220,18 @@ export default {
|
|||||||
},
|
},
|
||||||
setStatus(status) {
|
setStatus(status) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
},
|
||||||
|
resizeSoView() {
|
||||||
|
let cW = this.$refs.soView.offsetWidth;
|
||||||
|
let wClass = '';
|
||||||
|
|
||||||
|
if (cW < 220) {
|
||||||
|
wClass = CSS_WIDTH_LESS_STR + '220';
|
||||||
|
} else if (cW < 600) {
|
||||||
|
wClass = CSS_WIDTH_LESS_STR + '600';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.widthClass = wClass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -43,11 +43,11 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-in-small-container &,
|
.--width-less-than-220 &,
|
||||||
.c-fl-frame & {
|
.--width-less-than-600 & {
|
||||||
[class*="__label"] {
|
[class*="__label"] {
|
||||||
// button labels
|
// button labels
|
||||||
display: none;
|
display: none !important;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,6 +141,10 @@
|
|||||||
&.is-status--missing {
|
&.is-status--missing {
|
||||||
border: $borderMissing;
|
border: $borderMissing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leave for debugging
|
||||||
|
//&.--width-less-than-600 { background: rgba(orange, 0.2) !important; }
|
||||||
|
//&.--width-less-than-220 { background: rgba(red, 0.2) !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-angular-ov-wrapper {
|
.l-angular-ov-wrapper {
|
||||||
@ -149,3 +153,5 @@
|
|||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,6 +72,10 @@ const config = {
|
|||||||
transform: function (content) {
|
transform: function (content) {
|
||||||
return content.toString().replace(/dist\//g, '');
|
return content.toString().replace(/dist\//g, '');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 'src/plugins/imagery/layers',
|
||||||
|
to: 'imagery'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user