Notebook refactor (#2883)

* Code refactoring per https://github.com/nasa/openmct/issues/2825
This commit is contained in:
Nikhil 2020-10-01 15:42:32 -07:00 committed by GitHub
parent 505796d9f0
commit ee60013f45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 764 additions and 398 deletions

View File

@ -86,7 +86,7 @@ module.exports = (config) => {
reports: ['html', 'lcovonly', 'text-summary'],
thresholds: {
global: {
lines: 64
lines: 65
}
}
},

View File

@ -30,7 +30,6 @@ define([
"./src/controllers/CompositeController",
"./src/controllers/ColorController",
"./src/controllers/DialogButtonController",
"./src/controllers/SnapshotPreviewController",
"./res/templates/controls/autocomplete.html",
"./res/templates/controls/checkbox.html",
"./res/templates/controls/datetime.html",
@ -44,8 +43,7 @@ define([
"./res/templates/controls/menu-button.html",
"./res/templates/controls/dialog.html",
"./res/templates/controls/radio.html",
"./res/templates/controls/file-input.html",
"./res/templates/controls/snap-view.html"
"./res/templates/controls/file-input.html"
], function (
MCTForm,
MCTControl,
@ -56,7 +54,6 @@ define([
CompositeController,
ColorController,
DialogButtonController,
SnapshotPreviewController,
autocompleteTemplate,
checkboxTemplate,
datetimeTemplate,
@ -70,8 +67,7 @@ define([
menuButtonTemplate,
dialogTemplate,
radioTemplate,
fileInputTemplate,
snapViewTemplate
fileInputTemplate
) {
return {
@ -157,10 +153,6 @@ define([
{
"key": "file-input",
"template": fileInputTemplate
},
{
"key": "snap-view",
"template": snapViewTemplate
}
],
"controllers": [
@ -194,14 +186,6 @@ define([
"$scope",
"dialogService"
]
},
{
"key": "SnapshotPreviewController",
"implementation": SnapshotPreviewController,
"depends": [
"$scope",
"openmct"
]
}
],
"components": [

View File

@ -1,36 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2020, 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.
-->
<span ng-controller="SnapshotPreviewController"
class='form-control shell'>
<span class='field control {{structure.cssClass}}'>
<image
class="c-ne__embed__snap-thumb"
src="{{imageUrl || structure.src}}"
ng-click="previewImage(imageUrl || structure.src)"
name="mctControl">
</image>
<br>
<a title="Annotate" class="s-button icon-pencil" ng-click="annotateImage(ngModel, field, imageUrl || structure.src)">
<span class="title-label">Annotate</span>
</a>
</span>
</span>

View File

@ -1,132 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
define(
[
'painterro'
],
function (Painterro) {
function SnapshotPreviewController($scope, openmct) {
$scope.previewImage = function (imageUrl) {
let imageDiv = document.createElement('div');
imageDiv.classList = 'image-main s-image-main';
imageDiv.style.backgroundImage = `url(${imageUrl})`;
let previewImageOverlay = openmct.overlays.overlay(
{
element: imageDiv,
size: 'large',
buttons: [
{
label: 'Done',
callback: function () {
previewImageOverlay.dismiss();
}
}
]
}
);
};
$scope.annotateImage = function (ngModel, field, imageUrl) {
$scope.imageUrl = imageUrl;
let div = document.createElement('div'),
painterroInstance = {},
save = false;
div.id = 'snap-annotation';
let annotateImageOverlay = openmct.overlays.overlay(
{
element: div,
size: 'large',
buttons: [
{
label: 'Cancel',
callback: function () {
save = false;
painterroInstance.save();
annotateImageOverlay.dismiss();
}
},
{
label: 'Save',
callback: function () {
save = true;
painterroInstance.save();
annotateImageOverlay.dismiss();
}
}
]
}
);
painterroInstance = Painterro({
id: 'snap-annotation',
activeColor: '#ff0000',
activeColorAlpha: 1.0,
activeFillColor: '#fff',
activeFillColorAlpha: 0.0,
backgroundFillColor: '#000',
backgroundFillColorAlpha: 0.0,
defaultFontSize: 16,
defaultLineWidth: 2,
defaultTool: 'ellipse',
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
translation: {
name: 'en',
strings: {
lineColor: 'Line',
fillColor: 'Fill',
lineWidth: 'Size',
textColor: 'Color',
fontSize: 'Size',
fontStyle: 'Style'
}
},
saveHandler: function (image, done) {
if (save) {
let url = image.asBlob(),
reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = function () {
$scope.imageUrl = reader.result;
ngModel[field] = reader.result;
};
} else {
ngModel.field = imageUrl;
console.warn('You cancelled the annotation!!!');
}
done(true);
}
}).show(imageUrl);
};
}
return SnapshotPreviewController;
}
);

View File

@ -9,10 +9,11 @@
</div>
<SearchResults v-if="search.length"
ref="searchResults"
:results="getSearchResults()"
:domain-object="internalDomainObject"
:results="searchedEntries"
@changeSectionPage="changeSelectedSection"
@updateEntries="updateEntries"
/>
<div v-if="!search.length"
class="c-notebook__body"
>
@ -105,10 +106,10 @@
</template>
<script>
import NotebookEntry from './notebook-entry.vue';
import NotebookEntry from './NotebookEntry.vue';
import Search from '@/ui/components/search.vue';
import SearchResults from './search-results.vue';
import Sidebar from './sidebar.vue';
import SearchResults from './SearchResults.vue';
import Sidebar from './Sidebar.vue';
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
import { addNotebookEntry, createNewEmbed, getNotebookEntries } from '../utils/notebook-entries';
import { throttle } from 'lodash';
@ -153,6 +154,9 @@ export default {
pages() {
return this.getPages() || [];
},
searchedEntries() {
return this.getSearchResults();
},
sections() {
return this.internalDomainObject.configuration.sections || [];
},
@ -172,8 +176,6 @@ export default {
return this.sections.find(section => section.isSelected);
}
},
watch: {
},
beforeMount() {
this.throttledSearchItem = throttle(this.searchItem, 500);
},
@ -259,7 +261,7 @@ export default {
event.preventDefault();
event.stopImmediatePropagation();
const snapshotId = event.dataTransfer.getData('snapshot/id');
const snapshotId = event.dataTransfer.getData('openmct/snapshot/id');
if (snapshotId.length) {
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
this.newEntry(snapshot);

View File

@ -17,7 +17,7 @@
<div v-if="embed.snapshot"
class="c-ne__embed__time"
>
{{ formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss') }}
{{ createdOn }}
</div>
</div>
</div>
@ -25,10 +25,10 @@
<script>
import Moment from 'moment';
import PopupMenu from './popup-menu.vue';
import PopupMenu from './PopupMenu.vue';
import PreviewAction from '../../../ui/preview/PreviewAction';
import Painterro from 'painterro';
import RemoveDialog from '../utils/removeDialog';
import PainterroInstance from '../utils/painterroInstance';
import SnapshotTemplate from './snapshot-template.html';
import Vue from 'vue';
@ -56,7 +56,10 @@ export default {
popupMenuItems: []
};
},
watch: {
computed: {
createdOn() {
return this.formatTime(this.embed.createdOn, 'YYYY-MM-DD HH:mm:ss');
}
},
mounted() {
this.addPopupMenuItems();
@ -78,95 +81,44 @@ export default {
this.popupMenuItems = [removeEmbed, preview];
},
annotateSnapshot() {
const self = this;
let save = false;
let painterroInstance = {};
const annotateVue = new Vue({
template: '<div id="snap-annotation"></div>'
});
}).$mount();
let annotateOverlay = self.openmct.overlays.overlay({
element: annotateVue.$mount().$el,
const painterroInstance = new PainterroInstance(annotateVue.$el, this.updateSnapshot);
const annotateOverlay = this.openmct.overlays.overlay({
element: annotateVue.$el,
size: 'large',
dismissable: false,
buttons: [
{
label: 'Cancel',
callback: function () {
save = false;
painterroInstance.save();
emphasis: true,
callback: () => {
painterroInstance.dismiss();
annotateOverlay.dismiss();
}
},
{
label: 'Save',
callback: function () {
save = true;
callback: () => {
painterroInstance.save();
annotateOverlay.dismiss();
this.snapshotOverlay.dismiss();
this.openSnapshot();
}
}
],
onDestroy: function () {
onDestroy: () => {
annotateVue.$destroy(true);
}
});
painterroInstance = Painterro({
id: 'snap-annotation',
activeColor: '#ff0000',
activeColorAlpha: 1.0,
activeFillColor: '#fff',
activeFillColorAlpha: 0.0,
backgroundFillColor: '#000',
backgroundFillColorAlpha: 0.0,
defaultFontSize: 16,
defaultLineWidth: 2,
defaultTool: 'ellipse',
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
translation: {
name: 'en',
strings: {
lineColor: 'Line',
fillColor: 'Fill',
lineWidth: 'Size',
textColor: 'Color',
fontSize: 'Size',
fontStyle: 'Style'
}
},
saveHandler: function (image, done) {
if (save) {
const url = image.asBlob();
const reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = function () {
const snapshot = reader.result;
const snapshotObject = {
src: snapshot,
type: url.type,
size: url.size,
modified: Date.now()
};
self.embed.snapshot = snapshotObject;
self.updateEmbed(self.embed);
};
} else {
console.log('You cancelled the annotation!!!');
}
done(true);
}
}).show(this.embed.snapshot.src);
painterroInstance.intialize();
painterroInstance.show(this.embed.snapshot.src);
},
changeLocation() {
const link = this.embed.historicLink;
if (!link) {
return;
}
const bounds = this.openmct.time.bounds();
const isTimeBoundChanged = this.embed.bounds.start !== bounds.start
@ -209,7 +161,8 @@ export default {
this.snapshot = new Vue({
data: () => {
return {
embed: self.embed
createdOn: this.createdOn,
embed: this.embed
};
},
methods: {
@ -218,13 +171,11 @@ export default {
exportImage: self.exportImage
},
template: SnapshotTemplate
});
}).$mount();
const snapshotOverlay = this.openmct.overlays.overlay({
element: this.snapshot.$mount().$el,
onDestroy: () => {
this.snapshot.$destroy(true);
},
this.snapshotOverlay = this.openmct.overlays.overlay({
element: this.snapshot.$el,
onDestroy: () => this.snapshot.$destroy(true),
size: 'large',
dismissable: true,
buttons: [
@ -232,7 +183,7 @@ export default {
label: 'Done',
emphasis: true,
callback: () => {
snapshotOverlay.dismiss();
this.snapshotOverlay.dismiss();
}
}
]
@ -262,6 +213,10 @@ export default {
},
updateEmbed(embed) {
this.$emit('updateEmbed', embed);
},
updateSnapshot(snapshotObject) {
this.embed.snapshot = snapshotObject;
this.updateEmbed(this.embed);
}
}
};

View File

@ -1,13 +1,13 @@
<template>
<div class="c-notebook__entry c-ne has-local-controls"
@dragover="dragover"
@drop.capture="dropCapture"
@drop.prevent="dropOnEntry(entry.id, $event)"
@dragover="changeCursor"
@drop.capture="cancelEditMode"
@drop.prevent="dropOnEntry"
>
<div class="c-ne__time-and-content">
<div class="c-ne__time">
<span>{{ formatTime(entry.createdOn, 'YYYY-MM-DD') }}</span>
<span>{{ formatTime(entry.createdOn, 'HH:mm:ss') }}</span>
<span>{{ createdOnDate }}</span>
<span>{{ createdOnTime }}</span>
</div>
<div class="c-ne__content">
<div :id="entry.id"
@ -15,8 +15,8 @@
:class="{'c-input-inline' : !readOnly }"
:contenteditable="!readOnly"
:style="!entry.text.length ? defaultEntryStyle : ''"
@blur="textBlur($event, entry.id)"
@focus="textFocus($event, entry.id)"
@blur="updateEntryValue($event, entry.id)"
@focus="updateCurrentEntryValue($event, entry.id)"
>{{ entry.text.length ? entry.text : defaultText }}</div>
<div class="c-snapshots c-ne__embeds">
<NotebookEmbed v-for="embed in entry.embeds"
@ -57,7 +57,7 @@
</template>
<script>
import NotebookEmbed from './notebook-embed.vue';
import NotebookEmbed from './NotebookEmbed.vue';
import { createNewEmbed, getEntryPosById, getNotebookEntries } from '../utils/notebook-entries';
import Moment from 'moment';
@ -114,29 +114,32 @@ export default {
defaultText: 'add description'
};
},
watch: {
entry() {
computed: {
createdOnDate() {
return this.formatTime(this.entry.createdOn, 'YYYY-MM-DD');
},
readOnly(readOnly) {
},
selectedSection(selectedSection) {
},
selectedPage(selectedSection) {
createdOnTime() {
return this.formatTime(this.entry.createdOn, 'HH:mm:ss');
}
},
mounted() {
this.updateEntries = this.updateEntries.bind(this);
},
beforeDestory() {
this.dropOnEntry = this.dropOnEntry.bind(this);
},
methods: {
cancelEditMode(event) {
const isEditing = this.openmct.editor.isEditing();
if (isEditing) {
this.openmct.editor.cancel();
}
},
changeCursor() {
event.preventDefault();
event.dataTransfer.dropEffect = "copy";
},
deleteEntry() {
const self = this;
if (!self.domainObject || !self.selectedSection || !self.selectedPage || !self.entry.id) {
return;
}
const entryPosById = this.entryPosById(this.entry.id);
const entryPosById = self.entryPosById(self.entry.id);
if (entryPosById === -1) {
return;
}
@ -151,7 +154,7 @@ export default {
callback: () => {
const entries = getNotebookEntries(self.domainObject, self.selectedSection, self.selectedPage);
entries.splice(entryPosById, 1);
this.updateEntries(entries);
self.updateEntries(entries);
dialog.dismiss();
}
},
@ -164,24 +167,10 @@ export default {
]
});
},
dragover() {
event.preventDefault();
event.dataTransfer.dropEffect = "copy";
},
dropCapture(event) {
const isEditing = this.openmct.editor.isEditing();
if (isEditing) {
this.openmct.editor.cancel();
}
},
dropOnEntry(entryId, $event) {
dropOnEntry($event) {
event.stopImmediatePropagation();
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const snapshotId = $event.dataTransfer.getData('snapshot/id');
const snapshotId = $event.dataTransfer.getData('openmct/snapshot/id');
if (snapshotId.length) {
this.moveSnapshot(snapshotId);
@ -190,7 +179,7 @@ export default {
const data = $event.dataTransfer.getData('openmct/domain-object-path');
const objectPath = JSON.parse(data);
const entryPos = this.entryPosById(entryId);
const entryPos = this.entryPosById(this.entry.id);
const bounds = this.openmct.time.bounds();
const snapshotMeta = {
bounds,
@ -253,7 +242,44 @@ export default {
selection.removeAllRanges();
selection.addRange(range);
},
textBlur($event, entryId) {
updateCurrentEntryValue($event) {
if (this.readOnly) {
return;
}
const target = $event.target;
this.currentEntryValue = target ? target.innerText : '';
if (!this.entry.text.length) {
this.selectTextInsideElement(target);
}
},
updateEmbed(newEmbed) {
this.entry.embeds.some(e => {
const found = (e.id === newEmbed.id);
if (found) {
e = newEmbed;
}
return found;
});
this.updateEntry(this.entry);
},
updateEntry(newEntry) {
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
entries.some(entry => {
const found = (entry.id === newEntry.id);
if (found) {
entry = newEntry;
}
return found;
});
this.updateEntries(entries);
},
updateEntryValue($event, entryId) {
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
@ -272,42 +298,6 @@ export default {
this.updateEntries(entries);
}
},
textFocus($event) {
if (this.readOnly || !this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const target = $event.target;
this.currentEntryValue = target ? target.innerText : '';
if (!this.entry.text.length) {
this.selectTextInsideElement(target);
}
},
updateEmbed(newEmbed) {
let embed = this.entry.embeds.find(e => e.id === newEmbed.id);
if (!embed) {
return;
}
embed = newEmbed;
this.updateEntry(this.entry);
},
updateEntry(newEntry) {
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
entries.forEach(entry => {
if (entry.id === newEntry.id) {
entry = newEntry;
}
});
this.updateEntries(entries);
},
updateEntries(entries) {
this.$emit('updateEntries', entries);
}

View File

@ -49,8 +49,8 @@
</template>
<script>
import NotebookEmbed from './notebook-embed.vue';
import PopupMenu from './popup-menu.vue';
import NotebookEmbed from './NotebookEmbed.vue';
import PopupMenu from './PopupMenu.vue';
import RemoveDialog from '../utils/removeDialog';
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
@ -81,8 +81,6 @@ export default {
this.snapshotContainer.on(EVENT_SNAPSHOTS_UPDATED, this.snapshotsUpdated);
this.snapshots = this.snapshotContainer.getSnapshots();
},
beforeDestory() {
},
methods: {
addPopupMenuItems() {
const removeSnapshot = {
@ -122,7 +120,7 @@ export default {
},
startEmbedDrag(snapshot, event) {
event.dataTransfer.setData('text/plain', snapshot.id);
event.dataTransfer.setData('snapshot/id', snapshot.id);
event.dataTransfer.setData('openmct/snapshot/id', snapshot.id);
},
updateSnapshot(snapshot) {
this.snapshotContainer.updateSnapshot(snapshot);

View File

@ -18,7 +18,7 @@
</template>
<script>
import SnapshotContainerComponent from './notebook-snapshot-container.vue';
import SnapshotContainerComponent from './NotebookSnapshotContainer.vue';
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
import Vue from 'vue';

View File

@ -19,7 +19,7 @@
<script>
import { deleteNotebookEntries } from '../utils/notebook-entries';
import { getDefaultNotebook } from '../utils/notebook-storage';
import Page from './page-component.vue';
import Page from './PageComponent.vue';
export default {
inject: ['openmct'],
@ -70,12 +70,6 @@ export default {
return {
};
},
watch: {
},
mounted() {
},
destroyed() {
},
methods: {
deletePage(id) {
const selectedSection = this.sections.find(s => s.isSelected);

View File

@ -14,7 +14,7 @@
</template>
<script>
import PopupMenu from './popup-menu.vue';
import PopupMenu from './PopupMenu.vue';
import RemoveDialog from '../utils/removeDialog';
export default {
@ -55,8 +55,6 @@ export default {
this.addPopupMenuItems();
this.toggleContentEditable();
},
destroyed() {
},
methods: {
addPopupMenuItems() {
const removePage = {

View File

@ -8,7 +8,7 @@
</template>
<script>
import MenuItems from './menu-items.vue';
import MenuItems from './MenuItems.vue';
import Vue from 'vue';
export default {

View File

@ -4,26 +4,34 @@
<div class="c-notebook__entries">
<NotebookEntry v-for="(result, index) in results"
:key="index"
:domain-object="domainObject"
:result="result"
:entry="result.entry"
:read-only="true"
:selected-page="null"
:selected-section="null"
:selected-page="result.page"
:selected-section="result.section"
@changeSectionPage="changeSectionPage"
@updateEntries="updateEntries"
/>
</div>
</div>
</template>
<script>
import NotebookEntry from './notebook-entry.vue';
import NotebookEntry from './NotebookEntry.vue';
export default {
inject: ['openmct', 'domainObject'],
inject: ['openmct', 'snapshotContainer'],
components: {
NotebookEntry
},
props: {
domainObject: {
type: Object,
default() {
return {};
}
},
results: {
type: Array,
default() {
@ -31,19 +39,12 @@ export default {
}
}
},
data() {
return {};
},
watch: {
results(newResults) {}
},
destroyed() {
},
mounted() {
},
methods: {
changeSectionPage(data) {
this.$emit('changeSectionPage', data);
},
updateEntries(entries) {
this.$emit('updateEntries', entries);
}
}
};

View File

@ -19,7 +19,7 @@
<script>
import { deleteNotebookEntries } from '../utils/notebook-entries';
import { getDefaultNotebook } from '../utils/notebook-storage';
import sectionComponent from './section-component.vue';
import sectionComponent from './SectionComponent.vue';
export default {
inject: ['openmct'],
@ -57,12 +57,6 @@ export default {
return {
};
},
watch: {
},
mounted() {
},
destroyed() {
},
methods: {
deleteSection(id) {
const section = this.sections.find(s => s.id === id);

View File

@ -17,7 +17,7 @@
</style>
<script>
import PopupMenu from './popup-menu.vue';
import PopupMenu from './PopupMenu.vue';
import RemoveDialog from '../utils/removeDialog';
export default {
@ -58,8 +58,6 @@ export default {
this.addPopupMenuItems();
this.toggleContentEditable();
},
destroyed() {
},
methods: {
addPopupMenuItems() {
const removeSection = {

View File

@ -56,8 +56,8 @@
</template>
<script>
import SectionCollection from './section-collection.vue';
import PageCollection from './page-collection.vue';
import SectionCollection from './SectionCollection.vue';
import PageCollection from './PageCollection.vue';
import uuid from 'uuid';
export default {
@ -139,8 +139,6 @@ export default {
this.addSection();
}
},
destroyed() {
},
methods: {
addPage() {
const pageTitle = this.pageTitle;

View File

@ -13,7 +13,7 @@
<div class="l-browse-bar__end">
<div class="l-browse-bar__snapshot-datetime">
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
SNAPSHOT {{ createdOn }}
</div>
<span class="c-button-set c-button-set--strip-h">
<button

View File

@ -1,5 +1,5 @@
import Notebook from './components/notebook.vue';
import NotebookSnapshotIndicator from './components/notebook-snapshot-indicator.vue';
import Notebook from './components/Notebook.vue';
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
import SnapshotContainer from './snapshot-container';
import Vue from 'vue';
@ -95,7 +95,8 @@ export default function NotebookPlugin() {
template: '<NotebookSnapshotIndicator></NotebookSnapshotIndicator>'
});
const indicator = {
element: notebookSnapshotIndicator.$mount().$el
element: notebookSnapshotIndicator.$mount().$el,
key: 'notebook-snapshot-indicator'
};
openmct.indicators.add(indicator);

View File

@ -0,0 +1,222 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
import { createOpenMct, createMouseEvent, resetApplicationState } from 'utils/testing';
import NotebookPlugin from './plugin';
import Vue from 'vue';
let openmct;
let notebookDefinition;
let notebookPlugin;
let element;
let child;
let appHolder;
const notebookDomainObject = {
identifier: {
key: 'notebook',
namespace: ''
},
type: 'notebook'
};
describe("Notebook plugin:", () => {
beforeAll(done => {
appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
notebookPlugin = new NotebookPlugin();
openmct.install(notebookPlugin);
notebookDefinition = openmct.types.get('notebook').definition;
notebookDefinition.initialize(notebookDomainObject);
openmct.on('start', done);
openmct.start(appHolder);
document.body.append(appHolder);
});
afterAll(() => {
appHolder.remove();
resetApplicationState(openmct);
});
it("has type as Notebook", () => {
expect(notebookDefinition.name).toEqual('Notebook');
});
it("is creatable", () => {
expect(notebookDefinition.creatable).toEqual(true);
});
describe("Notebook view:", () => {
let notebookViewProvider;
let notebookView;
beforeEach(() => {
const notebookViewObject = {
...notebookDomainObject,
id: "test-object",
name: 'Notebook',
configuration: {
defaultSort: 'oldest',
entries: {},
pageTitle: 'Page',
sections: [],
sectionTitle: 'Section',
type: 'General'
}
};
const notebookObject = {
name: 'Notebook View',
key: 'notebook-vue',
creatable: true
};
const applicableViews = openmct.objectViews.get(notebookViewObject);
notebookViewProvider = applicableViews.find(viewProvider => viewProvider.key === notebookObject.key);
notebookView = notebookViewProvider.view(notebookViewObject);
notebookView.show(child);
return Vue.nextTick();
});
afterEach(() => {
notebookView.destroy();
});
it("provides notebook view", () => {
expect(notebookViewProvider).toBeDefined();
});
it("renders notebook element", () => {
const notebookElement = element.querySelectorAll('.c-notebook');
expect(notebookElement.length).toBe(1);
});
it("renders major elements", () => {
const notebookElement = element.querySelector('.c-notebook');
const searchElement = notebookElement.querySelector('.c-search');
const sidebarElement = notebookElement.querySelector('.c-sidebar');
const pageViewElement = notebookElement.querySelector('.c-notebook__page-view');
const hasMajorElements = Boolean(searchElement && sidebarElement && pageViewElement);
expect(hasMajorElements).toBe(true);
});
});
describe("Notebook Snapshots view:", () => {
let snapshotIndicator;
let drawerElement;
function clickSnapshotIndicator() {
const indicator = element.querySelector('.icon-notebook');
const button = indicator.querySelector('button');
const clickEvent = createMouseEvent('click');
button.dispatchEvent(clickEvent);
}
beforeAll(() => {
snapshotIndicator = openmct.indicators.indicatorObjects
.find(indicator => indicator.key === 'notebook-snapshot-indicator').element;
element.append(snapshotIndicator);
return Vue.nextTick();
});
afterAll(() => {
snapshotIndicator.remove();
if (drawerElement) {
drawerElement.remove();
}
});
beforeEach(() => {
drawerElement = document.querySelector('.l-shell__drawer');
});
afterEach(() => {
if (drawerElement) {
drawerElement.classList.remove('is-expanded');
}
});
it("has Snapshots indicator", () => {
const hasSnapshotIndicator = snapshotIndicator !== null && snapshotIndicator !== undefined;
expect(hasSnapshotIndicator).toBe(true);
});
it("snapshots container has class isExpanded", () => {
let classes = drawerElement.classList;
const isExpandedBefore = classes.contains('is-expanded');
clickSnapshotIndicator();
classes = drawerElement.classList;
const isExpandedAfterFirstClick = classes.contains('is-expanded');
const success = isExpandedBefore === false
&& isExpandedAfterFirstClick === true;
expect(success).toBe(true);
});
it("snapshots container does not have class isExpanded", () => {
let classes = drawerElement.classList;
const isExpandedBefore = classes.contains('is-expanded');
clickSnapshotIndicator();
classes = drawerElement.classList;
const isExpandedAfterFirstClick = classes.contains('is-expanded');
clickSnapshotIndicator();
classes = drawerElement.classList;
const isExpandedAfterSecondClick = classes.contains('is-expanded');
const success = isExpandedBefore === false
&& isExpandedAfterFirstClick === true
&& isExpandedAfterSecondClick === false;
expect(success).toBe(true);
});
it("show notebook snapshots container text", () => {
clickSnapshotIndicator();
const notebookSnapshots = drawerElement.querySelector('.l-browse-bar__object-name');
const snapshotsText = notebookSnapshots.textContent.trim();
expect(snapshotsText).toBe('Notebook Snapshots');
});
});
});

View File

@ -0,0 +1,195 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
import * as NotebookEntries from './notebook-entries';
import { createOpenMct, spyOnBuiltins, resetApplicationState } from 'utils/testing';
const notebookStorage = {
domainObject: {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
}
},
notebookMeta: {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
}
},
section: {
id: '03a79b6a-971c-4e56-9892-ec536332c3f0',
isDefault: true,
isSelected: true,
name: 'section',
pages: [],
sectionTitle: 'Section'
},
page: {
id: '8b548fd9-2b8a-4b02-93a9-4138e22eba00',
isDefault: true,
isSelected: true,
name: 'page',
pageTitle: 'Page'
}
};
const notebookEntries = {
'03a79b6a-971c-4e56-9892-ec536332c3f0': {
'8b548fd9-2b8a-4b02-93a9-4138e22eba00': []
}
};
const notebookDomainObject = {
identifier: {
key: 'notebook',
namespace: ''
},
type: 'notebook',
configuration: {
defaultSort: 'oldest',
entries: notebookEntries,
pageTitle: 'Page',
sections: [],
sectionTitle: 'Section',
type: 'General'
}
};
const selectedSection = {
id: '03a79b6a-971c-4e56-9892-ec536332c3f0',
isDefault: false,
isSelected: true,
name: 'Day 1',
pages: [
{
id: '54deb3d5-8267-4be4-95e9-3579ed8c082d',
isDefault: false,
isSelected: false,
name: 'Shift 1',
pageTitle: 'Page'
},
{
id: '2ea41c78-8e60-4657-a350-53f1a1fa3021',
isDefault: false,
isSelected: false,
name: 'Shift 2',
pageTitle: 'Page'
},
{
id: '8b548fd9-2b8a-4b02-93a9-4138e22eba00',
isDefault: false,
isSelected: true,
name: 'Unnamed Page',
pageTitle: 'Page'
}
],
sectionTitle: 'Section'
};
const selectedPage = {
id: '8b548fd9-2b8a-4b02-93a9-4138e22eba00',
isDefault: false,
isSelected: true,
name: 'Unnamed Page',
pageTitle: 'Page'
};
let openmct;
describe('Notebook Entries:', () => {
beforeEach(done => {
openmct = createOpenMct();
window.localStorage.setItem('notebook-storage', null);
spyOnBuiltins(['mutate'], openmct.objects);
done();
});
afterEach(() => {
notebookDomainObject.configuration.entries[selectedSection.id][selectedPage.id] = [];
resetApplicationState(openmct);
});
it('getNotebookEntries has no entries', () => {
const entries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);
expect(entries.length).toEqual(0);
});
it('addNotebookEntry mutates object', () => {
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
expect(openmct.objects.mutate).toHaveBeenCalled();
});
it('addNotebookEntry adds entry', () => {
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const entries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);
expect(entries.length).toEqual(1);
});
it('getEntryPosById returns valid position', () => {
const entryId = NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const position = NotebookEntries.getEntryPosById(entryId, notebookDomainObject, selectedSection, selectedPage);
expect(position).toEqual(0);
});
it('getEntryPosById returns valid position', () => {
const entryId1 = NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const position1 = NotebookEntries.getEntryPosById(entryId1, notebookDomainObject, selectedSection, selectedPage);
const entryId2 = NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const position2 = NotebookEntries.getEntryPosById(entryId2, notebookDomainObject, selectedSection, selectedPage);
const entryId3 = NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const position3 = NotebookEntries.getEntryPosById(entryId3, notebookDomainObject, selectedSection, selectedPage);
const success = position1 === 0
&& position2 === 1
&& position3 === 2;
expect(success).toBe(true);
});
it('deleteNotebookEntries mutates object', () => {
openmct.objects.mutate.calls.reset();
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
NotebookEntries.deleteNotebookEntries(openmct, notebookDomainObject, selectedSection, selectedPage);
expect(openmct.objects.mutate).toHaveBeenCalledTimes(2);
});
it('deleteNotebookEntries deletes correct entry', () => {
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
NotebookEntries.deleteNotebookEntries(openmct, notebookDomainObject, selectedSection, selectedPage);
const afterEntries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);
expect(afterEntries).toEqual(null);
});
});

View File

@ -0,0 +1,125 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
import * as NotebookStorage from './notebook-storage';
import { createOpenMct, resetApplicationState } from 'utils/testing';
const notebookStorage = {
domainObject: {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
}
},
notebookMeta: {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
}
},
section: {
id: 'temp-section',
isDefault: false,
isSelected: true,
name: 'section',
pages: [],
sectionTitle: 'Section'
},
page: {
id: 'temp-page',
isDefault: false,
isSelected: true,
name: 'page',
pageTitle: 'Page'
}
};
let openmct = createOpenMct();
describe('Notebook Storage:', () => {
beforeEach((done) => {
openmct = createOpenMct();
window.localStorage.setItem('notebook-storage', null);
done();
});
afterEach(() => {
resetApplicationState(openmct);
});
it('has empty local Storage', () => {
expect(window.localStorage).not.toBeNull();
});
it('has null notebookstorage on clearDefaultNotebook', () => {
window.localStorage.setItem('notebook-storage', notebookStorage);
NotebookStorage.clearDefaultNotebook();
const defaultNotebook = NotebookStorage.getDefaultNotebook();
expect(defaultNotebook).toBeNull();
});
it('has correct notebookstorage on setDefaultNotebook', () => {
NotebookStorage.setDefaultNotebook(openmct, notebookStorage);
const defaultNotebook = NotebookStorage.getDefaultNotebook();
expect(JSON.stringify(defaultNotebook)).toBe(JSON.stringify(notebookStorage));
});
it('has correct section on setDefaultNotebookSection', () => {
const section = {
id: 'new-temp-section',
isDefault: true,
isSelected: true,
name: 'new section',
pages: [],
sectionTitle: 'Section'
};
NotebookStorage.setDefaultNotebook(openmct, notebookStorage);
NotebookStorage.setDefaultNotebookSection(section);
const defaultNotebook = NotebookStorage.getDefaultNotebook();
const newSection = defaultNotebook.section;
expect(JSON.stringify(section)).toBe(JSON.stringify(newSection));
});
it('has correct page on setDefaultNotebookPage', () => {
const page = {
id: 'new-temp-page',
isDefault: true,
isSelected: true,
name: 'new page',
pageTitle: 'Page'
};
NotebookStorage.setDefaultNotebook(openmct, notebookStorage);
NotebookStorage.setDefaultNotebookPage(page);
const defaultNotebook = NotebookStorage.getDefaultNotebook();
const newPage = defaultNotebook.page;
expect(JSON.stringify(page)).toBe(JSON.stringify(newPage));
});
});

View File

@ -0,0 +1,79 @@
import Painterro from 'painterro';
const DEFAULT_CONFIG = {
activeColor: '#ff0000',
activeColorAlpha: 1.0,
activeFillColor: '#fff',
activeFillColorAlpha: 0.0,
backgroundFillColor: '#000',
backgroundFillColorAlpha: 0.0,
defaultFontSize: 16,
defaultLineWidth: 2,
defaultTool: 'ellipse',
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
translation: {
name: 'en',
strings: {
lineColor: 'Line',
fillColor: 'Fill',
lineWidth: 'Size',
textColor: 'Color',
fontSize: 'Size',
fontStyle: 'Style'
}
}
};
export default class PainterroInstance {
constructor(element, saveCallback) {
this.elementId = element.id;
this.isSave = false;
this.painterroInstance = null;
this.saveCallback = saveCallback;
}
dismiss() {
this.isSave = false;
this.painterroInstance.save();
}
intialize() {
this.config = Object.assign({}, DEFAULT_CONFIG);
this.config.id = this.elementId;
this.config.saveHandler = this.saveHandler.bind(this);
this.painterro = Painterro(this.config);
}
save() {
this.isSave = true;
this.painterroInstance.save();
}
saveHandler(image, done) {
if (this.isSave) {
const self = this;
const url = image.asBlob();
const reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = () => {
const snapshot = reader.result;
const snapshotObject = {
src: snapshot,
type: url.type,
size: url.size,
modified: Date.now()
};
self.saveCallback(snapshotObject);
};
}
done(true);
}
show(src) {
this.painterroInstance = this.painterro.show(src);
}
}

View File

@ -113,7 +113,7 @@
<script>
import ViewSwitcher from './ViewSwitcher.vue';
import NotebookMenuSwitcher from '@/plugins/notebook/components/notebook-menu-switcher.vue';
import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue';
const PLACEHOLDER_OBJECT = {};