Notebook saved link (#2998)

* https://github.com/nasa/openmct/issues/2859

* create and store link to default notebook in storage and pass it to notification.

* [Notebook] Add link to notebook inside 'Saved to Notebook' notification #2860

* Added custom autoDismissTimeout for into notifications.

* Backwards compatibility fix for old notebook model without link in metadata.

* lint fixes

* added JS Doc description for API changes + changed property names to appropriate function.

* fixed bug due to merging.

* fixed url update loop

Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Nikhil 2021-02-17 11:16:45 -08:00 committed by GitHub
parent 55c851873c
commit b7085f7f62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 137 additions and 26 deletions

View File

@ -75,13 +75,20 @@ export default class NotificationAPI extends EventEmitter {
* Info notifications are low priority informational messages for the user. They will be auto-destroy after a brief * Info notifications are low priority informational messages for the user. They will be auto-destroy after a brief
* period of time. * period of time.
* @param {string} message The message to display to the user * @param {string} message The message to display to the user
* @param {Object} [options] object with following properties
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
* link: {Object} Add a link to notifications for navigation
* onClick: callback function
* cssClass: css class name to add style on link
* text: text to display for link
* @returns {InfoNotification} * @returns {InfoNotification}
*/ */
info(message) { info(message, options = {}) {
let notificationModel = { let notificationModel = {
message: message, message: message,
autoDismiss: true, autoDismiss: true,
severity: "info" severity: "info",
options
}; };
return this._notify(notificationModel); return this._notify(notificationModel);
@ -90,12 +97,19 @@ export default class NotificationAPI extends EventEmitter {
/** /**
* Present an alert to the user. * Present an alert to the user.
* @param {string} message The message to display to the user. * @param {string} message The message to display to the user.
* @param {Object} [options] object with following properties
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
* link: {Object} Add a link to notifications for navigation
* onClick: callback function
* cssClass: css class name to add style on link
* text: text to display for link
* @returns {Notification} * @returns {Notification}
*/ */
alert(message) { alert(message, options = {}) {
let notificationModel = { let notificationModel = {
message: message, message: message,
severity: "alert" severity: "alert",
options
}; };
return this._notify(notificationModel); return this._notify(notificationModel);
@ -104,12 +118,19 @@ export default class NotificationAPI extends EventEmitter {
/** /**
* Present an error message to the user * Present an error message to the user
* @param {string} message * @param {string} message
* @param {Object} [options] object with following properties
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
* link: {Object} Add a link to notifications for navigation
* onClick: callback function
* cssClass: css class name to add style on link
* text: text to display for link
* @returns {Notification} * @returns {Notification}
*/ */
error(message) { error(message, options = {}) {
let notificationModel = { let notificationModel = {
message: message, message: message,
severity: "error" severity: "error",
options
}; };
return this._notify(notificationModel); return this._notify(notificationModel);
@ -325,9 +346,11 @@ export default class NotificationAPI extends EventEmitter {
this.emit('notification', notification); this.emit('notification', notification);
if (notification.model.autoDismiss || this._selectNextNotification()) { if (notification.model.autoDismiss || this._selectNextNotification()) {
const autoDismissTimeout = notification.model.options.autoDismissTimeout
|| DEFAULT_AUTO_DISMISS_TIMEOUT;
this.activeTimeout = setTimeout(() => { this.activeTimeout = setTimeout(() => {
this._dismissOrMinimize(notification); this._dismissOrMinimize(notification);
}, DEFAULT_AUTO_DISMISS_TIMEOUT); }, autoDismissTimeout);
} else { } else {
delete this.activeTimeout; delete this.activeTimeout;
} }

View File

@ -115,6 +115,7 @@ import { addNotebookEntry, createNewEmbed, getNotebookEntries, mutateObject } fr
import objectUtils from 'objectUtils'; import objectUtils from 'objectUtils';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import objectLink from '../../../ui/mixins/object-link';
export default { export default {
components: { components: {
@ -182,7 +183,9 @@ export default {
mounted() { mounted() {
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject); this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
this.formatSidebar(); this.formatSidebar();
window.addEventListener('orientationchange', this.formatSidebar); window.addEventListener('orientationchange', this.formatSidebar);
window.addEventListener("hashchange", this.navigateToSectionPage, false);
this.navigateToSectionPage(); this.navigateToSectionPage();
}, },
@ -190,6 +193,9 @@ export default {
if (this.unlisten) { if (this.unlisten) {
this.unlisten(); this.unlisten();
} }
window.removeEventListener('orientationchange', this.formatSidebar);
window.removeEventListener("hashchange", this.navigateToSectionPage);
}, },
updated: function () { updated: function () {
this.$nextTick(() => { this.$nextTick(() => {
@ -225,15 +231,17 @@ export default {
}, },
createNotebookStorageObject() { createNotebookStorageObject() {
const notebookMeta = { const notebookMeta = {
identifier: this.internalDomainObject.identifier name: this.internalDomainObject.name,
identifier: this.internalDomainObject.identifier,
link: this.getLinktoNotebook()
}; };
const page = this.getSelectedPage(); const page = this.getSelectedPage();
const section = this.getSelectedSection(); const section = this.getSelectedSection();
return { return {
notebookMeta, notebookMeta,
section, page,
page section
}; };
}, },
dragOver(event) { dragOver(event) {
@ -309,6 +317,20 @@ export default {
return this.openmct.objects.get(oldNotebookStorage.notebookMeta.identifier); return this.openmct.objects.get(oldNotebookStorage.notebookMeta.identifier);
}, },
getLinktoNotebook() {
const objectPath = this.openmct.router.path;
const link = objectLink.computed.objectLink.call({
objectPath,
openmct: this.openmct
});
const selectedSection = this.selectedSection;
const selectedPage = this.selectedPage;
const sectionId = selectedSection ? selectedSection.id : '';
const pageId = selectedPage ? selectedPage.id : '';
return `${link}?sectionId=${sectionId}&pageId=${pageId}`;
},
getPage(section, id) { getPage(section, id) {
return section.pages.find(p => p.id === id); return section.pages.find(p => p.id === id);
}, },
@ -393,6 +415,12 @@ export default {
return s; return s;
}); });
const selectedSectionId = this.selectedSection && this.selectedSection.id;
const selectedPageId = this.selectedPage && this.selectedPage.id;
if (selectedPageId === pageId && selectedSectionId === sectionId) {
return;
}
this.sectionsChanged({ sections }); this.sectionsChanged({ sections });
}, },
newEntry(embed = null) { newEntry(embed = null) {

View File

@ -1,5 +1,5 @@
import { addNotebookEntry, createNewEmbed } from './utils/notebook-entries'; import { addNotebookEntry, createNewEmbed } from './utils/notebook-entries';
import { getDefaultNotebook } from './utils/notebook-storage'; import { getDefaultNotebook, getDefaultNotebookLink, setDefaultNotebook } from './utils/notebook-storage';
import { NOTEBOOK_DEFAULT } from '@/plugins/notebook/notebook-constants'; import { NOTEBOOK_DEFAULT } from '@/plugins/notebook/notebook-constants';
import SnapshotContainer from './snapshot-container'; import SnapshotContainer from './snapshot-container';
@ -45,12 +45,21 @@ export default class Snapshot {
_saveToDefaultNoteBook(embed) { _saveToDefaultNoteBook(embed) {
const notebookStorage = getDefaultNotebook(); const notebookStorage = getDefaultNotebook();
this.openmct.objects.get(notebookStorage.notebookMeta.identifier) this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
.then(domainObject => { .then(async (domainObject) => {
addNotebookEntry(this.openmct, domainObject, notebookStorage, embed); addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
let link = notebookStorage.notebookMeta.link;
// Backwards compatibility fix (old notebook model without link)
if (!link) {
link = await getDefaultNotebookLink(this.openmct, domainObject);
notebookStorage.notebookMeta.link = link;
setDefaultNotebook(this.openmct, notebookStorage);
}
const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`; const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
const msg = `Saved to Notebook ${defaultPath}`; const msg = `Saved to Notebook ${defaultPath}`;
this._showNotification(msg); this._showNotification(msg, link);
}); });
} }
@ -58,16 +67,29 @@ export default class Snapshot {
* @private * @private
*/ */
_saveToNotebookSnapshots(embed) { _saveToNotebookSnapshots(embed) {
const saved = this.snapshotContainer.addSnapshot(embed); this.snapshotContainer.addSnapshot(embed);
if (!saved) { }
return;
_showNotification(msg, url) {
const options = {
autoDismissTimeout: 30000,
link: {
cssClass: '',
text: 'click to view',
onClick: this._navigateToNotebook(url)
}
};
this.openmct.notifications.info(msg, options);
}
_navigateToNotebook(url = null) {
if (!url) {
return () => {};
} }
const msg = 'Saved to Notebook Snapshots - click to view.'; return () => {
this._showNotification(msg); window.location.href = window.location.origin + url;
} };
_showNotification(msg) {
this.openmct.notifications.info(msg);
} }
} }

View File

@ -48,14 +48,29 @@ export function getDefaultNotebook() {
return JSON.parse(notebookStorage); return JSON.parse(notebookStorage);
} }
export async function getDefaultNotebookLink(openmct, domainObject = null) {
if (!domainObject) {
return null;
}
const path = await openmct.objects.getOriginalPath(domainObject.identifier)
.then(objectPath => objectPath
.map(o => o && openmct.objects.makeKeyString(o.identifier))
.reverse()
.join('/')
);
const { page, section } = getDefaultNotebook();
return `#/browse/${path}?sectionId=${section.id}&pageId=${page.id}`;
}
export function setDefaultNotebook(openmct, notebookStorage, domainObject) { export function setDefaultNotebook(openmct, notebookStorage, domainObject) {
observeDefaultNotebookObject(openmct, notebookStorage.notebookMeta, domainObject); observeDefaultNotebookObject(openmct, notebookStorage, domainObject);
saveDefaultNotebook(notebookStorage); saveDefaultNotebook(notebookStorage);
} }
export function setDefaultNotebookSection(section) { export function setDefaultNotebookSection(section) {
const notebookStorage = getDefaultNotebook(); const notebookStorage = getDefaultNotebook();
notebookStorage.section = section; notebookStorage.section = section;
saveDefaultNotebook(notebookStorage); saveDefaultNotebook(notebookStorage);
} }

View File

@ -29,6 +29,11 @@
@click="maximize()" @click="maximize()"
> >
<span class="c-message-banner__message">{{ activeModel.message }}</span> <span class="c-message-banner__message">{{ activeModel.message }}</span>
<span v-if="haslink"
class="c-message-banner__message"
:class="[haslink ? getLinkProps.cssClass : '']"
>{{ getLinkProps.text }}</span>
<progress-bar <progress-bar
v-if="activeModel.progressPerc !== undefined" v-if="activeModel.progressPerc !== undefined"
class="c-message-banner__progress-bar" class="c-message-banner__progress-bar"
@ -43,6 +48,7 @@
<script> <script>
import ProgressBar from '../../components/ProgressBar.vue'; import ProgressBar from '../../components/ProgressBar.vue';
let activeNotification = undefined; let activeNotification = undefined;
let maximizedDialog = undefined; let maximizedDialog = undefined;
let minimizeButton = { let minimizeButton = {
@ -78,11 +84,20 @@ export default {
message: undefined, message: undefined,
progressPerc: undefined, progressPerc: undefined,
progressText: undefined, progressText: undefined,
minimized: undefined minimized: undefined,
options: undefined
} }
}; };
}, },
computed: { computed: {
haslink() {
const options = this.activeModel.options;
return options && options.link;
},
getLinkProps() {
return this.activeModel.options.link;
},
progressWidth() { progressWidth() {
return { return {
width: this.activeModel.progress + '%' width: this.activeModel.progress + '%'
@ -145,6 +160,15 @@ export default {
activeNotification.off('destroy', dismissMaximizedDialog); activeNotification.off('destroy', dismissMaximizedDialog);
}, },
maximize() { maximize() {
if (this.haslink) {
const linkProps = this.getLinkProps;
linkProps.onClick();
activeNotification.dismiss();
return;
}
if (this.activeModel.progressPerc !== undefined) { if (this.activeModel.progressPerc !== undefined) {
maximizedDialog = this.openmct.overlays.progressDialog({ maximizedDialog = this.openmct.overlays.progressDialog({
buttons: [minimizeButton], buttons: [minimizeButton],
@ -164,6 +188,5 @@ export default {
} }
} }
} }
}; };
</script> </script>