Files
openmct/src/plugins/notebook/components/NotebookComponent.vue
Scott Bell 26d3bd1e69 When dropping an unsupported file onto a notebook entry, tell the user it isnt supported (#7115)
* handle unknown files and deal with copy/paste

* add some nice errors if couch pukes

* added how to adjust couchdb limits

* throw error on anntotation change

* keep blockquotes

* add test for blockquotes

* allow multi-file drop of images too

* add test for big files

* spell check

* check for null

* need to ignore console errors

* reorder tests so we can ignore console errors

* when creating new entry, ready it for editing

* fix tests and empty embeds

* fix tests

* found similar issue from notebooks in plots
2023-10-12 07:34:32 +02:00

1073 lines
33 KiB
Vue

<!--
Open MCT, Copyright (c) 2014-2023, 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.
-->
<template>
<div class="c-notebook" :class="[{ 'c-notebook--restricted': isRestricted }]">
<div class="c-notebook__head">
<Search
class="c-notebook__search"
:value="search"
@input="search = $event"
@clear="resetSearch()"
/>
</div>
<SearchResults
v-if="search.length"
ref="searchResults"
:domain-object="domainObject"
:results="searchResults"
@cancel-edit="cancelTransaction"
@editing-entry="startTransaction"
@change-section-page="changeSelectedSection"
@update-entries="updateEntries"
/>
<div v-if="!search.length" class="c-notebook__body">
<Sidebar
ref="sidebar"
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
:class="sidebarClasses"
:default-page-id="defaultPageId"
:selected-page-id="getSelectedPageId()"
:default-section-id="defaultSectionId"
:selected-section-id="getSelectedSectionId()"
:domain-object="domainObject"
:page-title="domainObject.configuration.pageTitle"
:section-title="domainObject.configuration.sectionTitle"
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
@default-page-deleted="cleanupDefaultNotebook"
@default-section-deleted="cleanupDefaultNotebook"
@pages-changed="pagesChanged"
@select-page="selectPage"
@sections-changed="sectionsChanged"
@select-section="selectSection"
@toggle-nav="toggleNav"
/>
<div
class="c-notebook__page-view"
:class="{
'c-notebook--page-locked': selectedPage?.isLocked,
'c-notebook--page-unlocked': !selectedPage?.isLocked
}"
>
<div class="c-notebook__page-view__header">
<button
class="c-notebook__toggle-nav-button c-icon-button c-icon-button--major icon-menu-hamburger"
@click="toggleNav"
></button>
<div class="c-notebook__page-view__path c-path">
<span class="c-notebook__path__section c-path__item">
{{ selectedSection ? selectedSection.name : '' }}
</span>
<span class="c-notebook__path__page c-path__item">
{{ selectedPage ? selectedPage.name : '' }}
</span>
</div>
<div class="c-notebook__page-view__controls">
<select v-model="showTime" class="c-notebook__controls__time">
<option value="0" :selected="showTime === 0">Show all</option>
<option value="1" :selected="showTime === 1">Last hour</option>
<option value="8" :selected="showTime === 8">Last 8 hours</option>
<option value="24" :selected="showTime === 24">Last 24 hours</option>
</select>
<select v-model="defaultSort" class="c-notebook__controls__time">
<option value="newest" :selected="defaultSort === 'newest'">Newest first</option>
<option value="oldest" :selected="defaultSort === 'oldest'">Oldest first</option>
</select>
</div>
</div>
<div
v-if="selectedPage && !selectedPage.isLocked"
:class="{ disabled: activeTransaction }"
class="c-notebook__drag-area icon-plus"
@click="newEntry(null, $event)"
@dragover="dragOver"
@drop.capture="dropCapture"
@drop="dropOnEntry($event)"
>
<span class="c-notebook__drag-area__label">
To start a new entry, click here or drag and drop any object
</span>
</div>
<progress-bar
v-if="savingTransaction"
class="c-telemetry-table__progress-bar"
:model="{ progressPerc: null }"
/>
<div v-if="selectedPage && selectedPage.isLocked" class="c-notebook__page-locked-message">
<div class="icon-lock"></div>
<div class="c-notebook__page-locked-message-text">
This page has been committed and cannot be modified or removed
</div>
</div>
<div
v-if="selectedSection && selectedPage"
ref="notebookEntries"
class="c-notebook__entries"
aria-label="Notebook Entries"
>
<NotebookEntry
v-for="entry in filteredAndSortedEntries"
:key="entry.id"
:entry="entry"
:domain-object="domainObject"
:notebook-annotations="notebookAnnotations[entry.id]"
:selected-page="selectedPage"
:selected-section="selectedSection"
:read-only="false"
:is-locked="selectedPage.isLocked"
:selected-entry-id="selectedEntryId"
@cancel-edit="cancelTransaction"
@editing-entry="startTransaction"
@delete-entry="deleteEntry"
@update-entry="updateEntry"
@entry-selection="entrySelection(entry)"
/>
</div>
<div
v-if="isRestricted && filteredAndSortedEntries?.length > 0 && !selectedPage.isLocked"
class="c-notebook__commit-entries-control"
>
<button
class="c-button commit-button icon-lock"
title="Commit entries and lock this page from further changes"
@click="lockPage()"
>
<span class="c-button__label">Commit Entries</span>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { debounce } from 'lodash';
import Search from '@/ui/components/SearchComponent.vue';
import ProgressBar from '../../../ui/components/ProgressBar.vue';
import objectLink from '../../../ui/mixins/object-link';
import { isNotebookViewType, RESTRICTED_NOTEBOOK_TYPE } from '../notebook-constants';
import {
addNotebookEntry,
createNewEmbed,
createNewImageEmbed,
getEntryPosById,
getNotebookEntries,
mutateObject,
selectEntry
} from '../utils/notebook-entries';
import {
saveNotebookImageDomainObject,
updateNamespaceOfDomainObject
} from '../utils/notebook-image';
import {
clearDefaultNotebook,
getDefaultNotebook,
setDefaultNotebook,
setDefaultNotebookPageId,
setDefaultNotebookSectionId
} from '../utils/notebook-storage';
import NotebookEntry from './NotebookEntry.vue';
import SearchResults from './SearchResults.vue';
import Sidebar from './SidebarComponent.vue';
function objectCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
export default {
components: {
NotebookEntry,
Search,
SearchResults,
Sidebar,
ProgressBar
},
inject: ['agent', 'openmct', 'snapshotContainer'],
props: {
domainObject: {
type: Object,
required: true
}
},
data() {
return {
defaultPageId: this.getDefaultPageId(),
defaultSectionId: this.getDefaultSectionId(),
selectedSectionId: this.getSelectedSectionId(),
selectedPageId: this.getSelectedPageId(),
defaultSort: this.domainObject.configuration.defaultSort,
focusEntryId: null,
isRestricted: this.domainObject.type === RESTRICTED_NOTEBOOK_TYPE,
search: '',
searchResults: [],
lastLocalAnnotationCreation: 0,
showTime: this.domainObject.configuration.showTime || 0,
showNav: false,
sidebarCoversEntries: false,
filteredAndSortedEntries: [],
notebookAnnotations: {},
selectedEntryId: undefined,
activeTransaction: false,
savingTransaction: false,
sections: this.domainObject.configuration.sections || []
};
},
computed: {
pages() {
return this.getPages() || [];
},
selectedPage() {
const pages = this.getPages();
if (!pages.length) {
return undefined;
}
const selectedPage = pages.find((page) => page.id === this.selectedPageId);
if (selectedPage) {
return selectedPage;
}
const defaultPage = pages.find((page) => page.id === this.defaultPageId);
if (defaultPage) {
return defaultPage;
}
return this.pages[0];
},
selectedSection() {
if (!this.sections.length) {
return undefined;
}
const selectedSection = this.sections.find(
(section) => section.id === this.selectedSectionId
);
if (selectedSection) {
return selectedSection;
}
const defaultSection = this.sections.find((section) => section.id === this.defaultSectionId);
if (defaultSection) {
return defaultSection;
}
return this.sections[0];
},
sidebarClasses() {
let sidebarClasses = [];
if (this.showNav) {
sidebarClasses.push('is-expanded');
}
if (this.sidebarCoversEntries) {
sidebarClasses.push('c-drawer--overlays');
} else {
sidebarClasses.push('c-drawer--push');
}
return sidebarClasses;
}
},
watch: {
search() {
this.getSearchResults();
},
defaultSort() {
mutateObject(this.openmct, this.domainObject, 'configuration.defaultSort', this.defaultSort);
this.filterAndSortEntries();
},
showTime() {
mutateObject(this.openmct, this.domainObject, 'configuration.showTime', this.showTime);
}
},
beforeMount() {
this.getSearchResults = debounce(this.getSearchResults, 500);
this.syncUrlWithPageAndSection = debounce(this.syncUrlWithPageAndSection, 100);
},
async created() {
this.transaction = null;
this.abortController = new AbortController();
try {
await this.loadAnnotations();
} catch (err) {
if (err.name !== 'AbortError') {
throw err;
}
}
},
mounted() {
this.formatSidebar();
this.setSectionAndPageFromUrl();
this.openmct.selection.on('change', this.updateSelection);
window.addEventListener('orientationchange', this.formatSidebar);
window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
this.filterAndSortEntries();
this.unobserveEntries = this.openmct.objects.observe(
this.domainObject,
'*',
this.filterAndSortEntries
);
},
beforeUnmount() {
this.abortController.abort();
if (this.unlisten) {
this.unlisten();
}
if (this.unobserveEntries) {
this.unobserveEntries();
}
Object.keys(this.notebookAnnotations).forEach((entryID) => {
const notebookAnnotationsForEntry = this.notebookAnnotations[entryID];
notebookAnnotationsForEntry.forEach((notebookAnnotation) => {
this.openmct.objects.destroyMutable(notebookAnnotation);
});
});
window.removeEventListener('orientationchange', this.formatSidebar);
window.removeEventListener('hashchange', this.setSectionAndPageFromUrl);
this.openmct.selection.off('change', this.updateSelection);
},
updated: function () {
this.$nextTick(() => {
this.focusOnEntryId();
});
},
methods: {
changeSectionPage(newParams, oldParams, changedParams) {
if (isNotebookViewType(newParams.view)) {
return;
}
let pageId = newParams.pageId;
let sectionId = newParams.sectionId;
if (!pageId && !sectionId) {
return;
}
this.sections.forEach((section) => {
section.isSelected = Boolean(section.id === sectionId);
if (section.isSelected) {
section.pages.forEach((page) => {
page.isSelected = Boolean(page.id === pageId);
});
}
});
},
updateSelection(selection) {
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
if (selection?.[0]?.[0]?.context?.targetDetails?.[keyString]?.entryId === undefined) {
this.selectedEntryId = undefined;
}
},
async loadAnnotations() {
if (!this.openmct.annotation.getAvailableTags().length) {
// don't bother loading annotations if there are no tags
return;
}
this.lastLocalAnnotationCreation = this.domainObject.annotationLastCreated ?? 0;
const foundAnnotations = await this.openmct.annotation.getAnnotations(
this.domainObject.identifier,
this.abortController.signal
);
foundAnnotations.forEach((foundAnnotation) => {
const target = foundAnnotation.targets?.[0];
const entryId = target.entryId;
if (!this.notebookAnnotations[entryId]) {
this.notebookAnnotations[entryId] = [];
}
const annotationExtant = this.notebookAnnotations[entryId].some((existingAnnotation) => {
return this.openmct.objects.areIdsEqual(
existingAnnotation.identifier,
foundAnnotation.identifier
);
});
if (!annotationExtant) {
const annotationArray = this.notebookAnnotations[entryId];
const mutableAnnotation = this.openmct.objects.toMutable(foundAnnotation);
annotationArray.push(mutableAnnotation);
}
});
},
filterAndSortEntries() {
const filterTime = this.openmct.time.now();
const pageEntries =
getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage) || [];
const hours = parseInt(this.showTime, 10);
const filteredPageEntriesByTime = hours
? pageEntries.filter((entry) => filterTime - entry.createdOn <= hours * 60 * 60 * 1000)
: pageEntries;
this.filteredAndSortedEntries =
this.defaultSort === 'oldest'
? filteredPageEntriesByTime
: [...filteredPageEntriesByTime].reverse();
if (this.lastLocalAnnotationCreation < this.domainObject.annotationLastCreated) {
this.loadAnnotations().catch((err) => {
if (err.name !== 'AbortError') {
throw err;
}
});
}
},
changeSelectedSection({ sectionId, pageId }) {
const sections = this.sections.map((s) => {
s.isSelected = false;
if (s.id === sectionId) {
s.isSelected = true;
}
s.pages.forEach((p, i) => {
p.isSelected = false;
if (pageId && pageId === p.id) {
p.isSelected = true;
}
if (!pageId && i === 0) {
p.isSelected = true;
}
});
return s;
});
this.sectionsChanged({ sections });
this.resetSearch();
},
cleanupDefaultNotebook() {
this.defaultPageId = undefined;
this.defaultSectionId = undefined;
this.removeDefaultClass(this.domainObject.identifier);
clearDefaultNotebook();
},
lockPage() {
let prompt = this.openmct.overlays.dialog({
iconClass: 'alert',
message:
'This action will lock this page and disallow any new entries, or editing of existing entries. Do you want to continue?',
buttons: [
{
label: 'Lock Page',
callback: () => {
this.selectedPage.isLocked = true;
// cant be default if it's locked
if (this.selectedPage.id === this.defaultPageId) {
this.cleanupDefaultNotebook();
}
if (!this.selectedSection.isLocked) {
this.selectedSection.isLocked = true;
}
mutateObject(
this.openmct,
this.domainObject,
'configuration.sections',
this.sections
);
if (!this.domainObject.locked) {
mutateObject(this.openmct, this.domainObject, 'locked', true);
}
prompt.dismiss();
}
},
{
label: 'Cancel',
callback: () => {
prompt.dismiss();
}
}
]
});
},
setSectionAndPageFromUrl() {
let sectionId =
this.getSectionIdFromUrl() || this.getDefaultSectionId() || this.getSelectedSectionId();
let pageId = this.getPageIdFromUrl() || this.getDefaultPageId() || this.getSelectedPageId();
this.selectSection(sectionId);
this.selectPage(pageId);
},
createNotebookStorageObject() {
const page = this.selectedPage;
const section = this.selectedSection;
return {
name: this.domainObject.name,
identifier: this.domainObject.identifier,
link: this.getLinktoNotebook(),
defaultSectionId: section.id,
defaultPageId: page.id
};
},
deleteEntry(entryId) {
const entryPos = getEntryPosById(
entryId,
this.domainObject,
this.selectedSection,
this.selectedPage
);
if (entryPos === -1) {
this.openmct.notifications.alert('Warning: unable to delete entry');
console.error(
`unable to delete entry ${entryId} from section ${this.selectedSection}, page ${this.selectedPage}`
);
this.cancelTransaction();
return;
}
const dialog = this.openmct.overlays.dialog({
iconClass: 'alert',
message: 'This action will permanently delete this entry. Do you wish to continue?',
buttons: [
{
label: 'Ok',
emphasis: true,
callback: () => {
const entries = getNotebookEntries(
this.domainObject,
this.selectedSection,
this.selectedPage
);
if (entries) {
entries.splice(entryPos, 1);
this.updateEntries(entries);
this.filterAndSortEntries();
this.removeAnnotations(entryId);
} else {
this.cancelTransaction();
}
dialog.dismiss();
}
},
{
label: 'Cancel',
callback: () => {
dialog.dismiss();
}
}
]
});
},
removeAnnotations(entryId) {
if (this.notebookAnnotations[entryId]) {
this.openmct.annotation.deleteAnnotations(this.notebookAnnotations[entryId]);
}
},
checkEntryPos(entry) {
const entryPos = getEntryPosById(
entry.id,
this.domainObject,
this.selectedSection,
this.selectedPage
);
if (entryPos === -1) {
this.openmct.notifications.alert('Warning: unable to tag entry');
console.error(
`unable to tag entry ${entry} from section ${this.selectedSection}, page ${this.selectedPage}`
);
return false;
}
return true;
},
dragOver(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
},
dropCapture(event) {
const isEditing = this.openmct.editor.isEditing();
if (isEditing) {
this.openmct.editor.cancel();
}
},
async dropOnEntry(dropEvent) {
dropEvent.preventDefault();
dropEvent.stopImmediatePropagation();
const dataTransferFiles = Array.from(dropEvent.dataTransfer.files);
const localImageDropped = dataTransferFiles.some((file) => file.type.includes('image'));
const snapshotId = dropEvent.dataTransfer.getData('openmct/snapshot/id');
const domainObjectData = dropEvent.dataTransfer.getData('openmct/domain-object-path');
const imageUrl = dropEvent.dataTransfer.getData('URL');
if (localImageDropped) {
// local image(s) dropped from disk (file)
const embeds = [];
await Promise.all(
dataTransferFiles.map(async (file) => {
if (file.type.includes('image')) {
const imageData = file;
const imageEmbed = await createNewImageEmbed(
imageData,
this.openmct,
imageData?.name
);
embeds.push(imageEmbed);
}
})
);
this.newEntry(embeds);
} 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);
const namespace = this.domainObject.identifier.namespace;
const notebookImageDomainObject = updateNamespaceOfDomainObject(
snapshot.notebookImageDomainObject,
namespace
);
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
} else if (domainObjectData) {
// plain domain object
const objectPath = JSON.parse(domainObjectData);
const bounds = this.openmct.time.bounds();
const snapshotMeta = {
bounds,
link: null,
objectPath,
openmct: this.openmct
};
const embed = await createNewEmbed(snapshotMeta);
this.newEntry([embed]);
} else {
this.openmct.notifications.error(
`Unknown object(s) dropped and cannot embed. Try again with an image or domain object.`
);
console.warn(
`Unknown object(s) dropped and cannot embed. Try again with an image or domain object.`
);
return;
}
},
focusOnEntryId() {
if (!this.focusEntryId) {
return;
}
const element = this.$refs.notebookEntries.querySelector(`#${this.focusEntryId}`);
if (!element) {
return;
}
element.focus();
this.focusEntryId = null;
},
formatSidebar() {
/*
Determine if the sidebar should slide over content, or compress it
Slide over checks:
- phone (all orientations)
- tablet portrait
- in a layout frame (within .c-so-view)
*/
const isPhone = this.agent.isPhone();
const isTablet = this.agent.isTablet();
const isPortrait = this.agent.isPortrait();
const isInLayout = Boolean(this.$el.closest('.c-so-view'));
const sidebarCoversEntries = isPhone || (isTablet && isPortrait) || isInLayout;
this.sidebarCoversEntries = sidebarCoversEntries;
},
getDefaultPageId() {
return this.isDefaultNotebook() ? getDefaultNotebook().defaultPageId : undefined;
},
isDefaultNotebook() {
const defaultNotebook = getDefaultNotebook();
const defaultNotebookIdentifier = defaultNotebook && defaultNotebook.identifier;
return (
defaultNotebookIdentifier !== null &&
this.openmct.objects.areIdsEqual(defaultNotebookIdentifier, this.domainObject.identifier)
);
},
getDefaultSectionId() {
return this.isDefaultNotebook() ? getDefaultNotebook().defaultSectionId : undefined;
},
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) {
return section.pages.find((p) => p.id === id);
},
getSection(id) {
return this.sections.find((s) => s.id === id);
},
getSearchResults() {
if (!this.search.length) {
return [];
}
const output = [];
const sections = this.domainObject.configuration.sections;
const entries = this.domainObject.configuration.entries;
const searchTextLower = this.search.toLowerCase();
const originalSearchText = this.search;
let sectionTrackPageHit;
let pageTrackEntryHit;
let sectionTrackEntryHit;
sections.forEach((section) => {
const pages = section.pages;
let resultMetadata = {
originalSearchText,
sectionHit: section.name && section.name.toLowerCase().includes(searchTextLower)
};
sectionTrackPageHit = false;
sectionTrackEntryHit = false;
pages.forEach((page) => {
resultMetadata.pageHit = page.name && page.name.toLowerCase().includes(searchTextLower);
pageTrackEntryHit = false;
if (resultMetadata.pageHit) {
sectionTrackPageHit = true;
}
// check for no entries first
if (entries[section.id] && entries[section.id][page.id]) {
const pageEntries = entries[section.id][page.id];
pageEntries.forEach((entry) => {
const entryHit = entry.text && entry.text.toLowerCase().includes(searchTextLower);
// any entry hit goes in, it's the most unique of the hits
if (entryHit) {
resultMetadata.entryHit = entryHit;
pageTrackEntryHit = true;
sectionTrackEntryHit = true;
output.push(
objectCopy({
metadata: resultMetadata,
section,
page,
entry
})
);
}
});
}
// all entries checked, now in pages,
// if page hit, but not in results, need to add
if (resultMetadata.pageHit && !pageTrackEntryHit) {
resultMetadata.entryHit = false;
output.push(
objectCopy({
metadata: resultMetadata,
section,
page
})
);
}
});
// all pages checked, now in sections,
// if section hit, but not in results, need to add and default page
if (resultMetadata.sectionHit && !sectionTrackPageHit && !sectionTrackEntryHit) {
resultMetadata.entryHit = false;
resultMetadata.pageHit = false;
output.push(
objectCopy({
metadata: resultMetadata,
section,
page: pages[0]
})
);
}
});
this.searchResults = output;
},
getPages() {
if (!this.selectedSection || !this.selectedSection.pages.length) {
return [];
}
return this.selectedSection.pages;
},
getSelectedPageId() {
return this.selectedPage?.id;
},
getSelectedSectionId() {
return this.selectedSection?.id;
},
async newEntry(embeds, event) {
this.startTransaction();
this.resetSearch();
const notebookStorage = this.createNotebookStorageObject();
this.updateDefaultNotebook(notebookStorage);
const id = await addNotebookEntry(this.openmct, this.domainObject, notebookStorage, embeds);
const element = this.$refs.notebookEntries.querySelector(`#${id}`);
const entryAnnotations = this.notebookAnnotations[id] ?? {};
selectEntry({
element,
entryId: id,
domainObject: this.domainObject,
openmct: this.openmct,
notebookAnnotations: entryAnnotations
});
if (event) {
event.stopPropagation();
}
this.filterAndSortEntries();
this.focusEntryId = id;
this.selectedEntryId = id;
// put entry into edit mode
this.$nextTick(() => {
element.dispatchEvent(new Event('click'));
});
},
orientationChange() {
this.formatSidebar();
},
pagesChanged({ pages = [], id = null }) {
const selectedSection = this.selectedSection;
if (!selectedSection) {
return;
}
selectedSection.pages = pages;
const sections = this.sections.map((section) => {
if (section.id === selectedSection.id) {
section = selectedSection;
}
return section;
});
this.sectionsChanged({ sections });
},
removeDefaultClass(identifier) {
this.openmct.status.delete(identifier);
},
resetSearch() {
this.search = '';
this.searchResults = [];
},
toggleNav() {
this.showNav = !this.showNav;
},
updateDefaultNotebook(updatedNotebookStorageObject) {
if (!this.isDefaultNotebook()) {
const persistedNotebookStorageObject = getDefaultNotebook();
if (
persistedNotebookStorageObject &&
persistedNotebookStorageObject.identifier !== undefined
) {
this.removeDefaultClass(persistedNotebookStorageObject.identifier);
}
setDefaultNotebook(this.openmct, updatedNotebookStorageObject, this.domainObject);
}
if (this.defaultSectionId !== updatedNotebookStorageObject.defaultSectionId) {
setDefaultNotebookSectionId(updatedNotebookStorageObject.defaultSectionId);
this.defaultSectionId = updatedNotebookStorageObject.defaultSectionId;
}
if (this.defaultPageId !== updatedNotebookStorageObject.defaultPageId) {
setDefaultNotebookPageId(updatedNotebookStorageObject.defaultPageId);
this.defaultPageId = updatedNotebookStorageObject.defaultPageId;
}
},
updateDefaultNotebookSection(sections, id) {
if (!id) {
return;
}
const notebookStorage = getDefaultNotebook();
if (!notebookStorage || notebookStorage.identifier.key !== this.domainObject.identifier.key) {
return;
}
const defaultNotebookSectionId = notebookStorage.defaultSectionId;
if (defaultNotebookSectionId === id) {
const section = sections.find((s) => s.id === id);
if (!section) {
this.removeDefaultClass(this.domainObject.identifier);
clearDefaultNotebook();
return;
}
}
if (id !== defaultNotebookSectionId) {
return;
}
setDefaultNotebookSectionId(defaultNotebookSectionId);
},
updateEntry(entry) {
const entries = getNotebookEntries(
this.domainObject,
this.selectedSection,
this.selectedPage
);
const entryPos = getEntryPosById(
entry.id,
this.domainObject,
this.selectedSection,
this.selectedPage
);
entries[entryPos] = entry;
this.updateEntries(entries);
},
updateEntries(entries) {
const configuration = this.domainObject.configuration;
const notebookEntries = configuration.entries || {};
notebookEntries[this.selectedSection.id][this.selectedPage.id] = entries;
mutateObject(this.openmct, this.domainObject, 'configuration.entries', notebookEntries);
this.saveTransaction();
},
getPageIdFromUrl() {
return this.openmct.router.getParams().pageId;
},
getSectionIdFromUrl() {
return this.openmct.router.getParams().sectionId;
},
syncUrlWithPageAndSection() {
this.openmct.router.updateParams({
pageId: this.selectedPageId,
sectionId: this.selectedSectionId
});
},
sectionsChanged({ sections, id = undefined }) {
this.sections = [...sections];
this.startTransaction();
mutateObject(this.openmct, this.domainObject, 'configuration.sections', sections);
this.saveTransaction();
this.updateDefaultNotebookSection(sections, id);
},
selectPage(pageId) {
if (!pageId) {
return;
}
this.selectedPageId = pageId;
this.syncUrlWithPageAndSection();
this.filterAndSortEntries();
},
selectSection(sectionId) {
if (!sectionId) {
return;
}
this.selectedSectionId = sectionId;
const pageId = this.selectedSection.pages[0].id;
this.selectPage(pageId);
this.syncUrlWithPageAndSection();
this.filterAndSortEntries();
},
startTransaction() {
if (!this.openmct.objects.isTransactionActive()) {
this.activeTransaction = true;
this.transaction = this.openmct.objects.startTransaction();
}
},
async saveTransaction() {
if (this.transaction !== null) {
this.savingTransaction = true;
try {
await this.transaction.commit();
} finally {
this.endTransaction();
}
}
},
async cancelTransaction() {
if (this.transaction !== null) {
try {
await this.transaction.cancel();
} finally {
this.endTransaction();
}
}
},
entrySelection(entry) {
this.selectedEntryId = entry.id;
},
endTransaction() {
this.openmct.objects.endTransaction();
this.transaction = null;
this.savingTransaction = false;
this.activeTransaction = false;
}
}
};
</script>