mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +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));
|
||||
|
||||
// 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 client.send('HeapProfiler.enable');
|
||||
|
@ -32,7 +32,6 @@ const { expect } = require('@playwright/test');
|
||||
test.describe('Example Imagery', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
page.on('console', msg => console.log(msg.text()));
|
||||
//Go to baseURL
|
||||
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 }) => {
|
||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||
const deltaYStep = 100; //equivalent to 1x zoom
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
|
||||
// zoom in
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
await page.mouse.wheel(0, deltaYStep * 2);
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
||||
// zoom out
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
await page.mouse.wheel(0, -deltaYStep);
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
const imageMouseZoomedOut = await page.locator(backgroundImageSelector).boundingBox();
|
||||
|
||||
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
||||
@ -88,11 +87,11 @@ test.describe('Example Imagery', () => {
|
||||
const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];
|
||||
|
||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
|
||||
// zoom in
|
||||
await page.mouse.wheel(0, deltaYStep * 2);
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
const zoomedBoundingBox = await bgImageLocator.boundingBox();
|
||||
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 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 }) => {
|
||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||
await bgImageLocator.hover();
|
||||
const zoomInBtn = page.locator('.t-btn-zoom-in');
|
||||
const zoomOutBtn = page.locator('.t-btn-zoom-out');
|
||||
await bgImageLocator.hover({trial: true});
|
||||
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
|
||||
const zoomOutBtn = page.locator('.t-btn-zoom-out').nth(0);
|
||||
const initialBoundingBox = await bgImageLocator.boundingBox();
|
||||
|
||||
await zoomInBtn.click();
|
||||
await zoomInBtn.click();
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
const zoomedInBoundingBox = await bgImageLocator.boundingBox();
|
||||
expect(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
|
||||
expect(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
|
||||
|
||||
await zoomOutBtn.click();
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
const zoomedOutBoundingBox = await bgImageLocator.boundingBox();
|
||||
expect(zoomedOutBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
|
||||
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 }) => {
|
||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
|
||||
const zoomInBtn = page.locator('.t-btn-zoom-in');
|
||||
const zoomResetBtn = page.locator('.t-btn-zoom-reset');
|
||||
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
|
||||
const zoomResetBtn = page.locator('.t-btn-zoom-reset').nth(0);
|
||||
const initialBoundingBox = await bgImageLocator.boundingBox();
|
||||
|
||||
await zoomInBtn.click();
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
await zoomInBtn.click();
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
|
||||
const zoomedInBoundingBox = await bgImageLocator.boundingBox();
|
||||
expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
|
||||
@ -195,7 +194,7 @@ test.describe('Example Imagery', () => {
|
||||
|
||||
await zoomResetBtn.click();
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
|
||||
const resetBoundingBox = await bgImageLocator.boundingBox();
|
||||
expect.soft(resetBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
|
||||
@ -209,18 +208,18 @@ test.describe('Example Imagery', () => {
|
||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||
const pausePlayButton = page.locator('.c-button.pause-play');
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
|
||||
// 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
|
||||
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/);
|
||||
const zoomInBtn = page.locator('.t-btn-zoom-in');
|
||||
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
|
||||
await zoomInBtn.click();
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
|
||||
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 expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
|
||||
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
|
||||
// Click previous image button
|
||||
const previousImageButton = page.locator('.c-nav--prev');
|
||||
@ -279,7 +278,7 @@ test('Example Imagery in Display layout', async ({ page }) => {
|
||||
|
||||
// Zoom in
|
||||
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
const deltaYStep = 100; // equivalent to 1x zoom
|
||||
await page.mouse.wheel(0, deltaYStep * 2);
|
||||
const zoomedBoundingBox = await bgImageLocator.boundingBox();
|
||||
@ -287,7 +286,7 @@ test('Example Imagery in Display layout', async ({ page }) => {
|
||||
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
|
||||
|
||||
// Wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
||||
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
||||
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();
|
||||
|
||||
// Zoom in on next image
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
await page.mouse.wheel(0, deltaYStep * 2);
|
||||
|
||||
// Wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await bgImageLocator.hover({trial: true});
|
||||
const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
||||
expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
||||
expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
|
||||
|
@ -59,7 +59,8 @@ export default function () {
|
||||
object.configuration = {
|
||||
imageLocation: '',
|
||||
imageLoadDelayInMilliSeconds: DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS,
|
||||
imageSamples: []
|
||||
imageSamples: [],
|
||||
layers: []
|
||||
};
|
||||
|
||||
object.telemetry = {
|
||||
@ -90,7 +91,21 @@ export default function () {
|
||||
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',
|
||||
|
@ -7,6 +7,7 @@
|
||||
<div class="c-overlay__outer">
|
||||
<button
|
||||
v-if="dismissable"
|
||||
aria-label="Close"
|
||||
class="c-click-icon c-overlay__close-button icon-x"
|
||||
@click="destroy"
|
||||
></button>
|
||||
|
@ -25,8 +25,7 @@
|
||||
class="l-layout__frame c-frame"
|
||||
:class="{
|
||||
'no-frame': !item.hasFrame,
|
||||
'u-inspectable': inspectable,
|
||||
'is-in-small-container': size.width < 600 || size.height < 600
|
||||
'u-inspectable': inspectable
|
||||
}"
|
||||
:style="style"
|
||||
>
|
||||
|
@ -9,10 +9,6 @@
|
||||
> *:first-child {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&.is-in-small-container {
|
||||
//background: rgba(blue, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover c-image-controls__controls">
|
||||
<div class="c-image-controls__control c-image-controls__zoom icon-magnify">
|
||||
<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>
|
||||
<div class="h-local-controls h-local-controls--overlay-content h-local-controls--menus-aligned c-local-controls--show-on-hover">
|
||||
<imagery-view-menu-switcher
|
||||
:icon-class="'icon-brightness'"
|
||||
:title="'Brightness and contrast'"
|
||||
>
|
||||
<filter-settings @filterChanged="updateFilterValues" />
|
||||
</imagery-view-menu-switcher>
|
||||
|
||||
<button
|
||||
class="c-button t-btn-zoom-in icon-plus"
|
||||
title="Zoom in"
|
||||
@click="zoomIn"
|
||||
></button>
|
||||
</div>
|
||||
<imagery-view-menu-switcher
|
||||
v-if="layers.length"
|
||||
:icon-class="'icon-layers'"
|
||||
:title="'Layers'"
|
||||
>
|
||||
<layer-settings
|
||||
:layers="layers"
|
||||
@toggleLayerVisibility="toggleLayerVisibility"
|
||||
/>
|
||||
</imagery-view-menu-switcher>
|
||||
|
||||
<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>
|
||||
<zoom-settings
|
||||
class="--hide-if-less-than-220"
|
||||
:pan-zoom-locked="panZoomLocked"
|
||||
:zoom-factor="zoomFactor"
|
||||
@zoomOut="zoomOut"
|
||||
@zoomIn="zoomIn"
|
||||
@toggleZoomLock="toggleZoomLock"
|
||||
@handleResetImage="handleResetImage"
|
||||
/>
|
||||
|
||||
<button
|
||||
class="c-button icon-reset t-btn-zoom-reset"
|
||||
title="Remove zoom and pan"
|
||||
@click="handleResetImage"
|
||||
></button>
|
||||
|
||||
<span class="c-image-controls__zoom-factor">x{{ formattedZoomFactor }}</span>
|
||||
</div>
|
||||
<div class="c-image-controls__control c-image-controls__brightness-contrast">
|
||||
<span
|
||||
class="c-image-controls__sliders"
|
||||
draggable="true"
|
||||
@dragstart.stop.prevent
|
||||
>
|
||||
<div class="c-image-controls__input icon-brightness">
|
||||
<input
|
||||
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>
|
||||
<imagery-view-menu-switcher
|
||||
class="--show-if-less-than-220"
|
||||
:icon-class="'icon-magnify'"
|
||||
:title="'Zoom settings'"
|
||||
>
|
||||
<zoom-settings
|
||||
:pan-zoom-locked="panZoomLocked"
|
||||
:class="'c-control-menu c-menu--has-close-btn'"
|
||||
:zoom-factor="zoomFactor"
|
||||
:is-menu="true"
|
||||
@zoomOut="zoomOut"
|
||||
@zoomIn="zoomIn"
|
||||
@toggleZoomLock="toggleZoomLock"
|
||||
@handleResetImage="handleResetImage"
|
||||
/>
|
||||
</imagery-view-menu-switcher>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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 = {
|
||||
brightness: '100',
|
||||
contrast: '100'
|
||||
@ -101,15 +88,27 @@ const ZOOM_STEP = 1;
|
||||
const ZOOM_WHEEL_SENSITIVITY_REDUCTION = 0.01;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FilterSettings,
|
||||
LayerSettings,
|
||||
ImageryViewMenuSwitcher,
|
||||
ZoomSettings
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
layers: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
zoomFactor: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
imageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: () => {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@ -126,9 +125,6 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
formattedZoomFactor() {
|
||||
return Number.parseFloat(this.zoomFactor).toPrecision(2);
|
||||
},
|
||||
cursorStates() {
|
||||
const isPannable = this.altPressed && this.zoomFactor > 1;
|
||||
const showCursorZoomIn = this.metaPressed && !this.shiftPressed;
|
||||
@ -270,6 +266,13 @@ export default {
|
||||
|
||||
const newScaleFactor = this.zoomFactor + (this.shiftPressed ? -ZOOM_STEP : ZOOM_STEP);
|
||||
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"
|
||||
@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
|
||||
ref="imageControls"
|
||||
:zoom-factor="zoomFactor"
|
||||
:image-url="imageUrl"
|
||||
:layers="layers"
|
||||
@resetImage="resetImage"
|
||||
@panZoomUpdated="handlePanZoomUpdate"
|
||||
@filtersUpdated="setFilters"
|
||||
@cursorsUpdated="setCursorStates"
|
||||
@startPan="startPan"
|
||||
@toggleLayerVisibility="toggleLayerVisibility"
|
||||
/>
|
||||
|
||||
<div
|
||||
ref="imageBG"
|
||||
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"
|
||||
>
|
||||
<div
|
||||
v-if="zoomFactor > 1"
|
||||
class="c-imagery__hints"
|
||||
>{{ formatImageAltText }}</div>
|
||||
>
|
||||
{{ formatImageAltText }}
|
||||
</div>
|
||||
<div
|
||||
ref="focusedImageWrapper"
|
||||
class="image-wrapper"
|
||||
@ -65,6 +65,13 @@
|
||||
}"
|
||||
@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
|
||||
ref="focusedImage"
|
||||
class="c-imagery__main-image__image js-imageryView-image "
|
||||
@ -81,25 +88,7 @@
|
||||
ref="focusedImageElement"
|
||||
class="c-imagery__main-image__background-image"
|
||||
:draggable="!isSelectable"
|
||||
:style="{
|
||||
'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`,
|
||||
|
||||
}"
|
||||
:style="focusImageStyles"
|
||||
></div>
|
||||
<Compass
|
||||
v-if="shouldDisplayCompass"
|
||||
@ -260,6 +249,9 @@ export default {
|
||||
this.requestCount = 0;
|
||||
|
||||
return {
|
||||
timeFormat: '',
|
||||
layers: [],
|
||||
visibleLayers: [],
|
||||
durationFormatter: undefined,
|
||||
imageHistory: [],
|
||||
timeSystem: timeSystem,
|
||||
@ -323,12 +315,41 @@ export default {
|
||||
displayThumbnailsSmall() {
|
||||
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() {
|
||||
return this.formatTime(this.focusedImage);
|
||||
},
|
||||
imageUrl() {
|
||||
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() {
|
||||
let cutoff = FIVE_MINUTES;
|
||||
if (this.imageFreshnessOptions) {
|
||||
@ -593,8 +614,10 @@ export default {
|
||||
}
|
||||
|
||||
this.listenTo(this.focusedImageWrapper, 'wheel', this.wheelZoom, this);
|
||||
this.loadVisibleLayers();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.persistVisibleLayers();
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
if (this.thumbWrapperResizeObserver) {
|
||||
@ -625,6 +648,13 @@ export default {
|
||||
calculateViewHeight() {
|
||||
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() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
@ -693,6 +723,37 @@ export default {
|
||||
|
||||
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
|
||||
subscribeToDataForKey(key) {
|
||||
if (this.relatedTelemetry[key].isSubscribed) {
|
||||
@ -1030,7 +1091,6 @@ export default {
|
||||
this.resizingWindow = false;
|
||||
});
|
||||
},
|
||||
// debounced method
|
||||
clearWheelZoom() {
|
||||
this.$refs.imageControls.clearWheelZoom();
|
||||
},
|
||||
@ -1093,6 +1153,11 @@ export default {
|
||||
},
|
||||
setCursorStates(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;
|
||||
flex-direction: column;
|
||||
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 {
|
||||
@ -45,19 +66,6 @@
|
||||
flex: 1 1 auto;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
&.unnsynced{
|
||||
@include sUnsynced();
|
||||
}
|
||||
&.cursor-zoom-in {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
&.cursor-zoom-out {
|
||||
cursor: zoom-out;
|
||||
}
|
||||
&.pannable {
|
||||
@include cursorGrab();
|
||||
|
||||
}
|
||||
}
|
||||
&__background-image {
|
||||
background-position: center;
|
||||
@ -77,6 +85,7 @@
|
||||
background: rgba(black, 0.2);
|
||||
border-radius: $smallCr;
|
||||
padding: 2px $interiorMargin;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: $m;
|
||||
top: $m;
|
||||
@ -146,6 +155,11 @@
|
||||
}
|
||||
|
||||
|
||||
&__layer-image {
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__thumbs-wrapper {
|
||||
display: flex; // Uses row layout
|
||||
justify-content: flex-end;
|
||||
@ -179,6 +193,50 @@
|
||||
font-size: 0.8em;
|
||||
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 */
|
||||
@ -229,70 +287,36 @@
|
||||
/*************************************** IMAGERY LOCAL CONTROLS*/
|
||||
.c-imagery {
|
||||
.h-local-controls--overlay-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
left: $interiorMargin; top: $interiorMargin;
|
||||
z-index: 70;
|
||||
background: $colorLocalControlOvrBg;
|
||||
border-radius: $basicCr;
|
||||
max-width: 250px;
|
||||
min-width: 170px;
|
||||
width: 35%;
|
||||
align-items: center;
|
||||
padding: $interiorMargin $interiorMarginLg;
|
||||
|
||||
input[type="range"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
&:not(:first-child) {
|
||||
margin-top: $interiorMarginLg;
|
||||
}
|
||||
|
||||
&:before {
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
padding: $interiorMargin $interiorMargin;
|
||||
|
||||
.s-status-taking-snapshot & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__lc {
|
||||
&__reset-btn {
|
||||
// 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;
|
||||
}
|
||||
[class*='--menus-aligned'] {
|
||||
> * + * {
|
||||
button { margin-left: $interiorMarginSm; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-image-controls {
|
||||
&__controls-wrapper {
|
||||
// Wraps __controls and __close-btn
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__controls {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
|
||||
> * + * {
|
||||
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 {
|
||||
> * + * { margin-left: $interiorMargin; }
|
||||
> * + * { margin-left: $interiorMargin; } // Is this used?
|
||||
}
|
||||
|
||||
&__sliders {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
&--filters {
|
||||
// Styles specific to the brightness and contrast controls
|
||||
|
||||
> * + * {
|
||||
margin-top: 11px;
|
||||
.c-image-controls {
|
||||
&__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 */
|
||||
@ -383,7 +443,7 @@
|
||||
@include cArrowButtonSizing($dimOuter: 48px);
|
||||
border-radius: $controlCr;
|
||||
|
||||
.is-in-small-container & {
|
||||
.--width-less-than-600 & {
|
||||
@include cArrowButtonSizing($dimOuter: 32px);
|
||||
}
|
||||
}
|
||||
@ -409,10 +469,6 @@
|
||||
background-color: $colorBodyFg;
|
||||
}
|
||||
|
||||
//[class*='__image-placeholder'] {
|
||||
// display: none;
|
||||
//}
|
||||
|
||||
img {
|
||||
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",
|
||||
modified: 0,
|
||||
persisted: 0,
|
||||
configuration: {
|
||||
layers: [{
|
||||
name: '16:9',
|
||||
visible: true
|
||||
}]
|
||||
},
|
||||
telemetry: {
|
||||
values: [
|
||||
{
|
||||
"name": "Image",
|
||||
"key": "url",
|
||||
"format": "image",
|
||||
"layers": [
|
||||
{
|
||||
source: location.host + '/images/bg-splash.jpg',
|
||||
name: '16:9'
|
||||
}
|
||||
],
|
||||
"hints": {
|
||||
"image": 1,
|
||||
"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) => {
|
||||
//Looks like we need Vue.nextTick here so that computed properties settle down
|
||||
Vue.nextTick(() => {
|
||||
|
@ -63,8 +63,9 @@
|
||||
padding-top: 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() {
|
||||
li {
|
||||
@include cControl();
|
||||
@ -479,6 +490,10 @@ select {
|
||||
&__row {
|
||||
> * + * { margin-left: $interiorMargin; }
|
||||
}
|
||||
|
||||
li {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** TABS */
|
||||
@ -567,6 +582,7 @@ select {
|
||||
/******************************************************** MENUS */
|
||||
.c-menu {
|
||||
@include menuOuter();
|
||||
@include menuPositioning();
|
||||
@include menuInner();
|
||||
|
||||
&__section-hint {
|
||||
@ -590,6 +606,7 @@ select {
|
||||
.c-super-menu {
|
||||
// Two column layout, menu items on left with detail of hover element on right
|
||||
@include menuOuter();
|
||||
@include menuPositioning();
|
||||
display: flex;
|
||||
padding: $interiorMarginLg;
|
||||
flex-direction: row;
|
||||
@ -1035,6 +1052,14 @@ input[type="range"] {
|
||||
display: inline-flex;
|
||||
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 {
|
||||
|
@ -349,3 +349,22 @@ body.desktop .has-local-controls {
|
||||
pointer-events: none !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 {
|
||||
display: none;
|
||||
}
|
||||
@ -498,7 +498,7 @@ mct-plot {
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
.is-in-small-container & {
|
||||
.--width-less-than-600 & {
|
||||
&.is-legend-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ div.c-table {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.is-in-small-container & {
|
||||
.--width-less-than-600 & {
|
||||
&:not(.is-paused) {
|
||||
.c-table-control-bar {
|
||||
display: none;
|
||||
|
@ -21,9 +21,11 @@
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div
|
||||
ref="soView"
|
||||
class="c-so-view js-notebook-snapshot-item-wrapper"
|
||||
:class="[
|
||||
statusClass,
|
||||
widthClass,
|
||||
'c-so-view--' + domainObject.type,
|
||||
{
|
||||
'c-so-view--no-frame': !hasFrame,
|
||||
@ -111,6 +113,7 @@ const SIMPLE_CONTENT_TYPES = [
|
||||
'hyperlink',
|
||||
'conditionWidget'
|
||||
];
|
||||
const CSS_WIDTH_LESS_STR = '--width-less-than-';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -150,6 +153,7 @@ export default {
|
||||
|
||||
return {
|
||||
cssClass,
|
||||
widthClass: '',
|
||||
complexContent,
|
||||
notebookEnabled: this.openmct.types.get('notebook'),
|
||||
statusBarItems: [],
|
||||
@ -168,6 +172,11 @@ export default {
|
||||
if (provider) {
|
||||
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() {
|
||||
this.removeStatusListener();
|
||||
@ -175,6 +184,10 @@ export default {
|
||||
if (this.actionCollection) {
|
||||
this.unlistenToActionCollection();
|
||||
}
|
||||
|
||||
if (this.soViewResizeObserver) {
|
||||
this.soViewResizeObserver.disconnect();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getSelectionContext() {
|
||||
@ -207,6 +220,18 @@ export default {
|
||||
},
|
||||
setStatus(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;
|
||||
}
|
||||
|
||||
.is-in-small-container &,
|
||||
.c-fl-frame & {
|
||||
.--width-less-than-220 &,
|
||||
.--width-less-than-600 & {
|
||||
[class*="__label"] {
|
||||
// button labels
|
||||
display: none;
|
||||
display: none !important;
|
||||
|
||||
}
|
||||
}
|
||||
@ -141,6 +141,10 @@
|
||||
&.is-status--missing {
|
||||
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 {
|
||||
@ -149,3 +153,5 @@
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
@ -72,6 +72,10 @@ const config = {
|
||||
transform: function (content) {
|
||||
return content.toString().replace(/dist\//g, '');
|
||||
}
|
||||
},
|
||||
{
|
||||
from: 'src/plugins/imagery/layers',
|
||||
to: 'imagery'
|
||||
}
|
||||
]
|
||||
}),
|
||||
|
Loading…
Reference in New Issue
Block a user