mirror of
https://github.com/nasa/openmct.git
synced 2025-05-02 08:43:17 +00:00
Example imagery vue (#2525)
* WIP: imagery vue refactor * cleaup * show orange border when paused. * resize image and thumbs wrappers. * scrollToBottom fixed. * fixed lint errors * use multipane vue component for resize + cleanup + style adjustments. * added min-height to image pane and thumbs-layout pane. * remove old plugin and using es6 const. * using ES6 imports. * clean up + formatting changes. * updated as per review comments. * extracted styles from vue component. * fixed lint errors. * updated as per review comments + cleanup.
This commit is contained in:
parent
2248c2da08
commit
3b195e9c7d
@ -1,86 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"./src/policies/ImageryViewPolicy",
|
|
||||||
"./src/controllers/ImageryController",
|
|
||||||
"./src/directives/MCTBackgroundImage",
|
|
||||||
"./res/templates/imagery.html"
|
|
||||||
], function (
|
|
||||||
ImageryViewPolicy,
|
|
||||||
ImageryController,
|
|
||||||
MCTBackgroundImage,
|
|
||||||
imageryTemplate
|
|
||||||
) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
name:"platform/features/imagery",
|
|
||||||
definition: {
|
|
||||||
"name": "Plot view for telemetry",
|
|
||||||
"extensions": {
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"name": "Imagery",
|
|
||||||
"key": "imagery",
|
|
||||||
"cssClass": "icon-image",
|
|
||||||
"template": imageryTemplate,
|
|
||||||
"priority": "preferred",
|
|
||||||
"needs": [
|
|
||||||
"telemetry"
|
|
||||||
],
|
|
||||||
"editable": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"category": "view",
|
|
||||||
"implementation": ImageryViewPolicy,
|
|
||||||
"depends": [
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "ImageryController",
|
|
||||||
"implementation": ImageryController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"$window",
|
|
||||||
"$element",
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "mctBackgroundImage",
|
|
||||||
"implementation": MCTBackgroundImage,
|
|
||||||
"depends": [
|
|
||||||
"$document"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,58 +0,0 @@
|
|||||||
<div class="t-imagery c-imagery" ng-controller="ImageryController as imagery">
|
|
||||||
<mct-split-pane class='abs' anchor="bottom" alias="imagery">
|
|
||||||
<div class="split-pane-component has-local-controls l-image-main-wrapper l-flex-col">
|
|
||||||
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc">
|
|
||||||
<span class="holder flex-elem grows c-imagery__lc__sliders">
|
|
||||||
<input class="icon-brightness" type="range"
|
|
||||||
min="0"
|
|
||||||
max="500"
|
|
||||||
ng-model="filters.brightness" />
|
|
||||||
<input class="icon-contrast" type="range"
|
|
||||||
min="0"
|
|
||||||
max="500"
|
|
||||||
ng-model="filters.contrast" />
|
|
||||||
</span>
|
|
||||||
<span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
|
|
||||||
<a class="s-icon-button icon-reset t-btn-reset"
|
|
||||||
ng-click="filters = { brightness: 100, contrast: 100 }"></a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="l-image-main s-image-main flex-elem grows"
|
|
||||||
ng-class="{ paused: imagery.paused(), stale:false }">
|
|
||||||
<div class="image-main"
|
|
||||||
|
|
||||||
mct-background-image="imagery.getImageUrl()"
|
|
||||||
filters="filters">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="l-image-main-controlbar flex-elem l-flex-row">
|
|
||||||
<div class="l-datetime-w flex-elem grows">
|
|
||||||
<a class="c-button show-thumbs sm hidden icon-thumbs-strip"
|
|
||||||
ng-click="showThumbsBubble = (showThumbsBubble) ? false:true"></a>
|
|
||||||
<span class="l-time">{{imagery.getTime()}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="h-local-controls flex-elem">
|
|
||||||
<a class="c-button icon-pause pause-play"
|
|
||||||
ng-click="imagery.paused(!imagery.paused())"
|
|
||||||
ng-class="{ 'is-paused': imagery.paused() }"></a>
|
|
||||||
<a href=""
|
|
||||||
class="s-button l-mag s-mag vsm icon-reset"
|
|
||||||
ng-click="clipped = false"
|
|
||||||
ng-show="clipped === true"
|
|
||||||
title="Not all of image is visible; click to reset."></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<mct-splitter></mct-splitter>
|
|
||||||
<div class="split-pane-component l-image-thumbs-wrapper">
|
|
||||||
<div class="l-image-thumb-item" ng-class="{selected: image.selected}" ng-repeat="image in imageHistory track by $index"
|
|
||||||
ng-click="imagery.setSelectedImage(image)" ng-init="imagery.scrollToBottom()">
|
|
||||||
<img class="l-thumb"
|
|
||||||
ng-src={{imagery.getImageUrl(image)}}>
|
|
||||||
<div class="l-time">{{imagery.getTime(image)}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mct-split-pane>
|
|
||||||
</div>
|
|
@ -1,284 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle implements views of image telemetry.
|
|
||||||
* @namespace platform/features/imagery
|
|
||||||
*/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'zepto',
|
|
||||||
'lodash'
|
|
||||||
],
|
|
||||||
function ($, _) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the "Imagery" view of a domain object which
|
|
||||||
* provides image telemetry.
|
|
||||||
* @constructor
|
|
||||||
* @memberof platform/features/imagery
|
|
||||||
*/
|
|
||||||
|
|
||||||
function ImageryController($scope, $window, element, openmct) {
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.$window = $window;
|
|
||||||
this.openmct = openmct;
|
|
||||||
this.date = "";
|
|
||||||
this.time = "";
|
|
||||||
this.zone = "";
|
|
||||||
this.imageUrl = "";
|
|
||||||
this.requestCount = 0;
|
|
||||||
this.scrollable = $(".l-image-thumbs-wrapper");
|
|
||||||
this.autoScroll = openmct.time.clock() ? true : false;
|
|
||||||
this.$scope.imageHistory = [];
|
|
||||||
this.$scope.filters = {
|
|
||||||
brightness: 100,
|
|
||||||
contrast: 100
|
|
||||||
};
|
|
||||||
|
|
||||||
this.subscribe = this.subscribe.bind(this);
|
|
||||||
this.stopListening = this.stopListening.bind(this);
|
|
||||||
this.updateValues = this.updateValues.bind(this);
|
|
||||||
this.updateHistory = this.updateHistory.bind(this);
|
|
||||||
this.onBoundsChange = this.onBoundsChange.bind(this);
|
|
||||||
this.onScroll = this.onScroll.bind(this);
|
|
||||||
this.setSelectedImage = this.setSelectedImage.bind(this);
|
|
||||||
|
|
||||||
this.subscribe(this.$scope.domainObject);
|
|
||||||
|
|
||||||
this.$scope.$on('$destroy', this.stopListening);
|
|
||||||
this.openmct.time.on('bounds', this.onBoundsChange);
|
|
||||||
this.scrollable.on('scroll', this.onScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageryController.prototype.subscribe = function (domainObject) {
|
|
||||||
this.date = "";
|
|
||||||
this.imageUrl = "";
|
|
||||||
this.openmct.objects.get(domainObject.getId())
|
|
||||||
.then(function (object) {
|
|
||||||
this.domainObject = object;
|
|
||||||
var metadata = this.openmct
|
|
||||||
.telemetry
|
|
||||||
.getMetadata(this.domainObject);
|
|
||||||
this.timeKey = this.openmct.time.timeSystem().key;
|
|
||||||
this.timeFormat = this.openmct
|
|
||||||
.telemetry
|
|
||||||
.getValueFormatter(metadata.value(this.timeKey));
|
|
||||||
this.imageFormat = this.openmct
|
|
||||||
.telemetry
|
|
||||||
.getValueFormatter(metadata.valuesForHints(['image'])[0]);
|
|
||||||
this.unsubscribe = this.openmct.telemetry
|
|
||||||
.subscribe(this.domainObject, function (datum) {
|
|
||||||
this.updateHistory(datum);
|
|
||||||
this.updateValues(datum);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.requestHistory(this.openmct.time.bounds());
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
ImageryController.prototype.requestHistory = function (bounds) {
|
|
||||||
this.requestCount++;
|
|
||||||
this.$scope.imageHistory = [];
|
|
||||||
var requestId = this.requestCount;
|
|
||||||
this.openmct.telemetry
|
|
||||||
.request(this.domainObject, bounds)
|
|
||||||
.then(function (values) {
|
|
||||||
if (this.requestCount > requestId) {
|
|
||||||
return Promise.resolve('Stale request');
|
|
||||||
}
|
|
||||||
|
|
||||||
values.forEach(function (datum) {
|
|
||||||
this.updateHistory(datum);
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.updateValues(values[values.length - 1]);
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
ImageryController.prototype.stopListening = function () {
|
|
||||||
this.openmct.time.off('bounds', this.onBoundsChange);
|
|
||||||
this.scrollable.off('scroll', this.onScroll);
|
|
||||||
if (this.unsubscribe) {
|
|
||||||
this.unsubscribe();
|
|
||||||
delete this.unsubscribe;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Responds to bound change event be requesting new
|
|
||||||
* historical data if the bound change was manual.
|
|
||||||
* @private
|
|
||||||
* @param {object} [newBounds] new bounds object
|
|
||||||
* @param {boolean} [tick] true when change is automatic
|
|
||||||
*/
|
|
||||||
ImageryController.prototype.onBoundsChange = function (newBounds, tick) {
|
|
||||||
if (this.domainObject && !tick) {
|
|
||||||
this.requestHistory(newBounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates displayable values to match those of the most
|
|
||||||
* recently received datum.
|
|
||||||
* @param {object} [datum] the datum
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ImageryController.prototype.updateValues = function (datum) {
|
|
||||||
if (this.isPaused) {
|
|
||||||
this.nextDatum = datum;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.time = this.timeFormat.format(datum);
|
|
||||||
this.imageUrl = this.imageFormat.format(datum);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends given imagery datum to running history.
|
|
||||||
* @private
|
|
||||||
* @param {object} [datum] target telemetry datum
|
|
||||||
* @returns {boolean} falsy when a duplicate datum is given
|
|
||||||
*/
|
|
||||||
ImageryController.prototype.updateHistory = function (datum) {
|
|
||||||
if (!this.datumMatchesMostRecent(datum)) {
|
|
||||||
var index = _.sortedIndex(this.$scope.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat));
|
|
||||||
this.$scope.imageHistory.splice(index, 0, datum);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks to see if the given datum is the same as the most recent in history.
|
|
||||||
* @private
|
|
||||||
* @param {object} [datum] target telemetry datum
|
|
||||||
* @returns {boolean} true if datum is most recent in history, false otherwise
|
|
||||||
*/
|
|
||||||
ImageryController.prototype.datumMatchesMostRecent = function (datum) {
|
|
||||||
if (this.$scope.imageHistory.length !== 0) {
|
|
||||||
var datumTime = this.timeFormat.format(datum);
|
|
||||||
var datumURL = this.imageFormat.format(datum);
|
|
||||||
var lastHistoryTime = this.timeFormat.format(this.$scope.imageHistory.slice(-1)[0]);
|
|
||||||
var lastHistoryURL = this.imageFormat.format(this.$scope.imageHistory.slice(-1)[0]);
|
|
||||||
|
|
||||||
return datumTime === lastHistoryTime && datumURL === lastHistoryURL;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
ImageryController.prototype.onScroll = function (event) {
|
|
||||||
this.$window.requestAnimationFrame(function () {
|
|
||||||
var thumbnailWrapperHeight = this.scrollable[0].offsetHeight;
|
|
||||||
var thumbnailWrapperWidth = this.scrollable[0].offsetWidth;
|
|
||||||
if (this.scrollable[0].scrollLeft <
|
|
||||||
(this.scrollable[0].scrollWidth - this.scrollable[0].clientWidth) - (thumbnailWrapperWidth) ||
|
|
||||||
this.scrollable[0].scrollTop <
|
|
||||||
(this.scrollable[0].scrollHeight - this.scrollable[0].clientHeight) - (thumbnailWrapperHeight)) {
|
|
||||||
this.autoScroll = false;
|
|
||||||
} else {
|
|
||||||
this.autoScroll = true;
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force history imagery div to scroll to bottom.
|
|
||||||
*/
|
|
||||||
ImageryController.prototype.scrollToBottom = function () {
|
|
||||||
if (this.autoScroll) {
|
|
||||||
this.scrollable[0].scrollTop = this.scrollable[0].scrollHeight;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the time portion (hours, minutes, seconds) of the
|
|
||||||
* timestamp associated with the incoming image telemetry
|
|
||||||
* if no parameter is given, or of a provided datum.
|
|
||||||
* @param {object} [datum] target telemetry datum
|
|
||||||
* @returns {string} the time
|
|
||||||
*/
|
|
||||||
ImageryController.prototype.getTime = function (datum) {
|
|
||||||
return datum ?
|
|
||||||
this.timeFormat.format(datum) :
|
|
||||||
this.time;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the URL of the most recent image telemetry if no
|
|
||||||
* parameter is given, or of a provided datum.
|
|
||||||
* @param {object} [datum] target telemetry datum
|
|
||||||
* @returns {string} URL for telemetry image
|
|
||||||
*/
|
|
||||||
ImageryController.prototype.getImageUrl = function (datum) {
|
|
||||||
return datum ?
|
|
||||||
this.imageFormat.format(datum) :
|
|
||||||
this.imageUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter-setter for paused state of the view (true means
|
|
||||||
* paused, false means not.)
|
|
||||||
* @param {boolean} [state] the state to set
|
|
||||||
* @returns {boolean} the current state
|
|
||||||
*/
|
|
||||||
ImageryController.prototype.paused = function (state) {
|
|
||||||
if (arguments.length > 0 && state !== this.isPaused) {
|
|
||||||
this.unselectAllImages();
|
|
||||||
this.isPaused = state;
|
|
||||||
if (this.nextDatum) {
|
|
||||||
this.updateValues(this.nextDatum);
|
|
||||||
delete this.nextDatum;
|
|
||||||
} else {
|
|
||||||
this.updateValues(this.$scope.imageHistory[this.$scope.imageHistory.length - 1]);
|
|
||||||
}
|
|
||||||
this.autoScroll = true;
|
|
||||||
}
|
|
||||||
return this.isPaused;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the selected image on the state for the large imagery div to use.
|
|
||||||
* @param {object} [image] the image object to get url from.
|
|
||||||
*/
|
|
||||||
ImageryController.prototype.setSelectedImage = function (image) {
|
|
||||||
this.imageUrl = this.getImageUrl(image);
|
|
||||||
this.time = this.getTime(image);
|
|
||||||
this.paused(true);
|
|
||||||
this.unselectAllImages();
|
|
||||||
image.selected = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loop through the history imagery data to set all images to unselected.
|
|
||||||
*/
|
|
||||||
ImageryController.prototype.unselectAllImages = function () {
|
|
||||||
for (var i = 0; i < this.$scope.imageHistory.length; i++) {
|
|
||||||
this.$scope.imageHistory[i].selected = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return ImageryController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,110 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the `mct-background-image` directive.
|
|
||||||
*
|
|
||||||
* Used as an attribute, this will set the `background-image`
|
|
||||||
* property to the URL given in its value, but only after that
|
|
||||||
* image has loaded; this avoids "flashing" as images change.
|
|
||||||
*
|
|
||||||
* If the value of `mct-background-image`is falsy, no image
|
|
||||||
* will be displayed (immediately.)
|
|
||||||
*
|
|
||||||
* Optionally, a `filters` attribute may be specified as an
|
|
||||||
* object with `brightness` and/or `contrast` properties,
|
|
||||||
* whose values are percentages. A value of 100 will make
|
|
||||||
* no changes to the image's brightness or contrast.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @memberof platform/features/imagery
|
|
||||||
*/
|
|
||||||
function MCTBackgroundImage($document) {
|
|
||||||
function link(scope, element) {
|
|
||||||
// General strategy here:
|
|
||||||
// - Keep count of how many images have been requested; this
|
|
||||||
// counter will be used as an internal identifier or sorts
|
|
||||||
// for each image that loads.
|
|
||||||
// - As the src attribute changes, begin loading those images.
|
|
||||||
// - When images do load, update the background-image property
|
|
||||||
// of the element, but only if a more recently
|
|
||||||
// requested image has not already been loaded.
|
|
||||||
// The order in which URLs are passed in and the order
|
|
||||||
// in which images are actually loaded may be different, so
|
|
||||||
// some strategy like this is necessary to ensure that images
|
|
||||||
// do not display out-of-order.
|
|
||||||
var requested = 0, loaded = 0;
|
|
||||||
|
|
||||||
function updateFilters(filters) {
|
|
||||||
var styleValue = filters ?
|
|
||||||
Object.keys(filters).map(function (k) {
|
|
||||||
return k + "(" + filters[k] + "%)";
|
|
||||||
}).join(' ') :
|
|
||||||
"";
|
|
||||||
element.css('filter', styleValue);
|
|
||||||
element.css('webkitFilter', styleValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextImage(url) {
|
|
||||||
var myCounter = requested,
|
|
||||||
image;
|
|
||||||
|
|
||||||
function useImage() {
|
|
||||||
if (loaded <= myCounter) {
|
|
||||||
loaded = myCounter;
|
|
||||||
element.css('background-image', "url('" + url + "')");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
loaded = myCounter;
|
|
||||||
element.css('background-image', 'none');
|
|
||||||
} else {
|
|
||||||
image = $document[0].createElement('img');
|
|
||||||
image.src = url;
|
|
||||||
image.onload = useImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
requested += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.$watch('mctBackgroundImage', nextImage);
|
|
||||||
scope.$watchCollection('filters', updateFilters);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
restrict: "A",
|
|
||||||
scope: {
|
|
||||||
mctBackgroundImage: "=",
|
|
||||||
filters: "="
|
|
||||||
},
|
|
||||||
link: link
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return MCTBackgroundImage;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'../../../../../src/api/objects/object-utils'
|
|
||||||
], function (
|
|
||||||
objectUtils
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Policy preventing the Imagery view from being made available for
|
|
||||||
* domain objects which do not have associated image telemetry.
|
|
||||||
* @implements {Policy.<View, DomainObject>}
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ImageryViewPolicy(openmct) {
|
|
||||||
this.openmct = openmct;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageryViewPolicy.prototype.hasImageTelemetry = function (domainObject) {
|
|
||||||
var newDO = objectUtils.toNewFormat(
|
|
||||||
domainObject.getModel(),
|
|
||||||
domainObject.getId()
|
|
||||||
);
|
|
||||||
|
|
||||||
var metadata = this.openmct.telemetry.getMetadata(newDO);
|
|
||||||
var values = metadata.valuesForHints(['image']);
|
|
||||||
return values.length >= 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
ImageryViewPolicy.prototype.allow = function (view, domainObject) {
|
|
||||||
if (view.key === 'imagery' || view.key === 'historical-imagery') {
|
|
||||||
return this.hasImageTelemetry(domainObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return ImageryViewPolicy;
|
|
||||||
});
|
|
||||||
|
|
@ -1,271 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"zepto",
|
|
||||||
"../../src/controllers/ImageryController"
|
|
||||||
],
|
|
||||||
function ($, ImageryController) {
|
|
||||||
|
|
||||||
var MOCK_ELEMENT_TEMPLATE =
|
|
||||||
'<div class="l-image-thumbs-wrapper"></div>';
|
|
||||||
|
|
||||||
xdescribe("The Imagery controller", function () {
|
|
||||||
var $scope,
|
|
||||||
openmct,
|
|
||||||
oldDomainObject,
|
|
||||||
newDomainObject,
|
|
||||||
unsubscribe,
|
|
||||||
metadata,
|
|
||||||
prefix,
|
|
||||||
controller,
|
|
||||||
requestPromise,
|
|
||||||
mockWindow,
|
|
||||||
mockElement;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
$scope = jasmine.createSpyObj('$scope', ['$on', '$watch']);
|
|
||||||
oldDomainObject = jasmine.createSpyObj(
|
|
||||||
'domainObject',
|
|
||||||
['getId']
|
|
||||||
);
|
|
||||||
newDomainObject = { name: 'foo' };
|
|
||||||
oldDomainObject.getId.and.returnValue('testID');
|
|
||||||
openmct = {
|
|
||||||
objects: jasmine.createSpyObj('objectAPI', [
|
|
||||||
'get'
|
|
||||||
]),
|
|
||||||
time: jasmine.createSpyObj('timeAPI', [
|
|
||||||
'timeSystem',
|
|
||||||
'clock',
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'bounds'
|
|
||||||
]),
|
|
||||||
telemetry: jasmine.createSpyObj('telemetryAPI', [
|
|
||||||
'subscribe',
|
|
||||||
'request',
|
|
||||||
'getValueFormatter',
|
|
||||||
'getMetadata'
|
|
||||||
])
|
|
||||||
};
|
|
||||||
metadata = jasmine.createSpyObj('metadata', [
|
|
||||||
'value',
|
|
||||||
'valuesForHints'
|
|
||||||
]);
|
|
||||||
metadata.value.and.returnValue("timestamp");
|
|
||||||
metadata.valuesForHints.and.returnValue(["value"]);
|
|
||||||
|
|
||||||
prefix = "formatted ";
|
|
||||||
unsubscribe = jasmine.createSpy('unsubscribe');
|
|
||||||
openmct.telemetry.subscribe.and.returnValue(unsubscribe);
|
|
||||||
openmct.time.timeSystem.and.returnValue({
|
|
||||||
key: 'testKey'
|
|
||||||
});
|
|
||||||
$scope.domainObject = oldDomainObject;
|
|
||||||
openmct.objects.get.and.returnValue(Promise.resolve(newDomainObject));
|
|
||||||
openmct.telemetry.getMetadata.and.returnValue(metadata);
|
|
||||||
openmct.telemetry.getValueFormatter.and.callFake(function (property) {
|
|
||||||
var formatter =
|
|
||||||
jasmine.createSpyObj("formatter-" + property, ['format']);
|
|
||||||
var isTime = (property === "timestamp");
|
|
||||||
formatter.format.and.callFake(function (datum) {
|
|
||||||
return (isTime ? prefix : "") + datum[property];
|
|
||||||
});
|
|
||||||
return formatter;
|
|
||||||
});
|
|
||||||
|
|
||||||
requestPromise = new Promise(function (resolve) {
|
|
||||||
setTimeout(function () {
|
|
||||||
resolve([{
|
|
||||||
timestamp: 1434600258123,
|
|
||||||
value: 'some/url'
|
|
||||||
}]);
|
|
||||||
}, 10);
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.telemetry.request.and.returnValue(requestPromise);
|
|
||||||
mockElement = $(MOCK_ELEMENT_TEMPLATE);
|
|
||||||
mockWindow = jasmine.createSpyObj('$window', ['requestAnimationFrame']);
|
|
||||||
mockWindow.requestAnimationFrame.and.callFake(function (f) {
|
|
||||||
return f();
|
|
||||||
});
|
|
||||||
|
|
||||||
controller = new ImageryController(
|
|
||||||
$scope,
|
|
||||||
mockWindow,
|
|
||||||
mockElement,
|
|
||||||
openmct
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when loaded", function () {
|
|
||||||
var callback,
|
|
||||||
boundsListener,
|
|
||||||
bounds;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
return requestPromise.then(function () {
|
|
||||||
openmct.time.on.calls.all().forEach(function (call) {
|
|
||||||
if (call.args[0] === "bounds") {
|
|
||||||
boundsListener = call.args[1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
callback =
|
|
||||||
openmct.telemetry.subscribe.calls.mostRecent().args[1];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("requests history", function () {
|
|
||||||
expect(openmct.telemetry.request).toHaveBeenCalledWith(
|
|
||||||
newDomainObject, bounds
|
|
||||||
);
|
|
||||||
expect(controller.getTime()).toEqual(prefix + 1434600258123);
|
|
||||||
expect(controller.getImageUrl()).toEqual('some/url');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("exposes the latest telemetry values", function () {
|
|
||||||
callback({
|
|
||||||
timestamp: 1434600259456,
|
|
||||||
value: "some/other/url"
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(controller.getTime()).toEqual(prefix + 1434600259456);
|
|
||||||
expect(controller.getImageUrl()).toEqual("some/other/url");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows updates to be paused and unpaused", function () {
|
|
||||||
var newTimestamp = 1434600259456,
|
|
||||||
newUrl = "some/other/url",
|
|
||||||
initialTimestamp = controller.getTime(),
|
|
||||||
initialUrl = controller.getImageUrl();
|
|
||||||
|
|
||||||
expect(initialTimestamp).not.toBe(prefix + newTimestamp);
|
|
||||||
expect(initialUrl).not.toBe(newUrl);
|
|
||||||
expect(controller.paused()).toBeFalsy();
|
|
||||||
|
|
||||||
controller.paused(true);
|
|
||||||
expect(controller.paused()).toBeTruthy();
|
|
||||||
callback({ timestamp: newTimestamp, value: newUrl });
|
|
||||||
|
|
||||||
expect(controller.getTime()).toEqual(initialTimestamp);
|
|
||||||
expect(controller.getImageUrl()).toEqual(initialUrl);
|
|
||||||
|
|
||||||
controller.paused(false);
|
|
||||||
expect(controller.paused()).toBeFalsy();
|
|
||||||
expect(controller.getTime()).toEqual(prefix + newTimestamp);
|
|
||||||
expect(controller.getImageUrl()).toEqual(newUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("forwards large image view to latest image in history on un-pause", function () {
|
|
||||||
$scope.imageHistory = [
|
|
||||||
{ utc: 1434600258122, url: 'some/url1', selected: false},
|
|
||||||
{ utc: 1434600258123, url: 'some/url2', selected: false}
|
|
||||||
];
|
|
||||||
controller.paused(true);
|
|
||||||
controller.paused(false);
|
|
||||||
|
|
||||||
expect(controller.getImageUrl()).toEqual(controller.getImageUrl($scope.imageHistory[1]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("subscribes to telemetry", function () {
|
|
||||||
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
|
|
||||||
newDomainObject,
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("requests telemetry", function () {
|
|
||||||
expect(openmct.telemetry.request).toHaveBeenCalledWith(
|
|
||||||
newDomainObject,
|
|
||||||
bounds
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("unsubscribes and unlistens when scope is destroyed", function () {
|
|
||||||
expect(unsubscribe).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
$scope.$on.calls.all().forEach(function (call) {
|
|
||||||
if (call.args[0] === '$destroy') {
|
|
||||||
call.args[1]();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(unsubscribe).toHaveBeenCalled();
|
|
||||||
expect(openmct.time.off)
|
|
||||||
.toHaveBeenCalledWith('bounds', jasmine.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for bounds event and responds to tick and manual change", function () {
|
|
||||||
var mockBounds = {start: 1434600000000, end: 1434600500000};
|
|
||||||
expect(openmct.time.on).toHaveBeenCalled();
|
|
||||||
openmct.telemetry.request.calls.reset();
|
|
||||||
boundsListener(mockBounds, true);
|
|
||||||
expect(openmct.telemetry.request).not.toHaveBeenCalled();
|
|
||||||
boundsListener(mockBounds, false);
|
|
||||||
expect(openmct.telemetry.request).toHaveBeenCalledWith(newDomainObject, mockBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it ("doesnt append duplicate datum", function () {
|
|
||||||
var mockDatum = {value: 'image/url', timestamp: 1434700000000};
|
|
||||||
var mockDatum2 = {value: 'image/url', timestamp: 1434700000000};
|
|
||||||
var mockDatum3 = {value: 'image/url', url: 'someval', timestamp: 1434700000000};
|
|
||||||
expect(controller.updateHistory(mockDatum)).toBe(true);
|
|
||||||
expect(controller.updateHistory(mockDatum)).toBe(false);
|
|
||||||
expect(controller.updateHistory(mockDatum)).toBe(false);
|
|
||||||
expect(controller.updateHistory(mockDatum2)).toBe(false);
|
|
||||||
expect(controller.updateHistory(mockDatum3)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when user clicks on imagery thumbnail", function () {
|
|
||||||
var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false};
|
|
||||||
|
|
||||||
it("pauses and adds selected class to imagery thumbnail", function () {
|
|
||||||
controller.setSelectedImage(mockDatum);
|
|
||||||
expect(controller.paused()).toBeTruthy();
|
|
||||||
expect(mockDatum.selected).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("unselects previously selected image", function () {
|
|
||||||
$scope.imageHistory = [{ utc: 1434600258123, url: 'some/url', selected: true}];
|
|
||||||
controller.unselectAllImages();
|
|
||||||
expect($scope.imageHistory[0].selected).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates larger image url and time", function () {
|
|
||||||
controller.setSelectedImage(mockDatum);
|
|
||||||
expect(controller.getImageUrl()).toEqual(controller.getImageUrl(mockDatum));
|
|
||||||
expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("initially shows an empty string for date/time", function () {
|
|
||||||
expect(controller.getTime()).toEqual("");
|
|
||||||
expect(controller.getImageUrl()).toEqual("");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/directives/MCTBackgroundImage"],
|
|
||||||
function (MCTBackgroundImage) {
|
|
||||||
|
|
||||||
describe("The mct-background-image directive", function () {
|
|
||||||
var mockDocument,
|
|
||||||
mockScope,
|
|
||||||
mockElement,
|
|
||||||
testImage,
|
|
||||||
directive;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockDocument = [
|
|
||||||
jasmine.createSpyObj('document', ['createElement'])
|
|
||||||
];
|
|
||||||
mockScope = jasmine.createSpyObj('scope', [
|
|
||||||
'$watch',
|
|
||||||
'$watchCollection'
|
|
||||||
]);
|
|
||||||
mockElement = jasmine.createSpyObj('element', ['css']);
|
|
||||||
testImage = {};
|
|
||||||
|
|
||||||
mockDocument[0].createElement.and.returnValue(testImage);
|
|
||||||
|
|
||||||
directive = new MCTBackgroundImage(mockDocument);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("is applicable as an attribute", function () {
|
|
||||||
expect(directive.restrict).toEqual("A");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("two-way-binds its own value", function () {
|
|
||||||
expect(directive.scope.mctBackgroundImage).toEqual("=");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("once linked", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
directive.link(mockScope, mockElement, {});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("watches for changes to the URL", function () {
|
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
|
||||||
'mctBackgroundImage',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates images in-order, even when they load out-of-order", function () {
|
|
||||||
var firstOnload;
|
|
||||||
|
|
||||||
mockScope.$watch.calls.mostRecent().args[1]("some/url/0");
|
|
||||||
firstOnload = testImage.onload;
|
|
||||||
|
|
||||||
mockScope.$watch.calls.mostRecent().args[1]("some/url/1");
|
|
||||||
|
|
||||||
// Resolve in a different order
|
|
||||||
testImage.onload();
|
|
||||||
firstOnload();
|
|
||||||
|
|
||||||
// Should still have taken the more recent value
|
|
||||||
expect(mockElement.css.calls.mostRecent().args).toEqual([
|
|
||||||
"background-image",
|
|
||||||
"url('some/url/1')"
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clears the background image when undefined is passed in", function () {
|
|
||||||
mockScope.$watch.calls.mostRecent().args[1]("some/url/0");
|
|
||||||
testImage.onload();
|
|
||||||
mockScope.$watch.calls.mostRecent().args[1](undefined);
|
|
||||||
|
|
||||||
expect(mockElement.css.calls.mostRecent().args).toEqual([
|
|
||||||
"background-image",
|
|
||||||
"none"
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates filters on change", function () {
|
|
||||||
var filters = { brightness: 123, contrast: 21 };
|
|
||||||
mockScope.$watchCollection.calls.all().forEach(function (call) {
|
|
||||||
if (call.args[0] === 'filters') {
|
|
||||||
call.args[1](filters);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
|
||||||
'filter',
|
|
||||||
'brightness(123%) contrast(21%)'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clears filters when none are present", function () {
|
|
||||||
mockScope.$watchCollection.calls.all().forEach(function (call) {
|
|
||||||
if (call.args[0] === 'filters') {
|
|
||||||
call.args[1](undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(mockElement.css)
|
|
||||||
.toHaveBeenCalledWith('filter', '');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/policies/ImageryViewPolicy"],
|
|
||||||
function (ImageryViewPolicy) {
|
|
||||||
|
|
||||||
describe("Imagery view policy", function () {
|
|
||||||
var testView,
|
|
||||||
openmct,
|
|
||||||
mockDomainObject,
|
|
||||||
mockTelemetry,
|
|
||||||
mockMetadata,
|
|
||||||
policy;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
testView = { key: "imagery" };
|
|
||||||
mockMetadata = jasmine.createSpyObj('metadata', [
|
|
||||||
"valuesForHints"
|
|
||||||
]);
|
|
||||||
mockDomainObject = jasmine.createSpyObj(
|
|
||||||
'domainObject',
|
|
||||||
['getId', 'getModel', 'getCapability']
|
|
||||||
);
|
|
||||||
mockTelemetry = jasmine.createSpyObj(
|
|
||||||
'telemetry',
|
|
||||||
['getMetadata']
|
|
||||||
);
|
|
||||||
mockDomainObject.getCapability.and.callFake(function (c) {
|
|
||||||
return c === 'telemetry' ? mockTelemetry : undefined;
|
|
||||||
});
|
|
||||||
mockDomainObject.getId.and.returnValue("some-id");
|
|
||||||
mockDomainObject.getModel.and.returnValue({ name: "foo" });
|
|
||||||
mockTelemetry.getMetadata.and.returnValue(mockMetadata);
|
|
||||||
mockMetadata.valuesForHints.and.returnValue(["bar"]);
|
|
||||||
|
|
||||||
openmct = { telemetry: mockTelemetry };
|
|
||||||
|
|
||||||
policy = new ImageryViewPolicy(openmct);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("checks for hints indicating image telemetry", function () {
|
|
||||||
policy.allow(testView, mockDomainObject);
|
|
||||||
expect(mockMetadata.valuesForHints)
|
|
||||||
.toHaveBeenCalledWith(["image"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows the imagery view for domain objects with image telemetry", function () {
|
|
||||||
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("disallows the imagery view for domain objects without image telemetry", function () {
|
|
||||||
mockMetadata.valuesForHints.and.returnValue([]);
|
|
||||||
expect(policy.allow(testView, mockDomainObject)).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows other views", function () {
|
|
||||||
testView.key = "somethingElse";
|
|
||||||
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -33,6 +33,7 @@ define([
|
|||||||
'./adapter/indicators/legacy-indicators-plugin',
|
'./adapter/indicators/legacy-indicators-plugin',
|
||||||
'./plugins/buildInfo/plugin',
|
'./plugins/buildInfo/plugin',
|
||||||
'./ui/registries/ViewRegistry',
|
'./ui/registries/ViewRegistry',
|
||||||
|
'./plugins/imagery/plugin',
|
||||||
'./ui/registries/InspectorViewRegistry',
|
'./ui/registries/InspectorViewRegistry',
|
||||||
'./ui/registries/ToolbarRegistry',
|
'./ui/registries/ToolbarRegistry',
|
||||||
'./ui/router/ApplicationRouter',
|
'./ui/router/ApplicationRouter',
|
||||||
@ -59,6 +60,7 @@ define([
|
|||||||
LegacyIndicatorsPlugin,
|
LegacyIndicatorsPlugin,
|
||||||
buildInfoPlugin,
|
buildInfoPlugin,
|
||||||
ViewRegistry,
|
ViewRegistry,
|
||||||
|
ImageryPlugin,
|
||||||
InspectorViewRegistry,
|
InspectorViewRegistry,
|
||||||
ToolbarRegistry,
|
ToolbarRegistry,
|
||||||
ApplicationRouter,
|
ApplicationRouter,
|
||||||
@ -257,6 +259,7 @@ define([
|
|||||||
this.install(RemoveActionPlugin.default());
|
this.install(RemoveActionPlugin.default());
|
||||||
this.install(this.plugins.FolderView());
|
this.install(this.plugins.FolderView());
|
||||||
this.install(this.plugins.Tabs());
|
this.install(this.plugins.Tabs());
|
||||||
|
this.install(ImageryPlugin.default());
|
||||||
this.install(this.plugins.FlexibleLayout());
|
this.install(this.plugins.FlexibleLayout());
|
||||||
this.install(this.plugins.GoToOriginalAction());
|
this.install(this.plugins.GoToOriginalAction());
|
||||||
this.install(this.plugins.ImportExport());
|
this.install(this.plugins.ImportExport());
|
||||||
|
@ -38,7 +38,6 @@ const DEFAULTS = [
|
|||||||
'platform/exporters',
|
'platform/exporters',
|
||||||
'platform/telemetry',
|
'platform/telemetry',
|
||||||
'platform/features/clock',
|
'platform/features/clock',
|
||||||
'platform/features/imagery',
|
|
||||||
'platform/features/hyperlink',
|
'platform/features/hyperlink',
|
||||||
'platform/features/timeline',
|
'platform/features/timeline',
|
||||||
'platform/forms',
|
'platform/forms',
|
||||||
@ -82,7 +81,6 @@ define([
|
|||||||
'../platform/execution/bundle',
|
'../platform/execution/bundle',
|
||||||
'../platform/exporters/bundle',
|
'../platform/exporters/bundle',
|
||||||
'../platform/features/clock/bundle',
|
'../platform/features/clock/bundle',
|
||||||
'../platform/features/imagery/bundle',
|
|
||||||
'../platform/features/my-items/bundle',
|
'../platform/features/my-items/bundle',
|
||||||
'../platform/features/hyperlink/bundle',
|
'../platform/features/hyperlink/bundle',
|
||||||
'../platform/features/static-markup/bundle',
|
'../platform/features/static-markup/bundle',
|
||||||
|
47
src/plugins/imagery/ImageryViewProvider.js
Normal file
47
src/plugins/imagery/ImageryViewProvider.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import ImageryViewLayout from './components/ImageryViewLayout.vue';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default function ImageryViewProvider(openmct) {
|
||||||
|
const type = 'example.imagery';
|
||||||
|
|
||||||
|
const hasImageTelemetry = function (domainObject) {
|
||||||
|
const metadata = openmct.telemetry.getMetadata(domainObject);
|
||||||
|
if (!metadata) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata.valuesForHints(['image']).length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: type,
|
||||||
|
name: 'Imagery Layout',
|
||||||
|
cssClass: 'icon-image',
|
||||||
|
canView: function (domainObject) {
|
||||||
|
return hasImageTelemetry(domainObject);
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
let component;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
component = new Vue({
|
||||||
|
components: {
|
||||||
|
ImageryViewLayout
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
domainObject
|
||||||
|
},
|
||||||
|
el: element,
|
||||||
|
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
component.$destroy();
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
249
src/plugins/imagery/components/ImageryViewLayout.vue
Normal file
249
src/plugins/imagery/components/ImageryViewLayout.vue
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
<template>
|
||||||
|
<multipane class="c-imagery-layout"
|
||||||
|
type="vertical"
|
||||||
|
>
|
||||||
|
<pane :style="{'min-height': `300px`}">
|
||||||
|
<div class="main-image-wrapper c-imagery 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">
|
||||||
|
<span class="holder flex-elem grows c-imagery__lc__sliders">
|
||||||
|
<input v-model="filters.brightness"
|
||||||
|
class="icon-brightness"
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="500"
|
||||||
|
>
|
||||||
|
<input v-model="filters.contrast"
|
||||||
|
class="icon-contrast"
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="500"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
|
||||||
|
<a class="s-icon-button icon-reset t-btn-reset"
|
||||||
|
@click="filters={brightness: 100, contrast: 100}"
|
||||||
|
></a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-image s-image-main"
|
||||||
|
:class="{'paused unnsynced': paused(),'stale':false }"
|
||||||
|
:style="{'background-image': `url(${getImageUrl()})`,
|
||||||
|
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="l-image-controller flex-elem l-flex-row">
|
||||||
|
<div class="l-datetime-w flex-elem grows">
|
||||||
|
<a class="c-button show-thumbs sm hidden icon-thumbs-strip"></a>
|
||||||
|
<span class="l-time">{{ getTime() }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="h-local-controls flex-elem">
|
||||||
|
<a class="c-button icon-pause pause-play"
|
||||||
|
:class="{'is-paused': paused()}"
|
||||||
|
@click="paused(!paused())"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</pane>
|
||||||
|
|
||||||
|
<pane class="c-inspector__elements"
|
||||||
|
handle="before"
|
||||||
|
:style="{'min-height': `100px`}"
|
||||||
|
>
|
||||||
|
<div class="c-elements-pool">
|
||||||
|
<div ref="thumbsWrapper"
|
||||||
|
class="thumbs-layout"
|
||||||
|
@scroll="handleScroll"
|
||||||
|
>
|
||||||
|
<div v-for="(imageData, index) in imageHistory"
|
||||||
|
:key="index"
|
||||||
|
class="l-image-thumb-item"
|
||||||
|
:class="{selected: imageData.selected}"
|
||||||
|
@click="setSelectedImage(imageData)"
|
||||||
|
>
|
||||||
|
<img class="l-thumb"
|
||||||
|
:src="getImageUrl(imageData)"
|
||||||
|
>
|
||||||
|
<div class="l-time">{{ getTime(imageData) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</pane>
|
||||||
|
</multipane>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import multipane from '@/ui/layout/multipane.vue';
|
||||||
|
import pane from '@/ui/layout/pane.vue';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
|
components: {
|
||||||
|
multipane,
|
||||||
|
pane
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
autoScroll: true,
|
||||||
|
date: '',
|
||||||
|
filters : {
|
||||||
|
brightness: 100,
|
||||||
|
contrast: 100
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
selected: ''
|
||||||
|
},
|
||||||
|
imageFormat: '',
|
||||||
|
imageHistory: [],
|
||||||
|
imageUrl: '',
|
||||||
|
isPaused: false,
|
||||||
|
requestCount: 0,
|
||||||
|
timeFormat: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
this.subscribe(this.domainObject);
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
this.scrollToBottom();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.stopListening();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
datumMatchesMostRecent(datum) {
|
||||||
|
if (this.imageHistory.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const datumTime = this.timeFormat.format(datum);
|
||||||
|
const datumURL = this.imageFormat.format(datum);
|
||||||
|
const lastHistoryTime = this.timeFormat.format(this.imageHistory.slice(-1)[0]);
|
||||||
|
const lastHistoryURL = this.imageFormat.format(this.imageHistory.slice(-1)[0]);
|
||||||
|
|
||||||
|
return (datumTime === lastHistoryTime) && (datumURL === lastHistoryURL);
|
||||||
|
},
|
||||||
|
getImageUrl(datum) {
|
||||||
|
return datum ?
|
||||||
|
this.imageFormat.format(datum) :
|
||||||
|
this.imageUrl;
|
||||||
|
},
|
||||||
|
getTime(datum) {
|
||||||
|
return datum ?
|
||||||
|
this.timeFormat.format(datum) :
|
||||||
|
this.time;
|
||||||
|
},
|
||||||
|
handleScroll() {
|
||||||
|
const thumbsWrapper = this.$refs.thumbsWrapper
|
||||||
|
if (!thumbsWrapper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { scrollLeft, scrollWidth, clientWidth, scrollTop, scrollHeight, clientHeight } = thumbsWrapper;
|
||||||
|
const disableScroll = (scrollWidth - scrollLeft) > 2 * clientWidth
|
||||||
|
|| (scrollHeight - scrollTop) > 2 * clientHeight;
|
||||||
|
this.autoScroll = !disableScroll;
|
||||||
|
},
|
||||||
|
paused(state) {
|
||||||
|
if (arguments.length > 0 && state !== this.isPaused) {
|
||||||
|
this.unselectAllImages();
|
||||||
|
this.isPaused = state;
|
||||||
|
|
||||||
|
if (this.nextDatum) {
|
||||||
|
this.updateValues(this.nextDatum);
|
||||||
|
delete this.nextDatum;
|
||||||
|
} else {
|
||||||
|
this.updateValues(this.imageHistory[this.imageHistory.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.autoScroll = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isPaused;
|
||||||
|
},
|
||||||
|
requestHistory(bounds) {
|
||||||
|
this.requestCount++;
|
||||||
|
this.imageHistory = [];
|
||||||
|
const requestId = this.requestCount;
|
||||||
|
this.openmct.telemetry
|
||||||
|
.request(this.domainObject, bounds)
|
||||||
|
.then((values = []) => {
|
||||||
|
if (this.requestCount > requestId) {
|
||||||
|
return Promise.resolve('Stale request');
|
||||||
|
}
|
||||||
|
|
||||||
|
values.forEach(this.updateHistory);
|
||||||
|
this.updateValues(values[values.length - 1]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
scrollToBottom() {
|
||||||
|
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollHeight = this.$refs.thumbsWrapper.scrollHeight || 0;
|
||||||
|
if (!scrollHeight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => this.$refs.thumbsWrapper.scrollTop = scrollHeight, 0);
|
||||||
|
},
|
||||||
|
setSelectedImage(image) {
|
||||||
|
this.imageUrl = this.getImageUrl(image);
|
||||||
|
this.time = this.getTime(image);
|
||||||
|
this.paused(true);
|
||||||
|
this.unselectAllImages();
|
||||||
|
image.selected = true;
|
||||||
|
},
|
||||||
|
stopListening() {
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
delete this.unsubscribe;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subscribe(domainObject) {
|
||||||
|
this.date = ''
|
||||||
|
this.imageUrl = '';
|
||||||
|
this.openmct.objects.get(this.keystring)
|
||||||
|
.then((object) => {
|
||||||
|
const metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
|
this.timeKey = this.openmct.time.timeSystem().key;
|
||||||
|
this.timeFormat = this.openmct.telemetry.getValueFormatter(metadata.value(this.timeKey));
|
||||||
|
this.imageFormat = this.openmct.telemetry.getValueFormatter(metadata.valuesForHints(['image'])[0]);
|
||||||
|
this.unsubscribe = this.openmct.telemetry
|
||||||
|
.subscribe(this.domainObject, (datum) => {
|
||||||
|
this.updateHistory(datum);
|
||||||
|
this.updateValues(datum);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.requestHistory(this.openmct.time.bounds());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unselectAllImages() {
|
||||||
|
this.imageHistory.forEach(image => image.selected = false);
|
||||||
|
},
|
||||||
|
updateHistory(datum) {
|
||||||
|
if (this.datumMatchesMostRecent(datum)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = _.sortedIndex(this.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat));
|
||||||
|
this.imageHistory.splice(index, 0, datum);
|
||||||
|
},
|
||||||
|
updateValues(datum) {
|
||||||
|
if (this.isPaused) {
|
||||||
|
this.nextDatum = datum;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.time = this.timeFormat.format(datum);
|
||||||
|
this.imageUrl = this.imageFormat.format(datum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
32
src/plugins/imagery/components/imagery-view-layout.scss
Normal file
32
src/plugins/imagery/components/imagery-view-layout.scss
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
.c-imagery-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.main-image-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-image {
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&.unnsynced{
|
||||||
|
@include sUnsynced();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-image-controller {
|
||||||
|
padding: 5px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbs-layout {
|
||||||
|
margin-top: 5px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
8
src/plugins/imagery/plugin.js
Normal file
8
src/plugins/imagery/plugin.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import ImageryViewProvider from './ImageryViewProvider';
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return function install(openmct) {
|
||||||
|
openmct.objectViews.addProvider(new ImageryViewProvider(openmct));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ define([
|
|||||||
'./autoflow/AutoflowTabularPlugin',
|
'./autoflow/AutoflowTabularPlugin',
|
||||||
'./timeConductor/plugin',
|
'./timeConductor/plugin',
|
||||||
'../../example/imagery/plugin',
|
'../../example/imagery/plugin',
|
||||||
|
'./imagery/plugin',
|
||||||
'../../platform/import-export/bundle',
|
'../../platform/import-export/bundle',
|
||||||
'./summaryWidget/plugin',
|
'./summaryWidget/plugin',
|
||||||
'./URLIndicatorPlugin/URLIndicatorPlugin',
|
'./URLIndicatorPlugin/URLIndicatorPlugin',
|
||||||
@ -57,6 +58,7 @@ define([
|
|||||||
AutoflowPlugin,
|
AutoflowPlugin,
|
||||||
TimeConductorPlugin,
|
TimeConductorPlugin,
|
||||||
ExampleImagery,
|
ExampleImagery,
|
||||||
|
ImageryPlugin,
|
||||||
ImportExport,
|
ImportExport,
|
||||||
SummaryWidget,
|
SummaryWidget,
|
||||||
URLIndicatorPlugin,
|
URLIndicatorPlugin,
|
||||||
@ -162,6 +164,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
plugins.ExampleImagery = ExampleImagery;
|
plugins.ExampleImagery = ExampleImagery;
|
||||||
|
plugins.ImageryPlugin = ImageryPlugin;
|
||||||
plugins.Plot = PlotPlugin;
|
plugins.Plot = PlotPlugin;
|
||||||
plugins.TelemetryTable = TelemetryTablePlugin;
|
plugins.TelemetryTable = TelemetryTablePlugin;
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
@import "../plugins/folderView/components/grid-view.scss";
|
@import "../plugins/folderView/components/grid-view.scss";
|
||||||
@import "../plugins/folderView/components/list-item.scss";
|
@import "../plugins/folderView/components/list-item.scss";
|
||||||
@import "../plugins/folderView/components/list-view.scss";
|
@import "../plugins/folderView/components/list-view.scss";
|
||||||
|
@import "../plugins/imagery/components/imagery-view-layout.scss";
|
||||||
@import "../plugins/telemetryTable/components/table-row.scss";
|
@import "../plugins/telemetryTable/components/table-row.scss";
|
||||||
@import "../plugins/telemetryTable/components/telemetry-filter-indicator.scss";
|
@import "../plugins/telemetryTable/components/telemetry-filter-indicator.scss";
|
||||||
@import "../plugins/tabs/components/tabs.scss";
|
@import "../plugins/tabs/components/tabs.scss";
|
||||||
|
@ -31,6 +31,7 @@ const webpackConfig = {
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
"@": path.join(__dirname, "src"),
|
||||||
"legacyRegistry": path.join(__dirname, "src/legacyRegistry"),
|
"legacyRegistry": path.join(__dirname, "src/legacyRegistry"),
|
||||||
"saveAs": "file-saver",
|
"saveAs": "file-saver",
|
||||||
"csv": "comma-separated-values",
|
"csv": "comma-separated-values",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user