View Large overlay doesn't allow taking Snapshots (#4091)

* Added NotebookMenuSwitcher in preview header
* Use preview component inside viewLargeAction
* Added autoHide flag to overlay API that allows developers to specify whether an overlay should remain visible if other overlays are subsequently triggered (eg. the snapshot overlay)
* When in edit mode, disable navigation link to notebook entry.

Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Nikhil
2021-09-13 17:36:14 -07:00
committed by GitHub
parent d7c9c9cb98
commit 1226459c6f
16 changed files with 146 additions and 118 deletions

View File

@ -358,6 +358,20 @@ ObjectAPI.prototype.applyGetInterceptors = function (identifier, domainObject) {
return domainObject; return domainObject;
}; };
/**
* Return relative url path from a given object path
* eg: #/browse/mine/cb56f6bf-c900-43b7-b923-2e3b64b412db/6e89e858-77ce-46e4-a1ad-749240286497/....
* @param {Array} objectPath
* @returns {string} relative url for object
*/
ObjectAPI.prototype.getRelativePath = function (objectPath) {
return objectPath
.map(p => this.makeKeyString(p.identifier))
.reverse()
.join('/')
;
};
/** /**
* Modify a domain object. * Modify a domain object.
* @param {module:openmct.DomainObject} object the object to mutate * @param {module:openmct.DomainObject} object the object to mutate

View File

@ -10,28 +10,37 @@ const cssClasses = {
}; };
class Overlay extends EventEmitter { class Overlay extends EventEmitter {
constructor(options) { constructor({
buttons,
autoHide = true,
dismissable = true,
element,
onDestroy,
size
} = {}) {
super(); super();
this.dismissable = options.dismissable !== false;
this.container = document.createElement('div'); this.container = document.createElement('div');
this.container.classList.add('l-overlay-wrapper', cssClasses[options.size]); this.container.classList.add('l-overlay-wrapper', cssClasses[size]);
this.autoHide = autoHide;
this.dismissable = dismissable !== false;
this.component = new Vue({ this.component = new Vue({
provide: {
dismiss: this.dismiss.bind(this),
element: options.element,
buttons: options.buttons,
dismissable: this.dismissable
},
components: { components: {
OverlayComponent: OverlayComponent OverlayComponent: OverlayComponent
}, },
provide: {
dismiss: this.dismiss.bind(this),
element,
buttons,
dismissable: this.dismissable
},
template: '<overlay-component></overlay-component>' template: '<overlay-component></overlay-component>'
}); });
if (options.onDestroy) { if (onDestroy) {
this.once('destroy', options.onDestroy); this.once('destroy', onDestroy);
} }
} }

View File

@ -30,7 +30,10 @@ class OverlayAPI {
*/ */
showOverlay(overlay) { showOverlay(overlay) {
if (this.activeOverlays.length) { if (this.activeOverlays.length) {
this.activeOverlays[this.activeOverlays.length - 1].container.classList.add('invisible'); const previousOverlay = this.activeOverlays[this.activeOverlays.length - 1];
if (previousOverlay.autoHide) {
previousOverlay.container.classList.add('invisible');
}
} }
this.activeOverlays.push(overlay); this.activeOverlays.push(overlay);

View File

@ -90,7 +90,7 @@ class ImageExporter {
element.id = oldId; element.id = oldId;
}, },
removeContainer: true // Set to false to debug what html2canvas renders removeContainer: true // Set to false to debug what html2canvas renders
}).then(function (canvas) { }).then(canvas => {
dialog.dismiss(); dialog.dismiss();
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
@ -105,9 +105,10 @@ class ImageExporter {
return canvas.toBlob(blob => resolve({ blob }), mimeType); return canvas.toBlob(blob => resolve({ blob }), mimeType);
}); });
}, function (error) { }).catch(error => {
console.log('error capturing image', error);
dialog.dismiss(); dialog.dismiss();
console.error('error capturing image', error);
const errorDialog = overlays.dialog({ const errorDialog = overlays.dialog({
iconClass: 'error', iconClass: 'error',
message: 'Image was not captured successfully!', message: 'Image was not captured successfully!',

View File

@ -273,10 +273,7 @@ export default {
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then( this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
(objectPath) => { (objectPath) => {
this.objectPath = objectPath; this.objectPath = objectPath;
this.navigateToPath = '#/browse/' + this.objectPath this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath);
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
.reverse()
.join('/');
} }
); );
}, },

View File

@ -297,10 +297,7 @@ export default {
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then( this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
(objectPath) => { (objectPath) => {
this.objectPath = objectPath; this.objectPath = objectPath;
this.navigateToPath = '#/browse/' + this.objectPath this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath);
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
.reverse()
.join('/');
} }
); );
}, },

View File

@ -456,12 +456,9 @@ export default {
: undefined; : undefined;
}, },
getDefaultNotebookObject() { getDefaultNotebookObject() {
const oldNotebookStorage = getDefaultNotebook(); const defaultNotebook = getDefaultNotebook();
if (!oldNotebookStorage) {
return null;
}
return this.openmct.objects.get(oldNotebookStorage.identifier); return defaultNotebook && this.openmct.objects.get(defaultNotebook.identifier);
}, },
getLinktoNotebook() { getLinktoNotebook() {
const objectPath = this.openmct.router.path; const objectPath = this.openmct.router.path;

View File

@ -17,19 +17,26 @@
<script> <script>
import Snapshot from '../snapshot'; import Snapshot from '../snapshot';
import { getDefaultNotebook, getNotebookSectionAndPage, validateNotebookStorageObject } from '../utils/notebook-storage'; import { getDefaultNotebook, validateNotebookStorageObject } from '../utils/notebook-storage';
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants'; import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
import { getMenuItems } from '../utils/notebook-snapshot-menu';
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
currentView: {
type: Object,
default() {
return {};
}
},
domainObject: { domainObject: {
type: Object, type: Object,
default() { default() {
return {}; return {};
} }
}, },
ignoreLink: { isPreview: {
type: Boolean, type: Boolean,
default() { default() {
return false; return false;
@ -50,51 +57,40 @@ export default {
}, },
mounted() { mounted() {
validateNotebookStorageObject(); validateNotebookStorageObject();
this.getDefaultNotebookObject();
this.notebookSnapshot = new Snapshot(this.openmct); this.notebookSnapshot = new Snapshot(this.openmct);
this.setDefaultNotebookStatus(); this.setDefaultNotebookStatus();
}, },
methods: { methods: {
getDefaultNotebookObject() { getPreviewObjectLink() {
const defaultNotebook = getDefaultNotebook(); const relativePath = this.openmct.objects.getRelativePath(this.objectPath);
const urlParams = this.openmct.router.getParams();
urlParams.view = this.currentView.key;
return defaultNotebook && this.openmct.objects.get(defaultNotebook.identifier); const urlParamsString = Object.entries(urlParams)
.map(([key, value]) => `${key}=${value}`)
.join('&');
return `#/browse/${relativePath}?${urlParamsString}`;
}, },
async showMenu(event) { async showMenu(event) {
const notebookTypes = []; const menuItemOptions = {
default: {
cssClass: 'icon-notebook',
name: `Save to Notebook`,
onItemClicked: () => this.snapshot(NOTEBOOK_DEFAULT, event.target)
},
snapshot: {
cssClass: 'icon-camera',
name: 'Save to Notebook Snapshots',
onItemClicked: () => this.snapshot(NOTEBOOK_SNAPSHOT, event.target)
}
};
const notebookTypes = await getMenuItems(this.openmct, menuItemOptions);
const elementBoundingClientRect = this.$el.getBoundingClientRect(); const elementBoundingClientRect = this.$el.getBoundingClientRect();
const x = elementBoundingClientRect.x; const x = elementBoundingClientRect.x;
const y = elementBoundingClientRect.y + elementBoundingClientRect.height; const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
const defaultNotebookObject = await this.getDefaultNotebookObject();
if (defaultNotebookObject) {
const defaultNotebook = getDefaultNotebook();
const { section, page } = getNotebookSectionAndPage(defaultNotebookObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
if (section && page) {
const name = defaultNotebookObject.name;
const sectionName = section.name;
const pageName = page.name;
const defaultPath = `${name} - ${sectionName} - ${pageName}`;
notebookTypes.push({
cssClass: 'icon-notebook',
name: `Save to Notebook ${defaultPath}`,
onItemClicked: () => {
return this.snapshot(NOTEBOOK_DEFAULT, event.target);
}
});
}
}
notebookTypes.push({
cssClass: 'icon-camera',
name: 'Save to Notebook Snapshots',
onItemClicked: () => {
return this.snapshot(NOTEBOOK_SNAPSHOT, event.target);
}
});
this.openmct.menus.showMenu(x, y, notebookTypes); this.openmct.menus.showMenu(x, y, notebookTypes);
}, },
snapshot(notebookType, target) { snapshot(notebookType, target) {
@ -102,15 +98,12 @@ export default {
const wrapper = target && target.closest('.js-notebook-snapshot-item-wrapper') const wrapper = target && target.closest('.js-notebook-snapshot-item-wrapper')
|| document; || document;
const element = wrapper.querySelector('.js-notebook-snapshot-item'); const element = wrapper.querySelector('.js-notebook-snapshot-item');
const bounds = this.openmct.time.bounds();
const link = !this.ignoreLink
? window.location.hash
: null;
const objectPath = this.objectPath || this.openmct.router.path; const objectPath = this.objectPath || this.openmct.router.path;
const link = this.isPreview
? this.getPreviewObjectLink()
: window.location.hash;
const snapshotMeta = { const snapshotMeta = {
bounds, bounds: this.openmct.time.bounds(),
link, link,
objectPath, objectPath,
openmct: this.openmct openmct: this.openmct

View File

@ -90,13 +90,16 @@ export default class Snapshot {
_showNotification(msg, url) { _showNotification(msg, url) {
const options = { const options = {
autoDismissTimeout: 30000, autoDismissTimeout: 30000
link: { };
if (!this.openmct.editor.isEditing()) {
options.link = {
cssClass: '', cssClass: '',
text: 'click to view', text: 'click to view',
onClick: this._navigateToNotebook(url) onClick: this._navigateToNotebook(url)
} };
}; }
this.openmct.notifications.info(msg, options); this.openmct.notifications.info(msg, options);
} }

View File

@ -0,0 +1,31 @@
import { getDefaultNotebook, getNotebookSectionAndPage } from './notebook-storage';
export async function getMenuItems(openmct, menuItemOptions) {
const notebookTypes = [];
const defaultNotebook = getDefaultNotebook();
const defaultNotebookObject = defaultNotebook && await openmct.objects.get(defaultNotebook.identifier);
if (defaultNotebookObject) {
const { section, page } = getNotebookSectionAndPage(defaultNotebookObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
if (section && page) {
const name = defaultNotebookObject.name;
const sectionName = section.name;
const pageName = page.name;
const defaultPath = `${name} - ${sectionName} - ${pageName}`;
notebookTypes.push({
cssClass: menuItemOptions.default.cssClass,
name: `${menuItemOptions.default.name} ${defaultPath}`,
onItemClicked: menuItemOptions.default.onItemClicked
});
}
}
notebookTypes.push({
cssClass: menuItemOptions.snapshot.cssClass,
name: menuItemOptions.snapshot.name,
onItemClicked: menuItemOptions.snapshot.onItemClicked
});
return notebookTypes;
}

View File

@ -71,11 +71,7 @@ export async function getDefaultNotebookLink(openmct, domainObject = null) {
} }
const path = await openmct.objects.getOriginalPath(domainObject.identifier) const path = await openmct.objects.getOriginalPath(domainObject.identifier)
.then(objectPath => objectPath .then(openmct.objects.getRelativePath);
.map(o => o && openmct.objects.makeKeyString(o.identifier))
.reverse()
.join('/')
);
const { defaultPageId, defaultSectionId } = getDefaultNotebook(); const { defaultPageId, defaultSectionId } = getDefaultNotebook();
return `#/browse/${path}?sectionId=${defaultSectionId}&pageId=${defaultPageId}`; return `#/browse/${path}?sectionId=${defaultSectionId}&pageId=${defaultPageId}`;

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import PreviewHeader from '@/ui/preview/preview-header.vue'; import Preview from '@/ui/preview/Preview.vue';
import Vue from 'vue'; import Vue from 'vue';
@ -46,7 +46,7 @@ export default class ViewLargeAction {
throw new Error(message); throw new Error(message);
} }
this._expand(objectPath, childElement, view); this._expand(objectPath, childElement);
} }
appliesTo(objectPath, view = {}) { appliesTo(objectPath, view = {}) {
@ -58,49 +58,29 @@ export default class ViewLargeAction {
return viewLargeAction; return viewLargeAction;
} }
_expand(objectPath, childElement, view) { _expand(objectPath, childElement) {
const parentElement = childElement.parentElement; const parentElement = childElement.parentElement;
this.overlay = this.openmct.overlays.overlay({ this.overlay = this.openmct.overlays.overlay({
element: this._getOverlayElement(objectPath, childElement, view), element: this._getPreview(objectPath),
size: 'large', size: 'large',
autoHide: false,
onDestroy() { onDestroy() {
parentElement.append(childElement); parentElement.append(childElement);
} }
}); });
} }
_getOverlayElement(objectPath, childElement, view) { _getPreview(objectPath) {
const fragment = new DocumentFragment();
const header = this._getPreviewHeader(objectPath, view);
fragment.append(header);
const wrapper = document.createElement('div');
wrapper.classList.add('l-preview-window__object-view');
wrapper.append(childElement);
fragment.append(wrapper);
return fragment;
}
_getPreviewHeader(objectPath, view) {
const domainObject = objectPath[0];
const actionCollection = this.openmct.actions.getActionsCollection(objectPath, view);
const preview = new Vue({ const preview = new Vue({
components: { components: {
PreviewHeader Preview
}, },
provide: { provide: {
openmct: this.openmct, openmct: this.openmct,
objectPath: this.objectPath objectPath
}, },
data() { template: '<Preview></Preview>'
return {
domainObject,
actionCollection
};
},
template: '<PreviewHeader :actionCollection="actionCollection" :domainObject="domainObject" :hideViewSwitcher="true" :showNotebookMenuSwitcher="true"></PreviewHeader>'
}); });
return preview.$mount().$el; return preview.$mount().$el;

View File

@ -39,13 +39,7 @@ export function paramsToArray(openmct) {
} }
export function identifierToString(openmct, objectPath) { export function identifierToString(openmct, objectPath) {
let identifier = '#/browse/' + objectPath.map(function (o) { return '#/browse/' + openmct.objects.getRelativePath(objectPath);
return o && openmct.objects.makeKeyString(o.identifier);
})
.reverse()
.join('/');
return identifier;
} }
export default function objectPathToUrl(openmct, objectPath) { export default function objectPathToUrl(openmct, objectPath) {

View File

@ -59,12 +59,13 @@ export default {
}, },
mounted() { mounted() {
this.views = this.openmct.objectViews.get(this.domainObject, this.objectPath).map((view) => { this.views = this.openmct.objectViews.get(this.domainObject, this.objectPath).map((view) => {
view.callBack = () => { view.onItemClicked = () => {
return this.setView(view); return this.setView(view);
}; };
return view; return view;
}); });
this.setView(this.views[0]); this.setView(this.views[0]);
}, },
beforeDestroy() { beforeDestroy() {

View File

@ -60,6 +60,7 @@ export default class PreviewAction {
let overlay = this._openmct.overlays.overlay({ let overlay = this._openmct.overlays.overlay({
element: preview.$el, element: preview.$el,
size: 'large', size: 'large',
autoHide: false,
buttons: [ buttons: [
{ {
label: 'Done', label: 'Done',

View File

@ -18,6 +18,12 @@
:views="views" :views="views"
:current-view="currentView" :current-view="currentView"
/> />
<NotebookMenuSwitcher :domain-object="domainObject"
:object-path="objectPath"
:is-preview="true"
:current-view="currentView"
class="c-notebook-snapshot-menubutton"
/>
<div class="l-browse-bar__actions"> <div class="l-browse-bar__actions">
<button <button
v-for="(item, index) in statusBarItems" v-for="(item, index) in statusBarItems"
@ -38,7 +44,9 @@
</template> </template>
<script> <script>
import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue';
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue'; import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
const HIDDEN_ACTIONS = [ const HIDDEN_ACTIONS = [
'remove', 'remove',
'move', 'move',
@ -48,10 +56,12 @@ const HIDDEN_ACTIONS = [
export default { export default {
components: { components: {
NotebookMenuSwitcher,
ViewSwitcher ViewSwitcher
}, },
inject: [ inject: [
'openmct' 'openmct',
'objectPath'
], ],
props: { props: {
currentView: { currentView: {
@ -143,6 +153,7 @@ export default {
showMenuItems(event) { showMenuItems(event) {
let sortedActions = this.openmct.actions._groupAndSortActions(this.menuActionItems); let sortedActions = this.openmct.actions._groupAndSortActions(this.menuActionItems);
const menuItems = this.openmct.menus.actionsToMenuItems(sortedActions, this.actionCollection.objectPath, this.actionCollection.view); const menuItems = this.openmct.menus.actionsToMenuItems(sortedActions, this.actionCollection.objectPath, this.actionCollection.view);
const visibleMenuItems = this.filterHiddenItems(menuItems); const visibleMenuItems = this.filterHiddenItems(menuItems);
this.openmct.menus.showMenu(event.x, event.y, visibleMenuItems); this.openmct.menus.showMenu(event.x, event.y, visibleMenuItems);
} }