mirror of
https://github.com/nasa/openmct.git
synced 2025-02-02 01:08:13 +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
|
as represented by the Administrator of the National Aeronautics and Space
|
||||||
Administration. All rights reserved.
|
Administration. All rights reserved.
|
||||||
|
|
||||||
|
@ -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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -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>
|
<template>
|
||||||
<div class="u-contents">
|
<div class="u-contents">
|
||||||
<div class="c-cdef__separator c-row-separator"></div>
|
<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 {
|
.c-cs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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-condition,
|
||||||
.c-test-datum {
|
.c-test-datum {
|
||||||
@include discreteItem();
|
@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>
|
<template>
|
||||||
<li class="c-tree__item-h">
|
<li class="c-tree__item-h">
|
||||||
<div
|
<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>
|
<template>
|
||||||
<div class="u-contents">
|
<div class="u-contents">
|
||||||
<div class="c-overlay__top-bar">
|
<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 */
|
/********************************************* INSPECTOR STYLES TAB */
|
||||||
.c-inspect-styles {
|
.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 = {
|
export const TRIGGER = {
|
||||||
ANY: 'any',
|
ANY: 'any',
|
||||||
ALL: 'all',
|
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) => {
|
export const computeCondition = (resultMap, allMustBeTrue) => {
|
||||||
let result = false;
|
let result = false;
|
||||||
for (let key in resultMap) {
|
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';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export const OPERATIONS = [
|
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) => {
|
export const getStyleProp = (key, defaultValue) => {
|
||||||
let styleProp = undefined;
|
let styleProp = undefined;
|
||||||
switch(key) {
|
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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* 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>
|
<template>
|
||||||
<a class="c-condition-widget"
|
<a class="c-condition-widget"
|
||||||
:href="internalDomainObject.url"
|
: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 {
|
.c-condition-widget {
|
||||||
$shdwSize: 3px;
|
$shdwSize: 3px;
|
||||||
@include userSelectNone();
|
@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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* 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";
|
import StyleRuleManager from "@/plugins/condition/StyleRuleManager";
|
||||||
|
|
||||||
export default {
|
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="c-notebook-snapshot__header l-browse-bar">
|
||||||
<div class="l-browse-bar__start">
|
<div class="l-browse-bar__start">
|
||||||
<div class="l-browse-bar__object-name--w">
|
<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"
|
v-bind:class="embed.cssClass"
|
||||||
>
|
>
|
||||||
{{embed.name}}
|
<span class="c-object-label__name">{{ embed.name }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -21,9 +21,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="c-notebook-snapshot__image">
|
<div class="c-notebook-snapshot__image"
|
||||||
<div class="image-main s-image-main"
|
|
||||||
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
|
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
|
||||||
></div>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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,33 +1,11 @@
|
|||||||
/*****************************************************************************
|
import Notebook from './components/notebook.vue';
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
import NotebookSnapshotIndicator from './components/notebook-snapshot-indicator.vue';
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
import SnapshotContainer from './snapshot-container';
|
||||||
* Administration. All rights reserved.
|
import Vue from 'vue';
|
||||||
*
|
|
||||||
* 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([
|
let installed = false;
|
||||||
"./src/controllers/NotebookController"
|
|
||||||
], function (
|
|
||||||
NotebookController
|
|
||||||
) {
|
|
||||||
var installed = false;
|
|
||||||
|
|
||||||
function NotebookPlugin() {
|
export default function NotebookPlugin() {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
if (installed) {
|
if (installed) {
|
||||||
return;
|
return;
|
||||||
@ -35,25 +13,25 @@ define([
|
|||||||
|
|
||||||
installed = true;
|
installed = true;
|
||||||
|
|
||||||
openmct.legacyRegistry.register('notebook', {
|
const notebookType = {
|
||||||
name: 'Notebook Plugin',
|
|
||||||
extensions: {
|
|
||||||
types: [
|
|
||||||
{
|
|
||||||
key: 'notebook',
|
|
||||||
name: 'Notebook',
|
name: 'Notebook',
|
||||||
cssClass: 'icon-notebook',
|
|
||||||
description: 'Create and save timestamped notes with embedded object snapshots.',
|
description: 'Create and save timestamped notes with embedded object snapshots.',
|
||||||
features: 'creation',
|
creatable: true,
|
||||||
model: {
|
cssClass: 'icon-notebook',
|
||||||
entries: [],
|
initialize: domainObject => {
|
||||||
entryTypes: [],
|
domainObject.configuration = {
|
||||||
defaultSort: 'oldest'
|
defaultSort: 'oldest',
|
||||||
|
entries: {},
|
||||||
|
pageTitle: 'Page',
|
||||||
|
sections: [],
|
||||||
|
sectionTitle: 'Section',
|
||||||
|
type: 'General'
|
||||||
|
};
|
||||||
},
|
},
|
||||||
properties: [
|
form: [
|
||||||
{
|
{
|
||||||
key: 'defaultSort',
|
key: 'defaultSort',
|
||||||
name: 'Default Sort',
|
name: 'Entry Sorting',
|
||||||
control: 'select',
|
control: 'select',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
@ -65,15 +43,62 @@ define([
|
|||||||
value: "oldest"
|
value: "oldest"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
cssClass: 'l-inline'
|
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');
|
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);
|
||||||
|
|
||||||
openmct.objectViews.addProvider({
|
openmct.objectViews.addProvider({
|
||||||
key: 'notebook-vue',
|
key: 'notebook-vue',
|
||||||
@ -83,17 +108,27 @@ define([
|
|||||||
return domainObject.type === 'notebook';
|
return domainObject.type === 'notebook';
|
||||||
},
|
},
|
||||||
view: function (domainObject) {
|
view: function (domainObject) {
|
||||||
var controller = new NotebookController (openmct, domainObject);
|
let component;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
show: controller.show,
|
show(container) {
|
||||||
destroy: controller.destroy
|
component = new Vue({
|
||||||
|
el: container,
|
||||||
|
components: {
|
||||||
|
Notebook
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
domainObject,
|
||||||
|
snapshotContainer
|
||||||
|
},
|
||||||
|
template: '<Notebook></Notebook>'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
component.$destroy();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotebookPlugin;
|
|
||||||
});
|
|
||||||
|
@ -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.SummaryWidget = SummaryWidget;
|
||||||
plugins.TelemetryMean = TelemetryMean;
|
plugins.TelemetryMean = TelemetryMean;
|
||||||
plugins.URLIndicator = URLIndicatorPlugin;
|
plugins.URLIndicator = URLIndicatorPlugin;
|
||||||
plugins.Notebook = Notebook;
|
plugins.Notebook = Notebook.default;
|
||||||
plugins.DisplayLayout = DisplayLayoutPlugin.default;
|
plugins.DisplayLayout = DisplayLayoutPlugin.default;
|
||||||
plugins.FolderView = FolderView;
|
plugins.FolderView = FolderView;
|
||||||
plugins.Tabs = Tabs;
|
plugins.Tabs = Tabs;
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
export default class RemoveAction {
|
export default class RemoveAction {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
this.name = 'Remove';
|
this.name = 'Remove';
|
||||||
@ -90,6 +89,13 @@ export default class RemoveAction {
|
|||||||
if (this.inNavigationPath(child) && this.openmct.editor.isEditing()) {
|
if (this.inNavigationPath(child) && this.openmct.editor.isEditing()) {
|
||||||
this.openmct.editor.save();
|
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) {
|
appliesTo(objectPath) {
|
||||||
|
@ -87,6 +87,11 @@ $colorSelectedFg: pullForward($colorBodyFg, 20%);
|
|||||||
// Layout
|
// Layout
|
||||||
$shellMainPad: 4px 0;
|
$shellMainPad: 4px 0;
|
||||||
$shellPanePad: $interiorMargin, 7px;
|
$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
|
// Status colors, mainly used for messaging and item ancillary symbols
|
||||||
$colorStatusFg: #999;
|
$colorStatusFg: #999;
|
||||||
@ -333,7 +338,7 @@ $colorSummaryFg: rgba($colorBodyFg, 0.7);
|
|||||||
$colorSummaryFgEm: $colorBodyFg;
|
$colorSummaryFgEm: $colorBodyFg;
|
||||||
|
|
||||||
// Plot
|
// Plot
|
||||||
$colorPlotBg: rgba(black, 0.05);
|
$colorPlotBg: rgba(black, 0.1);
|
||||||
$colorPlotFg: $colorBodyFg;
|
$colorPlotFg: $colorBodyFg;
|
||||||
$colorPlotHash: black;
|
$colorPlotHash: black;
|
||||||
$opacityPlotHash: 0.2;
|
$opacityPlotHash: 0.2;
|
||||||
|
@ -91,6 +91,11 @@ $colorSelectedFg: pullForward($colorBodyFg, 20%);
|
|||||||
// Layout
|
// Layout
|
||||||
$shellMainPad: 4px 0;
|
$shellMainPad: 4px 0;
|
||||||
$shellPanePad: $interiorMargin, 7px;
|
$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
|
// Status colors, mainly used for messaging and item ancillary symbols
|
||||||
$colorStatusFg: #999;
|
$colorStatusFg: #999;
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
$mobileListIconSize: 30px;
|
$mobileListIconSize: 30px;
|
||||||
$mobileTitleDescH: 35px;
|
$mobileTitleDescH: 35px;
|
||||||
$mobileOverlayMargin: 20px;
|
$mobileOverlayMargin: 20px;
|
||||||
$mobileMenuIconD: 34px;
|
$mobileMenuIconD: 25px;
|
||||||
$phoneItemH: floor($gridItemMobile / 4);
|
$phoneItemH: floor($gridItemMobile / 4);
|
||||||
$tabletItemH: floor($gridItemMobile / 3);
|
$tabletItemH: floor($gridItemMobile / 3);
|
||||||
|
|
||||||
|
@ -87,6 +87,11 @@ $colorSelectedFg: pullForward($colorBodyFg, 10%);
|
|||||||
// Layout
|
// Layout
|
||||||
$shellMainPad: 4px 0;
|
$shellMainPad: 4px 0;
|
||||||
$shellPanePad: $interiorMargin, 7px;
|
$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
|
// Status colors, mainly used for messaging and item ancillary symbols
|
||||||
$colorStatusFg: #999;
|
$colorStatusFg: #999;
|
||||||
|
@ -147,6 +147,7 @@ $glyph-icon-suitcase: '\e928';
|
|||||||
$glyph-icon-cursor-lock: '\e929';
|
$glyph-icon-cursor-lock: '\e929';
|
||||||
$glyph-icon-flag: '\e92a';
|
$glyph-icon-flag: '\e92a';
|
||||||
$glyph-icon-eye-disabled: '\e92b';
|
$glyph-icon-eye-disabled: '\e92b';
|
||||||
|
$glyph-icon-notebook-page: '\e92c';
|
||||||
$glyph-icon-arrows-right-left: '\ea00';
|
$glyph-icon-arrows-right-left: '\ea00';
|
||||||
$glyph-icon-arrows-up-down: '\ea01';
|
$glyph-icon-arrows-up-down: '\ea01';
|
||||||
$glyph-icon-bullet: '\ea02';
|
$glyph-icon-bullet: '\ea02';
|
||||||
|
@ -54,7 +54,6 @@ button {
|
|||||||
background: $splitterBtnColorBg;
|
background: $splitterBtnColorBg;
|
||||||
color: $splitterBtnColorFg;
|
color: $splitterBtnColorFg;
|
||||||
border-radius: $smallCr;
|
border-radius: $smallCr;
|
||||||
font-size: 6px;
|
|
||||||
line-height: 90%;
|
line-height: 90%;
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
|
|
||||||
@ -63,6 +62,10 @@ button {
|
|||||||
color: $colorBtnFgHov;
|
color: $colorBtnFgHov;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include desktop() {
|
||||||
|
font-size: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: $glyph-icon-arrow-down;
|
content: $glyph-icon-arrow-down;
|
||||||
font-size: 1.1em;
|
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 CONTROLS */
|
||||||
/********* Disclosure Button */
|
/********* Disclosure Button */
|
||||||
// Provides a downward arrow icon that when clicked displays additional options and/or info.
|
// Provides a downward arrow icon that when clicked displays additional options and/or info.
|
||||||
@ -709,7 +732,7 @@ select {
|
|||||||
$pTB: 2px;
|
$pTB: 2px;
|
||||||
padding: $pTB $pLR;
|
padding: $pTB $pLR;
|
||||||
|
|
||||||
&:hover {
|
@include hover() {
|
||||||
background: $editUIBaseColorHov !important;
|
background: $editUIBaseColorHov !important;
|
||||||
color: $editUIBaseColorFg !important;
|
color: $editUIBaseColorFg !important;
|
||||||
}
|
}
|
||||||
@ -724,7 +747,7 @@ select {
|
|||||||
color: $colorBtnCautionBg;
|
color: $colorBtnCautionBg;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
@include hover() {
|
||||||
background: rgba($colorBtnCautionBgHov, 0.2);
|
background: rgba($colorBtnCautionBgHov, 0.2);
|
||||||
:before {
|
:before {
|
||||||
color: $colorBtnCautionBgHov;
|
color: $colorBtnCautionBgHov;
|
||||||
|
@ -83,6 +83,7 @@
|
|||||||
.icon-cursor-lock { @include glyphBefore($glyph-icon-cursor-lock); }
|
.icon-cursor-lock { @include glyphBefore($glyph-icon-cursor-lock); }
|
||||||
.icon-flag { @include glyphBefore($glyph-icon-flag); }
|
.icon-flag { @include glyphBefore($glyph-icon-flag); }
|
||||||
.icon-eye-disabled { @include glyphBefore($glyph-icon-eye-disabled); }
|
.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-right-left { @include glyphBefore($glyph-icon-arrows-right-left); }
|
||||||
.icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); }
|
.icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); }
|
||||||
.icon-bullet { @include glyphBefore($glyph-icon-bullet); }
|
.icon-bullet { @include glyphBefore($glyph-icon-bullet); }
|
||||||
|
@ -80,4 +80,8 @@
|
|||||||
&__object-name {
|
&__object-name {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__object-details {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
|
|
||||||
/************************** EFFECTS */
|
/************************** EFFECTS */
|
||||||
@mixin pulse($animName: pulse, $dur: 500ms, $iteration: infinite, $opacity0: 0.5, $opacity100: 1) {
|
@mixin pulse($animName: pulse, $dur: 500ms, $iteration: infinite, $opacity0: 0.5, $opacity100: 1) {
|
||||||
@keyframes pulse {
|
@keyframes #{$animName} {
|
||||||
0% { opacity: $opacity0; }
|
0% { opacity: $opacity0; }
|
||||||
100% { opacity: $opacity100; }
|
100% { opacity: $opacity100; }
|
||||||
}
|
}
|
||||||
@ -62,6 +62,18 @@
|
|||||||
animation-timing-function: ease-in-out;
|
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 */
|
/************************** VISUALS */
|
||||||
@mixin ancillaryIcon($d, $c) {
|
@mixin ancillaryIcon($d, $c) {
|
||||||
// Used for small icons used in combination with larger icons,
|
// Used for small icons used in combination with larger icons,
|
||||||
@ -426,10 +438,15 @@
|
|||||||
color: $colorBodyFg;
|
color: $colorBodyFg;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px; // Bigger hit area
|
padding: 4px; // Bigger hit area
|
||||||
opacity: 0.6;
|
opacity: 0.7;
|
||||||
transition: $transOut;
|
transition: $transOut;
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
|
|
||||||
|
&[class*="--major"] {
|
||||||
|
color: $colorBtnMajorBg !important;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
@include hover() {
|
@include hover() {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
transition: $transIn;
|
transition: $transIn;
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -22,34 +22,61 @@
|
|||||||
|
|
||||||
/*********************************************** NOTEBOOK */
|
/*********************************************** NOTEBOOK */
|
||||||
.c-notebook {
|
.c-notebook {
|
||||||
|
$headerFontSize: 1.3em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
height: 100%;
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
|
|
||||||
&-snapshot {
|
/****************************** CONTENT */
|
||||||
flex: 1 1 auto;
|
&__body {
|
||||||
|
// Holds __nav and __page-view
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
> * + * {
|
&__nav {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
* {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__image {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> [class*="__"] + [class*="__"] {
|
> * + * {
|
||||||
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 {
|
&__entries {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
padding-right: $interiorMarginSm;
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
@include desktop() {
|
||||||
|
padding-right: $interiorMarginSm; // Scrollbar kickoff
|
||||||
|
}
|
||||||
|
|
||||||
[class*="__entry"] + [class*="__entry"] {
|
[class*="__entry"] + [class*="__entry"] {
|
||||||
margin-top: $interiorMarginSm;
|
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 {
|
&__embeds {
|
||||||
flex-wrap: wrap;
|
//flex-wrap: wrap;
|
||||||
|
|
||||||
> [class*="__embed"] {
|
> [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 */
|
/****************************** EMBEDS */
|
||||||
@mixin snapThumb() {
|
@mixin snapThumb() {
|
||||||
// LEGACY: TODO: refactor when .snap-thumb in New Entry dialog is refactored
|
// LEGACY: TODO: refactor when .snap-thumb in New Entry dialog is refactored
|
||||||
$d: 50px;
|
$d: 30px;
|
||||||
border: 1px solid $colorInteriorBorder;
|
border: 1px solid $colorInteriorBorder;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: $d;
|
width: $d;
|
||||||
@ -269,6 +391,64 @@
|
|||||||
.l-sticky-headers .l-tabular-body { overflow: auto; }
|
.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 */
|
/****************************** PAINTERRO OVERRIDES */
|
||||||
.annotation-dialog .abs.editor {
|
.annotation-dialog .abs.editor {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@ -401,7 +581,8 @@ body.mobile {
|
|||||||
|
|
||||||
&.phone.portrait {
|
&.phone.portrait {
|
||||||
.c-notebook__head,
|
.c-notebook__head,
|
||||||
.c-notebook__entry {
|
.c-notebook__entry,
|
||||||
|
.c-ne__time-and-content {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
> [class*="__"] + [class*="__"] {
|
> [class*="__"] + [class*="__"] {
|
||||||
@ -413,9 +594,14 @@ body.mobile {
|
|||||||
.c-notebook__entry {
|
.c-notebook__entry {
|
||||||
[class*="text"] {
|
[class*="text"] {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
padding: 0;
|
|
||||||
pointer-events: none;
|
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/preview/preview.scss";
|
||||||
@import "../ui/toolbar/components/toolbar-checkbox.scss";
|
@import "../ui/toolbar/components/toolbar-checkbox.scss";
|
||||||
@import "./notebook.scss";
|
@import "./notebook.scss";
|
||||||
|
@import "../plugins/notebook/components/sidebar.scss";
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
>
|
>
|
||||||
<div class="c-so-view__header">
|
<div class="c-so-view__header">
|
||||||
<div class="c-object-label"
|
<div class="c-object-label"
|
||||||
:class="cssClass"
|
:class="[cssClass, classList]"
|
||||||
>
|
>
|
||||||
<div class="c-object-label__name">
|
<div class="c-object-label__name">
|
||||||
{{ domainObject && domainObject.name }}
|
{{ domainObject && domainObject.name }}
|
||||||
@ -99,6 +99,16 @@ export default {
|
|||||||
complexContent
|
complexContent
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
classList() {
|
||||||
|
const classList = this.domainObject.classList;
|
||||||
|
if (!classList || !classList.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return classList.join(' ');
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
expand() {
|
expand() {
|
||||||
let objectView = this.$refs.objectView,
|
let objectView = this.$refs.objectView,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
class="c-tree__item__label c-object-label"
|
class="c-tree__item__label c-object-label"
|
||||||
|
:class="classList"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
:href="objectLink"
|
:href="objectLink"
|
||||||
@dragstart="dragStart"
|
@dragstart="dragStart"
|
||||||
@ -43,6 +44,14 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
classList() {
|
||||||
|
const classList = this.observedObject.classList;
|
||||||
|
if (!classList || !classList.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return classList.join(' ');
|
||||||
|
},
|
||||||
typeClass() {
|
typeClass() {
|
||||||
let type = this.openmct.types.get(this.observedObject.type);
|
let type = this.openmct.types.get(this.observedObject.type);
|
||||||
if (!type) {
|
if (!type) {
|
||||||
|
@ -47,6 +47,13 @@ export default {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
value(inputValue) {
|
||||||
|
if (!inputValue.length) {
|
||||||
|
this.clearInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clearInput() {
|
clearInput() {
|
||||||
// Clear the user's input and set 'active' to false
|
// 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>
|
<template>
|
||||||
<div class="u-contents"></div>
|
<div class="u-contents"></div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
|
.c-path,
|
||||||
.c-location {
|
.c-location {
|
||||||
|
// Path is two or more items, not clickable
|
||||||
|
// Location used in Inspector, is clickable
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
$m: $interiorMarginSm;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 0 $m $m 0;
|
min-width: 0;
|
||||||
|
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
&:after {
|
&:after {
|
||||||
@ -15,10 +15,19 @@
|
|||||||
content: $glyph-icon-arrow-right;
|
content: $glyph-icon-arrow-right;
|
||||||
font-family: symbolsfont;
|
font-family: symbolsfont;
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
margin-left: $m;
|
margin-left: $interiorMarginSm;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-location {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 $interiorMarginSm $interiorMarginSm 0;
|
||||||
|
|
||||||
.c-object-label {
|
.c-object-label {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
></button>
|
></button>
|
||||||
<div
|
<div
|
||||||
class="l-browse-bar__object-name--w c-object-label"
|
class="l-browse-bar__object-name--w c-object-label"
|
||||||
:class="type.cssClass"
|
:class="[ type.cssClass, classList ]"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="l-browse-bar__object-name c-object-label__name c-input-inline"
|
class="l-browse-bar__object-name c-object-label__name c-input-inline"
|
||||||
@ -33,13 +33,11 @@
|
|||||||
@setView="setView"
|
@setView="setView"
|
||||||
/>
|
/>
|
||||||
<!-- Action buttons -->
|
<!-- Action buttons -->
|
||||||
|
<NotebookMenuSwitcher v-if="notebookEnabled"
|
||||||
|
:domain-object="domainObject"
|
||||||
|
class="c-notebook-snapshot-menubutton"
|
||||||
|
/>
|
||||||
<div class="l-browse-bar__actions">
|
<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
|
<button
|
||||||
v-if="isViewEditable & !isEditing"
|
v-if="isViewEditable & !isEditing"
|
||||||
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
|
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
|
||||||
@ -91,26 +89,37 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NotebookSnapshot from '../utils/notebook-snapshot';
|
|
||||||
import ViewSwitcher from './ViewSwitcher.vue';
|
import ViewSwitcher from './ViewSwitcher.vue';
|
||||||
|
import NotebookMenuSwitcher from '@/plugins/notebook/components/notebook-menu-switcher.vue';
|
||||||
|
|
||||||
const PLACEHOLDER_OBJECT = {};
|
const PLACEHOLDER_OBJECT = {};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
|
NotebookMenuSwitcher,
|
||||||
ViewSwitcher
|
ViewSwitcher
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
notebookTypes: [],
|
||||||
showViewMenu: false,
|
showViewMenu: false,
|
||||||
showSaveMenu: false,
|
showSaveMenu: false,
|
||||||
domainObject: PLACEHOLDER_OBJECT,
|
domainObject: PLACEHOLDER_OBJECT,
|
||||||
viewKey: undefined,
|
viewKey: undefined,
|
||||||
isEditing: this.openmct.editor.isEditing(),
|
isEditing: this.openmct.editor.isEditing(),
|
||||||
notebookEnabled: false
|
notebookEnabled: this.openmct.types.get('notebook')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
classList() {
|
||||||
|
const classList = this.domainObject.classList;
|
||||||
|
if (!classList || !classList.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return classList.join(' ');
|
||||||
|
},
|
||||||
currentView() {
|
currentView() {
|
||||||
return this.views.filter(v => v.key === this.viewKey)[0] || {};
|
return this.views.filter(v => v.key === this.viewKey)[0] || {};
|
||||||
},
|
},
|
||||||
@ -163,12 +172,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
|
||||||
if (this.openmct.types.get('notebook')) {
|
|
||||||
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
|
|
||||||
this.notebookEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('click', this.closeViewAndSaveMenu);
|
document.addEventListener('click', this.closeViewAndSaveMenu);
|
||||||
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
|
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
|
||||||
|
|
||||||
@ -266,10 +269,6 @@ export default {
|
|||||||
showContextMenu(event) {
|
showContextMenu(event) {
|
||||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.openmct.router.path, event.clientX, event.clientY);
|
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() {
|
goToParent() {
|
||||||
window.location.hash = this.parentUrl;
|
window.location.hash = this.parentUrl;
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<app-logo />
|
<app-logo />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="l-shell__drawer c-drawer c-drawer--push c-drawer--align-top"></div>
|
||||||
|
|
||||||
<multipane
|
<multipane
|
||||||
class="l-shell__main"
|
class="l-shell__main"
|
||||||
type="horizontal"
|
type="horizontal"
|
||||||
|
@ -6,6 +6,21 @@
|
|||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
overflow: hidden;
|
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 {
|
&__pane-tree {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
|
|
||||||
@ -15,7 +30,7 @@
|
|||||||
@include cClickIconButton();
|
@include cClickIconButton();
|
||||||
color: $colorKey !important;
|
color: $colorKey !important;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -2 * nth($shellPanePad, 2); // Needs to be -1 * when pane is collapsed
|
right: -18px;
|
||||||
top: 0;
|
top: 0;
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
width: $mobileMenuIconD;
|
width: $mobileMenuIconD;
|
||||||
@ -51,13 +66,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__pane-main {
|
||||||
|
.l-pane__header { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
body.mobile & {
|
body.mobile & {
|
||||||
|
&__pane-main,
|
||||||
|
&__pane-tree {
|
||||||
|
padding: $interiorMarginLg;
|
||||||
|
}
|
||||||
|
|
||||||
&__pane-tree {
|
&__pane-tree {
|
||||||
background: linear-gradient(90deg, transparent 70%, rgba(black, 0.2) 99%, rgba(black, 0.3));
|
background: linear-gradient(90deg, transparent 70%, rgba(black, 0.2) 99%, rgba(black, 0.3));
|
||||||
|
|
||||||
&[class*="--collapsed"] {
|
&[class*="--collapsed"] {
|
||||||
[class*="collapse-button"] {
|
[class*="collapse-button"] {
|
||||||
right: -1 * nth($shellPanePad, 2);
|
right: -8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,14 +214,14 @@
|
|||||||
|
|
||||||
&__main {
|
&__main {
|
||||||
> .l-pane {
|
> .l-pane {
|
||||||
padding: nth($shellPanePad, 1) nth($shellPanePad, 2);
|
padding: nth($shellPanePad, 1) 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.desktop & {
|
body.desktop & {
|
||||||
&__main {
|
&__main {
|
||||||
// Top and bottom padding in container that holds tree, __pane-main and Inspector
|
// Top and bottom padding in container that holds tree, __pane-main and Inspector
|
||||||
padding: $shellMainPad;
|
padding: nth($shellPanePad, 1) 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
||||||
> .l-pane {
|
> .l-pane {
|
||||||
@ -208,15 +232,17 @@
|
|||||||
|
|
||||||
&__pane-tree,
|
&__pane-tree,
|
||||||
&__pane-inspector {
|
&__pane-inspector {
|
||||||
max-width: 30%;
|
max-width: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__pane-tree {
|
&__pane-tree {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
padding-left: nth($shellPanePad, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__pane-inspector {
|
&__pane-inspector {
|
||||||
width: 200px;
|
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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
padding-right: $interiorMarginSm;
|
//TODO: Do we need this???
|
||||||
|
//padding-right: $interiorMarginSm;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
> * + * { margin-top: $interiorMargin; }
|
> * + * { margin-top: $interiorMargin; }
|
||||||
@ -23,22 +24,22 @@
|
|||||||
&__tree {
|
&__tree {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 0; // Chrome 73 overflow bug fix
|
height: 0; // Chrome 73 overflow bug fix
|
||||||
|
padding-right: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-tree {
|
.c-tree,
|
||||||
|
.c-list {
|
||||||
@include userSelectNone();
|
@include userSelectNone();
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-right: $interiorMargin;
|
|
||||||
|
|
||||||
li {
|
li {
|
||||||
position: relative;
|
position: relative;
|
||||||
&.c-tree__item-h { display: block; }
|
&[class*="__item-h"] { display: block; }
|
||||||
|
+ li {
|
||||||
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-tree {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
@ -48,20 +49,13 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 110%;
|
line-height: 110%;
|
||||||
padding: $interiorMargin - $aPad;
|
padding: $interiorMarginSm $interiorMargin;
|
||||||
transition: background 150ms ease;
|
transition: background 150ms ease;
|
||||||
|
|
||||||
> * + * {
|
|
||||||
margin-left: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $colorItemTreeHoverBg;
|
background: $colorItemTreeHoverBg;
|
||||||
.c-tree__item__type-icon:before {
|
|
||||||
color: $colorItemTreeIconHover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-tree__item__name {
|
[class*="__name"] {
|
||||||
color: $colorItemTreeHoverFg;
|
color: $colorItemTreeHoverFg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,12 +63,34 @@
|
|||||||
&.is-navigated-object,
|
&.is-navigated-object,
|
||||||
&.is-selected {
|
&.is-selected {
|
||||||
background: $colorItemTreeSelectedBg;
|
background: $colorItemTreeSelectedBg;
|
||||||
|
|
||||||
|
[class*="__name"] {
|
||||||
|
color: $colorItemTreeSelectedFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-tree {
|
||||||
|
.c-tree {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
.c-tree__item__type-icon:before {
|
.c-tree__item__type-icon:before {
|
||||||
color: $colorItemTreeIconHover;
|
color: $colorItemTreeIconHover;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.c-tree__item__name {
|
&.is-navigated-object,
|
||||||
color: $colorItemTreeSelectedFg;
|
&.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-selector {
|
||||||
.c-tree-and-search__tree.c-tree {
|
.c-tree-and-search__tree.c-tree {
|
||||||
border: 1px solid $colorInteriorBorder;
|
border: 1px solid $colorInteriorBorder;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
.l-multipane {
|
.l-multipane {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&--horizontal,
|
&--horizontal,
|
||||||
> .l-pane {
|
> .l-pane {
|
||||||
@ -38,7 +39,7 @@
|
|||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: $interiorMargin;
|
@include desktop() { margin-bottom: $interiorMargin; }
|
||||||
}
|
}
|
||||||
|
|
||||||
&--reacts {
|
&--reacts {
|
||||||
@ -232,8 +233,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/************************** Horizontal Splitter Before */
|
/************************** Horizontal Splitter Before */
|
||||||
// Inspector pane
|
// Example: Inspector pane
|
||||||
&[class*="-before"] {
|
&[class*="-before"] {
|
||||||
|
margin-left: nth($shellPanePad, 2);
|
||||||
|
padding-left: nth($shellPanePad, 2);
|
||||||
|
|
||||||
> .l-pane__handle {
|
> .l-pane__handle {
|
||||||
left: 0;
|
left: 0;
|
||||||
transform: translateX(floor($splitterHandleD / -2)); // Center over the pane edge
|
transform: translateX(floor($splitterHandleD / -2)); // Center over the pane edge
|
||||||
@ -247,8 +251,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/************************** Horizontal Splitter After */
|
/************************** Horizontal Splitter After */
|
||||||
// Tree pane
|
// Example: Tree pane
|
||||||
&[class*="-after"] {
|
&[class*="-after"] {
|
||||||
|
margin-right: nth($shellPanePad, 2);
|
||||||
|
padding-right: nth($shellPanePad, 2);
|
||||||
|
|
||||||
> .l-pane__handle {
|
> .l-pane__handle {
|
||||||
right: 0;
|
right: 0;
|
||||||
transform: translateX(floor($splitterHandleD / 2));
|
transform: translateX(floor($splitterHandleD / 2));
|
||||||
|
@ -16,11 +16,10 @@
|
|||||||
class="l-pane__handle"
|
class="l-pane__handle"
|
||||||
@mousedown="start"
|
@mousedown="start"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div class="l-pane__header">
|
||||||
v-if="label"
|
<span v-if="label"
|
||||||
class="l-pane__header"
|
class="l-pane__label"
|
||||||
>
|
>{{ label }}</span>
|
||||||
<span class="l-pane__label">{{ label }}</span>
|
|
||||||
<button
|
<button
|
||||||
v-if="collapsable"
|
v-if="collapsable"
|
||||||
class="l-pane__collapse-button c-button"
|
class="l-pane__collapse-button c-button"
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
.c-indicator {
|
.c-indicator {
|
||||||
@include cControl();
|
@include cControl();
|
||||||
@include cClickIconButtonLayout();
|
@include cClickIconButtonLayout();
|
||||||
button { text-transform: uppercase; }
|
|
||||||
|
|
||||||
background: none !important;
|
|
||||||
border-radius: $controlCr;
|
border-radius: $controlCr;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
button { text-transform: uppercase; }
|
||||||
|
|
||||||
&.no-minify {
|
&.no-minify {
|
||||||
// For items that cannot be minified
|
// For items that cannot be minified
|
||||||
@ -42,7 +40,7 @@
|
|||||||
|
|
||||||
a,
|
a,
|
||||||
button,
|
button,
|
||||||
s-button,
|
.s-button,
|
||||||
.c-button {
|
.c-button {
|
||||||
// Make <a> in label look like buttons
|
// Make <a> in label look like buttons
|
||||||
transition: $transIn;
|
transition: $transIn;
|
||||||
|
@ -57,8 +57,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
|
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
|
||||||
|
import Snapshot from '@/plugins/notebook/snapshot';
|
||||||
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
|
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
|
||||||
import NotebookSnapshot from '../utils/notebook-snapshot';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -96,7 +96,7 @@ export default {
|
|||||||
this.setView(view);
|
this.setView(view);
|
||||||
|
|
||||||
if (this.openmct.types.get('notebook')) {
|
if (this.openmct.types.get('notebook')) {
|
||||||
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
|
this.notebookSnapshot = new Snapshot(this.openmct);
|
||||||
this.notebookEnabled = true;
|
this.notebookEnabled = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -9,22 +9,31 @@ define([
|
|||||||
let browseObject;
|
let browseObject;
|
||||||
let unobserve = undefined;
|
let unobserve = undefined;
|
||||||
let currentObjectPath;
|
let currentObjectPath;
|
||||||
|
let isRoutingInProgress = false;
|
||||||
|
|
||||||
openmct.router.route(/^\/browse\/?$/, navigateToFirstChildOfRoot);
|
openmct.router.route(/^\/browse\/?$/, navigateToFirstChildOfRoot);
|
||||||
|
|
||||||
openmct.router.route(/^\/browse\/(.*)$/, (path, results, params) => {
|
openmct.router.route(/^\/browse\/(.*)$/, (path, results, params) => {
|
||||||
|
isRoutingInProgress = true;
|
||||||
let navigatePath = results[1];
|
let navigatePath = results[1];
|
||||||
navigateToPath(navigatePath, params.view);
|
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) {
|
if (changed.view && browseObject) {
|
||||||
let provider = openmct
|
let provider = openmct
|
||||||
.objectViews
|
.objectViews
|
||||||
.getByProviderKey(changed.view);
|
.getByProviderKey(changed.view);
|
||||||
viewObject(browseObject, provider);
|
viewObject(browseObject, provider);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
function viewObject(object, viewProvider) {
|
function viewObject(object, viewProvider) {
|
||||||
currentObjectPath = openmct.router.path;
|
currentObjectPath = openmct.router.path;
|
||||||
@ -49,6 +58,8 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
return pathToObjects(path).then((objects)=>{
|
return pathToObjects(path).then((objects)=>{
|
||||||
|
isRoutingInProgress = false;
|
||||||
|
|
||||||
if (currentNavigation !== navigateCall) {
|
if (currentNavigation !== navigateCall) {
|
||||||
return; // Prevent race.
|
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…
x
Reference in New Issue
Block a user