mirror of
https://github.com/nasa/openmct.git
synced 2025-01-22 04:18:05 +00:00
Merge pull request #2829 from nasa/topic-conditionals-master-merge
Topic conditionals master merge
This commit is contained in:
commit
3956cd1c06
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2018, 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.
|
||||
|
||||
@ -18,4 +18,4 @@
|
||||
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.
|
||||
-->
|
||||
-->
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, 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.
|
||||
*
|
||||
@ -18,4 +18,4 @@
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
*****************************************************************************/
|
||||
|
@ -1,80 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Code coverage report for All files</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="prettify.css" />
|
||||
<link rel="stylesheet" href="base.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1>
|
||||
/
|
||||
</h1>
|
||||
<div class='clearfix'>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div><div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage
|
||||
generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Wed Dec 11 2019 13:15:10 GMT-0800 (Pacific Standard Time)
|
||||
</div>
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
if (typeof prettyPrint === 'function') {
|
||||
prettyPrint();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="sorter.js"></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 209 B |
@ -1,158 +0,0 @@
|
||||
var addSorting = (function () {
|
||||
"use strict";
|
||||
var cols,
|
||||
currentSort = {
|
||||
index: 0,
|
||||
desc: false
|
||||
};
|
||||
|
||||
// returns the summary table element
|
||||
function getTable() { return document.querySelector('.coverage-summary'); }
|
||||
// returns the thead element of the summary table
|
||||
function getTableHeader() { return getTable().querySelector('thead tr'); }
|
||||
// returns the tbody element of the summary table
|
||||
function getTableBody() { return getTable().querySelector('tbody'); }
|
||||
// returns the th element for nth column
|
||||
function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; }
|
||||
|
||||
// loads all columns
|
||||
function loadColumns() {
|
||||
var colNodes = getTableHeader().querySelectorAll('th'),
|
||||
colNode,
|
||||
cols = [],
|
||||
col,
|
||||
i;
|
||||
|
||||
for (i = 0; i < colNodes.length; i += 1) {
|
||||
colNode = colNodes[i];
|
||||
col = {
|
||||
key: colNode.getAttribute('data-col'),
|
||||
sortable: !colNode.getAttribute('data-nosort'),
|
||||
type: colNode.getAttribute('data-type') || 'string'
|
||||
};
|
||||
cols.push(col);
|
||||
if (col.sortable) {
|
||||
col.defaultDescSort = col.type === 'number';
|
||||
colNode.innerHTML = colNode.innerHTML + '<span class="sorter"></span>';
|
||||
}
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
// attaches a data attribute to every tr element with an object
|
||||
// of data values keyed by column name
|
||||
function loadRowData(tableRow) {
|
||||
var tableCols = tableRow.querySelectorAll('td'),
|
||||
colNode,
|
||||
col,
|
||||
data = {},
|
||||
i,
|
||||
val;
|
||||
for (i = 0; i < tableCols.length; i += 1) {
|
||||
colNode = tableCols[i];
|
||||
col = cols[i];
|
||||
val = colNode.getAttribute('data-value');
|
||||
if (col.type === 'number') {
|
||||
val = Number(val);
|
||||
}
|
||||
data[col.key] = val;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
// loads all row data
|
||||
function loadData() {
|
||||
var rows = getTableBody().querySelectorAll('tr'),
|
||||
i;
|
||||
|
||||
for (i = 0; i < rows.length; i += 1) {
|
||||
rows[i].data = loadRowData(rows[i]);
|
||||
}
|
||||
}
|
||||
// sorts the table using the data for the ith column
|
||||
function sortByIndex(index, desc) {
|
||||
var key = cols[index].key,
|
||||
sorter = function (a, b) {
|
||||
a = a.data[key];
|
||||
b = b.data[key];
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
},
|
||||
finalSorter = sorter,
|
||||
tableBody = document.querySelector('.coverage-summary tbody'),
|
||||
rowNodes = tableBody.querySelectorAll('tr'),
|
||||
rows = [],
|
||||
i;
|
||||
|
||||
if (desc) {
|
||||
finalSorter = function (a, b) {
|
||||
return -1 * sorter(a, b);
|
||||
};
|
||||
}
|
||||
|
||||
for (i = 0; i < rowNodes.length; i += 1) {
|
||||
rows.push(rowNodes[i]);
|
||||
tableBody.removeChild(rowNodes[i]);
|
||||
}
|
||||
|
||||
rows.sort(finalSorter);
|
||||
|
||||
for (i = 0; i < rows.length; i += 1) {
|
||||
tableBody.appendChild(rows[i]);
|
||||
}
|
||||
}
|
||||
// removes sort indicators for current column being sorted
|
||||
function removeSortIndicators() {
|
||||
var col = getNthColumn(currentSort.index),
|
||||
cls = col.className;
|
||||
|
||||
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
|
||||
col.className = cls;
|
||||
}
|
||||
// adds sort indicators for current column being sorted
|
||||
function addSortIndicators() {
|
||||
getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted';
|
||||
}
|
||||
// adds event listeners for all sorter widgets
|
||||
function enableUI() {
|
||||
var i,
|
||||
el,
|
||||
ithSorter = function ithSorter(i) {
|
||||
var col = cols[i];
|
||||
|
||||
return function () {
|
||||
var desc = col.defaultDescSort;
|
||||
|
||||
if (currentSort.index === i) {
|
||||
desc = !currentSort.desc;
|
||||
}
|
||||
sortByIndex(i, desc);
|
||||
removeSortIndicators();
|
||||
currentSort.index = i;
|
||||
currentSort.desc = desc;
|
||||
addSortIndicators();
|
||||
};
|
||||
};
|
||||
for (i =0 ; i < cols.length; i += 1) {
|
||||
if (cols[i].sortable) {
|
||||
// add the click event handler on the th so users
|
||||
// dont have to click on those tiny arrows
|
||||
el = getNthColumn(i).querySelector('.sorter').parentElement;
|
||||
if (el.addEventListener) {
|
||||
el.addEventListener('click', ithSorter(i));
|
||||
} else {
|
||||
el.attachEvent('onclick', ithSorter(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// adds sorting functionality to the UI
|
||||
return function () {
|
||||
if (!getTable()) {
|
||||
return;
|
||||
}
|
||||
cols = loadColumns();
|
||||
loadData(cols);
|
||||
addSortIndicators();
|
||||
enableUI();
|
||||
};
|
||||
})();
|
||||
|
||||
window.addEventListener('load', addSorting);
|
@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="u-contents">
|
||||
<div class="c-cdef__separator c-row-separator"></div>
|
||||
|
@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
.c-cs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
.c-condition,
|
||||
.c-test-datum {
|
||||
@include discreteItem();
|
||||
|
@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<li class="c-tree__item-h">
|
||||
<div
|
||||
|
@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="u-contents">
|
||||
<div class="c-overlay__top-bar">
|
||||
|
@ -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 {
|
||||
> * + * {
|
||||
|
@ -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',
|
||||
|
@ -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) {
|
||||
|
@ -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 = [
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<a class="c-condition-widget"
|
||||
:href="internalDomainObject.url"
|
||||
|
@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
.c-condition-widget {
|
||||
$shdwSize: 3px;
|
||||
@include userSelectNone();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, 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.
|
||||
*
|
||||
|
@ -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 StyleRuleManager from "@/plugins/condition/StyleRuleManager";
|
||||
|
||||
export default {
|
||||
|
272
src/plugins/notebook/components/notebook-embed.vue
Normal file
272
src/plugins/notebook/components/notebook-embed.vue
Normal file
@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<div class="c-snapshot c-ne__embed">
|
||||
<div v-if="embed.snapshot"
|
||||
class="c-ne__embed__snap-thumb"
|
||||
@click="openSnapshot()"
|
||||
>
|
||||
<img :src="embed.snapshot.src">
|
||||
</div>
|
||||
<div class="c-ne__embed__info">
|
||||
<div class="c-ne__embed__name">
|
||||
<a class="c-ne__embed__link"
|
||||
:class="embed.cssClass"
|
||||
@click="changeLocation"
|
||||
>{{ embed.name }}</a>
|
||||
<a class="c-ne__embed__context-available icon-arrow-down"
|
||||
@click="toggleActionMenu"
|
||||
></a>
|
||||
</div>
|
||||
<div class="hide-menu hidden">
|
||||
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
||||
<div class="c-menu">
|
||||
<ul>
|
||||
<li v-for="action in actions"
|
||||
:key="action.name"
|
||||
:class="action.cssClass"
|
||||
@click="action.perform(embed)"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="embed.snapshot"
|
||||
class="c-ne__embed__time"
|
||||
>
|
||||
{{ formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Moment from 'moment';
|
||||
import PreviewAction from '../../../ui/preview/PreviewAction';
|
||||
import Painterro from 'painterro';
|
||||
import SnapshotTemplate from './snapshot-template.html';
|
||||
import { togglePopupMenu } from '../utils/popup-menu';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
embed: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
removeActionString: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'Remove Embed';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actions: [this.removeEmbedAction()],
|
||||
agentService: this.openmct.$injector.get('agentService'),
|
||||
popupService: this.openmct.$injector.get('popupService')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
beforeMount() {
|
||||
this.populateActionMenu();
|
||||
},
|
||||
methods: {
|
||||
annotateSnapshot() {
|
||||
const self = this;
|
||||
|
||||
let save = false;
|
||||
let painterroInstance = {};
|
||||
const annotateVue = new Vue({
|
||||
template: '<div id="snap-annotation"></div>'
|
||||
});
|
||||
|
||||
let annotateOverlay = self.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) {
|
||||
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);
|
||||
},
|
||||
changeLocation() {
|
||||
this.openmct.time.stopClock();
|
||||
this.openmct.time.bounds({
|
||||
start: this.embed.bounds.start,
|
||||
end: this.embed.bounds.end
|
||||
});
|
||||
|
||||
const link = this.embed.historicLink;
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = link;
|
||||
const message = 'Time bounds changed to fixed timespan mode';
|
||||
this.openmct.notifications.alert(message);
|
||||
},
|
||||
formatTime(unixTime, timeFormat) {
|
||||
return Moment.utc(unixTime).format(timeFormat);
|
||||
},
|
||||
openSnapshot() {
|
||||
const self = this;
|
||||
const snapshot = new Vue({
|
||||
data: () => {
|
||||
return {
|
||||
embed: self.embed
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
formatTime: self.formatTime,
|
||||
annotateSnapshot: self.annotateSnapshot
|
||||
},
|
||||
template: SnapshotTemplate
|
||||
});
|
||||
|
||||
const snapshotOverlay = this.openmct.overlays.overlay({
|
||||
element: snapshot.$mount().$el,
|
||||
onDestroy: () => { snapshot.$destroy(true) },
|
||||
size: 'large',
|
||||
dismissable: true,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Done',
|
||||
emphasis: true,
|
||||
callback: () => {
|
||||
snapshotOverlay.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
populateActionMenu() {
|
||||
const self = this;
|
||||
const actions = [new PreviewAction(self.openmct)];
|
||||
self.openmct.objects.get(self.embed.type)
|
||||
.then((domainObject) => {
|
||||
actions.forEach((action) => {
|
||||
self.actions.push({
|
||||
cssClass: action.cssClass,
|
||||
name: action.name,
|
||||
perform: () => {
|
||||
action.invoke([domainObject].concat(self.openmct.router.path));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
removeEmbed(id) {
|
||||
this.$emit('removeEmbed', id);
|
||||
},
|
||||
removeEmbedAction() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
name: self.removeActionString,
|
||||
cssClass: 'icon-trash',
|
||||
perform: function (embed) {
|
||||
const dialog = self.openmct.overlays.dialog({
|
||||
iconClass: "error",
|
||||
message: `This action will permanently ${self.removeActionString.toLowerCase()}. Do you wish to continue?`,
|
||||
buttons: [{
|
||||
label: "No",
|
||||
callback: function () {
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Yes",
|
||||
emphasis: true,
|
||||
callback: function () {
|
||||
dialog.dismiss();
|
||||
self.removeEmbed(embed.id);
|
||||
}
|
||||
}]
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
toggleActionMenu(event) {
|
||||
togglePopupMenu(event, this.openmct);
|
||||
},
|
||||
updateEmbed(embed) {
|
||||
this.$emit('updateEmbed', embed);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
316
src/plugins/notebook/components/notebook-entry.vue
Normal file
316
src/plugins/notebook/components/notebook-entry.vue
Normal file
@ -0,0 +1,316 @@
|
||||
<template>
|
||||
<div class="c-notebook__entry c-ne has-local-controls"
|
||||
@dragover="dragover"
|
||||
@drop.capture="dropCapture"
|
||||
@drop.prevent="dropOnEntry(entry.id, $event)"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
<div class="c-ne__content">
|
||||
<div :id="entry.id"
|
||||
class="c-ne__text"
|
||||
:class="{'c-input-inline' : !readOnly }"
|
||||
:contenteditable="!readOnly"
|
||||
:style="!entry.text.length ? defaultEntryStyle : ''"
|
||||
@blur="textBlur($event, entry.id)"
|
||||
@focus="textFocus($event, entry.id)"
|
||||
>{{ entry.text.length ? entry.text : defaultText }}</div>
|
||||
<div class="c-snapshots c-ne__embeds">
|
||||
<NotebookEmbed v-for="embed in entry.embeds"
|
||||
:key="embed.id"
|
||||
:embed="embed"
|
||||
:entry="entry"
|
||||
@removeEmbed="removeEmbed"
|
||||
@updateEmbed="updateEmbed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!readOnly"
|
||||
class="c-ne__local-controls--hidden"
|
||||
>
|
||||
<button class="c-icon-button c-icon-button--major icon-trash"
|
||||
title="Delete this entry"
|
||||
@click="deleteEntry"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="readOnly"
|
||||
class="c-ne__section-and-page"
|
||||
>
|
||||
<a class="c-click-link"
|
||||
@click="navigateToSection()"
|
||||
>
|
||||
{{ result.section.name }}
|
||||
</a>
|
||||
<span class="icon-arrow-right"></span>
|
||||
<a class="c-click-link"
|
||||
@click="navigateToPage()"
|
||||
>
|
||||
{{ result.page.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NotebookEmbed from './notebook-embed.vue';
|
||||
import { createNewEmbed, getEntryPosById, getNotebookEntries } from '../utils/notebook-entries';
|
||||
import Moment from 'moment';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'snapshotContainer'],
|
||||
components: {
|
||||
NotebookEmbed
|
||||
},
|
||||
props: {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
entry: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
result: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
selectedPage: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
selectedSection: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentEntryValue: '',
|
||||
defaultEntryStyle: {
|
||||
fontStyle: 'italic',
|
||||
color: '#6e6e6e'
|
||||
},
|
||||
defaultText: 'add description'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
entry() {
|
||||
},
|
||||
readOnly(readOnly) {
|
||||
},
|
||||
selectedSection(selectedSection) {
|
||||
},
|
||||
selectedPage(selectedSection) {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateEntries = this.updateEntries.bind(this);
|
||||
},
|
||||
beforeDestory() {
|
||||
},
|
||||
methods: {
|
||||
deleteEntry() {
|
||||
const self = this;
|
||||
if (!self.domainObject || !self.selectedSection || !self.selectedPage || !self.entry.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entryPosById = this.entryPosById(this.entry.id);
|
||||
if (entryPosById === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dialog = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: 'This action will permanently delete this entry. Do you wish to continue?',
|
||||
buttons: [
|
||||
{
|
||||
label: "Ok",
|
||||
emphasis: true,
|
||||
callback: () => {
|
||||
const entries = getNotebookEntries(self.domainObject, self.selectedSection, self.selectedPage);
|
||||
entries.splice(entryPosById, 1);
|
||||
this.updateEntries(entries);
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Cancel",
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
dragover() {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
},
|
||||
dropCapture(event) {
|
||||
const isEditing = this.openmct.editor.isEditing();
|
||||
if (isEditing) {
|
||||
this.openmct.editor.cancel();
|
||||
}
|
||||
},
|
||||
dropOnEntry(entryId, $event) {
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshotId = $event.dataTransfer.getData('snapshot/id');
|
||||
if (snapshotId.length) {
|
||||
this.moveSnapshot(snapshotId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const data = $event.dataTransfer.getData('openmct/domain-object-path');
|
||||
const objectPath = JSON.parse(data);
|
||||
const entryPos = this.entryPosById(entryId);
|
||||
const bounds = this.openmct.time.bounds();
|
||||
const snapshotMeta = {
|
||||
bounds,
|
||||
link: null,
|
||||
objectPath,
|
||||
openmct: this.openmct
|
||||
}
|
||||
const newEmbed = createNewEmbed(snapshotMeta);
|
||||
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||
const currentEntryEmbeds = entries[entryPos].embeds;
|
||||
currentEntryEmbeds.push(newEmbed);
|
||||
this.updateEntries(entries);
|
||||
},
|
||||
entryPosById(entryId) {
|
||||
return getEntryPosById(entryId, this.domainObject, this.selectedSection, this.selectedPage);
|
||||
},
|
||||
findPositionInArray(array, id) {
|
||||
let position = -1;
|
||||
array.some((item, index) => {
|
||||
const found = item.id === id;
|
||||
if (found) {
|
||||
position = index;
|
||||
}
|
||||
|
||||
return found;
|
||||
});
|
||||
|
||||
return position;
|
||||
},
|
||||
formatTime(unixTime, timeFormat) {
|
||||
return Moment(unixTime).format(timeFormat);
|
||||
},
|
||||
moveSnapshot(snapshotId) {
|
||||
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
|
||||
this.entry.embeds.push(snapshot);
|
||||
this.updateEntry(this.entry);
|
||||
this.snapshotContainer.removeSnapshot(snapshotId);
|
||||
},
|
||||
navigateToPage() {
|
||||
this.$emit('changeSectionPage', {
|
||||
sectionId: this.result.section.id,
|
||||
pageId: this.result.page.id
|
||||
});
|
||||
},
|
||||
navigateToSection() {
|
||||
this.$emit('changeSectionPage', {
|
||||
sectionId: this.result.section.id,
|
||||
pageId: null
|
||||
});
|
||||
},
|
||||
removeEmbed(id) {
|
||||
const embedPosition = this.findPositionInArray(this.entry.embeds, id);
|
||||
this.entry.embeds.splice(embedPosition, 1);
|
||||
this.updateEntry(this.entry);
|
||||
},
|
||||
selectTextInsideElement(element) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
var selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
},
|
||||
textBlur($event, entryId) {
|
||||
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = $event.target;
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entryPos = this.entryPosById(entryId);
|
||||
const value = target.textContent.trim();
|
||||
if (this.currentEntryValue !== value) {
|
||||
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||
entries[entryPos].text = value;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
114
src/plugins/notebook/components/notebook-menu-switcher.vue
Normal file
114
src/plugins/notebook/components/notebook-menu-switcher.vue
Normal file
@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||
<button
|
||||
class="c-button--menu icon-notebook"
|
||||
title="Switch view type"
|
||||
@click="setNotebookTypes"
|
||||
@click.stop="toggleMenu"
|
||||
>
|
||||
<span class="c-button__label"></span>
|
||||
</button>
|
||||
<div
|
||||
v-show="showMenu"
|
||||
class="c-menu"
|
||||
>
|
||||
<ul>
|
||||
<li
|
||||
v-for="(type, index) in notebookTypes"
|
||||
:key="index"
|
||||
:class="type.cssClass"
|
||||
:title="type.name"
|
||||
@click="snapshot(type)"
|
||||
>
|
||||
{{ type.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Snapshot from '../snapshot';
|
||||
import { clearDefaultNotebook, getDefaultNotebook } from '../utils/notebook-storage';
|
||||
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
notebookSnapshot: null,
|
||||
notebookTypes: [],
|
||||
showMenu: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.notebookSnapshot = new Snapshot(this.openmct);
|
||||
|
||||
document.addEventListener('click', this.hideMenu);
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('click', this.hideMenu);
|
||||
},
|
||||
methods: {
|
||||
async setNotebookTypes() {
|
||||
const notebookTypes = [];
|
||||
let defaultPath = '';
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
|
||||
if (defaultNotebook) {
|
||||
const domainObject = await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier)
|
||||
.then(d => d);
|
||||
|
||||
if (!domainObject.location) {
|
||||
clearDefaultNotebook();
|
||||
} else {
|
||||
defaultPath = `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultPath.length !== 0) {
|
||||
notebookTypes.push({
|
||||
cssClass: 'icon-notebook',
|
||||
name: `Save to Notebook ${defaultPath}`,
|
||||
type: NOTEBOOK_DEFAULT
|
||||
});
|
||||
}
|
||||
|
||||
notebookTypes.push({
|
||||
cssClass: 'icon-notebook',
|
||||
name: 'Save to Notebook Snapshots',
|
||||
type: NOTEBOOK_SNAPSHOT
|
||||
});
|
||||
|
||||
this.notebookTypes = notebookTypes;
|
||||
},
|
||||
toggleMenu() {
|
||||
this.showMenu = !this.showMenu;
|
||||
},
|
||||
hideMenu() {
|
||||
this.showMenu = false;
|
||||
},
|
||||
snapshot(notebook) {
|
||||
let element = document.getElementsByClassName("l-shell__main-container")[0];
|
||||
const bounds = this.openmct.time.bounds();
|
||||
const objectPath = this.openmct.router.path;
|
||||
const snapshotMeta = {
|
||||
bounds,
|
||||
link: window.location.href,
|
||||
objectPath,
|
||||
openmct: this.openmct
|
||||
};
|
||||
|
||||
this.notebookSnapshot.capture(snapshotMeta, notebook.type, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
152
src/plugins/notebook/components/notebook-snapshot-container.vue
Normal file
152
src/plugins/notebook/components/notebook-snapshot-container.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="c-snapshots-h">
|
||||
<div class="l-browse-bar">
|
||||
<div class="l-browse-bar__start">
|
||||
<div class="l-browse-bar__object-name--w icon-notebook">
|
||||
<div class="l-browse-bar__object-name">
|
||||
Notebook Snapshots
|
||||
<span v-if="snapshots.length"
|
||||
class="l-browse-bar__object-details"
|
||||
> {{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
|
||||
</span>
|
||||
</div>
|
||||
<a class="l-browse-bar__context-actions c-disclosure-button"
|
||||
@click="toggleActionMenu"
|
||||
></a>
|
||||
<div class="hide-menu hidden">
|
||||
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
||||
<div class="c-menu">
|
||||
<ul>
|
||||
<li v-for="action in actions"
|
||||
:key="action.name"
|
||||
:class="action.cssClass"
|
||||
@click="action.perform()"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="l-browse-bar__end">
|
||||
<button class="c-click-icon c-click-icon--major icon-x"
|
||||
@click="close"
|
||||
></button>
|
||||
</div>
|
||||
</div><!-- closes l-browse-bar -->
|
||||
<div class="c-snapshots">
|
||||
<span v-for="snapshot in snapshots"
|
||||
:key="snapshot.id"
|
||||
draggable="true"
|
||||
@dragstart="startEmbedDrag(snapshot, $event)"
|
||||
>
|
||||
<NotebookEmbed ref="notebookEmbed"
|
||||
:key="snapshot.id"
|
||||
:embed="snapshot"
|
||||
:remove-action-string="'Delete Snapshot'"
|
||||
@updateEmbed="updateSnapshot"
|
||||
@removeEmbed="removeSnapshot"
|
||||
/>
|
||||
</span>
|
||||
<div v-if="!snapshots.length > 0"
|
||||
class="hint"
|
||||
>
|
||||
There are no Notebook Snapshots currently.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NotebookEmbed from './notebook-embed.vue';
|
||||
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
|
||||
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
|
||||
import { togglePopupMenu } from '../utils/popup-menu';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'snapshotContainer'],
|
||||
components: {
|
||||
NotebookEmbed
|
||||
},
|
||||
props: {
|
||||
toggleSnapshot: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actions: [this.removeAllSnapshotAction()],
|
||||
snapshots: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.snapshotContainer.on(EVENT_SNAPSHOTS_UPDATED, this.snapshotsUpdated);
|
||||
this.snapshots = this.snapshotContainer.getSnapshots();
|
||||
},
|
||||
beforeDestory() {
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.toggleSnapshot();
|
||||
},
|
||||
getNotebookSnapshotMaxCount() {
|
||||
return NOTEBOOK_SNAPSHOT_MAX_COUNT;
|
||||
},
|
||||
removeAllSnapshotAction() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
name: 'Delete All Snapshots',
|
||||
cssClass: 'icon-trash',
|
||||
perform: function (embed) {
|
||||
const dialog = self.openmct.overlays.dialog({
|
||||
iconClass: "error",
|
||||
message: 'This action will delete all notebook snapshots. Do you want to continue?',
|
||||
buttons: [
|
||||
{
|
||||
label: "No",
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Yes",
|
||||
emphasis: true,
|
||||
callback: () => {
|
||||
self.removeAllSnapshots();
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
removeAllSnapshots() {
|
||||
this.snapshotContainer.removeAllSnapshots();
|
||||
},
|
||||
removeSnapshot(id) {
|
||||
this.snapshotContainer.removeSnapshot(id);
|
||||
},
|
||||
snapshotsUpdated() {
|
||||
this.snapshots = this.snapshotContainer.getSnapshots();
|
||||
},
|
||||
startEmbedDrag(snapshot, event) {
|
||||
event.dataTransfer.setData('text/plain', snapshot.id);
|
||||
event.dataTransfer.setData('snapshot/id', snapshot.id);
|
||||
},
|
||||
toggleActionMenu(event) {
|
||||
togglePopupMenu(event, this.openmct);
|
||||
},
|
||||
updateSnapshot(snapshot) {
|
||||
this.snapshotContainer.updateSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div class="c-indicator c-indicator--clickable icon-notebook"
|
||||
:class="[
|
||||
{ 's-status-off': snapshotCount === 0 },
|
||||
{ 's-status-on': snapshotCount > 0 },
|
||||
{ 's-status-caution': snapshotCount === snapshotMaxCount },
|
||||
{ 'has-new-snapshot': flashIndicator }
|
||||
]"
|
||||
>
|
||||
<span class="label c-indicator__label">
|
||||
{{ indicatorTitle }}
|
||||
<button @click="toggleSnapshot">
|
||||
{{ expanded ? 'Hide' : 'Show' }}
|
||||
</button>
|
||||
</span>
|
||||
<span class="c-indicator__count">{{ snapshotCount }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SnapshotContainerComponent from './notebook-snapshot-container.vue';
|
||||
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
|
||||
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct','snapshotContainer'],
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
indicatorTitle: '',
|
||||
snapshotCount: 0,
|
||||
snapshotMaxCount: NOTEBOOK_SNAPSHOT_MAX_COUNT,
|
||||
flashIndicator: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.snapshotContainer.on(EVENT_SNAPSHOTS_UPDATED, this.snapshotsUpdated);
|
||||
this.updateSnapshotIndicatorTitle();
|
||||
},
|
||||
methods: {
|
||||
notifyNewSnapshot() {
|
||||
this.flashIndicator = true;
|
||||
setTimeout(this.removeNotify, 15000);
|
||||
},
|
||||
removeNotify() {
|
||||
this.flashIndicator = false;
|
||||
},
|
||||
snapshotsUpdated() {
|
||||
if (this.snapshotContainer.getSnapshots().length > this.snapshotCount) {
|
||||
this.notifyNewSnapshot();
|
||||
}
|
||||
this.updateSnapshotIndicatorTitle();
|
||||
},
|
||||
toggleSnapshot() {
|
||||
this.expanded = !this.expanded;
|
||||
|
||||
const drawerElement = document.querySelector('.l-shell__drawer');
|
||||
drawerElement.classList.toggle('is-expanded');
|
||||
|
||||
this.updateSnapshotContainer();
|
||||
},
|
||||
updateSnapshotContainer() {
|
||||
const { openmct, snapshotContainer } = this;
|
||||
const toggleSnapshot = this.toggleSnapshot.bind(this);
|
||||
const drawerElement = document.querySelector('.l-shell__drawer');
|
||||
drawerElement.innerHTML = '<div></div>';
|
||||
const divElement = document.querySelector('.l-shell__drawer div');
|
||||
|
||||
this.component = new Vue({
|
||||
provide: {
|
||||
openmct,
|
||||
snapshotContainer
|
||||
},
|
||||
el: divElement,
|
||||
components: {
|
||||
SnapshotContainerComponent
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
toggleSnapshot
|
||||
};
|
||||
},
|
||||
template: '<SnapshotContainerComponent :toggleSnapshot="toggleSnapshot"></SnapshotContainerComponent>'
|
||||
}).$mount();
|
||||
},
|
||||
updateSnapshotIndicatorTitle() {
|
||||
const snapshotCount = this.snapshotContainer.getSnapshots().length;
|
||||
this.snapshotCount = snapshotCount;
|
||||
const snapshotTitleSuffix = snapshotCount === 1
|
||||
? 'Snapshot'
|
||||
: 'Snapshots';
|
||||
this.indicatorTitle = `${snapshotCount} ${snapshotTitleSuffix}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
528
src/plugins/notebook/components/notebook.vue
Normal file
528
src/plugins/notebook/components/notebook.vue
Normal file
@ -0,0 +1,528 @@
|
||||
<template>
|
||||
<div class="c-notebook">
|
||||
<div class="c-notebook__head">
|
||||
<Search class="c-notebook__search"
|
||||
:value="search"
|
||||
@input="throttledSearchItem"
|
||||
@clear="throttledSearchItem"
|
||||
/>
|
||||
</div>
|
||||
<SearchResults v-if="search.length"
|
||||
ref="searchResults"
|
||||
:results="getSearchResults()"
|
||||
@changeSectionPage="changeSelectedSection"
|
||||
/>
|
||||
|
||||
<div v-if="!search.length"
|
||||
class="c-notebook__body"
|
||||
>
|
||||
<Sidebar ref="sidebar"
|
||||
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
|
||||
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
|
||||
:default-page-id="defaultPageId"
|
||||
:default-section-id="defaultSectionId"
|
||||
:domain-object="internalDomainObject"
|
||||
:page-title="internalDomainObject.configuration.pageTitle"
|
||||
:pages="pages"
|
||||
:section-title="internalDomainObject.configuration.sectionTitle"
|
||||
:sections="sections"
|
||||
:sidebar-covers-entries="sidebarCoversEntries"
|
||||
@updatePage="updatePage"
|
||||
@updateSection="updateSection"
|
||||
@toggleNav="toggleNav"
|
||||
/>
|
||||
<div class="c-notebook__page-view">
|
||||
<div class="c-notebook__page-view__header">
|
||||
<button class="c-notebook__toggle-nav-button c-icon-button c-icon-button--major icon-menu-hamburger"
|
||||
@click="toggleNav"
|
||||
></button>
|
||||
<div class="c-notebook__page-view__path c-path">
|
||||
<span class="c-notebook__path__section c-path__item">
|
||||
{{ getSelectedSection() ? getSelectedSection().name : '' }}
|
||||
</span>
|
||||
<span class="c-notebook__path__page c-path__item">
|
||||
{{ getSelectedPage() ? getSelectedPage().name : '' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="c-notebook__page-view__controls">
|
||||
<select v-model="showTime"
|
||||
class="c-notebook__controls__time"
|
||||
>
|
||||
<option value="0"
|
||||
selected="selected"
|
||||
>
|
||||
Show all
|
||||
</option>
|
||||
<option value="1">Last hour</option>
|
||||
<option value="8">Last 8 hours</option>
|
||||
<option value="24">Last 24 hours</option>
|
||||
</select>
|
||||
<select v-model="defaultSort"
|
||||
class="c-notebook__controls__time"
|
||||
>
|
||||
<option value="newest"
|
||||
:selected="defaultSort === 'newest'"
|
||||
>Newest first</option>
|
||||
<option value="oldest"
|
||||
:selected="defaultSort === 'oldest'"
|
||||
>Oldest first</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-notebook__drag-area icon-plus"
|
||||
@click="newEntry()"
|
||||
@dragover="dragOver"
|
||||
@drop.capture="dropCapture"
|
||||
@drop="dropOnEntry($event)"
|
||||
>
|
||||
<span class="c-notebook__drag-area__label">
|
||||
To start a new entry, click here or drag and drop any object
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="selectedSection && selectedPage"
|
||||
class="c-notebook__entries"
|
||||
>
|
||||
<NotebookEntry v-for="entry in filteredAndSortedEntries"
|
||||
ref="notebookEntry"
|
||||
:key="entry.id"
|
||||
:entry="entry"
|
||||
:domain-object="internalDomainObject"
|
||||
:selected-page="getSelectedPage()"
|
||||
:selected-section="getSelectedSection()"
|
||||
:read-only="false"
|
||||
@updateEntries="updateEntries"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NotebookEntry from './notebook-entry.vue';
|
||||
import Search from '@/ui/components/search.vue';
|
||||
import SearchResults from './search-results.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';
|
||||
|
||||
const DEFAULT_CLASS = 'is-notebook-default';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject', 'snapshotContainer'],
|
||||
components: {
|
||||
NotebookEntry,
|
||||
Search,
|
||||
SearchResults,
|
||||
Sidebar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultPageId: getDefaultNotebook() ? getDefaultNotebook().page.id : '',
|
||||
defaultSectionId: getDefaultNotebook() ? getDefaultNotebook().section.id : '',
|
||||
defaultSort: this.domainObject.configuration.defaultSort,
|
||||
internalDomainObject: this.domainObject,
|
||||
search: '',
|
||||
showTime: 0,
|
||||
showNav: false,
|
||||
sidebarCoversEntries: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredAndSortedEntries() {
|
||||
const pageEntries = getNotebookEntries(this.internalDomainObject, this.selectedSection, this.selectedPage) || [];
|
||||
|
||||
return pageEntries.sort(this.sortEntries);
|
||||
},
|
||||
pages() {
|
||||
return this.getPages() || [];
|
||||
},
|
||||
sections() {
|
||||
return this.internalDomainObject.configuration.sections || [];
|
||||
},
|
||||
selectedPage() {
|
||||
const pages = this.getPages();
|
||||
if (!pages) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return pages.find(page => page.isSelected);
|
||||
},
|
||||
selectedSection() {
|
||||
if (!this.sections.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.sections.find(section => section.isSelected);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
beforeMount() {
|
||||
this.throttledSearchItem = throttle(this.searchItem, 500);
|
||||
},
|
||||
mounted() {
|
||||
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
|
||||
this.formatSidebar();
|
||||
window.addEventListener('orientationchange', this.formatSidebar);
|
||||
|
||||
this.navigateToSectionPage();
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addDefaultClass() {
|
||||
const classList = this.internalDomainObject.classList || [];
|
||||
if (classList.includes(DEFAULT_CLASS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
classList.push(DEFAULT_CLASS);
|
||||
this.mutateObject('classList', classList);
|
||||
},
|
||||
changeSelectedSection({ sectionId, pageId }) {
|
||||
const sections = this.sections.map(s => {
|
||||
s.isSelected = false;
|
||||
|
||||
if (s.id === sectionId) {
|
||||
s.isSelected = true;
|
||||
}
|
||||
|
||||
s.pages.forEach((p, i) => {
|
||||
p.isSelected = false;
|
||||
|
||||
if (pageId && pageId === p.id) {
|
||||
p.isSelected = true;
|
||||
}
|
||||
|
||||
if (!pageId && i === 0) {
|
||||
p.isSelected = true;
|
||||
}
|
||||
});
|
||||
|
||||
return s;
|
||||
});
|
||||
|
||||
this.updateSection({ sections });
|
||||
this.throttledSearchItem('');
|
||||
},
|
||||
dragOver(event) {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
},
|
||||
dropCapture(event) {
|
||||
const isEditing = this.openmct.editor.isEditing();
|
||||
if (isEditing) {
|
||||
this.openmct.editor.cancel();
|
||||
}
|
||||
},
|
||||
dropOnEntry(event) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
const snapshotId = event.dataTransfer.getData('snapshot/id');
|
||||
if (snapshotId.length) {
|
||||
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
|
||||
this.newEntry(snapshot);
|
||||
this.snapshotContainer.removeSnapshot(snapshotId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const data = event.dataTransfer.getData('openmct/domain-object-path');
|
||||
const objectPath = JSON.parse(data);
|
||||
const bounds = this.openmct.time.bounds();
|
||||
const snapshotMeta = {
|
||||
bounds,
|
||||
link: null,
|
||||
objectPath,
|
||||
openmct: this.openmct
|
||||
};
|
||||
const embed = createNewEmbed(snapshotMeta);
|
||||
this.newEntry(embed);
|
||||
},
|
||||
formatSidebar() {
|
||||
/*
|
||||
Determine if the sidebar should slide over content, or compress it
|
||||
Slide over checks:
|
||||
- phone (all orientations)
|
||||
- tablet portrait
|
||||
- in a layout frame (within .c-so-view)
|
||||
*/
|
||||
const classList = document.querySelector('body').classList;
|
||||
const isPhone = Array.from(classList).includes('phone');
|
||||
const isTablet = Array.from(classList).includes('tablet');
|
||||
const isPortrait = window.screen.orientation.type.includes('portrait');
|
||||
const isInLayout = !!this.$el.closest('.c-so-view');
|
||||
const sidebarCoversEntries = (isPhone || (isTablet && isPortrait) || isInLayout);
|
||||
this.sidebarCoversEntries = sidebarCoversEntries;
|
||||
},
|
||||
getDefaultNotebookObject() {
|
||||
const oldNotebookStorage = getDefaultNotebook();
|
||||
if (!oldNotebookStorage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.openmct.objects.get(oldNotebookStorage.notebookMeta.identifier).then(d => d);
|
||||
},
|
||||
getPage(section, id) {
|
||||
return section.pages.find(p => p.id === id);
|
||||
},
|
||||
getSection(id) {
|
||||
return this.sections.find(s => s.id === id);
|
||||
},
|
||||
getSearchResults() {
|
||||
if (!this.search.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const output = [];
|
||||
const entries = this.internalDomainObject.configuration.entries;
|
||||
const sectionKeys = Object.keys(entries);
|
||||
sectionKeys.forEach(sectionKey => {
|
||||
const pages = entries[sectionKey];
|
||||
const pageKeys = Object.keys(pages);
|
||||
pageKeys.forEach(pageKey => {
|
||||
const pageEntries = entries[sectionKey][pageKey];
|
||||
pageEntries.forEach(entry => {
|
||||
if (entry.text && entry.text.toLowerCase().includes(this.search.toLowerCase())) {
|
||||
const section = this.getSection(sectionKey);
|
||||
output.push({
|
||||
section,
|
||||
page: this.getPage(section, pageKey),
|
||||
entry
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return output;
|
||||
},
|
||||
getPages() {
|
||||
const selectedSection = this.getSelectedSection();
|
||||
if (!selectedSection || !selectedSection.pages.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return selectedSection.pages;
|
||||
},
|
||||
getSelectedPage() {
|
||||
const pages = this.getPages();
|
||||
if (!pages) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedPage = pages.find(page => page.isSelected);
|
||||
if (selectedPage) {
|
||||
return selectedPage;
|
||||
}
|
||||
|
||||
if (!selectedPage && !pages.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
pages[0].isSelected = true;
|
||||
|
||||
return pages[0];
|
||||
},
|
||||
getSelectedSection() {
|
||||
if (!this.sections.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.sections.find(section => section.isSelected);
|
||||
},
|
||||
mutateObject(key, value) {
|
||||
this.openmct.objects.mutate(this.internalDomainObject, key, value);
|
||||
},
|
||||
navigateToSectionPage() {
|
||||
const { pageId, sectionId } = this.openmct.router.getParams();
|
||||
if(!pageId || !sectionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sections = this.sections.map(s => {
|
||||
s.isSelected = false;
|
||||
if (s.id === sectionId) {
|
||||
s.isSelected = true;
|
||||
s.pages.forEach(p => p.isSelected = (p.id === pageId));
|
||||
}
|
||||
|
||||
return s;
|
||||
});
|
||||
|
||||
this.updateSection({ sections });
|
||||
},
|
||||
newEntry(embed = null) {
|
||||
const selectedSection = this.getSelectedSection();
|
||||
const selectedPage = this.getSelectedPage();
|
||||
this.search = '';
|
||||
|
||||
this.updateDefaultNotebook(selectedSection, selectedPage);
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
const id = addNotebookEntry(this.openmct, this.internalDomainObject, notebookStorage, embed);
|
||||
|
||||
this.$nextTick(() => {
|
||||
const element = this.$el.querySelector(`#${id}`);
|
||||
element.focus();
|
||||
});
|
||||
|
||||
return id;
|
||||
},
|
||||
orientationChange() {
|
||||
this.formatSidebar();
|
||||
},
|
||||
removeDefaultClass(domainObject) {
|
||||
if (!domainObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
const classList = domainObject.classList || [];
|
||||
const index = classList.indexOf(DEFAULT_CLASS);
|
||||
if (!classList.length || index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
classList.splice(index, 1);
|
||||
this.openmct.objects.mutate(domainObject, 'classList', classList);
|
||||
},
|
||||
searchItem(input) {
|
||||
this.search = input;
|
||||
},
|
||||
sortEntries(right, left) {
|
||||
return this.defaultSort === 'newest'
|
||||
? left.createdOn - right.createdOn
|
||||
: right.createdOn - left.createdOn;
|
||||
},
|
||||
toggleNav() {
|
||||
this.showNav = !this.showNav;
|
||||
},
|
||||
async updateDefaultNotebook(selectedSection, selectedPage) {
|
||||
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
||||
this.removeDefaultClass(defaultNotebookObject);
|
||||
setDefaultNotebook(this.internalDomainObject, selectedSection, selectedPage);
|
||||
this.addDefaultClass();
|
||||
this.defaultSectionId = selectedSection.id;
|
||||
this.defaultPageId = selectedPage.id;
|
||||
},
|
||||
updateDefaultNotebookPage(pages, id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
if (!notebookStorage
|
||||
|| notebookStorage.notebookMeta.identifier.key !== this.internalDomainObject.identifier.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultNotebookPage = notebookStorage.page;
|
||||
const page = pages.find(p => p.id === id);
|
||||
if (!page && defaultNotebookPage.id === id) {
|
||||
this.defaultSectionId = null;
|
||||
this.defaultPageId = null
|
||||
this.removeDefaultClass(this.internalDomainObject);
|
||||
clearDefaultNotebook();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (id !== defaultNotebookPage.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDefaultNotebookPage(page);
|
||||
},
|
||||
updateDefaultNotebookSection(sections, id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
if (!notebookStorage
|
||||
|| notebookStorage.notebookMeta.identifier.key !== this.internalDomainObject.identifier.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultNotebookSection = notebookStorage.section;
|
||||
const section = sections.find(s => s.id === id);
|
||||
if (!section && defaultNotebookSection.id === id) {
|
||||
this.defaultSectionId = null;
|
||||
this.defaultPageId = null
|
||||
this.removeDefaultClass(this.internalDomainObject);
|
||||
clearDefaultNotebook();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.id !== defaultNotebookSection.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDefaultNotebookSection(section);
|
||||
},
|
||||
updateEntries(entries) {
|
||||
const configuration = this.internalDomainObject.configuration;
|
||||
const notebookEntries = configuration.entries || {};
|
||||
notebookEntries[this.selectedSection.id][this.selectedPage.id] = entries;
|
||||
|
||||
this.mutateObject('configuration.entries', notebookEntries);
|
||||
},
|
||||
updateInternalDomainObject(domainObject) {
|
||||
this.internalDomainObject = domainObject;
|
||||
},
|
||||
updatePage({ pages = [], id = null}) {
|
||||
const selectedSection = this.getSelectedSection();
|
||||
if (!selectedSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSection.pages = pages;
|
||||
const sections = this.sections.map(section => {
|
||||
if (section.id === selectedSection.id) {
|
||||
section = selectedSection;
|
||||
}
|
||||
|
||||
return section;
|
||||
});
|
||||
|
||||
this.updateSection({ sections });
|
||||
this.updateDefaultNotebookPage(pages, id);
|
||||
},
|
||||
updateParams(sections) {
|
||||
const selectedSection = sections.find(s => s.isSelected);
|
||||
if (!selectedSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedPage = selectedSection.pages.find(p => p.isSelected);
|
||||
if (!selectedPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sectionId = selectedSection.id;
|
||||
const pageId = selectedPage.id;
|
||||
|
||||
if (!sectionId || !pageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openmct.router.updateParams({
|
||||
sectionId,
|
||||
pageId
|
||||
});
|
||||
},
|
||||
updateSection({ sections, id = null }) {
|
||||
this.mutateObject('configuration.sections', sections);
|
||||
|
||||
this.updateParams(sections);
|
||||
this.updateDefaultNotebookSection(sections, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
132
src/plugins/notebook/components/page-collection.vue
Normal file
132
src/plugins/notebook/components/page-collection.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<ul class="c-list">
|
||||
<li v-for="page in pages"
|
||||
:key="page.id"
|
||||
class="c-list__item-h"
|
||||
>
|
||||
<Page ref="pageComponent"
|
||||
:default-page-id="defaultPageId"
|
||||
:page="page"
|
||||
:page-title="pageTitle"
|
||||
@deletePage="deletePage"
|
||||
@renamePage="updatePage"
|
||||
@selectPage="selectPage"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deleteNotebookEntries } from '../utils/notebook-entries';
|
||||
import { getDefaultNotebook } from '../utils/notebook-storage';
|
||||
import Page from './page-component.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
Page
|
||||
},
|
||||
props: {
|
||||
defaultPageId: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
domainObject: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
pages: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
sections: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
pageTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
sidebarCoversEntries: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
destroyed() {
|
||||
},
|
||||
methods: {
|
||||
deletePage(id) {
|
||||
const selectedSection = this.sections.find(s => s.isSelected);
|
||||
const page = this.pages.filter(p => p.id !== id);
|
||||
deleteNotebookEntries(this.openmct, this.domainObject, selectedSection, page);
|
||||
|
||||
const selectedPage = this.pages.find(p => p.isSelected);
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const defaultpage = defaultNotebook && defaultNotebook.page;
|
||||
const isPageSelected = selectedPage && selectedPage.id === id;
|
||||
const isPageDefault = defaultpage && defaultpage.id === id;
|
||||
const pages = this.pages.filter(s => s.id !== id);
|
||||
|
||||
if (isPageSelected && defaultpage) {
|
||||
pages.forEach(s => {
|
||||
s.isSelected = false;
|
||||
if (defaultpage && defaultpage.id === s.id) {
|
||||
s.isSelected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (pages.length && isPageSelected && (!defaultpage || isPageDefault)) {
|
||||
pages[0].isSelected = true;
|
||||
}
|
||||
|
||||
this.$emit('updatePage', { pages, id });
|
||||
},
|
||||
selectPage(id) {
|
||||
const pages = this.pages.map(page => {
|
||||
const isSelected = page.id === id;
|
||||
page.isSelected = isSelected;
|
||||
|
||||
return page;
|
||||
});
|
||||
|
||||
this.$emit('updatePage', { pages, id });
|
||||
|
||||
// Add test here for whether or not to toggle the nav
|
||||
if (this.sidebarCoversEntries) {
|
||||
this.$emit('toggleNav');
|
||||
}
|
||||
},
|
||||
updatePage(newPage) {
|
||||
const id = newPage.id;
|
||||
const pages = this.pages.map(page =>
|
||||
page.id === id
|
||||
? newPage
|
||||
: page);
|
||||
this.$emit('updatePage', { pages, id });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
146
src/plugins/notebook/components/page-component.vue
Normal file
146
src/plugins/notebook/components/page-component.vue
Normal file
@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div class="c-list__item js-list__item"
|
||||
:class="[{ 'is-selected': page.isSelected, 'is-notebook-default' : (defaultPageId === page.id) }]"
|
||||
:data-id="page.id"
|
||||
@click="selectPage"
|
||||
>
|
||||
<span class="c-list__item__name js-list__item__name"
|
||||
:data-id="page.id"
|
||||
@keydown.enter="updateName"
|
||||
@blur="updateName"
|
||||
>{{ page.name.length ? page.name : `Unnamed ${pageTitle}` }}</span>
|
||||
<a class="c-list__item__menu-indicator icon-arrow-down"
|
||||
@click="toggleActionMenu"
|
||||
></a>
|
||||
<div class="hide-menu hidden">
|
||||
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
||||
<div class="c-menu">
|
||||
<ul>
|
||||
<li v-for="action in actions"
|
||||
:key="action.name"
|
||||
:class="action.cssClass"
|
||||
@click="action.perform(page.id)"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { togglePopupMenu } from '../utils/popup-menu';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
defaultPageId: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
page: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
pageTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actions: [this.deletePage()]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
page(newPage) {
|
||||
this.toggleContentEditable(newPage);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.toggleContentEditable();
|
||||
},
|
||||
destroyed() {
|
||||
},
|
||||
methods: {
|
||||
deletePage() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
name: `Delete ${this.pageTitle}`,
|
||||
cssClass: 'icon-trash',
|
||||
perform: function (id) {
|
||||
const dialog = self.openmct.overlays.dialog({
|
||||
iconClass: "error",
|
||||
message: 'This action will delete this page and all of its entries. Do you want to continue?',
|
||||
buttons: [
|
||||
{
|
||||
label: "No",
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Yes",
|
||||
emphasis: true,
|
||||
callback: () => {
|
||||
self.$emit('deletePage', id);
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
selectPage(event) {
|
||||
const target = event.target;
|
||||
const page = target.closest('.js-list__item');
|
||||
const input = page.querySelector('.js-list__item__name');
|
||||
|
||||
if (page.className.indexOf('is-selected') > -1) {
|
||||
input.contentEditable = true;
|
||||
input.classList.add('c-input-inline');
|
||||
return;
|
||||
}
|
||||
|
||||
const id = target.dataset.id;
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('selectPage', id);
|
||||
},
|
||||
toggleActionMenu(event) {
|
||||
event.preventDefault();
|
||||
togglePopupMenu(event, this.openmct);
|
||||
},
|
||||
toggleContentEditable(page = this.page) {
|
||||
const pageTitle = this.$el.querySelector('span');
|
||||
pageTitle.contentEditable = page.isSelected;
|
||||
},
|
||||
updateName(event) {
|
||||
const target = event.target;
|
||||
const name = target.textContent.toString();
|
||||
target.contentEditable = false;
|
||||
target.classList.remove('c-input-inline');
|
||||
|
||||
if (this.page.name === name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('renamePage', Object.assign(this.page, { name }));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
50
src/plugins/notebook/components/search-results.vue
Normal file
50
src/plugins/notebook/components/search-results.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="c-notebook__search-results">
|
||||
<div class="c-notebook__search-results__header">Search Results</div>
|
||||
<div class="c-notebook__entries">
|
||||
<NotebookEntry v-for="(result, index) in results"
|
||||
:key="index"
|
||||
:result="result"
|
||||
:entry="result.entry"
|
||||
:read-only="true"
|
||||
:selected-page="null"
|
||||
:selected-section="null"
|
||||
@changeSectionPage="changeSectionPage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NotebookEntry from './notebook-entry.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
components: {
|
||||
NotebookEntry
|
||||
},
|
||||
props:{
|
||||
results: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
results(newResults) {}
|
||||
},
|
||||
destroyed() {
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
changeSectionPage(data) {
|
||||
this.$emit('changeSectionPage', data);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
113
src/plugins/notebook/components/section-collection.vue
Normal file
113
src/plugins/notebook/components/section-collection.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<ul class="c-list">
|
||||
<li v-for="section in sections"
|
||||
:key="section.id"
|
||||
class="c-list__item-h"
|
||||
>
|
||||
<sectionComponent ref="sectionComponent"
|
||||
:default-section-id="defaultSectionId"
|
||||
:section="section"
|
||||
:section-title="sectionTitle"
|
||||
@deleteSection="deleteSection"
|
||||
@renameSection="updateSection"
|
||||
@selectSection="selectSection"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deleteNotebookEntries } from '../utils/notebook-entries';
|
||||
import { getDefaultNotebook } from '../utils/notebook-storage';
|
||||
import sectionComponent from './section-component.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
sectionComponent
|
||||
},
|
||||
props: {
|
||||
defaultSectionId: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
domainObject: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
sections: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
sectionTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
destroyed() {
|
||||
},
|
||||
methods: {
|
||||
deleteSection(id) {
|
||||
const section = this.sections.find(s => s.id === id);
|
||||
deleteNotebookEntries(this.openmct, this.domainObject, section);
|
||||
|
||||
const selectedSection = this.sections.find(s => s.isSelected);
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const defaultSection = defaultNotebook && defaultNotebook.section;
|
||||
const isSectionSelected = selectedSection && selectedSection.id === id;
|
||||
const isSectionDefault = defaultSection && defaultSection.id === id;
|
||||
const sections = this.sections.filter(s => s.id !== id);
|
||||
|
||||
if (isSectionSelected && defaultSection) {
|
||||
sections.forEach(s => {
|
||||
s.isSelected = false;
|
||||
if (defaultSection && defaultSection.id === s.id) {
|
||||
s.isSelected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sections.length && isSectionSelected && (!defaultSection || isSectionDefault)) {
|
||||
sections[0].isSelected = true;
|
||||
}
|
||||
|
||||
this.$emit('updateSection', { sections, id });
|
||||
},
|
||||
selectSection(id, newSections) {
|
||||
const currentSections = newSections || this.sections;
|
||||
const sections = currentSections.map(section => {
|
||||
const isSelected = section.id === id;
|
||||
section.isSelected = isSelected;
|
||||
|
||||
return section;
|
||||
});
|
||||
this.$emit('updateSection', { sections, id });
|
||||
},
|
||||
updateSection(newSection) {
|
||||
const id = newSection.id;
|
||||
const sections = this.sections.map(section =>
|
||||
section.id === id
|
||||
? newSection
|
||||
: section);
|
||||
this.$emit('updateSection', { sections, id });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
149
src/plugins/notebook/components/section-component.vue
Normal file
149
src/plugins/notebook/components/section-component.vue
Normal file
@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="c-list__item js-list__item"
|
||||
:class="[{ 'is-selected': section.isSelected, 'is-notebook-default' : (defaultSectionId === section.id) }]"
|
||||
:data-id="section.id"
|
||||
@click="selectSection"
|
||||
>
|
||||
<span class="c-list__item__name js-list__item__name"
|
||||
:data-id="section.id"
|
||||
@keydown.enter="updateName"
|
||||
@blur="updateName"
|
||||
>{{ section.name.length ? section.name : `Unnamed ${sectionTitle}` }}</span>
|
||||
<a class="c-list__item__menu-indicator icon-arrow-down"
|
||||
@click="toggleActionMenu"
|
||||
></a>
|
||||
<div class="hide-menu hidden">
|
||||
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
||||
<div class="c-menu">
|
||||
<ul>
|
||||
<li v-for="action in actions"
|
||||
:key="action.name"
|
||||
:class="action.cssClass"
|
||||
@click="action.perform(section.id)"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { togglePopupMenu } from '../utils/popup-menu';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
defaultSectionId: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
section: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
sectionTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actions: [this.deleteSectionAction()]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
section(newSection) {
|
||||
this.toggleContentEditable(newSection);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.toggleContentEditable();
|
||||
},
|
||||
destroyed() {
|
||||
},
|
||||
methods: {
|
||||
deleteSectionAction() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
name: `Delete ${this.sectionTitle}`,
|
||||
cssClass: 'icon-trash',
|
||||
perform: function (id) {
|
||||
const dialog = self.openmct.overlays.dialog({
|
||||
iconClass: "error",
|
||||
message: 'This action will delete this section and all of its pages and entries. Do you want to continue?',
|
||||
buttons: [
|
||||
{
|
||||
label: "No",
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Yes",
|
||||
emphasis: true,
|
||||
callback: () => {
|
||||
self.$emit('deleteSection', id);
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
selectSection(event) {
|
||||
const target = event.target;
|
||||
const section = target.closest('.js-list__item');
|
||||
const input = section.querySelector('.js-list__item__name');
|
||||
|
||||
if (section.className.indexOf('is-selected') > -1) {
|
||||
input.contentEditable = true;
|
||||
input.classList.add('c-input-inline');
|
||||
return;
|
||||
}
|
||||
|
||||
const id = target.dataset.id;
|
||||
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('selectSection', id);
|
||||
},
|
||||
toggleActionMenu(event) {
|
||||
togglePopupMenu(event, this.openmct);
|
||||
},
|
||||
toggleContentEditable(section = this.section) {
|
||||
const sectionTitle = this.$el.querySelector('span');
|
||||
sectionTitle.contentEditable = section.isSelected;
|
||||
},
|
||||
updateName(event) {
|
||||
const target = event.target;
|
||||
target.contentEditable = false;
|
||||
target.classList.remove('c-input-inline');
|
||||
const name = target.textContent.trim();
|
||||
|
||||
if (this.section.name === name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('renameSection', Object.assign(this.section, { name }));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
119
src/plugins/notebook/components/sidebar.scss
Normal file
119
src/plugins/notebook/components/sidebar.scss
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
189
src/plugins/notebook/components/sidebar.vue
Normal file
189
src/plugins/notebook/components/sidebar.vue
Normal file
@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="c-sidebar c-drawer c-drawer--align-left">
|
||||
<div class="c-sidebar__pane">
|
||||
<div class="c-sidebar__header-w">
|
||||
<div class="c-sidebar__header">
|
||||
<span class="c-sidebar__header-label">{{ sectionTitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-sidebar__contents-and-controls">
|
||||
<button class="c-list-button"
|
||||
@click="addSection"
|
||||
>
|
||||
<span class="c-button c-list-button__button icon-plus"></span>
|
||||
<span class="c-list-button__label">Add {{ sectionTitle }}</span>
|
||||
</button>
|
||||
<SectionCollection class="c-sidebar__contents"
|
||||
:default-section-id="defaultSectionId"
|
||||
:domain-object="domainObject"
|
||||
:sections="sections"
|
||||
:section-title="sectionTitle"
|
||||
@updateSection="updateSection"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-sidebar__pane">
|
||||
<div class="c-sidebar__header-w">
|
||||
<div class="c-sidebar__header">
|
||||
<span class="c-sidebar__header-label">{{ pageTitle }}</span>
|
||||
</div>
|
||||
<button class="c-click-icon c-click-icon--major icon-x-in-circle"
|
||||
@click="toggleNav"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<div class="c-sidebar__contents-and-controls">
|
||||
<button class="c-list-button"
|
||||
@click="addPage"
|
||||
>
|
||||
<span class="c-button c-list-button__button icon-plus"></span>
|
||||
<span class="c-list-button__label">Add {{ pageTitle }}</span>
|
||||
</button>
|
||||
<PageCollection ref="pageCollection"
|
||||
class="c-sidebar__contents"
|
||||
:default-page-id="defaultPageId"
|
||||
:domain-object="domainObject"
|
||||
:pages="pages"
|
||||
:sections="sections"
|
||||
:sidebar-covers-entries="sidebarCoversEntries"
|
||||
:page-title="pageTitle"
|
||||
@toggleNav="toggleNav"
|
||||
@updatePage="updatePage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SectionCollection from './section-collection.vue';
|
||||
import PageCollection from './page-collection.vue';
|
||||
import uuid from 'uuid';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
SectionCollection,
|
||||
PageCollection
|
||||
},
|
||||
props: {
|
||||
defaultPageId: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
defaultSectionId: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
domainObject: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
pages: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
pageTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
sections: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
sectionTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
sidebarCoversEntries: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
pages(newpages) {
|
||||
if (!newpages.length) {
|
||||
this.addPage();
|
||||
}
|
||||
},
|
||||
sections(newSections) {
|
||||
if (!newSections.length) {
|
||||
this.addSection();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.sections.length) {
|
||||
this.addSection();
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
},
|
||||
methods: {
|
||||
addPage() {
|
||||
const pageTitle = this.pageTitle;
|
||||
const id = uuid();
|
||||
const page = {
|
||||
id,
|
||||
isDefault : false,
|
||||
isSelected: true,
|
||||
name : `Unnamed ${pageTitle}`,
|
||||
pageTitle
|
||||
};
|
||||
|
||||
this.pages.forEach(p => p.isSelected = false);
|
||||
const pages = this.pages.concat(page);
|
||||
|
||||
this.updatePage({ pages, id });
|
||||
},
|
||||
addSection() {
|
||||
const sectionTitle = this.sectionTitle;
|
||||
const id = uuid();
|
||||
const section = {
|
||||
id,
|
||||
isDefault : false,
|
||||
isSelected: true,
|
||||
name : `Unnamed ${sectionTitle}`,
|
||||
pages : [],
|
||||
sectionTitle
|
||||
};
|
||||
|
||||
this.sections.forEach(s => s.isSelected = false);
|
||||
const sections = this.sections.concat(section);
|
||||
|
||||
this.updateSection({ sections, id });
|
||||
},
|
||||
toggleNav() {
|
||||
this.$emit('toggleNav');
|
||||
},
|
||||
updatePage({ pages, id }) {
|
||||
this.$emit('updatePage', { pages, id });
|
||||
},
|
||||
updateSection({ sections, id }) {
|
||||
this.$emit('updateSection', { sections, id });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -3,10 +3,10 @@
|
||||
<div class="c-notebook-snapshot__header l-browse-bar">
|
||||
<div class="l-browse-bar__start">
|
||||
<div class="l-browse-bar__object-name--w">
|
||||
<span class="l-browse-bar__object-name"
|
||||
<span class="c-object-label l-browse-bar__object-name"
|
||||
v-bind:class="embed.cssClass"
|
||||
>
|
||||
{{embed.name}}
|
||||
<span class="c-object-label__name">{{ embed.name }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -21,9 +21,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-notebook-snapshot__image">
|
||||
<div class="image-main s-image-main"
|
||||
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
|
||||
></div>
|
||||
<div class="c-notebook-snapshot__image"
|
||||
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
|
||||
>
|
||||
</div>
|
||||
</div>
|
3
src/plugins/notebook/notebook-constants.js
Normal file
3
src/plugins/notebook/notebook-constants.js
Normal file
@ -0,0 +1,3 @@
|
||||
export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED';
|
||||
export const NOTEBOOK_DEFAULT = 'DEFAULT';
|
||||
export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT';
|
@ -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: '<NotebookSnapshotIndicator></NotebookSnapshotIndicator>'
|
||||
});
|
||||
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: '<Notebook></Notebook>'
|
||||
});
|
||||
},
|
||||
destroy() {
|
||||
component.$destroy();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
<div class="c-ne__embed">
|
||||
<div class="c-ne__embed__snap-thumb"
|
||||
v-if="embed.snapshot"
|
||||
v-on:click="openSnapshot(domainObject, entry, embed)">
|
||||
<img v-bind:src="embed.snapshot.src">
|
||||
</div>
|
||||
<div class="c-ne__embed__info">
|
||||
<div class="c-ne__embed__name">
|
||||
<a class="c-ne__embed__link"
|
||||
:href="objectLink"
|
||||
:class="embed.cssClass">{{embed.name}}</a>
|
||||
<a class="c-ne__embed__context-available icon-arrow-down"
|
||||
@click="toggleActionMenu"></a>
|
||||
</div>
|
||||
<div class="hide-menu hidden">
|
||||
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
||||
<div class="c-menu">
|
||||
<ul>
|
||||
<li v-for="action in actions"
|
||||
:key="action.name"
|
||||
:class="action.cssClass"
|
||||
@click="action.perform(embed, entry)">
|
||||
{{ action.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-ne__embed__time" v-if="embed.snapshot">
|
||||
{{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,34 +0,0 @@
|
||||
<li class="c-notebook__entry c-ne has-local-controls"
|
||||
@drop.prevent="dropOnEntry(entry.id, $event)">
|
||||
<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>
|
||||
</div>
|
||||
<div class="c-ne__content">
|
||||
<div class="c-ne__text c-input-inline"
|
||||
contenteditable="true"
|
||||
ref="contenteditable"
|
||||
@blur="textBlur($event, entry.id)"
|
||||
@focus="textFocus($event, entry.id)"
|
||||
v-html="entry.text">
|
||||
</div>
|
||||
<div class="c-ne__embeds">
|
||||
<notebook-embed
|
||||
v-for="embed in entry.embeds"
|
||||
:key="embed.id"
|
||||
:embed="embed"
|
||||
:objectPath="embed.objectPath"
|
||||
:entry="entry">
|
||||
</notebook-embed>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-ne__local-controls--hidden">
|
||||
<button class="c-icon-button c-icon-button--major icon-trash"
|
||||
title="Delete this entry"
|
||||
@click="deleteEntry">
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
@ -1,35 +0,0 @@
|
||||
<div class="c-notebook">
|
||||
<div class="c-notebook__head">
|
||||
<search class="c-notebook__search"
|
||||
:value="entrySearch"
|
||||
@input="search"
|
||||
@clear="search">
|
||||
</search>
|
||||
<div class="c-notebook__controls ">
|
||||
<select class="c-notebook__controls__time" v-model="showTime">
|
||||
<option value="0" selected="selected">Show all</option>
|
||||
<option value="1">Last hour</option>
|
||||
<option value="8">Last 8 hours</option>
|
||||
<option value="24">Last 24 hours</option>
|
||||
</select>
|
||||
<select class="c-notebook__controls__time" v-model="sortEntries">
|
||||
<option value="newest" :selected="sortEntries === 'newest'">Newest first</option>
|
||||
<option value="oldest" :selected="sortEntries === 'oldest'">Oldest first</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-notebook__drag-area icon-plus"
|
||||
@click="newEntry($event)"
|
||||
@drop="newEntry($event)">
|
||||
<span class="c-notebook__drag-area__label">To start a new entry, click here or drag and drop any object</span>
|
||||
</div>
|
||||
<div class="c-notebook__entries">
|
||||
<ul>
|
||||
<notebook-entry
|
||||
v-for="(entry,index) in filteredAndSortedEntries"
|
||||
:key="entry.key"
|
||||
:entry="entry">
|
||||
</notebook-entry>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
@ -1,50 +0,0 @@
|
||||
<div class="abs overlay l-large-view">
|
||||
<div class="abs blocker" v-on:click="close"></div>
|
||||
|
||||
<div class="abs outer-holder">
|
||||
|
||||
<a
|
||||
class="close icon-x-in-circle"
|
||||
v-on:click="close">
|
||||
</a>
|
||||
|
||||
<div class="abs inner-holder l-flex-col">
|
||||
<div class="t-contents flex-elem holder grows">
|
||||
|
||||
<div class="t-snapshot abs l-view-header">
|
||||
<div class="abs object-browse-bar l-flex-row">
|
||||
<div class="left flex-elem l-flex-row grows">
|
||||
<div class="object-header flex-elem l-flex-row grows">
|
||||
<div class="type-icon flex-elem embed-icon holder" v-bind:class="embed.cssClass"></div>
|
||||
<div class="title-label flex-elem holder flex-can-shrink">{{embed.name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
|
||||
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
|
||||
</div>
|
||||
<a class="s-button icon-pencil" title="Annotate">
|
||||
<span class="title-label">Annotate</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="abs object-holder t-image-holder s-image-holder">
|
||||
<div
|
||||
class="image-main s-image-main"
|
||||
v-bind:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="bottom-bar flex-elem holder"
|
||||
v-on:click="close">
|
||||
|
||||
<a class="t-done s-button major">Done</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
82
src/plugins/notebook/snapshot-container.js
Normal file
82
src/plugins/notebook/snapshot-container.js
Normal file
@ -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);
|
||||
}
|
||||
}
|
74
src/plugins/notebook/snapshot.js
Normal file
74
src/plugins/notebook/snapshot.js
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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: '<div id="snap-annotation"></div>'
|
||||
}),
|
||||
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;
|
||||
});
|
@ -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;
|
||||
});
|
@ -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;
|
||||
});
|
194
src/plugins/notebook/utils/notebook-entries.js
Normal file
194
src/plugins/notebook/utils/notebook-entries.js
Normal file
@ -0,0 +1,194 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
40
src/plugins/notebook/utils/notebook-storage.js
Normal file
40
src/plugins/notebook/utils/notebook-storage.js
Normal file
@ -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));
|
||||
}
|
45
src/plugins/notebook/utils/popup-menu.js
Normal file
45
src/plugins/notebook/utils/popup-menu.js
Normal file
@ -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);
|
||||
}
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -26,7 +26,7 @@
|
||||
$mobileListIconSize: 30px;
|
||||
$mobileTitleDescH: 35px;
|
||||
$mobileOverlayMargin: 20px;
|
||||
$mobileMenuIconD: 34px;
|
||||
$mobileMenuIconD: 25px;
|
||||
$phoneItemH: floor($gridItemMobile / 4);
|
||||
$tabletItemH: floor($gridItemMobile / 3);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
@ -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.
|
||||
@ -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;
|
||||
|
@ -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); }
|
||||
|
@ -80,4 +80,8 @@
|
||||
&__object-name {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
&__object-details {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Binary file not shown.
Binary file not shown.
@ -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));
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -29,7 +29,7 @@
|
||||
>
|
||||
<div class="c-so-view__header">
|
||||
<div class="c-object-label"
|
||||
:class="cssClass"
|
||||
:class="[cssClass, classList]"
|
||||
>
|
||||
<div class="c-object-label__name">
|
||||
{{ domainObject && domainObject.name }}
|
||||
@ -99,6 +99,16 @@ export default {
|
||||
complexContent
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
classList() {
|
||||
const classList = this.domainObject.classList;
|
||||
if (!classList || !classList.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return classList.join(' ');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
expand() {
|
||||
let objectView = this.$refs.objectView,
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<a
|
||||
class="c-tree__item__label c-object-label"
|
||||
:class="classList"
|
||||
draggable="true"
|
||||
:href="objectLink"
|
||||
@dragstart="dragStart"
|
||||
@ -43,6 +44,14 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classList() {
|
||||
const classList = this.observedObject.classList;
|
||||
if (!classList || !classList.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return classList.join(' ');
|
||||
},
|
||||
typeClass() {
|
||||
let type = this.openmct.types.get(this.observedObject.type);
|
||||
if (!type) {
|
||||
|
@ -47,6 +47,13 @@ export default {
|
||||
)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(inputValue) {
|
||||
if (!inputValue.length) {
|
||||
this.clearInput();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearInput() {
|
||||
// Clear the user's input and set 'active' to false
|
||||
|
@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="u-contents"></div>
|
||||
</template>
|
||||
|
@ -1,13 +1,13 @@
|
||||
.c-path,
|
||||
.c-location {
|
||||
// Path is two or more items, not clickable
|
||||
// Location used in Inspector, is clickable
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__item {
|
||||
$m: $interiorMarginSm;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 $m $m 0;
|
||||
min-width: 0;
|
||||
|
||||
&:not(:last-child) {
|
||||
&:after {
|
||||
@ -15,10 +15,19 @@
|
||||
content: $glyph-icon-arrow-right;
|
||||
font-family: symbolsfont;
|
||||
font-size: 0.7em;
|
||||
margin-left: $m;
|
||||
margin-left: $interiorMarginSm;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-location {
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__item {
|
||||
cursor: pointer;
|
||||
margin: 0 $interiorMarginSm $interiorMarginSm 0;
|
||||
|
||||
.c-object-label {
|
||||
padding: 0;
|
||||
|
@ -8,7 +8,7 @@
|
||||
></button>
|
||||
<div
|
||||
class="l-browse-bar__object-name--w c-object-label"
|
||||
:class="type.cssClass"
|
||||
:class="[ type.cssClass, classList ]"
|
||||
>
|
||||
<span
|
||||
class="l-browse-bar__object-name c-object-label__name c-input-inline"
|
||||
@ -33,13 +33,11 @@
|
||||
@setView="setView"
|
||||
/>
|
||||
<!-- Action buttons -->
|
||||
<NotebookMenuSwitcher v-if="notebookEnabled"
|
||||
:domain-object="domainObject"
|
||||
class="c-notebook-snapshot-menubutton"
|
||||
/>
|
||||
<div class="l-browse-bar__actions">
|
||||
<button
|
||||
v-if="notebookEnabled"
|
||||
class="l-browse-bar__actions__notebook-entry c-button icon-notebook"
|
||||
title="New Notebook entry"
|
||||
@click="snapshot()"
|
||||
></button>
|
||||
<button
|
||||
v-if="isViewEditable & !isEditing"
|
||||
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
|
||||
@ -91,26 +89,37 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NotebookSnapshot from '../utils/notebook-snapshot';
|
||||
import ViewSwitcher from './ViewSwitcher.vue';
|
||||
import NotebookMenuSwitcher from '@/plugins/notebook/components/notebook-menu-switcher.vue';
|
||||
|
||||
const PLACEHOLDER_OBJECT = {};
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
NotebookMenuSwitcher,
|
||||
ViewSwitcher
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
notebookTypes: [],
|
||||
showViewMenu: false,
|
||||
showSaveMenu: false,
|
||||
domainObject: PLACEHOLDER_OBJECT,
|
||||
viewKey: undefined,
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
notebookEnabled: false
|
||||
notebookEnabled: this.openmct.types.get('notebook')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
classList() {
|
||||
const classList = this.domainObject.classList;
|
||||
if (!classList || !classList.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return classList.join(' ');
|
||||
},
|
||||
currentView() {
|
||||
return this.views.filter(v => v.key === this.viewKey)[0] || {};
|
||||
},
|
||||
@ -163,12 +172,6 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
|
||||
if (this.openmct.types.get('notebook')) {
|
||||
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
|
||||
this.notebookEnabled = true;
|
||||
}
|
||||
|
||||
document.addEventListener('click', this.closeViewAndSaveMenu);
|
||||
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
|
||||
|
||||
@ -266,10 +269,6 @@ export default {
|
||||
showContextMenu(event) {
|
||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.openmct.router.path, event.clientX, event.clientY);
|
||||
},
|
||||
snapshot() {
|
||||
let element = document.getElementsByClassName("l-shell__main-container")[0];
|
||||
this.notebookSnapshot.capture(this.domainObject, element);
|
||||
},
|
||||
goToParent() {
|
||||
window.location.hash = this.parentUrl;
|
||||
}
|
||||
|
@ -34,6 +34,9 @@
|
||||
</div>
|
||||
<app-logo />
|
||||
</div>
|
||||
|
||||
<div class="l-shell__drawer c-drawer c-drawer--push c-drawer--align-top"></div>
|
||||
|
||||
<multipane
|
||||
class="l-shell__main"
|
||||
type="horizontal"
|
||||
|
@ -6,6 +6,21 @@
|
||||
flex-flow: column nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&__drawer {
|
||||
background: $drawerBg;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
max-height: 15%;
|
||||
overflow: hidden;
|
||||
transition: $transIn;
|
||||
|
||||
&.is-expanded {
|
||||
height: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
&__pane-tree {
|
||||
width: 40%;
|
||||
|
||||
@ -15,7 +30,7 @@
|
||||
@include cClickIconButton();
|
||||
color: $colorKey !important;
|
||||
position: absolute;
|
||||
right: -2 * nth($shellPanePad, 2); // Needs to be -1 * when pane is collapsed
|
||||
right: -18px;
|
||||
top: 0;
|
||||
transform: translateX(100%);
|
||||
width: $mobileMenuIconD;
|
||||
@ -51,13 +66,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__pane-main {
|
||||
.l-pane__header { display: none; }
|
||||
}
|
||||
|
||||
body.mobile & {
|
||||
&__pane-main,
|
||||
&__pane-tree {
|
||||
padding: $interiorMarginLg;
|
||||
}
|
||||
|
||||
&__pane-tree {
|
||||
background: linear-gradient(90deg, transparent 70%, rgba(black, 0.2) 99%, rgba(black, 0.3));
|
||||
|
||||
&[class*="--collapsed"] {
|
||||
[class*="collapse-button"] {
|
||||
right: -1 * nth($shellPanePad, 2);
|
||||
right: -8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -190,14 +214,14 @@
|
||||
|
||||
&__main {
|
||||
> .l-pane {
|
||||
padding: nth($shellPanePad, 1) nth($shellPanePad, 2);
|
||||
padding: nth($shellPanePad, 1) 0;
|
||||
}
|
||||
}
|
||||
|
||||
body.desktop & {
|
||||
&__main {
|
||||
// Top and bottom padding in container that holds tree, __pane-main and Inspector
|
||||
padding: $shellMainPad;
|
||||
padding: nth($shellPanePad, 1) 0;
|
||||
min-height: 0;
|
||||
|
||||
> .l-pane {
|
||||
@ -208,15 +232,17 @@
|
||||
|
||||
&__pane-tree,
|
||||
&__pane-inspector {
|
||||
max-width: 30%;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
&__pane-tree {
|
||||
width: 300px;
|
||||
padding-left: nth($shellPanePad, 2);
|
||||
}
|
||||
|
||||
&__pane-inspector {
|
||||
width: 200px;
|
||||
padding-right: nth($shellPanePad, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,3 +268,68 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-drawer {
|
||||
/* New sliding overlay or push element to contain things
|
||||
* Designed for mobile and compact desktop scenarios
|
||||
* Variations:
|
||||
* --overlays: position absolute, overlays neighboring elements
|
||||
* --push: position relative, pushs/collapses neighboring elements
|
||||
* --align-left, align-top: opens from left or top respectively
|
||||
* &.is-expanded: applied when expanded.
|
||||
*/
|
||||
|
||||
transition: $transOut;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&:not(.is-expanded) {
|
||||
// When collapsed, hide internal elements
|
||||
> * {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.c-drawer--align-left {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.c-drawer--align-top {
|
||||
// Need anything here?
|
||||
}
|
||||
|
||||
&.c-drawer--overlays {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
|
||||
&.is-expanded {
|
||||
// Height and width must be set per usage
|
||||
&.c-drawer--align-left {
|
||||
box-shadow: rgba(black, 0.7) 3px 0 20px;
|
||||
}
|
||||
|
||||
&.c-drawer--align-top {
|
||||
box-shadow: rgba(black, 0.7) 0 3px 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.c-drawer--push {
|
||||
position: relative;
|
||||
|
||||
&.is-expanded {
|
||||
// Height and width must be set per usage
|
||||
&.c-drawer--align-left {
|
||||
box-shadow: rgba(black, 0.2) 3px 0 20px;
|
||||
margin-right: $interiorMarginLg;
|
||||
}
|
||||
|
||||
&.c-drawer--align-top {
|
||||
box-shadow: rgba(black, 0.2) 0 3px 20px;
|
||||
margin-bottom: $interiorMarginLg; // Not sure this is desired here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
padding-right: $interiorMarginSm;
|
||||
//TODO: Do we need this???
|
||||
//padding-right: $interiorMarginSm;
|
||||
overflow: auto;
|
||||
|
||||
> * + * { margin-top: $interiorMargin; }
|
||||
@ -23,22 +24,22 @@
|
||||
&__tree {
|
||||
flex: 1 1 auto;
|
||||
height: 0; // Chrome 73 overflow bug fix
|
||||
padding-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
.c-tree {
|
||||
.c-tree,
|
||||
.c-list {
|
||||
@include userSelectNone();
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding-right: $interiorMargin;
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
&.c-tree__item-h { display: block; }
|
||||
}
|
||||
|
||||
.c-tree {
|
||||
margin-left: 15px;
|
||||
&[class*="__item-h"] { display: block; }
|
||||
+ li {
|
||||
margin-top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
@ -48,20 +49,13 @@
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
line-height: 110%;
|
||||
padding: $interiorMargin - $aPad;
|
||||
padding: $interiorMarginSm $interiorMargin;
|
||||
transition: background 150ms ease;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $colorItemTreeHoverBg;
|
||||
.c-tree__item__type-icon:before {
|
||||
color: $colorItemTreeIconHover;
|
||||
}
|
||||
|
||||
.c-tree__item__name {
|
||||
[class*="__name"] {
|
||||
color: $colorItemTreeHoverFg;
|
||||
}
|
||||
}
|
||||
@ -69,12 +63,34 @@
|
||||
&.is-navigated-object,
|
||||
&.is-selected {
|
||||
background: $colorItemTreeSelectedBg;
|
||||
|
||||
[class*="__name"] {
|
||||
color: $colorItemTreeSelectedFg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-tree {
|
||||
.c-tree {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
&__item {
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.c-tree__item__type-icon:before {
|
||||
color: $colorItemTreeIconHover;
|
||||
}
|
||||
}
|
||||
|
||||
.c-tree__item__name {
|
||||
color: $colorItemTreeSelectedFg;
|
||||
&.is-navigated-object,
|
||||
&.is-selected {
|
||||
.c-tree__item__type-icon:before {
|
||||
color: $colorItemTreeIconHover;
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,6 +137,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.c-list {
|
||||
padding-right: $interiorMargin;
|
||||
|
||||
&__item {
|
||||
&__name {
|
||||
$p: $interiorMarginSm;
|
||||
@include ellipsize();
|
||||
padding-bottom: $p;
|
||||
padding-top: $p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-selector {
|
||||
.c-tree-and-search__tree.c-tree {
|
||||
border: 1px solid $colorInteriorBorder;
|
||||
|
@ -2,6 +2,7 @@
|
||||
.l-multipane {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
|
||||
&--horizontal,
|
||||
> .l-pane {
|
||||
@ -38,7 +39,7 @@
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: $interiorMargin;
|
||||
@include desktop() { margin-bottom: $interiorMargin; }
|
||||
}
|
||||
|
||||
&--reacts {
|
||||
@ -232,8 +233,11 @@
|
||||
}
|
||||
|
||||
/************************** Horizontal Splitter Before */
|
||||
// Inspector pane
|
||||
// Example: Inspector pane
|
||||
&[class*="-before"] {
|
||||
margin-left: nth($shellPanePad, 2);
|
||||
padding-left: nth($shellPanePad, 2);
|
||||
|
||||
> .l-pane__handle {
|
||||
left: 0;
|
||||
transform: translateX(floor($splitterHandleD / -2)); // Center over the pane edge
|
||||
@ -247,8 +251,11 @@
|
||||
}
|
||||
|
||||
/************************** Horizontal Splitter After */
|
||||
// Tree pane
|
||||
// Example: Tree pane
|
||||
&[class*="-after"] {
|
||||
margin-right: nth($shellPanePad, 2);
|
||||
padding-right: nth($shellPanePad, 2);
|
||||
|
||||
> .l-pane__handle {
|
||||
right: 0;
|
||||
transform: translateX(floor($splitterHandleD / 2));
|
||||
|
@ -16,11 +16,10 @@
|
||||
class="l-pane__handle"
|
||||
@mousedown="start"
|
||||
></div>
|
||||
<div
|
||||
v-if="label"
|
||||
class="l-pane__header"
|
||||
>
|
||||
<span class="l-pane__label">{{ label }}</span>
|
||||
<div class="l-pane__header">
|
||||
<span v-if="label"
|
||||
class="l-pane__label"
|
||||
>{{ label }}</span>
|
||||
<button
|
||||
v-if="collapsable"
|
||||
class="l-pane__collapse-button c-button"
|
||||
|
@ -1,14 +1,12 @@
|
||||
.c-indicator {
|
||||
@include cControl();
|
||||
@include cClickIconButtonLayout();
|
||||
button { text-transform: uppercase; }
|
||||
|
||||
background: none !important;
|
||||
border-radius: $controlCr;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
text-transform: uppercase;
|
||||
|
||||
button { text-transform: uppercase; }
|
||||
|
||||
&.no-minify {
|
||||
// For items that cannot be minified
|
||||
@ -42,7 +40,7 @@
|
||||
|
||||
a,
|
||||
button,
|
||||
s-button,
|
||||
.s-button,
|
||||
.c-button {
|
||||
// Make <a> in label look like buttons
|
||||
transition: $transIn;
|
||||
|
@ -57,8 +57,8 @@
|
||||
|
||||
<script>
|
||||
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
|
||||
import Snapshot from '@/plugins/notebook/snapshot';
|
||||
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
|
||||
import NotebookSnapshot from '../utils/notebook-snapshot';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -96,7 +96,7 @@ export default {
|
||||
this.setView(view);
|
||||
|
||||
if (this.openmct.types.get('notebook')) {
|
||||
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
|
||||
this.notebookSnapshot = new Snapshot(this.openmct);
|
||||
this.notebookEnabled = true;
|
||||
}
|
||||
},
|
||||
|
@ -9,22 +9,31 @@ define([
|
||||
let browseObject;
|
||||
let unobserve = undefined;
|
||||
let currentObjectPath;
|
||||
let isRoutingInProgress = false;
|
||||
|
||||
openmct.router.route(/^\/browse\/?$/, navigateToFirstChildOfRoot);
|
||||
|
||||
openmct.router.route(/^\/browse\/(.*)$/, (path, results, params) => {
|
||||
isRoutingInProgress = true;
|
||||
let navigatePath = results[1];
|
||||
navigateToPath(navigatePath, params.view);
|
||||
onParamsChanged(null, null, params);
|
||||
});
|
||||
|
||||
openmct.router.on('change:params', function (newParams, oldParams, changed) {
|
||||
openmct.router.on('change:params', onParamsChanged);
|
||||
|
||||
function onParamsChanged(newParams, oldParams, changed) {
|
||||
if (isRoutingInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changed.view && browseObject) {
|
||||
let provider = openmct
|
||||
.objectViews
|
||||
.getByProviderKey(changed.view);
|
||||
viewObject(browseObject, provider);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function viewObject(object, viewProvider) {
|
||||
currentObjectPath = openmct.router.path;
|
||||
@ -49,6 +58,8 @@ define([
|
||||
}
|
||||
|
||||
return pathToObjects(path).then((objects)=>{
|
||||
isRoutingInProgress = false;
|
||||
|
||||
if (currentNavigation !== navigateCall) {
|
||||
return; // Prevent race.
|
||||
}
|
||||
|
@ -1,135 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
class NotebookSnapshot {
|
||||
constructor(openmct) {
|
||||
this.openmct = 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(domainObject, domElement) {
|
||||
|
||||
let type = this.openmct.types.get(domainObject.type),
|
||||
embedObject = {
|
||||
id: domainObject.identifier.key,
|
||||
cssClass: type.cssClass,
|
||||
name: domainObject.name
|
||||
};
|
||||
|
||||
this.exportImageService.exportPNGtoSRC(domElement, 's-status-taking-snapshot').then(function (blob) {
|
||||
|
||||
var reader = new window.FileReader();
|
||||
reader.readAsDataURL(blob);
|
||||
reader.onloadend = function () {
|
||||
this._saveSnapShot(reader.result, embedObject);
|
||||
}.bind(this);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_generateTaskForm(url) {
|
||||
var taskForm = {
|
||||
name: "Create a Notebook Entry",
|
||||
hint: "Please select a Notebook",
|
||||
sections: [{
|
||||
rows: [
|
||||
{
|
||||
name: 'Entry',
|
||||
key: 'entry',
|
||||
control: 'textarea',
|
||||
required: false,
|
||||
cssClass: "l-textarea-sm"
|
||||
},
|
||||
{
|
||||
name: 'Snap Preview',
|
||||
key:"snapPreview",
|
||||
control: "snap-view",
|
||||
cssClass: "l-textarea-sm",
|
||||
src: url
|
||||
},
|
||||
{
|
||||
name: 'Save in Notebook',
|
||||
key: 'notebook',
|
||||
control: 'locator',
|
||||
validate: validateLocation
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
|
||||
var overlayModel = {
|
||||
title: taskForm.name,
|
||||
message: 'Notebook Snapshot',
|
||||
structure: taskForm,
|
||||
value: {'entry': ""}
|
||||
};
|
||||
|
||||
function validateLocation(newParentObj) {
|
||||
return newParentObj.model.type === 'notebook';
|
||||
}
|
||||
|
||||
return overlayModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_saveSnapShot(imageUrl, embedObject) {
|
||||
let taskForm = this._generateTaskForm(imageUrl);
|
||||
|
||||
this.dialogService.getDialogResponse(
|
||||
'overlay-dialog',
|
||||
taskForm,
|
||||
() => taskForm.value
|
||||
).then(options => {
|
||||
let snapshotObject = {
|
||||
src: options.snapPreview || imageUrl
|
||||
};
|
||||
|
||||
options.notebook.useCapability('mutation', function (model) {
|
||||
var date = Date.now();
|
||||
|
||||
model.entries.push({
|
||||
id: 'entry-' + date,
|
||||
createdOn: date,
|
||||
text: options.entry,
|
||||
embeds: [{
|
||||
name: embedObject.name,
|
||||
cssClass: embedObject.cssClass,
|
||||
type: embedObject.id,
|
||||
id: 'embed-' + date,
|
||||
createdOn: date,
|
||||
snapshot: snapshotObject
|
||||
}]
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default NotebookSnapshot;
|
Loading…
Reference in New Issue
Block a user