mirror of
https://github.com/nasa/openmct.git
synced 2025-07-02 13:13:49 +00:00
Compare commits
125 Commits
legacy-per
...
openmct-st
Author | SHA1 | Date | |
---|---|---|---|
f04c274d33 | |||
3624236c26 | |||
0126542411 | |||
2c49e62863 | |||
c2df3cdd14 | |||
b0203f2272 | |||
77b720d00d | |||
ba982671b2 | |||
02be8a9875 | |||
3bd57c8fff | |||
5df7d92d64 | |||
a8228406de | |||
2401473012 | |||
94091b25ec | |||
c191ffb37d | |||
e502fb88fa | |||
37a52cb011 | |||
04fb4e8a82 | |||
5f03dc45ee | |||
14ac758760 | |||
eb709a60cb | |||
eba1a48a44 | |||
4a0654dbcb | |||
9b6d339d69 | |||
f90afb9277 | |||
018dfb1e28 | |||
5646a252f7 | |||
c72a02aaa3 | |||
0e6ce7f58b | |||
8cd6a4c6a3 | |||
02fc162197 | |||
84d21a3695 | |||
1a6369c2b9 | |||
463c44679d | |||
c1f3ea4e61 | |||
bf3fd66942 | |||
8414ded1ec | |||
142b767470 | |||
184b716b53 | |||
646c871c76 | |||
e53399495b | |||
d27f73579b | |||
ba401b3341 | |||
5ef02ec4a2 | |||
d788031019 | |||
1ae8199e89 | |||
2deb4e8474 | |||
7f10681424 | |||
c756adad6f | |||
f3d593bc1e | |||
b637307de6 | |||
b6e0208e71 | |||
631876cab3 | |||
a192d46c2b | |||
6923f17645 | |||
d870874649 | |||
711a7a2eb5 | |||
87a45de05b | |||
ab76451360 | |||
c105a08cfe | |||
b87375a809 | |||
9fed056d22 | |||
251bf21933 | |||
a180bf7c02 | |||
ed8a54f0f9 | |||
a91179091f | |||
5f7e34ce6c | |||
ff3c2da0f9 | |||
db33f0538a | |||
28d5821120 | |||
f5ee457274 | |||
9d2770e4d2 | |||
257a8e2e2d | |||
8b25009816 | |||
074fe4481a | |||
fbd928b842 | |||
110947db09 | |||
baa8078d23 | |||
ee60013f45 | |||
ef91e92fbc | |||
d201cac4ac | |||
dcb3ccfec7 | |||
505796d9f0 | |||
56120ba1bb | |||
225b235059 | |||
de614ff606 | |||
78522cd4f1 | |||
ca232d45cc | |||
df495c841a | |||
92a37ef36b | |||
fd731ca430 | |||
263b1cd3d5 | |||
978fc8b5a3 | |||
698ccc5a35 | |||
e5aa5b5a5f | |||
b942988ef8 | |||
1eec20f2ea | |||
767a2048eb | |||
e65cf1661c | |||
0eae48646c | |||
0ba8a275d2 | |||
d8d32cc3ac | |||
a800848fe1 | |||
6881d98ba6 | |||
48d077cd2e | |||
030dd93c91 | |||
03bf6fc0a3 | |||
ef0a2ed5d2 | |||
a40aa84752 | |||
d3b69dda82 | |||
d3126ebf5c | |||
4479cbc7a2 | |||
f8ff44dac0 | |||
8f4280d15b | |||
6daa27ff31 | |||
43f6c3f85d | |||
1a7c76cf3e | |||
cee9cd7bd1 | |||
c42df20281 | |||
b4149bd2b3 | |||
f436ac9ba0 | |||
8493b481dd | |||
28723b59b7 | |||
9fa7de0b77 | |||
54bfc84ada |
@ -76,6 +76,7 @@ define([
|
|||||||
|
|
||||||
workerRequest[prop] = Number(workerRequest[prop]);
|
workerRequest[prop] = Number(workerRequest[prop]);
|
||||||
});
|
});
|
||||||
|
|
||||||
workerRequest.name = domainObject.name;
|
workerRequest.name = domainObject.name;
|
||||||
|
|
||||||
return workerRequest;
|
return workerRequest;
|
||||||
|
@ -108,7 +108,6 @@
|
|||||||
|
|
||||||
for (; nextStep < end && data.length < 5000; nextStep += step) {
|
for (; nextStep < end && data.length < 5000; nextStep += step) {
|
||||||
data.push({
|
data.push({
|
||||||
name: request.name,
|
|
||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||||
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
|
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
|
||||||
|
@ -27,7 +27,7 @@ define([
|
|||||||
) {
|
) {
|
||||||
function ImageryPlugin() {
|
function ImageryPlugin() {
|
||||||
|
|
||||||
var IMAGE_SAMPLES = [
|
const IMAGE_SAMPLES = [
|
||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg",
|
||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg",
|
||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg",
|
||||||
@ -47,13 +47,14 @@ define([
|
|||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg",
|
||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
||||||
];
|
];
|
||||||
|
const IMAGE_DELAY = 20000;
|
||||||
|
|
||||||
function pointForTimestamp(timestamp, name) {
|
function pointForTimestamp(timestamp, name) {
|
||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
utc: Math.floor(timestamp / 5000) * 5000,
|
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||||
local: Math.floor(timestamp / 5000) * 5000,
|
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||||
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
url: IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ define([
|
|||||||
subscribe: function (domainObject, callback) {
|
subscribe: function (domainObject, callback) {
|
||||||
var interval = setInterval(function () {
|
var interval = setInterval(function () {
|
||||||
callback(pointForTimestamp(Date.now(), domainObject.name));
|
callback(pointForTimestamp(Date.now(), domainObject.name));
|
||||||
}, 5000);
|
}, IMAGE_DELAY);
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
@ -81,9 +82,9 @@ define([
|
|||||||
var start = options.start;
|
var start = options.start;
|
||||||
var end = Math.min(options.end, Date.now());
|
var end = Math.min(options.end, Date.now());
|
||||||
var data = [];
|
var data = [];
|
||||||
while (start <= end && data.length < 5000) {
|
while (start <= end && data.length < IMAGE_DELAY) {
|
||||||
data.push(pointForTimestamp(start, domainObject.name));
|
data.push(pointForTimestamp(start, domainObject.name));
|
||||||
start += 5000;
|
start += IMAGE_DELAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(data);
|
return Promise.resolve(data);
|
||||||
|
86
index.html
86
index.html
@ -30,12 +30,50 @@
|
|||||||
<link rel="icon" type="image/png" href="dist/favicons/favicon-96x96.png" sizes="96x96" type="image/x-icon">
|
<link rel="icon" type="image/png" href="dist/favicons/favicon-96x96.png" sizes="96x96" type="image/x-icon">
|
||||||
<link rel="icon" type="image/png" href="dist/favicons/favicon-32x32.png" sizes="32x32" type="image/x-icon">
|
<link rel="icon" type="image/png" href="dist/favicons/favicon-32x32.png" sizes="32x32" type="image/x-icon">
|
||||||
<link rel="icon" type="image/png" href="dist/favicons/favicon-16x16.png" sizes="16x16" type="image/x-icon">
|
<link rel="icon" type="image/png" href="dist/favicons/favicon-16x16.png" sizes="16x16" type="image/x-icon">
|
||||||
|
<style type="text/css">
|
||||||
|
@keyframes splash-spinner {
|
||||||
|
0% {
|
||||||
|
transform: translate(-50%, -50%) rotate(0deg); }
|
||||||
|
100% {
|
||||||
|
transform: translate(-50%, -50%) rotate(360deg); } }
|
||||||
|
|
||||||
|
#splash-screen {
|
||||||
|
background-color: black;
|
||||||
|
position: absolute;
|
||||||
|
top: 0; right: 0; bottom: 0; left: 0;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-screen:before {
|
||||||
|
animation-name: splash-spinner;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-color: rgba(255,255,255,0.25);
|
||||||
|
border-top-color: white;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 10px;
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
opacity: 0.25;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
height: 100px; width: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
const THIRTY_SECONDS = 30 * 1000;
|
const THIRTY_SECONDS = 30 * 1000;
|
||||||
const THIRTY_MINUTES = THIRTY_SECONDS * 60;
|
const ONE_MINUTE = THIRTY_SECONDS * 2;
|
||||||
|
const FIVE_MINUTES = ONE_MINUTE * 5;
|
||||||
|
const FIFTEEN_MINUTES = FIVE_MINUTES * 3;
|
||||||
|
const THIRTY_MINUTES = FIFTEEN_MINUTES * 2;
|
||||||
|
const ONE_HOUR = THIRTY_MINUTES * 2;
|
||||||
|
const TWO_HOURS = ONE_HOUR * 2;
|
||||||
|
const ONE_DAY = ONE_HOUR * 24;
|
||||||
|
|
||||||
[
|
[
|
||||||
'example/eventGenerator'
|
'example/eventGenerator'
|
||||||
@ -48,6 +86,7 @@
|
|||||||
openmct.install(openmct.plugins.MyItems());
|
openmct.install(openmct.plugins.MyItems());
|
||||||
openmct.install(openmct.plugins.Generator());
|
openmct.install(openmct.plugins.Generator());
|
||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
|
openmct.install(openmct.plugins.Timeline());
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
openmct.install(openmct.plugins.AutoflowView({
|
openmct.install(openmct.plugins.AutoflowView({
|
||||||
type: "telemetry.panel"
|
type: "telemetry.panel"
|
||||||
@ -72,21 +111,21 @@
|
|||||||
{
|
{
|
||||||
label: 'Last Day',
|
label: 'Last Day',
|
||||||
bounds: {
|
bounds: {
|
||||||
start: () => Date.now() - 1000 * 60 * 60 * 24,
|
start: () => Date.now() - ONE_DAY,
|
||||||
end: () => Date.now()
|
end: () => Date.now()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Last 2 hours',
|
label: 'Last 2 hours',
|
||||||
bounds: {
|
bounds: {
|
||||||
start: () => Date.now() - 1000 * 60 * 60 * 2,
|
start: () => Date.now() - TWO_HOURS,
|
||||||
end: () => Date.now()
|
end: () => Date.now()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Last hour',
|
label: 'Last hour',
|
||||||
bounds: {
|
bounds: {
|
||||||
start: () => Date.now() - 1000 * 60 * 60,
|
start: () => Date.now() - ONE_HOUR,
|
||||||
end: () => Date.now()
|
end: () => Date.now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,7 +134,7 @@
|
|||||||
records: 10,
|
records: 10,
|
||||||
// maximum duration between start and end bounds
|
// maximum duration between start and end bounds
|
||||||
// for utc-based time systems this is in milliseconds
|
// for utc-based time systems this is in milliseconds
|
||||||
limit: 1000 * 60 * 60 * 24
|
limit: ONE_DAY
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Realtime",
|
name: "Realtime",
|
||||||
@ -104,7 +143,44 @@
|
|||||||
clockOffsets: {
|
clockOffsets: {
|
||||||
start: - THIRTY_MINUTES,
|
start: - THIRTY_MINUTES,
|
||||||
end: THIRTY_SECONDS
|
end: THIRTY_SECONDS
|
||||||
|
},
|
||||||
|
presets: [
|
||||||
|
{
|
||||||
|
label: '1 Hour',
|
||||||
|
bounds: {
|
||||||
|
start: - ONE_HOUR,
|
||||||
|
end: THIRTY_SECONDS
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '30 Minutes',
|
||||||
|
bounds: {
|
||||||
|
start: - THIRTY_MINUTES,
|
||||||
|
end: THIRTY_SECONDS
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '15 Minutes',
|
||||||
|
bounds: {
|
||||||
|
start: - FIFTEEN_MINUTES,
|
||||||
|
end: THIRTY_SECONDS
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '5 Minutes',
|
||||||
|
bounds: {
|
||||||
|
start: - FIVE_MINUTES,
|
||||||
|
end: THIRTY_SECONDS
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1 Minute',
|
||||||
|
bounds: {
|
||||||
|
start: - ONE_MINUTE,
|
||||||
|
end: THIRTY_SECONDS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}));
|
}));
|
||||||
|
@ -86,7 +86,7 @@ module.exports = (config) => {
|
|||||||
reports: ['html', 'lcovonly', 'text-summary'],
|
reports: ['html', 'lcovonly', 'text-summary'],
|
||||||
thresholds: {
|
thresholds: {
|
||||||
global: {
|
global: {
|
||||||
lines: 64
|
lines: 65
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "1.3.0-SNAPSHOT",
|
"version": "1.4.1-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -143,8 +143,8 @@ define([
|
|||||||
"$window"
|
"$window"
|
||||||
],
|
],
|
||||||
"group": "windowing",
|
"group": "windowing",
|
||||||
"cssClass": "icon-new-window",
|
"priority": 10,
|
||||||
"priority": "preferred"
|
"cssClass": "icon-new-window"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"runs": [
|
"runs": [
|
||||||
|
@ -139,7 +139,9 @@ define([
|
|||||||
],
|
],
|
||||||
"description": "Edit",
|
"description": "Edit",
|
||||||
"category": "view-control",
|
"category": "view-control",
|
||||||
"cssClass": "major icon-pencil"
|
"cssClass": "major icon-pencil",
|
||||||
|
"group": "action",
|
||||||
|
"priority": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "properties",
|
"key": "properties",
|
||||||
@ -150,6 +152,8 @@ define([
|
|||||||
"implementation": PropertiesAction,
|
"implementation": PropertiesAction,
|
||||||
"cssClass": "major icon-pencil",
|
"cssClass": "major icon-pencil",
|
||||||
"name": "Edit Properties...",
|
"name": "Edit Properties...",
|
||||||
|
"group": "action",
|
||||||
|
"priority": 10,
|
||||||
"description": "Edit properties of this object.",
|
"description": "Edit properties of this object.",
|
||||||
"depends": [
|
"depends": [
|
||||||
"dialogService"
|
"dialogService"
|
||||||
|
@ -20,12 +20,12 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class="c-object-label"
|
<div class="c-object-label"
|
||||||
ng-class="{ 'is-missing': model.status === 'missing' }"
|
ng-class="{ 'is-status--missing': model.status === 'missing' }"
|
||||||
>
|
>
|
||||||
<div class="c-object-label__type-icon {{type.getCssClass()}}"
|
<div class="c-object-label__type-icon {{type.getCssClass()}}"
|
||||||
ng-class="{ 'l-icon-link':location.isLink() }"
|
ng-class="{ 'l-icon-link':location.isLink() }"
|
||||||
>
|
>
|
||||||
<span class="is-missing__indicator" title="This item is missing"></span>
|
<span class="is-status__indicator" title="This item is missing or suspect"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class='c-object-label__name'>{{model.name}}</div>
|
<div class='c-object-label__name'>{{model.name}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -114,7 +114,12 @@ define(["objectUtils"],
|
|||||||
var self = this,
|
var self = this,
|
||||||
domainObject = this.domainObject;
|
domainObject = this.domainObject;
|
||||||
|
|
||||||
let newStyleObject = objectUtils.toNewFormat(domainObject.getModel(), domainObject.getId());
|
const identifier = {
|
||||||
|
namespace: this.getSpace(),
|
||||||
|
key: this.getKey()
|
||||||
|
};
|
||||||
|
|
||||||
|
let newStyleObject = objectUtils.toNewFormat(domainObject.getModel(), identifier);
|
||||||
|
|
||||||
return this.openmct.objects
|
return this.openmct.objects
|
||||||
.save(newStyleObject)
|
.save(newStyleObject)
|
||||||
@ -146,6 +151,7 @@ define(["objectUtils"],
|
|||||||
return domainObject.useCapability("mutation", function () {
|
return domainObject.useCapability("mutation", function () {
|
||||||
return model;
|
return model;
|
||||||
}, modified);
|
}, modified);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,8 +99,8 @@ define(
|
|||||||
|
|
||||||
mockNewStyleDomainObject = Object.assign({}, model);
|
mockNewStyleDomainObject = Object.assign({}, model);
|
||||||
mockNewStyleDomainObject.identifier = {
|
mockNewStyleDomainObject.identifier = {
|
||||||
namespace: "",
|
namespace: SPACE,
|
||||||
key: id
|
key: key
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simulate mutation capability
|
// Simulate mutation capability
|
||||||
|
@ -66,6 +66,8 @@ define([
|
|||||||
"description": "Move object to another location.",
|
"description": "Move object to another location.",
|
||||||
"cssClass": "icon-move",
|
"cssClass": "icon-move",
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
|
"group": "action",
|
||||||
|
"priority": 9,
|
||||||
"implementation": MoveAction,
|
"implementation": MoveAction,
|
||||||
"depends": [
|
"depends": [
|
||||||
"policyService",
|
"policyService",
|
||||||
@ -79,6 +81,8 @@ define([
|
|||||||
"description": "Duplicate object to another location.",
|
"description": "Duplicate object to another location.",
|
||||||
"cssClass": "icon-duplicate",
|
"cssClass": "icon-duplicate",
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
|
"group": "action",
|
||||||
|
"priority": 8,
|
||||||
"implementation": CopyAction,
|
"implementation": CopyAction,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$log",
|
"$log",
|
||||||
@ -95,6 +99,8 @@ define([
|
|||||||
"description": "Create Link to object in another location.",
|
"description": "Create Link to object in another location.",
|
||||||
"cssClass": "icon-link",
|
"cssClass": "icon-link",
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
|
"group": "action",
|
||||||
|
"priority": 7,
|
||||||
"implementation": LinkAction,
|
"implementation": LinkAction,
|
||||||
"depends": [
|
"depends": [
|
||||||
"policyService",
|
"policyService",
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
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.
|
||||||
-->
|
-->
|
||||||
<div class="c-clock l-time-display" ng-controller="ClockController as clock">
|
<div class="c-clock l-time-display u-style-receiver js-style-receiver" ng-controller="ClockController as clock">
|
||||||
<div class="c-clock__timezone">
|
<div class="c-clock__timezone">
|
||||||
{{clock.zone()}}
|
{{clock.zone()}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
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.
|
||||||
-->
|
-->
|
||||||
<div class="c-timer is-{{timer.timerState}}" ng-controller="TimerController as timer">
|
<div class="c-timer u-style-receiver js-style-receiver is-{{timer.timerState}}" ng-controller="TimerController as timer">
|
||||||
<div class="c-timer__controls">
|
<div class="c-timer__controls">
|
||||||
<button ng-click="timer.clickStopButton()"
|
<button ng-click="timer.clickStopButton()"
|
||||||
ng-hide="timer.timerState == 'stopped'"
|
ng-hide="timer.timerState == 'stopped'"
|
||||||
|
@ -30,7 +30,6 @@ define([
|
|||||||
"./src/controllers/CompositeController",
|
"./src/controllers/CompositeController",
|
||||||
"./src/controllers/ColorController",
|
"./src/controllers/ColorController",
|
||||||
"./src/controllers/DialogButtonController",
|
"./src/controllers/DialogButtonController",
|
||||||
"./src/controllers/SnapshotPreviewController",
|
|
||||||
"./res/templates/controls/autocomplete.html",
|
"./res/templates/controls/autocomplete.html",
|
||||||
"./res/templates/controls/checkbox.html",
|
"./res/templates/controls/checkbox.html",
|
||||||
"./res/templates/controls/datetime.html",
|
"./res/templates/controls/datetime.html",
|
||||||
@ -44,8 +43,7 @@ define([
|
|||||||
"./res/templates/controls/menu-button.html",
|
"./res/templates/controls/menu-button.html",
|
||||||
"./res/templates/controls/dialog.html",
|
"./res/templates/controls/dialog.html",
|
||||||
"./res/templates/controls/radio.html",
|
"./res/templates/controls/radio.html",
|
||||||
"./res/templates/controls/file-input.html",
|
"./res/templates/controls/file-input.html"
|
||||||
"./res/templates/controls/snap-view.html"
|
|
||||||
], function (
|
], function (
|
||||||
MCTForm,
|
MCTForm,
|
||||||
MCTControl,
|
MCTControl,
|
||||||
@ -56,7 +54,6 @@ define([
|
|||||||
CompositeController,
|
CompositeController,
|
||||||
ColorController,
|
ColorController,
|
||||||
DialogButtonController,
|
DialogButtonController,
|
||||||
SnapshotPreviewController,
|
|
||||||
autocompleteTemplate,
|
autocompleteTemplate,
|
||||||
checkboxTemplate,
|
checkboxTemplate,
|
||||||
datetimeTemplate,
|
datetimeTemplate,
|
||||||
@ -70,8 +67,7 @@ define([
|
|||||||
menuButtonTemplate,
|
menuButtonTemplate,
|
||||||
dialogTemplate,
|
dialogTemplate,
|
||||||
radioTemplate,
|
radioTemplate,
|
||||||
fileInputTemplate,
|
fileInputTemplate
|
||||||
snapViewTemplate
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -157,10 +153,6 @@ define([
|
|||||||
{
|
{
|
||||||
"key": "file-input",
|
"key": "file-input",
|
||||||
"template": fileInputTemplate
|
"template": fileInputTemplate
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "snap-view",
|
|
||||||
"template": snapViewTemplate
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"controllers": [
|
"controllers": [
|
||||||
@ -194,14 +186,6 @@ define([
|
|||||||
"$scope",
|
"$scope",
|
||||||
"dialogService"
|
"dialogService"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "SnapshotPreviewController",
|
|
||||||
"implementation": SnapshotPreviewController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"components": [
|
"components": [
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<span ng-controller="SnapshotPreviewController"
|
|
||||||
class='form-control shell'>
|
|
||||||
<span class='field control {{structure.cssClass}}'>
|
|
||||||
<image
|
|
||||||
class="c-ne__embed__snap-thumb"
|
|
||||||
src="{{imageUrl || structure.src}}"
|
|
||||||
ng-click="previewImage(imageUrl || structure.src)"
|
|
||||||
name="mctControl">
|
|
||||||
</image>
|
|
||||||
<br>
|
|
||||||
<a title="Annotate" class="s-button icon-pencil" ng-click="annotateImage(ngModel, field, imageUrl || structure.src)">
|
|
||||||
<span class="title-label">Annotate</span>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
@ -29,7 +29,6 @@ define(["zepto"], function ($) {
|
|||||||
* @memberof platform/forms
|
* @memberof platform/forms
|
||||||
*/
|
*/
|
||||||
function FileInputService() {
|
function FileInputService() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +37,7 @@ define(["zepto"], function ($) {
|
|||||||
*
|
*
|
||||||
* @returns {Promise} promise for an object containing file meta-data
|
* @returns {Promise} promise for an object containing file meta-data
|
||||||
*/
|
*/
|
||||||
FileInputService.prototype.getInput = function () {
|
FileInputService.prototype.getInput = function (fileType) {
|
||||||
var input = this.newInput();
|
var input = this.newInput();
|
||||||
var read = this.readFile;
|
var read = this.readFile;
|
||||||
var fileInfo = {};
|
var fileInfo = {};
|
||||||
@ -51,6 +50,10 @@ define(["zepto"], function ($) {
|
|||||||
file = this.files[0];
|
file = this.files[0];
|
||||||
input.remove();
|
input.remove();
|
||||||
if (file) {
|
if (file) {
|
||||||
|
if (fileType && (!file.type || (file.type !== fileType))) {
|
||||||
|
reject("Incompatible file type");
|
||||||
|
}
|
||||||
|
|
||||||
read(file)
|
read(file)
|
||||||
.then(function (contents) {
|
.then(function (contents) {
|
||||||
fileInfo.name = file.name;
|
fileInfo.name = file.name;
|
||||||
|
@ -40,7 +40,7 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick() {
|
||||||
fileInputService.getInput().then(function (result) {
|
fileInputService.getInput(scope.structure.type).then(function (result) {
|
||||||
setText(result.name);
|
setText(result.name);
|
||||||
scope.ngModel[scope.field] = result;
|
scope.ngModel[scope.field] = result;
|
||||||
control.$setValidity("file-input", true);
|
control.$setValidity("file-input", true);
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'painterro'
|
|
||||||
],
|
|
||||||
function (Painterro) {
|
|
||||||
|
|
||||||
function SnapshotPreviewController($scope, openmct) {
|
|
||||||
|
|
||||||
$scope.previewImage = function (imageUrl) {
|
|
||||||
let imageDiv = document.createElement('div');
|
|
||||||
imageDiv.classList = 'image-main s-image-main';
|
|
||||||
imageDiv.style.backgroundImage = `url(${imageUrl})`;
|
|
||||||
|
|
||||||
let previewImageOverlay = openmct.overlays.overlay(
|
|
||||||
{
|
|
||||||
element: imageDiv,
|
|
||||||
size: 'large',
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
label: 'Done',
|
|
||||||
callback: function () {
|
|
||||||
previewImageOverlay.dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.annotateImage = function (ngModel, field, imageUrl) {
|
|
||||||
$scope.imageUrl = imageUrl;
|
|
||||||
|
|
||||||
let div = document.createElement('div'),
|
|
||||||
painterroInstance = {},
|
|
||||||
save = false;
|
|
||||||
|
|
||||||
div.id = 'snap-annotation';
|
|
||||||
|
|
||||||
let annotateImageOverlay = openmct.overlays.overlay(
|
|
||||||
{
|
|
||||||
element: div,
|
|
||||||
size: 'large',
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
label: 'Cancel',
|
|
||||||
callback: function () {
|
|
||||||
save = false;
|
|
||||||
painterroInstance.save();
|
|
||||||
annotateImageOverlay.dismiss();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Save',
|
|
||||||
callback: function () {
|
|
||||||
save = true;
|
|
||||||
painterroInstance.save();
|
|
||||||
annotateImageOverlay.dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
painterroInstance = Painterro({
|
|
||||||
id: 'snap-annotation',
|
|
||||||
activeColor: '#ff0000',
|
|
||||||
activeColorAlpha: 1.0,
|
|
||||||
activeFillColor: '#fff',
|
|
||||||
activeFillColorAlpha: 0.0,
|
|
||||||
backgroundFillColor: '#000',
|
|
||||||
backgroundFillColorAlpha: 0.0,
|
|
||||||
defaultFontSize: 16,
|
|
||||||
defaultLineWidth: 2,
|
|
||||||
defaultTool: 'ellipse',
|
|
||||||
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
|
|
||||||
translation: {
|
|
||||||
name: 'en',
|
|
||||||
strings: {
|
|
||||||
lineColor: 'Line',
|
|
||||||
fillColor: 'Fill',
|
|
||||||
lineWidth: 'Size',
|
|
||||||
textColor: 'Color',
|
|
||||||
fontSize: 'Size',
|
|
||||||
fontStyle: 'Style'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
saveHandler: function (image, done) {
|
|
||||||
if (save) {
|
|
||||||
let url = image.asBlob(),
|
|
||||||
reader = new window.FileReader();
|
|
||||||
|
|
||||||
reader.readAsDataURL(url);
|
|
||||||
reader.onloadend = function () {
|
|
||||||
$scope.imageUrl = reader.result;
|
|
||||||
ngModel[field] = reader.result;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
ngModel.field = imageUrl;
|
|
||||||
console.warn('You cancelled the annotation!!!');
|
|
||||||
}
|
|
||||||
|
|
||||||
done(true);
|
|
||||||
}
|
|
||||||
}).show(imageUrl);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return SnapshotPreviewController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -47,6 +47,8 @@ define([
|
|||||||
"implementation": ExportAsJSONAction,
|
"implementation": ExportAsJSONAction,
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
"cssClass": "icon-export",
|
"cssClass": "icon-export",
|
||||||
|
"group": "json",
|
||||||
|
"priority": 2,
|
||||||
"depends": [
|
"depends": [
|
||||||
"openmct",
|
"openmct",
|
||||||
"exportService",
|
"exportService",
|
||||||
@ -61,6 +63,8 @@ define([
|
|||||||
"implementation": ImportAsJSONAction,
|
"implementation": ImportAsJSONAction,
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
"cssClass": "icon-import",
|
"cssClass": "icon-import",
|
||||||
|
"group": "json",
|
||||||
|
"priority": 2,
|
||||||
"depends": [
|
"depends": [
|
||||||
"exportService",
|
"exportService",
|
||||||
"identifierService",
|
"identifierService",
|
||||||
|
@ -104,7 +104,7 @@ define([
|
|||||||
"depends": [
|
"depends": [
|
||||||
"$q",
|
"$q",
|
||||||
"$log",
|
"$log",
|
||||||
"modelService",
|
"objectService",
|
||||||
"workerService",
|
"workerService",
|
||||||
"topic",
|
"topic",
|
||||||
"GENERIC_SEARCH_ROOTS",
|
"GENERIC_SEARCH_ROOTS",
|
||||||
|
@ -38,16 +38,16 @@ define([
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @param $q Angular's $q, for promise consolidation.
|
* @param $q Angular's $q, for promise consolidation.
|
||||||
* @param $log Anglar's $log, for logging.
|
* @param $log Anglar's $log, for logging.
|
||||||
* @param {ModelService} modelService the model service.
|
* @param {ObjectService} objectService the object service.
|
||||||
* @param {WorkerService} workerService the workerService.
|
* @param {WorkerService} workerService the workerService.
|
||||||
* @param {TopicService} topic the topic service.
|
* @param {TopicService} topic the topic service.
|
||||||
* @param {Array} ROOTS An array of object Ids to begin indexing.
|
* @param {Array} ROOTS An array of object Ids to begin indexing.
|
||||||
*/
|
*/
|
||||||
function GenericSearchProvider($q, $log, modelService, workerService, topic, ROOTS, USE_LEGACY_INDEXER, openmct) {
|
function GenericSearchProvider($q, $log, objectService, workerService, topic, ROOTS, USE_LEGACY_INDEXER, openmct) {
|
||||||
var provider = this;
|
var provider = this;
|
||||||
this.$q = $q;
|
this.$q = $q;
|
||||||
this.$log = $log;
|
this.$log = $log;
|
||||||
this.modelService = modelService;
|
this.objectService = objectService;
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
|
|
||||||
this.indexedIds = {};
|
this.indexedIds = {};
|
||||||
@ -218,12 +218,12 @@ define([
|
|||||||
provider = this;
|
provider = this;
|
||||||
|
|
||||||
this.pendingRequests += 1;
|
this.pendingRequests += 1;
|
||||||
this.modelService
|
this.objectService
|
||||||
.getModels([idToIndex])
|
.getObjects([idToIndex])
|
||||||
.then(function (models) {
|
.then(function (objects) {
|
||||||
delete provider.pendingIndex[idToIndex];
|
delete provider.pendingIndex[idToIndex];
|
||||||
if (models[idToIndex]) {
|
if (objects[idToIndex]) {
|
||||||
provider.index(idToIndex, models[idToIndex]);
|
provider.index(idToIndex, objects[idToIndex].model);
|
||||||
}
|
}
|
||||||
}, function () {
|
}, function () {
|
||||||
provider
|
provider
|
||||||
|
@ -242,7 +242,11 @@ define([
|
|||||||
|
|
||||||
this.overlays = new OverlayAPI.default();
|
this.overlays = new OverlayAPI.default();
|
||||||
|
|
||||||
this.contextMenu = new api.ContextMenuRegistry();
|
this.menus = new api.MenuAPI(this);
|
||||||
|
|
||||||
|
this.actions = new api.ActionsAPI(this);
|
||||||
|
|
||||||
|
this.status = new api.StatusAPI(this);
|
||||||
|
|
||||||
this.router = new ApplicationRouter();
|
this.router = new ApplicationRouter();
|
||||||
|
|
||||||
@ -271,6 +275,7 @@ define([
|
|||||||
this.install(this.plugins.URLTimeSettingsSynchronizer());
|
this.install(this.plugins.URLTimeSettingsSynchronizer());
|
||||||
this.install(this.plugins.NotificationIndicator());
|
this.install(this.plugins.NotificationIndicator());
|
||||||
this.install(this.plugins.NewFolderAction());
|
this.install(this.plugins.NewFolderAction());
|
||||||
|
this.install(this.plugins.ViewDatumAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
@ -35,5 +35,5 @@ export default function LegacyActionAdapter(openmct, legacyActions) {
|
|||||||
|
|
||||||
legacyActions.filter(contextualCategoryOnly)
|
legacyActions.filter(contextualCategoryOnly)
|
||||||
.map(LegacyAction => new LegacyContextMenuAction(openmct, LegacyAction))
|
.map(LegacyAction => new LegacyContextMenuAction(openmct, LegacyAction))
|
||||||
.forEach(openmct.contextMenu.registerAction);
|
.forEach(openmct.actions.register);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,8 @@ export default class LegacyContextMenuAction {
|
|||||||
this.description = LegacyAction.definition.description;
|
this.description = LegacyAction.definition.description;
|
||||||
this.cssClass = LegacyAction.definition.cssClass;
|
this.cssClass = LegacyAction.definition.cssClass;
|
||||||
this.LegacyAction = LegacyAction;
|
this.LegacyAction = LegacyAction;
|
||||||
|
this.group = LegacyAction.definition.group;
|
||||||
|
this.priority = LegacyAction.definition.priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke(objectPath) {
|
invoke(objectPath) {
|
||||||
|
@ -128,7 +128,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
ObjectServiceProvider.prototype.get = function (key) {
|
ObjectServiceProvider.prototype.get = function (key) {
|
||||||
const keyString = utils.makeKeyString(key);
|
let keyString = utils.makeKeyString(key);
|
||||||
|
|
||||||
return this.objectService.getObjects([keyString])
|
return this.objectService.getObjects([keyString])
|
||||||
.then(function (results) {
|
.then(function (results) {
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import objectUtils from 'objectUtils';
|
import utils from 'objectUtils';
|
||||||
|
|
||||||
export default class LegacyPersistenceAdapter {
|
export default class LegacyPersistenceAdapter {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
@ -35,13 +35,43 @@ export default class LegacyPersistenceAdapter {
|
|||||||
return Promise.resolve(Object.keys(this.openmct.objects.providers));
|
return Promise.resolve(Object.keys(this.openmct.objects.providers));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateObject(legacyDomainObject) {
|
createObject(space, key, legacyDomainObject) {
|
||||||
return this.openmct.objects.save(legacyDomainObject.useCapability('adapter'));
|
let object = utils.toNewFormat(legacyDomainObject, {
|
||||||
|
namespace: space,
|
||||||
|
key: key
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.openmct.objects.save(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
readObject(keystring) {
|
deleteObject(space, key) {
|
||||||
let identifier = objectUtils.parseKeyString(keystring);
|
const identifier = {
|
||||||
|
namespace: space,
|
||||||
|
key: key
|
||||||
|
};
|
||||||
|
|
||||||
return this.openmct.legacyObject(this.openmct.objects.get(identifier));
|
return this.openmct.objects.delete(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateObject(space, key, legacyDomainObject) {
|
||||||
|
let object = utils.toNewFormat(legacyDomainObject, {
|
||||||
|
namespace: space,
|
||||||
|
key: key
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.openmct.objects.save(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
readObject(space, key) {
|
||||||
|
const identifier = {
|
||||||
|
namespace: space,
|
||||||
|
key: key
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.openmct.objects.get(identifier).then(domainObject => {
|
||||||
|
let object = this.openmct.legacyObject(domainObject);
|
||||||
|
|
||||||
|
return object.model;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
178
src/api/actions/ActionCollection.js
Normal file
178
src/api/actions/ActionCollection.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
class ActionCollection extends EventEmitter {
|
||||||
|
constructor(applicableActions, objectPath, view, openmct) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.applicableActions = applicableActions;
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.objectPath = objectPath;
|
||||||
|
this.view = view;
|
||||||
|
this.objectUnsubscribes = [];
|
||||||
|
|
||||||
|
let debounceOptions = {
|
||||||
|
leading: false,
|
||||||
|
trailing: true
|
||||||
|
};
|
||||||
|
|
||||||
|
this._updateActions = _.debounce(this._updateActions.bind(this), 150, debounceOptions);
|
||||||
|
this._update = _.debounce(this._update.bind(this), 150, debounceOptions);
|
||||||
|
|
||||||
|
this._observeObjectPath();
|
||||||
|
this._initializeActions();
|
||||||
|
|
||||||
|
this.openmct.editor.on('isEditing', this._updateActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
disable(actionKeys) {
|
||||||
|
actionKeys.forEach(actionKey => {
|
||||||
|
if (this.applicableActions[actionKey]) {
|
||||||
|
this.applicableActions[actionKey].isDisabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
enable(actionKeys) {
|
||||||
|
actionKeys.forEach(actionKey => {
|
||||||
|
if (this.applicableActions[actionKey]) {
|
||||||
|
this.applicableActions[actionKey].isDisabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
hide(actionKeys) {
|
||||||
|
actionKeys.forEach(actionKey => {
|
||||||
|
if (this.applicableActions[actionKey]) {
|
||||||
|
this.applicableActions[actionKey].isHidden = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
show(actionKeys) {
|
||||||
|
actionKeys.forEach(actionKey => {
|
||||||
|
if (this.applicableActions[actionKey]) {
|
||||||
|
this.applicableActions[actionKey].isHidden = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.objectUnsubscribes.forEach(unsubscribe => {
|
||||||
|
unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.openmct.editor.off('isEditing', this._updateActions);
|
||||||
|
|
||||||
|
this.emit('destroy', this.view);
|
||||||
|
}
|
||||||
|
|
||||||
|
getVisibleActions() {
|
||||||
|
let actionsArray = Object.keys(this.applicableActions);
|
||||||
|
let visibleActions = [];
|
||||||
|
|
||||||
|
actionsArray.forEach(actionKey => {
|
||||||
|
let action = this.applicableActions[actionKey];
|
||||||
|
|
||||||
|
if (!action.isHidden) {
|
||||||
|
visibleActions.push(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return visibleActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatusBarActions() {
|
||||||
|
let actionsArray = Object.keys(this.applicableActions);
|
||||||
|
let statusBarActions = [];
|
||||||
|
|
||||||
|
actionsArray.forEach(actionKey => {
|
||||||
|
let action = this.applicableActions[actionKey];
|
||||||
|
|
||||||
|
if (action.showInStatusBar && !action.isDisabled && !action.isHidden) {
|
||||||
|
statusBarActions.push(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return statusBarActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
_update() {
|
||||||
|
this.emit('update', this.applicableActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
_observeObjectPath() {
|
||||||
|
let actionCollection = this;
|
||||||
|
|
||||||
|
function updateObject(oldObject, newObject) {
|
||||||
|
Object.assign(oldObject, newObject);
|
||||||
|
|
||||||
|
actionCollection._updateActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.objectPath.forEach(object => {
|
||||||
|
if (object) {
|
||||||
|
let unsubscribe = this.openmct.objects.observe(object, '*', updateObject.bind(this, object));
|
||||||
|
|
||||||
|
this.objectUnsubscribes.push(unsubscribe);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_initializeActions() {
|
||||||
|
Object.keys(this.applicableActions).forEach(key => {
|
||||||
|
this.applicableActions[key].callBack = () => {
|
||||||
|
return this.applicableActions[key].invoke(this.objectPath, this.view);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateActions() {
|
||||||
|
let newApplicableActions = this.openmct.actions._applicableActions(this.objectPath, this.view);
|
||||||
|
|
||||||
|
this.applicableActions = this._mergeOldAndNewActions(this.applicableActions, newApplicableActions);
|
||||||
|
this._initializeActions();
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
_mergeOldAndNewActions(oldActions, newActions) {
|
||||||
|
let mergedActions = {};
|
||||||
|
Object.keys(newActions).forEach(key => {
|
||||||
|
if (oldActions[key]) {
|
||||||
|
mergedActions[key] = oldActions[key];
|
||||||
|
} else {
|
||||||
|
mergedActions[key] = newActions[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return mergedActions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActionCollection;
|
145
src/api/actions/ActionsAPI.js
Normal file
145
src/api/actions/ActionsAPI.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
import ActionCollection from './ActionCollection';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
class ActionsAPI extends EventEmitter {
|
||||||
|
constructor(openmct) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._allActions = {};
|
||||||
|
this._actionCollections = new WeakMap();
|
||||||
|
this._openmct = openmct;
|
||||||
|
|
||||||
|
this._groupOrder = ['windowing', 'undefined', 'view', 'action', 'json'];
|
||||||
|
|
||||||
|
this.register = this.register.bind(this);
|
||||||
|
this.get = this.get.bind(this);
|
||||||
|
this._applicableActions = this._applicableActions.bind(this);
|
||||||
|
this._updateCachedActionCollections = this._updateCachedActionCollections.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
register(actionDefinition) {
|
||||||
|
this._allActions[actionDefinition.key] = actionDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(objectPath, view) {
|
||||||
|
let viewContext = view && view.getViewContext && view.getViewContext() || {};
|
||||||
|
|
||||||
|
if (view && !viewContext.skipCache) {
|
||||||
|
let cachedActionCollection = this._actionCollections.get(view);
|
||||||
|
|
||||||
|
if (cachedActionCollection) {
|
||||||
|
return cachedActionCollection;
|
||||||
|
} else {
|
||||||
|
let applicableActions = this._applicableActions(objectPath, view);
|
||||||
|
let actionCollection = new ActionCollection(applicableActions, objectPath, view, this._openmct);
|
||||||
|
|
||||||
|
this._actionCollections.set(view, actionCollection);
|
||||||
|
actionCollection.on('destroy', this._updateCachedActionCollections);
|
||||||
|
|
||||||
|
return actionCollection;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let applicableActions = this._applicableActions(objectPath, view);
|
||||||
|
|
||||||
|
Object.keys(applicableActions).forEach(key => {
|
||||||
|
let action = applicableActions[key];
|
||||||
|
|
||||||
|
action.callBack = () => {
|
||||||
|
return action.invoke(objectPath, view);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return applicableActions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGroupOrder(groupArray) {
|
||||||
|
this._groupOrder = groupArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateCachedActionCollections(key) {
|
||||||
|
if (this._actionCollections.has(key)) {
|
||||||
|
let actionCollection = this._actionCollections.get(key);
|
||||||
|
actionCollection.off('destroy', this._updateCachedActionCollections);
|
||||||
|
|
||||||
|
this._actionCollections.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_applicableActions(objectPath, view) {
|
||||||
|
let actionsObject = {};
|
||||||
|
|
||||||
|
let keys = Object.keys(this._allActions).filter(key => {
|
||||||
|
let actionDefinition = this._allActions[key];
|
||||||
|
|
||||||
|
if (actionDefinition.appliesTo === undefined) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return actionDefinition.appliesTo(objectPath, view);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
keys.forEach(key => {
|
||||||
|
let action = _.clone(this._allActions[key]);
|
||||||
|
|
||||||
|
actionsObject[key] = action;
|
||||||
|
});
|
||||||
|
|
||||||
|
return actionsObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
_groupAndSortActions(actionsArray) {
|
||||||
|
if (!Array.isArray(actionsArray) && typeof actionsArray === 'object') {
|
||||||
|
actionsArray = Object.keys(actionsArray).map(key => actionsArray[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actionsObject = {};
|
||||||
|
let groupedSortedActionsArray = [];
|
||||||
|
|
||||||
|
function sortDescending(a, b) {
|
||||||
|
return b.priority - a.priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsArray.forEach(action => {
|
||||||
|
if (actionsObject[action.group] === undefined) {
|
||||||
|
actionsObject[action.group] = [action];
|
||||||
|
} else {
|
||||||
|
actionsObject[action.group].push(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._groupOrder.forEach(group => {
|
||||||
|
let groupArray = actionsObject[group];
|
||||||
|
|
||||||
|
if (groupArray) {
|
||||||
|
groupedSortedActionsArray.push(groupArray.sort(sortDescending));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return groupedSortedActionsArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActionsAPI;
|
119
src/api/actions/ActionsAPISpec.js
Normal file
119
src/api/actions/ActionsAPISpec.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 ActionsAPI from './ActionsAPI';
|
||||||
|
import { createOpenMct, resetApplicationState } from '../../utils/testing';
|
||||||
|
|
||||||
|
describe('The Actions API', () => {
|
||||||
|
let openmct;
|
||||||
|
let actionsAPI;
|
||||||
|
let mockAction;
|
||||||
|
let mockObjectPath;
|
||||||
|
let mockViewContext1;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
openmct = createOpenMct();
|
||||||
|
actionsAPI = new ActionsAPI(openmct);
|
||||||
|
mockAction = {
|
||||||
|
name: 'Test Action',
|
||||||
|
key: 'test-action',
|
||||||
|
cssClass: 'test-action',
|
||||||
|
description: 'This is a test action',
|
||||||
|
group: 'action',
|
||||||
|
priority: 9,
|
||||||
|
appliesTo: (objectPath, view = {}) => {
|
||||||
|
if (view.getViewContext) {
|
||||||
|
let viewContext = view.getViewContext();
|
||||||
|
|
||||||
|
return viewContext.onlyAppliesToTestCase;
|
||||||
|
} else if (objectPath.length) {
|
||||||
|
return objectPath[0].type === 'fake-folder';
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
invoke: () => {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mockObjectPath = [
|
||||||
|
{
|
||||||
|
name: 'mock folder',
|
||||||
|
type: 'fake-folder',
|
||||||
|
identifier: {
|
||||||
|
key: 'mock-folder',
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mock parent folder',
|
||||||
|
type: 'fake-folder',
|
||||||
|
identifier: {
|
||||||
|
key: 'mock-parent-folder',
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
mockViewContext1 = {
|
||||||
|
getViewContext: () => {
|
||||||
|
return {
|
||||||
|
onlyAppliesToTestCase: true,
|
||||||
|
skipCache: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("register method", () => {
|
||||||
|
it("adds action to ActionsAPI", () => {
|
||||||
|
actionsAPI.register(mockAction);
|
||||||
|
|
||||||
|
let action = actionsAPI.get(mockObjectPath, mockViewContext1)[mockAction.key];
|
||||||
|
|
||||||
|
expect(action.key).toEqual(mockAction.key);
|
||||||
|
expect(action.name).toEqual(mockAction.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("get method", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
actionsAPI.register(mockAction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an object with relevant actions when invoked with objectPath only", () => {
|
||||||
|
let action = actionsAPI.get(mockObjectPath, mockViewContext1)[mockAction.key];
|
||||||
|
|
||||||
|
expect(action.key).toEqual(mockAction.key);
|
||||||
|
expect(action.name).toEqual(mockAction.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an object with relevant actions when invoked with viewContext and skipCache", () => {
|
||||||
|
let action = actionsAPI.get(mockObjectPath, mockViewContext1)[mockAction.key];
|
||||||
|
|
||||||
|
expect(action.key).toEqual(mockAction.key);
|
||||||
|
expect(action.name).toEqual(mockAction.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -28,9 +28,10 @@ define([
|
|||||||
'./telemetry/TelemetryAPI',
|
'./telemetry/TelemetryAPI',
|
||||||
'./indicators/IndicatorAPI',
|
'./indicators/IndicatorAPI',
|
||||||
'./notifications/NotificationAPI',
|
'./notifications/NotificationAPI',
|
||||||
'./contextMenu/ContextMenuAPI',
|
'./Editor',
|
||||||
'./Editor'
|
'./menu/MenuAPI',
|
||||||
|
'./actions/ActionsAPI',
|
||||||
|
'./status/StatusAPI'
|
||||||
], function (
|
], function (
|
||||||
TimeAPI,
|
TimeAPI,
|
||||||
ObjectAPI,
|
ObjectAPI,
|
||||||
@ -39,8 +40,10 @@ define([
|
|||||||
TelemetryAPI,
|
TelemetryAPI,
|
||||||
IndicatorAPI,
|
IndicatorAPI,
|
||||||
NotificationAPI,
|
NotificationAPI,
|
||||||
ContextMenuAPI,
|
EditorAPI,
|
||||||
EditorAPI
|
MenuAPI,
|
||||||
|
ActionsAPI,
|
||||||
|
StatusAPI
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
TimeAPI: TimeAPI,
|
TimeAPI: TimeAPI,
|
||||||
@ -51,6 +54,8 @@ define([
|
|||||||
IndicatorAPI: IndicatorAPI,
|
IndicatorAPI: IndicatorAPI,
|
||||||
NotificationAPI: NotificationAPI.default,
|
NotificationAPI: NotificationAPI.default,
|
||||||
EditorAPI: EditorAPI,
|
EditorAPI: EditorAPI,
|
||||||
ContextMenuRegistry: ContextMenuAPI.default
|
MenuAPI: MenuAPI.default,
|
||||||
|
ActionsAPI: ActionsAPI.default,
|
||||||
|
StatusAPI: StatusAPI.default
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="c-menu">
|
|
||||||
<ul>
|
|
||||||
<li
|
|
||||||
v-for="action in actions"
|
|
||||||
:key="action.name"
|
|
||||||
:class="action.cssClass"
|
|
||||||
:title="action.description"
|
|
||||||
@click="action.invoke(objectPath)"
|
|
||||||
>
|
|
||||||
{{ action.name }}
|
|
||||||
</li>
|
|
||||||
<li v-if="actions.length === 0">
|
|
||||||
No actions defined.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
inject: ['actions', 'objectPath']
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,159 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
import ContextMenuComponent from './ContextMenu.vue';
|
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ContextMenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
|
|
||||||
* custom HTML elements.
|
|
||||||
* @interface ContextMenuAPI
|
|
||||||
* @memberof module:openmct
|
|
||||||
*/
|
|
||||||
class ContextMenuAPI {
|
|
||||||
constructor() {
|
|
||||||
this._allActions = [];
|
|
||||||
this._activeContextMenu = undefined;
|
|
||||||
|
|
||||||
this._hideActiveContextMenu = this._hideActiveContextMenu.bind(this);
|
|
||||||
this.registerAction = this.registerAction.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines an item to be added to context menus. Allows specification of text, appearance, and behavior when
|
|
||||||
* selected. Applicabilioty can be restricted by specification of an `appliesTo` function.
|
|
||||||
*
|
|
||||||
* @interface ContextMenuAction
|
|
||||||
* @memberof module:openmct
|
|
||||||
* @property {string} name the human-readable name of this view
|
|
||||||
* @property {string} description a longer-form description (typically
|
|
||||||
* a single sentence or short paragraph) of this kind of view
|
|
||||||
* @property {string} cssClass the CSS class to apply to labels for this
|
|
||||||
* view (to add icons, for instance)
|
|
||||||
* @property {string} key unique key to identify the context menu action
|
|
||||||
* (used in custom context menu eg table rows, to identify which actions to include)
|
|
||||||
* @property {boolean} hideInDefaultMenu optional flag to hide action from showing in the default context menu (tree item)
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @method appliesTo
|
|
||||||
* @memberof module:openmct.ContextMenuAction#
|
|
||||||
* @param {DomainObject[]} objectPath the path of the object that the context menu has been invoked on.
|
|
||||||
* @returns {boolean} true if the action applies to the objects specified in the 'objectPath', otherwise false.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Code to be executed when the action is selected from a context menu
|
|
||||||
* @method invoke
|
|
||||||
* @memberof module:openmct.ContextMenuAction#
|
|
||||||
* @param {DomainObject[]} objectPath the path of the object to invoke the action on.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @param {ContextMenuAction} actionDefinition
|
|
||||||
*/
|
|
||||||
registerAction(actionDefinition) {
|
|
||||||
this._allActions.push(actionDefinition);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_showContextMenuForObjectPath(objectPath, x, y, actionsToBeIncluded) {
|
|
||||||
|
|
||||||
let applicableActions = this._allActions.filter((action) => {
|
|
||||||
|
|
||||||
if (actionsToBeIncluded) {
|
|
||||||
if (action.appliesTo === undefined && actionsToBeIncluded.includes(action.key)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return action.appliesTo(objectPath, actionsToBeIncluded) && actionsToBeIncluded.includes(action.key);
|
|
||||||
} else {
|
|
||||||
if (action.appliesTo === undefined) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return action.appliesTo(objectPath) && !action.hideInDefaultMenu;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this._activeContextMenu) {
|
|
||||||
this._hideActiveContextMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._activeContextMenu = this._createContextMenuForObject(objectPath, applicableActions);
|
|
||||||
this._activeContextMenu.$mount();
|
|
||||||
document.body.appendChild(this._activeContextMenu.$el);
|
|
||||||
|
|
||||||
let position = this._calculatePopupPosition(x, y, this._activeContextMenu.$el);
|
|
||||||
this._activeContextMenu.$el.style.left = `${position.x}px`;
|
|
||||||
this._activeContextMenu.$el.style.top = `${position.y}px`;
|
|
||||||
|
|
||||||
document.addEventListener('click', this._hideActiveContextMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_calculatePopupPosition(eventPosX, eventPosY, menuElement) {
|
|
||||||
let menuDimensions = menuElement.getBoundingClientRect();
|
|
||||||
let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth;
|
|
||||||
let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight;
|
|
||||||
|
|
||||||
if (overflowX > 0) {
|
|
||||||
eventPosX = eventPosX - overflowX;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overflowY > 0) {
|
|
||||||
eventPosY = eventPosY - overflowY;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: eventPosX,
|
|
||||||
y: eventPosY
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_hideActiveContextMenu() {
|
|
||||||
document.removeEventListener('click', this._hideActiveContextMenu);
|
|
||||||
document.body.removeChild(this._activeContextMenu.$el);
|
|
||||||
this._activeContextMenu.$destroy();
|
|
||||||
this._activeContextMenu = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_createContextMenuForObject(objectPath, actions) {
|
|
||||||
return new Vue({
|
|
||||||
components: {
|
|
||||||
ContextMenu: ContextMenuComponent
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
actions: actions,
|
|
||||||
objectPath: objectPath
|
|
||||||
},
|
|
||||||
template: '<ContextMenu></ContextMenu>'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default ContextMenuAPI;
|
|
67
src/api/menu/MenuAPI.js
Normal file
67
src/api/menu/MenuAPI.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 Menu from './menu.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
|
||||||
|
* custom HTML elements.
|
||||||
|
* @interface MenuAPI
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
|
||||||
|
class MenuAPI {
|
||||||
|
constructor(openmct) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
|
||||||
|
this.showMenu = this.showMenu.bind(this);
|
||||||
|
this._clearMenuComponent = this._clearMenuComponent.bind(this);
|
||||||
|
this._showObjectMenu = this._showObjectMenu.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
showMenu(x, y, actions) {
|
||||||
|
if (this.menuComponent) {
|
||||||
|
this.menuComponent.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
actions
|
||||||
|
};
|
||||||
|
|
||||||
|
this.menuComponent = new Menu(options);
|
||||||
|
this.menuComponent.once('destroy', this._clearMenuComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearMenuComponent() {
|
||||||
|
this.menuComponent = undefined;
|
||||||
|
delete this.menuComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
_showObjectMenu(objectPath, x, y, actionsToBeIncluded) {
|
||||||
|
let applicableActions = this.openmct.actions._groupedAndSortedObjectActions(objectPath, actionsToBeIncluded);
|
||||||
|
|
||||||
|
this.showMenu(x, y, applicableActions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default MenuAPI;
|
125
src/api/menu/MenuAPISpec.js
Normal file
125
src/api/menu/MenuAPISpec.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import MenuAPI from './MenuAPI';
|
||||||
|
import Menu from './menu';
|
||||||
|
import { createOpenMct, resetApplicationState } from '../../utils/testing';
|
||||||
|
|
||||||
|
describe ('The Menu API', () => {
|
||||||
|
let openmct;
|
||||||
|
let menuAPI;
|
||||||
|
let actionsArray;
|
||||||
|
let x;
|
||||||
|
let y;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
openmct = createOpenMct();
|
||||||
|
menuAPI = new MenuAPI(openmct);
|
||||||
|
actionsArray = [
|
||||||
|
{
|
||||||
|
name: 'Test Action 1',
|
||||||
|
cssClass: 'test-css-class-1',
|
||||||
|
description: 'This is a test action',
|
||||||
|
callBack: () => {
|
||||||
|
result = 'Test Action 1 Invoked';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Test Action 2',
|
||||||
|
cssClass: 'test-css-class-2',
|
||||||
|
description: 'This is a test action',
|
||||||
|
callBack: () => {
|
||||||
|
result = 'Test Action 2 Invoked';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
x = 8;
|
||||||
|
y = 16;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("showMenu method", () => {
|
||||||
|
it("creates an instance of Menu when invoked", () => {
|
||||||
|
menuAPI.showMenu(x, y, actionsArray);
|
||||||
|
|
||||||
|
expect(menuAPI.menuComponent).toBeInstanceOf(Menu);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("creates a menu component", () => {
|
||||||
|
let menuComponent;
|
||||||
|
let vueComponent;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
menuAPI.showMenu(x, y, actionsArray);
|
||||||
|
vueComponent = menuAPI.menuComponent.component;
|
||||||
|
menuComponent = document.querySelector(".c-menu");
|
||||||
|
|
||||||
|
spyOn(vueComponent, '$destroy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders a menu component in the expected x and y coordinates", () => {
|
||||||
|
let boundingClientRect = menuComponent.getBoundingClientRect();
|
||||||
|
let left = boundingClientRect.left;
|
||||||
|
let top = boundingClientRect.top;
|
||||||
|
|
||||||
|
expect(left).toEqual(x);
|
||||||
|
expect(top).toEqual(y);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with all the actions passed in", () => {
|
||||||
|
expect(menuComponent).toBeDefined();
|
||||||
|
|
||||||
|
let listItems = menuComponent.children[0].children;
|
||||||
|
|
||||||
|
expect(listItems.length).toEqual(actionsArray.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with click-able menu items, that will invoke the correct callBacks", () => {
|
||||||
|
let listItem1 = menuComponent.children[0].children[0];
|
||||||
|
|
||||||
|
listItem1.click();
|
||||||
|
|
||||||
|
expect(result).toEqual("Test Action 1 Invoked");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("dismisses the menu when action is clicked on", () => {
|
||||||
|
let listItem1 = menuComponent.children[0].children[0];
|
||||||
|
|
||||||
|
listItem1.click();
|
||||||
|
|
||||||
|
let menu = document.querySelector('.c-menu');
|
||||||
|
|
||||||
|
expect(menu).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("invokes the destroy method when menu is dismissed", () => {
|
||||||
|
document.body.click();
|
||||||
|
|
||||||
|
expect(vueComponent.$destroy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
52
src/api/menu/components/Menu.vue
Normal file
52
src/api/menu/components/Menu.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<div class="c-menu">
|
||||||
|
<ul v-if="actions.length && actions[0].length">
|
||||||
|
<template
|
||||||
|
v-for="(actionGroups, index) in actions"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="action in actionGroups"
|
||||||
|
:key="action.name"
|
||||||
|
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||||
|
:title="action.description"
|
||||||
|
@click="action.callBack"
|
||||||
|
>
|
||||||
|
{{ action.name }}
|
||||||
|
</li>
|
||||||
|
<div
|
||||||
|
v-if="index !== actions.length - 1"
|
||||||
|
:key="index"
|
||||||
|
class="c-menu__section-separator"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<li
|
||||||
|
v-if="actionGroups.length === 0"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
No actions defined.
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul v-else>
|
||||||
|
<li
|
||||||
|
v-for="action in actions"
|
||||||
|
:key="action.name"
|
||||||
|
:class="action.cssClass"
|
||||||
|
:title="action.description"
|
||||||
|
@click="action.callBack"
|
||||||
|
>
|
||||||
|
{{ action.name }}
|
||||||
|
</li>
|
||||||
|
<li v-if="actions.length === 0">
|
||||||
|
No actions defined.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['actions']
|
||||||
|
};
|
||||||
|
</script>
|
94
src/api/menu/menu.js
Normal file
94
src/api/menu/menu.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
import MenuComponent from './components/Menu.vue';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
class Menu extends EventEmitter {
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
this.component = new Vue({
|
||||||
|
provide: {
|
||||||
|
actions: options.actions
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MenuComponent
|
||||||
|
},
|
||||||
|
template: '<menu-component />'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.onDestroy) {
|
||||||
|
this.once('destroy', options.onDestroy);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dismiss = this.dismiss.bind(this);
|
||||||
|
this.show = this.show.bind(this);
|
||||||
|
|
||||||
|
this.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss() {
|
||||||
|
this.emit('destroy');
|
||||||
|
document.body.removeChild(this.component.$el);
|
||||||
|
document.removeEventListener('click', this.dismiss);
|
||||||
|
this.component.$destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
show() {
|
||||||
|
this.component.$mount();
|
||||||
|
document.body.appendChild(this.component.$el);
|
||||||
|
|
||||||
|
let position = this._calculatePopupPosition(this.options.x, this.options.y, this.component.$el);
|
||||||
|
|
||||||
|
this.component.$el.style.left = `${position.x}px`;
|
||||||
|
this.component.$el.style.top = `${position.y}px`;
|
||||||
|
|
||||||
|
document.addEventListener('click', this.dismiss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_calculatePopupPosition(eventPosX, eventPosY, menuElement) {
|
||||||
|
let menuDimensions = menuElement.getBoundingClientRect();
|
||||||
|
let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth;
|
||||||
|
let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight;
|
||||||
|
|
||||||
|
if (overflowX > 0) {
|
||||||
|
eventPosX = eventPosX - overflowX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflowY > 0) {
|
||||||
|
eventPosY = eventPosY - overflowY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: eventPosX,
|
||||||
|
y: eventPosY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Menu;
|
@ -47,6 +47,7 @@ define([
|
|||||||
this.providers = {};
|
this.providers = {};
|
||||||
this.rootRegistry = new RootRegistry();
|
this.rootRegistry = new RootRegistry();
|
||||||
this.rootProvider = new RootObjectProvider.default(this.rootRegistry);
|
this.rootProvider = new RootObjectProvider.default(this.rootRegistry);
|
||||||
|
this.cache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,6 +155,11 @@ define([
|
|||||||
* has been saved, or be rejected if it cannot be saved
|
* has been saved, or be rejected if it cannot be saved
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.get = function (identifier) {
|
ObjectAPI.prototype.get = function (identifier) {
|
||||||
|
let keystring = this.makeKeyString(identifier);
|
||||||
|
if (this.cache[keystring] !== undefined) {
|
||||||
|
return this.cache[keystring];
|
||||||
|
}
|
||||||
|
|
||||||
identifier = utils.parseKeyString(identifier);
|
identifier = utils.parseKeyString(identifier);
|
||||||
const provider = this.getProvider(identifier);
|
const provider = this.getProvider(identifier);
|
||||||
|
|
||||||
@ -165,7 +171,15 @@ define([
|
|||||||
throw new Error('Provider does not support get!');
|
throw new Error('Provider does not support get!');
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider.get(identifier);
|
let objectPromise = provider.get(identifier);
|
||||||
|
|
||||||
|
this.cache[keystring] = objectPromise;
|
||||||
|
|
||||||
|
return objectPromise.then(result => {
|
||||||
|
delete this.cache[keystring];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ObjectAPI.prototype.delete = function () {
|
ObjectAPI.prototype.delete = function () {
|
||||||
|
@ -59,4 +59,25 @@ describe("The Object API", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("The get function", () => {
|
||||||
|
describe("when a provider is available", () => {
|
||||||
|
let mockProvider;
|
||||||
|
beforeEach(() => {
|
||||||
|
mockProvider = jasmine.createSpyObj("mock provider", [
|
||||||
|
"get"
|
||||||
|
]);
|
||||||
|
mockProvider.get.and.returnValue(Promise.resolve(mockDomainObject));
|
||||||
|
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Caches multiple requests for the same object", () => {
|
||||||
|
expect(mockProvider.get.calls.count()).toBe(0);
|
||||||
|
objectAPI.get(mockDomainObject.identifier);
|
||||||
|
expect(mockProvider.get.calls.count()).toBe(1);
|
||||||
|
objectAPI.get(mockDomainObject.identifier);
|
||||||
|
expect(mockProvider.get.calls.count()).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,7 @@ class OverlayAPI {
|
|||||||
this.dismissLastOverlay();
|
this.dismissLastOverlay();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,6 +128,7 @@ class OverlayAPI {
|
|||||||
|
|
||||||
return progressDialog;
|
return progressDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OverlayAPI;
|
export default OverlayAPI;
|
||||||
|
67
src/api/status/StatusAPI.js
Normal file
67
src/api/status/StatusAPI.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
|
||||||
|
export default class StatusAPI extends EventEmitter {
|
||||||
|
constructor(openmct) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._openmct = openmct;
|
||||||
|
this._statusCache = {};
|
||||||
|
|
||||||
|
this.get = this.get.bind(this);
|
||||||
|
this.set = this.set.bind(this);
|
||||||
|
this.observe = this.observe.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(identifier) {
|
||||||
|
let keyString = this._openmct.objects.makeKeyString(identifier);
|
||||||
|
|
||||||
|
return this._statusCache[keyString];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(identifier, value) {
|
||||||
|
let keyString = this._openmct.objects.makeKeyString(identifier);
|
||||||
|
|
||||||
|
this._statusCache[keyString] = value;
|
||||||
|
this.emit(keyString, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(identifier) {
|
||||||
|
let keyString = this._openmct.objects.makeKeyString(identifier);
|
||||||
|
|
||||||
|
this._statusCache[keyString] = undefined;
|
||||||
|
this.emit(keyString, undefined);
|
||||||
|
delete this._statusCache[keyString];
|
||||||
|
}
|
||||||
|
|
||||||
|
observe(identifier, callback) {
|
||||||
|
let key = this._openmct.objects.makeKeyString(identifier);
|
||||||
|
|
||||||
|
this.on(key, callback);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.off(key, callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
85
src/api/status/StatusAPISpec.js
Normal file
85
src/api/status/StatusAPISpec.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import StatusAPI from './StatusAPI.js';
|
||||||
|
import { createOpenMct, resetApplicationState } from '../../utils/testing';
|
||||||
|
|
||||||
|
describe("The Status API", () => {
|
||||||
|
let statusAPI;
|
||||||
|
let openmct;
|
||||||
|
let identifier;
|
||||||
|
let status;
|
||||||
|
let status2;
|
||||||
|
let callback;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
openmct = createOpenMct();
|
||||||
|
statusAPI = new StatusAPI(openmct);
|
||||||
|
identifier = {
|
||||||
|
namespace: "test-namespace",
|
||||||
|
key: "test-key"
|
||||||
|
};
|
||||||
|
status = "test-status";
|
||||||
|
status2 = 'test-status-deux';
|
||||||
|
callback = jasmine.createSpy('callback', (statusUpdate) => statusUpdate);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("set function", () => {
|
||||||
|
it("sets status for identifier", () => {
|
||||||
|
statusAPI.set(identifier, status);
|
||||||
|
|
||||||
|
let resultingStatus = statusAPI.get(identifier);
|
||||||
|
|
||||||
|
expect(resultingStatus).toEqual(status);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("get function", () => {
|
||||||
|
it("returns status for identifier", () => {
|
||||||
|
statusAPI.set(identifier, status2);
|
||||||
|
|
||||||
|
let resultingStatus = statusAPI.get(identifier);
|
||||||
|
|
||||||
|
expect(resultingStatus).toEqual(status2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("delete function", () => {
|
||||||
|
it("deletes status for identifier", () => {
|
||||||
|
statusAPI.set(identifier, status);
|
||||||
|
|
||||||
|
let resultingStatus = statusAPI.get(identifier);
|
||||||
|
expect(resultingStatus).toEqual(status);
|
||||||
|
|
||||||
|
statusAPI.delete(identifier);
|
||||||
|
resultingStatus = statusAPI.get(identifier);
|
||||||
|
|
||||||
|
expect(resultingStatus).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("observe function", () => {
|
||||||
|
|
||||||
|
it("allows callbacks to be attached to status set and delete events", () => {
|
||||||
|
let unsubscribe = statusAPI.observe(identifier, callback);
|
||||||
|
statusAPI.set(identifier, status);
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalledWith(status);
|
||||||
|
|
||||||
|
statusAPI.delete(identifier);
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalledWith(undefined);
|
||||||
|
unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns a unsubscribe function", () => {
|
||||||
|
let unsubscribe = statusAPI.observe(identifier, callback);
|
||||||
|
unsubscribe();
|
||||||
|
|
||||||
|
statusAPI.set(identifier, status);
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -176,7 +176,10 @@ export default {
|
|||||||
this.timestampKey = timeSystem.key;
|
this.timestampKey = timeSystem.key;
|
||||||
},
|
},
|
||||||
showContextMenu(event) {
|
showContextMenu(event) {
|
||||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
|
let allActions = this.openmct.actions.get(this.currentObjectPath, {}, {viewHistoricalData: true});
|
||||||
|
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
|
||||||
|
|
||||||
|
this.openmct.menus.showMenu(event.x, event.y, applicableActions);
|
||||||
},
|
},
|
||||||
resetValues() {
|
resetValues() {
|
||||||
this.value = '---';
|
this.value = '---';
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-lad-table-wrapper">
|
<div class="c-lad-table-wrapper u-style-receiver js-style-receiver">
|
||||||
<table class="c-table c-lad-table">
|
<table class="c-table c-lad-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
export default class ClearDataAction {
|
export default class ClearDataAction {
|
||||||
constructor(openmct, appliesToObjects) {
|
constructor(openmct, appliesToObjects) {
|
||||||
this.name = 'Clear Data for Object';
|
this.name = 'Clear Data for Object';
|
||||||
|
this.key = 'clear-data-action';
|
||||||
this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
|
this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
|
||||||
this.cssClass = 'icon-clear-data';
|
this.cssClass = 'icon-clear-data';
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ define([
|
|||||||
openmct.indicators.add(indicator);
|
openmct.indicators.add(indicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
|
openmct.actions.register(new ClearDataAction.default(openmct, appliesToObjects));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -26,12 +26,12 @@ import ClearDataAction from '../clearDataAction.js';
|
|||||||
describe('When the Clear Data Plugin is installed,', function () {
|
describe('When the Clear Data Plugin is installed,', function () {
|
||||||
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
|
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
|
||||||
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
|
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
|
||||||
const mockContextMenuProvider = jasmine.createSpyObj('contextMenu', ['registerAction']);
|
const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
|
||||||
|
|
||||||
const openmct = {
|
const openmct = {
|
||||||
objectViews: mockObjectViews,
|
objectViews: mockObjectViews,
|
||||||
indicators: mockIndicatorProvider,
|
indicators: mockIndicatorProvider,
|
||||||
contextMenu: mockContextMenuProvider,
|
actions: mockActionsProvider,
|
||||||
install: function (plugin) {
|
install: function (plugin) {
|
||||||
plugin(this);
|
plugin(this);
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ describe('When the Clear Data Plugin is installed,', function () {
|
|||||||
it('Clear Data context menu action is installed', function () {
|
it('Clear Data context menu action is installed', function () {
|
||||||
openmct.install(ClearDataActionPlugin([]));
|
openmct.install(ClearDataActionPlugin([]));
|
||||||
|
|
||||||
expect(mockContextMenuProvider.registerAction).toHaveBeenCalled();
|
expect(mockActionsProvider.register).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clear data action emits a clearData event when invoked', function () {
|
it('clear data action emits a clearData event when invoked', function () {
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
.c-cs {
|
.c-cs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex: 1 1 auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
@ -21,9 +21,10 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-style">
|
<div class="c-style has-local-controls c-toolbar">
|
||||||
<span :class="[
|
<div class="c-style__controls">
|
||||||
{ 'is-style-invisible': styleItem.style.isStyleInvisible },
|
<div :class="[
|
||||||
|
{ 'is-style-invisible': styleItem.style && styleItem.style.isStyleInvisible },
|
||||||
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
|
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
|
||||||
]"
|
]"
|
||||||
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
|
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
|
||||||
@ -34,8 +35,8 @@
|
|||||||
>
|
>
|
||||||
ABC
|
ABC
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
<span class="c-toolbar">
|
|
||||||
<toolbar-color-picker v-if="hasProperty(styleItem.style.border)"
|
<toolbar-color-picker v-if="hasProperty(styleItem.style.border)"
|
||||||
class="c-style__toolbar-button--border-color u-menu-to--center"
|
class="c-style__toolbar-button--border-color u-menu-to--center"
|
||||||
:options="borderColorOption"
|
:options="borderColorOption"
|
||||||
@ -61,7 +62,14 @@
|
|||||||
:options="isStyleInvisibleOption"
|
:options="isStyleInvisibleOption"
|
||||||
@change="updateStyleValue"
|
@change="updateStyleValue"
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
|
<!-- Save Styles -->
|
||||||
|
<toolbar-button v-if="canSaveStyle"
|
||||||
|
class="c-style__toolbar-button--save c-local-controls--show-on-hover c-icon-button c-icon-button--major"
|
||||||
|
:options="saveOptions"
|
||||||
|
@click="saveItemStyle()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -80,12 +88,11 @@ export default {
|
|||||||
ToolbarColorPicker,
|
ToolbarColorPicker,
|
||||||
ToolbarToggleButton
|
ToolbarToggleButton
|
||||||
},
|
},
|
||||||
inject: [
|
inject: ['openmct'],
|
||||||
'openmct'
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
isEditing: {
|
isEditing: {
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
mixedStyles: {
|
mixedStyles: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -93,6 +100,10 @@ export default {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
nonSpecificFontProperties: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
styleItem: {
|
styleItem: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
@ -182,7 +193,16 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
saveOptions() {
|
||||||
|
return {
|
||||||
|
icon: 'icon-save',
|
||||||
|
title: 'Save style',
|
||||||
|
isEditing: this.isEditing
|
||||||
|
};
|
||||||
|
},
|
||||||
|
canSaveStyle() {
|
||||||
|
return this.isEditing && !this.mixedStyles.length && !this.nonSpecificFontProperties.length;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -216,6 +236,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('persist', this.styleItem, item.property);
|
this.$emit('persist', this.styleItem, item.property);
|
||||||
|
},
|
||||||
|
saveItemStyle() {
|
||||||
|
this.$emit('save-style', this.itemStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -31,6 +31,11 @@
|
|||||||
<div class="c-inspect-styles__header">
|
<div class="c-inspect-styles__header">
|
||||||
Object Style
|
Object Style
|
||||||
</div>
|
</div>
|
||||||
|
<FontStyleEditor
|
||||||
|
v-if="canStyleFont"
|
||||||
|
:font-style="consolidatedFontStyle"
|
||||||
|
@set-font-property="setFontProperty"
|
||||||
|
/>
|
||||||
<div class="c-inspect-styles__content">
|
<div class="c-inspect-styles__content">
|
||||||
<div v-if="staticStyle"
|
<div v-if="staticStyle"
|
||||||
class="c-inspect-styles__style"
|
class="c-inspect-styles__style"
|
||||||
@ -39,7 +44,9 @@
|
|||||||
:style-item="staticStyle"
|
:style-item="staticStyle"
|
||||||
:is-editing="allowEditing"
|
:is-editing="allowEditing"
|
||||||
:mixed-styles="mixedStyles"
|
:mixed-styles="mixedStyles"
|
||||||
|
:non-specific-font-properties="nonSpecificFontProperties"
|
||||||
@persist="updateStaticStyle"
|
@persist="updateStaticStyle"
|
||||||
|
@save-style="saveStyle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -58,10 +65,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||||
<a v-if="conditionSetDomainObject"
|
<a v-if="conditionSetDomainObject"
|
||||||
class="c-object-label icon-conditional"
|
class="c-object-label"
|
||||||
:href="navigateToPath"
|
:href="navigateToPath"
|
||||||
@click="navigateOrPreview"
|
@click="navigateOrPreview"
|
||||||
>
|
>
|
||||||
|
<span class="c-object-label__type-icon icon-conditional"></span>
|
||||||
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
<template v-if="allowEditing">
|
<template v-if="allowEditing">
|
||||||
@ -80,6 +88,12 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<FontStyleEditor
|
||||||
|
v-if="canStyleFont"
|
||||||
|
:font-style="consolidatedFontStyle"
|
||||||
|
@set-font-property="setFontProperty"
|
||||||
|
/>
|
||||||
|
|
||||||
<div v-if="conditionsLoaded"
|
<div v-if="conditionsLoaded"
|
||||||
class="c-inspect-styles__conditions"
|
class="c-inspect-styles__conditions"
|
||||||
>
|
>
|
||||||
@ -97,8 +111,10 @@
|
|||||||
/>
|
/>
|
||||||
<style-editor class="c-inspect-styles__editor"
|
<style-editor class="c-inspect-styles__editor"
|
||||||
:style-item="conditionStyle"
|
:style-item="conditionStyle"
|
||||||
|
:non-specific-font-properties="nonSpecificFontProperties"
|
||||||
:is-editing="allowEditing"
|
:is-editing="allowEditing"
|
||||||
@persist="updateConditionalStyle"
|
@persist="updateConditionalStyle"
|
||||||
|
@save-style="saveStyle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -108,6 +124,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import FontStyleEditor from '@/ui/inspector/styles/FontStyleEditor.vue';
|
||||||
import StyleEditor from "./StyleEditor.vue";
|
import StyleEditor from "./StyleEditor.vue";
|
||||||
import PreviewAction from "@/ui/preview/PreviewAction.js";
|
import PreviewAction from "@/ui/preview/PreviewAction.js";
|
||||||
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionSetIdentifierForItem } from "@/plugins/condition/utils/styleUtils";
|
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionSetIdentifierForItem } from "@/plugins/condition/utils/styleUtils";
|
||||||
@ -116,16 +133,30 @@ import ConditionError from "@/plugins/condition/components/ConditionError.vue";
|
|||||||
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
|
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
const NON_SPECIFIC = '??';
|
||||||
|
const NON_STYLEABLE_CONTAINER_TYPES = [
|
||||||
|
'layout',
|
||||||
|
'flexible-layout',
|
||||||
|
'tabs'
|
||||||
|
];
|
||||||
|
const NON_STYLEABLE_LAYOUT_ITEM_TYPES = [
|
||||||
|
'line-view',
|
||||||
|
'box-view',
|
||||||
|
'image-view'
|
||||||
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'StylesView',
|
name: 'StylesView',
|
||||||
components: {
|
components: {
|
||||||
|
FontStyleEditor,
|
||||||
StyleEditor,
|
StyleEditor,
|
||||||
ConditionError,
|
ConditionError,
|
||||||
ConditionDescription
|
ConditionDescription
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
'openmct',
|
'openmct',
|
||||||
'selection'
|
'selection',
|
||||||
|
'stylesManager'
|
||||||
],
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -139,19 +170,80 @@ export default {
|
|||||||
conditionsLoaded: false,
|
conditionsLoaded: false,
|
||||||
navigateToPath: '',
|
navigateToPath: '',
|
||||||
selectedConditionId: '',
|
selectedConditionId: '',
|
||||||
locked: false
|
items: [],
|
||||||
|
domainObject: undefined,
|
||||||
|
consolidatedFontStyle: {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
locked() {
|
||||||
|
return this.selection.some(selectionPath => {
|
||||||
|
const self = selectionPath[0].context.item;
|
||||||
|
const parent = selectionPath.length > 1 ? selectionPath[1].context.item : undefined;
|
||||||
|
|
||||||
|
return (self && self.locked) || (parent && parent.locked);
|
||||||
|
});
|
||||||
|
},
|
||||||
allowEditing() {
|
allowEditing() {
|
||||||
return this.isEditing && !this.locked;
|
return this.isEditing && !this.locked;
|
||||||
|
},
|
||||||
|
styleableFontItems() {
|
||||||
|
return this.selection.filter(selectionPath => {
|
||||||
|
const item = selectionPath[0].context.item;
|
||||||
|
const itemType = item && item.type;
|
||||||
|
const layoutItem = selectionPath[0].context.layoutItem;
|
||||||
|
const layoutItemType = layoutItem && layoutItem.type;
|
||||||
|
|
||||||
|
if (itemType && NON_STYLEABLE_CONTAINER_TYPES.includes(itemType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layoutItemType && NON_STYLEABLE_LAYOUT_ITEM_TYPES.includes(layoutItemType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
computedconsolidatedFontStyle() {
|
||||||
|
let consolidatedFontStyle;
|
||||||
|
const styles = [];
|
||||||
|
|
||||||
|
this.styleableFontItems.forEach(styleable => {
|
||||||
|
const fontStyle = this.getFontStyle(styleable[0]);
|
||||||
|
|
||||||
|
styles.push(fontStyle);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (styles.length) {
|
||||||
|
const hasConsolidatedFontSize = styles.length && styles.every((fontStyle, i, arr) => fontStyle.fontSize === arr[0].fontSize);
|
||||||
|
const hasConsolidatedFont = styles.length && styles.every((fontStyle, i, arr) => fontStyle.font === arr[0].font);
|
||||||
|
|
||||||
|
consolidatedFontStyle = {
|
||||||
|
fontSize: hasConsolidatedFontSize ? styles[0].fontSize : NON_SPECIFIC,
|
||||||
|
font: hasConsolidatedFont ? styles[0].font : NON_SPECIFIC
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return consolidatedFontStyle;
|
||||||
|
},
|
||||||
|
nonSpecificFontProperties() {
|
||||||
|
if (!this.consolidatedFontStyle) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(this.consolidatedFontStyle).filter(property => this.consolidatedFontStyle[property] === NON_SPECIFIC);
|
||||||
|
},
|
||||||
|
canStyleFont() {
|
||||||
|
return this.styleableFontItems.length && this.allowEditing;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.removeListeners();
|
this.removeListeners();
|
||||||
|
this.openmct.editor.off('isEditing', this.setEditState);
|
||||||
|
this.stylesManager.off('styleSelected', this.applyStyleToSelection);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.items = [];
|
|
||||||
this.previewAction = new PreviewAction(this.openmct);
|
this.previewAction = new PreviewAction(this.openmct);
|
||||||
this.isMultipleSelection = this.selection.length > 1;
|
this.isMultipleSelection = this.selection.length > 1;
|
||||||
this.getObjectsAndItemsFromSelection();
|
this.getObjectsAndItemsFromSelection();
|
||||||
@ -166,7 +258,10 @@ export default {
|
|||||||
this.initializeStaticStyle();
|
this.initializeStaticStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setConsolidatedFontStyle();
|
||||||
|
|
||||||
this.openmct.editor.on('isEditing', this.setEditState);
|
this.openmct.editor.on('isEditing', this.setEditState);
|
||||||
|
this.stylesManager.on('styleSelected', this.applyStyleToSelection);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getObjectStyles() {
|
getObjectStyles() {
|
||||||
@ -178,10 +273,10 @@ export default {
|
|||||||
}
|
}
|
||||||
} else if (this.items.length) {
|
} else if (this.items.length) {
|
||||||
const itemId = this.items[0].id;
|
const itemId = this.items[0].id;
|
||||||
if (this.domainObject.configuration && this.domainObject.configuration.objectStyles && this.domainObject.configuration.objectStyles[itemId]) {
|
if (this.domainObject && this.domainObject.configuration && this.domainObject.configuration.objectStyles && this.domainObject.configuration.objectStyles[itemId]) {
|
||||||
objectStyles = this.domainObject.configuration.objectStyles[itemId];
|
objectStyles = this.domainObject.configuration.objectStyles[itemId];
|
||||||
}
|
}
|
||||||
} else if (this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
|
} else if (this.domainObject && this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
|
||||||
objectStyles = this.domainObject.configuration.objectStyles;
|
objectStyles = this.domainObject.configuration.objectStyles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +314,18 @@ export default {
|
|||||||
isItemType(type, item) {
|
isItemType(type, item) {
|
||||||
return item && (item.type === type);
|
return item && (item.type === type);
|
||||||
},
|
},
|
||||||
|
canPersistObject(item) {
|
||||||
|
// for now the only way to tell if an object can be persisted is if it is creatable.
|
||||||
|
let creatable = false;
|
||||||
|
if (item) {
|
||||||
|
const type = this.openmct.types.get(item.type);
|
||||||
|
if (type && type.definition) {
|
||||||
|
creatable = (type.definition.creatable === true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return creatable;
|
||||||
|
},
|
||||||
hasConditionalStyle(domainObject, layoutItem) {
|
hasConditionalStyle(domainObject, layoutItem) {
|
||||||
const id = layoutItem ? layoutItem.id : undefined;
|
const id = layoutItem ? layoutItem.id : undefined;
|
||||||
|
|
||||||
@ -235,13 +342,8 @@ export default {
|
|||||||
this.selection.forEach((selectionItem) => {
|
this.selection.forEach((selectionItem) => {
|
||||||
const item = selectionItem[0].context.item;
|
const item = selectionItem[0].context.item;
|
||||||
const layoutItem = selectionItem[0].context.layoutItem;
|
const layoutItem = selectionItem[0].context.layoutItem;
|
||||||
const layoutDomainObject = selectionItem[0].context.item;
|
|
||||||
const isChildItem = selectionItem.length > 1;
|
const isChildItem = selectionItem.length > 1;
|
||||||
|
|
||||||
if (layoutDomainObject && layoutDomainObject.locked) {
|
|
||||||
this.locked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isChildItem) {
|
if (!isChildItem) {
|
||||||
domainObject = item;
|
domainObject = item;
|
||||||
itemStyle = getApplicableStylesForItem(item);
|
itemStyle = getApplicableStylesForItem(item);
|
||||||
@ -251,7 +353,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.canHide = true;
|
this.canHide = true;
|
||||||
domainObject = selectionItem[1].context.item;
|
domainObject = selectionItem[1].context.item;
|
||||||
if (item && !layoutItem || this.isItemType('subobject-view', layoutItem)) {
|
if (item && !layoutItem || (this.isItemType('subobject-view', layoutItem) && this.canPersistObject(item))) {
|
||||||
subObjects.push(item);
|
subObjects.push(item);
|
||||||
itemStyle = getApplicableStylesForItem(item);
|
itemStyle = getApplicableStylesForItem(item);
|
||||||
if (this.hasConditionalStyle(item)) {
|
if (this.hasConditionalStyle(item)) {
|
||||||
@ -275,7 +377,7 @@ export default {
|
|||||||
const {styles, mixedStyles} = getConsolidatedStyleValues(itemInitialStyles);
|
const {styles, mixedStyles} = getConsolidatedStyleValues(itemInitialStyles);
|
||||||
this.initialStyles = styles;
|
this.initialStyles = styles;
|
||||||
this.mixedStyles = mixedStyles;
|
this.mixedStyles = mixedStyles;
|
||||||
|
// main layout
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.removeListeners();
|
this.removeListeners();
|
||||||
if (this.domainObject) {
|
if (this.domainObject) {
|
||||||
@ -298,6 +400,7 @@ export default {
|
|||||||
isKeyItemId(key) {
|
isKeyItemId(key) {
|
||||||
return (key !== 'styles')
|
return (key !== 'styles')
|
||||||
&& (key !== 'staticStyle')
|
&& (key !== 'staticStyle')
|
||||||
|
&& (key !== 'fontStyle')
|
||||||
&& (key !== 'defaultConditionId')
|
&& (key !== 'defaultConditionId')
|
||||||
&& (key !== 'selectedConditionId')
|
&& (key !== 'selectedConditionId')
|
||||||
&& (key !== 'conditionSetIdentifier');
|
&& (key !== 'conditionSetIdentifier');
|
||||||
@ -637,6 +740,124 @@ export default {
|
|||||||
},
|
},
|
||||||
persist(domainObject, style) {
|
persist(domainObject, style) {
|
||||||
this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
|
this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
|
||||||
|
},
|
||||||
|
applyStyleToSelection(style) {
|
||||||
|
if (!this.allowEditing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateSelectionFontStyle(style);
|
||||||
|
this.updateSelectionStyle(style);
|
||||||
|
},
|
||||||
|
updateSelectionFontStyle(style) {
|
||||||
|
const fontSizeProperty = {
|
||||||
|
fontSize: style.fontSize
|
||||||
|
};
|
||||||
|
const fontProperty = {
|
||||||
|
font: style.font
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setFontProperty(fontSizeProperty);
|
||||||
|
this.setFontProperty(fontProperty);
|
||||||
|
},
|
||||||
|
updateSelectionStyle(style) {
|
||||||
|
const foundStyle = this.findStyleByConditionId(this.selectedConditionId);
|
||||||
|
|
||||||
|
if (foundStyle && !this.isStaticAndConditionalStyles) {
|
||||||
|
Object.entries(style).forEach(([property, value]) => {
|
||||||
|
if (foundStyle.style[property] !== undefined && foundStyle.style[property] !== value) {
|
||||||
|
foundStyle.style[property] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.getAndPersistStyles();
|
||||||
|
} else {
|
||||||
|
this.removeConditionSet();
|
||||||
|
Object.entries(style).forEach(([property, value]) => {
|
||||||
|
if (this.staticStyle.style[property] !== undefined && this.staticStyle.style[property] !== value) {
|
||||||
|
this.staticStyle.style[property] = value;
|
||||||
|
this.getAndPersistStyles(property);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveStyle(style) {
|
||||||
|
const styleToSave = {
|
||||||
|
...style,
|
||||||
|
...this.consolidatedFontStyle
|
||||||
|
};
|
||||||
|
|
||||||
|
this.stylesManager.save(styleToSave);
|
||||||
|
},
|
||||||
|
setConsolidatedFontStyle() {
|
||||||
|
const styles = [];
|
||||||
|
|
||||||
|
this.styleableFontItems.forEach(styleable => {
|
||||||
|
const fontStyle = this.getFontStyle(styleable[0]);
|
||||||
|
|
||||||
|
styles.push(fontStyle);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (styles.length) {
|
||||||
|
const hasConsolidatedFontSize = styles.length && styles.every((fontStyle, i, arr) => fontStyle.fontSize === arr[0].fontSize);
|
||||||
|
const hasConsolidatedFont = styles.length && styles.every((fontStyle, i, arr) => fontStyle.font === arr[0].font);
|
||||||
|
|
||||||
|
const fontSize = hasConsolidatedFontSize ? styles[0].fontSize : NON_SPECIFIC;
|
||||||
|
const font = hasConsolidatedFont ? styles[0].font : NON_SPECIFIC;
|
||||||
|
|
||||||
|
this.$set(this.consolidatedFontStyle, 'fontSize', fontSize);
|
||||||
|
this.$set(this.consolidatedFontStyle, 'font', font);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFontStyle(selectionPath) {
|
||||||
|
const item = selectionPath.context.item;
|
||||||
|
const layoutItem = selectionPath.context.layoutItem;
|
||||||
|
let fontStyle = item && item.configuration && item.configuration.fontStyle;
|
||||||
|
|
||||||
|
// support for legacy where font styling in layouts only
|
||||||
|
if (!fontStyle) {
|
||||||
|
fontStyle = {
|
||||||
|
fontSize: layoutItem && layoutItem.fontSize || 'default',
|
||||||
|
font: layoutItem && layoutItem.font || 'default'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return fontStyle;
|
||||||
|
},
|
||||||
|
setFontProperty(fontStyleObject) {
|
||||||
|
let layoutDomainObject;
|
||||||
|
const [property, value] = Object.entries(fontStyleObject)[0];
|
||||||
|
|
||||||
|
this.styleableFontItems.forEach(styleable => {
|
||||||
|
if (!this.isLayoutObject(styleable)) {
|
||||||
|
const fontStyle = this.getFontStyle(styleable[0]);
|
||||||
|
fontStyle[property] = value;
|
||||||
|
|
||||||
|
this.openmct.objects.mutate(styleable[0].context.item, 'configuration.fontStyle', fontStyle);
|
||||||
|
} else {
|
||||||
|
// all layoutItems in this context will share same parent layout
|
||||||
|
if (!layoutDomainObject) {
|
||||||
|
layoutDomainObject = styleable[1].context.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save layout item font style to parent layout configuration
|
||||||
|
const layoutItemIndex = styleable[0].context.index;
|
||||||
|
const layoutItemConfiguration = layoutDomainObject.configuration.items[layoutItemIndex];
|
||||||
|
|
||||||
|
layoutItemConfiguration[property] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (layoutDomainObject) {
|
||||||
|
this.openmct.objects.mutate(layoutDomainObject, 'configuration.items', layoutDomainObject.configuration.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync vue component on font update
|
||||||
|
this.$set(this.consolidatedFontStyle, property, value);
|
||||||
|
},
|
||||||
|
isLayoutObject(selectionPath) {
|
||||||
|
const layoutItemType = selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type;
|
||||||
|
|
||||||
|
return layoutItemType && layoutItemType !== 'subobject-view';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -40,9 +40,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__condition-set {
|
&__condition-set {
|
||||||
|
align-items: baseline;
|
||||||
|
border-bottom: 1px solid $colorInteriorBorder;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
padding-bottom: $interiorMargin;
|
||||||
|
|
||||||
.c-object-label {
|
.c-object-label {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
@ -53,7 +55,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__style,
|
&__style {
|
||||||
|
padding-bottom: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
&__condition {
|
&__condition {
|
||||||
padding: $interiorMargin;
|
padding: $interiorMargin;
|
||||||
}
|
}
|
||||||
|
@ -146,6 +146,8 @@ describe('the plugin', function () {
|
|||||||
let displayLayoutItem;
|
let displayLayoutItem;
|
||||||
let lineLayoutItem;
|
let lineLayoutItem;
|
||||||
let boxLayoutItem;
|
let boxLayoutItem;
|
||||||
|
let notCreatableObjectItem;
|
||||||
|
let notCreatableObject;
|
||||||
let selection;
|
let selection;
|
||||||
let component;
|
let component;
|
||||||
let styleViewComponentObject;
|
let styleViewComponentObject;
|
||||||
@ -264,6 +266,19 @@ describe('the plugin', function () {
|
|||||||
"stroke": "#717171",
|
"stroke": "#717171",
|
||||||
"type": "line-view",
|
"type": "line-view",
|
||||||
"id": "57d49a28-7863-43bd-9593-6570758916f0"
|
"id": "57d49a28-7863-43bd-9593-6570758916f0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"width": 32,
|
||||||
|
"height": 18,
|
||||||
|
"x": 36,
|
||||||
|
"y": 8,
|
||||||
|
"identifier": {
|
||||||
|
"key": "~TEST~image",
|
||||||
|
"namespace": "test-space"
|
||||||
|
},
|
||||||
|
"hasFrame": true,
|
||||||
|
"type": "subobject-view",
|
||||||
|
"id": "6d9fe81b-a3ce-4e59-b404-a4a0be1a5d85"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"layoutGrid": [
|
"layoutGrid": [
|
||||||
@ -297,6 +312,52 @@ describe('the plugin', function () {
|
|||||||
"type": "box-view",
|
"type": "box-view",
|
||||||
"id": "89b88746-d325-487b-aec4-11b79afff9e8"
|
"id": "89b88746-d325-487b-aec4-11b79afff9e8"
|
||||||
};
|
};
|
||||||
|
notCreatableObjectItem = {
|
||||||
|
"width": 32,
|
||||||
|
"height": 18,
|
||||||
|
"x": 36,
|
||||||
|
"y": 8,
|
||||||
|
"identifier": {
|
||||||
|
"key": "~TEST~image",
|
||||||
|
"namespace": "test-space"
|
||||||
|
},
|
||||||
|
"hasFrame": true,
|
||||||
|
"type": "subobject-view",
|
||||||
|
"id": "6d9fe81b-a3ce-4e59-b404-a4a0be1a5d85"
|
||||||
|
};
|
||||||
|
notCreatableObject = {
|
||||||
|
"identifier": {
|
||||||
|
"key": "~TEST~image",
|
||||||
|
"namespace": "test-space"
|
||||||
|
},
|
||||||
|
"name": "test~image",
|
||||||
|
"location": "test-space:~TEST",
|
||||||
|
"type": "test.image",
|
||||||
|
"telemetry": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"key": "value",
|
||||||
|
"name": "Value",
|
||||||
|
"hints": {
|
||||||
|
"image": 1,
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
"format": "image",
|
||||||
|
"source": "value"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "utc",
|
||||||
|
"source": "timestamp",
|
||||||
|
"name": "Timestamp",
|
||||||
|
"format": "iso",
|
||||||
|
"hints": {
|
||||||
|
"domain": 1,
|
||||||
|
"priority": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
selection = [
|
selection = [
|
||||||
[{
|
[{
|
||||||
context: {
|
context: {
|
||||||
@ -316,6 +377,19 @@ describe('the plugin', function () {
|
|||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
item: displayLayoutItem,
|
||||||
|
"supportsMultiSelect": true
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
context: {
|
||||||
|
"item": notCreatableObject,
|
||||||
|
"layoutItem": notCreatableObjectItem,
|
||||||
|
"index": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
context: {
|
context: {
|
||||||
item: displayLayoutItem,
|
item: displayLayoutItem,
|
||||||
@ -344,7 +418,7 @@ describe('the plugin', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('initializes the items in the view', () => {
|
it('initializes the items in the view', () => {
|
||||||
expect(styleViewComponentObject.items.length).toBe(2);
|
expect(styleViewComponentObject.items.length).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('initializes conditional styles', () => {
|
it('initializes conditional styles', () => {
|
||||||
@ -363,7 +437,7 @@ describe('the plugin', function () {
|
|||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
expect(styleViewComponentObject.domainObject.configuration.objectStyles).toBeDefined();
|
expect(styleViewComponentObject.domainObject.configuration.objectStyles).toBeDefined();
|
||||||
[boxLayoutItem, lineLayoutItem].forEach((item) => {
|
[boxLayoutItem, lineLayoutItem, notCreatableObjectItem].forEach((item) => {
|
||||||
const itemStyles = styleViewComponentObject.domainObject.configuration.objectStyles[item.id].styles;
|
const itemStyles = styleViewComponentObject.domainObject.configuration.objectStyles[item.id].styles;
|
||||||
expect(itemStyles.length).toBe(2);
|
expect(itemStyles.length).toBe(2);
|
||||||
const foundStyle = itemStyles.find((style) => {
|
const foundStyle = itemStyles.find((style) => {
|
||||||
@ -385,7 +459,7 @@ describe('the plugin', function () {
|
|||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
expect(styleViewComponentObject.domainObject.configuration.objectStyles).toBeDefined();
|
expect(styleViewComponentObject.domainObject.configuration.objectStyles).toBeDefined();
|
||||||
[boxLayoutItem, lineLayoutItem].forEach((item) => {
|
[boxLayoutItem, lineLayoutItem, notCreatableObjectItem].forEach((item) => {
|
||||||
const itemStyle = styleViewComponentObject.domainObject.configuration.objectStyles[item.id].staticStyle;
|
const itemStyle = styleViewComponentObject.domainObject.configuration.objectStyles[item.id].staticStyle;
|
||||||
expect(itemStyle).toBeDefined();
|
expect(itemStyle).toBeDefined();
|
||||||
const applicableStyles = getApplicableStylesForItem(styleViewComponentObject.domainObject, item);
|
const applicableStyles = getApplicableStylesForItem(styleViewComponentObject.domainObject, item);
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component :is="urlDefined ? 'a' : 'span'"
|
<component :is="urlDefined ? 'a' : 'span'"
|
||||||
class="c-condition-widget"
|
class="c-condition-widget u-style-receiver js-style-receiver"
|
||||||
:href="urlDefined ? internalDomainObject.url : null"
|
:href="urlDefined ? internalDomainObject.url : null"
|
||||||
>
|
>
|
||||||
<div class="c-condition-widget__label">
|
<div class="c-condition-widget__label">
|
||||||
|
@ -64,9 +64,16 @@ define([
|
|||||||
components: {
|
components: {
|
||||||
AlphanumericFormatView: AlphanumericFormatView.default
|
AlphanumericFormatView: AlphanumericFormatView.default
|
||||||
},
|
},
|
||||||
template: '<alphanumeric-format-view></alphanumeric-format-view>'
|
template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getViewContext() {
|
||||||
|
if (component) {
|
||||||
|
return component.$refs.alphanumericFormatView.getViewContext();
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
component.$destroy();
|
component.$destroy();
|
||||||
component = undefined;
|
component = undefined;
|
||||||
|
@ -73,7 +73,6 @@ define(['lodash'], function (_) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const VIEW_TYPES = {
|
const VIEW_TYPES = {
|
||||||
'telemetry-view': {
|
'telemetry-view': {
|
||||||
value: 'telemetry-view',
|
value: 'telemetry-view',
|
||||||
@ -96,7 +95,6 @@ define(['lodash'], function (_) {
|
|||||||
class: 'icon-tabular-realtime'
|
class: 'icon-tabular-realtime'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const APPLICABLE_VIEWS = {
|
const APPLICABLE_VIEWS = {
|
||||||
'telemetry-view': [
|
'telemetry-view': [
|
||||||
VIEW_TYPES['telemetry.plot.overlay'],
|
VIEW_TYPES['telemetry.plot.overlay'],
|
||||||
@ -390,29 +388,6 @@ define(['lodash'], function (_) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTextSizeMenu(selectedParent, selection) {
|
|
||||||
const TEXT_SIZE = [8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96, 128];
|
|
||||||
|
|
||||||
return {
|
|
||||||
control: "select-menu",
|
|
||||||
domainObject: selectedParent,
|
|
||||||
applicableSelectedItems: selection.filter(selectionPath => {
|
|
||||||
let type = selectionPath[0].context.layoutItem.type;
|
|
||||||
|
|
||||||
return type === 'text-view' || type === 'telemetry-view';
|
|
||||||
}),
|
|
||||||
property: function (selectionPath) {
|
|
||||||
return getPath(selectionPath) + ".size";
|
|
||||||
},
|
|
||||||
title: "Set text size",
|
|
||||||
options: TEXT_SIZE.map(size => {
|
|
||||||
return {
|
|
||||||
value: size + "px"
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTextButton(selectedParent, selection) {
|
function getTextButton(selectedParent, selection) {
|
||||||
return {
|
return {
|
||||||
control: "button",
|
control: "button",
|
||||||
@ -423,7 +398,7 @@ define(['lodash'], function (_) {
|
|||||||
property: function (selectionPath) {
|
property: function (selectionPath) {
|
||||||
return getPath(selectionPath);
|
return getPath(selectionPath);
|
||||||
},
|
},
|
||||||
icon: "icon-font",
|
icon: "icon-pencil",
|
||||||
title: "Edit text properties",
|
title: "Edit text properties",
|
||||||
dialog: DIALOG_FORM.text
|
dialog: DIALOG_FORM.text
|
||||||
};
|
};
|
||||||
@ -623,6 +598,33 @@ define(['lodash'], function (_) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getToggleGridButton(selection, selectionPath) {
|
||||||
|
const ICON_GRID_SHOW = 'icon-grid-on';
|
||||||
|
const ICON_GRID_HIDE = 'icon-grid-off';
|
||||||
|
|
||||||
|
let displayLayoutContext;
|
||||||
|
|
||||||
|
if (selection.length === 1 && selectionPath === undefined) {
|
||||||
|
displayLayoutContext = selection[0][0].context;
|
||||||
|
} else {
|
||||||
|
displayLayoutContext = selectionPath[1].context;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
control: "button",
|
||||||
|
domainObject: displayLayoutContext.item,
|
||||||
|
icon: ICON_GRID_SHOW,
|
||||||
|
method: function () {
|
||||||
|
displayLayoutContext.toggleGrid();
|
||||||
|
|
||||||
|
this.icon = this.icon === ICON_GRID_SHOW
|
||||||
|
? ICON_GRID_HIDE
|
||||||
|
: ICON_GRID_SHOW;
|
||||||
|
},
|
||||||
|
secondary: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getSeparator() {
|
function getSeparator() {
|
||||||
return {
|
return {
|
||||||
control: "separator"
|
control: "separator"
|
||||||
@ -637,7 +639,9 @@ define(['lodash'], function (_) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isMainLayoutSelected(selectedObjects[0])) {
|
if (isMainLayoutSelected(selectedObjects[0])) {
|
||||||
return [getAddButton(selectedObjects)];
|
return [
|
||||||
|
getToggleGridButton(selectedObjects),
|
||||||
|
getAddButton(selectedObjects)];
|
||||||
}
|
}
|
||||||
|
|
||||||
let toolbar = {
|
let toolbar = {
|
||||||
@ -649,11 +653,11 @@ define(['lodash'], function (_) {
|
|||||||
'display-mode': [],
|
'display-mode': [],
|
||||||
'telemetry-value': [],
|
'telemetry-value': [],
|
||||||
'style': [],
|
'style': [],
|
||||||
'text-style': [],
|
|
||||||
'position': [],
|
'position': [],
|
||||||
'duplicate': [],
|
'duplicate': [],
|
||||||
'unit-toggle': [],
|
'unit-toggle': [],
|
||||||
'remove': []
|
'remove': [],
|
||||||
|
'toggle-grid': []
|
||||||
};
|
};
|
||||||
|
|
||||||
selectedObjects.forEach(selectionPath => {
|
selectedObjects.forEach(selectionPath => {
|
||||||
@ -699,12 +703,6 @@ define(['lodash'], function (_) {
|
|||||||
toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selectedObjects)];
|
toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selectedObjects)];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolbar['text-style'].length === 0) {
|
|
||||||
toolbar['text-style'] = [
|
|
||||||
getTextSizeMenu(selectedParent, selectedObjects)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toolbar.position.length === 0) {
|
if (toolbar.position.length === 0) {
|
||||||
toolbar.position = [
|
toolbar.position = [
|
||||||
getStackOrder(selectedParent, selectionPath),
|
getStackOrder(selectedParent, selectionPath),
|
||||||
@ -730,12 +728,6 @@ define(['lodash'], function (_) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (layoutItem.type === 'text-view') {
|
} else if (layoutItem.type === 'text-view') {
|
||||||
if (toolbar['text-style'].length === 0) {
|
|
||||||
toolbar['text-style'] = [
|
|
||||||
getTextSizeMenu(selectedParent, selectedObjects)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toolbar.position.length === 0) {
|
if (toolbar.position.length === 0) {
|
||||||
toolbar.position = [
|
toolbar.position = [
|
||||||
getStackOrder(selectedParent, selectionPath),
|
getStackOrder(selectedParent, selectionPath),
|
||||||
@ -800,6 +792,10 @@ define(['lodash'], function (_) {
|
|||||||
if (toolbar.duplicate.length === 0) {
|
if (toolbar.duplicate.length === 0) {
|
||||||
toolbar.duplicate = [getDuplicateButton(selectedParent, selectionPath, selectedObjects)];
|
toolbar.duplicate = [getDuplicateButton(selectedParent, selectionPath, selectedObjects)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toolbar['toggle-grid'].length === 0) {
|
||||||
|
toolbar['toggle-grid'] = [getToggleGridButton(selectedObjects, selectionPath)];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let toolbarArray = Object.values(toolbar);
|
let toolbarArray = Object.values(toolbar);
|
||||||
|
@ -56,6 +56,28 @@ define(function () {
|
|||||||
1
|
1
|
||||||
],
|
],
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Horizontal size (px)",
|
||||||
|
control: "numberfield",
|
||||||
|
cssClass: "l-input-sm l-numeric",
|
||||||
|
property: [
|
||||||
|
"configuration",
|
||||||
|
"layoutDimensions",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Vertical size (px)",
|
||||||
|
control: "numberfield",
|
||||||
|
cssClass: "l-input-sm l-numeric",
|
||||||
|
property: [
|
||||||
|
"configuration",
|
||||||
|
"layoutDimensions",
|
||||||
|
1
|
||||||
|
],
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
33
src/plugins/displayLayout/actions/CopyToClipboardAction.js
Normal file
33
src/plugins/displayLayout/actions/CopyToClipboardAction.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import clipboard from '@/utils/clipboard';
|
||||||
|
|
||||||
|
export default class CopyToClipboardAction {
|
||||||
|
constructor(openmct) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
|
||||||
|
this.cssClass = 'icon-duplicate';
|
||||||
|
this.description = 'Copy to Clipboard action';
|
||||||
|
this.group = "action";
|
||||||
|
this.key = 'copyToClipboard';
|
||||||
|
this.name = 'Copy to Clipboard';
|
||||||
|
this.priority = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(objectPath, viewContext) {
|
||||||
|
const formattedValue = viewContext.formattedValueForCopy();
|
||||||
|
clipboard.updateClipboard(formattedValue)
|
||||||
|
.then(() => {
|
||||||
|
this.openmct.notifications.info(`Success : copied to clipboard '${formattedValue}'`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.openmct.notifications.error(`Failed : to copy to clipboard '${formattedValue}'`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
appliesTo(objectPath, viewContext) {
|
||||||
|
if (viewContext && viewContext.getViewKey) {
|
||||||
|
return viewContext.getViewKey().includes('alphanumeric-format');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@
|
|||||||
@endMove="() => $emit('endMove')"
|
@endMove="() => $emit('endMove')"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-box-view"
|
class="c-box-view u-style-receiver js-style-receiver"
|
||||||
:class="[styleClass]"
|
:class="[styleClass]"
|
||||||
:style="style"
|
:style="style"
|
||||||
></div>
|
></div>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="l-layout"
|
class="l-layout u-style-receiver js-style-receiver"
|
||||||
:class="{
|
:class="{
|
||||||
'is-multi-selected': selectedLayoutItems.length > 1,
|
'is-multi-selected': selectedLayoutItems.length > 1,
|
||||||
'allow-editing': isEditing
|
'allow-editing': isEditing
|
||||||
@ -31,21 +31,19 @@
|
|||||||
@click.capture="bypassSelection"
|
@click.capture="bypassSelection"
|
||||||
@drop="handleDrop"
|
@drop="handleDrop"
|
||||||
>
|
>
|
||||||
<!-- Background grid -->
|
<display-layout-grid
|
||||||
<div
|
|
||||||
v-if="isEditing"
|
v-if="isEditing"
|
||||||
class="l-layout__grid-holder c-grid"
|
:grid-size="gridSize"
|
||||||
|
:show-grid="showGrid"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="shouldDisplayLayoutDimensions"
|
||||||
|
class="l-layout__dimensions"
|
||||||
|
:style="layoutDimensionsStyle"
|
||||||
>
|
>
|
||||||
<div
|
<div class="l-layout__dimensions-vals">
|
||||||
v-if="gridSize[0] >= 3"
|
{{ layoutDimensions[0] }},{{ layoutDimensions[1] }}
|
||||||
class="c-grid__x l-grid l-grid-x"
|
</div>
|
||||||
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
v-if="gridSize[1] >= 3"
|
|
||||||
class="c-grid__y l-grid l-grid-y"
|
|
||||||
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
<component
|
<component
|
||||||
:is="item.type"
|
:is="item.type"
|
||||||
@ -81,6 +79,7 @@ import TextView from './TextView.vue';
|
|||||||
import LineView from './LineView.vue';
|
import LineView from './LineView.vue';
|
||||||
import ImageView from './ImageView.vue';
|
import ImageView from './ImageView.vue';
|
||||||
import EditMarquee from './EditMarquee.vue';
|
import EditMarquee from './EditMarquee.vue';
|
||||||
|
import DisplayLayoutGrid from './DisplayLayoutGrid.vue';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
const TELEMETRY_IDENTIFIER_FUNCTIONS = {
|
const TELEMETRY_IDENTIFIER_FUNCTIONS = {
|
||||||
@ -127,6 +126,7 @@ const DUPLICATE_OFFSET = 3;
|
|||||||
|
|
||||||
let components = ITEM_TYPE_VIEW_MAP;
|
let components = ITEM_TYPE_VIEW_MAP;
|
||||||
components['edit-marquee'] = EditMarquee;
|
components['edit-marquee'] = EditMarquee;
|
||||||
|
components['display-layout-grid'] = DisplayLayoutGrid;
|
||||||
|
|
||||||
function getItemDefinition(itemType, ...options) {
|
function getItemDefinition(itemType, ...options) {
|
||||||
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
|
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
|
||||||
@ -140,6 +140,7 @@ function getItemDefinition(itemType, ...options) {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: components,
|
components: components,
|
||||||
|
inject: ['openmct', 'options', 'objectPath'],
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -156,7 +157,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
internalDomainObject: domainObject,
|
internalDomainObject: domainObject,
|
||||||
initSelectIndex: undefined,
|
initSelectIndex: undefined,
|
||||||
selection: []
|
selection: [],
|
||||||
|
showGrid: true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -171,6 +173,23 @@ export default {
|
|||||||
return this.itemIsInCurrentSelection(item);
|
return this.itemIsInCurrentSelection(item);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
layoutDimensions() {
|
||||||
|
return this.internalDomainObject.configuration.layoutDimensions;
|
||||||
|
},
|
||||||
|
shouldDisplayLayoutDimensions() {
|
||||||
|
return this.layoutDimensions
|
||||||
|
&& this.layoutDimensions[0] > 0
|
||||||
|
&& this.layoutDimensions[1] > 0;
|
||||||
|
},
|
||||||
|
layoutDimensionsStyle() {
|
||||||
|
const width = `${this.layoutDimensions[0]}px`;
|
||||||
|
const height = `${this.layoutDimensions[1]}px`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
};
|
||||||
|
},
|
||||||
showMarquee() {
|
showMarquee() {
|
||||||
let selectionPath = this.selection[0];
|
let selectionPath = this.selection[0];
|
||||||
let singleSelectedLine = this.selection.length === 1
|
let singleSelectedLine = this.selection.length === 1
|
||||||
@ -179,7 +198,13 @@ export default {
|
|||||||
return this.isEditing && selectionPath && selectionPath.length > 1 && !singleSelectedLine;
|
return this.isEditing && selectionPath && selectionPath.length > 1 && !singleSelectedLine;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'options', 'objectPath'],
|
watch: {
|
||||||
|
isEditing(value) {
|
||||||
|
if (value) {
|
||||||
|
this.showGrid = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) {
|
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) {
|
||||||
this.internalDomainObject = JSON.parse(JSON.stringify(obj));
|
this.internalDomainObject = JSON.parse(JSON.stringify(obj));
|
||||||
@ -798,6 +823,9 @@ export default {
|
|||||||
|
|
||||||
this.removeItem(selection);
|
this.removeItem(selection);
|
||||||
this.initSelectIndex = this.layoutItems.length - 1; //restore selection
|
this.initSelectIndex = this.layoutItems.length - 1; //restore selection
|
||||||
|
},
|
||||||
|
toggleGrid() {
|
||||||
|
this.showGrid = !this.showGrid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
34
src/plugins/displayLayout/components/DisplayLayoutGrid.vue
Normal file
34
src/plugins/displayLayout/components/DisplayLayoutGrid.vue
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="l-layout__grid-holder"
|
||||||
|
:class="{ 'c-grid': showGrid }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="gridSize[0] >= 3"
|
||||||
|
class="c-grid__x l-grid l-grid-x"
|
||||||
|
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-if="gridSize[1] >= 3"
|
||||||
|
class="c-grid__y l-grid l-grid-y"
|
||||||
|
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
gridSize: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
validator: (arr) => arr && arr.length === 2
|
||||||
|
&& arr.every(el => typeof el === 'number')
|
||||||
|
},
|
||||||
|
showGrid: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -81,6 +81,7 @@ export default {
|
|||||||
style() {
|
style() {
|
||||||
let backgroundImage = 'url(' + this.item.url + ')';
|
let backgroundImage = 'url(' + this.item.url + ')';
|
||||||
let border = '1px solid ' + this.item.stroke;
|
let border = '1px solid ' + this.item.stroke;
|
||||||
|
|
||||||
if (this.itemStyle) {
|
if (this.itemStyle) {
|
||||||
if (this.itemStyle.imageUrl !== undefined) {
|
if (this.itemStyle.imageUrl !== undefined) {
|
||||||
backgroundImage = 'url(' + this.itemStyle.imageUrl + ')';
|
backgroundImage = 'url(' + this.itemStyle.imageUrl + ')';
|
||||||
|
@ -35,6 +35,8 @@
|
|||||||
:object-path="currentObjectPath"
|
:object-path="currentObjectPath"
|
||||||
:has-frame="item.hasFrame"
|
:has-frame="item.hasFrame"
|
||||||
:show-edit-view="false"
|
:show-edit-view="false"
|
||||||
|
:layout-font-size="item.fontSize"
|
||||||
|
:layout-font="item.font"
|
||||||
/>
|
/>
|
||||||
</layout-frame>
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
@ -73,6 +75,8 @@ export default {
|
|||||||
y: position[1],
|
y: position[1],
|
||||||
identifier: domainObject.identifier,
|
identifier: domainObject.identifier,
|
||||||
hasFrame: hasFrameByDefault(domainObject.type),
|
hasFrame: hasFrameByDefault(domainObject.type),
|
||||||
|
fontSize: 'default',
|
||||||
|
font: 'default',
|
||||||
viewKey
|
viewKey
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -138,6 +142,9 @@ export default {
|
|||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
|
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
let reference = this.$refs.objectFrame;
|
||||||
|
|
||||||
|
if (reference) {
|
||||||
let childContext = this.$refs.objectFrame.getSelectionContext();
|
let childContext = this.$refs.objectFrame.getSelectionContext();
|
||||||
childContext.item = domainObject;
|
childContext.item = domainObject;
|
||||||
childContext.layoutItem = this.item;
|
childContext.layoutItem = this.item;
|
||||||
@ -146,6 +153,7 @@ export default {
|
|||||||
this.removeSelectable = this.openmct.selection.selectable(
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
this.$el, this.context, this.immediatelySelect || this.initSelect);
|
this.$el, this.context, this.immediatelySelect || this.initSelect);
|
||||||
delete this.immediatelySelect;
|
delete this.immediatelySelect;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,15 +31,14 @@
|
|||||||
<div
|
<div
|
||||||
v-if="domainObject"
|
v-if="domainObject"
|
||||||
class="c-telemetry-view"
|
class="c-telemetry-view"
|
||||||
:class="{
|
:class="[statusClass]"
|
||||||
styleClass,
|
|
||||||
'is-missing': domainObject.status === 'missing'
|
|
||||||
}"
|
|
||||||
:style="styleObject"
|
:style="styleObject"
|
||||||
|
:data-font-size="item.fontSize"
|
||||||
|
:data-font="item.font"
|
||||||
@contextmenu.prevent="showContextMenu"
|
@contextmenu.prevent="showContextMenu"
|
||||||
>
|
>
|
||||||
<div class="is-missing__indicator"
|
<div class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
v-if="showLabel"
|
v-if="showLabel"
|
||||||
@ -74,10 +73,11 @@
|
|||||||
import LayoutFrame from './LayoutFrame.vue';
|
import LayoutFrame from './LayoutFrame.vue';
|
||||||
import printj from 'printj';
|
import printj from 'printj';
|
||||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
||||||
|
import { getDefaultNotebook } from '@/plugins/notebook/utils/notebook-storage.js';
|
||||||
|
|
||||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
|
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
|
||||||
const DEFAULT_POSITION = [1, 1];
|
const DEFAULT_POSITION = [1, 1];
|
||||||
const CONTEXT_MENU_ACTIONS = ['viewHistoricalData'];
|
const CONTEXT_MENU_ACTIONS = ['copyToClipboard', 'copyToNotebook', 'viewHistoricalData'];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
makeDefinition(openmct, gridSize, domainObject, position) {
|
makeDefinition(openmct, gridSize, domainObject, position) {
|
||||||
@ -95,7 +95,8 @@ export default {
|
|||||||
stroke: "",
|
stroke: "",
|
||||||
fill: "",
|
fill: "",
|
||||||
color: "",
|
color: "",
|
||||||
size: "13px"
|
fontSize: 'default',
|
||||||
|
font: 'default'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'objectPath'],
|
inject: ['openmct', 'objectPath'],
|
||||||
@ -126,13 +127,18 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
currentObjectPath: undefined,
|
||||||
datum: undefined,
|
datum: undefined,
|
||||||
formats: undefined,
|
|
||||||
domainObject: undefined,
|
domainObject: undefined,
|
||||||
currentObjectPath: undefined
|
formats: undefined,
|
||||||
|
viewKey: `alphanumeric-format-${Math.random()}`,
|
||||||
|
status: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
statusClass() {
|
||||||
|
return (this.status) ? `is-status--${this.status}` : '';
|
||||||
|
},
|
||||||
showLabel() {
|
showLabel() {
|
||||||
let displayMode = this.item.displayMode;
|
let displayMode = this.item.displayMode;
|
||||||
|
|
||||||
@ -150,10 +156,15 @@ export default {
|
|||||||
return unit;
|
return unit;
|
||||||
},
|
},
|
||||||
styleObject() {
|
styleObject() {
|
||||||
return Object.assign({}, {
|
let size;
|
||||||
fontSize: this.item.size
|
//for legacy size support
|
||||||
}, this.itemStyle);
|
if (!this.item.fontSize) {
|
||||||
|
size = this.item.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, {
|
||||||
|
size
|
||||||
|
}, this.itemStyle);
|
||||||
},
|
},
|
||||||
fieldName() {
|
fieldName() {
|
||||||
return this.valueMetadata && this.valueMetadata.name;
|
return this.valueMetadata && this.valueMetadata.name;
|
||||||
@ -205,9 +216,13 @@ export default {
|
|||||||
this.openmct.objects.get(this.item.identifier)
|
this.openmct.objects.get(this.item.identifier)
|
||||||
.then(this.setObject);
|
.then(this.setObject);
|
||||||
this.openmct.time.on("bounds", this.refreshData);
|
this.openmct.time.on("bounds", this.refreshData);
|
||||||
|
|
||||||
|
this.status = this.openmct.status.get(this.item.identifier);
|
||||||
|
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.removeSubscription();
|
this.removeSubscription();
|
||||||
|
this.removeStatusListener();
|
||||||
|
|
||||||
if (this.removeSelectable) {
|
if (this.removeSelectable) {
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
@ -216,6 +231,18 @@ export default {
|
|||||||
this.openmct.time.off("bounds", this.refreshData);
|
this.openmct.time.off("bounds", this.refreshData);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getViewContext() {
|
||||||
|
return {
|
||||||
|
getViewKey: () => this.viewKey,
|
||||||
|
formattedValueForCopy: this.formattedValueForCopy
|
||||||
|
};
|
||||||
|
},
|
||||||
|
formattedValueForCopy() {
|
||||||
|
const timeFormatterKey = this.openmct.time.timeSystem().key;
|
||||||
|
const timeFormatter = this.formats[timeFormatterKey];
|
||||||
|
|
||||||
|
return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue} ${this.unit}`;
|
||||||
|
},
|
||||||
requestHistoricalData() {
|
requestHistoricalData() {
|
||||||
let bounds = this.openmct.time.bounds();
|
let bounds = this.openmct.time.bounds();
|
||||||
let options = {
|
let options = {
|
||||||
@ -253,6 +280,16 @@ export default {
|
|||||||
this.requestHistoricalData(this.domainObject);
|
this.requestHistoricalData(this.domainObject);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getView() {
|
||||||
|
return {
|
||||||
|
getViewContext() {
|
||||||
|
return {
|
||||||
|
viewHistoricalData: true,
|
||||||
|
skipCache: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
setObject(domainObject) {
|
setObject(domainObject) {
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
@ -276,12 +313,40 @@ export default {
|
|||||||
this.removeSelectable = this.openmct.selection.selectable(
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
this.$el, this.context, this.immediatelySelect || this.initSelect);
|
this.$el, this.context, this.immediatelySelect || this.initSelect);
|
||||||
delete this.immediatelySelect;
|
delete this.immediatelySelect;
|
||||||
|
|
||||||
|
let allActions = this.openmct.actions.get(this.currentObjectPath, this.getView());
|
||||||
|
|
||||||
|
this.applicableActions = CONTEXT_MENU_ACTIONS.map(actionKey => {
|
||||||
|
return allActions[actionKey];
|
||||||
|
});
|
||||||
},
|
},
|
||||||
updateTelemetryFormat(format) {
|
updateTelemetryFormat(format) {
|
||||||
this.$emit('formatChanged', this.item, format);
|
this.$emit('formatChanged', this.item, format);
|
||||||
},
|
},
|
||||||
showContextMenu(event) {
|
async getContextMenuActions() {
|
||||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
|
const defaultNotebook = getDefaultNotebook();
|
||||||
|
const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
|
||||||
|
|
||||||
|
const actionsObject = this.openmct.actions.get(this.currentObjectPath, this.getViewContext(), { viewHistoricalData: true }).applicableActions;
|
||||||
|
let applicableActionKeys = Object.keys(actionsObject)
|
||||||
|
.filter(key => {
|
||||||
|
const isCopyToNotebook = actionsObject[key].key === 'copyToNotebook';
|
||||||
|
if (defaultNotebook && isCopyToNotebook) {
|
||||||
|
const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
|
||||||
|
actionsObject[key].name = `Copy to Notebook ${defaultPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CONTEXT_MENU_ACTIONS.includes(actionsObject[key].key);
|
||||||
|
});
|
||||||
|
|
||||||
|
return applicableActionKeys.map(key => actionsObject[key]);
|
||||||
|
},
|
||||||
|
async showContextMenu(event) {
|
||||||
|
const contextMenuActions = await this.getContextMenuActions();
|
||||||
|
this.openmct.menus.showMenu(event.x, event.y, contextMenuActions);
|
||||||
|
},
|
||||||
|
setStatus(status) {
|
||||||
|
this.status = status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -29,7 +29,9 @@
|
|||||||
@endMove="() => $emit('endMove')"
|
@endMove="() => $emit('endMove')"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-text-view"
|
class="c-text-view u-style-receiver js-style-receiver"
|
||||||
|
:data-font-size="item.fontSize"
|
||||||
|
:data-font="item.font"
|
||||||
:class="[styleClass]"
|
:class="[styleClass]"
|
||||||
:style="style"
|
:style="style"
|
||||||
>
|
>
|
||||||
@ -47,13 +49,14 @@ export default {
|
|||||||
return {
|
return {
|
||||||
fill: '',
|
fill: '',
|
||||||
stroke: '',
|
stroke: '',
|
||||||
size: '13px',
|
|
||||||
color: '',
|
color: '',
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1,
|
y: 1,
|
||||||
width: 10,
|
width: 10,
|
||||||
height: 5,
|
height: 5,
|
||||||
text: element.text
|
text: element.text,
|
||||||
|
fontSize: 'default',
|
||||||
|
font: 'default'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
@ -84,8 +87,14 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
style() {
|
style() {
|
||||||
|
let size;
|
||||||
|
//legacy size support
|
||||||
|
if (!this.item.fontSize) {
|
||||||
|
size = this.item.size;
|
||||||
|
}
|
||||||
|
|
||||||
return Object.assign({
|
return Object.assign({
|
||||||
fontSize: this.item.size
|
size
|
||||||
}, this.itemStyle);
|
}, this.itemStyle);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -17,10 +17,29 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
&__grid-holder {
|
&__grid-holder,
|
||||||
|
&__dimensions {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__dimensions {
|
||||||
|
$b: 1px dashed $editDimensionsColor;
|
||||||
|
border-right: $b;
|
||||||
|
border-bottom: $b;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
&-vals {
|
||||||
|
$p: 2px;
|
||||||
|
color: $editDimensionsColor;
|
||||||
|
display: inline-block;
|
||||||
|
font-style: italic;
|
||||||
|
position: absolute;
|
||||||
|
bottom: $p; right: $p;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__frame {
|
&__frame {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
@ -34,6 +53,10 @@
|
|||||||
> .l-layout {
|
> .l-layout {
|
||||||
background: $editUIGridColorBg;
|
background: $editUIGridColorBg;
|
||||||
|
|
||||||
|
> [class*="__dimensions"] {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
> [class*="__grid-holder"] {
|
> [class*="__grid-holder"] {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -42,12 +65,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.l-layout__frame {
|
.l-layout__frame {
|
||||||
&[s-selected],
|
&[s-selected]:not([multi-select="true"]),
|
||||||
&[s-selected-parent] {
|
&[s-selected-parent] {
|
||||||
// Display grid and allow edit marquee to display in nested layouts when editing
|
// Display grid and allow edit marquee to display in nested layouts when editing
|
||||||
> * > * > .l-layout + .allow-editing {
|
> * > * > .l-layout.allow-editing {
|
||||||
box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
|
box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
|
||||||
|
|
||||||
|
> [class*="__dimensions"] {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
> [class*='grid-holder'] {
|
> [class*='grid-holder'] {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -29,12 +29,12 @@
|
|||||||
|
|
||||||
@include isMissing($absPos: true);
|
@include isMissing($absPos: true);
|
||||||
|
|
||||||
.is-missing__indicator {
|
.is-status__indicator {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-missing {
|
&.is-status--missing {
|
||||||
border: $borderMissing;
|
border: $borderMissing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ export default {
|
|||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
objectStyle: undefined,
|
||||||
itemStyle: undefined,
|
itemStyle: undefined,
|
||||||
styleClass: ''
|
styleClass: ''
|
||||||
};
|
};
|
||||||
|
@ -26,9 +26,12 @@ import objectUtils from 'objectUtils';
|
|||||||
import DisplayLayoutType from './DisplayLayoutType.js';
|
import DisplayLayoutType from './DisplayLayoutType.js';
|
||||||
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
|
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
|
||||||
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
|
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
|
||||||
|
import CopyToClipboardAction from './actions/CopyToClipboardAction';
|
||||||
|
|
||||||
export default function DisplayLayoutPlugin(options) {
|
export default function DisplayLayoutPlugin(options) {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
|
openmct.actions.register(new CopyToClipboardAction(openmct));
|
||||||
|
|
||||||
openmct.objectViews.addProvider({
|
openmct.objectViews.addProvider({
|
||||||
key: 'layout.view',
|
key: 'layout.view',
|
||||||
canView: function (domainObject) {
|
canView: function (domainObject) {
|
||||||
@ -72,7 +75,8 @@ export default function DisplayLayoutPlugin(options) {
|
|||||||
duplicateItem: component && component.$refs.displayLayout.duplicateItem,
|
duplicateItem: component && component.$refs.displayLayout.duplicateItem,
|
||||||
switchViewType: component && component.$refs.displayLayout.switchViewType,
|
switchViewType: component && component.$refs.displayLayout.switchViewType,
|
||||||
mergeMultipleTelemetryViews: component && component.$refs.displayLayout.mergeMultipleTelemetryViews,
|
mergeMultipleTelemetryViews: component && component.$refs.displayLayout.mergeMultipleTelemetryViews,
|
||||||
mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots
|
mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots,
|
||||||
|
toggleGrid: component && component.$refs.displayLayout.toggleGrid
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onEditModeChange: function (isEditing) {
|
onEditModeChange: function (isEditing) {
|
||||||
|
@ -340,6 +340,7 @@ describe('the plugin', function () {
|
|||||||
|
|
||||||
it('provides controls including separators', () => {
|
it('provides controls including separators', () => {
|
||||||
const displayLayoutToolbar = openmct.toolbars.get(selection);
|
const displayLayoutToolbar = openmct.toolbars.get(selection);
|
||||||
|
|
||||||
expect(displayLayoutToolbar.length).toBe(9);
|
expect(displayLayoutToolbar.length).toBe(9);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,20 +3,26 @@
|
|||||||
@include userSelectNone();
|
@include userSelectNone();
|
||||||
background: $colorFilterBg;
|
background: $colorFilterBg;
|
||||||
color: $colorFilterFg;
|
color: $colorFilterFg;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin-top: $interiorMarginSm;
|
|
||||||
padding: 2px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
font-family: symbolsfont-12px;
|
font-family: symbolsfont-12px;
|
||||||
content: $glyph-icon-filter;
|
content: $glyph-icon-filter;
|
||||||
display: block;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-right: $interiorMarginSm;
|
margin-right: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--mixed {
|
||||||
|
.c-filter-indication__mixed {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
+ .c-filter-indication__label {
|
||||||
|
&:before {
|
||||||
|
content: ', ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-filter-tree-item {
|
.c-filter-tree-item {
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
class="l-grid-view__item c-grid-item"
|
class="l-grid-view__item c-grid-item"
|
||||||
:class="{
|
:class="[{
|
||||||
'is-alias': item.isAlias === true,
|
'is-alias': item.isAlias === true,
|
||||||
'is-missing': item.model.status === 'missing',
|
|
||||||
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
|
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
|
||||||
}"
|
}, statusClass]"
|
||||||
:href="objectLink"
|
:href="objectLink"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -27,8 +26,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-grid-item__controls">
|
<div class="c-grid-item__controls">
|
||||||
<div class="is-missing__indicator"
|
<div class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
class="icon-people"
|
class="icon-people"
|
||||||
@ -46,9 +45,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
||||||
import objectLink from '../../../ui/mixins/object-link';
|
import objectLink from '../../../ui/mixins/object-link';
|
||||||
|
import statusListener from './status-listener';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [contextMenuGesture, objectLink],
|
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -8,18 +8,18 @@
|
|||||||
<a
|
<a
|
||||||
ref="objectLink"
|
ref="objectLink"
|
||||||
class="c-object-label"
|
class="c-object-label"
|
||||||
:class="{ 'is-missing': item.model.status === 'missing' }"
|
:class="[statusClass]"
|
||||||
:href="objectLink"
|
:href="objectLink"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-object-label__type-icon c-list-item__type-icon"
|
class="c-object-label__type-icon c-list-item__name__type-icon"
|
||||||
:class="item.type.cssClass"
|
:class="item.type.cssClass"
|
||||||
>
|
>
|
||||||
<span class="is-missing__indicator"
|
<span class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-object-label__name c-list-item__name">{{ item.model.name }}</div>
|
<div class="c-object-label__name c-list-item__name__name">{{ item.model.name }}</div>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="c-list-item__type">
|
<td class="c-list-item__type">
|
||||||
@ -39,9 +39,10 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
||||||
import objectLink from '../../../ui/mixins/object-link';
|
import objectLink from '../../../ui/mixins/object-link';
|
||||||
|
import statusListener from './status-listener';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [contextMenuGesture, objectLink],
|
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
body.desktop & {
|
body.desktop & {
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
height: $gridItemDesk;
|
height: $gridItemDesk;
|
||||||
width: $gridItemDesk;
|
width: $gridItemDesk;
|
||||||
@ -41,7 +43,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-missing {
|
&.is-status--missing {
|
||||||
@include isMissing();
|
@include isMissing();
|
||||||
|
|
||||||
[class*='__type-icon'],
|
[class*='__type-icon'],
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
/******************************* LIST ITEM */
|
/******************************* LIST ITEM */
|
||||||
.c-list-item {
|
.c-list-item {
|
||||||
&__type-icon {
|
&__name__type-icon {
|
||||||
color: $colorItemTreeIcon;
|
color: $colorItemTreeIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name__name {
|
||||||
@include ellipsize();
|
@include ellipsize();
|
||||||
|
|
||||||
|
a & {
|
||||||
|
color: $colorItemFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.c-list-item__name) {
|
||||||
|
color: $colorItemFgDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-alias {
|
&.is-alias {
|
||||||
|
@ -28,9 +28,5 @@
|
|||||||
padding-top: $p;
|
padding-top: $p;
|
||||||
padding-bottom: $p;
|
padding-bottom: $p;
|
||||||
width: 25%;
|
width: 25%;
|
||||||
|
|
||||||
&:not(.c-list-item__name) {
|
|
||||||
color: $colorItemFgDetails;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
src/plugins/folderView/components/status-listener.js
Normal file
33
src/plugins/folderView/components/status-listener.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
statusClass() {
|
||||||
|
return (this.status) ? `is-status--${this.status}` : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
status: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setStatus(status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
let identifier = this.item.model.identifier;
|
||||||
|
|
||||||
|
this.status = this.openmct.status.get(identifier);
|
||||||
|
this.removeStatusListener = this.openmct.status.observe(identifier, this.setStatus);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.removeStatusListener();
|
||||||
|
}
|
||||||
|
};
|
@ -25,6 +25,8 @@ export default class GoToOriginalAction {
|
|||||||
this.name = 'Go To Original';
|
this.name = 'Go To Original';
|
||||||
this.key = 'goToOriginal';
|
this.key = 'goToOriginal';
|
||||||
this.description = 'Go to the original unlinked instance of this object';
|
this.description = 'Go to the original unlinked instance of this object';
|
||||||
|
this.group = 'action';
|
||||||
|
this.priority = 4;
|
||||||
|
|
||||||
this._openmct = openmct;
|
this._openmct = openmct;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,6 @@ import GoToOriginalAction from './goToOriginalAction';
|
|||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
openmct.contextMenu.registerAction(new GoToOriginalAction(openmct));
|
openmct.actions.register(new GoToOriginalAction(openmct));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,116 +1,224 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-imagery">
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
class="c-imagery"
|
||||||
|
@keyup="arrowUpHandler"
|
||||||
|
@keydown="arrowDownHandler"
|
||||||
|
@mouseover="focusElement"
|
||||||
|
>
|
||||||
<div class="c-imagery__main-image-wrapper has-local-controls">
|
<div class="c-imagery__main-image-wrapper has-local-controls">
|
||||||
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc">
|
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover c-image-controls__controls">
|
||||||
<span class="holder flex-elem grows c-imagery__lc__sliders">
|
<span class="c-image-controls__sliders"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="startDrag"
|
||||||
|
>
|
||||||
|
<div class="c-image-controls__slider-wrapper icon-brightness">
|
||||||
<input v-model="filters.brightness"
|
<input v-model="filters.brightness"
|
||||||
class="icon-brightness"
|
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
max="500"
|
max="500"
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
|
<div class="c-image-controls__slider-wrapper icon-contrast">
|
||||||
<input v-model="filters.contrast"
|
<input v-model="filters.contrast"
|
||||||
class="icon-contrast"
|
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
max="500"
|
max="500"
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
|
<span class="t-reset-btn-holder c-imagery__lc__reset-btn c-image-controls__btn-reset">
|
||||||
<a class="s-icon-button icon-reset t-btn-reset"
|
<a class="s-icon-button icon-reset t-btn-reset"
|
||||||
@click="filters={brightness: 100, contrast: 100}"
|
@click="filters={brightness: 100, contrast: 100}"
|
||||||
></a>
|
></a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-image s-image-main c-imagery__main-image has-local-controls"
|
<div class="c-imagery__main-image__bg"
|
||||||
:class="{'paused unnsynced': paused(),'stale':false }"
|
:class="{'paused unnsynced': isPaused,'stale':false }"
|
||||||
:style="{'background-image': getImageUrl() ? `url(${getImageUrl()})` : 'none',
|
|
||||||
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
|
|
||||||
>
|
>
|
||||||
|
<div class="c-imagery__main-image__image"
|
||||||
|
:style="{
|
||||||
|
'background-image': imageUrl ? `url(${imageUrl})` : 'none',
|
||||||
|
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
|
||||||
|
}"
|
||||||
|
:data-openmct-image-timestamp="time"
|
||||||
|
:data-openmct-object-keystring="keyString"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
||||||
<button class="c-nav c-nav--prev"
|
<button class="c-nav c-nav--prev"
|
||||||
title="Previous image"
|
title="Previous image"
|
||||||
:disabled="isPrevDisabled()"
|
:disabled="isPrevDisabled"
|
||||||
@click="prevImage()"
|
@click="prevImage()"
|
||||||
></button>
|
></button>
|
||||||
<button class="c-nav c-nav--next"
|
<button class="c-nav c-nav--next"
|
||||||
title="Next image"
|
title="Next image"
|
||||||
:disabled="isNextDisabled()"
|
:disabled="isNextDisabled"
|
||||||
@click="nextImage()"
|
@click="nextImage()"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="c-imagery__control-bar">
|
<div class="c-imagery__control-bar">
|
||||||
<div class="c-imagery__timestamp">{{ getTime() }}</div>
|
<div class="c-imagery__time">
|
||||||
<div class="h-local-controls flex-elem">
|
<div class="c-imagery__timestamp u-style-receiver js-style-receiver">{{ time }}</div>
|
||||||
|
<div
|
||||||
|
v-if="canTrackDuration"
|
||||||
|
:class="{'c-imagery--new': isImageNew && !refreshCSS}"
|
||||||
|
class="c-imagery__age icon-timer"
|
||||||
|
>{{ formattedDuration }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-local-controls">
|
||||||
<button
|
<button
|
||||||
class="c-button icon-pause pause-play"
|
class="c-button icon-pause pause-play"
|
||||||
:class="{'is-paused': paused()}"
|
:class="{'is-paused': isPaused}"
|
||||||
@click="paused(!paused(), true)"
|
@click="paused(!isPaused, 'button')"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="thumbsWrapper"
|
<div ref="thumbsWrapper"
|
||||||
class="c-imagery__thumbs-wrapper"
|
class="c-imagery__thumbs-wrapper"
|
||||||
:class="{'is-paused': paused()}"
|
:class="{'is-paused': isPaused}"
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
>
|
>
|
||||||
<div v-for="(imageData, index) in imageHistory"
|
<div v-for="(datum, index) in imageHistory"
|
||||||
:key="index"
|
:key="datum.url"
|
||||||
class="c-imagery__thumb c-thumb"
|
class="c-imagery__thumb c-thumb"
|
||||||
:class="{selected: imageData.selected}"
|
:class="{ selected: focusedImageIndex === index && isPaused }"
|
||||||
@click="setSelectedImage(imageData)"
|
@click="setFocusedImage(index, thumbnailClick)"
|
||||||
>
|
>
|
||||||
<img class="c-thumb__image"
|
<img class="c-thumb__image"
|
||||||
:src="getImageUrl(imageData)"
|
:src="formatImageUrl(datum)"
|
||||||
>
|
>
|
||||||
<div class="c-thumb__timestamp">{{ getTime(imageData) }}</div>
|
<div class="c-thumb__timestamp">{{ formatTime(datum) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
const REFRESH_CSS_MS = 500;
|
||||||
|
const DURATION_TRACK_MS = 1000;
|
||||||
|
const ARROW_DOWN_DELAY_CHECK_MS = 400;
|
||||||
|
const ARROW_SCROLL_RATE_MS = 100;
|
||||||
|
const THUMBNAIL_CLICKED = true;
|
||||||
|
|
||||||
|
const ONE_MINUTE = 60 * 1000;
|
||||||
|
const FIVE_MINUTES = 5 * ONE_MINUTE;
|
||||||
|
const ONE_HOUR = ONE_MINUTE * 60;
|
||||||
|
const EIGHT_HOURS = 8 * ONE_HOUR;
|
||||||
|
const TWENTYFOUR_HOURS = EIGHT_HOURS * 3;
|
||||||
|
|
||||||
|
const ARROW_RIGHT = 39;
|
||||||
|
const ARROW_LEFT = 37;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
data() {
|
data() {
|
||||||
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
autoScroll: true,
|
autoScroll: true,
|
||||||
|
durationFormatter: undefined,
|
||||||
filters: {
|
filters: {
|
||||||
brightness: 100,
|
brightness: 100,
|
||||||
contrast: 100
|
contrast: 100
|
||||||
},
|
},
|
||||||
image: {
|
|
||||||
selected: ''
|
|
||||||
},
|
|
||||||
imageFormat: '',
|
|
||||||
imageHistory: [],
|
imageHistory: [],
|
||||||
imageUrl: '',
|
thumbnailClick: THUMBNAIL_CLICKED,
|
||||||
isPaused: false,
|
isPaused: false,
|
||||||
metadata: {},
|
metadata: {},
|
||||||
requestCount: 0,
|
requestCount: 0,
|
||||||
timeFormat: ''
|
timeSystem: timeSystem,
|
||||||
|
timeFormatter: undefined,
|
||||||
|
refreshCSS: false,
|
||||||
|
keyString: undefined,
|
||||||
|
focusedImageIndex: undefined,
|
||||||
|
numericDuration: undefined
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
bounds() {
|
time() {
|
||||||
return this.openmct.time.bounds();
|
return this.formatTime(this.focusedImage);
|
||||||
|
},
|
||||||
|
imageUrl() {
|
||||||
|
return this.formatImageUrl(this.focusedImage);
|
||||||
|
},
|
||||||
|
isImageNew() {
|
||||||
|
let cutoff = FIVE_MINUTES;
|
||||||
|
let age = this.numericDuration;
|
||||||
|
|
||||||
|
return age < cutoff && !this.refreshCSS;
|
||||||
|
},
|
||||||
|
canTrackDuration() {
|
||||||
|
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
||||||
|
},
|
||||||
|
isNextDisabled() {
|
||||||
|
let disabled = false;
|
||||||
|
|
||||||
|
if (this.focusedImageIndex === -1 || this.focusedImageIndex === this.imageHistory.length - 1) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return disabled;
|
||||||
|
},
|
||||||
|
isPrevDisabled() {
|
||||||
|
let disabled = false;
|
||||||
|
|
||||||
|
if (this.focusedImageIndex === 0 || this.imageHistory.length < 2) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return disabled;
|
||||||
|
},
|
||||||
|
focusedImage() {
|
||||||
|
return this.imageHistory[this.focusedImageIndex];
|
||||||
|
},
|
||||||
|
parsedSelectedTime() {
|
||||||
|
return this.parseTime(this.focusedImage);
|
||||||
|
},
|
||||||
|
formattedDuration() {
|
||||||
|
let result = 'N/A';
|
||||||
|
let negativeAge = -1;
|
||||||
|
|
||||||
|
if (this.numericDuration > TWENTYFOUR_HOURS) {
|
||||||
|
negativeAge *= (this.numericDuration / TWENTYFOUR_HOURS);
|
||||||
|
result = moment.duration(negativeAge, 'days').humanize(true);
|
||||||
|
} else if (this.numericDuration > EIGHT_HOURS) {
|
||||||
|
negativeAge *= (this.numericDuration / ONE_HOUR);
|
||||||
|
result = moment.duration(negativeAge, 'hours').humanize(true);
|
||||||
|
} else if (this.durationFormatter) {
|
||||||
|
result = this.durationFormatter.format(this.numericDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
focusedImageIndex() {
|
||||||
|
this.trackDuration();
|
||||||
|
this.resetAgeCSS();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// set
|
|
||||||
this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
|
||||||
this.imageFormat = this.openmct.telemetry.getValueFormatter(this.metadata.valuesForHints(['image'])[0]);
|
|
||||||
// initialize
|
|
||||||
this.timeKey = this.openmct.time.timeSystem().key;
|
|
||||||
this.timeFormat = this.openmct.telemetry.getValueFormatter(this.metadata.value(this.timeKey));
|
|
||||||
// listen
|
// listen
|
||||||
this.openmct.time.on('bounds', this.boundsChange);
|
this.openmct.time.on('bounds', this.boundsChange);
|
||||||
this.openmct.time.on('timeSystem', this.timeSystemChange);
|
this.openmct.time.on('timeSystem', this.timeSystemChange);
|
||||||
|
this.openmct.time.on('clock', this.clockChange);
|
||||||
|
|
||||||
|
// set
|
||||||
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
|
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.metadata.valuesForHints(['image'])[0]);
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
this.timeKey = this.timeSystem.key;
|
||||||
|
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||||
|
|
||||||
// kickoff
|
// kickoff
|
||||||
this.subscribe();
|
this.subscribe();
|
||||||
this.requestHistory();
|
this.requestHistory();
|
||||||
@ -124,38 +232,55 @@ export default {
|
|||||||
delete this.unsubscribe;
|
delete this.unsubscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.stopDurationTracking();
|
||||||
this.openmct.time.off('bounds', this.boundsChange);
|
this.openmct.time.off('bounds', this.boundsChange);
|
||||||
this.openmct.time.off('timeSystem', this.timeSystemChange);
|
this.openmct.time.off('timeSystem', this.timeSystemChange);
|
||||||
|
this.openmct.time.off('clock', this.clockChange);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
focusElement() {
|
||||||
|
this.$el.focus();
|
||||||
|
},
|
||||||
datumIsNotValid(datum) {
|
datumIsNotValid(datum) {
|
||||||
if (this.imageHistory.length === 0) {
|
if (this.imageHistory.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const datumTime = this.timeFormat.format(datum);
|
const datumURL = this.formatImageUrl(datum);
|
||||||
const datumURL = this.imageFormat.format(datum);
|
const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]);
|
||||||
const lastHistoryTime = this.timeFormat.format(this.imageHistory.slice(-1)[0]);
|
|
||||||
const lastHistoryURL = this.imageFormat.format(this.imageHistory.slice(-1)[0]);
|
|
||||||
|
|
||||||
// datum is not valid if it matches the last datum in history,
|
// datum is not valid if it matches the last datum in history,
|
||||||
// or it is before the last datum in the history
|
// or it is before the last datum in the history
|
||||||
const datumTimeCheck = this.timeFormat.parse(datum);
|
const datumTimeCheck = this.parseTime(datum);
|
||||||
const historyTimeCheck = this.timeFormat.parse(this.imageHistory.slice(-1)[0]);
|
const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
|
||||||
const matchesLast = (datumTime === lastHistoryTime) && (datumURL === lastHistoryURL);
|
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
|
||||||
const isStale = datumTimeCheck < historyTimeCheck;
|
const isStale = datumTimeCheck < historyTimeCheck;
|
||||||
|
|
||||||
return matchesLast || isStale;
|
return matchesLast || isStale;
|
||||||
},
|
},
|
||||||
getImageUrl(datum) {
|
formatImageUrl(datum) {
|
||||||
return datum
|
if (!datum) {
|
||||||
? this.imageFormat.format(datum)
|
return;
|
||||||
: this.imageUrl;
|
}
|
||||||
|
|
||||||
|
return this.imageFormatter.format(datum);
|
||||||
},
|
},
|
||||||
getTime(datum) {
|
formatTime(datum) {
|
||||||
return datum
|
if (!datum) {
|
||||||
? this.timeFormat.format(datum)
|
return;
|
||||||
: this.time;
|
}
|
||||||
|
|
||||||
|
let dateTimeStr = this.timeFormatter.format(datum);
|
||||||
|
|
||||||
|
// Replace ISO "T" with a space to allow wrapping
|
||||||
|
return dateTimeStr.replace("T", " ");
|
||||||
|
},
|
||||||
|
parseTime(datum) {
|
||||||
|
if (!datum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.timeFormatter.parse(datum);
|
||||||
},
|
},
|
||||||
handleScroll() {
|
handleScroll() {
|
||||||
const thumbsWrapper = this.$refs.thumbsWrapper;
|
const thumbsWrapper = this.$refs.thumbsWrapper;
|
||||||
@ -168,26 +293,35 @@ export default {
|
|||||||
|| (scrollHeight - scrollTop) > 2 * clientHeight;
|
|| (scrollHeight - scrollTop) > 2 * clientHeight;
|
||||||
this.autoScroll = !disableScroll;
|
this.autoScroll = !disableScroll;
|
||||||
},
|
},
|
||||||
paused(state, button = false) {
|
paused(state, type) {
|
||||||
if (arguments.length > 0 && state !== this.isPaused) {
|
|
||||||
this.unselectAllImages();
|
|
||||||
this.isPaused = state;
|
this.isPaused = state;
|
||||||
if (state === true && button) {
|
|
||||||
// If we are pausing, select the latest image in imageHistory
|
if (type === 'button') {
|
||||||
this.setSelectedImage(this.imageHistory[this.imageHistory.length - 1]);
|
this.setFocusedImage(this.imageHistory.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.nextDatum) {
|
if (this.nextImageIndex) {
|
||||||
this.updateValues(this.nextDatum);
|
this.setFocusedImage(this.nextImageIndex);
|
||||||
delete this.nextDatum;
|
delete this.nextImageIndex;
|
||||||
} else {
|
|
||||||
this.updateValues(this.imageHistory[this.imageHistory.length - 1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.autoScroll = true;
|
this.autoScroll = true;
|
||||||
|
},
|
||||||
|
scrollToFocused() {
|
||||||
|
const thumbsWrapper = this.$refs.thumbsWrapper;
|
||||||
|
if (!thumbsWrapper) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.isPaused;
|
let domThumb = thumbsWrapper.children[this.focusedImageIndex];
|
||||||
|
|
||||||
|
if (domThumb) {
|
||||||
|
domThumb.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center'
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
scrollToRight() {
|
scrollToRight() {
|
||||||
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
|
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
|
||||||
@ -201,22 +335,17 @@ export default {
|
|||||||
|
|
||||||
setTimeout(() => this.$refs.thumbsWrapper.scrollLeft = scrollWidth, 0);
|
setTimeout(() => this.$refs.thumbsWrapper.scrollLeft = scrollWidth, 0);
|
||||||
},
|
},
|
||||||
setSelectedImage(image) {
|
setFocusedImage(index, thumbnailClick = false) {
|
||||||
// If we are paused and the current image IS selected, unpause
|
if (this.isPaused && !thumbnailClick) {
|
||||||
// Otherwise, set current image and pause
|
this.nextImageIndex = index;
|
||||||
if (!image) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isPaused && image.selected) {
|
this.focusedImageIndex = index;
|
||||||
this.paused(false);
|
|
||||||
this.unselectAllImages();
|
if (thumbnailClick && !this.isPaused) {
|
||||||
} else {
|
|
||||||
this.imageUrl = this.getImageUrl(image);
|
|
||||||
this.time = this.getTime(image);
|
|
||||||
this.paused(true);
|
this.paused(true);
|
||||||
this.unselectAllImages();
|
|
||||||
image.selected = true;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
boundsChange(bounds, isTick) {
|
boundsChange(bounds, isTick) {
|
||||||
@ -224,98 +353,162 @@ export default {
|
|||||||
this.requestHistory();
|
this.requestHistory();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
requestHistory() {
|
async requestHistory() {
|
||||||
const requestId = ++this.requestCount;
|
let bounds = this.openmct.time.bounds();
|
||||||
|
this.requestCount++;
|
||||||
|
const requestId = this.requestCount;
|
||||||
this.imageHistory = [];
|
this.imageHistory = [];
|
||||||
this.openmct.telemetry
|
let data = await this.openmct.telemetry
|
||||||
.request(this.domainObject, this.bounds)
|
.request(this.domainObject, bounds) || [];
|
||||||
.then((values = []) => {
|
|
||||||
if (this.requestCount === requestId) {
|
if (this.requestCount === requestId) {
|
||||||
// add each image to the history
|
data.forEach((datum, index) => {
|
||||||
// update values for the very last image (set current image time and url)
|
this.updateHistory(datum, index === data.length - 1);
|
||||||
values.forEach((datum, index) => this.updateHistory(datum, index === values.length - 1));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
timeSystemChange(system) {
|
timeSystemChange(system) {
|
||||||
// reset timesystem dependent variables
|
this.timeSystem = this.openmct.time.timeSystem();
|
||||||
this.timeKey = system.key;
|
this.timeKey = this.timeSystem.key;
|
||||||
this.timeFormat = this.openmct.telemetry.getValueFormatter(this.metadata.value(this.timeKey));
|
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||||
|
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
this.trackDuration();
|
||||||
|
},
|
||||||
|
clockChange(clock) {
|
||||||
|
this.trackDuration();
|
||||||
},
|
},
|
||||||
subscribe() {
|
subscribe() {
|
||||||
this.unsubscribe = this.openmct.telemetry
|
this.unsubscribe = this.openmct.telemetry
|
||||||
.subscribe(this.domainObject, (datum) => {
|
.subscribe(this.domainObject, (datum) => {
|
||||||
let parsedTimestamp = this.timeFormat.parse(datum);
|
let parsedTimestamp = this.parseTime(datum);
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
|
||||||
if (parsedTimestamp >= this.bounds.start && parsedTimestamp <= this.bounds.end) {
|
if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
|
||||||
this.updateHistory(datum);
|
this.updateHistory(datum);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
unselectAllImages() {
|
updateHistory(datum, setFocused = true) {
|
||||||
this.imageHistory.forEach(image => image.selected = false);
|
|
||||||
},
|
|
||||||
updateHistory(datum, updateValues = true) {
|
|
||||||
if (this.datumIsNotValid(datum)) {
|
if (this.datumIsNotValid(datum)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.imageHistory.push(datum);
|
this.imageHistory.push(datum);
|
||||||
|
|
||||||
if (updateValues) {
|
if (setFocused) {
|
||||||
this.updateValues(datum);
|
this.setFocusedImage(this.imageHistory.length - 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateValues(datum) {
|
getFormatter(key) {
|
||||||
if (this.isPaused) {
|
let metadataValue = this.metadata.value(key) || { format: key };
|
||||||
this.nextDatum = datum;
|
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||||
|
|
||||||
|
return valueFormatter;
|
||||||
|
},
|
||||||
|
trackDuration() {
|
||||||
|
if (this.canTrackDuration) {
|
||||||
|
this.stopDurationTracking();
|
||||||
|
this.updateDuration();
|
||||||
|
this.durationTracker = window.setInterval(
|
||||||
|
this.updateDuration, DURATION_TRACK_MS
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.stopDurationTracking();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stopDurationTracking() {
|
||||||
|
window.clearInterval(this.durationTracker);
|
||||||
|
},
|
||||||
|
updateDuration() {
|
||||||
|
let currentTime = this.openmct.time.clock().currentValue();
|
||||||
|
this.numericDuration = currentTime - this.parsedSelectedTime;
|
||||||
|
},
|
||||||
|
resetAgeCSS() {
|
||||||
|
this.refreshCSS = true;
|
||||||
|
// unable to make this work with nextTick
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshCSS = false;
|
||||||
|
}, REFRESH_CSS_MS);
|
||||||
|
},
|
||||||
|
nextImage() {
|
||||||
|
if (this.isNextDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.time = this.timeFormat.format(datum);
|
let index = this.focusedImageIndex;
|
||||||
this.imageUrl = this.imageFormat.format(datum);
|
|
||||||
},
|
this.setFocusedImage(++index, THUMBNAIL_CLICKED);
|
||||||
selectedImageIndex() {
|
|
||||||
return this.imageHistory.findIndex(image => image.selected);
|
|
||||||
},
|
|
||||||
setSelectedByIndex(index) {
|
|
||||||
this.setSelectedImage(this.imageHistory[index]);
|
|
||||||
},
|
|
||||||
nextImage() {
|
|
||||||
let index = this.selectedImageIndex();
|
|
||||||
this.setSelectedByIndex(++index);
|
|
||||||
if (index === this.imageHistory.length - 1) {
|
if (index === this.imageHistory.length - 1) {
|
||||||
this.paused(false);
|
this.paused(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prevImage() {
|
prevImage() {
|
||||||
let index = this.selectedImageIndex();
|
if (this.isPrevDisabled) {
|
||||||
if (index === -1) {
|
return;
|
||||||
this.setSelectedByIndex(this.imageHistory.length - 2);
|
}
|
||||||
|
|
||||||
|
let index = this.focusedImageIndex;
|
||||||
|
|
||||||
|
if (index === this.imageHistory.length - 1) {
|
||||||
|
this.setFocusedImage(this.imageHistory.length - 2, THUMBNAIL_CLICKED);
|
||||||
} else {
|
} else {
|
||||||
this.setSelectedByIndex(--index);
|
this.setFocusedImage(--index, THUMBNAIL_CLICKED);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isNextDisabled() {
|
startDrag(e) {
|
||||||
let disabled = false;
|
e.preventDefault();
|
||||||
let index = this.selectedImageIndex();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (index === -1 || index === this.imageHistory.length - 1) {
|
|
||||||
disabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return disabled;
|
|
||||||
},
|
},
|
||||||
isPrevDisabled() {
|
arrowDownHandler(event) {
|
||||||
let disabled = false;
|
let key = event.keyCode;
|
||||||
let index = this.selectedImageIndex();
|
|
||||||
|
|
||||||
if (index === 0 || this.imageHistory.length < 2) {
|
if (this.isLeftOrRightArrowKey(key)) {
|
||||||
disabled = true;
|
this.arrowDown = true;
|
||||||
|
window.clearTimeout(this.arrowDownDelayTimeout);
|
||||||
|
this.arrowDownDelayTimeout = window.setTimeout(() => {
|
||||||
|
this.arrowKeyScroll(this.directionByKey(key));
|
||||||
|
}, ARROW_DOWN_DELAY_CHECK_MS);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
arrowUpHandler(event) {
|
||||||
|
let key = event.keyCode;
|
||||||
|
|
||||||
|
window.clearTimeout(this.arrowDownDelayTimeout);
|
||||||
|
|
||||||
|
if (this.isLeftOrRightArrowKey(key)) {
|
||||||
|
this.arrowDown = false;
|
||||||
|
let direction = this.directionByKey(key);
|
||||||
|
this[direction + 'Image']();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
arrowKeyScroll(direction) {
|
||||||
|
if (this.arrowDown) {
|
||||||
|
this.arrowKeyScrolling = true;
|
||||||
|
this[direction + 'Image']();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.arrowKeyScroll(direction);
|
||||||
|
}, ARROW_SCROLL_RATE_MS);
|
||||||
|
} else {
|
||||||
|
window.clearTimeout(this.arrowDownDelayTimeout);
|
||||||
|
this.arrowKeyScrolling = false;
|
||||||
|
this.scrollToFocused();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directionByKey(keyCode) {
|
||||||
|
let direction;
|
||||||
|
|
||||||
|
if (keyCode === ARROW_LEFT) {
|
||||||
|
direction = 'prev';
|
||||||
}
|
}
|
||||||
|
|
||||||
return disabled;
|
if (keyCode === ARROW_RIGHT) {
|
||||||
|
direction = 'next';
|
||||||
|
}
|
||||||
|
|
||||||
|
return direction;
|
||||||
|
},
|
||||||
|
isLeftOrRightArrowKey(keyCode) {
|
||||||
|
return [ARROW_RIGHT, ARROW_LEFT].includes(keyCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
.c-imagery {
|
.c-imagery {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex: 1 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-top: $interiorMargin;
|
margin-top: $interiorMargin;
|
||||||
@ -15,24 +19,75 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__main-image {
|
&__main-image {
|
||||||
background-position: center;
|
&__bg {
|
||||||
background-repeat: no-repeat;
|
background-color: $colorPlotBg;
|
||||||
background-size: contain;
|
border: 1px solid transparent;
|
||||||
height: 100%;
|
flex: 1 1 auto;
|
||||||
|
|
||||||
&.unnsynced{
|
&.unnsynced{
|
||||||
@include sUnsynced();
|
@include sUnsynced();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__control-bar {
|
&__image {
|
||||||
padding: 5px 0 0 0;
|
@include abs(); // Safari fix
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__control-bar,
|
||||||
|
&__time {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: baseline;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__control-bar {
|
||||||
|
margin-top: 2px;
|
||||||
|
padding: $interiorMarginSm 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__time {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__timestamp,
|
||||||
|
&__age {
|
||||||
|
@include ellipsize();
|
||||||
|
flex: 0 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__timestamp {
|
&__timestamp {
|
||||||
flex: 1 1 auto;
|
flex-shrink: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__age {
|
||||||
|
border-radius: $controlCr;
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: baseline;
|
||||||
|
padding: 1px $interiorMarginSm;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
opacity: 0.5;
|
||||||
|
margin-right: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--new {
|
||||||
|
// New imagery
|
||||||
|
$bgColor: $colorOk;
|
||||||
|
background: rgba($bgColor, 0.5);
|
||||||
|
@include flash($animName: flashImageAge, $dur: 250ms, $valStart: rgba($colorOk, 0.7), $valEnd: rgba($colorOk, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
&__thumbs-wrapper {
|
&__thumbs-wrapper {
|
||||||
@ -91,11 +146,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-image-main {
|
|
||||||
background-color: $colorPlotBg;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*************************************** IMAGERY LOCAL CONTROLS*/
|
/*************************************** IMAGERY LOCAL CONTROLS*/
|
||||||
.c-imagery {
|
.c-imagery {
|
||||||
.h-local-controls--overlay-content {
|
.h-local-controls--overlay-content {
|
||||||
@ -105,7 +155,7 @@
|
|||||||
background: $colorLocalControlOvrBg;
|
background: $colorLocalControlOvrBg;
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
min-width: 100px;
|
min-width: 70px;
|
||||||
width: 35%;
|
width: 35%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: $interiorMargin $interiorMarginLg;
|
padding: $interiorMargin $interiorMarginLg;
|
||||||
@ -126,6 +176,7 @@
|
|||||||
&__lc {
|
&__lc {
|
||||||
&__reset-btn {
|
&__reset-btn {
|
||||||
$bc: $scrollbarTrackColorBg;
|
$bc: $scrollbarTrackColorBg;
|
||||||
|
|
||||||
&:before,
|
&:before,
|
||||||
&:after {
|
&:after {
|
||||||
border-right: 1px solid $bc;
|
border-right: 1px solid $bc;
|
||||||
@ -148,9 +199,51 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-image-controls {
|
||||||
|
// Brightness/contrast
|
||||||
|
|
||||||
|
&__controls {
|
||||||
|
// Sliders and reset element
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: $interiorMargin; // Need some extra space due to proximity to close button
|
||||||
|
}
|
||||||
|
|
||||||
|
&__sliders {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-top: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__slider-wrapper {
|
||||||
|
// A wrapper is needed to add the type icon to left of each range input
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
color: rgba($colorMenuFg, 0.5);
|
||||||
|
margin-right: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='range'] {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btn-reset {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*************************************** BUTTONS */
|
/*************************************** BUTTONS */
|
||||||
.c-button.pause-play {
|
.c-button.pause-play {
|
||||||
// Pause icon set by default in markup
|
// Pause icon set by default in markup
|
||||||
|
justify-self: end;
|
||||||
|
|
||||||
&.is-paused {
|
&.is-paused {
|
||||||
background: $colorPausedBg !important;
|
background: $colorPausedBg !important;
|
||||||
color: $colorPausedFg;
|
color: $colorPausedFg;
|
||||||
@ -162,14 +255,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c-imagery__prev-next-buttons {
|
.c-imagery__prev-next-buttons {
|
||||||
//background: rgba(deeppink, 0.2);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-75%);
|
||||||
|
|
||||||
.c-nav {
|
.c-nav {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
@ -28,6 +28,8 @@ export default class NewFolderAction {
|
|||||||
this.key = 'newFolder';
|
this.key = 'newFolder';
|
||||||
this.description = 'Create a new folder';
|
this.description = 'Create a new folder';
|
||||||
this.cssClass = 'icon-folder-new';
|
this.cssClass = 'icon-folder-new';
|
||||||
|
this.group = "action";
|
||||||
|
this.priority = 9;
|
||||||
|
|
||||||
this._openmct = openmct;
|
this._openmct = openmct;
|
||||||
this._dialogForm = {
|
this._dialogForm = {
|
||||||
|
@ -23,6 +23,6 @@ import NewFolderAction from './newFolderAction';
|
|||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
openmct.contextMenu.registerAction(new NewFolderAction(openmct));
|
openmct.actions.register(new NewFolderAction(openmct));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,7 @@ describe("the plugin", () => {
|
|||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless();
|
openmct.startHeadless();
|
||||||
|
|
||||||
newFolderAction = openmct.contextMenu._allActions.filter(action => {
|
newFolderAction = openmct.actions._allActions.newFolder;
|
||||||
return action.key === 'newFolder';
|
|
||||||
})[0];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
39
src/plugins/notebook/actions/CopyToNotebookAction.js
Normal file
39
src/plugins/notebook/actions/CopyToNotebookAction.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { getDefaultNotebook } from '../utils/notebook-storage';
|
||||||
|
import { addNotebookEntry } from '../utils/notebook-entries';
|
||||||
|
|
||||||
|
export default class CopyToNotebookAction {
|
||||||
|
constructor(openmct) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
|
||||||
|
this.cssClass = 'icon-duplicate';
|
||||||
|
this.description = 'Copy to Notebook action';
|
||||||
|
this.group = "action";
|
||||||
|
this.key = 'copyToNotebook';
|
||||||
|
this.name = 'Copy to Notebook';
|
||||||
|
this.priority = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyToNotebook(entryText) {
|
||||||
|
const notebookStorage = getDefaultNotebook();
|
||||||
|
this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
|
||||||
|
.then(domainObject => {
|
||||||
|
addNotebookEntry(this.openmct, domainObject, notebookStorage, null, entryText);
|
||||||
|
|
||||||
|
const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
|
||||||
|
const msg = `Saved to Notebook ${defaultPath}`;
|
||||||
|
this.openmct.notifications.info(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(objectPath, viewContext) {
|
||||||
|
this.copyToNotebook(viewContext.formattedValueForCopy());
|
||||||
|
}
|
||||||
|
|
||||||
|
appliesTo(objectPath, viewContext) {
|
||||||
|
if (viewContext && viewContext.getViewKey) {
|
||||||
|
return viewContext.getViewKey().includes('alphanumeric-format');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -9,10 +9,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<SearchResults v-if="search.length"
|
<SearchResults v-if="search.length"
|
||||||
ref="searchResults"
|
ref="searchResults"
|
||||||
:results="getSearchResults()"
|
:domain-object="internalDomainObject"
|
||||||
|
:results="searchedEntries"
|
||||||
@changeSectionPage="changeSelectedSection"
|
@changeSectionPage="changeSelectedSection"
|
||||||
|
@updateEntries="updateEntries"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="!search.length"
|
<div v-if="!search.length"
|
||||||
class="c-notebook__body"
|
class="c-notebook__body"
|
||||||
>
|
>
|
||||||
@ -105,15 +106,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NotebookEntry from './notebook-entry.vue';
|
import NotebookEntry from './NotebookEntry.vue';
|
||||||
import Search from '@/ui/components/search.vue';
|
import Search from '@/ui/components/search.vue';
|
||||||
import SearchResults from './search-results.vue';
|
import SearchResults from './SearchResults.vue';
|
||||||
import Sidebar from './sidebar.vue';
|
import Sidebar from './Sidebar.vue';
|
||||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
||||||
import { addNotebookEntry, createNewEmbed, getNotebookEntries } from '../utils/notebook-entries';
|
import { addNotebookEntry, createNewEmbed, getNotebookEntries } from '../utils/notebook-entries';
|
||||||
import { throttle } from 'lodash';
|
import objectUtils from 'objectUtils';
|
||||||
|
|
||||||
const DEFAULT_CLASS = 'is-notebook-default';
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject', 'snapshotContainer'],
|
inject: ['openmct', 'domainObject', 'snapshotContainer'],
|
||||||
@ -153,6 +154,9 @@ export default {
|
|||||||
pages() {
|
pages() {
|
||||||
return this.getPages() || [];
|
return this.getPages() || [];
|
||||||
},
|
},
|
||||||
|
searchedEntries() {
|
||||||
|
return this.getSearchResults();
|
||||||
|
},
|
||||||
sections() {
|
sections() {
|
||||||
return this.internalDomainObject.configuration.sections || [];
|
return this.internalDomainObject.configuration.sections || [];
|
||||||
},
|
},
|
||||||
@ -172,8 +176,6 @@ export default {
|
|||||||
return this.sections.find(section => section.isSelected);
|
return this.sections.find(section => section.isSelected);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
},
|
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
this.throttledSearchItem = throttle(this.searchItem, 500);
|
this.throttledSearchItem = throttle(this.searchItem, 500);
|
||||||
},
|
},
|
||||||
@ -195,15 +197,6 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addDefaultClass() {
|
|
||||||
const classList = this.internalDomainObject.classList || [];
|
|
||||||
if (classList.includes(DEFAULT_CLASS)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
classList.push(DEFAULT_CLASS);
|
|
||||||
this.mutateObject('classList', classList);
|
|
||||||
},
|
|
||||||
changeSelectedSection({ sectionId, pageId }) {
|
changeSelectedSection({ sectionId, pageId }) {
|
||||||
const sections = this.sections.map(s => {
|
const sections = this.sections.map(s => {
|
||||||
s.isSelected = false;
|
s.isSelected = false;
|
||||||
@ -259,7 +252,7 @@ export default {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
const snapshotId = event.dataTransfer.getData('snapshot/id');
|
const snapshotId = event.dataTransfer.getData('openmct/snapshot/id');
|
||||||
if (snapshotId.length) {
|
if (snapshotId.length) {
|
||||||
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
|
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
|
||||||
this.newEntry(snapshot);
|
this.newEntry(snapshot);
|
||||||
@ -423,14 +416,7 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const classList = domainObject.classList || [];
|
this.openmct.status.delete(domainObject.identifier);
|
||||||
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) {
|
searchItem(input) {
|
||||||
this.search = input;
|
this.search = input;
|
||||||
@ -440,11 +426,20 @@ export default {
|
|||||||
},
|
},
|
||||||
async updateDefaultNotebook(notebookStorage) {
|
async updateDefaultNotebook(notebookStorage) {
|
||||||
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
||||||
|
if (!defaultNotebookObject) {
|
||||||
|
setDefaultNotebook(this.openmct, notebookStorage);
|
||||||
|
} else if (objectUtils.makeKeyString(defaultNotebookObject.identifier) !== objectUtils.makeKeyString(notebookStorage.notebookMeta.identifier)) {
|
||||||
this.removeDefaultClass(defaultNotebookObject);
|
this.removeDefaultClass(defaultNotebookObject);
|
||||||
setDefaultNotebook(this.openmct, notebookStorage);
|
setDefaultNotebook(this.openmct, notebookStorage);
|
||||||
this.addDefaultClass();
|
}
|
||||||
|
|
||||||
|
if (this.defaultSectionId.length === 0 || this.defaultSectionId !== notebookStorage.section.id) {
|
||||||
this.defaultSectionId = notebookStorage.section.id;
|
this.defaultSectionId = notebookStorage.section.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.defaultPageId.length === 0 || this.defaultPageId !== notebookStorage.page.id) {
|
||||||
this.defaultPageId = notebookStorage.page.id;
|
this.defaultPageId = notebookStorage.page.id;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateDefaultNotebookPage(pages, id) {
|
updateDefaultNotebookPage(pages, id) {
|
||||||
if (!id) {
|
if (!id) {
|
@ -17,7 +17,7 @@
|
|||||||
<div v-if="embed.snapshot"
|
<div v-if="embed.snapshot"
|
||||||
class="c-ne__embed__time"
|
class="c-ne__embed__time"
|
||||||
>
|
>
|
||||||
{{ formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss') }}
|
{{ createdOn }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -25,10 +25,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
import PopupMenu from './popup-menu.vue';
|
import PopupMenu from './PopupMenu.vue';
|
||||||
import PreviewAction from '../../../ui/preview/PreviewAction';
|
import PreviewAction from '../../../ui/preview/PreviewAction';
|
||||||
import Painterro from 'painterro';
|
|
||||||
import RemoveDialog from '../utils/removeDialog';
|
import RemoveDialog from '../utils/removeDialog';
|
||||||
|
import PainterroInstance from '../utils/painterroInstance';
|
||||||
import SnapshotTemplate from './snapshot-template.html';
|
import SnapshotTemplate from './snapshot-template.html';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
@ -56,7 +56,10 @@ export default {
|
|||||||
popupMenuItems: []
|
popupMenuItems: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
computed: {
|
||||||
|
createdOn() {
|
||||||
|
return this.formatTime(this.embed.createdOn, 'YYYY-MM-DD HH:mm:ss');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.addPopupMenuItems();
|
this.addPopupMenuItems();
|
||||||
@ -78,95 +81,44 @@ export default {
|
|||||||
this.popupMenuItems = [removeEmbed, preview];
|
this.popupMenuItems = [removeEmbed, preview];
|
||||||
},
|
},
|
||||||
annotateSnapshot() {
|
annotateSnapshot() {
|
||||||
const self = this;
|
|
||||||
|
|
||||||
let save = false;
|
|
||||||
let painterroInstance = {};
|
|
||||||
const annotateVue = new Vue({
|
const annotateVue = new Vue({
|
||||||
template: '<div id="snap-annotation"></div>'
|
template: '<div id="snap-annotation"></div>'
|
||||||
});
|
}).$mount();
|
||||||
|
|
||||||
let annotateOverlay = self.openmct.overlays.overlay({
|
const painterroInstance = new PainterroInstance(annotateVue.$el, this.updateSnapshot);
|
||||||
element: annotateVue.$mount().$el,
|
const annotateOverlay = this.openmct.overlays.overlay({
|
||||||
|
element: annotateVue.$el,
|
||||||
size: 'large',
|
size: 'large',
|
||||||
dismissable: false,
|
dismissable: false,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: 'Cancel',
|
label: 'Cancel',
|
||||||
callback: function () {
|
emphasis: true,
|
||||||
save = false;
|
callback: () => {
|
||||||
painterroInstance.save();
|
painterroInstance.dismiss();
|
||||||
annotateOverlay.dismiss();
|
annotateOverlay.dismiss();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Save',
|
label: 'Save',
|
||||||
callback: function () {
|
callback: () => {
|
||||||
|
|
||||||
save = true;
|
|
||||||
painterroInstance.save();
|
painterroInstance.save();
|
||||||
annotateOverlay.dismiss();
|
annotateOverlay.dismiss();
|
||||||
|
this.snapshotOverlay.dismiss();
|
||||||
|
this.openSnapshot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
onDestroy: function () {
|
onDestroy: () => {
|
||||||
annotateVue.$destroy(true);
|
annotateVue.$destroy(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
painterroInstance = Painterro({
|
painterroInstance.intialize();
|
||||||
id: 'snap-annotation',
|
painterroInstance.show(this.embed.snapshot.src);
|
||||||
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() {
|
changeLocation() {
|
||||||
const link = this.embed.historicLink;
|
const link = this.embed.historicLink;
|
||||||
if (!link) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bounds = this.openmct.time.bounds();
|
const bounds = this.openmct.time.bounds();
|
||||||
const isTimeBoundChanged = this.embed.bounds.start !== bounds.start
|
const isTimeBoundChanged = this.embed.bounds.start !== bounds.start
|
||||||
@ -191,7 +143,8 @@ export default {
|
|||||||
this.openmct.notifications.alert(message);
|
this.openmct.notifications.alert(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.href = link;
|
const url = new URL(link);
|
||||||
|
window.location.href = url.hash;
|
||||||
},
|
},
|
||||||
formatTime(unixTime, timeFormat) {
|
formatTime(unixTime, timeFormat) {
|
||||||
return Moment.utc(unixTime).format(timeFormat);
|
return Moment.utc(unixTime).format(timeFormat);
|
||||||
@ -209,7 +162,8 @@ export default {
|
|||||||
this.snapshot = new Vue({
|
this.snapshot = new Vue({
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
embed: self.embed
|
createdOn: this.createdOn,
|
||||||
|
embed: this.embed
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -218,13 +172,11 @@ export default {
|
|||||||
exportImage: self.exportImage
|
exportImage: self.exportImage
|
||||||
},
|
},
|
||||||
template: SnapshotTemplate
|
template: SnapshotTemplate
|
||||||
});
|
}).$mount();
|
||||||
|
|
||||||
const snapshotOverlay = this.openmct.overlays.overlay({
|
this.snapshotOverlay = this.openmct.overlays.overlay({
|
||||||
element: this.snapshot.$mount().$el,
|
element: this.snapshot.$el,
|
||||||
onDestroy: () => {
|
onDestroy: () => this.snapshot.$destroy(true),
|
||||||
this.snapshot.$destroy(true);
|
|
||||||
},
|
|
||||||
size: 'large',
|
size: 'large',
|
||||||
dismissable: true,
|
dismissable: true,
|
||||||
buttons: [
|
buttons: [
|
||||||
@ -232,7 +184,7 @@ export default {
|
|||||||
label: 'Done',
|
label: 'Done',
|
||||||
emphasis: true,
|
emphasis: true,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
snapshotOverlay.dismiss();
|
this.snapshotOverlay.dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -262,6 +214,10 @@ export default {
|
|||||||
},
|
},
|
||||||
updateEmbed(embed) {
|
updateEmbed(embed) {
|
||||||
this.$emit('updateEmbed', embed);
|
this.$emit('updateEmbed', embed);
|
||||||
|
},
|
||||||
|
updateSnapshot(snapshotObject) {
|
||||||
|
this.embed.snapshot = snapshotObject;
|
||||||
|
this.updateEmbed(this.embed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -1,23 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-notebook__entry c-ne has-local-controls"
|
<div class="c-notebook__entry c-ne has-local-controls"
|
||||||
@dragover="dragover"
|
@dragover="changeCursor"
|
||||||
@drop.capture="dropCapture"
|
@drop.capture="cancelEditMode"
|
||||||
@drop.prevent="dropOnEntry(entry.id, $event)"
|
@drop.prevent="dropOnEntry"
|
||||||
>
|
>
|
||||||
<div class="c-ne__time-and-content">
|
<div class="c-ne__time-and-content">
|
||||||
<div class="c-ne__time">
|
<div class="c-ne__time">
|
||||||
<span>{{ formatTime(entry.createdOn, 'YYYY-MM-DD') }}</span>
|
<span>{{ createdOnDate }}</span>
|
||||||
<span>{{ formatTime(entry.createdOn, 'HH:mm:ss') }}</span>
|
<span>{{ createdOnTime }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-ne__content">
|
<div class="c-ne__content">
|
||||||
<div :id="entry.id"
|
<div :id="entry.id"
|
||||||
class="c-ne__text"
|
class="c-ne__text"
|
||||||
:class="{'c-input-inline' : !readOnly }"
|
:class="{'c-ne__input' : !readOnly }"
|
||||||
:contenteditable="!readOnly"
|
:contenteditable="!readOnly"
|
||||||
:style="!entry.text.length ? defaultEntryStyle : ''"
|
@blur="updateEntryValue($event, entry.id)"
|
||||||
@blur="textBlur($event, entry.id)"
|
@focus="updateCurrentEntryValue($event, entry.id)"
|
||||||
@focus="textFocus($event, entry.id)"
|
>{{ entry.text }}</div>
|
||||||
>{{ entry.text.length ? entry.text : defaultText }}</div>
|
|
||||||
<div class="c-snapshots c-ne__embeds">
|
<div class="c-snapshots c-ne__embeds">
|
||||||
<NotebookEmbed v-for="embed in entry.embeds"
|
<NotebookEmbed v-for="embed in entry.embeds"
|
||||||
:key="embed.id"
|
:key="embed.id"
|
||||||
@ -57,7 +56,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NotebookEmbed from './notebook-embed.vue';
|
import NotebookEmbed from './NotebookEmbed.vue';
|
||||||
import { createNewEmbed, getEntryPosById, getNotebookEntries } from '../utils/notebook-entries';
|
import { createNewEmbed, getEntryPosById, getNotebookEntries } from '../utils/notebook-entries';
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
|
|
||||||
@ -106,37 +105,35 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentEntryValue: '',
|
currentEntryValue: ''
|
||||||
defaultEntryStyle: {
|
|
||||||
fontStyle: 'italic',
|
|
||||||
color: '#6e6e6e'
|
|
||||||
},
|
|
||||||
defaultText: 'add description'
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
computed: {
|
||||||
entry() {
|
createdOnDate() {
|
||||||
|
return this.formatTime(this.entry.createdOn, 'YYYY-MM-DD');
|
||||||
},
|
},
|
||||||
readOnly(readOnly) {
|
createdOnTime() {
|
||||||
},
|
return this.formatTime(this.entry.createdOn, 'HH:mm:ss');
|
||||||
selectedSection(selectedSection) {
|
|
||||||
},
|
|
||||||
selectedPage(selectedSection) {
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.updateEntries = this.updateEntries.bind(this);
|
this.updateEntries = this.updateEntries.bind(this);
|
||||||
},
|
this.dropOnEntry = this.dropOnEntry.bind(this);
|
||||||
beforeDestory() {
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
cancelEditMode(event) {
|
||||||
|
const isEditing = this.openmct.editor.isEditing();
|
||||||
|
if (isEditing) {
|
||||||
|
this.openmct.editor.cancel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeCursor() {
|
||||||
|
event.preventDefault();
|
||||||
|
event.dataTransfer.dropEffect = "copy";
|
||||||
|
},
|
||||||
deleteEntry() {
|
deleteEntry() {
|
||||||
const self = this;
|
const self = this;
|
||||||
if (!self.domainObject || !self.selectedSection || !self.selectedPage || !self.entry.id) {
|
const entryPosById = self.entryPosById(self.entry.id);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entryPosById = this.entryPosById(this.entry.id);
|
|
||||||
if (entryPosById === -1) {
|
if (entryPosById === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -151,7 +148,7 @@ export default {
|
|||||||
callback: () => {
|
callback: () => {
|
||||||
const entries = getNotebookEntries(self.domainObject, self.selectedSection, self.selectedPage);
|
const entries = getNotebookEntries(self.domainObject, self.selectedSection, self.selectedPage);
|
||||||
entries.splice(entryPosById, 1);
|
entries.splice(entryPosById, 1);
|
||||||
this.updateEntries(entries);
|
self.updateEntries(entries);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -164,24 +161,10 @@ export default {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
dragover() {
|
dropOnEntry($event) {
|
||||||
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();
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
|
const snapshotId = $event.dataTransfer.getData('openmct/snapshot/id');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const snapshotId = $event.dataTransfer.getData('snapshot/id');
|
|
||||||
if (snapshotId.length) {
|
if (snapshotId.length) {
|
||||||
this.moveSnapshot(snapshotId);
|
this.moveSnapshot(snapshotId);
|
||||||
|
|
||||||
@ -190,7 +173,7 @@ export default {
|
|||||||
|
|
||||||
const data = $event.dataTransfer.getData('openmct/domain-object-path');
|
const data = $event.dataTransfer.getData('openmct/domain-object-path');
|
||||||
const objectPath = JSON.parse(data);
|
const objectPath = JSON.parse(data);
|
||||||
const entryPos = this.entryPosById(entryId);
|
const entryPos = this.entryPosById(this.entry.id);
|
||||||
const bounds = this.openmct.time.bounds();
|
const bounds = this.openmct.time.bounds();
|
||||||
const snapshotMeta = {
|
const snapshotMeta = {
|
||||||
bounds,
|
bounds,
|
||||||
@ -246,14 +229,40 @@ export default {
|
|||||||
this.entry.embeds.splice(embedPosition, 1);
|
this.entry.embeds.splice(embedPosition, 1);
|
||||||
this.updateEntry(this.entry);
|
this.updateEntry(this.entry);
|
||||||
},
|
},
|
||||||
selectTextInsideElement(element) {
|
updateCurrentEntryValue($event) {
|
||||||
const range = document.createRange();
|
if (this.readOnly) {
|
||||||
range.selectNodeContents(element);
|
return;
|
||||||
let selection = window.getSelection();
|
}
|
||||||
selection.removeAllRanges();
|
|
||||||
selection.addRange(range);
|
const target = $event.target;
|
||||||
|
this.currentEntryValue = target ? target.textContent : '';
|
||||||
},
|
},
|
||||||
textBlur($event, entryId) {
|
updateEmbed(newEmbed) {
|
||||||
|
this.entry.embeds.some(e => {
|
||||||
|
const found = (e.id === newEmbed.id);
|
||||||
|
if (found) {
|
||||||
|
e = newEmbed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateEntry(this.entry);
|
||||||
|
},
|
||||||
|
updateEntry(newEntry) {
|
||||||
|
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
|
entries.some(entry => {
|
||||||
|
const found = (entry.id === newEntry.id);
|
||||||
|
if (found) {
|
||||||
|
entry = newEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateEntries(entries);
|
||||||
|
},
|
||||||
|
updateEntryValue($event, entryId) {
|
||||||
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
|
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -266,48 +275,14 @@ export default {
|
|||||||
const entryPos = this.entryPosById(entryId);
|
const entryPos = this.entryPosById(entryId);
|
||||||
const value = target.textContent.trim();
|
const value = target.textContent.trim();
|
||||||
if (this.currentEntryValue !== value) {
|
if (this.currentEntryValue !== value) {
|
||||||
|
target.textContent = value;
|
||||||
|
|
||||||
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
entries[entryPos].text = value;
|
entries[entryPos].text = value;
|
||||||
|
|
||||||
this.updateEntries(entries);
|
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) {
|
updateEntries(entries) {
|
||||||
this.$emit('updateEntries', entries);
|
this.$emit('updateEntries', entries);
|
||||||
}
|
}
|
@ -1,29 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
<button
|
<button
|
||||||
class="c-button--menu icon-notebook"
|
class="c-icon-button c-button--menu icon-camera"
|
||||||
title="Take a Notebook Snapshot"
|
title="Take a Notebook Snapshot"
|
||||||
@click="setNotebookTypes"
|
@click.stop.prevent="showMenu"
|
||||||
@click.stop="toggleMenu"
|
|
||||||
>
|
>
|
||||||
<span class="c-button__label"></span>
|
<span
|
||||||
|
title="Take Notebook Snapshot"
|
||||||
|
class="c-icon-button__label"
|
||||||
|
>
|
||||||
|
Snapshot
|
||||||
|
</span>
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -57,22 +45,20 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
notebookSnapshot: null,
|
notebookSnapshot: null,
|
||||||
notebookTypes: [],
|
notebookTypes: []
|
||||||
showMenu: false
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.notebookSnapshot = new Snapshot(this.openmct);
|
this.notebookSnapshot = new Snapshot(this.openmct);
|
||||||
|
this.setDefaultNotebookStatus();
|
||||||
document.addEventListener('click', this.hideMenu);
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
document.removeEventListener('click', this.hideMenu);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setNotebookTypes() {
|
showMenu(event) {
|
||||||
const notebookTypes = [];
|
const notebookTypes = [];
|
||||||
const defaultNotebook = getDefaultNotebook();
|
const defaultNotebook = getDefaultNotebook();
|
||||||
|
const elementBoundingClientRect = this.$el.getBoundingClientRect();
|
||||||
|
const x = elementBoundingClientRect.x;
|
||||||
|
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
|
||||||
|
|
||||||
if (defaultNotebook) {
|
if (defaultNotebook) {
|
||||||
const domainObject = defaultNotebook.domainObject;
|
const domainObject = defaultNotebook.domainObject;
|
||||||
@ -83,35 +69,31 @@ export default {
|
|||||||
notebookTypes.push({
|
notebookTypes.push({
|
||||||
cssClass: 'icon-notebook',
|
cssClass: 'icon-notebook',
|
||||||
name: `Save to Notebook ${defaultPath}`,
|
name: `Save to Notebook ${defaultPath}`,
|
||||||
type: NOTEBOOK_DEFAULT
|
callBack: () => {
|
||||||
|
return this.snapshot(NOTEBOOK_DEFAULT);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notebookTypes.push({
|
notebookTypes.push({
|
||||||
cssClass: 'icon-notebook',
|
cssClass: 'icon-camera',
|
||||||
name: 'Save to Notebook Snapshots',
|
name: 'Save to Notebook Snapshots',
|
||||||
type: NOTEBOOK_SNAPSHOT
|
callBack: () => {
|
||||||
|
return this.snapshot(NOTEBOOK_SNAPSHOT);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.notebookTypes = notebookTypes;
|
this.openmct.menus.showMenu(x, y, notebookTypes);
|
||||||
},
|
|
||||||
toggleMenu() {
|
|
||||||
this.showMenu = !this.showMenu;
|
|
||||||
},
|
|
||||||
hideMenu() {
|
|
||||||
this.showMenu = false;
|
|
||||||
},
|
},
|
||||||
snapshot(notebook) {
|
snapshot(notebook) {
|
||||||
this.hideMenu();
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const element = document.querySelector('.c-overlay__contents')
|
const element = document.querySelector('.c-overlay__contents')
|
||||||
|| document.getElementsByClassName('l-shell__main-container')[0];
|
|| document.getElementsByClassName('l-shell__main-container')[0];
|
||||||
|
|
||||||
const bounds = this.openmct.time.bounds();
|
const bounds = this.openmct.time.bounds();
|
||||||
const link = !this.ignoreLink
|
const link = !this.ignoreLink
|
||||||
? window.location.href
|
? window.location.hash
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const objectPath = this.objectPath || this.openmct.router.path;
|
const objectPath = this.objectPath || this.openmct.router.path;
|
||||||
@ -124,6 +106,15 @@ export default {
|
|||||||
|
|
||||||
this.notebookSnapshot.capture(snapshotMeta, notebook.type, element);
|
this.notebookSnapshot.capture(snapshotMeta, notebook.type, element);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
setDefaultNotebookStatus() {
|
||||||
|
let defaultNotebookObject = getDefaultNotebook();
|
||||||
|
|
||||||
|
if (defaultNotebookObject && defaultNotebookObject.notebookMeta) {
|
||||||
|
let notebookIdentifier = defaultNotebookObject.notebookMeta.identifier;
|
||||||
|
|
||||||
|
this.openmct.status.set(notebookIdentifier, 'notebook-default');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -4,7 +4,7 @@
|
|||||||
<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">
|
||||||
<div class="l-browse-bar__object-name c-object-label">
|
<div class="l-browse-bar__object-name c-object-label">
|
||||||
<div class="c-object-label__type-icon icon-notebook"></div>
|
<div class="c-object-label__type-icon icon-camera"></div>
|
||||||
<div class="c-object-label__name">
|
<div class="c-object-label__name">
|
||||||
Notebook Snapshots
|
Notebook Snapshots
|
||||||
<span v-if="snapshots.length"
|
<span v-if="snapshots.length"
|
||||||
@ -49,8 +49,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NotebookEmbed from './notebook-embed.vue';
|
import NotebookEmbed from './NotebookEmbed.vue';
|
||||||
import PopupMenu from './popup-menu.vue';
|
import PopupMenu from './PopupMenu.vue';
|
||||||
import RemoveDialog from '../utils/removeDialog';
|
import RemoveDialog from '../utils/removeDialog';
|
||||||
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
|
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
|
||||||
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
|
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
|
||||||
@ -81,8 +81,6 @@ export default {
|
|||||||
this.snapshotContainer.on(EVENT_SNAPSHOTS_UPDATED, this.snapshotsUpdated);
|
this.snapshotContainer.on(EVENT_SNAPSHOTS_UPDATED, this.snapshotsUpdated);
|
||||||
this.snapshots = this.snapshotContainer.getSnapshots();
|
this.snapshots = this.snapshotContainer.getSnapshots();
|
||||||
},
|
},
|
||||||
beforeDestory() {
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
addPopupMenuItems() {
|
addPopupMenuItems() {
|
||||||
const removeSnapshot = {
|
const removeSnapshot = {
|
||||||
@ -122,7 +120,7 @@ export default {
|
|||||||
},
|
},
|
||||||
startEmbedDrag(snapshot, event) {
|
startEmbedDrag(snapshot, event) {
|
||||||
event.dataTransfer.setData('text/plain', snapshot.id);
|
event.dataTransfer.setData('text/plain', snapshot.id);
|
||||||
event.dataTransfer.setData('snapshot/id', snapshot.id);
|
event.dataTransfer.setData('openmct/snapshot/id', snapshot.id);
|
||||||
},
|
},
|
||||||
updateSnapshot(snapshot) {
|
updateSnapshot(snapshot) {
|
||||||
this.snapshotContainer.updateSnapshot(snapshot);
|
this.snapshotContainer.updateSnapshot(snapshot);
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-indicator c-indicator--clickable icon-notebook"
|
<div class="c-indicator c-indicator--clickable icon-camera"
|
||||||
:class="[
|
:class="[
|
||||||
{ 's-status-off': snapshotCount === 0 },
|
{ 's-status-off': snapshotCount === 0 },
|
||||||
{ 's-status-on': snapshotCount > 0 },
|
{ 's-status-on': snapshotCount > 0 },
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SnapshotContainerComponent from './notebook-snapshot-container.vue';
|
import SnapshotContainerComponent from './NotebookSnapshotContainer.vue';
|
||||||
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
|
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
|
||||||
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
|
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
@ -19,7 +19,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { deleteNotebookEntries } from '../utils/notebook-entries';
|
import { deleteNotebookEntries } from '../utils/notebook-entries';
|
||||||
import { getDefaultNotebook } from '../utils/notebook-storage';
|
import { getDefaultNotebook } from '../utils/notebook-storage';
|
||||||
import Page from './page-component.vue';
|
import Page from './PageComponent.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
@ -70,12 +70,6 @@ export default {
|
|||||||
return {
|
return {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
deletePage(id) {
|
deletePage(id) {
|
||||||
const selectedSection = this.sections.find(s => s.isSelected);
|
const selectedSection = this.sections.find(s => s.isSelected);
|
@ -14,7 +14,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PopupMenu from './popup-menu.vue';
|
import PopupMenu from './PopupMenu.vue';
|
||||||
import RemoveDialog from '../utils/removeDialog';
|
import RemoveDialog from '../utils/removeDialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -55,8 +55,6 @@ export default {
|
|||||||
this.addPopupMenuItems();
|
this.addPopupMenuItems();
|
||||||
this.toggleContentEditable();
|
this.toggleContentEditable();
|
||||||
},
|
},
|
||||||
destroyed() {
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
addPopupMenuItems() {
|
addPopupMenuItems() {
|
||||||
const removePage = {
|
const removePage = {
|
@ -8,7 +8,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MenuItems from './menu-items.vue';
|
import MenuItems from './MenuItems.vue';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
@ -4,26 +4,34 @@
|
|||||||
<div class="c-notebook__entries">
|
<div class="c-notebook__entries">
|
||||||
<NotebookEntry v-for="(result, index) in results"
|
<NotebookEntry v-for="(result, index) in results"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
:domain-object="domainObject"
|
||||||
:result="result"
|
:result="result"
|
||||||
:entry="result.entry"
|
:entry="result.entry"
|
||||||
:read-only="true"
|
:read-only="true"
|
||||||
:selected-page="null"
|
:selected-page="result.page"
|
||||||
:selected-section="null"
|
:selected-section="result.section"
|
||||||
@changeSectionPage="changeSectionPage"
|
@changeSectionPage="changeSectionPage"
|
||||||
|
@updateEntries="updateEntries"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NotebookEntry from './notebook-entry.vue';
|
import NotebookEntry from './NotebookEntry.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'snapshotContainer'],
|
||||||
components: {
|
components: {
|
||||||
NotebookEntry
|
NotebookEntry
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
domainObject: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
results: {
|
results: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default() {
|
default() {
|
||||||
@ -31,19 +39,12 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
results(newResults) {}
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
changeSectionPage(data) {
|
changeSectionPage(data) {
|
||||||
this.$emit('changeSectionPage', data);
|
this.$emit('changeSectionPage', data);
|
||||||
|
},
|
||||||
|
updateEntries(entries) {
|
||||||
|
this.$emit('updateEntries', entries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -19,7 +19,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { deleteNotebookEntries } from '../utils/notebook-entries';
|
import { deleteNotebookEntries } from '../utils/notebook-entries';
|
||||||
import { getDefaultNotebook } from '../utils/notebook-storage';
|
import { getDefaultNotebook } from '../utils/notebook-storage';
|
||||||
import sectionComponent from './section-component.vue';
|
import sectionComponent from './SectionComponent.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
@ -57,12 +57,6 @@ export default {
|
|||||||
return {
|
return {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
deleteSection(id) {
|
deleteSection(id) {
|
||||||
const section = this.sections.find(s => s.id === id);
|
const section = this.sections.find(s => s.id === id);
|
@ -17,7 +17,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PopupMenu from './popup-menu.vue';
|
import PopupMenu from './PopupMenu.vue';
|
||||||
import RemoveDialog from '../utils/removeDialog';
|
import RemoveDialog from '../utils/removeDialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -58,8 +58,6 @@ export default {
|
|||||||
this.addPopupMenuItems();
|
this.addPopupMenuItems();
|
||||||
this.toggleContentEditable();
|
this.toggleContentEditable();
|
||||||
},
|
},
|
||||||
destroyed() {
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
addPopupMenuItems() {
|
addPopupMenuItems() {
|
||||||
const removeSection = {
|
const removeSection = {
|
@ -56,8 +56,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SectionCollection from './section-collection.vue';
|
import SectionCollection from './SectionCollection.vue';
|
||||||
import PageCollection from './page-collection.vue';
|
import PageCollection from './PageCollection.vue';
|
||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -139,8 +139,6 @@ export default {
|
|||||||
this.addSection();
|
this.addSection();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed() {
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
addPage() {
|
addPage() {
|
||||||
const pageTitle = this.pageTitle;
|
const pageTitle = this.pageTitle;
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<div class="l-browse-bar__end">
|
<div class="l-browse-bar__end">
|
||||||
<div class="l-browse-bar__snapshot-datetime">
|
<div class="l-browse-bar__snapshot-datetime">
|
||||||
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
|
SNAPSHOT {{ createdOn }}
|
||||||
</div>
|
</div>
|
||||||
<span class="c-button-set c-button-set--strip-h">
|
<span class="c-button-set c-button-set--strip-h">
|
||||||
<button
|
<button
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Notebook from './components/notebook.vue';
|
import CopyToNotebookAction from './actions/CopyToNotebookAction';
|
||||||
import NotebookSnapshotIndicator from './components/notebook-snapshot-indicator.vue';
|
import Notebook from './components/Notebook.vue';
|
||||||
|
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
|
||||||
import SnapshotContainer from './snapshot-container';
|
import SnapshotContainer from './snapshot-container';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
@ -13,6 +14,8 @@ export default function NotebookPlugin() {
|
|||||||
|
|
||||||
installed = true;
|
installed = true;
|
||||||
|
|
||||||
|
openmct.actions.register(new CopyToNotebookAction(openmct));
|
||||||
|
|
||||||
const notebookType = {
|
const notebookType = {
|
||||||
name: 'Notebook',
|
name: 'Notebook',
|
||||||
description: 'Create and save timestamped notes with embedded object snapshots.',
|
description: 'Create and save timestamped notes with embedded object snapshots.',
|
||||||
@ -95,7 +98,8 @@ export default function NotebookPlugin() {
|
|||||||
template: '<NotebookSnapshotIndicator></NotebookSnapshotIndicator>'
|
template: '<NotebookSnapshotIndicator></NotebookSnapshotIndicator>'
|
||||||
});
|
});
|
||||||
const indicator = {
|
const indicator = {
|
||||||
element: notebookSnapshotIndicator.$mount().$el
|
element: notebookSnapshotIndicator.$mount().$el,
|
||||||
|
key: 'notebook-snapshot-indicator'
|
||||||
};
|
};
|
||||||
|
|
||||||
openmct.indicators.add(indicator);
|
openmct.indicators.add(indicator);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user