From 541a022f3689ef8bf2547e2dcad6894a0b4ddd0b Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Mon, 18 Sep 2023 19:56:49 +0200 Subject: [PATCH] Embedding images in notebook entries (#7048) * initial drag drop, wip * images work as snapshots, but need to disable navigate to actions * embed image name * works now with images, need to be refactor so can duplicate code for entries too * works dropping on entries too * handle remote images too * add e2e test * spelling * address most PR comments --- .../notebook/notebookSnapshots.e2e.spec.js | 60 ++++++++++++++- src/plugins/notebook/components/Notebook.vue | 57 +++++++++----- .../notebook/components/NotebookEmbed.vue | 62 ++++++++------- .../notebook/components/NotebookEntry.vue | 34 +++++++-- .../notebook/utils/notebook-entries.js | 76 +++++++++++++++---- src/plugins/notebook/utils/notebook-image.js | 21 +---- .../notebook/utils/notebook-migration.js | 4 +- .../notebook/utils/painterroInstance.js | 4 +- 8 files changed, 229 insertions(+), 89 deletions(-) diff --git a/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js b/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js index 00c3f01f8c..e0d76dc53f 100644 --- a/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js @@ -24,9 +24,11 @@ This test suite is dedicated to tests which verify the basic operations surrounding Notebooks. */ +const fs = require('fs').promises; const { test, expect } = require('../../../../pluginFixtures'); -// const { expandTreePaneItemByName, createDomainObjectWithDefaults } = require('../../../../appActions'); -// const nbUtils = require('../../../../helper/notebookUtils'); +const { createDomainObjectWithDefaults } = require('../../../../appActions'); + +const NOTEBOOK_NAME = 'Notebook'; test.describe('Snapshot Menu tests', () => { test.fixme( @@ -161,3 +163,57 @@ test.describe('Snapshot Container tests', () => { } ); }); + +test.describe('Snapshot image tests', () => { + test.beforeEach(async ({ page }) => { + //Navigate to baseURL + await page.goto('./', { waitUntil: 'domcontentloaded' }); + + // Create Notebook + await createDomainObjectWithDefaults(page, { + type: NOTEBOOK_NAME + }); + }); + + test('Can drop an image onto a notebook and create a new entry', async ({ page }) => { + const imageData = await fs.readFile('src/images/favicons/favicon-96x96.png'); + const imageArray = new Uint8Array(imageData); + const fileData = Array.from(imageArray); + + const dropTransfer = await page.evaluateHandle((data) => { + const dataTransfer = new DataTransfer(); + const file = new File([new Uint8Array(data)], 'favicon-96x96.png', { type: 'image/png' }); + dataTransfer.items.add(file); + return dataTransfer; + }, fileData); + + await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer }); + + // be sure that entry was created + await expect(page.getByText('favicon-96x96.png')).toBeVisible(); + + // click on image (need to click twice to focus) + await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).click(); + await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).click(); + + // expect large image to be displayed + await expect(page.getByRole('dialog').getByText('favicon-96x96.png')).toBeVisible(); + + await page.getByLabel('Close').click(); + + // drop another image onto the entry + await page.dispatchEvent('.c-snapshots', 'drop', { dataTransfer: dropTransfer }); + + // expect two embedded images now + expect(await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).count()).toBe(2); + + await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click(); + + await page.getByRole('menuitem', { name: /Remove This Embed/ }).click(); + + await page.getByRole('button', { name: 'Ok', exact: true }).click(); + + // expect one embedded image now as we deleted the other + expect(await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).count()).toBe(1); + }); +}); diff --git a/src/plugins/notebook/components/Notebook.vue b/src/plugins/notebook/components/Notebook.vue index 277c6988df..d56228e922 100644 --- a/src/plugins/notebook/components/Notebook.vue +++ b/src/plugins/notebook/components/Notebook.vue @@ -165,6 +165,7 @@ import { isNotebookViewType, RESTRICTED_NOTEBOOK_TYPE } from '../notebook-consta import { addNotebookEntry, createNewEmbed, + createNewImageEmbed, getEntryPosById, getNotebookEntries, mutateObject, @@ -615,12 +616,31 @@ export default { this.openmct.editor.cancel(); } }, - async dropOnEntry(event) { - event.preventDefault(); - event.stopImmediatePropagation(); + async dropOnEntry(dropEvent) { + dropEvent.preventDefault(); + dropEvent.stopImmediatePropagation(); - const snapshotId = event.dataTransfer.getData('openmct/snapshot/id'); - if (snapshotId.length) { + const localImageDropped = dropEvent.dataTransfer.files?.[0]?.type.includes('image'); + const imageUrl = dropEvent.dataTransfer.getData('URL'); + const snapshotId = dropEvent.dataTransfer.getData('openmct/snapshot/id'); + if (localImageDropped) { + // local image dropped from disk (file) + const imageData = dropEvent.dataTransfer.files[0]; + const imageEmbed = await createNewImageEmbed(imageData, this.openmct, imageData?.name); + this.newEntry(imageEmbed); + } else if (imageUrl) { + // remote image dropped (URL) + try { + const response = await fetch(imageUrl); + const imageData = await response.blob(); + const imageEmbed = await createNewImageEmbed(imageData, this.openmct); + this.newEntry(imageEmbed); + } catch (error) { + this.openmct.notifications.alert(`Unable to add image: ${error.message} `); + console.error(`Problem embedding remote image`, error); + } + } else if (snapshotId.length) { + // snapshot object const snapshot = this.snapshotContainer.getSnapshot(snapshotId); this.newEntry(snapshot.embedObject); this.snapshotContainer.removeSnapshot(snapshotId); @@ -631,22 +651,21 @@ export default { namespace ); saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject); + } else { + // plain domain object + const data = dropEvent.dataTransfer.getData('openmct/domain-object-path'); + const objectPath = JSON.parse(data); + const bounds = this.openmct.time.bounds(); + const snapshotMeta = { + bounds, + link: null, + objectPath, + openmct: this.openmct + }; + const embed = await createNewEmbed(snapshotMeta); - return; + this.newEntry(embed); } - - const data = event.dataTransfer.getData('openmct/domain-object-path'); - const objectPath = JSON.parse(data); - const bounds = this.openmct.time.bounds(); - const snapshotMeta = { - bounds, - link: null, - objectPath, - openmct: this.openmct - }; - const embed = await createNewEmbed(snapshotMeta); - - this.newEntry(embed); }, focusOnEntryId() { if (!this.focusEntryId) { diff --git a/src/plugins/notebook/components/NotebookEmbed.vue b/src/plugins/notebook/components/NotebookEmbed.vue index 3d928a5b55..46de1d9ca9 100644 --- a/src/plugins/notebook/components/NotebookEmbed.vue +++ b/src/plugins/notebook/components/NotebookEmbed.vue @@ -27,13 +27,13 @@ @mouseleave="hideToolTip" >
- +
- {{ - embed.name - }} + + {{ embed.name }} +