mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
Encode urls for css background images (#7906)
* Add new utility to encode urls. Use the encode urls utility to encode all background images in css * need a commit to pull exampleimagery from * skip and fix on otherside --------- Co-authored-by: John Hill <john.c.hill@nasa.gov>
This commit is contained in:
parent
078cd341a5
commit
057a5f997c
BIN
e2e/test-data/rick space roll.jpg
Normal file
BIN
e2e/test-data/rick space roll.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -96,9 +96,6 @@ test.describe('Example Imagery Object', () => {
|
|||||||
expect(newPage.url()).toContain('.jpg');
|
expect(newPage.url()).toContain('.jpg');
|
||||||
});
|
});
|
||||||
|
|
||||||
// this requires CORS to be enabled in some fashion
|
|
||||||
test.fixme('Can right click on image and save it as a file', async ({ page }) => {});
|
|
||||||
|
|
||||||
test('Can adjust image brightness/contrast by dragging the sliders', async ({
|
test('Can adjust image brightness/contrast by dragging the sliders', async ({
|
||||||
page,
|
page,
|
||||||
browserName
|
browserName
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This test suite verifies modifying the image location of the example imagery object.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
||||||
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
|
test.describe('Example Imagery Object Custom Images', () => {
|
||||||
|
let exampleImagery;
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
// Create a default 'Example Imagery' object
|
||||||
|
exampleImagery = await createDomainObjectWithDefaults(page, {
|
||||||
|
name: 'Example Imagery',
|
||||||
|
type: 'Example Imagery'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify that the created object is focused
|
||||||
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText(exampleImagery.name);
|
||||||
|
await page.getByLabel('Focused Image Element').hover({ trial: true });
|
||||||
|
|
||||||
|
// Wait for image thumbnail auto-scroll to complete
|
||||||
|
await expect(page.getByLabel('Image Thumbnail from').last()).toBeInViewport();
|
||||||
|
});
|
||||||
|
// this requires CORS to be enabled in some fashion
|
||||||
|
test.fixme('Can right click on image and save it as a file', async ({ page }) => {});
|
||||||
|
test('Can provide a custom image location for the example imagery object', async ({ page }) => {
|
||||||
|
// Modify Example Imagery to create a really stable image which will never let us down
|
||||||
|
await page.getByRole('button', { name: 'More actions' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Edit Properties...' }).click();
|
||||||
|
await page
|
||||||
|
.locator('#imageLocation-textarea')
|
||||||
|
.fill(
|
||||||
|
'https://raw.githubusercontent.com/nasa/openmct/554f77c42fec81cf0f63e62b278012cb08d82af9/e2e/test-data/rick.jpg,https://raw.githubusercontent.com/nasa/openmct/554f77c42fec81cf0f63e62b278012cb08d82af9/e2e/test-data/rick.jpg'
|
||||||
|
);
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
// Wait for the thumbnails to finish their scroll animation
|
||||||
|
// (Wait until the rightmost thumbnail is in view)
|
||||||
|
await expect(page.getByLabel('Image Thumbnail from').last()).toBeInViewport();
|
||||||
|
|
||||||
|
await expect(page.getByLabel('Image Wrapper')).toBeVisible();
|
||||||
|
});
|
||||||
|
test.fixme('Can provide a custom image with spaces in name', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7903'
|
||||||
|
});
|
||||||
|
await page.goto(exampleImagery.url, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
// Modify Example Imagery to create a really stable image which will never let us down
|
||||||
|
await page.getByRole('button', { name: 'More actions' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Edit Properties...' }).click();
|
||||||
|
await page
|
||||||
|
.locator('#imageLocation-textarea')
|
||||||
|
.fill(
|
||||||
|
'https://raw.githubusercontent.com/nasa/openmct/d8c64f183400afb70137221fc1a035e091bea912/e2e/test-data/rick%20space%20roll.jpg'
|
||||||
|
);
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
// Wait for the thumbnails to finish their scroll animation
|
||||||
|
// (Wait until the rightmost thumbnail is in view)
|
||||||
|
await expect(page.getByLabel('Image Thumbnail from').last()).toBeInViewport();
|
||||||
|
|
||||||
|
await expect(page.getByLabel('Image Wrapper')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
@ -28,11 +28,7 @@
|
|||||||
{ 'is-style-invisible': styleItem.style && styleItem.style.isStyleInvisible },
|
{ 'is-style-invisible': styleItem.style && styleItem.style.isStyleInvisible },
|
||||||
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
|
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
|
||||||
]"
|
]"
|
||||||
:style="[
|
:style="[encodedImageUrl ? { backgroundImage: 'url(' + encodedImageUrl + ')' } : itemStyle]"
|
||||||
styleItem.style.imageUrl
|
|
||||||
? { backgroundImage: 'url(' + styleItem.style.imageUrl + ')' }
|
|
||||||
: itemStyle
|
|
||||||
]"
|
|
||||||
class="c-style-thumb"
|
class="c-style-thumb"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@ -62,7 +58,7 @@
|
|||||||
@change="updateStyleValue"
|
@change="updateStyleValue"
|
||||||
/>
|
/>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
v-if="hasProperty(styleItem.style.imageUrl)"
|
v-if="hasProperty(encodedImageUrl)"
|
||||||
class="c-style__toolbar-button--image-url"
|
class="c-style__toolbar-button--image-url"
|
||||||
:options="imageUrlOption"
|
:options="imageUrlOption"
|
||||||
@change="updateStyleValue"
|
@change="updateStyleValue"
|
||||||
@ -93,6 +89,8 @@ import ToolbarButton from '@/ui/toolbar/components/ToolbarButton.vue';
|
|||||||
import ToolbarColorPicker from '@/ui/toolbar/components/ToolbarColorPicker.vue';
|
import ToolbarColorPicker from '@/ui/toolbar/components/ToolbarColorPicker.vue';
|
||||||
import ToolbarToggleButton from '@/ui/toolbar/components/ToolbarToggleButton.vue';
|
import ToolbarToggleButton from '@/ui/toolbar/components/ToolbarToggleButton.vue';
|
||||||
|
|
||||||
|
import { encode_url } from '../../../../utils/encoding';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'StyleEditor',
|
name: 'StyleEditor',
|
||||||
components: {
|
components: {
|
||||||
@ -183,11 +181,14 @@ export default {
|
|||||||
},
|
},
|
||||||
property: 'imageUrl',
|
property: 'imageUrl',
|
||||||
formKeys: ['url'],
|
formKeys: ['url'],
|
||||||
value: { url: this.styleItem.style.imageUrl },
|
value: { url: this.encodedImageUrl },
|
||||||
isEditing: this.isEditing,
|
isEditing: this.isEditing,
|
||||||
nonSpecific: this.mixedStyles.indexOf('imageUrl') > -1
|
nonSpecific: this.mixedStyles.indexOf('imageUrl') > -1
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
encodedImageUrl() {
|
||||||
|
return encode_url(this.styleItem.style.imageUrl);
|
||||||
|
},
|
||||||
isStyleInvisibleOption() {
|
isStyleInvisibleOption() {
|
||||||
return {
|
return {
|
||||||
value: this.styleItem.style.isStyleInvisible,
|
value: this.styleItem.style.isStyleInvisible,
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { encode_url } from '../../../utils/encoding';
|
||||||
import conditionalStylesMixin from '../mixins/objectStyles-mixin.js';
|
import conditionalStylesMixin from '../mixins/objectStyles-mixin.js';
|
||||||
import LayoutFrame from './LayoutFrame.vue';
|
import LayoutFrame from './LayoutFrame.vue';
|
||||||
|
|
||||||
@ -80,12 +81,12 @@ export default {
|
|||||||
return this.isEditing || !this.itemStyle?.isStyleInvisible;
|
return this.isEditing || !this.itemStyle?.isStyleInvisible;
|
||||||
},
|
},
|
||||||
style() {
|
style() {
|
||||||
let backgroundImage = 'url(' + this.item.url + ')';
|
let backgroundImage = `url('${encode_url(this.item.url)}')`;
|
||||||
let border = '1px solid ' + this.item.stroke;
|
let border = '1px solid ' + this.item.stroke;
|
||||||
|
|
||||||
if (this.itemStyle) {
|
if (this.itemStyle) {
|
||||||
if (this.itemStyle.imageUrl !== undefined) {
|
if (this.itemStyle.imageUrl !== undefined) {
|
||||||
backgroundImage = 'url(' + this.itemStyle.imageUrl + ')';
|
backgroundImage = `url('${encode_url(this.itemStyle.imageUrl)}')`;
|
||||||
}
|
}
|
||||||
|
|
||||||
border = this.itemStyle.border;
|
border = this.itemStyle.border;
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<img
|
<img
|
||||||
ref="img"
|
ref="img"
|
||||||
class="c-thumb__image"
|
class="c-thumb__image"
|
||||||
:src="`${image.thumbnailUrl || image.url}`"
|
:src="imageSrc"
|
||||||
fetchpriority="low"
|
fetchpriority="low"
|
||||||
@load="imageLoadCompleted"
|
@load="imageLoadCompleted"
|
||||||
/>
|
/>
|
||||||
@ -54,6 +54,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { encode_url } from '../../../utils/encoding';
|
||||||
|
|
||||||
const THUMB_PADDING = 4;
|
const THUMB_PADDING = 4;
|
||||||
const BORDER_WIDTH = 2;
|
const BORDER_WIDTH = 2;
|
||||||
|
|
||||||
@ -96,6 +98,9 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
imageSrc() {
|
||||||
|
return `${encode_url(this.image.thumbnailUrl) || encode_url(this.image.url)}`;
|
||||||
|
},
|
||||||
ariaLabel() {
|
ariaLabel() {
|
||||||
return `Image thumbnail from ${this.image.formattedTime}${this.showAnnotationIndicator ? ', has annotations' : ''}`;
|
return `Image thumbnail from ${this.image.formattedTime}${this.showAnnotationIndicator ? ', has annotations' : ''}`;
|
||||||
},
|
},
|
||||||
|
@ -222,6 +222,7 @@ import { TIME_CONTEXT_EVENTS } from '@/api/time/constants.js';
|
|||||||
import imageryData from '@/plugins/imagery/mixins/imageryData.js';
|
import imageryData from '@/plugins/imagery/mixins/imageryData.js';
|
||||||
import { VIEW_LARGE_ACTION_KEY } from '@/plugins/viewLargeAction/viewLargeAction.js';
|
import { VIEW_LARGE_ACTION_KEY } from '@/plugins/viewLargeAction/viewLargeAction.js';
|
||||||
|
|
||||||
|
import { encode_url } from '../../../utils/encoding';
|
||||||
import eventHelpers from '../lib/eventHelpers.js';
|
import eventHelpers from '../lib/eventHelpers.js';
|
||||||
import AnnotationsCanvas from './AnnotationsCanvas.vue';
|
import AnnotationsCanvas from './AnnotationsCanvas.vue';
|
||||||
import Compass from './Compass/CompassComponent.vue';
|
import Compass from './Compass/CompassComponent.vue';
|
||||||
@ -364,7 +365,7 @@ export default {
|
|||||||
filter: `brightness(${this.filters.brightness}%) contrast(${this.filters.contrast}%)`,
|
filter: `brightness(${this.filters.brightness}%) contrast(${this.filters.contrast}%)`,
|
||||||
backgroundImage: `${
|
backgroundImage: `${
|
||||||
this.imageUrl
|
this.imageUrl
|
||||||
? `url(${this.imageUrl}),
|
? `url(${encode_url(this.imageUrl)}),
|
||||||
repeating-linear-gradient(
|
repeating-linear-gradient(
|
||||||
45deg,
|
45deg,
|
||||||
transparent,
|
transparent,
|
||||||
@ -789,7 +790,7 @@ export default {
|
|||||||
},
|
},
|
||||||
getVisibleLayerStyles(layer) {
|
getVisibleLayerStyles(layer) {
|
||||||
return {
|
return {
|
||||||
backgroundImage: `url(${layer.source})`,
|
backgroundImage: `url(${encode_url(layer.source)})`,
|
||||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX / 2}px, ${
|
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX / 2}px, ${
|
||||||
this.imageTranslateY / 2
|
this.imageTranslateY / 2
|
||||||
}px)`,
|
}px)`,
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import mount from 'utils/mount';
|
import mount from 'utils/mount';
|
||||||
|
|
||||||
|
import { encode_url } from '../../utils/encoding';
|
||||||
import AboutDialog from './AboutDialog.vue';
|
import AboutDialog from './AboutDialog.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -39,7 +40,7 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
const branding = this.openmct.branding();
|
const branding = this.openmct.branding();
|
||||||
if (branding.smallLogoImage) {
|
if (branding.smallLogoImage) {
|
||||||
this.$refs.aboutLogo.style.backgroundImage = `url('${branding.smallLogoImage}')`;
|
this.$refs.aboutLogo.style.backgroundImage = `url('${encode_url(branding.smallLogoImage)}')`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
25
src/utils/encoding.js
Normal file
25
src/utils/encoding.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
export function encode_url(url) {
|
||||||
|
return url ? encodeURI(url) : url;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user