diff --git a/src/plugins/condition/components/inspector/conditional-styles.scss b/src/plugins/condition/components/inspector/conditional-styles.scss
index 161967d22b..4033d27b01 100644
--- a/src/plugins/condition/components/inspector/conditional-styles.scss
+++ b/src/plugins/condition/components/inspector/conditional-styles.scss
@@ -1,3 +1,25 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
/********************************************* INSPECTOR STYLES TAB */
.c-inspect-styles {
> * + * {
diff --git a/src/plugins/condition/criterion/AllTelemetryCriterion.js b/src/plugins/condition/criterion/AllTelemetryCriterion.js
new file mode 100644
index 0000000000..8355975d50
--- /dev/null
+++ b/src/plugins/condition/criterion/AllTelemetryCriterion.js
@@ -0,0 +1,172 @@
+/*****************************************************************************
+ * 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 EventEmitter from 'EventEmitter';
+import {OPERATIONS} from '../utils/operations';
+import {computeCondition} from "@/plugins/condition/utils/evaluator";
+
+export default class TelemetryCriterion extends EventEmitter {
+
+ /**
+ * Subscribes/Unsubscribes to telemetry and emits the result
+ * of operations performed on the telemetry data returned and a given input value.
+ * @constructor
+ * @param telemetryDomainObjectDefinition {id: uuid, operation: enum, input: Array, metadata: string, key: {domainObject.identifier} }
+ * @param openmct
+ */
+ constructor(telemetryDomainObjectDefinition, openmct) {
+ super();
+
+ this.openmct = openmct;
+ this.objectAPI = this.openmct.objects;
+ this.telemetryAPI = this.openmct.telemetry;
+ this.timeAPI = this.openmct.time;
+ this.id = telemetryDomainObjectDefinition.id;
+ this.telemetry = telemetryDomainObjectDefinition.telemetry;
+ this.operation = telemetryDomainObjectDefinition.operation;
+ this.telemetryObjects = Object.assign({}, telemetryDomainObjectDefinition.telemetryObjects);
+ this.input = telemetryDomainObjectDefinition.input;
+ this.metadata = telemetryDomainObjectDefinition.metadata;
+ this.telemetryDataCache = {};
+ }
+
+ updateTelemetry(telemetryObjects) {
+ this.telemetryObjects = Object.assign({}, telemetryObjects);
+ }
+
+ formatData(data, telemetryObjects) {
+ if (data) {
+ this.telemetryDataCache[data.id] = this.computeResult(data);
+ }
+
+ let keys = Object.keys(telemetryObjects);
+ keys.forEach((key) => {
+ let telemetryObject = telemetryObjects[key];
+ const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
+ if (this.telemetryDataCache[id] === undefined) {
+ this.telemetryDataCache[id] = false;
+ }
+ });
+
+ const datum = {
+ result: computeCondition(this.telemetryDataCache, this.telemetry === 'all')
+ };
+
+ if (data) {
+ // TODO check back to see if we should format times here
+ this.timeAPI.getAllTimeSystems().forEach(timeSystem => {
+ datum[timeSystem.key] = data[timeSystem.key]
+ });
+ }
+ return datum;
+ }
+
+ handleSubscription(data, telemetryObjects) {
+ if(this.isValid()) {
+ this.emitEvent('criterionResultUpdated', this.formatData(data, telemetryObjects));
+ } else {
+ this.emitEvent('criterionResultUpdated', this.formatData({}, telemetryObjects));
+ }
+ }
+
+ findOperation(operation) {
+ for (let i=0; i < OPERATIONS.length; i++) {
+ if (operation === OPERATIONS[i].name) {
+ return OPERATIONS[i].operation;
+ }
+ }
+ return null;
+ }
+
+ computeResult(data) {
+ let result = false;
+ if (data) {
+ let comparator = this.findOperation(this.operation);
+ let params = [];
+ params.push(data[this.metadata]);
+ if (this.input instanceof Array && this.input.length) {
+ this.input.forEach(input => params.push(input));
+ }
+ if (typeof comparator === 'function') {
+ result = comparator(params);
+ }
+ }
+ return result;
+ }
+
+ emitEvent(eventName, data) {
+ this.emit(eventName, {
+ id: this.id,
+ data: data
+ });
+ }
+
+ isValid() {
+ return (this.telemetry === 'any' || this.telemetry === 'all') && this.metadata && this.operation;
+ }
+
+ requestLAD(options) {
+ options = Object.assign({},
+ options,
+ {
+ strategy: 'latest',
+ size: 1
+ }
+ );
+
+ if (!this.isValid()) {
+ return this.formatData({}, options.telemetryObjects);
+ }
+
+ const telemetryRequests = options.telemetryObjects
+ .map(telemetryObject => this.telemetryAPI.request(
+ telemetryObject,
+ options
+ ));
+
+ return Promise.all(telemetryRequests)
+ .then(telemetryRequestsResults => {
+ telemetryRequestsResults.forEach((results, index) => {
+ const latestDatum = results.length ? results[results.length - 1] : {};
+ if (index === telemetryRequestsResults.length-1) {
+ //when the last result is computed, we return the result
+ return {
+ id: this.id,
+ data: this.formatData(latestDatum, options.telemetryObjects)
+ };
+ } else {
+ if (latestDatum) {
+ this.telemetryDataCache[latestDatum.id] = this.computeResult(latestDatum);
+ }
+ }
+ });
+ });
+ }
+
+ destroy() {
+ this.emitEvent('criterionRemoved');
+ delete this.telemetryObjects;
+ delete this.telemetryDataCache;
+ delete this.telemetryObjectIdAsString;
+ delete this.telemetryObject;
+ }
+}
diff --git a/src/plugins/condition/criterion/TelemetryCriterion.js b/src/plugins/condition/criterion/TelemetryCriterion.js
index 47e0cf19a8..998e27431c 100644
--- a/src/plugins/condition/criterion/TelemetryCriterion.js
+++ b/src/plugins/condition/criterion/TelemetryCriterion.js
@@ -71,6 +71,8 @@ export default class TelemetryCriterion extends EventEmitter {
handleSubscription(data) {
if(this.isValid()) {
this.emitEvent('criterionResultUpdated', this.formatData(data));
+ } else {
+ this.emitEvent('criterionResultUpdated', this.formatData({}));
}
}
diff --git a/src/plugins/condition/plugin.js b/src/plugins/condition/plugin.js
index 1d4855a8f5..7239435ca6 100644
--- a/src/plugins/condition/plugin.js
+++ b/src/plugins/condition/plugin.js
@@ -44,7 +44,7 @@ export default function ConditionPlugin() {
id: uuid(),
configuration: {
name: 'Default',
- output: 'false',
+ output: 'Default',
trigger: 'all',
criteria: []
},
diff --git a/src/plugins/condition/utils/constants.js b/src/plugins/condition/utils/constants.js
index 4df0efc0db..9ccd08436e 100644
--- a/src/plugins/condition/utils/constants.js
+++ b/src/plugins/condition/utils/constants.js
@@ -1,3 +1,25 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
export const TRIGGER = {
ANY: 'any',
ALL: 'all',
diff --git a/src/plugins/condition/utils/evaluator.js b/src/plugins/condition/utils/evaluator.js
index bb11a2b1d8..7e2b70f7e2 100644
--- a/src/plugins/condition/utils/evaluator.js
+++ b/src/plugins/condition/utils/evaluator.js
@@ -1,3 +1,25 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
export const computeCondition = (resultMap, allMustBeTrue) => {
let result = false;
for (let key in resultMap) {
diff --git a/src/plugins/condition/utils/operations.js b/src/plugins/condition/utils/operations.js
index e22d17f03a..3026f887bd 100644
--- a/src/plugins/condition/utils/operations.js
+++ b/src/plugins/condition/utils/operations.js
@@ -1,3 +1,25 @@
+/*****************************************************************************
+ * 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 _ from 'lodash';
export const OPERATIONS = [
diff --git a/src/plugins/condition/utils/styleUtils.js b/src/plugins/condition/utils/styleUtils.js
index 686527ccd7..10dcb986f1 100644
--- a/src/plugins/condition/utils/styleUtils.js
+++ b/src/plugins/condition/utils/styleUtils.js
@@ -1,3 +1,25 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
export const getStyleProp = (key, defaultValue) => {
let styleProp = undefined;
switch(key) {
diff --git a/src/plugins/conditionWidget/ConditionWidgetViewProvider.js b/src/plugins/conditionWidget/ConditionWidgetViewProvider.js
index 34bfe1def0..d34af872d2 100644
--- a/src/plugins/conditionWidget/ConditionWidgetViewProvider.js
+++ b/src/plugins/conditionWidget/ConditionWidgetViewProvider.js
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2019, United States Government
+ * Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
diff --git a/src/plugins/conditionWidget/components/ConditionWidget.vue b/src/plugins/conditionWidget/components/ConditionWidget.vue
index 1377478606..c6011eba25 100644
--- a/src/plugins/conditionWidget/components/ConditionWidget.vue
+++ b/src/plugins/conditionWidget/components/ConditionWidget.vue
@@ -1,3 +1,25 @@
+/*****************************************************************************
+* 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.
+*****************************************************************************/
+
{
- return selectionPath[0].context.layoutItem.type === 'image-view';
- }),
- property: function (selectionPath) {
- return getPath(selectionPath);
- },
- icon: "icon-image",
- title: "Edit image properties",
- dialog: DIALOG_FORM.image
- };
- }
-
function getTextButton(selectedParent, selection) {
return {
control: "button",
@@ -555,9 +539,6 @@ define(['lodash'], function (_) {
getWidthInput(selectedParent, selectedObjects)
];
}
- if (toolbar.url.length === 0) {
- toolbar.url = [getURLButton(selectedParent, selectedObjects)];
- }
if (toolbar.remove.length === 0) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
diff --git a/src/plugins/displayLayout/components/BoxView.vue b/src/plugins/displayLayout/components/BoxView.vue
index 2d48b8ed18..aeb303e6e1 100644
--- a/src/plugins/displayLayout/components/BoxView.vue
+++ b/src/plugins/displayLayout/components/BoxView.vue
@@ -37,7 +37,7 @@
diff --git a/src/plugins/notebook/components/notebook-entry.vue b/src/plugins/notebook/components/notebook-entry.vue
new file mode 100644
index 0000000000..d2d256955a
--- /dev/null
+++ b/src/plugins/notebook/components/notebook-entry.vue
@@ -0,0 +1,316 @@
+
+
+
+
+ {{ formatTime(entry.createdOn, 'YYYY-MM-DD') }}
+ {{ formatTime(entry.createdOn, 'HH:mm:ss') }}
+
+
+
{{ entry.text.length ? entry.text : defaultText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/plugins/notebook/components/notebook-menu-switcher.vue b/src/plugins/notebook/components/notebook-menu-switcher.vue
new file mode 100644
index 0000000000..069df76358
--- /dev/null
+++ b/src/plugins/notebook/components/notebook-menu-switcher.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
diff --git a/src/plugins/notebook/components/notebook-snapshot-container.vue b/src/plugins/notebook/components/notebook-snapshot-container.vue
new file mode 100644
index 0000000000..2f5ace4d85
--- /dev/null
+++ b/src/plugins/notebook/components/notebook-snapshot-container.vue
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+ Notebook Snapshots
+ {{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ There are no Notebook Snapshots currently.
+
+
+
+
+
+
diff --git a/src/plugins/notebook/components/notebook-snapshot-indicator.vue b/src/plugins/notebook/components/notebook-snapshot-indicator.vue
new file mode 100644
index 0000000000..f9f6006339
--- /dev/null
+++ b/src/plugins/notebook/components/notebook-snapshot-indicator.vue
@@ -0,0 +1,97 @@
+
+
+
+ {{ indicatorTitle }}
+
+ {{ expanded ? 'Hide' : 'Show' }}
+
+
+ {{ snapshotCount }}
+
+
+
+
diff --git a/src/plugins/notebook/components/notebook.vue b/src/plugins/notebook/components/notebook.vue
new file mode 100644
index 0000000000..7122854e00
--- /dev/null
+++ b/src/plugins/notebook/components/notebook.vue
@@ -0,0 +1,528 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To start a new entry, click here or drag and drop any object
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/plugins/notebook/components/page-collection.vue b/src/plugins/notebook/components/page-collection.vue
new file mode 100644
index 0000000000..0161f8335f
--- /dev/null
+++ b/src/plugins/notebook/components/page-collection.vue
@@ -0,0 +1,132 @@
+
+
+
+
+
diff --git a/src/plugins/notebook/components/page-component.vue b/src/plugins/notebook/components/page-component.vue
new file mode 100644
index 0000000000..8a93914d36
--- /dev/null
+++ b/src/plugins/notebook/components/page-component.vue
@@ -0,0 +1,146 @@
+
+
+ {{ page.name.length ? page.name : `Unnamed ${pageTitle}` }}
+
+
+
+
+
+
diff --git a/src/plugins/notebook/components/search-results.vue b/src/plugins/notebook/components/search-results.vue
new file mode 100644
index 0000000000..d0ad922a31
--- /dev/null
+++ b/src/plugins/notebook/components/search-results.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
diff --git a/src/plugins/notebook/components/section-collection.vue b/src/plugins/notebook/components/section-collection.vue
new file mode 100644
index 0000000000..f1ef68b7cf
--- /dev/null
+++ b/src/plugins/notebook/components/section-collection.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
diff --git a/src/plugins/notebook/components/section-component.vue b/src/plugins/notebook/components/section-component.vue
new file mode 100644
index 0000000000..3b139c45be
--- /dev/null
+++ b/src/plugins/notebook/components/section-component.vue
@@ -0,0 +1,149 @@
+
+
+ {{ section.name.length ? section.name : `Unnamed ${sectionTitle}` }}
+
+
+
+
+
+
+
+
diff --git a/src/plugins/notebook/components/sidebar.scss b/src/plugins/notebook/components/sidebar.scss
new file mode 100644
index 0000000000..e5c8a8cd00
--- /dev/null
+++ b/src/plugins/notebook/components/sidebar.scss
@@ -0,0 +1,119 @@
+.c-sidebar {
+ @include userSelectNone();
+ background: $sideBarBg;
+ display: flex;
+ justify-content: stretch;
+ max-width: 300px;
+
+ &.c-drawer--push.is-expanded {
+ margin-right: $interiorMargin;
+ width: 50%;
+ }
+
+ &.c-drawer--overlays.is-expanded {
+ width: 95%;
+ }
+
+ > * {
+ // Hardcoded for two columns
+ background: $sideBarBg;
+ display: flex;
+ flex: 1 1 50%;
+ flex-direction: column;
+
+ + * {
+ margin-left: $interiorMarginSm;
+ }
+
+ > * + * {
+ // Add margin-top to first and second level children
+ margin-top: $interiorMargin;
+ }
+ }
+
+ &__pane {
+ > * + * { margin-top: $interiorMargin; }
+ }
+
+ &__header-w {
+ // Wraps header, used for page pane with collapse button
+ display: flex;
+ flex: 0 0 auto;
+ background: $sideBarHeaderBg;
+ align-items: center;
+
+ .c-icon-button {
+ font-size: 0.8em;
+ color: $colorBodyFg;
+ }
+ }
+
+ &__header {
+ color: $sideBarHeaderFg;
+ display: flex;
+ flex: 1 1 auto;
+ padding: $interiorMargin;
+ text-transform: uppercase;
+
+ > * {
+ @include ellipsize();
+ }
+ }
+
+ &__contents-and-controls {
+ // Encloses pane buttons and contents elements
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 auto;
+
+ > * {
+ margin: auto $interiorMargin $interiorMargin $interiorMargin;
+
+ &:first-child {
+ border-bottom: 1px solid $colorInteriorBorder;
+ flex: 0 0 auto;
+ }
+
+ + * {
+ margin-top: $interiorMargin;
+ }
+ }
+ }
+
+ &__contents {
+ flex: 1 1 auto;
+ overflow-x: hidden;
+ overflow-y: auto;
+ padding: auto $interiorMargin;
+ }
+
+ .c-list-button {
+ .c-button {
+ font-size: 0.8em;
+ }
+ }
+
+ .c-list__item {
+ @include hover() {
+ [class*="__menu-indicator"] {
+ opacity: 0.7;
+ transition: $transIn;
+ }
+ }
+
+ > * + * {
+ margin-left: $interiorMargin;
+ }
+
+ &__name {
+ flex: 0 1 auto;
+ }
+
+ &__menu-indicator {
+ flex: 0 0 auto;
+ font-size: 0.8em;
+ opacity: 0;
+ transition: $transOut;
+ }
+ }
+}
diff --git a/src/plugins/notebook/components/sidebar.vue b/src/plugins/notebook/components/sidebar.vue
new file mode 100644
index 0000000000..81f69ce118
--- /dev/null
+++ b/src/plugins/notebook/components/sidebar.vue
@@ -0,0 +1,189 @@
+
+
+
+
+
diff --git a/src/plugins/notebook/res/templates/snapshotTemplate.html b/src/plugins/notebook/components/snapshot-template.html
similarity index 74%
rename from src/plugins/notebook/res/templates/snapshotTemplate.html
rename to src/plugins/notebook/components/snapshot-template.html
index 5bd3220409..5fdf3f09fc 100644
--- a/src/plugins/notebook/res/templates/snapshotTemplate.html
+++ b/src/plugins/notebook/components/snapshot-template.html
@@ -3,10 +3,10 @@
-
diff --git a/src/plugins/notebook/notebook-constants.js b/src/plugins/notebook/notebook-constants.js
new file mode 100644
index 0000000000..6253000460
--- /dev/null
+++ b/src/plugins/notebook/notebook-constants.js
@@ -0,0 +1,3 @@
+export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED';
+export const NOTEBOOK_DEFAULT = 'DEFAULT';
+export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT';
diff --git a/src/plugins/notebook/plugin.js b/src/plugins/notebook/plugin.js
index 0715e31b3d..46810d286d 100644
--- a/src/plugins/notebook/plugin.js
+++ b/src/plugins/notebook/plugin.js
@@ -1,99 +1,134 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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 Notebook from './components/notebook.vue';
+import NotebookSnapshotIndicator from './components/notebook-snapshot-indicator.vue';
+import SnapshotContainer from './snapshot-container';
+import Vue from 'vue';
-define([
- "./src/controllers/NotebookController"
-], function (
- NotebookController
-) {
- var installed = false;
+let installed = false;
- function NotebookPlugin() {
- return function install(openmct) {
- if (installed) {
- return;
- }
+export default function NotebookPlugin() {
+ return function install(openmct) {
+ if (installed) {
+ return;
+ }
- installed = true;
+ installed = true;
- openmct.legacyRegistry.register('notebook', {
- name: 'Notebook Plugin',
- extensions: {
- types: [
+ const notebookType = {
+ name: 'Notebook',
+ description: 'Create and save timestamped notes with embedded object snapshots.',
+ creatable: true,
+ cssClass: 'icon-notebook',
+ initialize: domainObject => {
+ domainObject.configuration = {
+ defaultSort: 'oldest',
+ entries: {},
+ pageTitle: 'Page',
+ sections: [],
+ sectionTitle: 'Section',
+ type: 'General'
+ };
+ },
+ form: [
+ {
+ key: 'defaultSort',
+ name: 'Entry Sorting',
+ control: 'select',
+ options: [
{
- key: 'notebook',
- name: 'Notebook',
- cssClass: 'icon-notebook',
- description: 'Create and save timestamped notes with embedded object snapshots.',
- features: 'creation',
- model: {
- entries: [],
- entryTypes: [],
- defaultSort: 'oldest'
- },
- properties: [
- {
- key: 'defaultSort',
- name: 'Default Sort',
- control: 'select',
- options: [
- {
- name: 'Newest First',
- value: "newest"
- },
- {
- name: 'Oldest First',
- value: "oldest"
- }
- ],
- cssClass: 'l-inline'
- }
- ]
+ name: 'Newest First',
+ value: "newest"
+ },
+ {
+ name: 'Oldest First',
+ value: "oldest"
}
+ ],
+ cssClass: 'l-inline',
+ property: [
+ "configuration",
+ "defaultSort"
+ ]
+ },
+ {
+ key: 'type',
+ name: 'Note book Type',
+ control: 'textfield',
+ cssClass: 'l-inline',
+ property: [
+ "configuration",
+ "type"
+ ]
+ },
+ {
+ key: 'sectionTitle',
+ name: 'Section Title',
+ control: 'textfield',
+ cssClass: 'l-inline',
+ property: [
+ "configuration",
+ "sectionTitle"
+ ]
+ },
+ {
+ key: 'pageTitle',
+ name: 'Page Title',
+ control: 'textfield',
+ cssClass: 'l-inline',
+ property: [
+ "configuration",
+ "pageTitle"
]
}
- });
+ ]
+ };
+ openmct.types.addType('notebook', notebookType);
- openmct.legacyRegistry.enable('notebook');
-
- openmct.objectViews.addProvider({
- key: 'notebook-vue',
- name: 'Notebook View',
- cssClass: 'icon-notebook',
- canView: function (domainObject) {
- return domainObject.type === 'notebook';
- },
- view: function (domainObject) {
- var controller = new NotebookController (openmct, domainObject);
-
- return {
- show: controller.show,
- destroy: controller.destroy
- };
- }
- });
+ const snapshotContainer = new SnapshotContainer(openmct);
+ const notebookSnapshotIndicator = new Vue ({
+ provide: {
+ openmct,
+ snapshotContainer
+ },
+ components: {
+ NotebookSnapshotIndicator
+ },
+ template: '
'
+ });
+ const indicator = {
+ element: notebookSnapshotIndicator.$mount().$el
};
- }
+ openmct.indicators.add(indicator);
- return NotebookPlugin;
-});
+ openmct.objectViews.addProvider({
+ key: 'notebook-vue',
+ name: 'Notebook View',
+ cssClass: 'icon-notebook',
+ canView: function (domainObject) {
+ return domainObject.type === 'notebook';
+ },
+ view: function (domainObject) {
+ let component;
+ return {
+ show(container) {
+ component = new Vue({
+ el: container,
+ components: {
+ Notebook
+ },
+ provide: {
+ openmct,
+ domainObject,
+ snapshotContainer
+ },
+ template: '
'
+ });
+ },
+ destroy() {
+ component.$destroy();
+ }
+ };
+ }
+ });
+ };
+}
diff --git a/src/plugins/notebook/res/templates/embed.html b/src/plugins/notebook/res/templates/embed.html
deleted file mode 100644
index 5890c3f91d..0000000000
--- a/src/plugins/notebook/res/templates/embed.html
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
- {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
-
-
-
\ No newline at end of file
diff --git a/src/plugins/notebook/res/templates/entry.html b/src/plugins/notebook/res/templates/entry.html
deleted file mode 100644
index 349cb3cd7a..0000000000
--- a/src/plugins/notebook/res/templates/entry.html
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- {{formatTime(entry.createdOn, 'YYYY-MM-DD')}}
- {{formatTime(entry.createdOn, 'HH:mm:ss')}}
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/plugins/notebook/res/templates/notebook.html b/src/plugins/notebook/res/templates/notebook.html
deleted file mode 100644
index cbc14af170..0000000000
--- a/src/plugins/notebook/res/templates/notebook.html
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
- Show all
- Last hour
- Last 8 hours
- Last 24 hours
-
-
- Newest first
- Oldest first
-
-
-
-
- To start a new entry, click here or drag and drop any object
-
-
-
\ No newline at end of file
diff --git a/src/plugins/notebook/res/templates/viewSnapshot.html b/src/plugins/notebook/res/templates/viewSnapshot.html
deleted file mode 100644
index 77b23f11b5..0000000000
--- a/src/plugins/notebook/res/templates/viewSnapshot.html
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
-
-
- Annotate
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/plugins/notebook/snapshot-container.js b/src/plugins/notebook/snapshot-container.js
new file mode 100644
index 0000000000..960b4ebde0
--- /dev/null
+++ b/src/plugins/notebook/snapshot-container.js
@@ -0,0 +1,82 @@
+import EventEmitter from 'EventEmitter';
+import { EVENT_SNAPSHOTS_UPDATED } from './notebook-constants';
+const NOTEBOOK_SNAPSHOT_STORAGE = 'notebook-snapshot-storage';
+
+export const NOTEBOOK_SNAPSHOT_MAX_COUNT = 5;
+
+export default class SnapshotContainer extends EventEmitter {
+ constructor(openmct) {
+ super();
+
+ if (!SnapshotContainer.instance) {
+ SnapshotContainer.instance = this;
+ }
+
+ this.openmct = openmct;
+
+ return SnapshotContainer.instance;
+ }
+
+ addSnapshot(embedObject) {
+ const snapshots = this.getSnapshots();
+ if (snapshots.length >= NOTEBOOK_SNAPSHOT_MAX_COUNT) {
+ snapshots.pop();
+ }
+
+ snapshots.unshift(embedObject);
+
+ return this.saveSnapshots(snapshots);
+ }
+
+ getSnapshot(id) {
+ const snapshots = this.getSnapshots();
+
+ return snapshots.find(s => s.id === id);
+ }
+
+ getSnapshots() {
+ const snapshots = window.localStorage.getItem(NOTEBOOK_SNAPSHOT_STORAGE) || '[]';
+
+ return JSON.parse(snapshots);
+ }
+
+ removeSnapshot(id) {
+ if (!id) {
+ return;
+ }
+
+ const snapshots = this.getSnapshots();
+ const filteredsnapshots = snapshots.filter(snapshot => snapshot.id !== id);
+
+ return this.saveSnapshots(filteredsnapshots);
+ }
+
+ removeAllSnapshots() {
+ return this.saveSnapshots([]);
+ }
+
+ saveSnapshots(snapshots) {
+ try {
+ window.localStorage.setItem(NOTEBOOK_SNAPSHOT_STORAGE, JSON.stringify(snapshots));
+ this.emit(EVENT_SNAPSHOTS_UPDATED, true);
+
+ return true;
+ } catch (e) {
+ const message = 'Insufficient memory in localstorage to store snapshot, please delete some snapshots and try again!';
+ this.openmct.notifications.error(message);
+
+ return false;
+ }
+ }
+
+ updateSnapshot(snapshot) {
+ const snapshots = this.getSnapshots();
+ const updatedSnapshots = snapshots.map(s => {
+ return s.id === snapshot.id
+ ? snapshot
+ : s;
+ });
+
+ return this.saveSnapshots(updatedSnapshots);
+ }
+}
diff --git a/src/plugins/notebook/snapshot.js b/src/plugins/notebook/snapshot.js
new file mode 100644
index 0000000000..44bddccc91
--- /dev/null
+++ b/src/plugins/notebook/snapshot.js
@@ -0,0 +1,74 @@
+import { addNotebookEntry, createNewEmbed } from './utils/notebook-entries';
+import { getDefaultNotebook } from './utils/notebook-storage';
+import { NOTEBOOK_DEFAULT } from '@/plugins/notebook/notebook-constants';
+import SnapshotContainer from './snapshot-container';
+
+export default class Snapshot {
+ constructor(openmct) {
+ this.openmct = openmct;
+ this.snapshotContainer = new SnapshotContainer(openmct);
+ this.exportImageService = openmct.$injector.get('exportImageService');
+ this.dialogService = openmct.$injector.get('dialogService');
+
+ this.capture = this.capture.bind(this);
+ this._saveSnapShot = this._saveSnapShot.bind(this);
+ }
+
+ capture(snapshotMeta, notebookType, domElement) {
+ this.exportImageService.exportPNGtoSRC(domElement, 's-status-taking-snapshot')
+ .then(function (blob) {
+ const reader = new window.FileReader();
+ reader.readAsDataURL(blob);
+ reader.onloadend = function () {
+ this._saveSnapShot(notebookType, reader.result, snapshotMeta);
+ }.bind(this);
+ }.bind(this));
+ }
+
+ /**
+ * @private
+ */
+ _saveSnapShot(notebookType, imageUrl, snapshotMeta) {
+ const snapshot = imageUrl ? { src: imageUrl } : '';
+ const embed = createNewEmbed(snapshotMeta, snapshot);
+ if (notebookType === NOTEBOOK_DEFAULT) {
+ this._saveToDefaultNoteBook(embed);
+
+ return;
+ }
+
+ this._saveToNotebookSnapshots(embed);
+ }
+
+ /**
+ * @private
+ */
+ _saveToDefaultNoteBook(embed) {
+ const notebookStorage = getDefaultNotebook();
+ this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
+ .then(domainObject => {
+ addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
+
+ const defaultPath = `${domainObject.name} > ${notebookStorage.section.name} > ${notebookStorage.page.name}`;
+ const msg = `Saved to Notebook ${defaultPath}`;
+ this._showNotification(msg);
+ });
+ }
+
+ /**
+ * @private
+ */
+ _saveToNotebookSnapshots(embed) {
+ const saved = this.snapshotContainer.addSnapshot(embed);
+ if (!saved) {
+ return;
+ }
+
+ const msg = 'Saved to Notebook Snapshots - click to view.';
+ this._showNotification(msg);
+ }
+
+ _showNotification(msg) {
+ this.openmct.notifications.info(msg);
+ }
+}
diff --git a/src/plugins/notebook/src/controllers/EmbedController.js b/src/plugins/notebook/src/controllers/EmbedController.js
deleted file mode 100644
index 63ac92673a..0000000000
--- a/src/plugins/notebook/src/controllers/EmbedController.js
+++ /dev/null
@@ -1,302 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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([
- 'moment',
- 'zepto',
- '../../res/templates/snapshotTemplate.html',
- 'vue',
- 'painterro'
-],
-function (
- Moment,
- $,
- SnapshotTemplate,
- Vue,
- Painterro
-) {
- function EmbedController(openmct, domainObject) {
- this.openmct = openmct;
- this.domainObject = domainObject;
- this.popupService = openmct.$injector.get('popupService');
- this.agentService = openmct.$injector.get('agentService');
-
- this.exposedData = this.exposedData.bind(this);
- this.exposedMethods = this.exposedMethods.bind(this);
- this.toggleActionMenu = this.toggleActionMenu.bind(this);
- }
-
- EmbedController.prototype.openSnapshot = function (domainObject, entry, embed) {
-
- function annotateSnapshot(openmct) {
- return function () {
-
- var save = false,
- painterroInstance = {},
- annotateVue = new Vue({
- template: '
'
- }),
- self = this;
-
- let annotateOverlay = openmct.overlays.overlay({
- element: annotateVue.$mount().$el,
- size: 'large',
- dismissable: false,
- buttons: [
- {
- label: 'Cancel',
- callback: function () {
- save = false;
- painterroInstance.save();
- annotateOverlay.dismiss();
- }
- },
- {
- label: 'Save',
- callback: function () {
- save = true;
- painterroInstance.save();
- annotateOverlay.dismiss();
- }
- }
- ],
- onDestroy: function () {
- 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) {
- var entryPos = self.findInArray(domainObject.entries, entry.id),
- embedPos = self.findInArray(entry.embeds, embed.id);
-
- if (entryPos !== -1 && embedPos !== -1) {
- var url = image.asBlob(),
- reader = new window.FileReader();
-
- reader.readAsDataURL(url);
- reader.onloadend = function () {
- var snapshot = reader.result,
- snapshotObject = {
- src: snapshot,
- type: url.type,
- size: url.size,
- modified: Date.now()
- },
- dirString = 'entries[' + entryPos + '].embeds[' + embedPos + '].snapshot';
-
- openmct.objects.mutate(domainObject, dirString, snapshotObject);
- };
- }
- } else {
- console.log('You cancelled the annotation!!!');
- }
- done(true);
- }
- }).show(embed.snapshot.src);
- };
- }
-
- var self = this,
- snapshot = new Vue({
- data: function () {
- return {
- embed: self.embed
- };
- },
- methods: {
- formatTime: self.formatTime,
- annotateSnapshot: annotateSnapshot(self.openmct),
- findInArray: self.findInArray
- },
- template: SnapshotTemplate
- });
-
- var snapshotOverlay = this.openmct.overlays.overlay({
- element: snapshot.$mount().$el,
- onDestroy: () => {snapshot.$destroy(true)},
- size: 'large',
- dismissable: true,
- buttons: [
- {
- label: 'Done',
- emphasis: true,
- callback: function () {
- snapshotOverlay.dismiss();
- }
- }
- ]
- });
- };
-
- EmbedController.prototype.formatTime = function (unixTime, timeFormat) {
- return Moment(unixTime).format(timeFormat);
- };
-
- EmbedController.prototype.findInArray = function (array, id) {
- var foundId = -1;
-
- array.forEach(function (element, index) {
- if (element.id === id) {
- foundId = index;
- return;
- }
- });
-
- return foundId;
- };
-
- EmbedController.prototype.populateActionMenu = function (openmct, actions) {
- return function () {
- var self = this;
-
- openmct.objects.get(self.embed.type).then(function (domainObject) {
- actions.forEach((action) => {
- self.actions.push({
- cssClass: action.cssClass,
- name: action.name,
- perform: () => {
- action.invoke([domainObject].concat(openmct.router.path));
- }
- });
- });
- });
- };
- };
-
- EmbedController.prototype.removeEmbedAction = function () {
- var self = this;
-
- return {
- name: 'Remove Embed',
- cssClass: 'icon-trash',
- perform: function (embed, entry) {
- var entryPosition = self.findInArray(self.domainObject.entries, entry.id),
- embedPosition = self.findInArray(entry.embeds, embed.id);
-
- var dialog = self.openmct.overlays.dialog({
- iconClass: "alert",
- message: 'This Action will permanently delete this embed. Do you wish to continue?',
- buttons: [{
- label: "No",
- callback: function () {
- dialog.dismiss();
- }
- },
- {
- label: "Yes",
- emphasis: true,
- callback: function () {
- entry.embeds.splice(embedPosition, 1);
- var dirString = 'entries[' + entryPosition + '].embeds';
-
- self.openmct.objects.mutate(self.domainObject, dirString, entry.embeds);
- dialog.dismiss();
- }
- }]
- });
- }
- };
- };
-
- EmbedController.prototype.toggleActionMenu = function (event) {
- event.preventDefault();
-
- var body = $(document.body),
- container = $(event.target.parentElement.parentElement),
- initiatingEvent = this.agentService.isMobile() ?
- 'touchstart' : 'mousedown',
- menu = container.find('.menu-element'),
- dismissExistingMenu;
-
- // Remove the context menu
- function dismiss() {
- container.find('.hide-menu').append(menu);
- body.off(initiatingEvent, menuClickHandler);
- dismissExistingMenu = undefined;
- }
-
- function menuClickHandler(e) {
- window.setTimeout(() => {
- dismiss();
- }, 100);
- }
-
- // Dismiss any menu which was already showing
- if (dismissExistingMenu) {
- dismissExistingMenu();
- }
-
- // ...and record the presence of this menu.
- dismissExistingMenu = dismiss;
-
- this.popupService.display(menu, [event.pageX,event.pageY], {
- marginX: 0,
- marginY: -50
- });
-
- body.on(initiatingEvent, menuClickHandler);
- };
-
- EmbedController.prototype.exposedData = function () {
- return {
- actions: [this.removeEmbedAction()],
- showActionMenu: false
- };
- };
-
- EmbedController.prototype.exposedMethods = function () {
- var self = this;
-
- return {
- openSnapshot: self.openSnapshot,
- formatTime: self.formatTime,
- toggleActionMenu: self.toggleActionMenu,
- findInArray: self.findInArray
- };
- };
-
- return EmbedController;
-});
diff --git a/src/plugins/notebook/src/controllers/EntryController.js b/src/plugins/notebook/src/controllers/EntryController.js
deleted file mode 100644
index a64253c9f8..0000000000
--- a/src/plugins/notebook/src/controllers/EntryController.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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([
- 'moment'
-],
-function (
- Moment
-) {
-
- function EntryController(openmct, domainObject) {
- this.openmct = openmct;
- this.domainObject = domainObject;
-
- this.currentEntryValue = '';
-
- this.exposedMethods = this.exposedMethods.bind(this);
- this.exposedData = this.exposedData.bind(this);
- }
-
- EntryController.prototype.entryPosById = function (entryId) {
- var foundId = -1;
-
- this.domainObject.entries.forEach(function (element, index) {
- if (element.id === entryId) {
- foundId = index;
- return;
- }
- });
-
- return foundId;
- };
-
- EntryController.prototype.textFocus = function ($event) {
- if ($event.target) {
- this.currentEntryValue = $event.target.innerText;
- } else {
- $event.target.innerText = '';
- }
- };
-
- EntryController.prototype.textBlur = function ($event, entryId) {
- if ($event.target) {
- var entryPos = this.entryPosById(entryId);
-
- if (this.currentEntryValue !== $event.target.innerText) {
- this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].text', $event.target.innerText);
- }
- }
- };
-
- EntryController.prototype.formatTime = function (unixTime, timeFormat) {
- return Moment(unixTime).format(timeFormat);
- };
-
- EntryController.prototype.deleteEntry = function () {
- var entryPos = this.entryPosById(this.entry.id),
- domainObject = this.domainObject,
- openmct = this.openmct;
-
- if (entryPos !== -1) {
-
- var 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: function () {
- domainObject.entries.splice(entryPos, 1);
- openmct.objects.mutate(domainObject, 'entries', domainObject.entries);
- dialog.dismiss();
- }
- },
- {
- label: "Cancel",
- callback: function () {
- dialog.dismiss();
- }
- }
- ]
- });
- }
- };
-
- EntryController.prototype.dropOnEntry = function (entryid, event) {
- var data = event.dataTransfer.getData('openmct/domain-object-path');
-
- if (data) {
- var objectPath = JSON.parse(data),
- domainObject = objectPath[0],
- domainObjectKey = domainObject.identifier.key,
- domainObjectType = this.openmct.types.get(domainObject.type),
- cssClass = domainObjectType && domainObjectType.definition ?
- domainObjectType.definition.cssClass : 'icon-object-unknown',
- entryPos = this.entryPosById(entryid),
- currentEntryEmbeds = this.domainObject.entries[entryPos].embeds,
- newEmbed = {
- id: '' + Date.now(),
- domainObject: domainObject,
- objectPath: objectPath,
- type: domainObjectKey,
- cssClass: cssClass,
- name: domainObject.name,
- snapshot: ''
- };
- currentEntryEmbeds.push(newEmbed);
- this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].embeds', currentEntryEmbeds);
- }
- };
-
- EntryController.prototype.exposedData = function () {
- return {
- openmct: this.openmct,
- domainObject: this.domainObject,
- currentEntryValue: this.currentEntryValue
- };
- };
-
- EntryController.prototype.exposedMethods = function () {
- return {
- entryPosById: this.entryPosById,
- textFocus: this.textFocus,
- textBlur: this.textBlur,
- formatTime: this.formatTime,
- deleteEntry: this.deleteEntry,
- dropOnEntry: this.dropOnEntry
- };
- };
- return EntryController;
-});
diff --git a/src/plugins/notebook/src/controllers/NotebookController.js b/src/plugins/notebook/src/controllers/NotebookController.js
deleted file mode 100644
index b63dfae6a2..0000000000
--- a/src/plugins/notebook/src/controllers/NotebookController.js
+++ /dev/null
@@ -1,237 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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([
- 'vue',
- './EntryController',
- './EmbedController',
- '../../res/templates/notebook.html',
- '../../res/templates/entry.html',
- '../../res/templates/embed.html',
- '../../../../ui/components/search.vue',
- '../../../../ui/preview/PreviewAction',
- '../../../../ui/mixins/object-link'
-],
-function (
- Vue,
- EntryController,
- EmbedController,
- NotebookTemplate,
- EntryTemplate,
- EmbedTemplate,
- search,
- PreviewAction,
- objectLinkMixin
-) {
-
- function NotebookController(openmct, domainObject) {
- this.openmct = openmct;
- this.domainObject = domainObject;
- this.entrySearch = '';
- this.previewAction = new PreviewAction.default(openmct);
-
- this.show = this.show.bind(this);
- this.destroy = this.destroy.bind(this);
- this.newEntry = this.newEntry.bind(this);
- this.entryPosById = this.entryPosById.bind(this);
- }
-
- NotebookController.prototype.initializeVue = function (container) {
- var self = this,
- entryController = new EntryController(this.openmct, this.domainObject),
- embedController = new EmbedController(this.openmct, this.domainObject);
-
- this.container = container;
-
- var notebookEmbed = {
- inject:['openmct', 'domainObject'],
- mixins:[objectLinkMixin.default],
- props:['embed', 'entry'],
- template: EmbedTemplate,
- data: embedController.exposedData,
- methods: embedController.exposedMethods(),
- beforeMount: embedController.populateActionMenu(self.openmct, [self.previewAction])
- };
-
- var entryComponent = {
- props:['entry'],
- template: EntryTemplate,
- components: {
- 'notebook-embed': notebookEmbed
- },
- data: entryController.exposedData,
- methods: entryController.exposedMethods(),
- mounted: self.focusOnEntry
- };
-
- var NotebookVue = Vue.extend({
- provide: {openmct: self.openmct, domainObject: self.domainObject},
- components: {
- 'notebook-entry': entryComponent,
- 'search': search.default
- },
- data: function () {
- return {
- entrySearch: self.entrySearch,
- showTime: '0',
- sortEntries: self.domainObject.defaultSort,
- entries: self.domainObject.entries,
- currentEntryValue: ''
- };
- },
- computed: {
- filteredAndSortedEntries() {
- return this.sort(this.filterBySearch(this.entries, this.entrySearch), this.sortEntries);
- }
- },
- methods: {
- search(value) {
- this.entrySearch = value;
- },
- newEntry: self.newEntry,
- filterBySearch: self.filterBySearch,
- sort: self.sort
- },
- template: NotebookTemplate
- });
-
- this.NotebookVue = new NotebookVue();
- container.appendChild(this.NotebookVue.$mount().$el);
- };
-
- NotebookController.prototype.newEntry = function (event) {
- this.NotebookVue.search('');
-
- var date = Date.now(),
- embed;
-
- if (event.dataTransfer && event.dataTransfer.getData('openmct/domain-object-path')) {
- var selectedObject = JSON.parse(event.dataTransfer.getData('openmct/domain-object-path'))[0],
- selectedObjectId = selectedObject.identifier.key,
- cssClass = this.openmct.types.get(selectedObject.type);
-
- embed = {
- type: selectedObjectId,
- id: '' + date,
- cssClass: cssClass,
- name: selectedObject.name,
- snapshot: ''
- };
- }
-
- var entries = this.domainObject.entries,
- lastEntryIndex = this.NotebookVue.sortEntries === 'newest' ? 0 : entries.length - 1,
- lastEntry = entries[lastEntryIndex];
-
- if (lastEntry === undefined || lastEntry.text || lastEntry.embeds.length) {
- var createdEntry = {'id': 'entry-' + date, 'createdOn': date, 'embeds':[]};
-
- if (embed) {
- createdEntry.embeds.push(embed);
- }
-
- entries.push(createdEntry);
- this.openmct.objects.mutate(this.domainObject, 'entries', entries);
- } else {
- lastEntry.createdOn = date;
-
- if(embed) {
- lastEntry.embeds.push(embed);
- }
-
- this.openmct.objects.mutate(this.domainObject, 'entries[entries.length-1]', lastEntry);
- this.focusOnEntry.bind(this.NotebookVue.$children[lastEntryIndex+1])();
- }
- };
-
- NotebookController.prototype.entryPosById = function (entryId) {
- var foundId = -1;
-
- this.domainObject.entries.forEach(function (element, index) {
- if (element.id === entryId) {
- foundId = index;
- return;
- }
- });
-
- return foundId;
- };
-
- NotebookController.prototype.focusOnEntry = function () {
- if (!this.entry.text) {
- this.$refs.contenteditable.focus();
- }
- };
-
- NotebookController.prototype.filterBySearch = function (entryArray, filterString) {
- if (filterString) {
- var lowerCaseFilterString = filterString.toLowerCase();
-
- return entryArray.filter(function (entry) {
- if (entry.text) {
- return entry.text.toLowerCase().includes(lowerCaseFilterString);
- } else {
- return false;
- }
- });
- } else {
- return entryArray;
- }
- };
-
- NotebookController.prototype.sort = function (array, sortDirection) {
- let oldest = (a,b) => {
- if (a.createdOn < b.createdOn) {
- return -1;
- } else if (a.createdOn > b.createdOn) {
- return 1;
- } else {
- return 0;
- }
- },
- newest = (a,b) => {
- if (a.createdOn < b.createdOn) {
- return 1;
- } else if (a.createdOn > b.createdOn) {
- return -1;
- } else {
- return 0;
- }
- };
-
- if (sortDirection === 'newest') {
- return array.sort(newest);
- } else {
- return array.sort(oldest);
- }
- };
-
- NotebookController.prototype.show = function (container) {
- this.initializeVue(container);
- };
-
- NotebookController.prototype.destroy = function (container) {
- this.NotebookVue.$destroy(true);
- };
-
- return NotebookController;
-});
diff --git a/src/plugins/notebook/utils/notebook-entries.js b/src/plugins/notebook/utils/notebook-entries.js
new file mode 100644
index 0000000000..6b026e33ea
--- /dev/null
+++ b/src/plugins/notebook/utils/notebook-entries.js
@@ -0,0 +1,195 @@
+import objectLink from '../../../ui/mixins/object-link';
+
+const TIME_BOUNDS = {
+ START_BOUND: 'tc.startBound',
+ END_BOUND: 'tc.endBound',
+ START_DELTA: 'tc.startDelta',
+ END_DELTA: 'tc.endDelta'
+}
+
+export const getHistoricLinkInFixedMode = (openmct, bounds, historicLink) => {
+ if (historicLink.includes('tc.mode=fixed')) {
+ return historicLink;
+ }
+
+ openmct.time.getAllClocks().forEach(clock => {
+ if (historicLink.includes(`tc.mode=${clock.key}`)) {
+ historicLink.replace(`tc.mode=${clock.key}`, 'tc.mode=fixed');
+
+ return;
+ }
+ });
+
+ const params = historicLink.split('&').map(param => {
+ if (param.includes(TIME_BOUNDS.START_BOUND)
+ || param.includes(TIME_BOUNDS.START_DELTA)) {
+ param = `${TIME_BOUNDS.START_BOUND}=${bounds.start}`;
+ }
+
+ if (param.includes(TIME_BOUNDS.END_BOUND)
+ || param.includes(TIME_BOUNDS.END_DELTA)) {
+ param = `${TIME_BOUNDS.END_BOUND}=${bounds.end}`;
+ }
+
+ return param;
+ });
+
+ return params.join('&');
+}
+
+export const getNotebookDefaultEntries = (notebookStorage, domainObject) => {
+ if (!notebookStorage || !domainObject) {
+ return null;
+ }
+
+ const defaultSection = notebookStorage.section;
+ const defaultPage = notebookStorage.page;
+ if (!defaultSection || !defaultPage) {
+ return null;
+ }
+
+ const configuration = domainObject.configuration;
+ const entries = configuration.entries || {};
+
+ let section = entries[defaultSection.id];
+ if (!section) {
+ section = {};
+ entries[defaultSection.id] = section;
+ }
+
+ let page = entries[defaultSection.id][defaultPage.id];
+ if (!page) {
+ page = [];
+ entries[defaultSection.id][defaultPage.id] = [];
+ }
+
+ return entries[defaultSection.id][defaultPage.id];
+}
+
+export const createNewEmbed = (snapshotMeta, snapshot = '') => {
+ const {
+ bounds,
+ link,
+ objectPath,
+ openmct
+ } = snapshotMeta;
+ const domainObject = objectPath[0];
+ const domainObjectType = openmct.types.get(domainObject.type);
+
+ const cssClass = domainObjectType && domainObjectType.definition
+ ? domainObjectType.definition.cssClass
+ : 'icon-object-unknown';
+ const date = Date.now();
+ const historicLink = link
+ ? getHistoricLinkInFixedMode(openmct, bounds, link)
+ : objectLink.computed.objectLink.call({ objectPath, openmct });
+ const name = domainObject.name;
+ const type = domainObject.identifier.key;
+
+ return {
+ bounds,
+ createdOn: date,
+ cssClass,
+ domainObject,
+ historicLink,
+ id: 'embed-' + date,
+ name,
+ snapshot,
+ type,
+ objectPath: JSON.stringify(objectPath)
+ };
+}
+
+export const addNotebookEntry = (openmct, domainObject, notebookStorage, embed = null) => {
+ if (!openmct || !domainObject || !notebookStorage) {
+ return;
+ }
+
+ const date = Date.now();
+ const configuration = domainObject.configuration;
+ const entries = configuration.entries || {};
+
+ if (!entries) {
+ return;
+ }
+
+ const embeds = embed
+ ? [embed]
+ : [];
+
+ const defaultEntries = getNotebookDefaultEntries(notebookStorage, domainObject);
+ const id = `entry-${date}`;
+ defaultEntries.push({
+ id,
+ createdOn: date,
+ text: '',
+ embeds
+ });
+
+ openmct.objects.mutate(domainObject, 'configuration.entries', entries);
+
+ return id;
+}
+
+export const getNotebookEntries = (domainObject, selectedSection, selectedPage) => {
+ if (!domainObject || !selectedSection || !selectedPage) {
+ return null;
+ }
+
+ const configuration = domainObject.configuration;
+ const entries = configuration.entries || {};
+
+ let section = entries[selectedSection.id];
+ if (!section) {
+ return null;
+ }
+
+ let page = entries[selectedSection.id][selectedPage.id];
+ if (!page) {
+ return null;
+ }
+
+ return entries[selectedSection.id][selectedPage.id];
+}
+
+export const getEntryPosById = (entryId, domainObject, selectedSection, selectedPage) => {
+ if (!domainObject || !selectedSection || !selectedPage) {
+ return;
+ }
+
+ const entries = getNotebookEntries(domainObject, selectedSection, selectedPage);
+ let foundId = -1;
+ entries.forEach((element, index) => {
+ if (element.id === entryId) {
+ foundId = index;
+
+ return;
+ }
+ });
+
+ return foundId;
+}
+
+export const deleteNotebookEntries = (openmct, domainObject, selectedSection, selectedPage) => {
+ if (!domainObject || !selectedSection) {
+ return;
+ }
+
+ const configuration = domainObject.configuration;
+ const entries = configuration.entries || {};
+
+ // Delete entire section
+ if (!selectedPage) {
+ delete entries[selectedSection.id];
+
+ return;
+ }
+
+ let section = entries[selectedSection.id];
+ if (!section) {
+ return;
+ }
+
+ delete entries[selectedSection.id][selectedPage.id];
+ openmct.objects.mutate(domainObject, 'configuration.entries', entries);
+}
diff --git a/src/plugins/notebook/utils/notebook-storage.js b/src/plugins/notebook/utils/notebook-storage.js
new file mode 100644
index 0000000000..37a71317ad
--- /dev/null
+++ b/src/plugins/notebook/utils/notebook-storage.js
@@ -0,0 +1,40 @@
+const NOTEBOOK_LOCAL_STORAGE = 'notebook-storage';
+
+export function clearDefaultNotebook() {
+ window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, null);
+}
+
+export function getDefaultNotebook() {
+ const notebookStorage = window.localStorage.getItem(NOTEBOOK_LOCAL_STORAGE);
+
+ return JSON.parse(notebookStorage);
+}
+
+export function setDefaultNotebook(domainObject, section, page) {
+ const notebookMeta = {
+ name: domainObject.name,
+ identifier: domainObject.identifier
+ };
+
+ const notebookStorage = {
+ notebookMeta,
+ section,
+ page
+ }
+
+ window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, JSON.stringify(notebookStorage));
+}
+
+export function setDefaultNotebookSection(section) {
+ const notebookStorage = getDefaultNotebook();
+
+ notebookStorage.section = section;
+ window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, JSON.stringify(notebookStorage));
+
+}
+
+export function setDefaultNotebookPage(page) {
+ const notebookStorage = getDefaultNotebook();
+ notebookStorage.page = page;
+ window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, JSON.stringify(notebookStorage));
+}
diff --git a/src/plugins/notebook/utils/popup-menu.js b/src/plugins/notebook/utils/popup-menu.js
new file mode 100644
index 0000000000..3830513936
--- /dev/null
+++ b/src/plugins/notebook/utils/popup-menu.js
@@ -0,0 +1,45 @@
+import $ from 'zepto';
+
+export const togglePopupMenu = (event, openmct) => {
+ event.preventDefault();
+
+ const body = $(document.body);
+ const container = $(event.target.parentElement.parentElement);
+ const classList = document.querySelector('body').classList;
+ const isPhone = Array.from(classList).includes('phone');
+ const isTablet = Array.from(classList).includes('tablet');
+
+ const initiatingEvent = isPhone || isTablet
+ ? 'touchstart'
+ : 'mousedown';
+ const menu = container.find('.menu-element');
+ let dismissExistingMenu;
+
+ function dismiss() {
+ container.find('.hide-menu').append(menu);
+ body.off(initiatingEvent, menuClickHandler);
+ dismissExistingMenu = undefined;
+ }
+
+ function menuClickHandler(e) {
+ window.setTimeout(() => {
+ dismiss();
+ }, 100);
+ }
+
+ // Dismiss any menu which was already showing
+ if (dismissExistingMenu) {
+ dismissExistingMenu();
+ }
+
+ // ...and record the presence of this menu.
+ dismissExistingMenu = dismiss;
+
+ const popupService = openmct.$injector.get('popupService');
+ popupService.display(menu, [event.pageX,event.pageY], {
+ marginX: 0,
+ marginY: -50
+ });
+
+ body.on(initiatingEvent, menuClickHandler);
+}
diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js
index cfba102cc3..0918d437d9 100644
--- a/src/plugins/plugins.js
+++ b/src/plugins/plugins.js
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2019, United States Government
+ * Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -175,7 +175,7 @@ define([
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin;
- plugins.Notebook = Notebook;
+ plugins.Notebook = Notebook.default;
plugins.DisplayLayout = DisplayLayoutPlugin.default;
plugins.FolderView = FolderView;
plugins.Tabs = Tabs;
diff --git a/src/plugins/remove/RemoveAction.js b/src/plugins/remove/RemoveAction.js
index 9db2ff4c3f..57f265d486 100644
--- a/src/plugins/remove/RemoveAction.js
+++ b/src/plugins/remove/RemoveAction.js
@@ -19,7 +19,6 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
-
export default class RemoveAction {
constructor(openmct) {
this.name = 'Remove';
@@ -90,6 +89,13 @@ export default class RemoveAction {
if (this.inNavigationPath(child) && this.openmct.editor.isEditing()) {
this.openmct.editor.save();
}
+
+ const parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
+ const isAlias = parentKeyString !== child.location;
+
+ if (!isAlias) {
+ this.openmct.objects.mutate(child, 'location', null);
+ }
}
appliesTo(objectPath) {
diff --git a/src/plugins/telemetryTable/TelemetryTableColumn.js b/src/plugins/telemetryTable/TelemetryTableColumn.js
index c517bb1336..4870f71ab5 100644
--- a/src/plugins/telemetryTable/TelemetryTableColumn.js
+++ b/src/plugins/telemetryTable/TelemetryTableColumn.js
@@ -56,6 +56,10 @@ define(function () {
return formattedValue;
}
}
+
+ getParsedValue(telemetryDatum) {
+ return this.formatter.parse(telemetryDatum);
+ }
}
return TelemetryTableColumn;
diff --git a/src/plugins/telemetryTable/TelemetryTableRow.js b/src/plugins/telemetryTable/TelemetryTableRow.js
index 33f2195e8d..56d23a9486 100644
--- a/src/plugins/telemetryTable/TelemetryTableRow.js
+++ b/src/plugins/telemetryTable/TelemetryTableRow.js
@@ -42,6 +42,11 @@ define([], function () {
return column && column.getFormattedValue(this.datum[key]);
}
+ getParsedValue(key) {
+ let column = this.columns[key];
+ return column && column.getParsedValue(this.datum[key]);
+ }
+
getCellComponentName(key) {
let column = this.columns[key];
return column &&
diff --git a/src/plugins/telemetryTable/collections/SortedTableRowCollection.js b/src/plugins/telemetryTable/collections/SortedTableRowCollection.js
index cded96cf09..043ac29032 100644
--- a/src/plugins/telemetryTable/collections/SortedTableRowCollection.js
+++ b/src/plugins/telemetryTable/collections/SortedTableRowCollection.js
@@ -201,7 +201,7 @@ define(
sortBy(sortOptions) {
if (arguments.length > 0) {
this.sortOptions = sortOptions;
- this.rows = _.sortByOrder(this.rows, 'datum.' + sortOptions.key, sortOptions.direction);
+ this.rows = _.sortByOrder(this.rows, (row) => row.getParsedValue(sortOptions.key) , sortOptions.direction);
this.emit('sort');
}
// Return duplicate to avoid direct modification of underlying object
@@ -222,7 +222,7 @@ define(
}
getValueForSortColumn(row) {
- return row.datum[this.sortOptions.key];
+ return row.getParsedValue(this.sortOptions.key);
}
remove(removedRows) {
diff --git a/src/styles/_constants-espresso.scss b/src/styles/_constants-espresso.scss
index e08668720c..1ea130b891 100644
--- a/src/styles/_constants-espresso.scss
+++ b/src/styles/_constants-espresso.scss
@@ -87,6 +87,11 @@ $colorSelectedFg: pullForward($colorBodyFg, 20%);
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px;
+$drawerBg: lighten($colorBodyBg, 5%);
+$drawerFg: lighten($colorBodyFg, 5%);
+$sideBarBg: $drawerBg;
+$sideBarHeaderBg: rgba($colorBodyFg, 0.2);
+$sideBarHeaderFg: rgba($colorBodyFg, 0.7);
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #999;
@@ -333,7 +338,7 @@ $colorSummaryFg: rgba($colorBodyFg, 0.7);
$colorSummaryFgEm: $colorBodyFg;
// Plot
-$colorPlotBg: rgba(black, 0.05);
+$colorPlotBg: rgba(black, 0.1);
$colorPlotFg: $colorBodyFg;
$colorPlotHash: black;
$opacityPlotHash: 0.2;
diff --git a/src/styles/_constants-maelstrom.scss b/src/styles/_constants-maelstrom.scss
index 8a5973dc9f..81a9cf3eae 100644
--- a/src/styles/_constants-maelstrom.scss
+++ b/src/styles/_constants-maelstrom.scss
@@ -91,6 +91,11 @@ $colorSelectedFg: pullForward($colorBodyFg, 20%);
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px;
+$drawerBg: lighten($colorBodyBg, 5%);
+$drawerFg: lighten($colorBodyFg, 5%);
+$sideBarBg: $drawerBg;
+$sideBarHeaderBg: rgba($colorBodyFg, 0.2);
+$sideBarHeaderFg: rgba($colorBodyFg, 0.7);
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #999;
diff --git a/src/styles/_constants-mobile.scss b/src/styles/_constants-mobile.scss
index 8016fa1d63..c58d37a5c4 100644
--- a/src/styles/_constants-mobile.scss
+++ b/src/styles/_constants-mobile.scss
@@ -26,7 +26,7 @@
$mobileListIconSize: 30px;
$mobileTitleDescH: 35px;
$mobileOverlayMargin: 20px;
-$mobileMenuIconD: 34px;
+$mobileMenuIconD: 25px;
$phoneItemH: floor($gridItemMobile / 4);
$tabletItemH: floor($gridItemMobile / 3);
diff --git a/src/styles/_constants-snow.scss b/src/styles/_constants-snow.scss
index a7961f8a72..dcc4998f1a 100644
--- a/src/styles/_constants-snow.scss
+++ b/src/styles/_constants-snow.scss
@@ -87,6 +87,11 @@ $colorSelectedFg: pullForward($colorBodyFg, 10%);
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px;
+$drawerBg: darken($colorBodyBg, 5%);
+$drawerFg: darken($colorBodyFg, 5%);
+$sideBarBg: $drawerBg;
+$sideBarHeaderBg: rgba(black, 0.25);
+$sideBarHeaderFg: rgba($colorBodyFg, 0.7);
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #999;
diff --git a/src/styles/_constants.scss b/src/styles/_constants.scss
index 94d175ae09..64619d92ee 100755
--- a/src/styles/_constants.scss
+++ b/src/styles/_constants.scss
@@ -147,6 +147,7 @@ $glyph-icon-suitcase: '\e928';
$glyph-icon-cursor-lock: '\e929';
$glyph-icon-flag: '\e92a';
$glyph-icon-eye-disabled: '\e92b';
+$glyph-icon-notebook-page: '\e92c';
$glyph-icon-arrows-right-left: '\ea00';
$glyph-icon-arrows-up-down: '\ea01';
$glyph-icon-bullet: '\ea02';
diff --git a/src/styles/_controls.scss b/src/styles/_controls.scss
index f6ea5b475b..d7bdaadc13 100644
--- a/src/styles/_controls.scss
+++ b/src/styles/_controls.scss
@@ -54,7 +54,6 @@ button {
background: $splitterBtnColorBg;
color: $splitterBtnColorFg;
border-radius: $smallCr;
- font-size: 6px;
line-height: 90%;
padding: 3px 10px;
@@ -63,6 +62,10 @@ button {
color: $colorBtnFgHov;
}
+ @include desktop() {
+ font-size: 6px;
+ }
+
&:before {
content: $glyph-icon-arrow-down;
font-size: 1.1em;
@@ -158,6 +161,26 @@ button {
}
}
+.c-list-button {
+ @include cControl();
+ color: $colorBodyFg;
+ cursor: pointer;
+ justify-content: start;
+ padding: $interiorMargin;
+
+ > * + * {
+ margin-left: $interiorMargin;
+ }
+
+ @include hover() {
+ background: $colorItemTreeHoverBg;
+ }
+
+ .c-button {
+ flex: 0 0 auto;
+ }
+}
+
/******************************************************** DISCLOSURE CONTROLS */
/********* Disclosure Button */
// Provides a downward arrow icon that when clicked displays additional options and/or info.
@@ -232,13 +255,13 @@ section {
}
&.is-expanded {
- flex: 1 1 100%;
- max-height: max-content;
+ flex: 0 1 auto;
}
.c-section__header {
@include propertiesHeader();
display: flex;
+ flex: 0 0 auto;
align-items: center;
margin-bottom: $interiorMargin;
@@ -709,7 +732,7 @@ select {
$pTB: 2px;
padding: $pTB $pLR;
- &:hover {
+ @include hover() {
background: $editUIBaseColorHov !important;
color: $editUIBaseColorFg !important;
}
@@ -724,7 +747,7 @@ select {
color: $colorBtnCautionBg;
}
- &:hover {
+ @include hover() {
background: rgba($colorBtnCautionBgHov, 0.2);
:before {
color: $colorBtnCautionBgHov;
diff --git a/src/styles/_glyphs.scss b/src/styles/_glyphs.scss
index 3c3881db7a..b6d5899d3b 100755
--- a/src/styles/_glyphs.scss
+++ b/src/styles/_glyphs.scss
@@ -83,6 +83,7 @@
.icon-cursor-lock { @include glyphBefore($glyph-icon-cursor-lock); }
.icon-flag { @include glyphBefore($glyph-icon-flag); }
.icon-eye-disabled { @include glyphBefore($glyph-icon-eye-disabled); }
+.icon-notebook-page { @include glyphBefore($glyph-icon-notebook-page); }
.icon-arrows-right-left { @include glyphBefore($glyph-icon-arrows-right-left); }
.icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); }
.icon-bullet { @include glyphBefore($glyph-icon-bullet); }
diff --git a/src/styles/_layout.scss b/src/styles/_layout.scss
index 57ba12d4a1..e57627a00d 100644
--- a/src/styles/_layout.scss
+++ b/src/styles/_layout.scss
@@ -80,4 +80,8 @@
&__object-name {
flex: 0 1 auto;
}
+
+ &__object-details {
+ opacity: 0.5;
+ }
}
diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss
index 8f82c03cf7..6e1d7313dd 100644
--- a/src/styles/_mixins.scss
+++ b/src/styles/_mixins.scss
@@ -51,7 +51,7 @@
/************************** EFFECTS */
@mixin pulse($animName: pulse, $dur: 500ms, $iteration: infinite, $opacity0: 0.5, $opacity100: 1) {
- @keyframes pulse {
+ @keyframes #{$animName} {
0% { opacity: $opacity0; }
100% { opacity: $opacity100; }
}
@@ -62,6 +62,18 @@
animation-timing-function: ease-in-out;
}
+@mixin pulseProp($animName: pulseProp, $dur: 500ms, $iter: 5, $prop: opacity, $valStart: 0, $valEnd: 1) {
+ @keyframes #{$animName} {
+ 0% { #{$prop}: $valStart; }
+ 100% { #{$prop}: $valEnd; }
+ }
+ animation-name: $animName;
+ animation-duration: $dur;
+ animation-direction: alternate;
+ animation-iteration-count: $iter;
+ animation-timing-function: ease-in-out;
+}
+
/************************** VISUALS */
@mixin ancillaryIcon($d, $c) {
// Used for small icons used in combination with larger icons,
@@ -426,10 +438,15 @@
color: $colorBodyFg;
cursor: pointer;
padding: 4px; // Bigger hit area
- opacity: 0.6;
+ opacity: 0.7;
transition: $transOut;
transform-origin: center;
+ &[class*="--major"] {
+ color: $colorBtnMajorBg !important;
+ opacity: 0.8;
+ }
+
@include hover() {
transform: scale(1.1);
transition: $transIn;
diff --git a/src/styles/fonts/Open-MCT-Symbols-16px.ttf b/src/styles/fonts/Open-MCT-Symbols-16px.ttf
index 3bc82c5187..8aa05cbcb9 100644
Binary files a/src/styles/fonts/Open-MCT-Symbols-16px.ttf and b/src/styles/fonts/Open-MCT-Symbols-16px.ttf differ
diff --git a/src/styles/fonts/Open-MCT-Symbols-16px.woff b/src/styles/fonts/Open-MCT-Symbols-16px.woff
index 2e1865224e..16930f2c78 100644
Binary files a/src/styles/fonts/Open-MCT-Symbols-16px.woff and b/src/styles/fonts/Open-MCT-Symbols-16px.woff differ
diff --git a/src/styles/notebook.scss b/src/styles/notebook.scss
index fcdb72e0ef..d6bd6af9e4 100644
--- a/src/styles/notebook.scss
+++ b/src/styles/notebook.scss
@@ -22,34 +22,61 @@
/*********************************************** NOTEBOOK */
.c-notebook {
+ $headerFontSize: 1.3em;
display: flex;
flex-direction: column;
overflow: hidden;
- position: absolute;
- top: 0px;
- right: 0px;
- bottom: 0px;
- left: 0px;
+ height: 100%;
- &-snapshot {
- flex: 1 1 auto;
+ /****************************** CONTENT */
+ &__body {
+ // Holds __nav and __page-view
display: flex;
- flex-direction: column;
+ flex: 1 1 auto;
+ overflow: hidden;
+ }
- > * + * {
- margin-top: $interiorMargin;
- }
-
- &__header {
- flex: 0 0 auto;
- }
-
- &__image {
- flex: 1 1 auto;
+ &__nav {
+ flex: 0 0 auto;
+ * {
+ overflow: hidden;
}
}
- > [class*="__"] + [class*="__"] {
+ .c-sidebar {
+ background: $sideBarBg;
+ .c-sidebar__pane {
+ flex-basis: 50%;
+ }
+ }
+
+ body.mobile & {
+ .c-list-button,
+ &-snapshot-menubutton {
+ display: none;
+ }
+ }
+
+ /****************************** CONTENT */
+ &__contents {
+ width: 70%;
+ }
+
+ &__page-view {
+ // Holds __header, __drag-area and __entries
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: column;
+ width: 100%;
+ > * {
+ flex: 0 0 auto;
+ + * {
+ margin-top: $interiorMargin;
+ }
+ }
+ }
+
+ > * + * {
margin-top: $interiorMargin;
}
@@ -111,17 +138,93 @@
}
}
+ /***** PAGE VIEW */
+ &__page-view {
+ &__header {
+ display: flex;
+ flex-wrap: wrap; // Allows wrapping in mobile portrait and narrow placements
+ line-height: 220%;
+ > * {
+ flex: 0 0 auto;
+ }
+ }
+
+ &__path {
+ flex: 1 1 auto;
+ margin: 0 $interiorMargin;
+ overflow: hidden;
+ white-space: nowrap;
+ font-size: $headerFontSize;
+ > * {
+ // Section
+ flex: 0 0 auto;
+ + * {
+ // Page
+ display: inline;
+ flex: 1 1 auto;
+ @include ellipsize();
+ }
+ }
+ }
+ }
+
&__entries {
flex-direction: column;
flex: 1 1 auto;
- padding-right: $interiorMarginSm;
overflow-x: hidden;
overflow-y: scroll;
+ @include desktop() {
+ padding-right: $interiorMarginSm; // Scrollbar kickoff
+ }
+
[class*="__entry"] + [class*="__entry"] {
margin-top: $interiorMarginSm;
}
+ }
+ /***** SEARCH RESULTS */
+ &__search-results {
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: column;
+
+ > * + * {
+ margin-top: 5px;
+ }
+
+ &__header {
+ font-size: $headerFontSize;
+ flex: 0 0 auto;
+ }
+
+ .c-notebook__entries {
+ flex: 1 1 auto;
+ }
+
+ .c-ne {
+ flex-direction: column;
+
+ > * + * {
+ margin-top: $interiorMargin;
+ }
+ }
+ }
+}
+
+.is-notebook-default {
+ &:after {
+ color: $colorFilter;
+ content: $glyph-icon-notebook-page;
+ display: block;
+ font-family: symbolsfont;
+ font-size: 0.9em;
+ margin-left: $interiorMargin;
+ }
+
+ &.c-list__item:after {
+ flex: 1 0 auto;
+ text-align: right;
}
}
@@ -183,10 +286,29 @@
}
&__embeds {
- flex-wrap: wrap;
+ //flex-wrap: wrap;
> [class*="__embed"] {
- margin: 0 $interiorMarginSm $interiorMarginSm 0;
+ //margin: 0 $interiorMarginSm $interiorMarginSm 0;
+ }
+ }
+
+ &__section-and-page {
+ // Shown when c-ne within search results
+ background: rgba($colorBodyFg, 0.1); //$colorInteriorBorder;
+ border-radius: $controlCr;
+ display: inline-flex;
+ align-items: center;
+ align-self: flex-start;
+ padding: $interiorMargin;
+
+ > * + * {
+ margin-left: $interiorMargin;
+ }
+
+ [class*='icon'] {
+ font-size: 0.8em;
+ opacity: 0.7;
}
}
}
@@ -194,7 +316,7 @@
/****************************** EMBEDS */
@mixin snapThumb() {
// LEGACY: TODO: refactor when .snap-thumb in New Entry dialog is refactored
- $d: 50px;
+ $d: 30px;
border: 1px solid $colorInteriorBorder;
cursor: pointer;
width: $d;
@@ -269,6 +391,64 @@
.l-sticky-headers .l-tabular-body { overflow: auto; }
}
+.c-notebook-snapshot {
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
+
+ > * + * {
+ margin-top: $interiorMargin;
+ }
+
+ &__header {
+ flex: 0 0 auto;
+ }
+
+ &__image {
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center center;
+ flex: 1 1 auto;
+ }
+}
+
+/****************************** SNAPSHOT CONTAINER */
+.c-snapshots-h {
+ // Is hidden when the parent div l-shell__drawer is collapsed, so no worries about padding, etc.
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ padding: $interiorMarginLg;
+
+ > * {
+ flex: 1 1 auto;
+ &:first-child {
+ flex: 0 0 auto;
+ }
+ }
+
+ > * + * {
+ margin-top: $interiorMargin;
+ }
+}
+
+.c-snapshots {
+ flex-wrap: wrap;
+ overflow: auto;
+
+ .c-snapshot {
+ margin: 0 $interiorMarginSm $interiorMarginSm 0;
+ }
+
+ .hint {
+ font-size: 1.25em;
+ font-style: italic;
+ opacity: 0.7;
+ padding: $interiorMarginLg;
+ text-align: center;
+ }
+}
+
/****************************** PAINTERRO OVERRIDES */
.annotation-dialog .abs.editor {
border-radius: 0;
@@ -401,7 +581,8 @@ body.mobile {
&.phone.portrait {
.c-notebook__head,
- .c-notebook__entry {
+ .c-notebook__entry,
+ .c-ne__time-and-content {
flex-direction: column;
> [class*="__"] + [class*="__"] {
@@ -413,9 +594,14 @@ body.mobile {
.c-notebook__entry {
[class*="text"] {
min-height: 0;
- padding: 0;
pointer-events: none;
}
}
}
}
+
+/****************************** INDICATOR */
+.c-indicator.has-new-snapshot {
+ $c: $colorOk;
+ @include pulseProp($animName: flashSnapshot, $dur: 500ms, $iter: infinite, $prop: background, $valStart: rgba($c, 0.4), $valEnd: rgba($c, 0));
+}
diff --git a/src/styles/vue-styles.scss b/src/styles/vue-styles.scss
index 27d89822f8..1e8b2815ee 100644
--- a/src/styles/vue-styles.scss
+++ b/src/styles/vue-styles.scss
@@ -47,3 +47,4 @@
@import "../ui/preview/preview.scss";
@import "../ui/toolbar/components/toolbar-checkbox.scss";
@import "./notebook.scss";
+@import "../plugins/notebook/components/sidebar.scss";
diff --git a/src/ui/components/ObjectFrame.vue b/src/ui/components/ObjectFrame.vue
index e9657a6e9c..3357db37c8 100644
--- a/src/ui/components/ObjectFrame.vue
+++ b/src/ui/components/ObjectFrame.vue
@@ -29,7 +29,7 @@
>