Skip to content

Commit

Permalink
Add right click actions on imagery to allow high resolution download (#…
Browse files Browse the repository at this point in the history
…7371)

* action stubs in place

* first draft of actions

* delete copy

* add test

* remove console.debug
  • Loading branch information
scottbell authored Jan 11, 2024
1 parent 3ff2f02 commit 6fd7b6f
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 1 deletion.
25 changes: 25 additions & 0 deletions e2e/tests/functional/plugins/imagery/exampleImagery.e2e.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,31 @@ test.describe('Example Imagery Object', () => {
await expect(page.locator('.c-hud')).toBeHidden();
});

test('Can right click on image and open it in a new tab', async ({ page, context }) => {
// try to right click on image
const backgroundImage = await page.locator(backgroundImageSelector);
await backgroundImage.click({
button: 'right',
// eslint-disable-next-line playwright/no-force-option
force: true
});
// expect context menu to appear
await expect(page.getByText('Save Image As')).toBeVisible();
await expect(page.getByText('Open Image in New Tab')).toBeVisible();

// click on open image in new tab
const pagePromise = context.waitForEvent('page');
await page.getByText('Open Image in New Tab').click();
// expect new tab to be in browser
const newPage = await pagePromise;
await newPage.waitForLoadState();
// expect new tab url to have jpg in it
await 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 ({
page,
browserName
Expand Down
46 changes: 46 additions & 0 deletions src/plugins/imagery/actions/OpenImageInNewTabAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*****************************************************************************
* 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 default class OpenImageInNewTabAction {
constructor(openmct) {
this.openmct = openmct;

this.cssClass = 'icon-new-window';
this.description = 'Open the image in a new tab';
this.group = 'action';
this.key = 'openImageInNewTab';
this.name = 'Open Image in New Tab';
this.priority = 1;
}

invoke(objectPath, view) {
const viewContext = (view.getViewContext && view.getViewContext()) || {};
window.open(viewContext.imageUrl, '_blank').focus();
}

appliesTo(objectPath, view = {}) {
const viewContext = (view.getViewContext && view.getViewContext()) || {};
if (!viewContext.imageUrl) {
return false;
}
}
}
67 changes: 67 additions & 0 deletions src/plugins/imagery/actions/SaveImageAsAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*****************************************************************************
* 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 default class SaveImageAction {
constructor(openmct) {
this.openmct = openmct;

this.cssClass = 'icon-save-as';
this.description = 'Save image to file';
this.group = 'action';
this.key = 'saveImageAs';
this.name = 'Save Image As';
this.priority = 1;
}

async invoke(objectPath, view) {
const viewContext = (view.getViewContext && view.getViewContext()) || {};
try {
const filename =
viewContext.imageUrl.split('/').pop().split('#')[0].split('?')[0] || 'downloaded-image.png';
const response = await fetch(viewContext.imageUrl);
const blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);

// Create a temporary anchor element and trigger the download
const a = document.createElement('a');
a.href = blobUrl;
a.download = filename; // Set the filename for the download

// Append anchor to body, trigger click, then remove it from the DOM
document.body.appendChild(a);
a.click();
document.body.removeChild(a);

// Revoke the blob URL after the download
URL.revokeObjectURL(blobUrl);
} catch (error) {
console.error('Could not download the image.', error);
}
}

appliesTo(objectPath, view = {}) {
const viewContext = (view.getViewContext && view.getViewContext()) || {};
if (!viewContext.imageUrl) {
return false;
}
}
}
20 changes: 19 additions & 1 deletion src/plugins/imagery/components/AnnotationsCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
@mousedown="clearSelectedAnnotations"
@mousemove="trackAnnotationDrag"
@click="selectOrCreateAnnotation"
@contextmenu="showContextMenu"
></canvas>
</template>

Expand All @@ -43,8 +44,10 @@ const EXISTING_ANNOTATION_FILL_STYLE = 'rgba(202, 202, 142, 0.2)';
const SELECTED_ANNOTATION_STROKE_COLOR = '#BD8ECC';
const SELECTED_ANNOTATION_FILL_STYLE = 'rgba(199, 87, 231, 0.2)';

const CONTEXT_MENU_ACTIONS = ['openImageInNewTab', 'saveImageAs'];

export default {
inject: ['openmct', 'domainObject', 'objectPath'],
inject: ['openmct', 'domainObject', 'objectPath', 'currentView'],
props: {
image: {
type: Object,
Expand Down Expand Up @@ -481,6 +484,21 @@ export default {
drawnRectangles.push(annotationRectangle);
}
});
},
showContextMenu: function (event) {
event.preventDefault();

let objectPath = this.objectPath;

const actions = CONTEXT_MENU_ACTIONS.map((key) => this.openmct.actions.getAction(key));
const menuItems = this.openmct.menus.actionsToMenuItems(
actions,
objectPath,
this.currentView
);
if (menuItems.length) {
this.openmct.menus.showMenu(event.x, event.y, menuItems);
}
}
}
};
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/imagery/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/

import OpenImageInNewTabAction from './actions/OpenImageInNewTabAction.js';
import SaveImageAsAction from './actions/SaveImageAsAction.js';
import ImageryTimestripViewProvider from './ImageryTimestripViewProvider.js';
import ImageryViewProvider from './ImageryViewProvider.js';

export default function (options) {
return function install(openmct) {
openmct.objectViews.addProvider(new ImageryViewProvider(openmct, options));
openmct.objectViews.addProvider(new ImageryTimestripViewProvider(openmct));
openmct.actions.register(new OpenImageInNewTabAction(openmct));
openmct.actions.register(new SaveImageAsAction(openmct));
};
}

0 comments on commit 6fd7b6f

Please sign in to comment.