mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 10:44:21 +00:00
Compare commits
19 Commits
vue-table-
...
table-fixe
Author | SHA1 | Date | |
---|---|---|---|
d2e064dc52 | |||
7483e886f1 | |||
ef92da4d9d | |||
80b02672a6 | |||
fbf1c68c7a | |||
5eac6e646b | |||
82e5bf2325 | |||
40b7117987 | |||
07ca60e13a | |||
08cd6b1d99 | |||
78ae7b334c | |||
3a28caac06 | |||
c883bbe6c2 | |||
eaa971cb56 | |||
0301d88033 | |||
e2e0cf17db | |||
01a39f4fb7 | |||
1bb1330cba | |||
78c731dbf7 |
23
app.js
23
app.js
@ -42,15 +42,30 @@ app.use('/proxyUrl', function proxyRequest(req, res, next) {
|
||||
|
||||
const webpack = require('webpack');
|
||||
const webpackConfig = require('./webpack.config.js');
|
||||
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||
webpackConfig.plugins.push(function() { this.plugin('watch-run', function(watching, callback) { console.log('Begin compile at ' + new Date()); callback(); }) });
|
||||
|
||||
webpackConfig.entry.openmct = [
|
||||
'webpack-hot-middleware/client',
|
||||
webpackConfig.entry.openmct
|
||||
];
|
||||
|
||||
const compiler = webpack(webpackConfig);
|
||||
const webpackDevRoute = require('webpack-dev-middleware')(
|
||||
compiler, {
|
||||
|
||||
app.use(require('webpack-dev-middleware')(
|
||||
compiler,
|
||||
{
|
||||
publicPath: '/dist',
|
||||
logLevel: 'warn'
|
||||
}
|
||||
);
|
||||
));
|
||||
|
||||
app.use(webpackDevRoute);
|
||||
app.use(require('webpack-hot-middleware')(
|
||||
compiler,
|
||||
{
|
||||
|
||||
}
|
||||
));
|
||||
|
||||
// Expose index.html for development users.
|
||||
app.get('/', function (req, res) {
|
||||
|
@ -18,15 +18,4 @@
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
|
||||
<div ng-if="domainObject.getCapability('editor').inEditContext()"
|
||||
ng-controller="TableOptionsController"
|
||||
class="flex-elem grows l-inspector-part">
|
||||
<mct-form
|
||||
ng-model="configuration.table.columns"
|
||||
structure="columnsForm"
|
||||
name="columnsFormState"
|
||||
class="flex-elem no-margin">
|
||||
</mct-form>
|
||||
</div>
|
||||
-->
|
21
copyright-notice.js
Normal file
21
copyright-notice.js
Normal file
@ -0,0 +1,21 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
@ -27,8 +27,14 @@ define([
|
||||
|
||||
) {
|
||||
|
||||
var RED = 0.9,
|
||||
YELLOW = 0.5,
|
||||
var RED = {
|
||||
sin: 0.9,
|
||||
cos: 0.9
|
||||
},
|
||||
YELLOW = {
|
||||
sin: 0.5,
|
||||
cos: 0.5
|
||||
},
|
||||
LIMITS = {
|
||||
rh: {
|
||||
cssClass: "s-limit-upr s-limit-red",
|
||||
@ -67,17 +73,18 @@ define([
|
||||
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
|
||||
return {
|
||||
evaluate: function (datum, valueMetadata) {
|
||||
var range = valueMetadata ? valueMetadata.key : 'sin'
|
||||
if (datum[range] > RED) {
|
||||
var range = valueMetadata && valueMetadata.key;
|
||||
|
||||
if (datum[range] > RED[range]) {
|
||||
return LIMITS.rh;
|
||||
}
|
||||
if (datum[range] < -RED) {
|
||||
if (datum[range] < -RED[range]) {
|
||||
return LIMITS.rl;
|
||||
}
|
||||
if (datum[range] > YELLOW) {
|
||||
if (datum[range] > YELLOW[range]) {
|
||||
return LIMITS.yh;
|
||||
}
|
||||
if (datum[range] < -YELLOW) {
|
||||
if (datum[range] < -YELLOW[range]) {
|
||||
return LIMITS.yl;
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +34,6 @@
|
||||
<link rel="shortcut icon" href="dist/favicons/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<div class="l-splash-holder s-splash-holder">
|
||||
<div class="l-splash s-splash"></div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
var THIRTY_MINUTES = 30 * 60 * 1000;
|
||||
@ -48,7 +45,6 @@
|
||||
);
|
||||
openmct.install(openmct.plugins.MyItems());
|
||||
openmct.install(openmct.plugins.LocalStorage());
|
||||
openmct.install(openmct.plugins.Espresso());
|
||||
openmct.install(openmct.plugins.Generator());
|
||||
openmct.install(openmct.plugins.ExampleImagery());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
|
15
openmct.js
15
openmct.js
@ -29,22 +29,9 @@ if (document.currentScript) {
|
||||
__webpack_public_path__ = src.replace(matcher, '') + '/';
|
||||
}
|
||||
}
|
||||
const Main = require('./platform/framework/src/Main');
|
||||
const defaultRegistry = require('./src/defaultRegistry');
|
||||
|
||||
const MCT = require('./src/MCT');
|
||||
const buildInfo = require('./src/plugins/buildInfo/plugin');
|
||||
|
||||
var openmct = new MCT();
|
||||
|
||||
openmct.legacyRegistry = defaultRegistry;
|
||||
openmct.install(openmct.plugins.Plot());
|
||||
|
||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||
openmct.install(buildInfo(BUILD_CONSTANTS));
|
||||
}
|
||||
|
||||
openmct.on('start', function () {
|
||||
return new Main().run(defaultRegistry);
|
||||
});
|
||||
|
||||
module.exports = openmct;
|
||||
|
@ -43,6 +43,7 @@
|
||||
"karma-html-reporter": "^0.2.7",
|
||||
"karma-jasmine": "^1.1.2",
|
||||
"karma-webpack": "^3.0.0",
|
||||
"location-bar": "^3.0.1",
|
||||
"lodash": "^3.10.1",
|
||||
"markdown-toc": "^0.11.7",
|
||||
"marked": "^0.3.5",
|
||||
@ -67,6 +68,7 @@
|
||||
"webpack": "^4.16.2",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-dev-middleware": "^3.1.3",
|
||||
"webpack-hot-middleware": "^2.22.3",
|
||||
"zepto": "^1.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -73,15 +73,6 @@ define([
|
||||
legacyRegistry.register("platform/commonUI/browse", {
|
||||
"extensions": {
|
||||
"routes": [
|
||||
{
|
||||
"when": "/browse/:ids*?",
|
||||
"template": browseTemplate,
|
||||
"reloadOnSearch": false
|
||||
},
|
||||
{
|
||||
"when": "",
|
||||
"redirectTo": "/browse/"
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
@ -295,6 +286,20 @@ define([
|
||||
]
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
key: "browseRoot",
|
||||
template: browseTemplate
|
||||
},
|
||||
{
|
||||
key: "browseObject",
|
||||
template: browseObjectTemplate
|
||||
},
|
||||
{
|
||||
key: "inspectorRegion",
|
||||
template: inspectorRegionTemplate
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"name": "screenfull.js",
|
||||
|
@ -66,5 +66,4 @@
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
<mct-include key="'conductor'" class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder"></mct-include>
|
||||
</div>
|
||||
|
@ -47,6 +47,7 @@ define(
|
||||
urlService,
|
||||
defaultPath
|
||||
) {
|
||||
window.browseScope = $scope;
|
||||
var initialPath = ($route.current.params.ids || defaultPath).split("/"),
|
||||
currentIds;
|
||||
|
||||
|
@ -319,6 +319,12 @@ define([
|
||||
]
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
key: "elementsPool",
|
||||
template: elementsTemplate
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"type": "decorator",
|
||||
|
@ -55,7 +55,7 @@ define(
|
||||
|
||||
// A view is editable unless explicitly flagged as not
|
||||
(views || []).forEach(function (view) {
|
||||
if (view.editable === true ||
|
||||
if (isEditable(view) ||
|
||||
(view.key === 'plot' && type.getKey() === 'telemetry.panel') ||
|
||||
(view.key === 'table' && type.getKey() === 'table') ||
|
||||
(view.key === 'rt-table' && type.getKey() === 'rttable')
|
||||
@ -64,6 +64,14 @@ define(
|
||||
}
|
||||
});
|
||||
|
||||
function isEditable(view) {
|
||||
if (typeof view.editable === Function) {
|
||||
return view.editable(domainObject.useCapability('adapter'));
|
||||
} else {
|
||||
return view.editable === true;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
|
@ -55,16 +55,16 @@ define(
|
||||
navigatedObject = this.navigationService.getNavigation(),
|
||||
actionMetadata = action.getMetadata ? action.getMetadata() : {};
|
||||
|
||||
if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
|
||||
// if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
|
||||
if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()) {
|
||||
return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
|
||||
} else {
|
||||
//Target is in the context menu
|
||||
return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
};
|
||||
|
||||
return EditContextualActionPolicy;
|
||||
|
@ -45,15 +45,27 @@ define(
|
||||
|
||||
// Link; install event handlers.
|
||||
function link(scope, element, attrs) {
|
||||
var removeSelectable = openmct.selection.selectable(
|
||||
element[0],
|
||||
scope.$eval(attrs.mctSelectable),
|
||||
attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
|
||||
);
|
||||
|
||||
var isDestroyed = false;
|
||||
scope.$on("$destroy", function () {
|
||||
removeSelectable();
|
||||
isDestroyed = true;
|
||||
});
|
||||
|
||||
openmct.$injector.get('$timeout')(function () {
|
||||
if (isDestroyed) {
|
||||
return;
|
||||
}
|
||||
var removeSelectable = openmct.selection.selectable(
|
||||
element[0],
|
||||
scope.$eval(attrs.mctSelectable),
|
||||
attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
|
||||
);
|
||||
|
||||
scope.$on("$destroy", function () {
|
||||
removeSelectable();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -76,7 +76,7 @@ define(
|
||||
* @returns a domain object
|
||||
*/
|
||||
InspectorController.prototype.selectedItem = function () {
|
||||
return this.$scope.selection[0].context.oldItem;
|
||||
return this.$scope.selection[0] && this.$scope.selection[0].context.oldItem;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,294 +0,0 @@
|
||||
define([
|
||||
"legacyRegistry",
|
||||
"./src/controllers/NotebookController",
|
||||
"./src/controllers/NewEntryController",
|
||||
"./src/controllers/SelectSnapshotController",
|
||||
"./src/controllers/LayoutNotebookController",
|
||||
"./src/directives/MCTSnapshot",
|
||||
"./src/directives/EntryDnd",
|
||||
"./src/actions/ViewSnapshot",
|
||||
"./src/actions/AnnotateSnapshot",
|
||||
"./src/actions/RemoveEmbed",
|
||||
"./src/actions/RemoveSnapshot",
|
||||
"./src/actions/NewEntryContextual",
|
||||
"./src/capabilities/NotebookCapability",
|
||||
"./src/policies/CompositionPolicy",
|
||||
"./res/templates/notebook.html",
|
||||
"./res/templates/entry.html",
|
||||
"./res/templates/annotation.html",
|
||||
"./res/templates/notifications.html",
|
||||
"../layout/res/templates/frame.html",
|
||||
"./res/templates/controls/embedControl.html",
|
||||
"./res/templates/controls/snapSelect.html"
|
||||
], function (
|
||||
legacyRegistry,
|
||||
NotebookController,
|
||||
NewEntryController,
|
||||
SelectSnapshotController,
|
||||
LayoutNotebookController,
|
||||
MCTSnapshot,
|
||||
MCTEntryDnd,
|
||||
ViewSnapshotAction,
|
||||
AnnotateSnapshotAction,
|
||||
RemoveEmbedAction,
|
||||
RemoveSnapshotAction,
|
||||
newEntryAction,
|
||||
NotebookCapability,
|
||||
CompositionPolicy,
|
||||
notebookTemplate,
|
||||
entryTemplate,
|
||||
annotationTemplate,
|
||||
notificationsTemplate,
|
||||
frameTemplate,
|
||||
embedControlTemplate,
|
||||
snapSelectTemplate
|
||||
) {
|
||||
legacyRegistry.register("platform/features/notebook", {
|
||||
"name": "Notebook Plugin",
|
||||
"description": "Create and save timestamped notes with embedded object snapshots.",
|
||||
"extensions":
|
||||
{
|
||||
"types": [
|
||||
{
|
||||
"key": "notebook",
|
||||
"name": "Notebook",
|
||||
"cssClass": "icon-notebook",
|
||||
"description": "Create and save timestamped notes with embedded object snapshots.",
|
||||
"features": ["creation"],
|
||||
"model": {
|
||||
"entries": [],
|
||||
"composition": [],
|
||||
"entryTypes": [],
|
||||
"defaultSort": "-createdOn"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"key": "defaultSort",
|
||||
"name": "Default Sort",
|
||||
"control": "select",
|
||||
"options": [
|
||||
{
|
||||
"name": "Newest First",
|
||||
"value": "-createdOn"
|
||||
},
|
||||
{
|
||||
"name": "Oldest First",
|
||||
"value": "createdOn"
|
||||
}
|
||||
],
|
||||
"cssClass": "l-inline"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"views": [
|
||||
{
|
||||
"key": "notebook.view",
|
||||
"type": "notebook",
|
||||
"cssClass": "icon-notebook",
|
||||
"name": "notebook",
|
||||
"template": notebookTemplate,
|
||||
"editable": false,
|
||||
"uses": [
|
||||
"composition",
|
||||
"action"
|
||||
],
|
||||
"gestures": [
|
||||
"drop"
|
||||
]
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "NotebookController",
|
||||
"implementation": NotebookController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"dialogService",
|
||||
"popupService",
|
||||
"agentService",
|
||||
"objectService",
|
||||
"navigationService",
|
||||
"now",
|
||||
"actionService",
|
||||
"$timeout",
|
||||
"$element",
|
||||
"$sce"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "NewEntryController",
|
||||
"implementation": NewEntryController,
|
||||
"depends": ["$scope",
|
||||
"$rootScope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "selectSnapshotController",
|
||||
"implementation": SelectSnapshotController,
|
||||
"depends": ["$scope",
|
||||
"$rootScope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "LayoutNotebookController",
|
||||
"implementation": LayoutNotebookController,
|
||||
"depends": ["$scope"]
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
{
|
||||
"key": "draggedEntry",
|
||||
"template": entryTemplate
|
||||
},
|
||||
{
|
||||
"key": "frameLayoutNotebook",
|
||||
"template": frameTemplate
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
"key": "annotate-snapshot",
|
||||
"template": annotationTemplate
|
||||
},
|
||||
{
|
||||
"key": "notificationTemplate",
|
||||
"template": notificationsTemplate
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctSnapshot",
|
||||
"implementation": MCTSnapshot,
|
||||
"depends": [
|
||||
"$rootScope",
|
||||
"$document",
|
||||
"exportImageService",
|
||||
"dialogService",
|
||||
"notificationService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctEntryDnd",
|
||||
"implementation": MCTEntryDnd,
|
||||
"depends": [
|
||||
"$rootScope",
|
||||
"$compile",
|
||||
"dndService",
|
||||
"typeService",
|
||||
"notificationService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"key": "view-snapshot",
|
||||
"implementation": ViewSnapshotAction,
|
||||
"name": "View Snapshot",
|
||||
"description": "View the large image in a modal",
|
||||
"category": "embed",
|
||||
"depends": [
|
||||
"$compile"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "annotate-snapshot",
|
||||
"implementation": AnnotateSnapshotAction,
|
||||
"name": "Annotate Snapshot",
|
||||
"cssClass": "icon-pencil labeled",
|
||||
"description": "Annotate embed's snapshot",
|
||||
"category": "embed",
|
||||
"depends": [
|
||||
"dialogService",
|
||||
"dndService",
|
||||
"$rootScope"
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"key": "remove-embed",
|
||||
"implementation": RemoveEmbedAction,
|
||||
"name": "Remove...",
|
||||
"cssClass": "icon-trash labeled",
|
||||
"description": "Remove this embed",
|
||||
"category": [
|
||||
"embed",
|
||||
"embed-no-snap"
|
||||
],
|
||||
"depends": [
|
||||
"dialogService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "remove-snapshot",
|
||||
"implementation": RemoveSnapshotAction,
|
||||
"name": "Remove Snapshot",
|
||||
"cssClass": "icon-trash labeled",
|
||||
"description": "Remove Snapshot of the embed",
|
||||
"category": "embed",
|
||||
"depends": [
|
||||
"dialogService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "notebook-new-entry",
|
||||
"implementation": newEntryAction,
|
||||
"name": "New Notebook Entry",
|
||||
"cssClass": "icon-notebook labeled",
|
||||
"description": "Add a new Notebook entry",
|
||||
"category": [
|
||||
"view-control"
|
||||
],
|
||||
"depends": [
|
||||
"$compile",
|
||||
"$rootScope",
|
||||
"dialogService",
|
||||
"notificationService",
|
||||
"linkService"
|
||||
],
|
||||
"priority": "preferred"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"name": "painterro",
|
||||
"version": "0.2.65",
|
||||
"author": "Ivan Borshchov",
|
||||
"description": "Painterro is JavaScript paint widget which allows editing images directly in a browser.",
|
||||
"website": "https://github.com/ivictbor/painterro",
|
||||
"copyright": "Copyright 2017 Ivan Borshchov",
|
||||
"license": "MIT",
|
||||
"link": "https://github.com/ivictbor/painterro/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"capabilities": [
|
||||
{
|
||||
"key": "notebook",
|
||||
"name": "Notebook Capability",
|
||||
"description": "Provides a capability for looking for a notebook domain object",
|
||||
"implementation": NotebookCapability,
|
||||
"depends": [
|
||||
"typeService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"category": "composition",
|
||||
"implementation": CompositionPolicy,
|
||||
"message": "Objects of this type cannot contain objects of that type."
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"key": "embed-control",
|
||||
"template": embedControlTemplate
|
||||
},
|
||||
{
|
||||
"key": "snapshot-select",
|
||||
"template": snapSelectTemplate
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
@ -1,118 +0,0 @@
|
||||
<div ng-controller="NotebookController as controller" class="mct-notebook w-notebook l-flex-col">
|
||||
<div class="l-notebook-head holder l-flex-row flex-elem">
|
||||
<div class="c-search flex-elem holder grows">
|
||||
<input class="c-search__search-input"
|
||||
type="text" tabindex="10000"
|
||||
ng-model="entrySearch"
|
||||
ng-keyup="controller.search()"/>
|
||||
<a class="c-search__clear-input clear-icon icon-x-in-circle"
|
||||
ng-class="{show: !(entrySearch === '' || entrySearch === undefined)}"
|
||||
ng-click="entrySearch = ''; controller.search()"></a>
|
||||
</div>
|
||||
<div class="notebook-view-controls l-flex-row flex-elem holder">
|
||||
<div class="select notebook-view-controls__filter-time">
|
||||
<select ng-model="showTime">
|
||||
<option value="0" selected="selected">Show all</option>
|
||||
<option value="1">Last hour</option>
|
||||
<option value="8">Last 8 hours</option>
|
||||
<option value="24">Last 24 hours</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select notebook-view-controls__sort">
|
||||
<select ng-model="sortEntries">
|
||||
<option value="-createdOn" selected="selected">Newest first</option>
|
||||
<option value="createdOn">Oldest first</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- drag area -->
|
||||
<div class="holder flex-elem l-flex-row icon-plus labeled l-notebook-drag-area" ng-click="newEntry($event)"
|
||||
id="newEntry" mct-entry-dnd>
|
||||
<span class="label">To start a new entry, click here or drag and drop any object</span>
|
||||
</div>
|
||||
|
||||
<!-- entries -->
|
||||
<div class="holder flex-elem grows w-notebook-entries t-entries-list" ng-mouseover="handleActive()">
|
||||
<ul>
|
||||
<li class="l-flex-row has-local-controls l-notebook-entry s-notebook-entry"
|
||||
id="{{'entry_'+ entry.id}}"
|
||||
ng-if="hoursFilter(showTime,entry.createdOn)"
|
||||
ng-repeat="entry in model.entries | filter:entrySearch | orderBy: sortEntries track by $index"
|
||||
ng-init="$last && finished(model.entries)"
|
||||
mct-entry-dnd>
|
||||
<div class="holder flex-elem l-flex-row grows w-notebook-entry-time-and-content">
|
||||
<div class="holder flex-elem s-notebook-entry-time">
|
||||
<span>{{entry.createdOn | date:'yyyy-MM-dd'}}</span>
|
||||
<span>{{entry.createdOn | date:'HH:mm:ss'}}</span>
|
||||
</div>
|
||||
<div class="holder flex-elem l-flex-col grows l-notebook-entry-content">
|
||||
<div contenteditable="true"
|
||||
ng-blur="textBlur($event, entry.id)"
|
||||
ng-focus="textFocus($event, entry.id)"
|
||||
ng-model="entry.text"
|
||||
placeholder="Enter text here"
|
||||
class="flex-elem s-input-inline t-notebook-entry-input s-notebook-entry-text"
|
||||
ng-bind="entry.text">
|
||||
</div>
|
||||
<!-- embeds -->
|
||||
<div class="flex-elem entry-embeds l-flex-row">
|
||||
<div class="l-flex-row l-entry-embed {{embed.cssClass}}"
|
||||
ng-repeat="embed in entry.embeds track by $index"
|
||||
ng-class="{ 'has-snapshot' : embed.snapshot }"
|
||||
id="{{embed.id}}">
|
||||
<div class="snap-thumb"
|
||||
ng-if="embed.snapshot"
|
||||
ng-click="viewSnapshot($event,embed.snapshot.src,embed.id,entry.createdOn,this,embed)">
|
||||
<img ng-src="{{embed.snapshot.src}}" src="//:0" alt="{{embed.id}}">
|
||||
</div>
|
||||
<div class="embed-info l-flex-col">
|
||||
<div class="embed-title object-header">
|
||||
<a ng-click='navigate($event,embed.type)'>{{embed.name}}</a>
|
||||
<a class='context-available' ng-click='openMenu($event,embed.type)'></a>
|
||||
</div>
|
||||
<div class="hide-menu" ng-show="false">
|
||||
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
||||
<div class="menu context-menu">
|
||||
<ul>
|
||||
<li ng-repeat="menu in menuEmbed"
|
||||
ng-click="menu.perform($event,embed.snapshot.src,embed.id,entry.createdOn,this,embed)"
|
||||
title="{{menu.getMetadata().description}}"
|
||||
class="{{menu.getMetadata().cssClass}}"
|
||||
ng-if="embed.snapshot">
|
||||
{{menu.getMetadata().name}}
|
||||
</li>
|
||||
<li ng-repeat="menu in menuEmbedNoSnap"
|
||||
ng-click="menu.perform($event,embed.snapshot.src,embed.id,entry.createdOn,this)"
|
||||
title="{{menu.getMetadata().description}}"
|
||||
class="{{menu.getMetadata().cssClass}}"
|
||||
ng-if="!embed.snapshot">
|
||||
{{menu.getMetadata().name}}
|
||||
</li>
|
||||
<li ng-repeat="menu in embedActions"
|
||||
ng-click="menu.perform()"
|
||||
title="{{menu.name}}"
|
||||
class="{{menu.cssClass}}">
|
||||
{{menu.name}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="embed-date"
|
||||
ng-if="embed.snapshot">{{embed.id| date:'yyyy-MM-dd HH:mm:ss'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- delete entry -->
|
||||
<div class="holder flex-elem local-control local-controls-hidden notebook-entry-delete">
|
||||
<a class="s-icon-button icon-trash" id={{entry.id}} title="Delete Entry" ng-click="deleteEntry($event)"></a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
@ -1,8 +0,0 @@
|
||||
<span class="status block">
|
||||
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
|
||||
<span class="status-indicator icon-bell"></span>
|
||||
<span class="label">
|
||||
Notifications
|
||||
</span>
|
||||
<span class="count"></span>
|
||||
</span>
|
@ -1,34 +0,0 @@
|
||||
<div class="t-snapshot abs l-view-header">
|
||||
<div class="abs object-browse-bar l-flex-row">
|
||||
<div class="left flex-elem l-flex-row grows">
|
||||
<div class="object-header flex-elem l-flex-row grows">
|
||||
<div class="type-icon flex-elem embed-icon holder" ng-class="cssClass"></div>
|
||||
<div class="title-label flex-elem holder flex-can-shrink">{{entryName}}</div>
|
||||
<a class="context-available flex-elem holder" ng-click="openMenu($event,embedType)"></a>
|
||||
<div class="hide-menu" ng-show="false">
|
||||
<div class="menu-element menu-view context-menu-wrapper mobile-disable-select">
|
||||
<div class="menu context-menu">
|
||||
<ul>
|
||||
<li ng-repeat="menu in embedActions"
|
||||
ng-click="menuPerform(menu)"
|
||||
title="{{menu.name}}"
|
||||
class="{{menu.cssClass}}">
|
||||
{{menu.name}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
|
||||
SNAPSHOT {{snapDate | date:'yyyy-MM-dd HH:mm:ss'}}
|
||||
</div>
|
||||
<a class="s-button icon-pencil" title="Annotate">
|
||||
<span class="title-label">Annotate</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,72 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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 () {
|
||||
|
||||
function RemoveEmbed(dialogService,context) {
|
||||
context = context || {};
|
||||
|
||||
this.domainObject = context.selectedObject || context.domainObject;
|
||||
this.dialogService = dialogService;
|
||||
}
|
||||
|
||||
|
||||
RemoveEmbed.prototype.perform = function ($event,snapshot,embedId,entryId) {
|
||||
var domainObject = this.domainObject;
|
||||
var errorDialog = this.dialogService.showBlockingMessage({
|
||||
severity: "error",
|
||||
title: "This action will permanently delete this Embed. Do you want to continue?",
|
||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
||||
options: [{
|
||||
label: "OK",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
remove();
|
||||
}
|
||||
},{
|
||||
label: "Cancel",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
function remove() {
|
||||
domainObject.useCapability('mutation', function (model) {
|
||||
var elementPos = model.entries.map(function (x) {
|
||||
return x.createdOn;
|
||||
}).indexOf(entryId);
|
||||
var entryEmbeds = model.entries[elementPos].embeds;
|
||||
var embedPos = entryEmbeds.map(function (x) {
|
||||
return x.id;
|
||||
}).indexOf(embedId);
|
||||
model.entries[elementPos].embeds.splice(embedPos, 1);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return RemoveEmbed;
|
||||
}
|
||||
);
|
@ -1,74 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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 () {
|
||||
|
||||
function RemoveSnapshot(dialogService, context) {
|
||||
context = context || {};
|
||||
|
||||
this.domainObject = context.selectedObject || context.domainObject;
|
||||
this.dialogService = dialogService;
|
||||
}
|
||||
|
||||
|
||||
|
||||
RemoveSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId) {
|
||||
|
||||
var domainObject = this.domainObject;
|
||||
var errorDialog = this.dialogService.showBlockingMessage({
|
||||
severity: "error",
|
||||
title: "This action will permanently delete this Snapshot. Do you want to continue?",
|
||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
||||
options: [{
|
||||
label: "OK",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
remove();
|
||||
}
|
||||
},{
|
||||
label: "Cancel",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
function remove() {
|
||||
domainObject.useCapability('mutation', function (model) {
|
||||
var elementPos = model.entries.map(function (x) {
|
||||
return x.createdOn;
|
||||
}).indexOf(entryId);
|
||||
var entryEmbeds = model.entries[elementPos].embeds;
|
||||
var embedPos = entryEmbeds.map(function (x) {
|
||||
return x.id;
|
||||
}).indexOf(embedId);
|
||||
model.entries[elementPos].embeds[embedPos].snapshot = "";
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return RemoveSnapshot;
|
||||
}
|
||||
);
|
@ -1,132 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining ViewSnapshot
|
||||
*/
|
||||
|
||||
var OVERLAY_TEMPLATE = '' +
|
||||
' <div class="abs blocker"></div>' +
|
||||
' <div class="abs outer-holder">' +
|
||||
' <a class="close icon-x-in-circle"></a>' +
|
||||
' <div class="abs inner-holder l-flex-col">' +
|
||||
' <div class="t-contents flex-elem holder grows"></div>' +
|
||||
' <div class="bottom-bar flex-elem holder">' +
|
||||
' <a class="t-done s-button major">Done</a>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' </div>';
|
||||
|
||||
define([
|
||||
'zepto',
|
||||
"../../res/templates/snapshotHeader.html"
|
||||
],
|
||||
function ($, headerTemplate) {
|
||||
|
||||
var toggleOverlay,
|
||||
overlay,
|
||||
closeButton,
|
||||
doneButton,
|
||||
blocker,
|
||||
overlayContainer,
|
||||
img,
|
||||
annotateButton,
|
||||
annotateImg;
|
||||
|
||||
function ViewSnapshot($compile) {
|
||||
this.$compile = $compile;
|
||||
}
|
||||
|
||||
function openOverlay(url, header) {
|
||||
overlay = document.createElement('div');
|
||||
$(overlay).addClass('abs overlay l-large-view');
|
||||
overlay.innerHTML = OVERLAY_TEMPLATE;
|
||||
overlayContainer = overlay.querySelector('.t-contents');
|
||||
closeButton = overlay.querySelector('a.close');
|
||||
closeButton.addEventListener('click', toggleOverlay);
|
||||
doneButton = overlay.querySelector('a.t-done');
|
||||
doneButton.addEventListener('click', toggleOverlay);
|
||||
blocker = overlay.querySelector('.abs.blocker');
|
||||
blocker.addEventListener('click', toggleOverlay);
|
||||
annotateButton = header.querySelector('a.icon-pencil');
|
||||
annotateButton.addEventListener('click', annotateImg);
|
||||
document.body.appendChild(overlay);
|
||||
img = document.createElement('div');
|
||||
$(img).addClass('abs object-holder t-image-holder s-image-holder');
|
||||
img.innerHTML = '<div class="image-main s-image-main" style="background-image: url(' + url + ');"></div>';
|
||||
overlayContainer.appendChild(header);
|
||||
overlayContainer.appendChild(img);
|
||||
}
|
||||
|
||||
function closeOverlay() {
|
||||
overlayContainer.removeChild(img);
|
||||
document.body.removeChild(overlay);
|
||||
closeButton.removeEventListener('click', toggleOverlay);
|
||||
closeButton = undefined;
|
||||
doneButton.removeEventListener('click', toggleOverlay);
|
||||
doneButton = undefined;
|
||||
blocker.removeEventListener('click', toggleOverlay);
|
||||
blocker = undefined;
|
||||
overlayContainer = undefined;
|
||||
overlay = undefined;
|
||||
img = undefined;
|
||||
}
|
||||
|
||||
ViewSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId, $scope, embed) {
|
||||
var isOpen = false;
|
||||
|
||||
// onclick for menu items in overlay header context menu
|
||||
$scope.menuPerform = function (menu) {
|
||||
menu.perform();
|
||||
closeOverlay();
|
||||
};
|
||||
|
||||
// Create the overlay element and add it to the document's body
|
||||
$scope.cssClass = embed.cssClass;
|
||||
$scope.embedType = embed.type;
|
||||
$scope.entryName = embed.name;
|
||||
$scope.snapDate = +embedId;
|
||||
var element = this.$compile(headerTemplate)($scope);
|
||||
|
||||
var annotateAction = $scope.action.getActions({category: 'embed'})[1];
|
||||
|
||||
toggleOverlay = function () {
|
||||
if (!isOpen) {
|
||||
openOverlay(snapshot, element[0]);
|
||||
isOpen = true;
|
||||
} else {
|
||||
closeOverlay();
|
||||
isOpen = false;
|
||||
}
|
||||
};
|
||||
|
||||
annotateImg = function () {
|
||||
closeOverlay();
|
||||
annotateAction.perform($event, snapshot, embedId, entryId, $scope);
|
||||
};
|
||||
|
||||
toggleOverlay();
|
||||
};
|
||||
|
||||
return ViewSnapshot;
|
||||
}
|
||||
);
|
@ -1,367 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/*-- main controller file, here is the core functionality of the notebook plugin --*/
|
||||
|
||||
define(
|
||||
['zepto'],
|
||||
function ($) {
|
||||
|
||||
|
||||
function NotebookController(
|
||||
$scope,
|
||||
dialogService,
|
||||
popupService,
|
||||
agentService,
|
||||
objectService,
|
||||
navigationService,
|
||||
now,
|
||||
actionService,
|
||||
$timeout,
|
||||
$element,
|
||||
$sce
|
||||
) {
|
||||
|
||||
$scope.entriesEl = $(document.body).find('.t-entries-list');
|
||||
$scope.sortEntries = $scope.domainObject.getModel().defaultSort;
|
||||
$scope.showTime = "0";
|
||||
$scope.editEntry = false;
|
||||
$scope.entrySearch = '';
|
||||
$scope.entryTypes = [];
|
||||
$scope.embedActions = [];
|
||||
$scope.currentEntryValue = '';
|
||||
|
||||
var SECONDS_IN_AN_HOUR = 60 * 60 * 1000;
|
||||
|
||||
this.scope = $scope;
|
||||
|
||||
$scope.hoursFilter = function (hours,entryTime) {
|
||||
if (+hours) {
|
||||
return entryTime > (Date.now() - SECONDS_IN_AN_HOUR * (+hours));
|
||||
}else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.scrollToTop = function () {
|
||||
var entriesContainer = $scope.entriesEl.parent();
|
||||
entriesContainer[0].scrollTop = 0;
|
||||
};
|
||||
|
||||
$scope.findEntryEl = function (entryId) {
|
||||
var element = $($scope.entriesEl).find('#entry_' + entryId);
|
||||
|
||||
if (element[0]) {
|
||||
return element.find("[contenteditable='true']");
|
||||
} else {
|
||||
var entries = $scope.entriesEl.children().children(),
|
||||
lastOrFirst = $scope.sortEntries === "-createdOn" ? 0 : (entries.length - 1);
|
||||
|
||||
return $(entries[lastOrFirst]).find("[contenteditable='true']");
|
||||
}
|
||||
};
|
||||
|
||||
$scope.findEntryPositionById = function (id) {
|
||||
var foundId = -1;
|
||||
|
||||
$scope.domainObject.model.entries.forEach(function (element, index) {
|
||||
if (element.id === id) {
|
||||
foundId = index;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return foundId;
|
||||
};
|
||||
|
||||
$scope.newEntry = function ($event) {
|
||||
$scope.scrollToTop();
|
||||
|
||||
var entries = $scope.domainObject.model.entries,
|
||||
lastEntry = entries[entries.length - 1],
|
||||
id = Date.now();
|
||||
|
||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
|
||||
var createdEntry = {'id': id, 'createdOn': id};
|
||||
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries.push(createdEntry);
|
||||
});
|
||||
} else {
|
||||
$scope.findEntryEl(lastEntry.id).focus();
|
||||
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[entries.length - 1].createdOn = id;
|
||||
});
|
||||
}
|
||||
$scope.entrySearch = '';
|
||||
};
|
||||
|
||||
|
||||
$scope.deleteEntry = function ($event) {
|
||||
var delId = +$event.currentTarget.id;
|
||||
var errorDialog = dialogService.showBlockingMessage({
|
||||
severity: "error",
|
||||
title: "This action will permanently delete this Notebook entry. Do you want to continue?",
|
||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
||||
options: [{
|
||||
label: "OK",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
var elementPos = $scope.findEntryPositionById(delId);
|
||||
|
||||
if (elementPos !== -1) {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries.splice(elementPos, 1);
|
||||
});
|
||||
} else {
|
||||
window.console.log('delete error');
|
||||
}
|
||||
|
||||
}
|
||||
},{
|
||||
label: "Cancel",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
}
|
||||
}]
|
||||
});
|
||||
};
|
||||
|
||||
$scope.textFocus = function ($event, entryId) {
|
||||
if ($event.srcElement) {
|
||||
$scope.currentEntryValue = $event.srcElement.innerText;
|
||||
} else {
|
||||
$event.target.innerText = '';
|
||||
}
|
||||
};
|
||||
|
||||
//On text blur(when focus is removed)
|
||||
$scope.textBlur = function ($event, entryId) {
|
||||
// entryId is the unique numeric based on the original createdOn
|
||||
if ($event.target) {
|
||||
var elementPos = $scope.findEntryPositionById(+entryId);
|
||||
|
||||
// If the text of an entry has been changed, then update the text and the modifiedOn numeric
|
||||
// Otherwise, don't do anything
|
||||
if ($scope.currentEntryValue !== $event.target.innerText) {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[elementPos].text = $event.target.innerText;
|
||||
model.entries[elementPos].modified = Date.now();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.finished = function (model) {
|
||||
var lastEntry = model[model.length - 1];
|
||||
|
||||
if (!lastEntry.text) {
|
||||
$scope.findEntryEl(lastEntry.id).focus();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.handleActive = function () {
|
||||
var newEntry = $scope.entriesEl.find('.active');
|
||||
if (newEntry) {
|
||||
newEntry.removeClass('active');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$scope.clearSearch = function () {
|
||||
$scope.entrySearch = '';
|
||||
};
|
||||
|
||||
$scope.viewSnapshot = function ($event,snapshot,embedId,entryId,$innerScope,domainObject) {
|
||||
var viewAction = $scope.action.getActions({category: 'embed'})[0];
|
||||
viewAction.perform($event, snapshot, embedId, entryId, $innerScope, domainObject);
|
||||
};
|
||||
|
||||
$scope.renderImage = function (img) {
|
||||
return URL.createObjectURL(img);
|
||||
};
|
||||
|
||||
$scope.getDomainObj = function (id) {
|
||||
return objectService.getObjects([id]);
|
||||
};
|
||||
|
||||
function refreshComp(change) {
|
||||
if (change && change.length) {
|
||||
change[0].getCapability('action').getActions({key: 'remove'})[0].perform();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.actionToMenuOption = function (action) {
|
||||
return {
|
||||
key: action.getMetadata().key,
|
||||
name: action.getMetadata().name,
|
||||
cssClass: action.getMetadata().cssClass,
|
||||
perform: action.perform
|
||||
};
|
||||
};
|
||||
|
||||
// Maintain all "conclude-editing" and "save" actions in the
|
||||
// present context.
|
||||
function updateActions() {
|
||||
$scope.menuEmbed = $scope.action ?
|
||||
$scope.action.getActions({category: 'embed'}) :
|
||||
[];
|
||||
|
||||
$scope.menuEmbedNoSnap = $scope.action ?
|
||||
$scope.action.getActions({category: 'embed-no-snap'}) :
|
||||
[];
|
||||
|
||||
$scope.menuActions = $scope.action ?
|
||||
$scope.action.getActions({key: 'window'}) :
|
||||
[];
|
||||
}
|
||||
|
||||
// Update set of actions whenever the action capability
|
||||
// changes or becomes available.
|
||||
$scope.$watch("action", updateActions);
|
||||
|
||||
$scope.navigate = function ($event,embedType) {
|
||||
if ($event) {
|
||||
$event.preventDefault();
|
||||
}
|
||||
$scope.getDomainObj(embedType).then(function (resp) {
|
||||
navigationService.setNavigation(resp[embedType]);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.saveSnap = function (url,embedPos,entryPos) {
|
||||
var snapshot = false;
|
||||
if (url) {
|
||||
if (embedPos !== -1 && entryPos !== -1) {
|
||||
var reader = new window.FileReader();
|
||||
reader.readAsDataURL(url);
|
||||
reader.onloadend = function () {
|
||||
snapshot = reader.result;
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
if (model.entries[entryPos]) {
|
||||
model.entries[entryPos].embeds[embedPos].snapshot = {
|
||||
'src': snapshot,
|
||||
'type': url.type,
|
||||
'size': url.size,
|
||||
'modified': Date.now()
|
||||
};
|
||||
model.entries[entryPos].embeds[embedPos].id = Date.now();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}else {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[entryPos].embeds[embedPos].snapshot = snapshot;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*---popups menu embeds----*/
|
||||
|
||||
function getEmbedActions(embedType) {
|
||||
if (!$scope.embedActions.length) {
|
||||
$scope.getDomainObj(embedType).then(function (resp) {
|
||||
$scope.embedActions = [];
|
||||
$scope.embedActions.push($scope.actionToMenuOption(
|
||||
$scope.action.getActions({key: 'mct-preview-action', selectedObject: resp[embedType]})[0]
|
||||
));
|
||||
$scope.embedActions.push($scope.actionToMenuOption(
|
||||
$scope.action.getActions({key: 'window', selectedObject: resp[embedType]})[0]
|
||||
));
|
||||
$scope.embedActions.push({
|
||||
key: 'navigate',
|
||||
name: 'Go to Original',
|
||||
cssClass: '',
|
||||
perform: function () {
|
||||
$scope.navigate('', embedType);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.openMenu = function ($event,embedType) {
|
||||
$event.preventDefault();
|
||||
|
||||
getEmbedActions(embedType);
|
||||
|
||||
var body = $(document).find('body'),
|
||||
initiatingEvent = agentService.isMobile() ?
|
||||
'touchstart' : 'mousedown',
|
||||
dismissExistingMenu,
|
||||
menu;
|
||||
|
||||
var container = $($event.currentTarget).parent().parent();
|
||||
|
||||
menu = container.find('.menu-element');
|
||||
|
||||
// Remove the context menu
|
||||
function dismiss() {
|
||||
container.find('.hide-menu').append(menu);
|
||||
body.off("mousedown", dismiss);
|
||||
dismissExistingMenu = undefined;
|
||||
$scope.embedActions = [];
|
||||
}
|
||||
|
||||
// Dismiss any menu which was already showing
|
||||
if (dismissExistingMenu) {
|
||||
dismissExistingMenu();
|
||||
}
|
||||
|
||||
// ...and record the presence of this menu.
|
||||
dismissExistingMenu = dismiss;
|
||||
|
||||
popupService.display(menu, [$event.pageX,$event.pageY], {
|
||||
marginX: 0,
|
||||
marginY: -50
|
||||
});
|
||||
|
||||
// Stop propagation so that clicks or touches on the menu do not close the menu
|
||||
menu.on(initiatingEvent, function (event) {
|
||||
event.stopPropagation();
|
||||
$timeout(dismiss, 300);
|
||||
});
|
||||
|
||||
// Dismiss the menu when body is clicked/touched elsewhere
|
||||
// ('mousedown' because 'click' breaks left-click context menus)
|
||||
// ('touchstart' because 'touch' breaks context menus up)
|
||||
body.on(initiatingEvent, dismiss);
|
||||
|
||||
};
|
||||
|
||||
|
||||
$scope.$watchCollection("composition", refreshComp);
|
||||
|
||||
$scope.$watch('domainObject.getModel().defaultSort', function (newDefaultSort, oldDefaultSort) {
|
||||
if (newDefaultSort !== oldDefaultSort) {
|
||||
$scope.sortEntries = newDefaultSort;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {});
|
||||
|
||||
}
|
||||
|
||||
return NotebookController;
|
||||
});
|
@ -1,128 +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/MCTTable",
|
||||
"./src/controllers/TelemetryTableController",
|
||||
"./src/controllers/TableOptionsController",
|
||||
'../../commonUI/regions/src/Region',
|
||||
'../../commonUI/browse/src/InspectorRegion',
|
||||
"./res/templates/table-options-edit.html",
|
||||
"./res/templates/telemetry-table.html",
|
||||
"legacyRegistry"
|
||||
], function (
|
||||
MCTTable,
|
||||
TelemetryTableController,
|
||||
TableOptionsController,
|
||||
Region,
|
||||
InspectorRegion,
|
||||
tableOptionsEditTemplate,
|
||||
telemetryTableTemplate,
|
||||
legacyRegistry
|
||||
) {
|
||||
/**
|
||||
* Two region parts are defined here. One that appears only in browse
|
||||
* mode, and one that appears only in edit mode. For not they both point
|
||||
* to the same representation, but a different key could be used here to
|
||||
* include a customized representation for edit mode.
|
||||
*/
|
||||
var tableInspector = new InspectorRegion(),
|
||||
tableOptionsEditRegion = new Region({
|
||||
name: "table-options",
|
||||
title: "Table Options",
|
||||
modes: ['edit'],
|
||||
content: {
|
||||
key: "table-options-edit"
|
||||
}
|
||||
});
|
||||
tableInspector.addRegion(tableOptionsEditRegion);
|
||||
|
||||
legacyRegistry.register("platform/features/table", {
|
||||
"extensions": {
|
||||
"types": [
|
||||
{
|
||||
"key": "table",
|
||||
"name": "Telemetry Table",
|
||||
"cssClass": "icon-tabular-realtime",
|
||||
"description": "A table of values over a given time period. The table will be automatically updated with new values as they become available",
|
||||
"priority": 861,
|
||||
"features": "creation",
|
||||
"delegates": [
|
||||
"telemetry"
|
||||
],
|
||||
"inspector": "table-options-edit",
|
||||
"contains": [
|
||||
{
|
||||
"has": "telemetry"
|
||||
}
|
||||
],
|
||||
"model": {
|
||||
"composition": []
|
||||
},
|
||||
"views": [
|
||||
"table"
|
||||
]
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "TelemetryTableController",
|
||||
"implementation": TelemetryTableController,
|
||||
"depends": ["$scope", "$timeout", "openmct"]
|
||||
},
|
||||
{
|
||||
"key": "TableOptionsController",
|
||||
"implementation": TableOptionsController,
|
||||
"depends": ["$scope"]
|
||||
}
|
||||
|
||||
],
|
||||
"views": [
|
||||
{
|
||||
"name": "Telemetry Table",
|
||||
"key": "table",
|
||||
"cssClass": "icon-tabular-realtime",
|
||||
"template": telemetryTableTemplate,
|
||||
"needs": [
|
||||
"telemetry"
|
||||
],
|
||||
"delegation": true,
|
||||
"editable": false
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctTable",
|
||||
"implementation": MCTTable,
|
||||
"depends": ["$timeout"]
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
{
|
||||
"key": "table-options-edit",
|
||||
"template": tableOptionsEditTemplate
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
});
|
@ -1,95 +0,0 @@
|
||||
<div class="l-control-bar">
|
||||
<a class="s-button t-export icon-download labeled"
|
||||
ng-click="exportAsCSV()"
|
||||
title="Export This View's Data">
|
||||
Export
|
||||
</a>
|
||||
</div>
|
||||
<div class="mct-table-headers-w" mct-scroll-x="scroll.x">
|
||||
<table class="mct-table l-tabular-headers filterable"
|
||||
ng-style="{
|
||||
'max-width': totalWidth
|
||||
}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-repeat="header in displayHeaders"
|
||||
ng-style="{
|
||||
width: columnWidths[$index] + 'px',
|
||||
'max-width': columnWidths[$index] + 'px',
|
||||
}"
|
||||
ng-class="[
|
||||
enableSort ? 'sortable' : '',
|
||||
sortColumn === header ? 'sort' : '',
|
||||
sortDirection || ''
|
||||
].join(' ')"
|
||||
ng-click="toggleSort(header)">
|
||||
{{ header }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr ng-if="enableFilter" class="s-filters">
|
||||
<th ng-repeat="header in displayHeaders"
|
||||
ng-style="{
|
||||
width: columnWidths[$index] + 'px',
|
||||
'max-width': columnWidths[$index] + 'px',
|
||||
}">
|
||||
<div class="holder l-filter flex-elem grows"
|
||||
ng-class="{active: filters[header]}">
|
||||
<input type="text"
|
||||
ng-model="filters[header]"/>
|
||||
<a class="clear-icon clear-input icon-x-in-circle"
|
||||
ng-class="{show: filters[header]}"
|
||||
ng-click="filters[header] = undefined"></a>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<table class="mct-sizing-table t-sizing-table"
|
||||
ng-style="{
|
||||
width: calcTableWidthPx
|
||||
}">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td ng-repeat="header in displayHeaders">{{header}}</td>
|
||||
</tr>
|
||||
<tr><td ng-repeat="header in displayHeaders" >
|
||||
{{sizingRow[header].text}}
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="l-tabular-body t-scrolling vscroll--persist" mct-resize="resize()" mct-scroll-x="scroll.x">
|
||||
<div class="mct-table-scroll-forcer"
|
||||
ng-style="{
|
||||
width: totalWidth
|
||||
}"></div>
|
||||
<table class="mct-table"
|
||||
ng-style="{
|
||||
height: totalHeight + 'px',
|
||||
'max-width': totalWidth
|
||||
}">
|
||||
<tbody>
|
||||
<tr ng-repeat-start="visibleRow in visibleRows track by $index"
|
||||
ng-if="visibleRow.rowIndex === toiRowIndex"
|
||||
ng-style="{ top: visibleRow.offsetY + 'px' }"
|
||||
class="l-toi-tablerow">
|
||||
<td colspan="999">
|
||||
<mct-include key="'time-of-interest'"
|
||||
class="l-toi-holder pinned"></mct-include>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-end
|
||||
ng-style="{ top: visibleRow.offsetY + 'px' }"
|
||||
ng-click="table.onRowClick($event, visibleRow.rowIndex)">
|
||||
<td ng-repeat="header in displayHeaders"
|
||||
ng-style="{
|
||||
width: columnWidths[$index] + 'px',
|
||||
'max-width': columnWidths[$index] + 'px',
|
||||
}"
|
||||
class="{{visibleRow.contents[header].cssClass}}">
|
||||
{{ visibleRow.contents[header].text }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
@ -1,15 +0,0 @@
|
||||
<div ng-controller="TelemetryTableController as tableController"
|
||||
ng-class="{'loading': loading}">
|
||||
<mct-table
|
||||
headers="headers"
|
||||
rows="rows"
|
||||
time-columns="[tableController.table.timeSystemColumnTitle]"
|
||||
format-cell="formatCell"
|
||||
enableFilter="true"
|
||||
enableSort="true"
|
||||
auto-scroll="autoScroll"
|
||||
default-sort="defaultSort"
|
||||
export-as="{{ exportAs }}"
|
||||
class="tabular-holder l-sticky-headers has-control-bar">
|
||||
</mct-table>
|
||||
</div>
|
@ -1,67 +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 () {
|
||||
function TableColumn(openmct, telemetryObject, metadatum) {
|
||||
this.openmct = openmct;
|
||||
this.telemetryObject = telemetryObject;
|
||||
this.metadatum = metadatum;
|
||||
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
|
||||
|
||||
this.titleValue = this.metadatum.name;
|
||||
}
|
||||
|
||||
TableColumn.prototype.title = function (title) {
|
||||
if (arguments.length > 0) {
|
||||
this.titleValue = title;
|
||||
}
|
||||
return this.titleValue;
|
||||
};
|
||||
|
||||
TableColumn.prototype.isCurrentTimeSystem = function () {
|
||||
var isCurrentTimeSystem = this.metadatum.hints.hasOwnProperty('domain') &&
|
||||
this.metadatum.key === this.openmct.time.timeSystem().key;
|
||||
|
||||
return isCurrentTimeSystem;
|
||||
};
|
||||
|
||||
TableColumn.prototype.hasValue = function (telemetryObject, telemetryDatum) {
|
||||
var keyStringForDatum = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
var keyStringForColumn = this.openmct.objects.makeKeyString(this.telemetryObject.identifier);
|
||||
return keyStringForDatum === keyStringForColumn && telemetryDatum.hasOwnProperty(this.metadatum.source);
|
||||
};
|
||||
|
||||
TableColumn.prototype.getValue = function (telemetryDatum, limitEvaluator) {
|
||||
var alarm = limitEvaluator &&
|
||||
limitEvaluator.evaluate(telemetryDatum, this.metadatum);
|
||||
var value = {
|
||||
text: this.formatter.format(telemetryDatum),
|
||||
value: this.formatter.parse(telemetryDatum)
|
||||
};
|
||||
|
||||
if (alarm) {
|
||||
value.cssClass = alarm.cssClass;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
return TableColumn;
|
||||
});
|
@ -1,164 +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.
|
||||
*****************************************************************************/
|
||||
/* global Set */
|
||||
define(
|
||||
['./TableColumn'],
|
||||
function (TableColumn) {
|
||||
|
||||
/**
|
||||
* Class that manages table metadata, state, and contents.
|
||||
* @memberof platform/features/table
|
||||
* @param domainObject
|
||||
* @constructor
|
||||
*/
|
||||
function TableConfiguration(domainObject, openmct) {
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
this.timeSystemColumn = undefined;
|
||||
this.columns = [];
|
||||
this.headers = new Set();
|
||||
this.timeSystemColumnTitle = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build column definition based on supplied telemetry metadata
|
||||
* @param telemetryObject the telemetry producing object associated with this column
|
||||
* @param metadata Metadata describing the domains and ranges available
|
||||
* @returns {TableConfiguration} This object
|
||||
*/
|
||||
TableConfiguration.prototype.addColumn = function (telemetryObject, metadatum) {
|
||||
var column = new TableColumn(this.openmct, telemetryObject, metadatum);
|
||||
|
||||
if (column.isCurrentTimeSystem()) {
|
||||
if (!this.timeSystemColumnTitle) {
|
||||
this.timeSystemColumnTitle = column.title();
|
||||
}
|
||||
column.title(this.timeSystemColumnTitle);
|
||||
}
|
||||
|
||||
this.columns.push(column);
|
||||
this.headers.add(column.title());
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve and format values for a given telemetry datum.
|
||||
* @param telemetryObject The object that the telemetry data is
|
||||
* associated with
|
||||
* @param datum The telemetry datum to retrieve values from
|
||||
* @returns {Object} Key value pairs where the key is the column
|
||||
* title, and the value is the formatted value from the provided datum.
|
||||
*/
|
||||
TableConfiguration.prototype.getRowValues = function (telemetryObject, limitEvaluator, datum) {
|
||||
return this.columns.reduce(function (rowObject, column) {
|
||||
var columnTitle = column.title();
|
||||
var columnValue = {
|
||||
text: '',
|
||||
value: undefined
|
||||
};
|
||||
if (rowObject[columnTitle] === undefined) {
|
||||
rowObject[columnTitle] = columnValue;
|
||||
}
|
||||
|
||||
if (column.hasValue(telemetryObject, datum)) {
|
||||
columnValue = column.getValue(datum, limitEvaluator);
|
||||
|
||||
if (columnValue.text === undefined) {
|
||||
columnValue.text = '';
|
||||
}
|
||||
// Don't replace something with nothing.
|
||||
// This occurs when there are multiple columns with the same
|
||||
// column title
|
||||
if (rowObject[columnTitle].text === undefined ||
|
||||
rowObject[columnTitle].text.length === 0) {
|
||||
rowObject[columnTitle] = columnValue;
|
||||
}
|
||||
}
|
||||
|
||||
return rowObject;
|
||||
}, {});
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TableConfiguration.prototype.defaultColumnConfiguration = function () {
|
||||
return ((this.domainObject.getModel().configuration || {}).table || {}).columns || {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the established configuration on the domain object
|
||||
* @private
|
||||
*/
|
||||
TableConfiguration.prototype.saveColumnConfiguration = function (columnConfig) {
|
||||
this.domainObject.useCapability('mutation', function (model) {
|
||||
model.configuration = model.configuration || {};
|
||||
model.configuration.table = model.configuration.table || {};
|
||||
model.configuration.table.columns = columnConfig;
|
||||
});
|
||||
};
|
||||
|
||||
function configChanged(config1, config2) {
|
||||
var config1Keys = Object.keys(config1),
|
||||
config2Keys = Object.keys(config2);
|
||||
|
||||
return (config1Keys.length !== config2Keys.length) ||
|
||||
config1Keys.some(function (key) {
|
||||
return config1[key] !== config2[key];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* As part of the process of building the table definition, extract
|
||||
* configuration from column definitions.
|
||||
* @returns {Object} A configuration object consisting of key-value
|
||||
* pairs where the key is the column title, and the value is a
|
||||
* boolean indicating whether the column should be shown.
|
||||
*/
|
||||
TableConfiguration.prototype.buildColumnConfiguration = function () {
|
||||
var configuration = {},
|
||||
//Use existing persisted config, or default it
|
||||
defaultConfig = this.defaultColumnConfiguration();
|
||||
|
||||
/**
|
||||
* For each column header, define a configuration value
|
||||
* specifying whether the column is visible or not. Default to
|
||||
* existing (persisted) configuration if available
|
||||
*/
|
||||
this.headers.forEach(function (columnTitle) {
|
||||
configuration[columnTitle] =
|
||||
typeof defaultConfig[columnTitle] === 'undefined' ? true :
|
||||
defaultConfig[columnTitle];
|
||||
});
|
||||
|
||||
//Synchronize column configuration with model
|
||||
if (this.domainObject.hasCapability('editor') &&
|
||||
this.domainObject.getCapability('editor').isEditContextRoot() &&
|
||||
configChanged(configuration, defaultConfig)) {
|
||||
this.saveColumnConfiguration(configuration);
|
||||
}
|
||||
|
||||
return configuration;
|
||||
};
|
||||
|
||||
return TableConfiguration;
|
||||
}
|
||||
);
|
@ -1,249 +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(
|
||||
[
|
||||
'lodash',
|
||||
'EventEmitter'
|
||||
],
|
||||
function (_, EventEmitter) {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
function TelemetryCollection() {
|
||||
EventEmitter.call(this, arguments);
|
||||
this.dupeCheck = false;
|
||||
this.telemetry = [];
|
||||
this.highBuffer = [];
|
||||
this.sortField = undefined;
|
||||
this.lastBounds = {};
|
||||
|
||||
_.bindAll(this, [
|
||||
'addOne',
|
||||
'iteratee'
|
||||
]);
|
||||
}
|
||||
|
||||
TelemetryCollection.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
TelemetryCollection.prototype.iteratee = function (item) {
|
||||
return _.get(item, this.sortField);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function is optimized for ticking - it assumes that start and end
|
||||
* bounds will only increase and as such this cannot be used for decreasing
|
||||
* bounds changes.
|
||||
*
|
||||
* An implication of this is that data will not be discarded that exceeds
|
||||
* the given end bounds. For arbitrary bounds changes, it's assumed that
|
||||
* a telemetry requery is performed anyway, and the collection is cleared
|
||||
* and repopulated.
|
||||
*
|
||||
* @fires TelemetryCollection#added
|
||||
* @fires TelemetryCollection#discarded
|
||||
* @param bounds
|
||||
*/
|
||||
TelemetryCollection.prototype.bounds = function (bounds) {
|
||||
var startChanged = this.lastBounds.start !== bounds.start;
|
||||
var endChanged = this.lastBounds.end !== bounds.end;
|
||||
var startIndex = 0;
|
||||
var endIndex = 0;
|
||||
var discarded;
|
||||
var added;
|
||||
var testValue;
|
||||
|
||||
this.lastBounds = bounds;
|
||||
|
||||
// If collection is not sorted by a time field, we cannot respond to
|
||||
// bounds events
|
||||
if (this.sortField === undefined) {
|
||||
this.lastBounds = bounds;
|
||||
return;
|
||||
}
|
||||
|
||||
if (startChanged) {
|
||||
testValue = _.set({}, this.sortField, bounds.start);
|
||||
// Calculate the new index of the first item within the bounds
|
||||
startIndex = _.sortedIndex(this.telemetry, testValue, this.sortField);
|
||||
discarded = this.telemetry.splice(0, startIndex);
|
||||
}
|
||||
if (endChanged) {
|
||||
testValue = _.set({}, this.sortField, bounds.end);
|
||||
// Calculate the new index of the last item in bounds
|
||||
endIndex = _.sortedLastIndex(this.highBuffer, testValue, this.sortField);
|
||||
added = this.highBuffer.splice(0, endIndex);
|
||||
added.forEach(function (datum) {
|
||||
this.telemetry.push(datum);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
if (discarded && discarded.length > 0) {
|
||||
/**
|
||||
* A `discarded` event is emitted when telemetry data fall out of
|
||||
* bounds due to a bounds change event
|
||||
* @type {object[]} discarded the telemetry data
|
||||
* discarded as a result of the bounds change
|
||||
*/
|
||||
this.emit('discarded', discarded);
|
||||
}
|
||||
if (added && added.length > 0) {
|
||||
/**
|
||||
* An `added` event is emitted when a bounds change results in
|
||||
* received telemetry falling within the new bounds.
|
||||
* @type {object[]} added the telemetry data that is now within bounds
|
||||
*/
|
||||
this.emit('added', added);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds an individual item to the collection. Used internally only
|
||||
* @private
|
||||
* @param item
|
||||
*/
|
||||
TelemetryCollection.prototype.addOne = function (item) {
|
||||
var isDuplicate = false;
|
||||
var boundsDefined = this.lastBounds &&
|
||||
(this.lastBounds.start !== undefined && this.lastBounds.end !== undefined);
|
||||
var array;
|
||||
var boundsLow;
|
||||
var boundsHigh;
|
||||
|
||||
// If collection is not sorted by a time field, we cannot respond to
|
||||
// bounds events, so no bounds checking necessary
|
||||
if (this.sortField === undefined) {
|
||||
this.telemetry.push(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Insert into either in-bounds array, or the out of bounds high buffer.
|
||||
// Data in the high buffer will be re-evaluated for possible insertion on next tick
|
||||
|
||||
if (boundsDefined) {
|
||||
boundsHigh = _.get(item, this.sortField) > this.lastBounds.end;
|
||||
boundsLow = _.get(item, this.sortField) < this.lastBounds.start;
|
||||
|
||||
if (!boundsHigh && !boundsLow) {
|
||||
array = this.telemetry;
|
||||
} else if (boundsHigh) {
|
||||
array = this.highBuffer;
|
||||
}
|
||||
} else {
|
||||
array = this.telemetry;
|
||||
}
|
||||
|
||||
// If out of bounds low, disregard data
|
||||
if (!boundsLow) {
|
||||
// Going to check for duplicates. Bound the search problem to
|
||||
// items around the given time. Use sortedIndex because it
|
||||
// employs a binary search which is O(log n). Can use binary search
|
||||
// based on time stamp because the array is guaranteed ordered due
|
||||
// to sorted insertion.
|
||||
var startIx = _.sortedIndex(array, item, this.sortField);
|
||||
var endIx;
|
||||
|
||||
if (this.dupeCheck && startIx !== array.length) {
|
||||
endIx = _.sortedLastIndex(array, item, this.sortField);
|
||||
|
||||
// Create an array of potential dupes, based on having the
|
||||
// same time stamp
|
||||
var potentialDupes = array.slice(startIx, endIx + 1);
|
||||
// Search potential dupes for exact dupe
|
||||
isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, item)) > -1;
|
||||
}
|
||||
|
||||
if (!isDuplicate) {
|
||||
array.splice(endIx || startIx, 0, item);
|
||||
|
||||
//Return true if it was added and in bounds
|
||||
return array === this.telemetry;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an array of objects to this telemetry collection
|
||||
* @fires TelemetryCollection#added
|
||||
* @param {object[]} items
|
||||
*/
|
||||
TelemetryCollection.prototype.add = function (items) {
|
||||
var added = items.filter(this.addOne);
|
||||
this.emit('added', added);
|
||||
this.dupeCheck = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the contents of the telemetry collection
|
||||
*/
|
||||
TelemetryCollection.prototype.clear = function () {
|
||||
this.telemetry = [];
|
||||
this.highBuffer = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts the telemetry collection based on the provided sort field
|
||||
* specifier. Subsequent inserts are sorted to maintain specified sport
|
||||
* order.
|
||||
*
|
||||
* @example
|
||||
* // First build some mock telemetry for the purpose of an example
|
||||
* let now = Date.now();
|
||||
* let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) {
|
||||
* return {
|
||||
* // define an object property to demonstrate nested paths
|
||||
* timestamp: {
|
||||
* ms: now - value * 1000,
|
||||
* text:
|
||||
* },
|
||||
* value: value
|
||||
* }
|
||||
* });
|
||||
* let collection = new TelemetryCollection();
|
||||
*
|
||||
* collection.add(telemetry);
|
||||
*
|
||||
* // Sort by telemetry value
|
||||
* collection.sort("value");
|
||||
*
|
||||
* // Sort by ms since epoch
|
||||
* collection.sort("timestamp.ms");
|
||||
*
|
||||
* // Sort by formatted date text
|
||||
* collection.sort("timestamp.text");
|
||||
*
|
||||
*
|
||||
* @param {string} sortField An object property path.
|
||||
*/
|
||||
TelemetryCollection.prototype.sort = function (sortField) {
|
||||
this.sortField = sortField;
|
||||
if (sortField !== undefined) {
|
||||
this.telemetry = _.sortBy(this.telemetry, this.iteratee);
|
||||
}
|
||||
};
|
||||
|
||||
return TelemetryCollection;
|
||||
}
|
||||
);
|
@ -1,828 +0,0 @@
|
||||
|
||||
define(
|
||||
[
|
||||
'zepto',
|
||||
'lodash'
|
||||
],
|
||||
function ($, _) {
|
||||
|
||||
/**
|
||||
* A controller for the MCTTable directive. Populates scope with
|
||||
* data used for populating, sorting, and filtering
|
||||
* tables.
|
||||
* @param $scope
|
||||
* @param $timeout
|
||||
* @param element
|
||||
* @constructor
|
||||
*/
|
||||
function MCTTableController($scope, $window, element, exportService, formatService, openmct) {
|
||||
var self = this;
|
||||
|
||||
this.$scope = $scope;
|
||||
this.element = $(element[0]);
|
||||
this.$window = $window;
|
||||
this.maxDisplayRows = 100;
|
||||
|
||||
this.scrollable = this.element.find('.t-scrolling').first();
|
||||
this.resultsHeader = this.element.find('.mct-table>thead').first();
|
||||
this.sizingTableBody = this.element.find('.t-sizing-table>tbody').first();
|
||||
this.$scope.sizingRow = {};
|
||||
this.$scope.calcTableWidthPx = '100%';
|
||||
this.timeApi = openmct.time;
|
||||
this.toiFormatter = undefined;
|
||||
this.formatService = formatService;
|
||||
this.callbacks = {};
|
||||
|
||||
//Bind all class functions to 'this'
|
||||
_.bindAll(this, [
|
||||
'addRows',
|
||||
'binarySearch',
|
||||
'buildLargestRow',
|
||||
'changeBounds',
|
||||
'changeTimeOfInterest',
|
||||
'changeTimeSystem',
|
||||
'destroyConductorListeners',
|
||||
'digest',
|
||||
'filterAndSort',
|
||||
'filterRows',
|
||||
'firstVisible',
|
||||
'insertSorted',
|
||||
'lastVisible',
|
||||
'onRowClick',
|
||||
'onScroll',
|
||||
'removeRows',
|
||||
'resize',
|
||||
'scrollToBottom',
|
||||
'scrollToRow',
|
||||
'setElementSizes',
|
||||
'setHeaders',
|
||||
'setRows',
|
||||
'setTimeOfInterestRow',
|
||||
'setVisibleRows',
|
||||
'sortComparator',
|
||||
'sortRows'
|
||||
]);
|
||||
|
||||
this.scrollable.on('scroll', this.onScroll);
|
||||
|
||||
$scope.visibleRows = [];
|
||||
$scope.displayRows = [];
|
||||
|
||||
/**
|
||||
* Set default values for optional parameters on a given scope
|
||||
*/
|
||||
function setDefaults(scope) {
|
||||
if (typeof scope.enableFilter === 'undefined') {
|
||||
scope.enableFilter = true;
|
||||
scope.filters = {};
|
||||
}
|
||||
if (typeof scope.enableSort === 'undefined') {
|
||||
scope.enableSort = true;
|
||||
scope.sortColumn = undefined;
|
||||
scope.sortDirection = undefined;
|
||||
}
|
||||
if (scope.sortColumn !== undefined) {
|
||||
scope.sortDirection = "asc";
|
||||
}
|
||||
}
|
||||
|
||||
setDefaults($scope);
|
||||
|
||||
$scope.exportAsCSV = function () {
|
||||
var headers = $scope.displayHeaders,
|
||||
filename = $(element[0]).attr('export-as');
|
||||
|
||||
exportService.exportCSV($scope.displayRows.map(function (row) {
|
||||
return headers.reduce(function (r, header) {
|
||||
r[header] = row[header].text;
|
||||
return r;
|
||||
}, {});
|
||||
}), {
|
||||
headers: headers,
|
||||
filename: filename
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleSort = function (key) {
|
||||
if (!$scope.enableSort) {
|
||||
return;
|
||||
}
|
||||
if ($scope.sortColumn !== key) {
|
||||
$scope.sortColumn = key;
|
||||
$scope.sortDirection = 'asc';
|
||||
} else if ($scope.sortDirection === 'asc') {
|
||||
$scope.sortDirection = 'desc';
|
||||
} else if ($scope.sortDirection === 'desc') {
|
||||
$scope.sortColumn = undefined;
|
||||
$scope.sortDirection = undefined;
|
||||
} else if ($scope.sortColumn !== undefined &&
|
||||
$scope.sortDirection === undefined) {
|
||||
$scope.sortDirection = 'asc';
|
||||
}
|
||||
self.setRows($scope.rows);
|
||||
self.setTimeOfInterestRow(self.timeApi.timeOfInterest());
|
||||
};
|
||||
|
||||
/*
|
||||
* Define watches to listen for changes to headers and rows.
|
||||
*/
|
||||
$scope.$watchCollection('filters', function () {
|
||||
self.setRows($scope.rows);
|
||||
});
|
||||
$scope.$watch('headers', function (newHeaders, oldHeaders) {
|
||||
if (newHeaders !== oldHeaders) {
|
||||
this.setHeaders(newHeaders);
|
||||
}
|
||||
}.bind(this));
|
||||
$scope.$watch('rows', this.setRows);
|
||||
|
||||
/*
|
||||
* Listen for rows added individually (eg. for real-time tables)
|
||||
*/
|
||||
$scope.$on('add:rows', this.addRows);
|
||||
$scope.$on('remove:rows', this.removeRows);
|
||||
|
||||
/**
|
||||
* Populated from the default-sort attribute on MctTable
|
||||
* directive tag.
|
||||
*/
|
||||
$scope.$watch('defaultSort', function (newColumn, oldColumn) {
|
||||
if (newColumn !== oldColumn) {
|
||||
$scope.toggleSort(newColumn);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Listen for resize events to trigger recalculation of table width
|
||||
*/
|
||||
$scope.resize = this.setElementSizes;
|
||||
|
||||
/**
|
||||
* Scope variable that is populated from the 'time-columns'
|
||||
* attribute on the MctTable tag. Indicates which columns, while
|
||||
* sorted, can be used for indicated time of interest.
|
||||
*/
|
||||
$scope.$watch("timeColumns", function (timeColumns) {
|
||||
if (timeColumns) {
|
||||
this.destroyConductorListeners();
|
||||
|
||||
this.timeApi.on('timeSystem', this.changeTimeSystem);
|
||||
this.timeApi.on('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.timeApi.on('bounds', this.changeBounds);
|
||||
|
||||
// If time system defined, set initially
|
||||
if (this.timeApi.timeSystem() !== undefined) {
|
||||
this.changeTimeSystem(this.timeApi.timeSystem());
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
this.scrollable.off('scroll', this.onScroll);
|
||||
this.destroyConductorListeners();
|
||||
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
MCTTableController.prototype.destroyConductorListeners = function () {
|
||||
this.timeApi.off('timeSystem', this.changeTimeSystem);
|
||||
this.timeApi.off('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.timeApi.off('bounds', this.changeBounds);
|
||||
};
|
||||
|
||||
MCTTableController.prototype.changeTimeSystem = function (timeSystem) {
|
||||
var format = timeSystem.timeFormat;
|
||||
this.toiFormatter = this.formatService.getFormat(format);
|
||||
};
|
||||
|
||||
/**
|
||||
* If auto-scroll is enabled, this function will scroll to the
|
||||
* bottom of the page
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.scrollToBottom = function () {
|
||||
this.scrollable[0].scrollTop = this.scrollable[0].scrollHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a row add event. Rows can be added as needed using the
|
||||
* `add:row` broadcast event.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.addRows = function (event, rows) {
|
||||
//Does the row pass the current filter?
|
||||
if (this.filterRows(rows).length > 0) {
|
||||
rows.forEach(this.insertSorted.bind(this, this.$scope.displayRows));
|
||||
|
||||
//Resize the columns , then update the rows visible in the table
|
||||
this.resize([this.$scope.sizingRow].concat(rows))
|
||||
.then(this.setVisibleRows)
|
||||
.then(function () {
|
||||
if (this.$scope.autoScroll) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
var toi = this.timeApi.timeOfInterest();
|
||||
if (toi !== -1) {
|
||||
this.setTimeOfInterestRow(toi);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a row remove event. Rows can be removed as needed using the
|
||||
* `remove:row` broadcast event.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.removeRows = function (event, rows) {
|
||||
var indexInDisplayRows;
|
||||
rows.forEach(function (row) {
|
||||
// Do a sequential search here. Only way of finding row is by
|
||||
// object equality, so array is in effect unsorted.
|
||||
indexInDisplayRows = this.$scope.displayRows.indexOf(row);
|
||||
if (indexInDisplayRows !== -1) {
|
||||
this.$scope.displayRows.splice(indexInDisplayRows, 1);
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.$scope.sizingRow = this.buildLargestRow([this.$scope.sizingRow].concat(rows));
|
||||
|
||||
this.setElementSizes();
|
||||
this.setVisibleRows()
|
||||
.then(function () {
|
||||
if (this.$scope.autoScroll) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.onScroll = function (event) {
|
||||
this.scrollWindow = {
|
||||
top: this.scrollable[0].scrollTop,
|
||||
bottom: this.scrollable[0].scrollTop + this.scrollable[0].offsetHeight,
|
||||
offsetHeight: this.scrollable[0].offsetHeight,
|
||||
height: this.scrollable[0].scrollHeight
|
||||
};
|
||||
this.$window.requestAnimationFrame(function () {
|
||||
this.setVisibleRows();
|
||||
this.digest();
|
||||
|
||||
// If user scrolls away from bottom, disable auto-scroll.
|
||||
// Auto-scroll will be re-enabled if user scrolls to bottom again.
|
||||
if (this.scrollWindow.top <
|
||||
(this.scrollWindow.height - this.scrollWindow.offsetHeight) - 20) {
|
||||
this.$scope.autoScroll = false;
|
||||
} else {
|
||||
this.$scope.autoScroll = true;
|
||||
}
|
||||
this.scrolling = false;
|
||||
delete this.scrollWindow;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Return first visible row, based on current scroll state.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.firstVisible = function () {
|
||||
var topScroll = this.scrollWindow ?
|
||||
this.scrollWindow.top :
|
||||
this.scrollable[0].scrollTop;
|
||||
|
||||
return Math.floor(
|
||||
(topScroll) / this.$scope.rowHeight
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Return last visible row, based on current scroll state.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.lastVisible = function () {
|
||||
var bottomScroll = this.scrollWindow ?
|
||||
this.scrollWindow.bottom :
|
||||
this.scrollable[0].scrollTop + this.scrollable[0].offsetHeight;
|
||||
|
||||
return Math.ceil(
|
||||
(bottomScroll) /
|
||||
this.$scope.rowHeight
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets visible rows based on array
|
||||
* content and current scroll state.
|
||||
*/
|
||||
MCTTableController.prototype.setVisibleRows = function () {
|
||||
var self = this,
|
||||
totalVisible,
|
||||
numberOffscreen,
|
||||
firstVisible,
|
||||
lastVisible,
|
||||
start,
|
||||
end;
|
||||
|
||||
//No need to scroll
|
||||
if (this.$scope.displayRows.length < this.maxDisplayRows) {
|
||||
start = 0;
|
||||
end = this.$scope.displayRows.length;
|
||||
} else {
|
||||
firstVisible = this.firstVisible();
|
||||
lastVisible = this.lastVisible();
|
||||
totalVisible = lastVisible - firstVisible;
|
||||
numberOffscreen = this.maxDisplayRows - totalVisible;
|
||||
start = firstVisible - Math.floor(numberOffscreen / 2);
|
||||
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
||||
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
end = Math.min(this.maxDisplayRows,
|
||||
this.$scope.displayRows.length);
|
||||
} else if (end >= this.$scope.displayRows.length) {
|
||||
end = this.$scope.displayRows.length;
|
||||
start = end - this.maxDisplayRows + 1;
|
||||
}
|
||||
if (this.$scope.visibleRows[0] &&
|
||||
this.$scope.visibleRows[0].rowIndex === start &&
|
||||
this.$scope.visibleRows[this.$scope.visibleRows.length - 1]
|
||||
.rowIndex === end) {
|
||||
return this.digest();
|
||||
}
|
||||
}
|
||||
//Set visible rows from display rows, based on calculated offset.
|
||||
this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
|
||||
.map(function (row, i) {
|
||||
return {
|
||||
rowIndex: start + i,
|
||||
offsetY: ((start + i) * self.$scope.rowHeight),
|
||||
contents: row
|
||||
};
|
||||
});
|
||||
return this.digest();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update table headers with new headers. If filtering is
|
||||
* enabled, reset filters. If sorting is enabled, reset
|
||||
* sorting.
|
||||
*/
|
||||
MCTTableController.prototype.setHeaders = function (newHeaders) {
|
||||
if (!newHeaders) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$scope.displayHeaders = newHeaders;
|
||||
if (this.$scope.enableFilter) {
|
||||
this.$scope.filters = {};
|
||||
}
|
||||
// Reset column sort information unless the new headers
|
||||
// contain the column currently sorted on.
|
||||
if (this.$scope.enableSort &&
|
||||
newHeaders.indexOf(this.$scope.sortColumn) === -1) {
|
||||
this.$scope.sortColumn = undefined;
|
||||
this.$scope.sortDirection = undefined;
|
||||
}
|
||||
this.setRows(this.$scope.rows);
|
||||
};
|
||||
|
||||
/**
|
||||
* Read styles from the DOM and use them to calculate offsets
|
||||
* for individual rows.
|
||||
*/
|
||||
MCTTableController.prototype.setElementSizes = function () {
|
||||
var tbody = this.sizingTableBody,
|
||||
firstRow = tbody.find('tr'),
|
||||
column = firstRow.find('td'),
|
||||
rowHeight = firstRow.prop('offsetHeight'),
|
||||
columnWidth,
|
||||
tableWidth = 0,
|
||||
overallHeight = (rowHeight *
|
||||
(this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
|
||||
|
||||
this.$scope.columnWidths = [];
|
||||
|
||||
while (column.length) {
|
||||
columnWidth = column.prop('offsetWidth');
|
||||
this.$scope.columnWidths.push(column.prop('offsetWidth'));
|
||||
tableWidth += columnWidth;
|
||||
column = column.next();
|
||||
}
|
||||
this.$scope.rowHeight = rowHeight;
|
||||
this.$scope.totalHeight = overallHeight;
|
||||
|
||||
var scrollW = this.scrollable[0].offsetWidth - this.scrollable[0].clientWidth;
|
||||
if (scrollW && scrollW > 0) {
|
||||
this.$scope.calcTableWidthPx = 'calc(100% - ' + scrollW + 'px)';
|
||||
}
|
||||
|
||||
if (tableWidth > 0) {
|
||||
this.$scope.totalWidth = tableWidth + 'px';
|
||||
} else {
|
||||
this.$scope.totalWidth = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the correct insertion point for a new row, which takes into
|
||||
* account duplicates to make sure new rows are inserted in a way that
|
||||
* maintains arrival order.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} searchArray
|
||||
* @param {Object} searchElement Object to find the insertion point for
|
||||
*/
|
||||
MCTTableController.prototype.findInsertionPoint = function (searchArray, searchElement) {
|
||||
var index;
|
||||
var testIndex;
|
||||
var first = searchArray[0];
|
||||
var last = searchArray[searchArray.length - 1];
|
||||
|
||||
if (first) {
|
||||
first = first[this.$scope.sortColumn].text;
|
||||
}
|
||||
if (last) {
|
||||
last = last[this.$scope.sortColumn].text;
|
||||
}
|
||||
// Shortcut check for append/prepend
|
||||
if (first && this.sortComparator(first, searchElement) >= 0) {
|
||||
index = testIndex = 0;
|
||||
} else if (last && this.sortComparator(last, searchElement) <= 0) {
|
||||
index = testIndex = searchArray.length;
|
||||
} else {
|
||||
// use a binary search to find the correct insertion point
|
||||
index = testIndex = this.binarySearch(
|
||||
searchArray,
|
||||
searchElement,
|
||||
0,
|
||||
searchArray.length - 1
|
||||
);
|
||||
}
|
||||
|
||||
//It's possible that the insertion point is a duplicate of the element to be inserted
|
||||
var isDupe = function () {
|
||||
return this.sortComparator(searchElement,
|
||||
searchArray[testIndex][this.$scope.sortColumn].text) === 0;
|
||||
}.bind(this);
|
||||
|
||||
// In the event of a duplicate, scan left or right (depending on
|
||||
// sort order) to find an insertion point that maintains order received
|
||||
while (testIndex >= 0 && testIndex < searchArray.length && isDupe()) {
|
||||
if (this.$scope.sortDirection === 'asc') {
|
||||
index = ++testIndex;
|
||||
} else {
|
||||
index = testIndex--;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.binarySearch = function (searchArray, searchElement, min, max) {
|
||||
var sampleAt = Math.floor((max - min) / 2) + min;
|
||||
|
||||
if (max < min) {
|
||||
return min; // Element is not in array, min gives direction
|
||||
}
|
||||
switch (this.sortComparator(searchElement,
|
||||
searchArray[sampleAt][this.$scope.sortColumn].text)) {
|
||||
case -1:
|
||||
return this.binarySearch(searchArray, searchElement, min,
|
||||
sampleAt - 1);
|
||||
case 0:
|
||||
return sampleAt;
|
||||
case 1:
|
||||
return this.binarySearch(searchArray, searchElement,
|
||||
sampleAt + 1, max);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.insertSorted = function (array, element) {
|
||||
var index = -1;
|
||||
|
||||
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
|
||||
//No sorting applied, push it on the end.
|
||||
index = array.length;
|
||||
} else {
|
||||
//Sort is enabled, perform binary search to find insertion point
|
||||
index = this.findInsertionPoint(array, element[this.$scope.sortColumn].text);
|
||||
}
|
||||
if (index === -1) {
|
||||
array.unshift(element);
|
||||
} else if (index === array.length) {
|
||||
array.push(element);
|
||||
} else {
|
||||
array.splice(index, 0, element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare two variables, returning a number that represents
|
||||
* which is larger. Similar to the default array sort
|
||||
* comparator, but does not coerce all values to string before
|
||||
* conversion. Strings are lowercased before comparison.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.sortComparator = function (a, b) {
|
||||
var result = 0,
|
||||
sortDirectionMultiplier,
|
||||
numberA,
|
||||
numberB;
|
||||
/**
|
||||
* Given a value, if it is a number, or a string representation of a
|
||||
* number, then return a number representation. Otherwise, return
|
||||
* the original value. It's a little more robust than using just
|
||||
* Number() or parseFloat, or isNaN in isolation, all of which are
|
||||
* fairly inconsistent in their results.
|
||||
* @param value The value to return as a number.
|
||||
* @returns {*} The value cast to a Number, or the original value if
|
||||
* a Number representation is not possible.
|
||||
*/
|
||||
function toNumber(value) {
|
||||
var val = !isNaN(Number(value)) && !isNaN(parseFloat(value)) ? Number(value) : value;
|
||||
return val;
|
||||
}
|
||||
|
||||
numberA = toNumber(a);
|
||||
numberB = toNumber(b);
|
||||
|
||||
//If they're both numbers, then compare them as numbers
|
||||
if (typeof numberA === "number" && typeof numberB === "number") {
|
||||
a = numberA;
|
||||
b = numberB;
|
||||
}
|
||||
|
||||
//If they're both strings, then ignore case
|
||||
if (typeof a === "string" && typeof b === "string") {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
}
|
||||
|
||||
if (a < b) {
|
||||
result = -1;
|
||||
}
|
||||
if (a > b) {
|
||||
result = 1;
|
||||
}
|
||||
|
||||
if (this.$scope.sortDirection === 'asc') {
|
||||
sortDirectionMultiplier = 1;
|
||||
} else if (this.$scope.sortDirection === 'desc') {
|
||||
sortDirectionMultiplier = -1;
|
||||
}
|
||||
|
||||
return result * sortDirectionMultiplier;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a new array which is a result of applying the sort
|
||||
* criteria defined in $scope.
|
||||
*
|
||||
* Does not modify the array that was passed in.
|
||||
*/
|
||||
MCTTableController.prototype.sortRows = function (rowsToSort) {
|
||||
var self = this,
|
||||
sortKey = this.$scope.sortColumn;
|
||||
|
||||
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
|
||||
return rowsToSort;
|
||||
}
|
||||
|
||||
return rowsToSort.sort(function (a, b) {
|
||||
return self.sortComparator(a[sortKey].text, b[sortKey].text);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an object which contains the largest values
|
||||
* for each key in the given set of rows. This is used to
|
||||
* pre-calculate optimal column sizes without having to render
|
||||
* every row.
|
||||
*/
|
||||
MCTTableController.prototype.buildLargestRow = function (rows) {
|
||||
var largestRow = rows.reduce(function (prevLargest, row) {
|
||||
Object.keys(row).forEach(function (key) {
|
||||
var currentColumn,
|
||||
currentColumnLength,
|
||||
largestColumn,
|
||||
largestColumnLength;
|
||||
if (row[key]) {
|
||||
currentColumn = (row[key]).text;
|
||||
currentColumnLength =
|
||||
(currentColumn && currentColumn.length) ?
|
||||
currentColumn.length :
|
||||
currentColumn;
|
||||
largestColumn = prevLargest[key] ? prevLargest[key].text : "";
|
||||
largestColumnLength = largestColumn.length;
|
||||
|
||||
if (currentColumnLength > largestColumnLength) {
|
||||
prevLargest[key] = JSON.parse(JSON.stringify(row[key]));
|
||||
}
|
||||
}
|
||||
});
|
||||
return prevLargest;
|
||||
}, JSON.parse(JSON.stringify(rows[0] || {})));
|
||||
return largestRow;
|
||||
};
|
||||
|
||||
// Will effectively cap digests at 60Hz
|
||||
// Also turns digest into a promise allowing code to force digest, then
|
||||
// schedule something to happen afterwards
|
||||
MCTTableController.prototype.digest = function () {
|
||||
var scope = this.$scope;
|
||||
var self = this;
|
||||
var raf = this.$window.requestAnimationFrame;
|
||||
var promise = this.digestPromise;
|
||||
|
||||
if (!promise) {
|
||||
self.digestPromise = promise = new Promise(function (resolve) {
|
||||
raf(function () {
|
||||
scope.$digest();
|
||||
self.digestPromise = undefined;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the widest row in the table, and if necessary, resizes
|
||||
* the table accordingly
|
||||
*
|
||||
* @param rows the rows on which to resize
|
||||
* @returns {Promise} a promise that will resolve when resizing has
|
||||
* occurred.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.resize = function (rows) {
|
||||
this.$scope.sizingRow = this.buildLargestRow(rows);
|
||||
return this.digest().then(this.setElementSizes);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.filterAndSort = function (rows) {
|
||||
var displayRows = rows;
|
||||
if (this.$scope.enableFilter) {
|
||||
displayRows = this.filterRows(displayRows);
|
||||
}
|
||||
|
||||
if (this.$scope.enableSort) {
|
||||
displayRows = this.sortRows(displayRows.slice(0));
|
||||
}
|
||||
return displayRows;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update rows with new data. If filtering is enabled, rows
|
||||
* will be sorted before display.
|
||||
*/
|
||||
MCTTableController.prototype.setRows = function (newRows) {
|
||||
//Nothing to show because no columns visible
|
||||
if (!this.$scope.displayHeaders || !newRows) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$scope.displayRows = this.filterAndSort(newRows || []);
|
||||
return this.resize(newRows)
|
||||
.then(function (rows) {
|
||||
return this.setVisibleRows(rows);
|
||||
}.bind(this))
|
||||
//Timeout following setVisibleRows to allow digest to
|
||||
// perform DOM changes, otherwise scrollTo won't work.
|
||||
.then(function () {
|
||||
//If TOI specified, scroll to it
|
||||
var timeOfInterest = this.timeApi.timeOfInterest();
|
||||
if (timeOfInterest) {
|
||||
this.setTimeOfInterestRow(timeOfInterest);
|
||||
this.scrollToRow(this.$scope.toiRowIndex);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies user defined filters to rows. These filters are based on
|
||||
* the text entered in the search areas in each column.
|
||||
* @param rowsToFilter {Object[]} The rows to apply filters to
|
||||
* @returns {Object[]} A filtered copy of the supplied rows
|
||||
*/
|
||||
MCTTableController.prototype.filterRows = function (rowsToFilter) {
|
||||
var filters = {},
|
||||
self = this;
|
||||
|
||||
/**
|
||||
* Returns true if row matches all filters.
|
||||
*/
|
||||
function matchRow(filterMap, row) {
|
||||
return Object.keys(filterMap).every(function (key) {
|
||||
if (!row[key]) {
|
||||
return false;
|
||||
}
|
||||
var testVal = String(row[key].text).toLowerCase();
|
||||
return testVal.indexOf(filterMap[key]) !== -1;
|
||||
});
|
||||
}
|
||||
|
||||
if (!Object.keys(this.$scope.filters).length) {
|
||||
return rowsToFilter;
|
||||
}
|
||||
|
||||
Object.keys(this.$scope.filters).forEach(function (key) {
|
||||
if (!self.$scope.filters[key]) {
|
||||
return;
|
||||
}
|
||||
filters[key] = self.$scope.filters[key].toLowerCase();
|
||||
});
|
||||
|
||||
return rowsToFilter.filter(matchRow.bind(null, filters));
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll the view to a given row index
|
||||
* @param displayRowIndex {number} The index in the displayed rows
|
||||
* to scroll to.
|
||||
*/
|
||||
MCTTableController.prototype.scrollToRow = function (displayRowIndex) {
|
||||
|
||||
var visible = displayRowIndex > this.firstVisible() && displayRowIndex < this.lastVisible();
|
||||
|
||||
if (!visible) {
|
||||
var scrollTop = displayRowIndex * this.$scope.rowHeight +
|
||||
(this.scrollable[0].offsetHeight / 2);
|
||||
this.scrollable[0].scrollTop = scrollTop;
|
||||
this.setVisibleRows();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update rows with new data. If filtering is enabled, rows
|
||||
* will be sorted before display.
|
||||
*/
|
||||
MCTTableController.prototype.setTimeOfInterestRow = function (newTOI) {
|
||||
var isSortedByTime =
|
||||
this.$scope.timeColumns &&
|
||||
this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1;
|
||||
|
||||
this.$scope.toiRowIndex = -1;
|
||||
|
||||
if (newTOI && isSortedByTime) {
|
||||
var formattedTOI = this.toiFormatter.format(newTOI);
|
||||
var rowIndex = this.binarySearch(
|
||||
this.$scope.displayRows,
|
||||
formattedTOI,
|
||||
0,
|
||||
this.$scope.displayRows.length - 1);
|
||||
|
||||
if (rowIndex > 0 && rowIndex < this.$scope.displayRows.length) {
|
||||
this.$scope.toiRowIndex = rowIndex;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MCTTableController.prototype.changeTimeOfInterest = function (newTOI) {
|
||||
this.setTimeOfInterestRow(newTOI);
|
||||
this.scrollToRow(this.$scope.toiRowIndex);
|
||||
};
|
||||
|
||||
/**
|
||||
* On zoom, pan, etc. reset TOI
|
||||
* @param bounds
|
||||
*/
|
||||
MCTTableController.prototype.changeBounds = function (bounds) {
|
||||
this.setTimeOfInterestRow(this.timeApi.timeOfInterest());
|
||||
if (this.$scope.toiRowIndex !== -1) {
|
||||
this.scrollToRow(this.$scope.toiRowIndex);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.onRowClick = function (event, rowIndex) {
|
||||
if (this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1) {
|
||||
var selectedTime = this.$scope.displayRows[rowIndex][this.$scope.sortColumn].text;
|
||||
if (selectedTime &&
|
||||
this.toiFormatter.validate(selectedTime) &&
|
||||
event.altKey) {
|
||||
this.timeApi.timeOfInterest(this.toiFormatter.parse(selectedTime));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return MCTTableController;
|
||||
}
|
||||
);
|
@ -1,113 +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 () {
|
||||
|
||||
/**
|
||||
* Notes on implementation of plot options
|
||||
*
|
||||
* Multiple y-axes will have to be handled with multiple forms as
|
||||
* they will need to be stored on distinct model object
|
||||
*
|
||||
* Likewise plot series options per-child will need to be separate
|
||||
* forms.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The LayoutController is responsible for supporting the
|
||||
* Layout view. It arranges frames according to saved configuration
|
||||
* and provides methods for updating these based on mouse
|
||||
* movement.
|
||||
* @memberof platform/features/plot
|
||||
* @constructor
|
||||
* @param {Scope} $scope the controller's Angular scope
|
||||
*/
|
||||
function TableOptionsController($scope) {
|
||||
|
||||
var self = this;
|
||||
|
||||
this.$scope = $scope;
|
||||
this.domainObject = $scope.domainObject;
|
||||
this.listeners = [];
|
||||
|
||||
$scope.columnsForm = {};
|
||||
|
||||
function unlisten() {
|
||||
self.listeners.forEach(function (listener) {
|
||||
listener();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.$watch('domainObject', function (domainObject) {
|
||||
unlisten();
|
||||
self.populateForm(domainObject.getModel());
|
||||
|
||||
self.listeners.push(self.domainObject.getCapability('mutation').listen(function (model) {
|
||||
self.populateForm(model);
|
||||
}));
|
||||
});
|
||||
|
||||
/**
|
||||
* Maintain a configuration object on scope that stores column
|
||||
* configuration. On change, synchronize with object model.
|
||||
*/
|
||||
$scope.$watchCollection('configuration.table.columns', function (newColumns, oldColumns) {
|
||||
if (newColumns !== oldColumns) {
|
||||
self.domainObject.useCapability('mutation', function (model) {
|
||||
model.configuration.table.columns = newColumns;
|
||||
});
|
||||
self.domainObject.getCapability('persistence').persist();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Destroy all mutation listeners
|
||||
*/
|
||||
$scope.$on('$destroy', unlisten);
|
||||
|
||||
}
|
||||
|
||||
TableOptionsController.prototype.populateForm = function (model) {
|
||||
var columnsDefinition = (((model.configuration || {}).table || {}).columns || {}),
|
||||
rows = [];
|
||||
this.$scope.columnsForm = {
|
||||
'name': 'Columns',
|
||||
'sections': [{
|
||||
'name': 'Columns',
|
||||
'rows': rows
|
||||
}]};
|
||||
|
||||
Object.keys(columnsDefinition).forEach(function (key) {
|
||||
rows.push({
|
||||
'name': key,
|
||||
'control': 'checkbox',
|
||||
'key': key
|
||||
});
|
||||
});
|
||||
this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration || {}));
|
||||
};
|
||||
|
||||
return TableOptionsController;
|
||||
}
|
||||
);
|
@ -1,450 +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.
|
||||
*****************************************************************************/
|
||||
/* global console*/
|
||||
|
||||
/**
|
||||
* This bundle adds a table view for displaying telemetry data.
|
||||
* @namespace platform/features/table
|
||||
*/
|
||||
define(
|
||||
[
|
||||
'../TableConfiguration',
|
||||
'../../../../../src/api/objects/object-utils',
|
||||
'../TelemetryCollection',
|
||||
'lodash'
|
||||
|
||||
],
|
||||
function (TableConfiguration, objectUtils, TelemetryCollection, _) {
|
||||
|
||||
/**
|
||||
* The TableController is responsible for getting data onto the page
|
||||
* in the table widget. This includes handling composition,
|
||||
* configuration, and telemetry subscriptions.
|
||||
* @memberof platform/features/table
|
||||
* @param $scope
|
||||
* @constructor
|
||||
*/
|
||||
function TelemetryTableController(
|
||||
$scope,
|
||||
$timeout,
|
||||
openmct
|
||||
) {
|
||||
|
||||
this.$scope = $scope;
|
||||
this.$timeout = $timeout;
|
||||
this.openmct = openmct;
|
||||
this.batchSize = 1000;
|
||||
|
||||
/*
|
||||
* Initialization block
|
||||
*/
|
||||
this.columns = {}; //Range and Domain columns
|
||||
this.unobserveObject = undefined;
|
||||
this.subscriptions = [];
|
||||
this.timeColumns = [];
|
||||
$scope.rows = [];
|
||||
this.table = new TableConfiguration($scope.domainObject,
|
||||
openmct);
|
||||
this.lastBounds = this.openmct.time.bounds();
|
||||
this.lastRequestTime = 0;
|
||||
this.telemetry = new TelemetryCollection();
|
||||
if (this.lastBounds) {
|
||||
this.telemetry.bounds(this.lastBounds);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new format object from legacy object, and replace it
|
||||
* when it changes
|
||||
*/
|
||||
this.domainObject = objectUtils.toNewFormat($scope.domainObject.getModel(),
|
||||
$scope.domainObject.getId());
|
||||
|
||||
this.$scope.exportAs = this.$scope.domainObject.getModel().name;
|
||||
|
||||
_.bindAll(this, [
|
||||
'destroy',
|
||||
'sortByTimeSystem',
|
||||
'loadColumns',
|
||||
'getHistoricalData',
|
||||
'subscribeToNewData',
|
||||
'changeBounds',
|
||||
'setClock',
|
||||
'addRowsToTable',
|
||||
'removeRowsFromTable'
|
||||
]);
|
||||
|
||||
// Retrieve data when domain object is available.
|
||||
// Also deferring telemetry request makes testing easier as controller
|
||||
// construction has no unintended consequences.
|
||||
$scope.$watch("domainObject", function () {
|
||||
this.getData();
|
||||
this.registerChangeListeners();
|
||||
}.bind(this));
|
||||
|
||||
this.setClock(this.openmct.time.clock());
|
||||
|
||||
this.$scope.$on("$destroy", this.destroy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {boolean} scroll
|
||||
*/
|
||||
TelemetryTableController.prototype.setClock = function (clock) {
|
||||
this.$scope.autoScroll = clock !== undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Based on the selected time system, find a matching domain column
|
||||
* to sort by. By default will just match on key.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
TelemetryTableController.prototype.sortByTimeSystem = function () {
|
||||
var scope = this.$scope;
|
||||
var sortColumn;
|
||||
scope.defaultSort = undefined;
|
||||
|
||||
sortColumn = this.table.columns.filter(function (column) {
|
||||
return column.isCurrentTimeSystem();
|
||||
})[0];
|
||||
if (sortColumn) {
|
||||
scope.defaultSort = sortColumn.title();
|
||||
this.telemetry.sort(sortColumn.title() + '.value');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches listeners that respond to state change in domain object,
|
||||
* conductor, and receipt of telemetry
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
TelemetryTableController.prototype.registerChangeListeners = function () {
|
||||
if (this.unobserveObject) {
|
||||
this.unobserveObject();
|
||||
}
|
||||
|
||||
this.unobserveObject = this.openmct.objects.observe(this.domainObject, "*",
|
||||
function (domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.getData();
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.openmct.time.on('timeSystem', this.sortByTimeSystem);
|
||||
this.openmct.time.on('bounds', this.changeBounds);
|
||||
this.openmct.time.on('clock', this.setClock);
|
||||
|
||||
this.telemetry.on('added', this.addRowsToTable);
|
||||
this.telemetry.on('discarded', this.removeRowsFromTable);
|
||||
};
|
||||
|
||||
/**
|
||||
* On receipt of new telemetry, informs mct-table directive that new rows
|
||||
* are available and passes populated rows to it
|
||||
*
|
||||
* @private
|
||||
* @param rows
|
||||
*/
|
||||
TelemetryTableController.prototype.addRowsToTable = function (rows) {
|
||||
this.$scope.$broadcast('add:rows', rows);
|
||||
};
|
||||
|
||||
/**
|
||||
* When rows are to be removed, informs mct-table directive. Row removal
|
||||
* happens when rows call outside the bounds of the time conductor
|
||||
*
|
||||
* @private
|
||||
* @param rows
|
||||
*/
|
||||
TelemetryTableController.prototype.removeRowsFromTable = function (rows) {
|
||||
this.$scope.$broadcast('remove:rows', rows);
|
||||
};
|
||||
|
||||
/**
|
||||
* On Time Conductor bounds change, update displayed telemetry. In the
|
||||
* case of a tick, previously visible telemetry that is now out of band
|
||||
* will be removed from the table.
|
||||
* @param {openmct.TimeConductorBounds~TimeConductorBounds} bounds
|
||||
*/
|
||||
TelemetryTableController.prototype.changeBounds = function (bounds, isTick) {
|
||||
if (isTick) {
|
||||
this.telemetry.bounds(bounds);
|
||||
} else {
|
||||
// Is fixed bounds change
|
||||
this.getData();
|
||||
}
|
||||
this.lastBounds = bounds;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean controller, deregistering listeners etc.
|
||||
*/
|
||||
TelemetryTableController.prototype.destroy = function () {
|
||||
|
||||
this.openmct.time.off('timeSystem', this.sortByTimeSystem);
|
||||
this.openmct.time.off('bounds', this.changeBounds);
|
||||
this.openmct.time.off('clock', this.setClock);
|
||||
|
||||
this.subscriptions.forEach(function (subscription) {
|
||||
subscription();
|
||||
});
|
||||
|
||||
if (this.unobserveObject) {
|
||||
this.unobserveObject();
|
||||
}
|
||||
this.subscriptions = [];
|
||||
|
||||
if (this.timeoutHandle) {
|
||||
this.$timeout.cancel(this.timeoutHandle);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* For given objects, populate column metadata and table headers.
|
||||
* @private
|
||||
* @param {module:openmct.DomainObject[]} objects the domain objects for
|
||||
* which columns should be populated
|
||||
*/
|
||||
TelemetryTableController.prototype.loadColumns = function (objects) {
|
||||
var telemetryApi = this.openmct.telemetry;
|
||||
|
||||
this.table = new TableConfiguration(this.$scope.domainObject,
|
||||
this.openmct);
|
||||
|
||||
this.$scope.headers = [];
|
||||
|
||||
if (objects.length > 0) {
|
||||
objects.forEach(function (object) {
|
||||
var metadataValues = telemetryApi.getMetadata(object).values();
|
||||
metadataValues.forEach(function (metadatum) {
|
||||
this.table.addColumn(object, metadatum);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
|
||||
this.filterColumns();
|
||||
this.sortByTimeSystem();
|
||||
}
|
||||
|
||||
return objects;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request telemetry data from an historical store for given objects.
|
||||
* @private
|
||||
* @param {object[]} The domain objects to request telemetry for
|
||||
* @returns {Promise} resolved when historical data is available
|
||||
*/
|
||||
TelemetryTableController.prototype.getHistoricalData = function (objects) {
|
||||
var self = this;
|
||||
var openmct = this.openmct;
|
||||
var bounds = openmct.time.bounds();
|
||||
var scope = this.$scope;
|
||||
var rowData = [];
|
||||
var processedObjects = 0;
|
||||
var requestTime = this.lastRequestTime = Date.now();
|
||||
var telemetryCollection = this.telemetry;
|
||||
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
/*
|
||||
* On completion of batched processing, set the rows on scope
|
||||
*/
|
||||
function finishProcessing() {
|
||||
telemetryCollection.add(rowData);
|
||||
scope.rows = telemetryCollection.telemetry;
|
||||
self.loading(false);
|
||||
|
||||
resolve(scope.rows);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a batch of historical data
|
||||
*/
|
||||
function processData(object, historicalData, index, limitEvaluator) {
|
||||
if (index >= historicalData.length) {
|
||||
processedObjects++;
|
||||
|
||||
if (processedObjects === objects.length) {
|
||||
finishProcessing();
|
||||
}
|
||||
} else {
|
||||
rowData = rowData.concat(historicalData.slice(index, index + self.batchSize)
|
||||
.map(self.table.getRowValues.bind(self.table, object, limitEvaluator)));
|
||||
/*
|
||||
Use timeout to yield process to other UI activities. On
|
||||
return, process next batch
|
||||
*/
|
||||
self.timeoutHandle = self.$timeout(function () {
|
||||
processData(object, historicalData, index + self.batchSize, limitEvaluator);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function makeTableRows(object, historicalData) {
|
||||
// Only process the most recent request
|
||||
if (requestTime === self.lastRequestTime) {
|
||||
var limitEvaluator = openmct.telemetry.limitEvaluator(object);
|
||||
processData(object, historicalData, 0, limitEvaluator);
|
||||
} else {
|
||||
resolve(rowData);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Use the telemetry API to request telemetry for a given object
|
||||
*/
|
||||
function requestData(object) {
|
||||
return openmct.telemetry.request(object, {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
}).then(makeTableRows.bind(undefined, object))
|
||||
.catch(reject);
|
||||
}
|
||||
this.$timeout.cancel(this.timeoutHandle);
|
||||
|
||||
if (objects.length > 0) {
|
||||
objects.forEach(requestData);
|
||||
} else {
|
||||
self.loading(false);
|
||||
resolve([]);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Subscribe to real-time data for the given objects.
|
||||
* @private
|
||||
* @param {object[]} objects The objects to subscribe to.
|
||||
*/
|
||||
TelemetryTableController.prototype.subscribeToNewData = function (objects) {
|
||||
var telemetryApi = this.openmct.telemetry;
|
||||
var telemetryCollection = this.telemetry;
|
||||
//Set table max length to avoid unbounded growth.
|
||||
var limitEvaluator;
|
||||
var table = this.table;
|
||||
|
||||
this.subscriptions.forEach(function (subscription) {
|
||||
subscription();
|
||||
});
|
||||
this.subscriptions = [];
|
||||
|
||||
function newData(domainObject, datum) {
|
||||
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
|
||||
telemetryCollection.add([table.getRowValues(domainObject, limitEvaluator, datum)]);
|
||||
}
|
||||
|
||||
objects.forEach(function (object) {
|
||||
this.subscriptions.push(
|
||||
telemetryApi.subscribe(object, newData.bind(this, object), {}));
|
||||
}.bind(this));
|
||||
|
||||
return objects;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an array of telemetry objects in this view that should be
|
||||
* subscribed to.
|
||||
* @private
|
||||
* @returns {Promise<Array>} a promise that resolves with an array of
|
||||
* telemetry objects in this view.
|
||||
*/
|
||||
TelemetryTableController.prototype.getTelemetryObjects = function () {
|
||||
var telemetryApi = this.openmct.telemetry;
|
||||
var compositionApi = this.openmct.composition;
|
||||
|
||||
function filterForTelemetry(objects) {
|
||||
return objects.filter(telemetryApi.isTelemetryObject.bind(telemetryApi));
|
||||
}
|
||||
|
||||
/*
|
||||
* If parent object is a telemetry object, subscribe to it. Do not
|
||||
* test composees.
|
||||
*/
|
||||
if (telemetryApi.isTelemetryObject(this.domainObject)) {
|
||||
return Promise.resolve([this.domainObject]);
|
||||
} else {
|
||||
/*
|
||||
* If parent object is not a telemetry object, subscribe to all
|
||||
* composees that are telemetry producing objects.
|
||||
*/
|
||||
var composition = compositionApi.get(this.domainObject);
|
||||
|
||||
if (composition) {
|
||||
return composition
|
||||
.load()
|
||||
.then(filterForTelemetry);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Request historical data, and subscribe to for real-time data.
|
||||
* @private
|
||||
* @returns {Promise} A promise that is resolved once subscription is
|
||||
* established, and historical telemetry is received and processed.
|
||||
*/
|
||||
TelemetryTableController.prototype.getData = function () {
|
||||
var scope = this.$scope;
|
||||
|
||||
this.telemetry.clear();
|
||||
this.telemetry.bounds(this.openmct.time.bounds());
|
||||
|
||||
this.loading(true);
|
||||
scope.rows = [];
|
||||
|
||||
return this.getTelemetryObjects()
|
||||
.then(this.loadColumns)
|
||||
.then(this.subscribeToNewData)
|
||||
.then(this.getHistoricalData)
|
||||
.catch(function error(e) {
|
||||
this.loading(false);
|
||||
console.error(e.stack || e);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
TelemetryTableController.prototype.loading = function (loading) {
|
||||
this.$timeout(function () {
|
||||
this.$scope.loading = loading;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* When column configuration changes, update the visible headers
|
||||
* accordingly.
|
||||
* @private
|
||||
*/
|
||||
TelemetryTableController.prototype.filterColumns = function () {
|
||||
var columnConfig = this.table.buildColumnConfiguration();
|
||||
|
||||
//Populate headers with visible columns (determined by configuration)
|
||||
this.$scope.headers = Object.keys(columnConfig).filter(function (column) {
|
||||
return columnConfig[column];
|
||||
});
|
||||
};
|
||||
|
||||
return TelemetryTableController;
|
||||
}
|
||||
);
|
@ -1,115 +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(
|
||||
[
|
||||
"../controllers/MCTTableController",
|
||||
"../../res/templates/mct-table.html"
|
||||
],
|
||||
function (MCTTableController, TableTemplate) {
|
||||
/**
|
||||
* Defines a generic 'Table' component. The table can be populated
|
||||
* en-masse by setting the rows attribute, or rows can be added as
|
||||
* needed via a broadcast 'addRow' event.
|
||||
*
|
||||
* This directive accepts parameters specifying header and row
|
||||
* content, as well as some additional options.
|
||||
*
|
||||
* Two broadcast events for notifying the table that the rows have
|
||||
* changed. For performance reasons, the table does not monitor the
|
||||
* content of `rows` constantly.
|
||||
* - 'add:row': A $broadcast event that will notify the table that
|
||||
* a new row has been added to the table.
|
||||
* eg.
|
||||
* <pre><code>
|
||||
* $scope.rows.push(newRow);
|
||||
* $scope.$broadcast('add:row', $scope.rows.length-1);
|
||||
* </code></pre>
|
||||
* The code above adds a new row, and alerts the table using the
|
||||
* add:row event. Sorting and filtering will be applied
|
||||
* automatically by the table component.
|
||||
*
|
||||
* - 'remove:row': A $broadcast event that will notify the table that a
|
||||
* row should be removed from the table.
|
||||
* eg.
|
||||
* <pre><code>
|
||||
* $scope.rows.slice(5, 1);
|
||||
* $scope.$broadcast('remove:row', 5);
|
||||
* </code></pre>
|
||||
* The code above removes a row from the rows array, and then alerts
|
||||
* the table to its removal.
|
||||
*
|
||||
* @memberof platform/features/table
|
||||
* @param {string[]} headers The column titles to appear at the top
|
||||
* of the table. Corresponding values are specified in the rows
|
||||
* using the header title provided here.
|
||||
* @param {Object[]} rows The row content. Each row is an object
|
||||
* with key-value pairs where the key corresponds to a header
|
||||
* specified in the headers parameter.
|
||||
* @param {boolean} enableFilter If true, values will be searchable
|
||||
* and results filtered
|
||||
* @param {boolean} enableSort If true, sorting will be enabled
|
||||
* allowing sorting by clicking on column headers
|
||||
* @param {boolean} autoScroll If true, table will automatically
|
||||
* scroll to the bottom as new data arrives. Auto-scroll can be
|
||||
* disengaged manually by scrolling away from the bottom of the
|
||||
* table, and can also be enabled manually by scrolling to the bottom of
|
||||
* the table rows.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MCTTable() {
|
||||
return {
|
||||
restrict: "E",
|
||||
template: TableTemplate,
|
||||
controller: [
|
||||
'$scope',
|
||||
'$window',
|
||||
'$element',
|
||||
'exportService',
|
||||
'formatService',
|
||||
'openmct',
|
||||
MCTTableController
|
||||
],
|
||||
controllerAs: "table",
|
||||
scope: {
|
||||
headers: "=",
|
||||
rows: "=",
|
||||
formatCell: "=?",
|
||||
enableFilter: "=?",
|
||||
enableSort: "=?",
|
||||
autoScroll: "=?",
|
||||
// Used to indicate which columns contain time data. This
|
||||
// will be used for determining when the table is sorted
|
||||
// by the column that can be used for time conductor
|
||||
// time of interest.
|
||||
timeColumns: "=?",
|
||||
// Indicate a column to sort on. Allows control of sort
|
||||
// via configuration (eg. for default sort column).
|
||||
defaultSort: "=?"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return MCTTable;
|
||||
}
|
||||
);
|
@ -1,214 +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/TableConfiguration"
|
||||
],
|
||||
function (Table) {
|
||||
|
||||
describe("A table", function () {
|
||||
var mockTableObject,
|
||||
mockTelemetryObject,
|
||||
mockAPI,
|
||||
mockTelemetryAPI,
|
||||
table,
|
||||
mockTimeAPI,
|
||||
mockObjectsAPI,
|
||||
mockModel;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTableObject = jasmine.createSpyObj('domainObject',
|
||||
['getModel', 'useCapability', 'getCapability', 'hasCapability']
|
||||
);
|
||||
mockModel = {};
|
||||
mockTableObject.getModel.and.returnValue(mockModel);
|
||||
mockTableObject.getCapability.and.callFake(function (name) {
|
||||
return name === 'editor' && {
|
||||
isEditContextRoot: function () {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
});
|
||||
mockTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: 'mock',
|
||||
key: 'domainObject'
|
||||
}
|
||||
};
|
||||
|
||||
mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
|
||||
'getValueFormatter'
|
||||
]);
|
||||
mockTimeAPI = jasmine.createSpyObj('timeAPI', [
|
||||
'timeSystem'
|
||||
]);
|
||||
mockObjectsAPI = jasmine.createSpyObj('objectsAPI', [
|
||||
'makeKeyString'
|
||||
]);
|
||||
mockObjectsAPI.makeKeyString.and.callFake(function (identifier) {
|
||||
return [identifier.namespace, identifier.key].join(':');
|
||||
});
|
||||
|
||||
mockAPI = {
|
||||
telemetry: mockTelemetryAPI,
|
||||
time: mockTimeAPI,
|
||||
objects: mockObjectsAPI
|
||||
};
|
||||
mockTelemetryAPI.getValueFormatter.and.callFake(function (metadata) {
|
||||
var formatter = jasmine.createSpyObj(
|
||||
'telemetryFormatter:' + metadata.key,
|
||||
[
|
||||
'format',
|
||||
'parse'
|
||||
]
|
||||
);
|
||||
var getter = function (datum) {
|
||||
return datum[metadata.key];
|
||||
};
|
||||
formatter.format.and.callFake(getter);
|
||||
formatter.parse.and.callFake(getter);
|
||||
return formatter;
|
||||
});
|
||||
|
||||
table = new Table(mockTableObject, mockAPI);
|
||||
});
|
||||
|
||||
describe("Building columns from telemetry metadata", function () {
|
||||
var metadata = [
|
||||
{
|
||||
name: 'Range 1',
|
||||
key: 'range1',
|
||||
source: 'range1',
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Range 2',
|
||||
key: 'range2',
|
||||
source: 'range2',
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Domain 1',
|
||||
key: 'domain1',
|
||||
source: 'domain1',
|
||||
format: 'utc',
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Domain 2',
|
||||
key: 'domain2',
|
||||
source: 'domain2',
|
||||
format: 'utc',
|
||||
hints: {
|
||||
domain: 2
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeAPI.timeSystem.and.returnValue({
|
||||
key: 'domain1'
|
||||
});
|
||||
metadata.forEach(function (metadatum) {
|
||||
table.addColumn(mockTelemetryObject, metadatum);
|
||||
});
|
||||
});
|
||||
|
||||
it("populates columns", function () {
|
||||
expect(table.columns.length).toBe(4);
|
||||
});
|
||||
|
||||
it("Produces headers for each column based on metadata name", function () {
|
||||
expect(table.headers.size).toBe(4);
|
||||
Array.from(table.headers.values).forEach(function (header, i) {
|
||||
expect(header).toEqual(metadata[i].name);
|
||||
});
|
||||
});
|
||||
|
||||
it("Provides a default configuration with all columns" +
|
||||
" visible", function () {
|
||||
var configuration = table.buildColumnConfiguration();
|
||||
|
||||
expect(configuration).toBeDefined();
|
||||
expect(Object.keys(configuration).every(function (key) {
|
||||
return configuration[key];
|
||||
}));
|
||||
});
|
||||
|
||||
it("Column configuration exposes persisted configuration", function () {
|
||||
var tableConfig,
|
||||
modelConfig = {
|
||||
table: {
|
||||
columns : {
|
||||
'Range 1': false
|
||||
}
|
||||
}
|
||||
};
|
||||
mockModel.configuration = modelConfig;
|
||||
|
||||
tableConfig = table.buildColumnConfiguration();
|
||||
|
||||
expect(tableConfig).toBeDefined();
|
||||
expect(tableConfig['Range 1']).toBe(false);
|
||||
});
|
||||
|
||||
describe('retrieving row values', function () {
|
||||
var datum,
|
||||
rowValues;
|
||||
|
||||
beforeEach(function () {
|
||||
datum = {
|
||||
'range1': 10,
|
||||
'range2': 20,
|
||||
'domain1': 0,
|
||||
'domain2': 1
|
||||
};
|
||||
var limitEvaluator = {
|
||||
evaluate: function () {
|
||||
return {
|
||||
"cssClass": "alarm-class"
|
||||
};
|
||||
}
|
||||
};
|
||||
rowValues = table.getRowValues(mockTelemetryObject, limitEvaluator, datum);
|
||||
});
|
||||
|
||||
it("Returns a value for every column", function () {
|
||||
expect(rowValues['Range 1'].text).toEqual(10);
|
||||
});
|
||||
|
||||
it("Applies appropriate css class if limit violated.", function () {
|
||||
expect(rowValues['Range 1'].cssClass).toEqual("alarm-class");
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -1,212 +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/TelemetryCollection"
|
||||
],
|
||||
function (TelemetryCollection) {
|
||||
|
||||
describe("A telemetry collection", function () {
|
||||
|
||||
var collection;
|
||||
var telemetryObjects;
|
||||
var ms;
|
||||
var integerTextMap = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE",
|
||||
"SIX", "SEVEN", "EIGHT", "NINE", "TEN", "ELEVEN"];
|
||||
|
||||
beforeEach(function () {
|
||||
telemetryObjects = [0,9,2,4,7,8,5,1,3,6].map(function (number) {
|
||||
ms = number * 1000;
|
||||
return {
|
||||
timestamp: ms,
|
||||
value: {
|
||||
integer: number,
|
||||
text: integerTextMap[number]
|
||||
}
|
||||
};
|
||||
});
|
||||
collection = new TelemetryCollection();
|
||||
});
|
||||
|
||||
it("Sorts inserted telemetry by specified field",
|
||||
function () {
|
||||
collection.sort('value.integer');
|
||||
collection.add(telemetryObjects);
|
||||
expect(collection.telemetry[0].value.integer).toBe(0);
|
||||
expect(collection.telemetry[1].value.integer).toBe(1);
|
||||
expect(collection.telemetry[2].value.integer).toBe(2);
|
||||
expect(collection.telemetry[3].value.integer).toBe(3);
|
||||
|
||||
collection.sort('value.text');
|
||||
expect(collection.telemetry[0].value.text).toBe("EIGHT");
|
||||
expect(collection.telemetry[1].value.text).toBe("FIVE");
|
||||
expect(collection.telemetry[2].value.text).toBe("FOUR");
|
||||
expect(collection.telemetry[3].value.text).toBe("NINE");
|
||||
}
|
||||
);
|
||||
|
||||
describe("on bounds change", function () {
|
||||
var discardedCallback;
|
||||
|
||||
beforeEach(function () {
|
||||
discardedCallback = jasmine.createSpy("discarded");
|
||||
collection.on("discarded", discardedCallback);
|
||||
collection.sort("timestamp");
|
||||
collection.add(telemetryObjects);
|
||||
collection.bounds({start: 5000, end: 8000});
|
||||
});
|
||||
|
||||
|
||||
it("emits an event indicating that telemetry has " +
|
||||
"been discarded", function () {
|
||||
expect(discardedCallback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("discards telemetry data with a time stamp " +
|
||||
"before specified start bound", function () {
|
||||
var discarded = discardedCallback.calls.mostRecent().args[0];
|
||||
|
||||
// Expect 5 because as an optimization, the TelemetryCollection
|
||||
// will not consider telemetry values that exceed the upper
|
||||
// bounds. Arbitrary bounds changes in which the end bound is
|
||||
// decreased is assumed to require a new historical query, and
|
||||
// hence re-population of the collection anyway
|
||||
expect(discarded.length).toBe(5);
|
||||
expect(discarded[0].value.integer).toBe(0);
|
||||
expect(discarded[1].value.integer).toBe(1);
|
||||
expect(discarded[4].value.integer).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when adding telemetry to a collection", function () {
|
||||
var addedCallback;
|
||||
beforeEach(function () {
|
||||
collection.sort("timestamp");
|
||||
collection.add(telemetryObjects);
|
||||
addedCallback = jasmine.createSpy("added");
|
||||
collection.on("added", addedCallback);
|
||||
});
|
||||
|
||||
it("emits an event",
|
||||
function () {
|
||||
var addedObject = {
|
||||
timestamp: 10000,
|
||||
value: {
|
||||
integer: 10,
|
||||
text: integerTextMap[10]
|
||||
}
|
||||
};
|
||||
collection.add([addedObject]);
|
||||
expect(addedCallback).toHaveBeenCalledWith([addedObject]);
|
||||
}
|
||||
);
|
||||
it("inserts in the correct order",
|
||||
function () {
|
||||
var addedObjectA = {
|
||||
timestamp: 10000,
|
||||
value: {
|
||||
integer: 10,
|
||||
text: integerTextMap[10]
|
||||
}
|
||||
};
|
||||
var addedObjectB = {
|
||||
timestamp: 11000,
|
||||
value: {
|
||||
integer: 11,
|
||||
text: integerTextMap[11]
|
||||
}
|
||||
};
|
||||
collection.add([addedObjectB, addedObjectA]);
|
||||
|
||||
expect(collection.telemetry[11]).toBe(addedObjectB);
|
||||
}
|
||||
);
|
||||
it("maintains insertion order in the case of duplicate time stamps",
|
||||
function () {
|
||||
var addedObjectA = {
|
||||
timestamp: 10000,
|
||||
value: {
|
||||
integer: 10,
|
||||
text: integerTextMap[10]
|
||||
}
|
||||
};
|
||||
var addedObjectB = {
|
||||
timestamp: 10000,
|
||||
value: {
|
||||
integer: 11,
|
||||
text: integerTextMap[11]
|
||||
}
|
||||
};
|
||||
collection.add([addedObjectA, addedObjectB]);
|
||||
|
||||
expect(collection.telemetry[11]).toBe(addedObjectB);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe("buffers telemetry", function () {
|
||||
var addedObjectA;
|
||||
var addedObjectB;
|
||||
|
||||
beforeEach(function () {
|
||||
collection.sort("timestamp");
|
||||
collection.add(telemetryObjects);
|
||||
|
||||
addedObjectA = {
|
||||
timestamp: 10000,
|
||||
value: {
|
||||
integer: 10,
|
||||
text: integerTextMap[10]
|
||||
}
|
||||
};
|
||||
addedObjectB = {
|
||||
timestamp: 11000,
|
||||
value: {
|
||||
integer: 11,
|
||||
text: integerTextMap[11]
|
||||
}
|
||||
};
|
||||
|
||||
collection.bounds({start: 0, end: 10000});
|
||||
collection.add([addedObjectA, addedObjectB]);
|
||||
});
|
||||
it("when it falls outside of bounds", function () {
|
||||
expect(collection.highBuffer).toBeDefined();
|
||||
expect(collection.highBuffer.length).toBe(1);
|
||||
expect(collection.highBuffer[0]).toBe(addedObjectB);
|
||||
});
|
||||
it("and adds it to collection when it falls within bounds", function () {
|
||||
expect(collection.telemetry.length).toBe(11);
|
||||
collection.bounds({start: 0, end: 11000});
|
||||
expect(collection.telemetry.length).toBe(12);
|
||||
expect(collection.telemetry[11]).toBe(addedObjectB);
|
||||
});
|
||||
it("and removes it from the buffer when it falls within bounds", function () {
|
||||
expect(collection.highBuffer.length).toBe(1);
|
||||
collection.bounds({start: 0, end: 11000});
|
||||
expect(collection.highBuffer.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -1,598 +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",
|
||||
"moment",
|
||||
"../../src/controllers/MCTTableController"
|
||||
],
|
||||
function ($, moment, MCTTableController) {
|
||||
|
||||
var MOCK_ELEMENT_TEMPLATE =
|
||||
'<div><div class="l-view-section t-scrolling">' +
|
||||
'<table class="sizing-table"><tbody></tbody></table>' +
|
||||
'<table class="mct-table"><thead></thead></table>' +
|
||||
'</div></div>';
|
||||
|
||||
describe('The MCTTable Controller', function () {
|
||||
|
||||
var controller,
|
||||
mockScope,
|
||||
watches,
|
||||
mockWindow,
|
||||
mockElement,
|
||||
mockExportService,
|
||||
mockConductor,
|
||||
mockFormatService,
|
||||
mockFormat;
|
||||
|
||||
function getCallback(target, event) {
|
||||
return target.calls.all().filter(function (call) {
|
||||
return call.args[0] === event;
|
||||
})[0].args[1];
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
watches = {};
|
||||
|
||||
mockScope = jasmine.createSpyObj('scope', [
|
||||
'$watch',
|
||||
'$on',
|
||||
'$watchCollection',
|
||||
'$digest'
|
||||
]);
|
||||
mockScope.$watchCollection.and.callFake(function (event, callback) {
|
||||
watches[event] = callback;
|
||||
});
|
||||
|
||||
mockElement = $(MOCK_ELEMENT_TEMPLATE);
|
||||
mockExportService = jasmine.createSpyObj('exportService', [
|
||||
'exportCSV'
|
||||
]);
|
||||
|
||||
mockConductor = jasmine.createSpyObj('conductor', [
|
||||
'bounds',
|
||||
'timeOfInterest',
|
||||
'timeSystem',
|
||||
'on',
|
||||
'off'
|
||||
]);
|
||||
|
||||
mockScope.displayHeaders = true;
|
||||
mockWindow = jasmine.createSpyObj('$window', ['requestAnimationFrame']);
|
||||
mockWindow.requestAnimationFrame.and.callFake(function (f) {
|
||||
return f();
|
||||
});
|
||||
|
||||
mockFormat = jasmine.createSpyObj('formatter', [
|
||||
'parse',
|
||||
'format'
|
||||
]);
|
||||
mockFormatService = jasmine.createSpyObj('formatService', [
|
||||
'getFormat'
|
||||
]);
|
||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
||||
|
||||
controller = new MCTTableController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockElement,
|
||||
mockExportService,
|
||||
mockFormatService,
|
||||
{time: mockConductor}
|
||||
);
|
||||
spyOn(controller, 'setVisibleRows').and.callThrough();
|
||||
});
|
||||
|
||||
it('Reacts to changes to filters, headers, and rows', function () {
|
||||
expect(mockScope.$watchCollection).toHaveBeenCalledWith('filters', jasmine.any(Function));
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith('headers', jasmine.any(Function));
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith('rows', jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('unregisters listeners on destruction', function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
||||
getCallback(mockScope.$on, '$destroy')();
|
||||
|
||||
expect(mockConductor.off).toHaveBeenCalledWith('timeSystem', controller.changeTimeSystem);
|
||||
expect(mockConductor.off).toHaveBeenCalledWith('timeOfInterest', controller.changeTimeOfInterest);
|
||||
expect(mockConductor.off).toHaveBeenCalledWith('bounds', controller.changeBounds);
|
||||
});
|
||||
|
||||
describe('The time of interest', function () {
|
||||
var rowsAsc = [];
|
||||
var rowsDesc = [];
|
||||
beforeEach(function () {
|
||||
rowsAsc = [
|
||||
{
|
||||
'col1': {'text': 'row1 col1 match'},
|
||||
'col2': {'text': '2012-10-31 00:00:00.000Z'},
|
||||
'col3': {'text': 'row1 col3'}
|
||||
},
|
||||
{
|
||||
'col1': {'text': 'row2 col1 match'},
|
||||
'col2': {'text': '2012-11-01 00:00:00.000Z'},
|
||||
'col3': {'text': 'row2 col3'}
|
||||
},
|
||||
{
|
||||
'col1': {'text': 'row3 col1'},
|
||||
'col2': {'text': '2012-11-03 00:00:00.000Z'},
|
||||
'col3': {'text': 'row3 col3'}
|
||||
},
|
||||
{
|
||||
'col1': {'text': 'row3 col1'},
|
||||
'col2': {'text': '2012-11-04 00:00:00.000Z'},
|
||||
'col3': {'text': 'row3 col3'}
|
||||
}
|
||||
];
|
||||
rowsDesc = [
|
||||
{
|
||||
'col1': {'text': 'row1 col1 match'},
|
||||
'col2': {'text': '2012-11-02 00:00:00.000Z'},
|
||||
'col3': {'text': 'row1 col3'}
|
||||
},
|
||||
{
|
||||
'col1': {'text': 'row2 col1 match'},
|
||||
'col2': {'text': '2012-11-01 00:00:00.000Z'},
|
||||
'col3': {'text': 'row2 col3'}
|
||||
},
|
||||
{
|
||||
'col1': {'text': 'row3 col1'},
|
||||
'col2': {'text': '2012-10-30 00:00:00.000Z'},
|
||||
'col3': {'text': 'row3 col3'}
|
||||
},
|
||||
{
|
||||
'col1': {'text': 'row3 col1'},
|
||||
'col2': {'text': '2012-10-29 00:00:00.000Z'},
|
||||
'col3': {'text': 'row3 col3'}
|
||||
}
|
||||
];
|
||||
mockScope.timeColumns = ['col2'];
|
||||
mockScope.sortColumn = 'col2';
|
||||
controller.toiFormatter = mockFormat;
|
||||
});
|
||||
it("is observed for changes", function () {
|
||||
//Mock setting time columns
|
||||
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
|
||||
|
||||
expect(mockConductor.on).toHaveBeenCalledWith('timeOfInterest',
|
||||
jasmine.any(Function));
|
||||
});
|
||||
describe("causes corresponding row to be highlighted", function () {
|
||||
it("when changed and rows sorted ascending", function () {
|
||||
var testDate = "2012-11-02 00:00:00.000Z";
|
||||
mockScope.rows = rowsAsc;
|
||||
mockScope.displayRows = rowsAsc;
|
||||
mockScope.sortDirection = 'asc';
|
||||
|
||||
var toi = moment.utc(testDate).valueOf();
|
||||
mockFormat.parse.and.returnValue(toi);
|
||||
mockFormat.format.and.returnValue(testDate);
|
||||
|
||||
//mock setting the timeColumns parameter
|
||||
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
|
||||
|
||||
var toiCallback = getCallback(mockConductor.on, 'timeOfInterest');
|
||||
toiCallback(toi);
|
||||
|
||||
expect(mockScope.toiRowIndex).toBe(2);
|
||||
});
|
||||
it("when changed and rows sorted descending", function () {
|
||||
var testDate = "2012-10-31 00:00:00.000Z";
|
||||
mockScope.rows = rowsDesc;
|
||||
mockScope.displayRows = rowsDesc;
|
||||
mockScope.sortDirection = 'desc';
|
||||
|
||||
var toi = moment.utc(testDate).valueOf();
|
||||
mockFormat.parse.and.returnValue(toi);
|
||||
mockFormat.format.and.returnValue(testDate);
|
||||
|
||||
//mock setting the timeColumns parameter
|
||||
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
|
||||
|
||||
var toiCallback = getCallback(mockConductor.on, 'timeOfInterest');
|
||||
toiCallback(toi);
|
||||
|
||||
expect(mockScope.toiRowIndex).toBe(2);
|
||||
});
|
||||
it("when rows are set and sorted ascending", function () {
|
||||
var testDate = "2012-11-02 00:00:00.000Z";
|
||||
mockScope.sortDirection = 'asc';
|
||||
|
||||
var toi = moment.utc(testDate).valueOf();
|
||||
mockFormat.parse.and.returnValue(toi);
|
||||
mockFormat.format.and.returnValue(testDate);
|
||||
mockConductor.timeOfInterest.and.returnValue(toi);
|
||||
|
||||
//mock setting the timeColumns parameter
|
||||
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
|
||||
|
||||
//Mock setting the rows on scope
|
||||
var rowsCallback = getCallback(mockScope.$watch, 'rows');
|
||||
var setRowsPromise = rowsCallback(rowsAsc);
|
||||
|
||||
return setRowsPromise.then(function () {
|
||||
expect(mockScope.toiRowIndex).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('rows', function () {
|
||||
var testRows = [];
|
||||
beforeEach(function () {
|
||||
testRows = [
|
||||
{
|
||||
'col1': {'text': 'row1 col1 match'},
|
||||
'col2': {'text': 'def'},
|
||||
'col3': {'text': 'row1 col3'}
|
||||
},
|
||||
{
|
||||
'col1': {'text': 'row2 col1 match'},
|
||||
'col2': {'text': 'abc'},
|
||||
'col3': {'text': 'row2 col3'}
|
||||
},
|
||||
{
|
||||
'col1': {'text': 'row3 col1'},
|
||||
'col2': {'text': 'ghi'},
|
||||
'col3': {'text': 'row3 col3'}
|
||||
}
|
||||
];
|
||||
mockScope.rows = testRows;
|
||||
});
|
||||
|
||||
it('Filters results based on filter input', function () {
|
||||
var filters = {},
|
||||
filteredRows;
|
||||
|
||||
mockScope.filters = filters;
|
||||
|
||||
filteredRows = controller.filterRows(testRows);
|
||||
expect(filteredRows.length).toBe(3);
|
||||
filters.col1 = 'row1';
|
||||
filteredRows = controller.filterRows(testRows);
|
||||
expect(filteredRows.length).toBe(1);
|
||||
filters.col1 = 'match';
|
||||
filteredRows = controller.filterRows(testRows);
|
||||
expect(filteredRows.length).toBe(2);
|
||||
});
|
||||
|
||||
it('Sets rows on scope when rows change', function () {
|
||||
controller.setRows(testRows);
|
||||
expect(mockScope.displayRows.length).toBe(3);
|
||||
expect(mockScope.displayRows).toEqual(testRows);
|
||||
});
|
||||
|
||||
it('Supports adding rows individually', function () {
|
||||
var addRowFunc = getCallback(mockScope.$on, 'add:rows'),
|
||||
row4 = {
|
||||
'col1': {'text': 'row3 col1'},
|
||||
'col2': {'text': 'ghi'},
|
||||
'col3': {'text': 'row3 col3'}
|
||||
};
|
||||
controller.setRows(testRows);
|
||||
expect(mockScope.displayRows.length).toBe(3);
|
||||
testRows.push(row4);
|
||||
addRowFunc(undefined, [row4]);
|
||||
expect(mockScope.displayRows.length).toBe(4);
|
||||
});
|
||||
|
||||
it('Supports removing rows individually', function () {
|
||||
var removeRowFunc = getCallback(mockScope.$on, 'remove:rows');
|
||||
controller.setRows(testRows);
|
||||
expect(mockScope.displayRows.length).toBe(3);
|
||||
removeRowFunc(undefined, [testRows[2]]);
|
||||
expect(mockScope.displayRows.length).toBe(2);
|
||||
expect(controller.setVisibleRows).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("can be exported as CSV", function () {
|
||||
controller.setRows(testRows);
|
||||
controller.setHeaders(Object.keys(testRows[0]));
|
||||
mockScope.exportAsCSV();
|
||||
expect(mockExportService.exportCSV)
|
||||
.toHaveBeenCalled();
|
||||
mockExportService.exportCSV.calls.mostRecent().args[0]
|
||||
.forEach(function (row, i) {
|
||||
Object.keys(row).forEach(function (k) {
|
||||
expect(row[k]).toEqual(
|
||||
mockScope.displayRows[i][k].text
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sorting', function () {
|
||||
var sortedRows;
|
||||
|
||||
beforeEach(function () {
|
||||
sortedRows = [];
|
||||
});
|
||||
|
||||
it('Sorts rows ascending', function () {
|
||||
mockScope.sortColumn = 'col1';
|
||||
mockScope.sortDirection = 'asc';
|
||||
|
||||
sortedRows = controller.sortRows(testRows);
|
||||
expect(sortedRows[0].col1.text).toEqual('row1 col1 match');
|
||||
expect(sortedRows[1].col1.text).toEqual('row2 col1' +
|
||||
' match');
|
||||
expect(sortedRows[2].col1.text).toEqual('row3 col1');
|
||||
|
||||
});
|
||||
|
||||
it('Sorts rows descending', function () {
|
||||
mockScope.sortColumn = 'col1';
|
||||
mockScope.sortDirection = 'desc';
|
||||
|
||||
sortedRows = controller.sortRows(testRows);
|
||||
expect(sortedRows[0].col1.text).toEqual('row3 col1');
|
||||
expect(sortedRows[1].col1.text).toEqual('row2 col1 match');
|
||||
expect(sortedRows[2].col1.text).toEqual('row1 col1 match');
|
||||
});
|
||||
it('Sorts rows descending based on selected sort column', function () {
|
||||
mockScope.sortColumn = 'col2';
|
||||
mockScope.sortDirection = 'desc';
|
||||
|
||||
sortedRows = controller.sortRows(testRows);
|
||||
expect(sortedRows[0].col2.text).toEqual('ghi');
|
||||
expect(sortedRows[1].col2.text).toEqual('def');
|
||||
expect(sortedRows[2].col2.text).toEqual('abc');
|
||||
});
|
||||
|
||||
it('Allows sort column to be changed externally by ' +
|
||||
'setting or changing sortBy attribute', function () {
|
||||
mockScope.displayRows = testRows;
|
||||
var sortByCB = getCallback(mockScope.$watch, 'defaultSort');
|
||||
sortByCB('col2');
|
||||
|
||||
expect(mockScope.sortDirection).toEqual('asc');
|
||||
|
||||
expect(mockScope.displayRows[0].col2.text).toEqual('abc');
|
||||
expect(mockScope.displayRows[1].col2.text).toEqual('def');
|
||||
expect(mockScope.displayRows[2].col2.text).toEqual('ghi');
|
||||
|
||||
});
|
||||
|
||||
// https://github.com/nasa/openmct/issues/910
|
||||
it('updates visible rows in scope', function () {
|
||||
var oldRows;
|
||||
mockScope.rows = testRows;
|
||||
var setRowsPromise = controller.setRows(testRows);
|
||||
|
||||
oldRows = mockScope.visibleRows;
|
||||
mockScope.toggleSort('col2');
|
||||
|
||||
return setRowsPromise.then(function () {
|
||||
expect(mockScope.visibleRows).not.toEqual(oldRows);
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly sorts rows of differing types', function () {
|
||||
mockScope.sortColumn = 'col2';
|
||||
mockScope.sortDirection = 'desc';
|
||||
|
||||
testRows.push({
|
||||
'col1': {'text': 'row4 col1'},
|
||||
'col2': {'text': '123'},
|
||||
'col3': {'text': 'row4 col3'}
|
||||
});
|
||||
testRows.push({
|
||||
'col1': {'text': 'row5 col1'},
|
||||
'col2': {'text': '456'},
|
||||
'col3': {'text': 'row5 col3'}
|
||||
});
|
||||
testRows.push({
|
||||
'col1': {'text': 'row5 col1'},
|
||||
'col2': {'text': ''},
|
||||
'col3': {'text': 'row5 col3'}
|
||||
});
|
||||
|
||||
sortedRows = controller.sortRows(testRows);
|
||||
expect(sortedRows[0].col2.text).toEqual('ghi');
|
||||
expect(sortedRows[1].col2.text).toEqual('def');
|
||||
expect(sortedRows[2].col2.text).toEqual('abc');
|
||||
|
||||
expect(sortedRows[sortedRows.length - 3].col2.text).toEqual('456');
|
||||
expect(sortedRows[sortedRows.length - 2].col2.text).toEqual('123');
|
||||
expect(sortedRows[sortedRows.length - 1].col2.text).toEqual('');
|
||||
});
|
||||
|
||||
describe('The sort comparator', function () {
|
||||
it('Correctly sorts different data types', function () {
|
||||
var val1 = "",
|
||||
val2 = "1",
|
||||
val3 = "2016-04-05 18:41:30.713Z",
|
||||
val4 = "1.1",
|
||||
val5 = "8.945520958175627e-13";
|
||||
mockScope.sortDirection = "asc";
|
||||
|
||||
expect(controller.sortComparator(val1, val2)).toEqual(-1);
|
||||
expect(controller.sortComparator(val3, val1)).toEqual(1);
|
||||
expect(controller.sortComparator(val3, val2)).toEqual(1);
|
||||
expect(controller.sortComparator(val4, val2)).toEqual(1);
|
||||
expect(controller.sortComparator(val2, val5)).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Adding new rows', function () {
|
||||
var row4,
|
||||
row5,
|
||||
row6;
|
||||
|
||||
beforeEach(function () {
|
||||
row4 = {
|
||||
'col1': {'text': 'row4 col1'},
|
||||
'col2': {'text': 'xyz'},
|
||||
'col3': {'text': 'row4 col3'}
|
||||
};
|
||||
row5 = {
|
||||
'col1': {'text': 'row5 col1'},
|
||||
'col2': {'text': 'aaa'},
|
||||
'col3': {'text': 'row5 col3'}
|
||||
};
|
||||
row6 = {
|
||||
'col1': {'text': 'row6 col1'},
|
||||
'col2': {'text': 'ggg'},
|
||||
'col3': {'text': 'row6 col3'}
|
||||
};
|
||||
});
|
||||
|
||||
it('Adds new rows at the correct sort position when' +
|
||||
' sorted ', function () {
|
||||
mockScope.sortColumn = 'col2';
|
||||
mockScope.sortDirection = 'desc';
|
||||
|
||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||
|
||||
controller.addRows(undefined, [row4, row5, row6, row6]);
|
||||
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
||||
expect(mockScope.displayRows[6].col2.text).toEqual('aaa');
|
||||
//Added a duplicate row
|
||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
||||
});
|
||||
|
||||
it('Inserts duplicate values for sort column in order received when sorted descending', function () {
|
||||
mockScope.sortColumn = 'col2';
|
||||
mockScope.sortDirection = 'desc';
|
||||
|
||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||
|
||||
var row6b = {
|
||||
'col1': {'text': 'row6b col1'},
|
||||
'col2': {'text': 'ggg'},
|
||||
'col3': {'text': 'row6b col3'}
|
||||
};
|
||||
var row6c = {
|
||||
'col1': {'text': 'row6c col1'},
|
||||
'col2': {'text': 'ggg'},
|
||||
'col3': {'text': 'row6c col3'}
|
||||
};
|
||||
|
||||
controller.addRows(undefined, [row4, row5]);
|
||||
controller.addRows(undefined, [row6, row6b, row6c]);
|
||||
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
||||
expect(mockScope.displayRows[7].col2.text).toEqual('aaa');
|
||||
|
||||
// Added duplicate rows
|
||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
||||
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
||||
|
||||
// Check that original order is maintained with dupes
|
||||
expect(mockScope.displayRows[2].col3.text).toEqual('row6c col3');
|
||||
expect(mockScope.displayRows[3].col3.text).toEqual('row6b col3');
|
||||
expect(mockScope.displayRows[4].col3.text).toEqual('row6 col3');
|
||||
});
|
||||
|
||||
it('Inserts duplicate values for sort column in order received when sorted ascending', function () {
|
||||
mockScope.sortColumn = 'col2';
|
||||
mockScope.sortDirection = 'asc';
|
||||
|
||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||
|
||||
var row6b = {
|
||||
'col1': {'text': 'row6b col1'},
|
||||
'col2': {'text': 'ggg'},
|
||||
'col3': {'text': 'row6b col3'}
|
||||
};
|
||||
var row6c = {
|
||||
'col1': {'text': 'row6c col1'},
|
||||
'col2': {'text': 'ggg'},
|
||||
'col3': {'text': 'row6c col3'}
|
||||
};
|
||||
|
||||
controller.addRows(undefined, [row4, row5, row6]);
|
||||
controller.addRows(undefined, [row6b, row6c]);
|
||||
expect(mockScope.displayRows[0].col2.text).toEqual('aaa');
|
||||
expect(mockScope.displayRows[7].col2.text).toEqual('xyz');
|
||||
|
||||
// Added duplicate rows
|
||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
||||
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
||||
expect(mockScope.displayRows[5].col2.text).toEqual('ggg');
|
||||
// Check that original order is maintained with dupes
|
||||
expect(mockScope.displayRows[3].col3.text).toEqual('row6 col3');
|
||||
expect(mockScope.displayRows[4].col3.text).toEqual('row6b col3');
|
||||
expect(mockScope.displayRows[5].col3.text).toEqual('row6c col3');
|
||||
});
|
||||
|
||||
it('Adds new rows at the correct sort position when' +
|
||||
' sorted and filtered', function () {
|
||||
mockScope.sortColumn = 'col2';
|
||||
mockScope.sortDirection = 'desc';
|
||||
mockScope.filters = {'col2': 'a'};//Include only
|
||||
// rows with 'a'
|
||||
|
||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||
mockScope.displayRows = controller.filterRows(testRows);
|
||||
|
||||
controller.addRows(undefined, [row5]);
|
||||
expect(mockScope.displayRows.length).toBe(2);
|
||||
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
|
||||
|
||||
controller.addRows(undefined, [row6]);
|
||||
expect(mockScope.displayRows.length).toBe(2);
|
||||
//Row was not added because does not match filter
|
||||
});
|
||||
|
||||
it('Adds new rows at the correct sort position when' +
|
||||
' not sorted ', function () {
|
||||
mockScope.sortColumn = undefined;
|
||||
mockScope.sortDirection = undefined;
|
||||
mockScope.filters = {};
|
||||
|
||||
mockScope.displayRows = testRows.slice(0);
|
||||
|
||||
controller.addRows(undefined, [row5]);
|
||||
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
|
||||
|
||||
controller.addRows(undefined, [row6]);
|
||||
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
||||
});
|
||||
|
||||
it('Resizes columns if length of any columns in new' +
|
||||
' row exceeds corresponding existing column', function () {
|
||||
var row7 = {
|
||||
'col1': {'text': 'row6 col1'},
|
||||
'col2': {'text': 'some longer string'},
|
||||
'col3': {'text': 'row6 col3'}
|
||||
};
|
||||
|
||||
mockScope.sortColumn = undefined;
|
||||
mockScope.sortDirection = undefined;
|
||||
mockScope.filters = {};
|
||||
|
||||
mockScope.displayRows = testRows.slice(0);
|
||||
|
||||
controller.addRows(undefined, [row7]);
|
||||
expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,113 +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/controllers/TableOptionsController"
|
||||
],
|
||||
function (TableOptionsController) {
|
||||
|
||||
describe('The Table Options Controller', function () {
|
||||
var mockDomainObject,
|
||||
mockCapability,
|
||||
controller,
|
||||
mockScope;
|
||||
|
||||
beforeEach(function () {
|
||||
mockCapability = jasmine.createSpyObj('mutationCapability', [
|
||||
'listen'
|
||||
]);
|
||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
||||
'getCapability',
|
||||
'getModel'
|
||||
]);
|
||||
mockDomainObject.getCapability.and.returnValue(mockCapability);
|
||||
mockDomainObject.getModel.and.returnValue({});
|
||||
|
||||
mockScope = jasmine.createSpyObj('scope', [
|
||||
'$watchCollection',
|
||||
'$watch',
|
||||
'$on'
|
||||
]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
controller = new TableOptionsController(mockScope);
|
||||
});
|
||||
|
||||
it('Listens for changing domain object', function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('On destruction of controller, destroys listeners', function () {
|
||||
var unlistenFunc = jasmine.createSpy("unlisten");
|
||||
controller.listeners.push(unlistenFunc);
|
||||
expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
||||
mockScope.$on.calls.mostRecent().args[1]();
|
||||
expect(unlistenFunc).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Registers a listener for mutation events on the object', function () {
|
||||
mockScope.$watch.calls.mostRecent().args[1](mockDomainObject);
|
||||
expect(mockCapability.listen).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Listens for changes to object composition and updates' +
|
||||
' options accordingly', function () {
|
||||
expect(mockScope.$watchCollection).toHaveBeenCalledWith('configuration.table.columns', jasmine.any(Function));
|
||||
});
|
||||
|
||||
describe('Populates scope with a form definition based on provided' +
|
||||
' column configuration', function () {
|
||||
var mockModel;
|
||||
|
||||
beforeEach(function () {
|
||||
mockModel = {
|
||||
configuration: {
|
||||
table: {
|
||||
columns: {
|
||||
'column1': true,
|
||||
'column2': true,
|
||||
'column3': false,
|
||||
'column4': true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
controller.populateForm(mockModel);
|
||||
});
|
||||
|
||||
it('creates form on scope', function () {
|
||||
expect(mockScope.columnsForm).toBeDefined();
|
||||
expect(mockScope.columnsForm.sections[0]).toBeDefined();
|
||||
expect(mockScope.columnsForm.sections[0].rows).toBeDefined();
|
||||
expect(mockScope.columnsForm.sections[0].rows.length).toBe(4);
|
||||
});
|
||||
|
||||
it('presents columns as checkboxes', function () {
|
||||
expect(mockScope.columnsForm.sections[0].rows.every(function (row) {
|
||||
return row.control === 'checkbox';
|
||||
})).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -1,417 +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/controllers/TelemetryTableController',
|
||||
'../../../../../src/api/objects/object-utils',
|
||||
'lodash'
|
||||
],
|
||||
function (TelemetryTableController, objectUtils, _) {
|
||||
|
||||
describe('The TelemetryTableController', function () {
|
||||
|
||||
var controller,
|
||||
mockScope,
|
||||
mockTimeout,
|
||||
mockConductor,
|
||||
mockAPI,
|
||||
mockDomainObject,
|
||||
mockTelemetryAPI,
|
||||
mockObjectAPI,
|
||||
mockCompositionAPI,
|
||||
unobserve,
|
||||
mockBounds;
|
||||
|
||||
function getCallback(target, event) {
|
||||
return target.calls.all().filter(function (call) {
|
||||
return call.args[0] === event;
|
||||
})[0].args[1];
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockBounds = {
|
||||
start: 0,
|
||||
end: 10
|
||||
};
|
||||
mockConductor = jasmine.createSpyObj("conductor", [
|
||||
"bounds",
|
||||
"clock",
|
||||
"on",
|
||||
"off",
|
||||
"timeSystem"
|
||||
]);
|
||||
mockConductor.bounds.and.returnValue(mockBounds);
|
||||
mockConductor.clock.and.returnValue(undefined);
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj("domainObject", [
|
||||
"getModel",
|
||||
"getId",
|
||||
"useCapability",
|
||||
"hasCapability"
|
||||
]);
|
||||
mockDomainObject.getModel.and.returnValue({});
|
||||
mockDomainObject.getId.and.returnValue("mockId");
|
||||
mockDomainObject.useCapability.and.returnValue(true);
|
||||
|
||||
mockCompositionAPI = jasmine.createSpyObj("compositionAPI", [
|
||||
"get"
|
||||
]);
|
||||
|
||||
mockObjectAPI = jasmine.createSpyObj("objectAPI", [
|
||||
"observe",
|
||||
"makeKeyString"
|
||||
]);
|
||||
unobserve = jasmine.createSpy("unobserve");
|
||||
mockObjectAPI.observe.and.returnValue(unobserve);
|
||||
|
||||
mockScope = jasmine.createSpyObj("scope", [
|
||||
"$on",
|
||||
"$watch",
|
||||
"$broadcast"
|
||||
]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
mockTelemetryAPI = jasmine.createSpyObj("telemetryAPI", [
|
||||
"isTelemetryObject",
|
||||
"subscribe",
|
||||
"getMetadata",
|
||||
"commonValuesForHints",
|
||||
"request",
|
||||
"limitEvaluator",
|
||||
"getValueFormatter"
|
||||
]);
|
||||
mockTelemetryAPI.commonValuesForHints.and.returnValue([]);
|
||||
mockTelemetryAPI.request.and.returnValue(Promise.resolve([]));
|
||||
mockTelemetryAPI.getMetadata.and.returnValue({
|
||||
values: function () {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
mockTelemetryAPI.getValueFormatter.and.callFake(function (metadata) {
|
||||
var formatter = jasmine.createSpyObj(
|
||||
'telemetryFormatter:' + metadata.key,
|
||||
[
|
||||
'format',
|
||||
'parse'
|
||||
]
|
||||
);
|
||||
var getter = function (datum) {
|
||||
return datum[metadata.key];
|
||||
};
|
||||
formatter.format.and.callFake(getter);
|
||||
formatter.parse.and.callFake(getter);
|
||||
return formatter;
|
||||
});
|
||||
|
||||
mockTelemetryAPI.isTelemetryObject.and.returnValue(false);
|
||||
|
||||
mockTimeout = jasmine.createSpy("timeout");
|
||||
mockTimeout.and.returnValue(1); // Return something
|
||||
mockTimeout.cancel = jasmine.createSpy("cancel");
|
||||
|
||||
mockAPI = {
|
||||
time: mockConductor,
|
||||
objects: mockObjectAPI,
|
||||
telemetry: mockTelemetryAPI,
|
||||
composition: mockCompositionAPI
|
||||
};
|
||||
controller = new TelemetryTableController(mockScope, mockTimeout, mockAPI);
|
||||
});
|
||||
|
||||
describe('listens for', function () {
|
||||
beforeEach(function () {
|
||||
controller.registerChangeListeners();
|
||||
});
|
||||
it('object mutation', function () {
|
||||
var calledObject = mockObjectAPI.observe.calls.mostRecent().args[0];
|
||||
|
||||
expect(mockObjectAPI.observe).toHaveBeenCalled();
|
||||
expect(calledObject.identifier.key).toEqual(mockDomainObject.getId());
|
||||
});
|
||||
it('conductor changes', function () {
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", jasmine.any(Function));
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("bounds", jasmine.any(Function));
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("clock", jasmine.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe('deregisters all listeners on scope destruction', function () {
|
||||
var timeSystemListener,
|
||||
boundsListener,
|
||||
clockListener;
|
||||
|
||||
beforeEach(function () {
|
||||
controller.registerChangeListeners();
|
||||
|
||||
timeSystemListener = getCallback(mockConductor.on, "timeSystem");
|
||||
boundsListener = getCallback(mockConductor.on, "bounds");
|
||||
clockListener = getCallback(mockConductor.on, "clock");
|
||||
|
||||
var destroy = getCallback(mockScope.$on, "$destroy");
|
||||
destroy();
|
||||
});
|
||||
|
||||
it('object mutation', function () {
|
||||
expect(unobserve).toHaveBeenCalled();
|
||||
});
|
||||
it('conductor changes', function () {
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", timeSystemListener);
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", boundsListener);
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("clock", clockListener);
|
||||
});
|
||||
});
|
||||
|
||||
describe ('when getting telemetry', function () {
|
||||
var mockComposition,
|
||||
mockTelemetryObject,
|
||||
mockChildren,
|
||||
unsubscribe;
|
||||
|
||||
beforeEach(function () {
|
||||
mockComposition = jasmine.createSpyObj("composition", [
|
||||
"load"
|
||||
]);
|
||||
|
||||
mockTelemetryObject = {};
|
||||
mockTelemetryObject.identifier = {
|
||||
key: "mockTelemetryObject"
|
||||
};
|
||||
|
||||
unsubscribe = jasmine.createSpy("unsubscribe");
|
||||
mockTelemetryAPI.subscribe.and.returnValue(unsubscribe);
|
||||
|
||||
mockChildren = [mockTelemetryObject];
|
||||
mockComposition.load.and.returnValue(Promise.resolve(mockChildren));
|
||||
mockCompositionAPI.get.and.returnValue(mockComposition);
|
||||
|
||||
mockTelemetryAPI.isTelemetryObject.and.callFake(function (obj) {
|
||||
return obj.identifier.key === mockTelemetryObject.identifier.key;
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches historical data for the time period specified by the conductor bounds', function () {
|
||||
return controller.getData().then(function () {
|
||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds);
|
||||
});
|
||||
});
|
||||
|
||||
it('unsubscribes on view destruction', function () {
|
||||
return controller.getData().then(function () {
|
||||
var destroy = getCallback(mockScope.$on, "$destroy");
|
||||
destroy();
|
||||
|
||||
expect(unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it('fetches historical data for the time period specified by the conductor bounds', function () {
|
||||
return controller.getData().then(function () {
|
||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds);
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches data for, and subscribes to parent object if it is a telemetry object', function () {
|
||||
return controller.getData().then(function () {
|
||||
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
|
||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
|
||||
});
|
||||
});
|
||||
it('fetches data for, and subscribes to parent object if it is a telemetry object', function () {
|
||||
return controller.getData().then(function () {
|
||||
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
|
||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches data for, and subscribes to any composees that are telemetry objects if parent is not', function () {
|
||||
mockChildren = [
|
||||
{name: "child 1"}
|
||||
];
|
||||
var mockTelemetryChildren = [
|
||||
{name: "child 2"},
|
||||
{name: "child 3"},
|
||||
{name: "child 4"}
|
||||
];
|
||||
mockChildren = mockChildren.concat(mockTelemetryChildren);
|
||||
mockComposition.load.and.returnValue(Promise.resolve(mockChildren));
|
||||
|
||||
mockTelemetryAPI.isTelemetryObject.and.callFake(function (object) {
|
||||
if (object === mockTelemetryObject) {
|
||||
return false;
|
||||
} else {
|
||||
return mockTelemetryChildren.indexOf(object) !== -1;
|
||||
}
|
||||
});
|
||||
|
||||
return controller.getData().then(function () {
|
||||
mockTelemetryChildren.forEach(function (child) {
|
||||
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(child, jasmine.any(Function), {});
|
||||
});
|
||||
|
||||
mockTelemetryChildren.forEach(function (child) {
|
||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(child, jasmine.any(Object));
|
||||
});
|
||||
|
||||
expect(mockTelemetryAPI.subscribe).not.toHaveBeenCalledWith(mockChildren[0], jasmine.any(Function), {});
|
||||
expect(mockTelemetryAPI.subscribe).not.toHaveBeenCalledWith(mockTelemetryObject[0], jasmine.any(Function), {});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('When in real-time mode, enables auto-scroll', function () {
|
||||
controller.registerChangeListeners();
|
||||
|
||||
var clockCallback = getCallback(mockConductor.on, "clock");
|
||||
//Confirm pre-condition
|
||||
expect(mockScope.autoScroll).toBeFalsy();
|
||||
|
||||
//Mock setting the a clock in the Time API
|
||||
clockCallback({});
|
||||
expect(mockScope.autoScroll).toBe(true);
|
||||
});
|
||||
|
||||
describe('populates table columns', function () {
|
||||
var allMetadata;
|
||||
var mockTimeSystem1;
|
||||
var mockTimeSystem2;
|
||||
|
||||
beforeEach(function () {
|
||||
allMetadata = [{
|
||||
key: "column1",
|
||||
name: "Column 1",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "column2",
|
||||
name: "Column 2",
|
||||
hints: {
|
||||
domain: 2
|
||||
}
|
||||
}, {
|
||||
key: "column3",
|
||||
name: "Column 3",
|
||||
hints: {}
|
||||
}];
|
||||
|
||||
mockTimeSystem1 = {
|
||||
key: "column1"
|
||||
};
|
||||
mockTimeSystem2 = {
|
||||
key: "column2"
|
||||
};
|
||||
|
||||
mockConductor.timeSystem.and.returnValue(mockTimeSystem1);
|
||||
|
||||
mockTelemetryAPI.getMetadata.and.returnValue({
|
||||
values: function () {
|
||||
return allMetadata;
|
||||
}
|
||||
});
|
||||
|
||||
controller.loadColumns([mockDomainObject]);
|
||||
});
|
||||
|
||||
it('based on metadata for given objects', function () {
|
||||
expect(mockScope.headers).toBeDefined();
|
||||
expect(mockScope.headers.length).toBeGreaterThan(0);
|
||||
expect(mockScope.headers.indexOf(allMetadata[0].name)).not.toBe(-1);
|
||||
expect(mockScope.headers.indexOf(allMetadata[1].name)).not.toBe(-1);
|
||||
expect(mockScope.headers.indexOf(allMetadata[2].name)).not.toBe(-1);
|
||||
});
|
||||
|
||||
it('and sorts by column matching time system', function () {
|
||||
expect(mockScope.defaultSort).toEqual("Column 1");
|
||||
|
||||
mockConductor.timeSystem.and.returnValue(mockTimeSystem2);
|
||||
controller.sortByTimeSystem();
|
||||
|
||||
expect(mockScope.defaultSort).toEqual("Column 2");
|
||||
});
|
||||
|
||||
it('batches processing of rows for performance when receiving historical telemetry', function () {
|
||||
var mockHistoricalData = [
|
||||
{
|
||||
"column1": 1,
|
||||
"column2": 2,
|
||||
"column3": 3
|
||||
},{
|
||||
"column1": 4,
|
||||
"column2": 5,
|
||||
"column3": 6
|
||||
}, {
|
||||
"column1": 7,
|
||||
"column2": 8,
|
||||
"column3": 9
|
||||
}
|
||||
];
|
||||
|
||||
controller.batchSize = 2;
|
||||
mockTelemetryAPI.request.and.returnValue(Promise.resolve(mockHistoricalData));
|
||||
controller.getHistoricalData([mockDomainObject]);
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
mockTimeout.and.callFake(function () {
|
||||
resolve();
|
||||
});
|
||||
}).then(function () {
|
||||
mockTimeout.calls.mostRecent().args[0]();
|
||||
expect(mockTimeout.calls.count()).toBe(2);
|
||||
mockTimeout.calls.mostRecent().args[0]();
|
||||
expect(mockScope.rows.length).toBe(3);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Removes telemetry rows from table when they fall out of bounds', function () {
|
||||
var discardedRows = [
|
||||
{"column1": "value 1"},
|
||||
{"column2": "value 2"},
|
||||
{"column3": "value 3"}
|
||||
];
|
||||
|
||||
spyOn(controller.telemetry, "on").and.callThrough();
|
||||
|
||||
controller.registerChangeListeners();
|
||||
expect(controller.telemetry.on).toHaveBeenCalledWith("discarded", jasmine.any(Function));
|
||||
var onDiscard = getCallback(controller.telemetry.on, "discarded");
|
||||
onDiscard(discardedRows);
|
||||
expect(mockScope.$broadcast).toHaveBeenCalledWith("remove:rows", discardedRows);
|
||||
});
|
||||
|
||||
describe('when telemetry is added', function () {
|
||||
var testRows;
|
||||
|
||||
beforeEach(function () {
|
||||
testRows = [{ a: 0 }, { a: 1 }, { a: 2 }];
|
||||
|
||||
controller.registerChangeListeners();
|
||||
controller.telemetry.add(testRows);
|
||||
});
|
||||
|
||||
it("Adds the rows to the MCTTable directive", function () {
|
||||
expect(mockScope.$broadcast).toHaveBeenCalledWith("add:rows", testRows);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -93,7 +93,7 @@ define([
|
||||
|
||||
// Initialize the application
|
||||
$log.info("Initializing application.");
|
||||
initializer.runApplication();
|
||||
return initializer.runApplication();
|
||||
};
|
||||
|
||||
return FrameworkLayer;
|
||||
|
@ -52,10 +52,7 @@ define(
|
||||
return match ? match[1] : "";
|
||||
}
|
||||
|
||||
|
||||
// Reconfigure base url, since bundle paths will all be relative
|
||||
// to the root now.
|
||||
injector.instantiate(['$http', '$log', FrameworkLayer])
|
||||
return injector.instantiate(['$http', '$log', FrameworkLayer])
|
||||
.initializeApplication(angular, legacyRegistry, logLevel());
|
||||
};
|
||||
|
||||
|
@ -55,9 +55,12 @@ define(
|
||||
var angular = this.angular,
|
||||
document = this.document,
|
||||
$log = this.$log;
|
||||
$log.info("Bootstrapping application " + (app || {}).name);
|
||||
angular.element(document).ready(function () {
|
||||
angular.bootstrap(document, [app.name], { strictDi: true });
|
||||
return new Promise(function (resolve, reject) {
|
||||
$log.info("Bootstrapping application " + (app || {}).name);
|
||||
angular.element(document).ready(function () {
|
||||
angular.bootstrap(document, [app.name], { strictDi: true });
|
||||
resolve(angular);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -44,6 +44,7 @@ define(function () {
|
||||
function SearchController($scope, searchService) {
|
||||
var controller = this;
|
||||
this.$scope = $scope;
|
||||
this.$scope.ngModel = this.$scope.ngModel || {};
|
||||
this.searchService = searchService;
|
||||
this.numberToDisplay = this.RESULTS_PER_PAGE;
|
||||
this.availabileResults = 0;
|
||||
|
82
src/MCT.js
82
src/MCT.js
@ -24,28 +24,44 @@ define([
|
||||
'EventEmitter',
|
||||
'legacyRegistry',
|
||||
'uuid',
|
||||
'./defaultRegistry',
|
||||
'./api/api',
|
||||
'./selection/Selection',
|
||||
'./api/objects/object-utils',
|
||||
'./plugins/plugins',
|
||||
'./ui/ViewRegistry',
|
||||
'./ui/InspectorViewRegistry',
|
||||
'./ui/ToolbarRegistry',
|
||||
'./adapter/indicators/legacy-indicators-plugin',
|
||||
'./styles/core.scss'
|
||||
'./plugins/buildInfo/plugin',
|
||||
'./ui/registries/ViewRegistry',
|
||||
'./ui/registries/InspectorViewRegistry',
|
||||
'./ui/registries/ToolbarRegistry',
|
||||
'./ui/router/ApplicationRouter',
|
||||
'./ui/router/Browse',
|
||||
'../platform/framework/src/Main',
|
||||
'./styles-new/core.scss',
|
||||
'./styles-new/notebook.scss',
|
||||
'./ui/components/layout/Layout.vue',
|
||||
'vue'
|
||||
], function (
|
||||
EventEmitter,
|
||||
legacyRegistry,
|
||||
uuid,
|
||||
defaultRegistry,
|
||||
api,
|
||||
Selection,
|
||||
objectUtils,
|
||||
plugins,
|
||||
LegacyIndicatorsPlugin,
|
||||
buildInfoPlugin,
|
||||
ViewRegistry,
|
||||
InspectorViewRegistry,
|
||||
ToolbarRegistry,
|
||||
LegacyIndicatorsPlugin,
|
||||
coreStyles
|
||||
ApplicationRouter,
|
||||
Browse,
|
||||
Main,
|
||||
coreStyles,
|
||||
NotebookStyles,
|
||||
Layout,
|
||||
Vue
|
||||
) {
|
||||
/**
|
||||
* Open MCT is an extensible web application for building mission
|
||||
@ -207,6 +223,14 @@ define([
|
||||
|
||||
this.Dialog = api.Dialog;
|
||||
|
||||
this.legacyRegistry = defaultRegistry;
|
||||
this.install(this.plugins.Plot());
|
||||
this.install(this.plugins.TelemetryTable());
|
||||
|
||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||
this.install(buildInfoPlugin(BUILD_CONSTANTS));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||
@ -245,11 +269,6 @@ define([
|
||||
domElement = document.body;
|
||||
}
|
||||
|
||||
var appDiv = document.createElement('div');
|
||||
appDiv.setAttribute('ng-view', '');
|
||||
appDiv.className = 'user-environ';
|
||||
domElement.appendChild(appDiv);
|
||||
|
||||
this.legacyExtension('runs', {
|
||||
depends: ['navigationService'],
|
||||
implementation: function (navigationService) {
|
||||
@ -258,6 +277,7 @@ define([
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
// TODO: remove with legacy types.
|
||||
this.types.listKeys().forEach(function (typeKey) {
|
||||
var type = this.types.get(typeKey);
|
||||
var legacyDefinition = type.toLegacyDefinition();
|
||||
@ -265,32 +285,44 @@ define([
|
||||
this.legacyExtension('types', legacyDefinition);
|
||||
}.bind(this));
|
||||
|
||||
this.objectViews.getAllProviders().forEach(function (p) {
|
||||
this.legacyExtension('views', {
|
||||
key: p.key,
|
||||
provider: p,
|
||||
name: p.name,
|
||||
cssClass: p.cssClass,
|
||||
description: p.description,
|
||||
editable: p.editable,
|
||||
template: '<mct-view mct-provider-key="' + p.key + '"/>'
|
||||
});
|
||||
}, this);
|
||||
|
||||
legacyRegistry.register('adapter', this.legacyBundle);
|
||||
legacyRegistry.enable('adapter');
|
||||
|
||||
this.install(LegacyIndicatorsPlugin());
|
||||
|
||||
this.router = new ApplicationRouter();
|
||||
|
||||
this.router.route(/^\/$/, () => {
|
||||
this.router.setPath('/browse/mine');
|
||||
});
|
||||
|
||||
/**
|
||||
* Fired by [MCT]{@link module:openmct.MCT} when the application
|
||||
* is started.
|
||||
* @event start
|
||||
* @memberof module:openmct.MCT~
|
||||
*/
|
||||
this.emit('start');
|
||||
};
|
||||
var startPromise = new Main().run(this.legacyRegistry)
|
||||
.then(function (angular) {
|
||||
this.$angular = angular;
|
||||
// OpenMCT Object provider doesn't operate properly unless
|
||||
// something has depended upon objectService. Cool, right?
|
||||
this.$injector.get('objectService');
|
||||
|
||||
var appLayout = new Vue({
|
||||
mixins: [Layout.default],
|
||||
provide: {
|
||||
openmct: this
|
||||
}
|
||||
});
|
||||
domElement.appendChild(appLayout.$mount().$el);
|
||||
|
||||
this.layout = appLayout;
|
||||
Browse(this);
|
||||
this.router.start();
|
||||
this.emit('start');
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Install a plugin in MCT.
|
||||
|
@ -34,7 +34,9 @@ define([
|
||||
'./runs/TimeSettingsURLHandler',
|
||||
'./runs/TypeDeprecationChecker',
|
||||
'./runs/LegacyTelemetryProvider',
|
||||
'./services/LegacyObjectAPIInterceptor'
|
||||
'./runs/RegisterLegacyTypes',
|
||||
'./services/LegacyObjectAPIInterceptor',
|
||||
'./views/installLegacyViews'
|
||||
], function (
|
||||
legacyRegistry,
|
||||
ActionDialogDecorator,
|
||||
@ -49,7 +51,9 @@ define([
|
||||
TimeSettingsURLHandler,
|
||||
TypeDeprecationChecker,
|
||||
LegacyTelemetryProvider,
|
||||
LegacyObjectAPIInterceptor
|
||||
RegisterLegacyTypes,
|
||||
LegacyObjectAPIInterceptor,
|
||||
installLegacyViews
|
||||
) {
|
||||
legacyRegistry.register('src/adapter', {
|
||||
"extensions": {
|
||||
@ -149,6 +153,21 @@ define([
|
||||
"openmct",
|
||||
"instantiate"
|
||||
]
|
||||
},
|
||||
{
|
||||
implementation: installLegacyViews,
|
||||
depends: [
|
||||
"openmct",
|
||||
"views[]",
|
||||
"instantiate"
|
||||
]
|
||||
},
|
||||
{
|
||||
implementation: RegisterLegacyTypes,
|
||||
depends: [
|
||||
"types[]",
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
],
|
||||
licenses: [
|
||||
|
17
src/adapter/runs/RegisterLegacyTypes.js
Normal file
17
src/adapter/runs/RegisterLegacyTypes.js
Normal file
@ -0,0 +1,17 @@
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
function RegisterLegacyTypes(types, openmct) {
|
||||
types.forEach(function (legacyDefinition) {
|
||||
if (!openmct.types.get(legacyDefinition.key)) {
|
||||
console.warn(`DEPRECATION WARNING: Migrate type ${legacyDefinition.key} from ${legacyDefinition.bundle.path} to use the new Types API. Legacy type support will be removed soon.`);
|
||||
}
|
||||
});
|
||||
|
||||
openmct.types.importLegacyTypes(types);
|
||||
}
|
||||
|
||||
return RegisterLegacyTypes;
|
||||
});
|
93
src/adapter/views/LegacyViewProvider.js
Normal file
93
src/adapter/views/LegacyViewProvider.js
Normal file
@ -0,0 +1,93 @@
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
||||
console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs. Legacy view support will be removed soon.`);
|
||||
return {
|
||||
key: legacyView.key,
|
||||
name: legacyView.name,
|
||||
cssClass: legacyView.cssClass,
|
||||
description: legacyView.description,
|
||||
editable: legacyView.editable,
|
||||
canView: function (domainObject) {
|
||||
if (!domainObject || !domainObject.identifier) {
|
||||
return false;
|
||||
}
|
||||
if (legacyView.type) {
|
||||
return domainObject.type === legacyView.type;
|
||||
}
|
||||
let legacyObject = convertToLegacyObject(domainObject);
|
||||
if (legacyView.needs) {
|
||||
let meetsNeeds = legacyView.needs.every(k => legacyObject.hasCapability(k));
|
||||
if (!meetsNeeds) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return openmct.$injector.get('policyService').allow(
|
||||
'view', legacyView, legacyObject
|
||||
);
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let $rootScope = openmct.$injector.get('$rootScope');
|
||||
let templateLinker = openmct.$injector.get('templateLinker');
|
||||
let scope = $rootScope.$new();
|
||||
let legacyObject = convertToLegacyObject(domainObject);
|
||||
let isDestroyed = false;
|
||||
scope.domainObject = legacyObject;
|
||||
scope.model = legacyObject.getModel();
|
||||
|
||||
|
||||
return {
|
||||
show: function (container) {
|
||||
// TODO: implement "gestures" support ?
|
||||
let uses = legacyView.uses || [];
|
||||
let promises = [];
|
||||
let results = uses.map(function (capabilityKey, i) {
|
||||
let result = legacyObject.useCapability(capabilityKey);
|
||||
if (result.then) {
|
||||
promises.push(result.then(function (r) {
|
||||
results[i] = r;
|
||||
}));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
function link() {
|
||||
if (isDestroyed) {
|
||||
return;
|
||||
}
|
||||
uses.forEach(function (key, i) {
|
||||
scope[key] = results[i];
|
||||
});
|
||||
templateLinker.link(
|
||||
scope,
|
||||
openmct.$angular.element(container),
|
||||
legacyView
|
||||
);
|
||||
container.style.height = '100%';
|
||||
}
|
||||
|
||||
if (promises.length) {
|
||||
Promise.all(promises)
|
||||
.then(function () {
|
||||
link();
|
||||
scope.$digest();
|
||||
});
|
||||
} else {
|
||||
link();
|
||||
}
|
||||
},
|
||||
destroy: function () {
|
||||
scope.$destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return LegacyViewProvider;
|
||||
|
||||
});
|
22
src/adapter/views/installLegacyViews.js
Normal file
22
src/adapter/views/installLegacyViews.js
Normal file
@ -0,0 +1,22 @@
|
||||
define([
|
||||
'./LegacyViewProvider',
|
||||
'../../api/objects/object-utils'
|
||||
], function (
|
||||
LegacyViewProvider,
|
||||
objectUtils
|
||||
) {
|
||||
function installLegacyViews(openmct, legacyViews, instantiate) {
|
||||
|
||||
function convertToLegacyObject(domainObject) {
|
||||
let keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
let oldModel = objectUtils.toOldFormat(domainObject);
|
||||
return instantiate(oldModel, keyString);
|
||||
}
|
||||
|
||||
legacyViews.forEach(function (legacyView) {
|
||||
openmct.objectViews.addProvider(new LegacyViewProvider(legacyView, openmct, convertToLegacyObject));
|
||||
});
|
||||
}
|
||||
|
||||
return installLegacyViews;
|
||||
});
|
@ -32,6 +32,9 @@ define(function () {
|
||||
*/
|
||||
function Type(definition) {
|
||||
this.definition = definition;
|
||||
if (definition.key) {
|
||||
this.key = definition.key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,5 +73,29 @@ define(function () {
|
||||
return def;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a type definition from a legacy definition.
|
||||
*/
|
||||
Type.definitionFromLegacyDefinition = function (legacyDefinition) {
|
||||
let definition = {};
|
||||
definition.name = legacyDefinition.name;
|
||||
definition.cssClass = legacyDefinition.cssClass;
|
||||
definition.description = legacyDefinition.description;
|
||||
definition.form = legacyDefinition.properties;
|
||||
if (legacyDefinition.model) {
|
||||
definition.initialize = function (model) {
|
||||
for (let [k, v] of Object.entries(legacyDefinition.model)) {
|
||||
model[k] = JSON.parse(JSON.stringify(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (legacyDefinition.features && legacyDefinition.features.includes("creation")) {
|
||||
definition.creatable = true;
|
||||
}
|
||||
|
||||
return definition;
|
||||
};
|
||||
|
||||
return Type;
|
||||
});
|
||||
|
@ -98,6 +98,14 @@ define(['./Type'], function (Type) {
|
||||
return this.types[typeKey];
|
||||
};
|
||||
|
||||
TypeRegistry.prototype.importLegacyTypes = function (types) {
|
||||
types.filter((t) => !this.get(t.key))
|
||||
.forEach((type) => {
|
||||
let def = Type.definitionFromLegacyDefinition(type);
|
||||
this.addType(type.key, def);
|
||||
});
|
||||
}
|
||||
|
||||
return TypeRegistry;
|
||||
});
|
||||
|
||||
|
@ -64,7 +64,6 @@ define([
|
||||
'../platform/features/pages/bundle',
|
||||
'../platform/features/hyperlink/bundle',
|
||||
'../platform/features/static-markup/bundle',
|
||||
'../platform/features/table/bundle',
|
||||
'../platform/features/timeline/bundle',
|
||||
'../platform/forms/bundle',
|
||||
'../platform/framework/bundle',
|
||||
@ -108,7 +107,6 @@ define([
|
||||
'platform/features/pages',
|
||||
'platform/features/hyperlink',
|
||||
'platform/features/timeline',
|
||||
'platform/features/table',
|
||||
'platform/forms',
|
||||
'platform/identity',
|
||||
'platform/persistence/aggregator',
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
@ -20,31 +20,18 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import CSV from 'comma-separated-values';
|
||||
import {saveAs} from 'file-saver/FileSaver';
|
||||
|
||||
define(
|
||||
function () {
|
||||
|
||||
/**
|
||||
* The notebook capability allows a domain object to know whether the
|
||||
* notebook plugin is present or not.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function NotebookCapability(typeService, domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.typeService = typeService;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a notebook domain Object.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
NotebookCapability.prototype.isNotebook = function () {
|
||||
return !!this.typeService.getType('notebook');
|
||||
};
|
||||
|
||||
return NotebookCapability;
|
||||
class CSVExporter {
|
||||
export(rows, options) {
|
||||
let headers = (options && options.headers) ||
|
||||
(Object.keys((rows[0] || {})).sort());
|
||||
let filename = (options && options.filename) || "export.csv";
|
||||
let csvText = new CSV(rows, { header: headers }).encode();
|
||||
let blob = new Blob([csvText], { type: "text/csv" });
|
||||
saveAs(blob, filename);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default CSVExporter;
|
@ -23,7 +23,7 @@
|
||||
define([
|
||||
'./AutoflowTabularController',
|
||||
'./AutoflowTabularConstants',
|
||||
'../../ui/VueView',
|
||||
'./VueView',
|
||||
'./autoflow-tabular.html'
|
||||
], function (
|
||||
AutoflowTabularController,
|
||||
|
215
src/plugins/notebook/plugin.js
Normal file
215
src/plugins/notebook/plugin.js
Normal file
@ -0,0 +1,215 @@
|
||||
/*****************************************************************************
|
||||
* 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/controllers/NotebookController",
|
||||
"./src/controllers/NewEntryController",
|
||||
"./src/controllers/SelectSnapshotController",
|
||||
"./src/actions/NewEntryContextual",
|
||||
"./src/actions/AnnotateSnapshot",
|
||||
"./src/directives/MCTSnapshot",
|
||||
"./src/directives/EntryDnd",
|
||||
"./res/templates/controls/snapSelect.html",
|
||||
"./res/templates/controls/embedControl.html",
|
||||
"./res/templates/annotation.html",
|
||||
"./res/templates/draggedEntry.html"
|
||||
], function (
|
||||
NotebookController,
|
||||
NewEntryController,
|
||||
SelectSnapshotController,
|
||||
newEntryAction,
|
||||
AnnotateSnapshotAction,
|
||||
MCTSnapshotDirective,
|
||||
EntryDndDirective,
|
||||
snapSelectTemplate,
|
||||
embedControlTemplate,
|
||||
annotationTemplate,
|
||||
draggedEntryTemplate
|
||||
) {
|
||||
var installed = false;
|
||||
|
||||
function NotebookPlugin() {
|
||||
return function install(openmct) {
|
||||
if (installed) {
|
||||
return;
|
||||
}
|
||||
|
||||
installed = true;
|
||||
|
||||
openmct.legacyRegistry.register('notebook', {
|
||||
name: 'Notebook Plugin',
|
||||
extensions: {
|
||||
types: [
|
||||
{
|
||||
key: 'notebook',
|
||||
name: 'Notebook',
|
||||
cssClass: 'icon-notebook',
|
||||
description: 'Create and save timestamped notes with embedded object snapshots.',
|
||||
features: 'creation',
|
||||
model: {
|
||||
entries: [],
|
||||
composition: [],
|
||||
entryTypes: [],
|
||||
defaultSort: '-createdOn'
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
key: 'defaultSort',
|
||||
name: 'Default Sort',
|
||||
control: 'select',
|
||||
options: [
|
||||
{
|
||||
name: 'Newest First',
|
||||
value: "-createdOn",
|
||||
},
|
||||
{
|
||||
name: 'Oldest First',
|
||||
value: "createdOn"
|
||||
}
|
||||
],
|
||||
cssClass: 'l-inline'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
"key": "notebook-new-entry",
|
||||
"implementation": newEntryAction,
|
||||
"name": "New Notebook Entry",
|
||||
"cssClass": "icon-notebook labeled",
|
||||
"description": "Add a new Notebook entry",
|
||||
"category": [
|
||||
"view-control"
|
||||
],
|
||||
"depends": [
|
||||
"$compile",
|
||||
"$rootScope",
|
||||
"dialogService",
|
||||
"notificationService",
|
||||
"linkService"
|
||||
],
|
||||
"priority": "preferred"
|
||||
},
|
||||
{
|
||||
"key": "annotate-snapshot",
|
||||
"implementation": AnnotateSnapshotAction,
|
||||
"name": "Annotate Snapshot",
|
||||
"cssClass": "icon-pencil labeled",
|
||||
"description": "Annotate embed's snapshot",
|
||||
"category": "embed",
|
||||
"depends": [
|
||||
"dialogService",
|
||||
"dndService",
|
||||
"$rootScope"
|
||||
]
|
||||
}
|
||||
],
|
||||
controllers: [
|
||||
{
|
||||
"key": "NewEntryController",
|
||||
"implementation": NewEntryController,
|
||||
"depends": ["$scope",
|
||||
"$rootScope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "selectSnapshotController",
|
||||
"implementation": SelectSnapshotController,
|
||||
"depends": ["$scope",
|
||||
"$rootScope"
|
||||
]
|
||||
}
|
||||
],
|
||||
controls: [
|
||||
{
|
||||
"key": "snapshot-select",
|
||||
"template": snapSelectTemplate
|
||||
},
|
||||
{
|
||||
"key": "embed-control",
|
||||
"template": embedControlTemplate
|
||||
}
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
"key": "annotate-snapshot",
|
||||
"template": annotationTemplate
|
||||
}
|
||||
],
|
||||
directives: [
|
||||
{
|
||||
"key": "mctSnapshot",
|
||||
"implementation": MCTSnapshotDirective,
|
||||
"depends": [
|
||||
"$rootScope",
|
||||
"$document",
|
||||
"exportImageService",
|
||||
"dialogService",
|
||||
"notificationService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctEntryDnd",
|
||||
"implementation": EntryDndDirective,
|
||||
"depends": [
|
||||
"$rootScope",
|
||||
"$compile",
|
||||
"dndService",
|
||||
"typeService",
|
||||
"notificationService"
|
||||
]
|
||||
}
|
||||
],
|
||||
representations: [
|
||||
{
|
||||
"key": "draggedEntry",
|
||||
"template": draggedEntryTemplate
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
openmct.legacyRegistry.enable('notebook');
|
||||
|
||||
openmct.objectViews.addProvider({
|
||||
key: 'notebook-vue',
|
||||
name: 'Notebook View',
|
||||
cssClass: 'icon-notebook',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'notebook';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
var controller = new NotebookController (openmct, domainObject);
|
||||
|
||||
return {
|
||||
show: controller.show,
|
||||
destroy: controller.destroy
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return NotebookPlugin;
|
||||
});
|
@ -25,6 +25,5 @@
|
||||
ng-options="opt.value as opt.name for opt in options"
|
||||
ng-required="ngRequired"
|
||||
name="mctControl">
|
||||
<!-- <option value="" ng-show="!ngModel[field]">- Select One -</option> -->
|
||||
</select>
|
||||
</div>
|
32
src/plugins/notebook/res/templates/embed.html
Normal file
32
src/plugins/notebook/res/templates/embed.html
Normal file
@ -0,0 +1,32 @@
|
||||
<div class="c-ne__embed">
|
||||
<div class="c-ne__embed__snap-thumb"
|
||||
v-if="embed.snapshot"
|
||||
v-on:click="openSnapshot">
|
||||
<img v-bind:src="embed.snapshot.src">
|
||||
</div>
|
||||
<div class="c-ne__embed__info">
|
||||
<div class="c-ne__embed__name">
|
||||
<a class="c-ne__embed__link"
|
||||
v-on:click="navigate(embed.type)"
|
||||
v-bind:class="[embed.cssClass]">{{embed.name}}</a>
|
||||
<a class="c-ne__embed__context-available icon-arrow-down"
|
||||
v-on:click="toggleActionMenu"></a>
|
||||
</div>
|
||||
<div class="hide-menu hidden">
|
||||
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
||||
<div class="menu context-menu">
|
||||
<ul>
|
||||
<li v-for="action in actions"
|
||||
v-bind:class="[action.cssClass]"
|
||||
v-on:click="action.perform(embed, entry)">
|
||||
{{ action.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-ne__embed__time" v-if="embed.snapshot">
|
||||
{{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
35
src/plugins/notebook/res/templates/entry.html
Normal file
35
src/plugins/notebook/res/templates/entry.html
Normal file
@ -0,0 +1,35 @@
|
||||
<li class="c-notebook__entry c-ne has-local-controls"
|
||||
v-on:drop="dropOnEntry(entry.id)"
|
||||
v-on:dragover="dragoverOnEntry"
|
||||
>
|
||||
<div class="c-ne__time-and-content">
|
||||
<div class="c-ne__time">
|
||||
<span>{{formatTime(entry.createdOn, 'YYYY-MM-DD')}}</span>
|
||||
<span>{{formatTime(entry.createdOn, 'HH:mm:ss')}}</span>
|
||||
</div>
|
||||
<div class="c-ne__content">
|
||||
<!-- TODO: fix styling for c-input-inline when SCSS is merged and remove s-input-inline class here -->
|
||||
<div class="c-ne__text c-input-inline s-input-inline"
|
||||
contenteditable="true"
|
||||
ref="contenteditable"
|
||||
v-on:blur="textBlur($event, entry.id)"
|
||||
v-on:focus="textFocus($event, entry.id)"
|
||||
v-bind:key="entry.id"
|
||||
v-html="entry.text">
|
||||
</div>
|
||||
<div class="c-ne__embeds">
|
||||
<notebook-embed
|
||||
v-for="(embed, index) in entry.embeds"
|
||||
v-bind:embed="embed"
|
||||
v-bind:entry="entry"
|
||||
></notebook-embed>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-ne__local-controls--hidden">
|
||||
<a class="c-icon-button icon-trash"
|
||||
title="Delete this entry"
|
||||
v-on:click="deleteEntry"></a>
|
||||
</div>
|
||||
</li>
|
37
src/plugins/notebook/res/templates/notebook.html
Normal file
37
src/plugins/notebook/res/templates/notebook.html
Normal file
@ -0,0 +1,37 @@
|
||||
<div class="c-notebook">
|
||||
<div class="c-notebook__head">
|
||||
<search class="c-notebook__search"
|
||||
v-model="entrySearch"
|
||||
v-on:input="search($event)"
|
||||
v-on:clear="entrySearch = ''; search($event)"></search>
|
||||
<div class="c-notebook__controls">
|
||||
<div class="select c-notebook__controls__time">
|
||||
<select v-model="showTime">
|
||||
<option value="0" selected="selected">Show all</option>
|
||||
<option value="1">Last hour</option>
|
||||
<option value="8">Last 8 hours</option>
|
||||
<option value="24">Last 24 hours</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select c-notebook__controls__sort">
|
||||
<select v-model="sortEntries">
|
||||
<option value="-createdOn" selected="selected">Newest first</option>
|
||||
<option value="createdOn">Oldest first</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-notebook__drag-area icon-plus"
|
||||
v-on:click="newEntry($event)"
|
||||
id="newEntry" mct-entry-dnd>
|
||||
<span class="c-notebook__drag-area__label">To start a new entry, click here or drag and drop any object</span>
|
||||
</div>
|
||||
<div class="c-notebook__entries" ng-mouseover="handleActive()">
|
||||
<ul>
|
||||
<notebook-entry
|
||||
v-for="entry in filterBySearch(entries, entrySearch)"
|
||||
v-bind:entry="entry"
|
||||
></notebook-entry>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
50
src/plugins/notebook/res/templates/viewSnapshot.html
Normal file
50
src/plugins/notebook/res/templates/viewSnapshot.html
Normal file
@ -0,0 +1,50 @@
|
||||
<div class="abs overlay l-large-view">
|
||||
<div class="abs blocker" v-on:click="close"></div>
|
||||
|
||||
<div class="abs outer-holder">
|
||||
|
||||
<a
|
||||
class="close icon-x-in-circle"
|
||||
v-on:click="close">
|
||||
</a>
|
||||
|
||||
<div class="abs inner-holder l-flex-col">
|
||||
<div class="t-contents flex-elem holder grows">
|
||||
|
||||
<div class="t-snapshot abs l-view-header">
|
||||
<div class="abs object-browse-bar l-flex-row">
|
||||
<div class="left flex-elem l-flex-row grows">
|
||||
<div class="object-header flex-elem l-flex-row grows">
|
||||
<div class="type-icon flex-elem embed-icon holder" v-bind:class="embed.cssClass"></div>
|
||||
<div class="title-label flex-elem holder flex-can-shrink">{{embed.name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
|
||||
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
|
||||
</div>
|
||||
<a class="s-button icon-pencil" title="Annotate">
|
||||
<span class="title-label">Annotate</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="abs object-holder t-image-holder s-image-holder">
|
||||
<div
|
||||
class="image-main s-image-main"
|
||||
v-bind:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="bottom-bar flex-elem holder"
|
||||
v-on:click="close">
|
||||
|
||||
<a class="t-done s-button major">Done</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -26,6 +26,7 @@
|
||||
define(
|
||||
["painterro", "zepto"],
|
||||
function (Painterro, $) {
|
||||
|
||||
var annotationStruct = {
|
||||
title: "Annotate Snapshot",
|
||||
template: "annotate-snapshot",
|
||||
@ -107,9 +108,6 @@ define(
|
||||
done(true);
|
||||
}
|
||||
}).show(snapshot);
|
||||
|
||||
$(document.body).find('.ptro-icon-btn').addClass('s-button');
|
||||
$(document.body).find('.ptro-input').addClass('s-button');
|
||||
});
|
||||
}];
|
||||
|
@ -90,7 +90,7 @@ define(
|
||||
var dialogService = this.dialogService;
|
||||
var rootScope = this.$rootScope;
|
||||
rootScope.newEntryText = '';
|
||||
// Create the overlay element and add it to the document's body
|
||||
// // Create the overlay element and add it to the document's body
|
||||
this.$rootScope.selObj = domainObj;
|
||||
this.$rootScope.selValue = "";
|
||||
var newScope = rootScope.$new();
|
||||
@ -187,7 +187,7 @@ define(
|
||||
var domainObject = context.domainObject;
|
||||
|
||||
if (domainObject) {
|
||||
if (domainObject.getModel().type === 'Notebook') {
|
||||
if (domainObject.getModel().type === 'notebook') {
|
||||
// do not allow in context of a notebook
|
||||
return false;
|
||||
} else if (domainObject.getModel().type.includes('imagery')) {
|
130
src/plugins/notebook/src/actions/snapshotAction.js
Normal file
130
src/plugins/notebook/src/actions/snapshotAction.js
Normal file
@ -0,0 +1,130 @@
|
||||
/*****************************************************************************
|
||||
* 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'],
|
||||
function ($) {
|
||||
|
||||
function SnapshotAction (exportImageService, dialogService, context) {
|
||||
this.exportImageService = exportImageService;
|
||||
this.dialogService = dialogService;
|
||||
this.domainObject = context.domainObject;
|
||||
}
|
||||
|
||||
SnapshotAction.prototype.perform = function () {
|
||||
var elementToSnapshot =
|
||||
$(document.body).find(".overlay .object-holder")[0] ||
|
||||
$(document.body).find("[key='representation.selected.key']")[0];
|
||||
|
||||
$(elementToSnapshot).addClass("s-status-taking-snapshot");
|
||||
|
||||
this.exportImageService.exportPNGtoSRC(elementToSnapshot).then(function (blob) {
|
||||
$(elementToSnapshot).removeClass("s-status-taking-snapshot");
|
||||
|
||||
if (blob) {
|
||||
var reader = new window.FileReader();
|
||||
reader.readAsDataURL(blob);
|
||||
reader.onloadend = function () {
|
||||
this.saveSnapshot(reader.result, blob.type, blob.size);
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
SnapshotAction.prototype.saveSnapshot = function (imageURL, imageType, imageSize) {
|
||||
var taskForm = this.generateTaskForm(),
|
||||
domainObject = this.domainObject,
|
||||
domainObjectId = domainObject.getId(),
|
||||
cssClass = domainObject.getCapability('type').typeDef.cssClass,
|
||||
name = domainObject.model.name;
|
||||
|
||||
this.dialogService.getDialogResponse(
|
||||
'overlay-dialog',
|
||||
taskForm,
|
||||
function () {
|
||||
return taskForm.value;
|
||||
}
|
||||
).then(function (options) {
|
||||
var snapshotObject = {
|
||||
src: imageURL,
|
||||
type: imageType,
|
||||
size: imageSize
|
||||
};
|
||||
|
||||
options.notebook.useCapability('mutation', function (model) {
|
||||
var date = Date.now();
|
||||
|
||||
model.entries.push({
|
||||
id: 'entry-' + date,
|
||||
createdOn: date,
|
||||
text: options.entry,
|
||||
embeds: [{
|
||||
name: name,
|
||||
cssClass: cssClass,
|
||||
type: domainObjectId,
|
||||
id: 'embed-' + date,
|
||||
createdOn: date,
|
||||
snapshot: snapshotObject
|
||||
}]
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
SnapshotAction.prototype.generateTaskForm = function () {
|
||||
var taskForm = {
|
||||
name: "Create a Notebook Entry",
|
||||
hint: "Please select a Notebook",
|
||||
sections: [{
|
||||
rows: [{
|
||||
name: 'Entry',
|
||||
key: 'entry',
|
||||
control: 'textarea',
|
||||
required: false,
|
||||
"cssClass": "l-textarea-sm"
|
||||
},
|
||||
{
|
||||
name: 'Save in Notebook',
|
||||
key: 'notebook',
|
||||
control: 'locator',
|
||||
validate: validateLocation
|
||||
}]
|
||||
}]
|
||||
};
|
||||
|
||||
var overlayModel = {
|
||||
title: taskForm.name,
|
||||
message: 'AHAHAH',
|
||||
structure: taskForm,
|
||||
value: {'entry': ""}
|
||||
};
|
||||
|
||||
function validateLocation(newParentObj) {
|
||||
return newParentObj.model.type === 'notebook';
|
||||
}
|
||||
|
||||
return overlayModel;
|
||||
};
|
||||
|
||||
return SnapshotAction;
|
||||
}
|
||||
);
|
198
src/plugins/notebook/src/controllers/EmbedController.js
Normal file
198
src/plugins/notebook/src/controllers/EmbedController.js
Normal file
@ -0,0 +1,198 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'moment',
|
||||
'zepto',
|
||||
'../utils/SnapshotOverlay',
|
||||
],
|
||||
function (
|
||||
Moment,
|
||||
$,
|
||||
SnapshotOverlay
|
||||
) {
|
||||
function EmbedController (openmct, domainObject) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.objectService = openmct.$injector.get('objectService');
|
||||
this.navigationService = openmct.$injector.get('navigationService');
|
||||
this.popupService = openmct.$injector.get('popupService');
|
||||
this.agentService = openmct.$injector.get('agentService');
|
||||
this.dialogService = openmct.$injector.get('dialogService');
|
||||
|
||||
|
||||
this.navigate = this.navigate.bind(this);
|
||||
this.exposedData = this.exposedData.bind(this);
|
||||
this.exposedMethods = this.exposedMethods.bind(this);
|
||||
this.toggleActionMenu = this.toggleActionMenu.bind(this);
|
||||
}
|
||||
|
||||
EmbedController.prototype.navigate = function (embedType) {
|
||||
this.objectService.getObjects([embedType]).then(function (objects) {
|
||||
this.navigationService.setNavigation(objects[embedType]);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
EmbedController.prototype.openSnapshot = function () {
|
||||
if (!this.snapshotOverlay) {
|
||||
this.snapShotOverlay = new SnapshotOverlay(this.embed, this.formatTime);
|
||||
} else {
|
||||
this.snapShotOverlay = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
EmbedController.prototype.formatTime = function (unixTime, timeFormat) {
|
||||
return Moment(unixTime).format(timeFormat);
|
||||
};
|
||||
|
||||
EmbedController.prototype.findInArray = function (array, id) {
|
||||
var foundId = -1;
|
||||
|
||||
array.forEach(function (element, index) {
|
||||
if (element.id === id) {
|
||||
foundId = index;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return foundId;
|
||||
};
|
||||
|
||||
EmbedController.prototype.actionToMenuDecorator = function (action) {
|
||||
return {
|
||||
name: action.getMetadata().name,
|
||||
cssClass: action.getMetadata().cssClass,
|
||||
perform: action.perform
|
||||
};
|
||||
};
|
||||
|
||||
EmbedController.prototype.populateActionMenu = function (objectService, actionService) {
|
||||
return function () {
|
||||
var self = this;
|
||||
|
||||
objectService.getObjects([self.embed.type]).then(function (resp) {
|
||||
var domainObject = resp[self.embed.type],
|
||||
previewAction = actionService.getActions({key: 'mct-preview-action', domainObject: domainObject})[0];
|
||||
|
||||
self.actions.push(self.actionToMenuDecorator(previewAction));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
EmbedController.prototype.removeEmbedAction = function () {
|
||||
var self = this;
|
||||
|
||||
return {
|
||||
name: 'Remove Embed',
|
||||
cssClass: 'icon-trash',
|
||||
perform: function (embed, entry) {
|
||||
var entryPosition = self.findInArray(self.domainObject.entries, entry.id),
|
||||
embedPosition = self.findInArray(entry.embeds, embed.id);
|
||||
|
||||
var warningDialog = self.dialogService.showBlockingMessage({
|
||||
severity: "error",
|
||||
title: "This action will permanently delete this embed. Do you wish to continue?",
|
||||
options: [{
|
||||
label: "OK",
|
||||
callback: function () {
|
||||
entry.embeds.splice(embedPosition, 1);
|
||||
var dirString = 'entries[' + entryPosition + '].embeds';
|
||||
|
||||
self.openmct.objects.mutate(self.domainObject, dirString, entry.embeds);
|
||||
|
||||
warningDialog.dismiss();
|
||||
}
|
||||
},{
|
||||
label: "Cancel",
|
||||
callback: function () {
|
||||
warningDialog.dismiss();
|
||||
}
|
||||
}]
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
EmbedController.prototype.toggleActionMenu = function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
var body = $(document.body),
|
||||
container = $(event.target.parentElement.parentElement),
|
||||
initiatingEvent = this.agentService.isMobile() ?
|
||||
'touchstart' : 'mousedown',
|
||||
menu = container.find('.menu-element'),
|
||||
dismissExistingMenu;
|
||||
|
||||
// Remove the context menu
|
||||
function dismiss() {
|
||||
container.find('.hide-menu').append(menu);
|
||||
body.off(initiatingEvent, dismiss);
|
||||
menu.off(initiatingEvent, menuClickHandler);
|
||||
dismissExistingMenu = undefined;
|
||||
}
|
||||
|
||||
function menuClickHandler(e) {
|
||||
e.stopPropagation();
|
||||
window.setTimeout(dismiss, 300);
|
||||
}
|
||||
|
||||
// Dismiss any menu which was already showing
|
||||
if (dismissExistingMenu) {
|
||||
dismissExistingMenu();
|
||||
}
|
||||
|
||||
// ...and record the presence of this menu.
|
||||
dismissExistingMenu = dismiss;
|
||||
|
||||
this.popupService.display(menu, [event.pageX,event.pageY], {
|
||||
marginX: 0,
|
||||
marginY: -50
|
||||
});
|
||||
|
||||
// Stop propagation so that clicks or touches on the menu do not close the menu
|
||||
menu.on(initiatingEvent, menuClickHandler);
|
||||
|
||||
body.on(initiatingEvent, dismiss);
|
||||
|
||||
};
|
||||
|
||||
EmbedController.prototype.exposedData = function () {
|
||||
return {
|
||||
actions: [this.removeEmbedAction()],
|
||||
showActionMenu: false
|
||||
};
|
||||
};
|
||||
|
||||
EmbedController.prototype.exposedMethods = function () {
|
||||
var self = this;
|
||||
|
||||
return {
|
||||
navigate: self.navigate,
|
||||
openSnapshot: self.openSnapshot,
|
||||
formatTime: self.formatTime,
|
||||
toggleActionMenu: self.toggleActionMenu,
|
||||
actionToMenuDecorator: self.actionToMenuDecorator
|
||||
};
|
||||
};
|
||||
|
||||
return EmbedController;
|
||||
});
|
150
src/plugins/notebook/src/controllers/EntryController.js
Normal file
150
src/plugins/notebook/src/controllers/EntryController.js
Normal file
@ -0,0 +1,150 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'moment'
|
||||
],
|
||||
function (
|
||||
Moment
|
||||
) {
|
||||
|
||||
function EntryController (openmct, domainObject) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.dndService = this.openmct.$injector.get('dndService');
|
||||
this.dialogService = this.openmct.$injector.get('dialogService');
|
||||
|
||||
this.currentEntryValue = '';
|
||||
|
||||
this.exposedMethods = this.exposedMethods.bind(this);
|
||||
this.exposedData = this.exposedData.bind(this);
|
||||
}
|
||||
|
||||
EntryController.prototype.entryPosById = function (entryId) {
|
||||
var foundId = -1;
|
||||
|
||||
this.domainObject.entries.forEach(function (element, index) {
|
||||
if (element.id === entryId) {
|
||||
foundId = index;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return foundId;
|
||||
};
|
||||
|
||||
EntryController.prototype.textFocus = function ($event) {
|
||||
if ($event.target) {
|
||||
this.currentEntryValue = $event.target.innerText;
|
||||
} else {
|
||||
$event.target.innerText = '';
|
||||
}
|
||||
};
|
||||
|
||||
EntryController.prototype.textBlur = function ($event, entryId) {
|
||||
if ($event.target) {
|
||||
var entryPos = this.entryPosById(entryId);
|
||||
|
||||
if (this.currentEntryValue !== $event.target.innerText) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].text', $event.target.innerText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EntryController.prototype.formatTime = function (unixTime, timeFormat) {
|
||||
return Moment(unixTime).format(timeFormat);
|
||||
};
|
||||
|
||||
EntryController.prototype.deleteEntry = function () {
|
||||
var entryPos = this.entryPosById(this.entry.id),
|
||||
domainObject = this.domainObject,
|
||||
openmct = this.openmct;
|
||||
|
||||
if (entryPos !== -1) {
|
||||
|
||||
var errorDialog = this.dialogService.showBlockingMessage({
|
||||
severity: "error",
|
||||
title: "This action will permanently delete this Notebook entry. Do you wish to continue?",
|
||||
options: [{
|
||||
label: "OK",
|
||||
callback: function () {
|
||||
domainObject.entries.splice(entryPos, 1);
|
||||
openmct.objects.mutate(domainObject, 'entries', domainObject.entries);
|
||||
|
||||
errorDialog.dismiss();
|
||||
}
|
||||
},{
|
||||
label: "Cancel",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
}
|
||||
}]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
EntryController.prototype.dropOnEntry = function (entryId) {
|
||||
var selectedObject = this.dndService.getData('mct-domain-object'),
|
||||
selectedObjectId = selectedObject.getId(),
|
||||
selectedModel = selectedObject.getModel(),
|
||||
cssClass = selectedObject.getCapability('type').typeDef.cssClass,
|
||||
entryPos = this.entryPosById(entryId),
|
||||
currentEntryEmbeds = this.domainObject.entries[entryPos].embeds,
|
||||
newEmbed = {
|
||||
type: selectedObjectId,
|
||||
id: '' + Date.now(),
|
||||
cssClass: cssClass,
|
||||
name: selectedModel.name,
|
||||
snapshot: ''
|
||||
};
|
||||
|
||||
currentEntryEmbeds.push(newEmbed);
|
||||
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].embeds', currentEntryEmbeds);
|
||||
};
|
||||
|
||||
EntryController.prototype.dragoverOnEntry = function () {
|
||||
|
||||
};
|
||||
|
||||
EntryController.prototype.exposedData = function () {
|
||||
return {
|
||||
openmct: this.openmct,
|
||||
domainObject: this.domainObject,
|
||||
dndService: this.dndService,
|
||||
dialogService: this.dialogService,
|
||||
currentEntryValue: this.currentEntryValue
|
||||
};
|
||||
};
|
||||
|
||||
EntryController.prototype.exposedMethods = function () {
|
||||
return {
|
||||
entryPosById: this.entryPosById,
|
||||
textFocus: this.textFocus,
|
||||
textBlur: this.textBlur,
|
||||
formatTime: this.formatTime,
|
||||
deleteEntry: this.deleteEntry,
|
||||
dropOnEntry: this.dropOnEntry,
|
||||
dragoverOnEntry: this.dragoverOnEntry
|
||||
};
|
||||
};
|
||||
return EntryController;
|
||||
});
|
@ -31,8 +31,7 @@ define(
|
||||
$scope.snapshot = undefined;
|
||||
$scope.snapToggle = true;
|
||||
$scope.entryText = '';
|
||||
var annotateAction = $rootScope.selObj.getCapability('action').getActions(
|
||||
{category: 'embed'})[1];
|
||||
var annotateAction = $rootScope.selObj.getCapability('action').getActions({key: 'annotate-snapshot'})[0];
|
||||
|
||||
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = $rootScope.selObj;
|
||||
$scope.objectName = $rootScope.selObj.getModel().name;
|
177
src/plugins/notebook/src/controllers/NotebookController.js
Normal file
177
src/plugins/notebook/src/controllers/NotebookController.js
Normal file
@ -0,0 +1,177 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'vue',
|
||||
'./EntryController',
|
||||
'./EmbedController',
|
||||
'../../res/templates/notebook.html',
|
||||
'../../res/templates/entry.html',
|
||||
'../../res/templates/embed.html',
|
||||
'../../../../ui/components/controls/search.vue'
|
||||
],
|
||||
function (
|
||||
Vue,
|
||||
EntryController,
|
||||
EmbedController,
|
||||
NotebookTemplate,
|
||||
EntryTemplate,
|
||||
EmbedTemplate,
|
||||
search
|
||||
) {
|
||||
|
||||
function NotebookController(openmct, domainObject) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.entrySearch = '';
|
||||
this.objectService = openmct.$injector.get('objectService');
|
||||
this.actionService = openmct.$injector.get('actionService');
|
||||
|
||||
this.show = this.show.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this.newEntry = this.newEntry.bind(this);
|
||||
this.entryPosById = this.entryPosById.bind(this);
|
||||
}
|
||||
|
||||
NotebookController.prototype.initializeVue = function (container) {
|
||||
var self = this,
|
||||
entryController = new EntryController(this.openmct, this.domainObject),
|
||||
embedController = new EmbedController(this.openmct, this.domainObject);
|
||||
|
||||
this.container = container;
|
||||
|
||||
var notebookEmbed = {
|
||||
props:['embed', 'entry'],
|
||||
template: EmbedTemplate,
|
||||
data: embedController.exposedData,
|
||||
methods: embedController.exposedMethods(),
|
||||
beforeMount: embedController.populateActionMenu(self.objectService, self.actionService)
|
||||
};
|
||||
|
||||
var entryComponent = {
|
||||
props:['entry'],
|
||||
template: EntryTemplate,
|
||||
components: {
|
||||
'notebook-embed': notebookEmbed
|
||||
},
|
||||
data: entryController.exposedData,
|
||||
methods: entryController.exposedMethods(),
|
||||
mounted: self.focusOnEntry
|
||||
};
|
||||
|
||||
var notebookVue = Vue.extend({
|
||||
template: NotebookTemplate,
|
||||
components: {
|
||||
'notebook-entry': entryComponent,
|
||||
'search': search.default
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
entrySearch: self.entrySearch,
|
||||
showTime: '0',
|
||||
sortEntries: '-createdOn',
|
||||
entries: self.domainObject.entries,
|
||||
currentEntryValue: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
search: function (event) {
|
||||
if (event.target.value) {
|
||||
this.entrySearch = event.target.value;
|
||||
}
|
||||
},
|
||||
newEntry: self.newEntry,
|
||||
filterBySearch: self.filterBySearch
|
||||
}
|
||||
});
|
||||
|
||||
this.NotebookVue = new notebookVue();
|
||||
container.appendChild(this.NotebookVue.$mount().$el);
|
||||
};
|
||||
|
||||
NotebookController.prototype.newEntry = function (event) {
|
||||
|
||||
var entries = this.domainObject.entries,
|
||||
lastEntryIndex = entries.length - 1,
|
||||
lastEntry = entries[lastEntryIndex],
|
||||
date = Date.now();
|
||||
|
||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds.length) {
|
||||
var createdEntry = {'id': 'entry-' + date, 'createdOn': date, 'embeds':[]};
|
||||
|
||||
entries.push(createdEntry);
|
||||
this.openmct.objects.mutate(this.domainObject, 'entries', entries);
|
||||
} else {
|
||||
lastEntry.createdOn = date;
|
||||
|
||||
this.openmct.objects.mutate(this.domainObject, 'entries[entries.length-1]', lastEntry);
|
||||
this.focusOnEntry.bind(this.NotebookVue.$children[lastEntryIndex])();
|
||||
}
|
||||
|
||||
this.entrySearch = '';
|
||||
};
|
||||
|
||||
NotebookController.prototype.entryPosById = function (entryId) {
|
||||
var foundId = -1;
|
||||
|
||||
this.domainObject.entries.forEach(function (element, index) {
|
||||
if (element.id === entryId) {
|
||||
foundId = index;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return foundId;
|
||||
};
|
||||
|
||||
NotebookController.prototype.focusOnEntry = function () {
|
||||
if (!this.entry.text) {
|
||||
this.$refs.contenteditable.focus();
|
||||
}
|
||||
};
|
||||
|
||||
NotebookController.prototype.filterBySearch = function (entryArray, filterString) {
|
||||
if (filterString) {
|
||||
var lowerCaseFilterString = filterString.toLowerCase();
|
||||
|
||||
return entryArray.filter(function (entry) {
|
||||
if (entry.text) {
|
||||
return entry.text.toLowerCase().includes(lowerCaseFilterString);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return entryArray;
|
||||
}
|
||||
};
|
||||
|
||||
NotebookController.prototype.show = function (container) {
|
||||
this.initializeVue(container);
|
||||
};
|
||||
|
||||
NotebookController.prototype.destroy = function (container) {
|
||||
this.NotebookVue.$destroy(true);
|
||||
};
|
||||
|
||||
return NotebookController;
|
||||
});
|
@ -31,32 +31,34 @@ define(['zepto'], function ($) {
|
||||
var selectedModel = selectedObject.getModel();
|
||||
var cssClass = selectedObject.getCapability('type').typeDef.cssClass;
|
||||
var entryId = -1;
|
||||
var embedId = -1;
|
||||
$scope.clearSearch();
|
||||
if ($element[0].id === 'newEntry') {
|
||||
entryId = $scope.domainObject.model.entries.length;
|
||||
embedId = 0;
|
||||
var lastEntry = $scope.domainObject.model.entries[entryId - 1];
|
||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries.push({'createdOn': +Date.now(),
|
||||
'id': +Date.now(),
|
||||
'embeds': [{'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
}]
|
||||
});
|
||||
'id': +Date.now(),
|
||||
'embeds': [{'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
}]
|
||||
});
|
||||
});
|
||||
}else {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[entryId - 1] =
|
||||
{'createdOn': +Date.now(),
|
||||
'embeds': [{'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
}]
|
||||
'embeds': [{'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
}]
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -75,13 +77,15 @@ define(['zepto'], function ($) {
|
||||
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[entryId].embeds.push({'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
});
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
});
|
||||
});
|
||||
|
||||
embedId = $scope.domainObject.model.entries[entryId].embeds.length - 1;
|
||||
|
||||
if (selectedObject) {
|
||||
e.preventDefault();
|
||||
|
66
src/plugins/notebook/src/utils/SnapshotOverlay.js
Normal file
66
src/plugins/notebook/src/utils/SnapshotOverlay.js
Normal file
@ -0,0 +1,66 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
define([
|
||||
'vue',
|
||||
'../../res/templates/viewSnapshot.html'
|
||||
], function (
|
||||
Vue,
|
||||
snapshotOverlayTemplate
|
||||
) {
|
||||
function SnapshotOverlay (embedObject, formatTime) {
|
||||
this.embedObject = embedObject;
|
||||
|
||||
this.snapshotOverlayVue = new Vue({
|
||||
template: snapshotOverlayTemplate,
|
||||
data: function () {
|
||||
return {
|
||||
embed: embedObject
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
close: this.close.bind(this),
|
||||
formatTime: formatTime
|
||||
}
|
||||
});
|
||||
|
||||
this.open();
|
||||
}
|
||||
|
||||
SnapshotOverlay.prototype.open = function () {
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.classList.add('abs');
|
||||
|
||||
document.body.appendChild(this.overlay);
|
||||
|
||||
this.overlay.appendChild(this.snapshotOverlayVue.$mount().$el);
|
||||
};
|
||||
|
||||
SnapshotOverlay.prototype.close = function (event) {
|
||||
event.stopPropagation();
|
||||
this.snapshotOverlayVue.$destroy();
|
||||
this.overlay.parentNode.removeChild(this.overlay);
|
||||
};
|
||||
|
||||
return SnapshotOverlay;
|
||||
});
|
@ -27,13 +27,14 @@ define([
|
||||
'./autoflow/AutoflowTabularPlugin',
|
||||
'./timeConductor/plugin',
|
||||
'../../example/imagery/plugin',
|
||||
'../../platform/features/notebook/bundle',
|
||||
'../../platform/import-export/bundle',
|
||||
'./summaryWidget/plugin',
|
||||
'./URLIndicatorPlugin/URLIndicatorPlugin',
|
||||
'./telemetryMean/plugin',
|
||||
'./plot/plugin',
|
||||
'./staticRootPlugin/plugin'
|
||||
'./telemetryTable/plugin',
|
||||
'./staticRootPlugin/plugin',
|
||||
'./notebook/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@ -41,18 +42,18 @@ define([
|
||||
AutoflowPlugin,
|
||||
TimeConductorPlugin,
|
||||
ExampleImagery,
|
||||
Notebook,
|
||||
ImportExport,
|
||||
SummaryWidget,
|
||||
URLIndicatorPlugin,
|
||||
TelemetryMean,
|
||||
PlotPlugin,
|
||||
StaticRootPlugin
|
||||
TelemetryTablePlugin,
|
||||
StaticRootPlugin,
|
||||
Notebook
|
||||
) {
|
||||
var bundleMap = {
|
||||
LocalStorage: 'platform/persistence/local',
|
||||
MyItems: 'platform/features/my-items',
|
||||
Notebook: 'platform/features/notebook'
|
||||
MyItems: 'platform/features/my-items'
|
||||
};
|
||||
|
||||
var plugins = _.mapValues(bundleMap, function (bundleName, pluginName) {
|
||||
@ -152,10 +153,12 @@ define([
|
||||
|
||||
plugins.ExampleImagery = ExampleImagery;
|
||||
plugins.Plot = PlotPlugin;
|
||||
plugins.TelemetryTable = TelemetryTablePlugin;
|
||||
|
||||
plugins.SummaryWidget = SummaryWidget;
|
||||
plugins.TelemetryMean = TelemetryMean;
|
||||
plugins.URLIndicator = URLIndicatorPlugin;
|
||||
plugins.Notebook = Notebook;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
@ -15,7 +15,8 @@ define([
|
||||
function SummaryWidgetViewProvider(openmct) {
|
||||
return {
|
||||
key: 'summary-widget-viewer',
|
||||
name: 'Widget View',
|
||||
name: 'Summary View',
|
||||
cssClass: 'icon-summary-widget',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'summary-widget';
|
||||
},
|
||||
|
101
src/plugins/telemetryTable/TableConfigurationViewProvider.js
Normal file
101
src/plugins/telemetryTable/TableConfigurationViewProvider.js
Normal file
@ -0,0 +1,101 @@
|
||||
/*****************************************************************************
|
||||
* 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([
|
||||
'../../api/objects/object-utils',
|
||||
'./components/table-configuration.vue',
|
||||
'./TelemetryTableConfiguration',
|
||||
'vue'
|
||||
], function (
|
||||
objectUtils,
|
||||
TableConfigurationComponent,
|
||||
TelemetryTableConfiguration,
|
||||
Vue
|
||||
) {
|
||||
|
||||
function TableConfigurationViewProvider(openmct) {
|
||||
let instantiateService;
|
||||
|
||||
function isBeingEdited(object) {
|
||||
let oldStyleObject = getOldStyleObject(object);
|
||||
|
||||
return oldStyleObject.hasCapability('editor') &&
|
||||
oldStyleObject.getCapability('editor').inEditContext();
|
||||
}
|
||||
|
||||
function getOldStyleObject(object) {
|
||||
let oldFormatModel = objectUtils.toOldFormat(object);
|
||||
let oldFormatId = objectUtils.makeKeyString(object.identifier);
|
||||
|
||||
return instantiate(oldFormatModel, oldFormatId);
|
||||
}
|
||||
|
||||
function instantiate(model, id) {
|
||||
if (!instantiateService) {
|
||||
instantiateService = openmct.$injector.get('instantiate');
|
||||
}
|
||||
return instantiateService(model, id);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
key: 'table-configuration',
|
||||
name: 'Telemetry Table Configuration',
|
||||
canView: function (selection) {
|
||||
if (selection.length === 0) {
|
||||
return false;
|
||||
}
|
||||
let object = selection[0].context.item;
|
||||
return object.type === 'table' &&
|
||||
isBeingEdited(object);
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
let domainObject = selection[0].context.item;
|
||||
const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
provide: {
|
||||
openmct,
|
||||
tableConfiguration
|
||||
},
|
||||
components: {
|
||||
TableConfiguration: TableConfigurationComponent.default
|
||||
},
|
||||
template: '<table-configuration></table-configuration>',
|
||||
el: element
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return TableConfigurationViewProvider;
|
||||
});
|
201
src/plugins/telemetryTable/TelemetryTable.js
Normal file
201
src/plugins/telemetryTable/TelemetryTable.js
Normal file
@ -0,0 +1,201 @@
|
||||
/*****************************************************************************
|
||||
* 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([
|
||||
'EventEmitter',
|
||||
'lodash',
|
||||
'./collections/BoundedTableRowCollection',
|
||||
'./collections/FilteredTableRowCollection',
|
||||
'./TelemetryTableRow',
|
||||
'./TelemetryTableConfiguration'
|
||||
], function (
|
||||
EventEmitter,
|
||||
_,
|
||||
BoundedTableRowCollection,
|
||||
FilteredTableRowCollection,
|
||||
TelemetryTableRow,
|
||||
TelemetryTableConfiguration
|
||||
) {
|
||||
class TelemetryTable extends EventEmitter {
|
||||
constructor(domainObject, openmct) {
|
||||
super();
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
this.rowCount = 100;
|
||||
this.subscriptions = {};
|
||||
this.tableComposition = undefined;
|
||||
this.telemetryObjects = [];
|
||||
this.outstandingRequests = 0;
|
||||
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||
|
||||
this.addTelemetryObject = this.addTelemetryObject.bind(this);
|
||||
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
|
||||
this.isTelemetryObject = this.isTelemetryObject.bind(this);
|
||||
this.refreshData = this.refreshData.bind(this);
|
||||
this.requestDataFor = this.requestDataFor.bind(this);
|
||||
|
||||
this.createTableRowCollections();
|
||||
openmct.time.on('bounds', this.refreshData);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
if (this.domainObject.type === 'table') {
|
||||
this.loadComposition();
|
||||
} else {
|
||||
this.addTelemetryObject(this.domainObject);
|
||||
}
|
||||
}
|
||||
|
||||
createTableRowCollections() {
|
||||
this.boundedRows = new BoundedTableRowCollection(this.openmct);
|
||||
|
||||
//By default, sort by current time system, ascending.
|
||||
this.filteredRows = new FilteredTableRowCollection(this.boundedRows);
|
||||
this.filteredRows.sortBy({
|
||||
key: this.openmct.time.timeSystem().key,
|
||||
direction: 'asc'
|
||||
});
|
||||
}
|
||||
|
||||
loadComposition() {
|
||||
this.tableComposition = this.openmct.composition.get(this.domainObject);
|
||||
if (this.tableComposition !== undefined){
|
||||
this.tableComposition.load().then((composition)=>{
|
||||
composition = composition.filter(this.isTelemetryObject);
|
||||
|
||||
this.configuration.addColumnsForAllObjects(composition);
|
||||
composition.forEach(this.addTelemetryObject);
|
||||
|
||||
this.tableComposition.on('add', this.addTelemetryObject);
|
||||
this.tableComposition.on('remove', this.removeTelemetryObject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addTelemetryObject(telemetryObject) {
|
||||
this.configuration.addColumnsForObject(telemetryObject, true);
|
||||
this.requestDataFor(telemetryObject);
|
||||
this.subscribeTo(telemetryObject);
|
||||
this.telemetryObjects.push(telemetryObject);
|
||||
|
||||
this.emit('object-added', telemetryObject);
|
||||
}
|
||||
|
||||
removeTelemetryObject(objectIdentifier) {
|
||||
this.configuration.removeColumnsForObject(objectIdentifier, true);
|
||||
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
|
||||
this.boundedRows.removeAllRowsForObject(keyString);
|
||||
this.unsubscribe(keyString);
|
||||
this.telemetryObjects = this.telemetryObjects.filter((object) => !_.eq(objectIdentifier, object.identifier));
|
||||
|
||||
this.emit('object-removed', objectIdentifier);
|
||||
}
|
||||
|
||||
requestDataFor(telemetryObject) {
|
||||
this.incrementOutstandingRequests();
|
||||
|
||||
return this.openmct.telemetry.request(telemetryObject)
|
||||
.then(telemetryData => {
|
||||
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
let columnMap = this.getColumnMapForObject(keyString);
|
||||
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||
|
||||
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
||||
this.boundedRows.add(telemetryRows);
|
||||
console.log('Loaded %i rows', telemetryRows.length);
|
||||
this.decrementOutstandingRequests();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
incrementOutstandingRequests() {
|
||||
if (this.outstandingRequests === 0){
|
||||
this.emit('outstanding-requests', true);
|
||||
}
|
||||
this.outstandingRequests++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
decrementOutstandingRequests() {
|
||||
this.outstandingRequests--;
|
||||
|
||||
if (this.outstandingRequests === 0){
|
||||
this.emit('outstanding-requests', false);
|
||||
}
|
||||
}
|
||||
|
||||
refreshData(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
this.filteredRows.clear();
|
||||
this.boundedRows.clear();
|
||||
this.telemetryObjects.forEach(this.requestDataFor);
|
||||
}
|
||||
}
|
||||
|
||||
getColumnMapForObject(objectKeyString) {
|
||||
let columns = this.configuration.getColumns();
|
||||
|
||||
return columns[objectKeyString].reduce((map, column) => {
|
||||
map[column.getKey()] = column;
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
subscribeTo(telemetryObject) {
|
||||
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
let columnMap = this.getColumnMapForObject(keyString);
|
||||
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||
|
||||
this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
|
||||
this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
||||
});
|
||||
}
|
||||
|
||||
isTelemetryObject(domainObject) {
|
||||
return domainObject.hasOwnProperty('telemetry');
|
||||
}
|
||||
|
||||
unsubscribe(keyString) {
|
||||
this.subscriptions[keyString]();
|
||||
delete this.subscriptions[keyString];
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.boundedRows.destroy();
|
||||
this.filteredRows.destroy();
|
||||
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
|
||||
this.openmct.time.off('bounds', this.refreshData);
|
||||
|
||||
if (this.tableComposition !== undefined) {
|
||||
this.tableComposition.off('add', this.addTelemetryObject);
|
||||
this.tableComposition.off('remove', this.removeTelemetryObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TelemetryTable;
|
||||
});
|
62
src/plugins/telemetryTable/TelemetryTableColumn.js
Normal file
62
src/plugins/telemetryTable/TelemetryTableColumn.js
Normal file
@ -0,0 +1,62 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
class TelemetryTableColumn {
|
||||
constructor (openmct, metadatum) {
|
||||
this.metadatum = metadatum;
|
||||
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
|
||||
this.titleValue = this.metadatum.name;
|
||||
}
|
||||
|
||||
getKey() {
|
||||
return this.metadatum.key;
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
return this.metadatum.name;
|
||||
}
|
||||
|
||||
getMetadatum() {
|
||||
return this.metadatum;
|
||||
}
|
||||
|
||||
hasValueForDatum(telemetryDatum) {
|
||||
return telemetryDatum.hasOwnProperty(this.metadatum.source);
|
||||
}
|
||||
|
||||
getRawValue(telemetryDatum) {
|
||||
return telemetryDatum[this.metadatum.source];
|
||||
}
|
||||
|
||||
getFormattedValue(telemetryDatum) {
|
||||
let formattedValue = this.formatter.format(telemetryDatum);
|
||||
if (typeof formattedValue !== 'string') {
|
||||
return formattedValue.toString();
|
||||
} else {
|
||||
return formattedValue;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return TelemetryTableColumn;
|
||||
});
|
141
src/plugins/telemetryTable/TelemetryTableConfiguration.js
Normal file
141
src/plugins/telemetryTable/TelemetryTableConfiguration.js
Normal file
@ -0,0 +1,141 @@
|
||||
/*****************************************************************************
|
||||
* 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([
|
||||
'lodash',
|
||||
'EventEmitter',
|
||||
'./TelemetryTableColumn',
|
||||
], function (_, EventEmitter, TelemetryTableColumn) {
|
||||
|
||||
class TelemetryTableConfiguration extends EventEmitter{
|
||||
constructor(domainObject, openmct) {
|
||||
super();
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
this.columns = {};
|
||||
|
||||
this.addColumnsForObject = this.addColumnsForObject.bind(this);
|
||||
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
|
||||
this.objectMutated = this.objectMutated.bind(this);
|
||||
|
||||
this.unlistenFromMutation = openmct.objects.observe(domainObject, '*', this.objectMutated);
|
||||
}
|
||||
|
||||
getConfiguration() {
|
||||
let configuration = this.domainObject.configuration || {};
|
||||
configuration.hiddenColumns = configuration.hiddenColumns || {};
|
||||
return configuration;
|
||||
}
|
||||
|
||||
updateConfiguration(configuration) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration', configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {*} object
|
||||
*/
|
||||
objectMutated(object) {
|
||||
let oldConfiguration = this.domainObject.configuration;
|
||||
|
||||
//Synchronize domain object reference. Duplicate object otherwise change detection becomes impossible.
|
||||
this.domainObject = JSON.parse(JSON.stringify(object));
|
||||
if (!_.eq(object.configuration, oldConfiguration)){
|
||||
this.emit('change', object.configuration);
|
||||
}
|
||||
}
|
||||
|
||||
addColumnsForAllObjects(objects) {
|
||||
objects.forEach(object => this.addColumnsForObject(object, false));
|
||||
}
|
||||
|
||||
addColumnsForObject(telemetryObject) {
|
||||
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
|
||||
let objectKeyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
this.columns[objectKeyString] = [];
|
||||
|
||||
metadataValues.forEach(metadatum => {
|
||||
let column = new TelemetryTableColumn(this.openmct, metadatum);
|
||||
this.columns[objectKeyString].push(column);
|
||||
});
|
||||
}
|
||||
|
||||
removeColumnsForObject(objectIdentifier) {
|
||||
let objectKeyString = this.openmct.objects.makeKeyString(objectIdentifier);
|
||||
let columnsToRemove = this.columns[objectKeyString];
|
||||
|
||||
delete this.columns[objectKeyString];
|
||||
columnsToRemove.forEach((column) => {
|
||||
//There may be more than one column with the same key (eg. time system columns)
|
||||
if (!this.hasColumnWithKey(column.getKey())) {
|
||||
let configuration = this.domainObject.configuration;
|
||||
delete configuration.hiddenColumns[column.getKey()];
|
||||
// If there are no more columns with this key, delete any configuration, and trigger
|
||||
// a column refresh.
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration', configuration);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hasColumnWithKey(columnKey) {
|
||||
return _.flatten(Object.values(this.columns))
|
||||
.findIndex(column => column.getKey() === columnKey) !== -1;
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
return this.columns;
|
||||
}
|
||||
|
||||
getAllHeaders() {
|
||||
let flattenedColumns = _.flatten(Object.values(this.columns));
|
||||
let headers = _.uniq(flattenedColumns, false, column => column.getKey())
|
||||
.reduce(fromColumnsToHeadersMap, {});
|
||||
|
||||
function fromColumnsToHeadersMap(headersMap, column){
|
||||
headersMap[column.getKey()] = column.getTitle();
|
||||
return headersMap;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
getVisibleHeaders() {
|
||||
let headers = this.getAllHeaders();
|
||||
let configuration = this.getConfiguration();
|
||||
|
||||
Object.keys(headers).forEach((headerKey) => {
|
||||
if (configuration.hiddenColumns[headerKey] === true) {
|
||||
delete headers[headerKey];
|
||||
}
|
||||
});
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.unlistenFromMutation();
|
||||
}
|
||||
}
|
||||
|
||||
return TelemetryTableConfiguration;
|
||||
});
|
81
src/plugins/telemetryTable/TelemetryTableRow.js
Normal file
81
src/plugins/telemetryTable/TelemetryTableRow.js
Normal file
@ -0,0 +1,81 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
class TelemetryTableRow {
|
||||
constructor(datum, columns, objectKeyString, limitEvaluator) {
|
||||
this.columns = columns;
|
||||
|
||||
this.datum = createNormalizedDatum(datum, columns);
|
||||
this.limitEvaluator = limitEvaluator;
|
||||
this.objectKeyString = objectKeyString;
|
||||
}
|
||||
|
||||
getFormattedDatum(headers) {
|
||||
return Object.keys(headers).reduce((formattedDatum, columnKey) => {
|
||||
formattedDatum[columnKey] = this.getFormattedValue(columnKey);
|
||||
return formattedDatum;
|
||||
}, {});
|
||||
}
|
||||
|
||||
getFormattedValue(key) {
|
||||
let column = this.columns[key];
|
||||
return column && column.getFormattedValue(this.datum[key]);
|
||||
}
|
||||
|
||||
getRowLimitClass() {
|
||||
if (!this.rowLimitClass) {
|
||||
let limitEvaluation = this.limitEvaluator.evaluate(this.datum);
|
||||
this.rowLimitClass = limitEvaluation && limitEvaluation.cssClass;
|
||||
}
|
||||
return this.rowLimitClass;
|
||||
}
|
||||
|
||||
getCellLimitClasses() {
|
||||
if (!this.cellLimitClasses) {
|
||||
this.cellLimitClasses = Object.values(this.columns).reduce((alarmStateMap, column) => {
|
||||
let limitEvaluation = this.limitEvaluator.evaluate(this.datum, column.getMetadatum());
|
||||
alarmStateMap[column.getKey()] = limitEvaluation && limitEvaluation.cssClass;
|
||||
|
||||
return alarmStateMap;
|
||||
}, {});
|
||||
}
|
||||
return this.cellLimitClasses;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the structure of datums to assist sorting and merging of columns.
|
||||
* Maps all sources to keys.
|
||||
* @private
|
||||
* @param {*} telemetryDatum
|
||||
* @param {*} metadataValues
|
||||
*/
|
||||
function createNormalizedDatum(datum, columns) {
|
||||
return Object.values(columns).reduce((normalizedDatum, column) => {
|
||||
normalizedDatum[column.getKey()] = column.getRawValue(datum);
|
||||
return normalizedDatum;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return TelemetryTableRow;
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
/******************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
@ -20,25 +20,20 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* This bundle implements "containment" rules, which determine which objects
|
||||
* can be contained within a notebook.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
function CompositionPolicy() {
|
||||
}
|
||||
|
||||
CompositionPolicy.prototype.allow = function (parent, child) {
|
||||
var parentDef = parent.getCapability('type').getName();
|
||||
|
||||
if (parentDef === 'Notebook' && child.getCapability('status').list().length) {
|
||||
return false;
|
||||
define(function () {
|
||||
function TelemetryTableType() {
|
||||
return {
|
||||
name: 'Telemetry Table',
|
||||
description: 'Display telemetry values for the current time bounds in tabular form. Supports filtering and sorting.',
|
||||
creatable: true,
|
||||
cssClass: 'icon-tabular-realtime',
|
||||
initialize(domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {
|
||||
columns: {}
|
||||
};
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return CompositionPolicy;
|
||||
}
|
||||
}
|
||||
);
|
||||
return TelemetryTableType;
|
||||
});
|
76
src/plugins/telemetryTable/TelemetryTableViewProvider.js
Normal file
76
src/plugins/telemetryTable/TelemetryTableViewProvider.js
Normal file
@ -0,0 +1,76 @@
|
||||
/*****************************************************************************
|
||||
* 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([
|
||||
'./components/table.vue',
|
||||
'../../exporters/CSVExporter',
|
||||
'./TelemetryTable',
|
||||
'vue'
|
||||
], function (
|
||||
TableComponent,
|
||||
CSVExporter,
|
||||
TelemetryTable,
|
||||
Vue
|
||||
) {
|
||||
function TelemetryTableViewProvider(openmct) {
|
||||
return {
|
||||
key: 'table',
|
||||
name: 'Telemetry Table',
|
||||
cssClass: 'icon-tabular-realtime',
|
||||
editable: function(domainObject) {
|
||||
return domainObject.type === 'table';
|
||||
},
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'table' || domainObject.hasOwnProperty('telemetry');
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let csvExporter = new CSVExporter.default();
|
||||
let table = new TelemetryTable(domainObject, openmct);
|
||||
let component;
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
TableComponent: TableComponent.default,
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
csvExporter,
|
||||
table
|
||||
},
|
||||
el: element,
|
||||
template: '<table-component></table-component>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return TelemetryTableViewProvider;
|
||||
});
|
@ -0,0 +1,139 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
[
|
||||
'lodash',
|
||||
'./SortedTableRowCollection'
|
||||
],
|
||||
function (
|
||||
_,
|
||||
SortedTableRowCollection
|
||||
) {
|
||||
|
||||
class BoundedTableRowCollection extends SortedTableRowCollection {
|
||||
constructor (openmct) {
|
||||
super();
|
||||
|
||||
this.futureBuffer = new SortedTableRowCollection();
|
||||
this.openmct = openmct;
|
||||
|
||||
this.sortByTimeSystem = this.sortByTimeSystem.bind(this)
|
||||
this.bounds = this.bounds.bind(this)
|
||||
|
||||
this.sortByTimeSystem(openmct.time.timeSystem());
|
||||
openmct.time.on('timeSystem', this.sortByTimeSystem);
|
||||
|
||||
this.lastBounds = openmct.time.bounds();
|
||||
openmct.time.on('bounds', this.bounds);
|
||||
}
|
||||
|
||||
addOne (item) {
|
||||
// Insert into either in-bounds array, or the future buffer.
|
||||
// Data in the future buffer will be re-evaluated for possible
|
||||
// insertion on next bounds change
|
||||
let beforeStartOfBounds = item.datum[this.sortOptions.key] < this.lastBounds.start;
|
||||
let afterEndOfBounds = item.datum[this.sortOptions.key] > this.lastBounds.end;
|
||||
|
||||
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
||||
return super.addOne(item);
|
||||
} else if (afterEndOfBounds) {
|
||||
this.futureBuffer.addOne(item);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
sortByTimeSystem(timeSystem) {
|
||||
this.sortBy({key: timeSystem.key, direction: 'asc'});
|
||||
this.futureBuffer.sortBy({key: timeSystem.key, direction: 'asc'});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is optimized for ticking - it assumes that start and end
|
||||
* bounds will only increase and as such this cannot be used for decreasing
|
||||
* bounds changes.
|
||||
*
|
||||
* An implication of this is that data will not be discarded that exceeds
|
||||
* the given end bounds. For arbitrary bounds changes, it's assumed that
|
||||
* a telemetry requery is performed anyway, and the collection is cleared
|
||||
* and repopulated.
|
||||
*
|
||||
* @fires TelemetryCollection#added
|
||||
* @fires TelemetryCollection#discarded
|
||||
* @param bounds
|
||||
*/
|
||||
bounds (bounds) {
|
||||
let startChanged = this.lastBounds.start !== bounds.start;
|
||||
let endChanged = this.lastBounds.end !== bounds.end;
|
||||
|
||||
let startIndex = 0;
|
||||
let endIndex = 0;
|
||||
|
||||
let discarded = [];
|
||||
let added = [];
|
||||
let testValue = {
|
||||
datum: {}
|
||||
};
|
||||
|
||||
this.lastBounds = bounds;
|
||||
|
||||
if (startChanged) {
|
||||
testValue.datum[this.sortOptions.key] = bounds.start;
|
||||
// Calculate the new index of the first item within the bounds
|
||||
startIndex = this.sortedIndex(this.rows, testValue);
|
||||
discarded = this.rows.splice(0, startIndex);
|
||||
}
|
||||
|
||||
if (endChanged) {
|
||||
testValue.datum[this.sortOptions.key] = bounds.end;
|
||||
// Calculate the new index of the last item in bounds
|
||||
endIndex = this.sortedLastIndex(this.futureBuffer.rows, testValue);
|
||||
added = this.futureBuffer.rows.splice(0, endIndex);
|
||||
added.forEach((datum) => this.rows.push(datum));
|
||||
}
|
||||
|
||||
if (discarded && discarded.length > 0) {
|
||||
/**
|
||||
* A `discarded` event is emitted when telemetry data fall out of
|
||||
* bounds due to a bounds change event
|
||||
* @type {object[]} discarded the telemetry data
|
||||
* discarded as a result of the bounds change
|
||||
*/
|
||||
this.emit('remove', discarded);
|
||||
}
|
||||
if (added && added.length > 0) {
|
||||
/**
|
||||
* An `added` event is emitted when a bounds change results in
|
||||
* received telemetry falling within the new bounds.
|
||||
* @type {object[]} added the telemetry data that is now within bounds
|
||||
*/
|
||||
this.emit('add', added);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.openmct.time.off('timeSystem', this.sortByTimeSystem);
|
||||
this.openmct.time.off('bounds', this.bounds);
|
||||
}
|
||||
}
|
||||
return BoundedTableRowCollection;
|
||||
});
|
@ -0,0 +1,112 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
[
|
||||
'./SortedTableRowCollection'
|
||||
],
|
||||
function (
|
||||
SortedTableRowCollection
|
||||
) {
|
||||
class FilteredTableRowCollection extends SortedTableRowCollection {
|
||||
constructor(masterCollection) {
|
||||
super();
|
||||
|
||||
this.masterCollection = masterCollection;
|
||||
this.columnFilters = {};
|
||||
|
||||
//Synchronize with master collection
|
||||
this.masterCollection.on('add', this.add);
|
||||
this.masterCollection.on('remove', this.remove);
|
||||
|
||||
//Default to master collection's sort options
|
||||
this.sortOptions = masterCollection.sortBy();
|
||||
}
|
||||
|
||||
setColumnFilter(columnKey, filter) {
|
||||
filter = filter.trim().toLowerCase();
|
||||
|
||||
let rowsToFilter = this.getRowsToFilter(columnKey, filter);
|
||||
if (filter.length === 0) {
|
||||
delete this.columnFilters[columnKey];
|
||||
} else {
|
||||
this.columnFilters[columnKey] = filter;
|
||||
}
|
||||
this.rows = rowsToFilter.filter(this.matchesFilters, this);
|
||||
this.emit('filter');
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getRowsToFilter(columnKey, filter) {
|
||||
if (this.isSubsetOfCurrentFilter(columnKey, filter)) {
|
||||
return this.getRows();
|
||||
} else {
|
||||
return this.masterCollection.getRows();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
isSubsetOfCurrentFilter(columnKey, filter) {
|
||||
return this.columnFilters[columnKey] &&
|
||||
filter.startsWith(this.columnFilters[columnKey]) &&
|
||||
// startsWith check will otherwise fail when filter cleared
|
||||
// because anyString.startsWith('') === true
|
||||
filter !== '';
|
||||
}
|
||||
|
||||
addOne(row) {
|
||||
return this.matchesFilters(row) && super.addOne(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
matchesFilters(row) {
|
||||
let doesMatchFilters = true;
|
||||
for (const key in this.columnFilters) {
|
||||
if (!this.rowHasColumn(row, key)) {
|
||||
return false;
|
||||
} else {
|
||||
let formattedValue = row.getFormattedValue(key).toLowerCase();
|
||||
doesMatchFilters = doesMatchFilters &&
|
||||
formattedValue.indexOf(this.columnFilters[key]) !== -1;
|
||||
}
|
||||
}
|
||||
return doesMatchFilters;
|
||||
}
|
||||
|
||||
rowHasColumn(row, key) {
|
||||
return row.columns.hasOwnProperty(key);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.masterCollection.off('add', this.add);
|
||||
this.masterCollection.off('remove', this.remove);
|
||||
}
|
||||
}
|
||||
|
||||
return FilteredTableRowCollection;
|
||||
});
|
@ -0,0 +1,240 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
[
|
||||
'lodash',
|
||||
'EventEmitter'
|
||||
],
|
||||
function (
|
||||
_,
|
||||
EventEmitter
|
||||
) {
|
||||
const LESS_THAN = -1;
|
||||
const EQUAL = 0;
|
||||
const GREATER_THAN = 1;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
class SortedTableRowCollection extends EventEmitter {
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
this.dupeCheck = false;
|
||||
this.rows = [];
|
||||
|
||||
this.add = this.add.bind(this);
|
||||
this.remove = this.remove.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a datum or array of data to this telemetry collection
|
||||
* @fires TelemetryCollection#added
|
||||
* @param {object | object[]} rows
|
||||
*/
|
||||
add(rows) {
|
||||
if (Array.isArray(rows)) {
|
||||
this.dupeCheck = false;
|
||||
|
||||
let rowsAdded = rows.filter(this.addOne, this);
|
||||
if (rowsAdded.length > 0) {
|
||||
this.emit('add', rowsAdded);
|
||||
}
|
||||
this.dupeCheck = true;
|
||||
} else {
|
||||
let wasAdded = this.addOne(rows);
|
||||
if (wasAdded) {
|
||||
this.emit('add', rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
addOne(row) {
|
||||
if (this.sortOptions === undefined) {
|
||||
throw 'Please specify sort options';
|
||||
}
|
||||
|
||||
let isDuplicate = false;
|
||||
|
||||
// Going to check for duplicates. Bound the search problem to
|
||||
// items around the given time. Use sortedIndex because it
|
||||
// employs a binary search which is O(log n). Can use binary search
|
||||
// because the array is guaranteed ordered due to sorted insertion.
|
||||
let startIx = this.sortedIndex(this.rows, row);
|
||||
let endIx = undefined;
|
||||
|
||||
if (this.dupeCheck && startIx !== this.rows.length) {
|
||||
endIx = this.sortedLastIndex(this.rows, row);
|
||||
|
||||
// Create an array of potential dupes, based on having the
|
||||
// same time stamp
|
||||
let potentialDupes = this.rows.slice(startIx, endIx + 1);
|
||||
// Search potential dupes for exact dupe
|
||||
isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, row)) > -1;
|
||||
}
|
||||
|
||||
if (!isDuplicate) {
|
||||
this.rows.splice(endIx || startIx, 0, row);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
sortedLastIndex(rows, testRow) {
|
||||
return this.sortedIndex(rows, testRow, _.sortedLastIndex);
|
||||
}
|
||||
/**
|
||||
* Finds the correct insertion point for the given row.
|
||||
* Leverages lodash's `sortedIndex` function which implements a binary search.
|
||||
* @private
|
||||
*/
|
||||
sortedIndex(rows, testRow, lodashFunction) {
|
||||
if (this.rows.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const sortOptionsKey = this.sortOptions.key;
|
||||
const testRowValue = testRow.datum[sortOptionsKey];
|
||||
const firstValue = this.rows[0].datum[sortOptionsKey];
|
||||
const lastValue = this.rows[this.rows.length - 1].datum[sortOptionsKey];
|
||||
|
||||
lodashFunction = lodashFunction || _.sortedIndex;
|
||||
|
||||
if (this.sortOptions.direction === 'asc') {
|
||||
if (testRowValue > lastValue) {
|
||||
return this.rows.length;
|
||||
} else if (testRowValue === lastValue) {
|
||||
return this.rows.length - 1;
|
||||
} else if (testRowValue <= firstValue) {
|
||||
return 0;
|
||||
} else {
|
||||
return lodashFunction(rows, testRow, (thisRow) => {
|
||||
return thisRow.datum[sortOptionsKey];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (testRowValue >= firstValue) {
|
||||
return 0;
|
||||
} else if (testRowValue < lastValue) {
|
||||
return this.rows.length;
|
||||
} else if (testRowValue === lastValue) {
|
||||
return this.rows.length - 1;
|
||||
} else {
|
||||
// Use a custom comparison function to support descending sort.
|
||||
return lodashFunction(rows, testRow, (thisRow) => {
|
||||
const thisRowValue = thisRow.datum[sortOptionsKey];
|
||||
if (testRowValue === thisRowValue) {
|
||||
return EQUAL;
|
||||
} else if (testRowValue < thisRowValue) {
|
||||
return LESS_THAN;
|
||||
} else {
|
||||
return GREATER_THAN;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the telemetry collection based on the provided sort field
|
||||
* specifier. Subsequent inserts are sorted to maintain specified sport
|
||||
* order.
|
||||
*
|
||||
* @example
|
||||
* // First build some mock telemetry for the purpose of an example
|
||||
* let now = Date.now();
|
||||
* let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) {
|
||||
* return {
|
||||
* // define an object property to demonstrate nested paths
|
||||
* timestamp: {
|
||||
* ms: now - value * 1000,
|
||||
* text:
|
||||
* },
|
||||
* value: value
|
||||
* }
|
||||
* });
|
||||
* let collection = new TelemetryCollection();
|
||||
*
|
||||
* collection.add(telemetry);
|
||||
*
|
||||
* // Sort by telemetry value
|
||||
* collection.sortBy({
|
||||
* key: 'value', direction: 'asc'
|
||||
* });
|
||||
*
|
||||
* // Sort by ms since epoch
|
||||
* collection.sort({
|
||||
* key: 'timestamp.ms',
|
||||
* direction: 'asc'
|
||||
* });
|
||||
*
|
||||
* // Sort by 'text' attribute, descending
|
||||
* collection.sort("timestamp.text");
|
||||
*
|
||||
*
|
||||
* @param {object} sortOptions An object specifying a sort key, and direction.
|
||||
*/
|
||||
sortBy(sortOptions) {
|
||||
if (arguments.length > 0) {
|
||||
this.sortOptions = sortOptions;
|
||||
this.rows = _.sortByOrder(this.rows, 'datum.' + sortOptions.key, sortOptions.direction);
|
||||
this.emit('sort');
|
||||
}
|
||||
// Return duplicate to avoid direct modification of underlying object
|
||||
return Object.assign({}, this.sortOptions);
|
||||
}
|
||||
|
||||
removeAllRowsForObject(objectKeyString) {
|
||||
let removed = [];
|
||||
this.rows = this.rows.filter(row => {
|
||||
if (row.objectKeyString === objectKeyString) {
|
||||
removed.push(row);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this.emit('remove', removed);
|
||||
}
|
||||
|
||||
remove(removedRows) {
|
||||
this.rows = this.rows.filter(row => {
|
||||
return removedRows.indexOf(row) === -1;
|
||||
});
|
||||
this.emit('remove', removedRows);
|
||||
}
|
||||
|
||||
getRows () {
|
||||
return this.rows;
|
||||
}
|
||||
|
||||
clear() {
|
||||
let removedRows = this.rows;
|
||||
this.rows = [];
|
||||
this.emit('remove', removedRows);
|
||||
}
|
||||
}
|
||||
return SortedTableRowCollection;
|
||||
});
|
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="grid-properties">
|
||||
<!--form class="form" -->
|
||||
<ul class="l-inspector-part">
|
||||
<h2>Table Columns</h2>
|
||||
<li class="grid-row" v-for="(title, key) in headers">
|
||||
<div class="grid-cell label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
|
||||
<div class="grid-cell value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
|
||||
</li>
|
||||
</ul>
|
||||
<!--/form -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['tableConfiguration', 'openmct'],
|
||||
data() {
|
||||
return {
|
||||
headers: {},
|
||||
configuration: this.tableConfiguration.getConfiguration()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateHeaders(headers) {
|
||||
this.headers = headers;
|
||||
},
|
||||
toggleColumn(key) {
|
||||
let isHidden = this.configuration.hiddenColumns[key] === true;
|
||||
|
||||
this.configuration.hiddenColumns[key] = !isHidden;
|
||||
this.tableConfiguration.updateConfiguration(this.configuration);
|
||||
},
|
||||
addObject(domainObject) {
|
||||
this.tableConfiguration.addColumnsForObject(domainObject, true);
|
||||
this.updateHeaders(this.tableConfiguration.getAllHeaders());
|
||||
},
|
||||
removeObject(objectIdentifier) {
|
||||
this.tableConfiguration.removeColumnsForObject(objectIdentifier, true);
|
||||
this.updateHeaders(this.tableConfiguration.getAllHeaders());
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.unlisteners = [];
|
||||
let compositionCollection = this.openmct.composition.get(this.tableConfiguration.domainObject);
|
||||
|
||||
compositionCollection.load()
|
||||
.then((composition) => {
|
||||
this.tableConfiguration.addColumnsForAllObjects(composition);
|
||||
this.updateHeaders(this.tableConfiguration.getAllHeaders());
|
||||
|
||||
compositionCollection.on('add', this.addObject);
|
||||
this.unlisteners.push(compositionCollection.off.bind(compositionCollection, 'add', this.addObject));
|
||||
|
||||
compositionCollection.on('remove', this.removeObject);
|
||||
this.unlisteners.push(compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject));
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
this.tableConfiguration.destroy();
|
||||
this.unlisteners.forEach((unlisten) => unlisten());
|
||||
}
|
||||
}
|
||||
</script>
|
74
src/plugins/telemetryTable/components/table-row.vue
Normal file
74
src/plugins/telemetryTable/components/table-row.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<tr :style="{ top: rowTop }" :class="rowLimitClass">
|
||||
<td v-for="(title, key, headerIndex) in headers"
|
||||
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}"
|
||||
:title="formattedRow[key]"
|
||||
:class="cellLimitClasses[key]">{{formattedRow[key]}}</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
|
||||
formattedRow: this.row.getFormattedDatum(this.headers),
|
||||
rowLimitClass: this.row.getRowLimitClass(),
|
||||
cellLimitClasses: this.row.getCellLimitClasses()
|
||||
}
|
||||
},
|
||||
props: {
|
||||
headers: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
columnWidths: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
rowIndex: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
rowOffset: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
rowHeight: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
calculateRowTop: function (rowOffset) {
|
||||
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
|
||||
},
|
||||
formatRow: function (row) {
|
||||
this.formattedRow = row.getFormattedDatum(this.headers);
|
||||
this.rowLimitClass = row.getRowLimitClass();
|
||||
this.cellLimitClasses = row.getCellLimitClasses();
|
||||
}
|
||||
},
|
||||
// TODO: use computed properties
|
||||
watch: {
|
||||
rowOffset: 'calculateRowTop',
|
||||
row: {
|
||||
handler: 'formatRow',
|
||||
deep: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
543
src/plugins/telemetryTable/components/table.vue
Normal file
543
src/plugins/telemetryTable/components/table.vue
Normal file
@ -0,0 +1,543 @@
|
||||
<template>
|
||||
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
|
||||
:class="{'loading': loading}">
|
||||
<div class="c-table__control-bar c-control-bar">
|
||||
<a class="s-button t-export icon-download labeled"
|
||||
v-on:click="exportAsCSV()"
|
||||
title="Export This View's Data">
|
||||
Export As CSV
|
||||
</a>
|
||||
</div>
|
||||
<!-- Headers table -->
|
||||
<div class="c-table__headers-w js-table__headers-w">
|
||||
<table class="c-table__headers c-telemetry-table__headers"
|
||||
:style="{ 'max-width': totalWidth + 'px'}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(title, key, headerIndex) in headers"
|
||||
v-on:click="sortBy(key)"
|
||||
:class="['is-sortable', sortOptions.key === key ? 'is-sorting' : '', sortOptions.direction].join(' ')"
|
||||
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}">{{title}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th v-for="(title, key, headerIndex) in headers"
|
||||
:style="{
|
||||
width: columnWidths[headerIndex],
|
||||
'max-width': columnWidths[headerIndex],
|
||||
}">
|
||||
<search class="c-table__search"
|
||||
v-model="filters[key]"
|
||||
v-on:input="filterChanged(key)"
|
||||
v-on:clear="clearFilter(key)" />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Content table -->
|
||||
<div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll">
|
||||
<div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth }"></div>
|
||||
<table class="c-table__body c-telemetry-table__body"
|
||||
:style="{ height: totalHeight + 'px', 'max-width': totalWidth + 'px'}">
|
||||
<tbody>
|
||||
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
|
||||
:headers="headers"
|
||||
:columnWidths="columnWidths"
|
||||
:rowIndex="rowIndex"
|
||||
:rowOffset="rowOffset"
|
||||
:rowHeight="rowHeight"
|
||||
:row="row"
|
||||
>
|
||||
</telemetry-table-row>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Sizing table -->
|
||||
<table class="c-telemetry-table__sizing js-telemetry-table__sizing"
|
||||
:style="{width: calcTableWidth}">
|
||||
<tr>
|
||||
<th v-for="(title, key, headerIndex) in headers">{{title}}</th>
|
||||
</tr>
|
||||
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
|
||||
:headers="headers"
|
||||
:row="sizingRowData">
|
||||
</telemetry-table-row>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.c-table {
|
||||
// Can be used by any type of table, scrolling, LAD, etc.
|
||||
$min-w: 50px;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0; right: 0; bottom: 0; left: 0;
|
||||
|
||||
&__control-bar,
|
||||
&__headers-w {
|
||||
// Don't allow top level elements to grow or shrink
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/******************************* ELEMENTS */
|
||||
th, td {
|
||||
display: block;
|
||||
flex: 1 0 auto;
|
||||
font-size: 0.7rem; // TEMP LEGACY TODO: refactor this when __main-container font-size is dealt with
|
||||
white-space: nowrap;
|
||||
min-width: $min-w;
|
||||
padding: $tabularTdPadTB $tabularTdPadLR;
|
||||
vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default
|
||||
}
|
||||
|
||||
td {
|
||||
color: $colorTelemFresh;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&__control-bar {
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
/******************************* WRAPPERS */
|
||||
&__headers-w {
|
||||
// Wraps __headers table
|
||||
background: $colorTabHeaderBg;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/******************************* TABLES */
|
||||
&__headers,
|
||||
&__body {
|
||||
tr {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
&__headers {
|
||||
// A table
|
||||
thead {
|
||||
display: block;
|
||||
}
|
||||
|
||||
th {
|
||||
&:not(:first-child) {
|
||||
border-left: 1px solid $colorTabHeaderBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
// A table
|
||||
tr {
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid $colorTabBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************* MODIFIERS */
|
||||
&--filterable {
|
||||
// TODO: discuss using the search.vue custom control here
|
||||
|
||||
.l-filter {
|
||||
input[type="text"],
|
||||
input[type="search"] {
|
||||
$p: 20px;
|
||||
transition: padding 200ms ease-in-out;
|
||||
box-sizing: border-box;
|
||||
padding-right: $p; // Fend off from icon
|
||||
padding-left: $p; // Fend off from icon
|
||||
width: 100%;
|
||||
}
|
||||
&.active {
|
||||
// When user has typed something, hide the icon and collapse left padding
|
||||
&:before {
|
||||
opacity: 0;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="search"] {
|
||||
padding-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--sortable {
|
||||
.is-sorting {
|
||||
&:after {
|
||||
color: $colorIconLink;
|
||||
content: $glyph-icon-arrow-tall-up;
|
||||
font-family: symbolsfont;
|
||||
font-size: 8px;
|
||||
display: inline-block;
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
&.desc:after {
|
||||
content: $glyph-icon-arrow-tall-down;
|
||||
}
|
||||
}
|
||||
.is-sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-telemetry-table {
|
||||
// Table that displays telemetry in a scrolling body area
|
||||
|
||||
/******************************* ELEMENTS */
|
||||
&__scroll-forcer {
|
||||
// Force horz scroll when needed; width set via JS
|
||||
font-size: 0;
|
||||
height: 1px; // Height 0 won't force scroll properly
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/******************************* WRAPPERS */
|
||||
&__body-w {
|
||||
// Wraps __body table provides scrolling
|
||||
flex: 1 1 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
/******************************* TABLES */
|
||||
&__body {
|
||||
// A table
|
||||
flex: 1 1 100%;
|
||||
overflow-x: auto;
|
||||
|
||||
tr {
|
||||
display: flex; // flex-flow defaults to row nowrap (which is what we want) so no need to define
|
||||
align-items: stretch;
|
||||
position: absolute;
|
||||
height: 18px; // Needed when a row has empty values in its cells
|
||||
}
|
||||
|
||||
td {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
&__sizing {
|
||||
// A table
|
||||
display: table;
|
||||
z-index: -1;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
|
||||
//Add some padding to allow for decorations such as limits indicator
|
||||
tr {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
th, td {
|
||||
display: table-cell;
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-table__control-bar {
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
/******************************* LEGACY */
|
||||
.s-status-taking-snapshot,
|
||||
.overlay.snapshot {
|
||||
// Handle overflow-y issues with tables and html2canvas
|
||||
// Replaces .l-sticky-headers .l-tabular-body { overflow: auto; }
|
||||
.c-table__body-w { overflow: auto; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import TelemetryTableRow from './table-row.vue';
|
||||
import search from '../../../ui/components/controls/search.vue';
|
||||
import _ from 'lodash';
|
||||
|
||||
const VISIBLE_ROW_COUNT = 100;
|
||||
const ROW_HEIGHT = 17;
|
||||
const RESIZE_POLL_INTERVAL = 200;
|
||||
const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TelemetryTableRow,
|
||||
search
|
||||
},
|
||||
inject: ['table', 'openmct', 'csvExporter'],
|
||||
props: ['configuration'],
|
||||
data() {
|
||||
return {
|
||||
headers: {},
|
||||
visibleRows: [],
|
||||
columnWidths: [],
|
||||
sizingRows: {},
|
||||
rowHeight: ROW_HEIGHT,
|
||||
scrollOffset: 0,
|
||||
totalHeight: 0,
|
||||
totalWidth: 0,
|
||||
rowOffset: 0,
|
||||
autoScroll: true,
|
||||
sortOptions: {},
|
||||
filters: {},
|
||||
loading: false,
|
||||
scrollable: undefined,
|
||||
tableEl: undefined,
|
||||
headersHolderEl: undefined,
|
||||
calcTableWidth: '100%',
|
||||
processingScroll: false,
|
||||
updatingView: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateVisibleRows() {
|
||||
|
||||
let start = 0;
|
||||
let end = VISIBLE_ROW_COUNT;
|
||||
let filteredRows = this.table.filteredRows.getRows();
|
||||
let filteredRowsLength = filteredRows.length;
|
||||
|
||||
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
|
||||
|
||||
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
|
||||
end = filteredRowsLength;
|
||||
} else {
|
||||
let firstVisible = this.calculateFirstVisibleRow();
|
||||
let lastVisible = this.calculateLastVisibleRow();
|
||||
let totalVisible = lastVisible - firstVisible;
|
||||
|
||||
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
|
||||
start = firstVisible - Math.floor(numberOffscreen / 2);
|
||||
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
||||
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
|
||||
} else if (end >= filteredRowsLength) {
|
||||
end = filteredRowsLength;
|
||||
start = end - VISIBLE_ROW_COUNT + 1;
|
||||
}
|
||||
}
|
||||
this.rowOffset = start;
|
||||
this.visibleRows = filteredRows.slice(start, end);
|
||||
},
|
||||
calculateFirstVisibleRow() {
|
||||
return Math.floor(this.scrollable.scrollTop / this.rowHeight);
|
||||
},
|
||||
calculateLastVisibleRow() {
|
||||
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
|
||||
return Math.floor(bottomScroll / this.rowHeight);
|
||||
},
|
||||
updateHeaders() {
|
||||
let headers = this.table.configuration.getVisibleHeaders();
|
||||
|
||||
this.headers = headers;
|
||||
this.$nextTick().then(this.calculateColumnWidths);
|
||||
},
|
||||
setSizingTableWidth() {
|
||||
let scrollW = this.scrollable.offsetWidth - this.scrollable.clientWidth;
|
||||
|
||||
if (scrollW && scrollW > 0) {
|
||||
this.calcTableWidth = 'calc(100% - ' + scrollW + 'px)';
|
||||
}
|
||||
},
|
||||
calculateColumnWidths() {
|
||||
let columnWidths = [];
|
||||
let totalWidth = 0;
|
||||
let sizingRowEl = this.sizingTable.children[0];
|
||||
let sizingCells = Array.from(sizingRowEl.children);
|
||||
|
||||
sizingCells.forEach((cell) => {
|
||||
let columnWidth = cell.offsetWidth;
|
||||
columnWidths.push(columnWidth + 'px');
|
||||
totalWidth += columnWidth;
|
||||
});
|
||||
|
||||
this.columnWidths = columnWidths;
|
||||
this.totalWidth = totalWidth;
|
||||
},
|
||||
sortBy(columnKey) {
|
||||
// If sorting by the same column, flip the sort direction.
|
||||
if (this.sortOptions.key === columnKey) {
|
||||
if (this.sortOptions.direction === 'asc') {
|
||||
this.sortOptions.direction = 'desc';
|
||||
} else {
|
||||
this.sortOptions.direction = 'asc';
|
||||
}
|
||||
} else {
|
||||
this.sortOptions = {
|
||||
key: columnKey,
|
||||
direction: 'asc'
|
||||
}
|
||||
}
|
||||
this.table.filteredRows.sortBy(this.sortOptions);
|
||||
},
|
||||
scroll() {
|
||||
if (!this.processingScroll) {
|
||||
this.processingScroll = true;
|
||||
requestAnimationFrame(()=> {
|
||||
this.updateVisibleRows();
|
||||
this.synchronizeScrollX();
|
||||
|
||||
if (this.shouldSnapToBottom()) {
|
||||
this.autoScroll = true;
|
||||
} else {
|
||||
// If user scrolls away from bottom, disable auto-scroll.
|
||||
// Auto-scroll will be re-enabled if user scrolls to bottom again.
|
||||
this.autoScroll = false;
|
||||
}
|
||||
this.processingScroll = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
shouldSnapToBottom() {
|
||||
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
|
||||
},
|
||||
scrollToBottom() {
|
||||
this.scrollable.scrollTop = this.scrollable.scrollHeight;
|
||||
},
|
||||
synchronizeScrollX() {
|
||||
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
||||
},
|
||||
filterChanged(columnKey) {
|
||||
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
||||
},
|
||||
clearFilter(columnKey) {
|
||||
this.filters[columnKey] = '';
|
||||
this.table.filteredRows.setColumnFilter(columnKey, '');
|
||||
},
|
||||
rowsAdded(rows) {
|
||||
let sizingRow;
|
||||
if (Array.isArray(rows)) {
|
||||
sizingRow = rows[0];
|
||||
} else {
|
||||
sizingRow = rows;
|
||||
}
|
||||
if (!this.sizingRows[sizingRow.objectKeyString]) {
|
||||
this.sizingRows[sizingRow.objectKeyString] = sizingRow;
|
||||
this.$nextTick().then(this.calculateColumnWidths);
|
||||
}
|
||||
|
||||
if (!this.updatingView) {
|
||||
this.updatingView = true;
|
||||
requestAnimationFrame(()=> {
|
||||
this.updateVisibleRows();
|
||||
if (this.autoScroll) {
|
||||
this.$nextTick().then(this.scrollToBottom);
|
||||
}
|
||||
this.updatingView = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
rowsRemoved(rows) {
|
||||
if (!this.updatingView) {
|
||||
this.updatingView = true;
|
||||
requestAnimationFrame(()=> {
|
||||
this.updateVisibleRows();
|
||||
this.updatingView = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
exportAsCSV() {
|
||||
const headerKeys = Object.keys(this.headers);
|
||||
const justTheData = this.table.filteredRows.getRows()
|
||||
.map(row => row.getFormattedDatum(this.headers));
|
||||
this.csvExporter.export(justTheData, {
|
||||
filename: this.table.domainObject.name + '.csv',
|
||||
headers: headerKeys
|
||||
});
|
||||
},
|
||||
outstandingRequests(loading) {
|
||||
this.loading = loading;
|
||||
},
|
||||
calculateTableSize() {
|
||||
this.setSizingTableWidth();
|
||||
this.$nextTick().then(this.calculateColumnWidths);
|
||||
},
|
||||
pollForResize() {
|
||||
let el = this.$el;
|
||||
let width = el.clientWidth;
|
||||
let height = el.clientHeight;
|
||||
|
||||
this.resizePollHandle = setInterval(() => {
|
||||
if (el.clientWidth !== width || el.clientHeight !== height) {
|
||||
this.calculateTableSize();
|
||||
width = el.clientWidth;
|
||||
height = el.clientHeight;
|
||||
}
|
||||
}, RESIZE_POLL_INTERVAL);
|
||||
},
|
||||
updateConfiguration(configuration) {
|
||||
this.configuration = configuration;
|
||||
this.updateHeaders();
|
||||
},
|
||||
addObject() {
|
||||
this.updateHeaders();
|
||||
},
|
||||
removeObject(objectIdentifier) {
|
||||
let objectKeyString = this.openmct.objects.makeKeyString(objectIdentifier);
|
||||
delete this.sizingRows[objectKeyString];
|
||||
this.updateHeaders();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.filterChanged = _.debounce(this.filterChanged, 500);
|
||||
},
|
||||
mounted() {
|
||||
this.table.on('object-added', this.addObject);
|
||||
this.table.on('object-removed', this.removeObject);
|
||||
this.table.on('outstanding-requests', this.outstandingRequests);
|
||||
|
||||
this.table.filteredRows.on('add', this.rowsAdded);
|
||||
this.table.filteredRows.on('remove', this.rowsRemoved);
|
||||
this.table.filteredRows.on('sort', this.updateVisibleRows);
|
||||
this.table.filteredRows.on('filter', this.updateVisibleRows);
|
||||
|
||||
//Default sort
|
||||
this.sortOptions = this.table.filteredRows.sortBy();
|
||||
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
|
||||
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
|
||||
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
|
||||
|
||||
this.table.configuration.on('change', this.updateConfiguration);
|
||||
|
||||
this.calculateTableSize();
|
||||
this.pollForResize();
|
||||
|
||||
this.table.initialize();
|
||||
},
|
||||
destroyed() {
|
||||
this.table.off('object-added', this.addObject);
|
||||
this.table.off('object-removed', this.removeObject);
|
||||
this.table.off('outstanding-requests', this.outstandingRequests);
|
||||
|
||||
this.table.filteredRows.off('add', this.rowsAdded);
|
||||
this.table.filteredRows.off('remove', this.rowsRemoved);
|
||||
this.table.filteredRows.off('sort', this.updateVisibleRows);
|
||||
this.table.filteredRows.off('filter', this.updateVisibleRows);
|
||||
|
||||
this.table.configuration.off('change', this.updateConfiguration);
|
||||
|
||||
clearInterval(this.resizePollHandle);
|
||||
|
||||
this.table.configuration.destroy();
|
||||
|
||||
this.table.destroy();
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
@ -20,35 +20,20 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* This bundle implements object types and associated views for
|
||||
* display-building.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* The LayoutNotebookController is responsible for supporting the
|
||||
* notebook feature creation on theLayout view.
|
||||
**/
|
||||
|
||||
function LayoutNotebookController($scope) {
|
||||
$scope.hasNotebookAction = undefined;
|
||||
|
||||
$scope.newNotebook = undefined;
|
||||
|
||||
var actions = $scope.domainObject.getCapability('action');
|
||||
var notebookAction = actions.getActions({'key': 'notebook-new-entry'});
|
||||
if (notebookAction.length > 0) {
|
||||
$scope.hasNotebookAction = true;
|
||||
$scope.newNotebook = function () {
|
||||
notebookAction[0].perform();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return LayoutNotebookController;
|
||||
}
|
||||
);
|
||||
|
||||
define([
|
||||
'./TelemetryTableViewProvider',
|
||||
'./TableConfigurationViewProvider',
|
||||
'./TelemetryTableType'
|
||||
], function (
|
||||
TelemetryTableViewProvider,
|
||||
TableConfigurationViewProvider,
|
||||
TelemetryTableType
|
||||
) {
|
||||
return function plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct));
|
||||
openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct));
|
||||
openmct.types.addType('table', TelemetryTableType());
|
||||
};
|
||||
};
|
||||
});
|
6
src/plugins/telemetryTable/table-row.html
Normal file
6
src/plugins/telemetryTable/table-row.html
Normal file
@ -0,0 +1,6 @@
|
||||
<tr :style="{ top: rowTop }" :class="rowLimitClass">
|
||||
<td v-for="(title, key, headerIndex) in headers"
|
||||
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}"
|
||||
:title="formattedRow[key]"
|
||||
:class="cellLimitClasses[key]">{{formattedRow[key]}}</td>
|
||||
</tr>
|
147
src/styles-new/_constants-mobile.scss
Normal file
147
src/styles-new/_constants-mobile.scss
Normal file
@ -0,0 +1,147 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/* REQUIRES /platform/commonUI/general/res/sass/_constants.scss */
|
||||
|
||||
/************************** MOBILE REPRESENTATION ITEMS DIMENSIONS */
|
||||
$mobileListIconSize: 30px;
|
||||
$mobileTitleDescH: 35px;
|
||||
$mobileOverlayMargin: 20px;
|
||||
$mobileMenuIconD: 34px;
|
||||
$phoneItemH: floor($ueBrowseGridItemLg/4);
|
||||
$tabletItemH: floor($ueBrowseGridItemLg/3);
|
||||
|
||||
/************************** MOBILE TREE MENU DIMENSIONS */
|
||||
$mobileTreeItemH: 35px;
|
||||
$mobileTreeItemIndent: 15px;
|
||||
$mobileTreeRightArrowW: 30px;
|
||||
|
||||
/************************** DEVICE WIDTHS */
|
||||
// IMPORTANT! Usage assumes that ranges are mutually exclusive and have no gaps
|
||||
$phoMaxW: 767px;
|
||||
$tabMinW: 768px;
|
||||
$tabMaxW: 1024px;
|
||||
$desktopMinW: 1025px;
|
||||
|
||||
/************************** MEDIA QUERIES: WINDOW CHECKS FOR SPECIFIC ORIENTATIONS FOR EACH DEVICE */
|
||||
$screenPortrait: "(orientation: portrait)";
|
||||
$screenLandscape: "(orientation: landscape)";
|
||||
|
||||
//$mobileDevice: "(max-device-width: #{$tabMaxW})";
|
||||
|
||||
$phoneCheck: "(max-device-width: #{$phoMaxW})";
|
||||
$tabletCheck: "(min-device-width: #{$tabMinW}) and (max-device-width: #{$tabMaxW})";
|
||||
$desktopCheck: "(min-device-width: #{$desktopMinW}) and (-webkit-min-device-pixel-ratio: 1)";
|
||||
|
||||
/************************** MEDIA QUERIES: WINDOWS FOR SPECIFIC ORIENTATIONS FOR EACH DEVICE */
|
||||
$phonePortrait: "only screen and #{$screenPortrait} and #{$phoneCheck}";
|
||||
$phoneLandscape: "only screen and #{$screenLandscape} and #{$phoneCheck}";
|
||||
|
||||
$tabletPortrait: "only screen and #{$screenPortrait} and #{$tabletCheck}";
|
||||
$tabletLandscape: "only screen and #{$screenLandscape} and #{$tabletCheck}";
|
||||
|
||||
$desktop: "only screen and #{$desktopCheck}";
|
||||
|
||||
/************************** DEVICE PARAMETERS FOR MENUS/REPRESENTATIONS */
|
||||
$proporMenuOnly: 90%;
|
||||
$proporMenuWithView: 40%;
|
||||
|
||||
// Phones in any orientation
|
||||
@mixin phone {
|
||||
@media #{$phonePortrait},
|
||||
#{$phoneLandscape} {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
//Phones in portrait orientation
|
||||
@mixin phonePortrait {
|
||||
@media #{$phonePortrait} {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
// Phones in landscape orientation
|
||||
@mixin phoneLandscape {
|
||||
@media #{$phoneLandscape} {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
// Tablets in any orientation
|
||||
@mixin tablet {
|
||||
@media #{$tabletPortrait},
|
||||
#{$tabletLandscape} {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
// Tablets in portrait orientation
|
||||
@mixin tabletPortrait {
|
||||
@media #{$tabletPortrait} {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
// Tablets in landscape orientation
|
||||
@mixin tabletLandscape {
|
||||
@media #{$tabletLandscape} {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
// Phones and tablets in any orientation
|
||||
@mixin phoneandtablet {
|
||||
@media #{$phonePortrait},
|
||||
#{$phoneLandscape},
|
||||
#{$tabletPortrait},
|
||||
#{$tabletLandscape} {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
// Desktop monitors in any orientation
|
||||
@mixin desktopandtablet {
|
||||
// Keeping only for legacy - should not be used moving forward
|
||||
// Use body.desktop, body.tablet instead.
|
||||
@media #{$tabletPortrait},
|
||||
#{$tabletLandscape},
|
||||
#{$desktop} {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
// Desktop monitors in any orientation
|
||||
@mixin desktop {
|
||||
// Keeping only for legacy - should not be used moving forward
|
||||
// Use body.desktop instead.
|
||||
@media #{$desktop} {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
// Transition used for the slide menu
|
||||
@mixin slMenuTransitions {
|
||||
@include transition-duration(.35s);
|
||||
transition-timing-function: ease;
|
||||
backface-visibility: hidden;
|
||||
}
|
309
src/styles-new/_constants-snow.scss
Normal file
309
src/styles-new/_constants-snow.scss
Normal file
@ -0,0 +1,309 @@
|
||||
@import "constants";
|
||||
|
||||
// Functions
|
||||
@function pullForward($c: $colorBodyBg, $p: 20%) {
|
||||
// For dark interfaces, lighter things come forward - opposite for light interfaces
|
||||
@return darken($c, $p);
|
||||
}
|
||||
|
||||
@function pushBack($c: $colorBodyBg, $p: 20%) {
|
||||
// For dark interfaces, darker things move back - opposite for light interfaces
|
||||
@return lighten($c, $p);
|
||||
}
|
||||
|
||||
// Global
|
||||
$fontBaseSize: 12px;
|
||||
$colorBodyBg: #fcfcfc;
|
||||
$colorBodyFg: #666;
|
||||
$colorGenBg: #fff;
|
||||
$colorStatusBarBg: #000;
|
||||
$colorStatusBarFg: #999;
|
||||
$colorStatusBarFgHov: #aaa;
|
||||
$colorKey: #0099cc;
|
||||
$colorKeyFilter: brightness(0.9) sepia(1) hue-rotate(145deg) saturate(6);
|
||||
$colorKeySelectedBg: $colorKey;
|
||||
$colorKeyFg: #fff;
|
||||
$colorKeyHov: #00c0f6;
|
||||
$colorEditAreaBg: #eafaff;
|
||||
$colorEditAreaFg: #4bb1c7;
|
||||
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||
$colorA: #999;
|
||||
$colorAHov: $colorKey;
|
||||
$contrastRatioPercent: 40%;
|
||||
$hoverRatioPercent: 10%;
|
||||
$basicCr: 4px;
|
||||
$controlCr: 3px;
|
||||
$smallCr: 2px;
|
||||
$overlayCr: 11px;
|
||||
$shdwTextSubtle: rgba(black, 0.2) 0 1px 2px;
|
||||
|
||||
// Buttons and Controls
|
||||
$btnPad: $interiorMargin, $interiorMargin * 1.25;
|
||||
$colorBtnBg: #aaaaaa;
|
||||
$colorBtnBgHov: pullForward($colorBtnBg, $hoverRatioPercent);
|
||||
$colorBtnFg: #fff;
|
||||
$colorBtnFgHov: $colorBtnFg;
|
||||
$colorBtnIcon: #eee;
|
||||
$colorBtnIconHov: $colorBtnFgHov;
|
||||
$colorBtnMajorBg: $colorKey;
|
||||
$colorBtnMajorBgHov: $colorKeyHov;
|
||||
$colorBtnMajorFg: $colorKeyFg;
|
||||
$colorBtnMajorFgHov: pushBack($colorBtnMajorFg, $hoverRatioPercent);
|
||||
$colorClickIcon: $colorKey;
|
||||
$colorClickIconHov: $colorKeyHov;
|
||||
$colorToggleIcon: rgba($colorClickIcon, 0.5);
|
||||
$colorToggleIconActive: $colorKey;
|
||||
$colorToggleIconHov: rgba($colorToggleIconActive, 0.5);
|
||||
$colorInvokeMenu: #000;
|
||||
$contrastInvokeMenuPercent: 40%;
|
||||
$shdwBtns: none;
|
||||
$shdwBtnsOverlay: none;
|
||||
$sliderColorBase: $colorKey;
|
||||
$sliderColorRangeHolder: rgba(black, 0.07);
|
||||
$sliderColorRange: rgba($sliderColorBase, 0.2);
|
||||
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
|
||||
$sliderColorKnob: pushBack($sliderColorBase, 20%);
|
||||
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
|
||||
$sliderColorRangeValHovBg: $sliderColorRange;
|
||||
$sliderColorRangeValHovFg: $colorBodyFg;
|
||||
$sliderKnobW: 15px;
|
||||
$sliderKnobR: 2px;
|
||||
$timeControllerToiLineColor: $colorBodyFg;
|
||||
$timeControllerToiLineColorHov: #0052b5;
|
||||
$colorTransLucBg: #666; // Used as a visual blocking element over variable backgrounds, like imagery
|
||||
$createBtnTextTransform: uppercase;
|
||||
|
||||
// Foundation Colors
|
||||
$colorAlt1: #776ba2;
|
||||
$colorAlert: #ff3c00;
|
||||
$colorWarningHi: #990000;
|
||||
$colorWarningLo: #ff9900;
|
||||
$colorDiagnostic: #a4b442;
|
||||
$colorCommand: #3693bd;
|
||||
$colorInfo: #2294a2;
|
||||
$colorOk: #33cc33;
|
||||
$colorIconLink: #49dedb;
|
||||
$colorPausedBg: #ff9900;
|
||||
$colorPausedFg: #fff;
|
||||
$colorCreateBtn: $colorKey;
|
||||
$colorGridLines: rgba(#000, 0.05);
|
||||
$colorInvokeMenu: #fff;
|
||||
$colorObjHdrTxt: $colorBodyFg;
|
||||
$colorObjHdrIc: lighten($colorObjHdrTxt, 30%);
|
||||
$colorTick: rgba(black, 0.2);
|
||||
$colorSelectableSelectedPrimary: $colorKey;
|
||||
$colorSelectableHov: rgba($colorBodyFg, 0.4);
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
||||
$colorMenuFg: pullForward($colorMenuBg, 70%);
|
||||
$colorMenuIc: $colorKey;
|
||||
$colorMenuHovBg: $colorMenuIc; //pullForward($colorMenuBg, $hoverRatioPercent);
|
||||
$colorMenuHovFg: $colorMenuBg;
|
||||
$colorMenuHovIc: $colorMenuBg;
|
||||
$shdwMenu: rgba(black, 0.5) 0 1px 5px;
|
||||
$shdwMenuText: none;
|
||||
$colorCreateMenuLgIcon: $colorKey;
|
||||
$colorCreateMenuText: $colorBodyFg;
|
||||
$menuItemPad: ($interiorMargin, nth($btnPad, 2));
|
||||
|
||||
// Form colors
|
||||
$colorCheck: $colorKey;
|
||||
$colorFormRequired: $colorKey;
|
||||
$colorFormValid: $colorOk;
|
||||
$colorFormError: $colorWarningHi;
|
||||
$colorFormInvalid: #ff2200;
|
||||
$colorFormFieldErrorBg: $colorFormError;
|
||||
$colorFormFieldErrorFg: rgba(#fff, 0.6);
|
||||
$colorFormLines: rgba(#000, 0.1);
|
||||
$colorFormSectionHeader: rgba(#000, 0.05);
|
||||
$colorInputBg: $colorGenBg;
|
||||
$colorInputFg: $colorBodyFg;
|
||||
$colorInputPlaceholder: pushBack($colorBodyFg, 20%);
|
||||
$colorFormText: pushBack($colorBodyFg, 10%);
|
||||
$colorInputIcon: pushBack($colorBodyFg, 25%);
|
||||
$colorFieldHint: pullForward($colorBodyFg, 40%);
|
||||
|
||||
// Inspector
|
||||
$colorInspectorBg: pullForward($colorBodyBg, 5%);
|
||||
$colorInspectorFg: $colorBodyFg;
|
||||
$colorInspectorPropName: pushBack($colorBodyFg, 20%);
|
||||
$colorInspectorPropVal: pullForward($colorInspectorFg, 15%);
|
||||
$colorInspectorSectionHeaderBg: pullForward($colorInspectorBg, 5%);
|
||||
$colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
|
||||
|
||||
// Status colors, mainly used for messaging and item ancillary symbols
|
||||
$colorStatusFg: #999;
|
||||
$colorStatusDefault: #ccc;
|
||||
$colorStatusInfo: #60ba7b;
|
||||
$colorStatusAlert: #ffb66c;
|
||||
$colorStatusError: #da0004;
|
||||
$colorStatusBtnBg: #666;
|
||||
$colorProgressBarOuter: rgba(#000, 0.1);
|
||||
$colorProgressBarAmt: #0a0;
|
||||
$progressBarHOverlay: 15px;
|
||||
$progressBarStripeW: 20px;
|
||||
$shdwStatusIc: rgba(white, 0.8) 0 0px 5px;
|
||||
$animPausedPulseDur: 1s;
|
||||
|
||||
// Indicator colors
|
||||
$colorIndicatorAvailable: $colorKey;
|
||||
$colorIndicatorDisabled: #444;
|
||||
$colorIndicatorOn: $colorOk;
|
||||
$colorIndicatorOff: #666;
|
||||
|
||||
// Selects
|
||||
$colorSelectBg: $colorBtnBg;
|
||||
$colorSelectFg: $colorBtnFg;
|
||||
|
||||
// Limits and staleness colors//
|
||||
$colorTelemFresh: pullForward($colorBodyFg, 20%);
|
||||
$colorTelemStale: pushBack($colorBodyFg, 20%);
|
||||
$styleTelemStale: italic;
|
||||
$colorLimitYellowBg: rgba(#ffaa00, 0.3);
|
||||
$colorLimitYellowIc: #ffaa00;
|
||||
$colorLimitRedBg: rgba(red, 0.3);
|
||||
$colorLimitRedIc: red;
|
||||
|
||||
// Bubble colors
|
||||
$colorInfoBubbleBg: $colorMenuBg;
|
||||
$colorInfoBubbleFg: #666;
|
||||
$colorThumbsBubbleFg: pullForward($colorBodyFg, 10%);
|
||||
$colorThumbsBubbleBg: pullForward($colorBodyBg, 10%);
|
||||
|
||||
// Overlay
|
||||
$colorOvrBlocker: rgba(black, 0.7);//
|
||||
$colorOvrBg: $colorBodyBg;
|
||||
$colorOvrFg: $colorBodyFg;
|
||||
$colorOvrBtnBg: pullForward($colorOvrBg, 40%);
|
||||
$colorOvrBtnFg: #fff;
|
||||
$colorFieldHintOverlay: pullForward($colorOvrBg, 40%);
|
||||
$durLargeViewExpand: 250ms;
|
||||
|
||||
// Items
|
||||
$colorItemBg: #ddd;
|
||||
$colorItemBgHov: pullForward($colorItemBg, $hoverRatioPercent * 0.7);
|
||||
$colorItemFg: $colorBodyFg;
|
||||
$colorItemFgDetails: pushBack($colorItemFg, 15%);
|
||||
$colorItemIc: $colorKey;
|
||||
$colorItemSubIcons: $colorItemFgDetails;
|
||||
$colorItemOpenIcon: $colorItemFgDetails;
|
||||
$shdwItemText: none;
|
||||
$colorItemBgSelected: $colorKey;
|
||||
|
||||
// Tabular
|
||||
$colorTabBorder: pullForward($colorBodyBg, 10%);
|
||||
$colorTabBodyBg: $colorBodyBg;
|
||||
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
|
||||
$colorTabHeaderBg: pullForward($colorBodyBg, 10%);
|
||||
$colorTabHeaderFg: pullForward($colorBodyFg, 20%);
|
||||
$colorTabHeaderBorder: $colorBodyBg;
|
||||
|
||||
// Plot
|
||||
$colorPlotBg: rgba(black, 0.05);
|
||||
$colorPlotFg: $colorBodyFg;
|
||||
$colorPlotHash: black;
|
||||
$opacityPlotHash: 0.2;
|
||||
$stylePlotHash: dashed;
|
||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
||||
$legendCollapsedNameMaxW: 50%;
|
||||
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
||||
|
||||
// Tree
|
||||
$colorTreeBg: #f0f0f0; // Used
|
||||
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
||||
$colorItemTreeHoverFg: pullForward($colorBodyFg, $hoverRatioPercent);
|
||||
$colorItemTreeIcon: $colorKey; // Used
|
||||
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
||||
$colorItemTreeFg: $colorBodyFg;
|
||||
$colorItemTreeSelectedBg: pushBack($colorKey, 15%);
|
||||
$colorItemTreeSelectedFg: $colorBodyBg;
|
||||
$colorItemTreeEditingBg: #caf1ff;
|
||||
$colorItemTreeEditingFg: $colorEditAreaFg;
|
||||
$colorItemTreeVC: $colorBodyFg;
|
||||
$colorItemTreeVCHover: $colorKey;
|
||||
$colorItemTreeSelectedVC: $colorBodyBg;
|
||||
$shdwItemTreeIcon: none;
|
||||
|
||||
// Images
|
||||
$colorThumbHoverBg: $colorItemTreeHoverBg;
|
||||
|
||||
// Scrollbar
|
||||
$scrollbarTrackSize: 7px;
|
||||
$scrollbarTrackShdw: rgba(#000, 0.2) 0 1px 2px;
|
||||
$scrollbarTrackColorBg: rgba(#000, 0.2);
|
||||
$scrollbarThumbColor: darken($colorBodyBg, 50%);
|
||||
$scrollbarThumbColorHov: $colorKey;
|
||||
$scrollbarThumbColorOverlay: darken($colorOvrBg, 50%);
|
||||
$scrollbarThumbColorOverlayHov: $scrollbarThumbColorHov;
|
||||
$scrollbarThumbColorMenu: pullForward($colorMenuBg, 10%);
|
||||
$scrollbarThumbColorMenuHov: pullForward($scrollbarThumbColorMenu, 2%);
|
||||
|
||||
// Splitter
|
||||
$splitterD: 7px;
|
||||
$splitterHandleD: 2px;
|
||||
$splitterHandleHitMargin: 4px;
|
||||
$splitterGrippyD: ($splitterHandleD - 4, 75px, 50px); // thickness, length, min-length
|
||||
$colorSplitterBaseBg: $colorBodyBg;
|
||||
$colorSplitterBg: pullForward($colorSplitterBaseBg, 20%);
|
||||
$colorSplitterFg: $colorBodyBg;
|
||||
$colorSplitterHover: $colorKey; // pullForward($colorSplitterBg, $hoverRatioPercent * 2);
|
||||
$colorSplitterActive: $colorKey;
|
||||
$splitterBtnD: (16px, 35px); // height, width
|
||||
$splitterBtnColorBg: #eee;
|
||||
$splitterBtnColorFg: #999;
|
||||
$splitterBtnColorHoverBg: rgba($colorKey, 1);
|
||||
$splitterBtnColorHoverFg: $colorBodyBg;
|
||||
$colorSplitterGrippy: pullForward($colorSplitterBaseBg, 30%);
|
||||
$splitterShdw: none;
|
||||
$splitterEndCr: none;
|
||||
|
||||
// Minitabs
|
||||
$colorMiniTabBg: $colorSplitterBg;
|
||||
$colorMiniTabFg: pullForward($colorMiniTabBg, 30%);
|
||||
$colorMiniTabBgHov: $colorSplitterHover;
|
||||
$colorMiniTabFgHov: #fff;
|
||||
|
||||
// Mobile
|
||||
$colorMobilePaneLeft: darken($colorBodyBg, 2%);
|
||||
$colorMobilePaneLeftTreeItemBg: rgba($colorBodyFg, 0.1); //pullForward($colorMobilePaneLeft, 3%);
|
||||
$colorMobilePaneLeftTreeItemFg: $colorItemTreeFg;
|
||||
$colorMobileSelectListTreeItemBg: rgba(#000, 0.05);
|
||||
|
||||
// Datetime Picker, Calendar
|
||||
$colorCalCellHovBg: $colorKey;
|
||||
$colorCalCellHovFg: $colorKeyFg;
|
||||
$colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
|
||||
|
||||
// Palettes
|
||||
$colorPaletteFg: pullForward($colorMenuBg, 30%);
|
||||
$colorPaletteSelected: #333;
|
||||
$shdwPaletteFg: none;
|
||||
$shdwPaletteSelected: inset 0 0 0 1px #fff;
|
||||
|
||||
// About Screen
|
||||
$colorAboutLink: #84b3ff;
|
||||
|
||||
// Loading
|
||||
$colorLoadingFg: $colorAlt1;
|
||||
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
|
||||
|
||||
// Transitions
|
||||
$transIn: all 50ms ease-in;
|
||||
$transOut: all 250ms ease-out;
|
||||
|
||||
// Discrete items, like Notebook entries, Widget rules
|
||||
@mixin discreteItem() {
|
||||
background: rgba($colorBodyFg,0.1);
|
||||
border: 1px solid $colorInteriorBorder;
|
||||
border-radius: $controlCr;
|
||||
.c-input-inline:hover {
|
||||
background: $colorBodyBg;
|
||||
}
|
||||
}
|
||||
@mixin discreteItemInnerElem() {
|
||||
border: 1px solid $colorBodyBg;
|
||||
border-radius: $controlCr; }
|
182
src/styles-new/_constants.scss
Normal file
182
src/styles-new/_constants.scss
Normal file
@ -0,0 +1,182 @@
|
||||
/************************** PATHS */
|
||||
// Paths need to be relative to /platform/commonUI/theme/<theme-name>/css/ directory
|
||||
$dirFonts: 'fonts/';
|
||||
$dirImgs: 'images/';
|
||||
|
||||
/************************** TIMINGS */
|
||||
$controlFadeMs: 100ms;
|
||||
$browseToEditAnimMs: 400ms;
|
||||
$editBorderPulseMs: 500ms;
|
||||
|
||||
/************************** SPATIAL */
|
||||
$interiorMarginSm: 3px;
|
||||
$interiorMargin: 5px;
|
||||
$interiorMarginLg: 10px;
|
||||
$inputTextPTopBtm: 2px;
|
||||
$inputTextPLeftRight: 5px;
|
||||
$inputTextP: $inputTextPTopBtm $inputTextPLeftRight;
|
||||
$menuLineH: 1.5rem;
|
||||
$treeItemIndent: 16px;
|
||||
$treeTypeIconW: 18px;
|
||||
|
||||
/*************** Items */
|
||||
$itemPadLR: 5px;
|
||||
$ueBrowseGridItemLg: 200px;
|
||||
/*************** Tabular */
|
||||
$tabularHeaderH: 22px;
|
||||
$tabularTdPadLR: $itemPadLR;
|
||||
$tabularTdPadTB: 2px;
|
||||
|
||||
|
||||
/************************** VISUAL */
|
||||
$controlDisabledOpacity: 0.3;
|
||||
|
||||
/************************** GLYPH CHAR UNICODES */
|
||||
$glyph-icon-alert-rect: '\e900';
|
||||
$glyph-icon-alert-triangle: '\e901';
|
||||
$glyph-icon-arrow-down: '\e902';
|
||||
$glyph-icon-arrow-left: '\e903';
|
||||
$glyph-icon-arrow-right: '\e904';
|
||||
$glyph-icon-arrow-double-up: '\e905';
|
||||
$glyph-icon-arrow-tall-up: '\e906';
|
||||
$glyph-icon-arrow-tall-down: '\e907';
|
||||
$glyph-icon-arrow-double-down: '\e908';
|
||||
$glyph-icon-arrow-up: '\e909';
|
||||
$glyph-icon-asterisk: '\e910';
|
||||
$glyph-icon-bell: '\e911';
|
||||
$glyph-icon-box: '\e912';
|
||||
$glyph-icon-box-with-arrow: '\e913';
|
||||
$glyph-icon-check: '\e914';
|
||||
$glyph-icon-connectivity: '\e915';
|
||||
$glyph-icon-database-in-brackets: '\e916';
|
||||
$glyph-icon-eye-open: '\e917';
|
||||
$glyph-icon-gear: '\e918';
|
||||
$glyph-icon-hourglass: '\e919';
|
||||
$glyph-icon-info: '\e920';
|
||||
$glyph-icon-link: '\e921';
|
||||
$glyph-icon-lock: '\e922';
|
||||
$glyph-icon-minus: '\e923';
|
||||
$glyph-icon-people: '\e924';
|
||||
$glyph-icon-person: '\e925';
|
||||
$glyph-icon-plus: '\e926';
|
||||
$glyph-icon-trash: '\e927';
|
||||
$glyph-icon-x: '\e928';
|
||||
$glyph-icon-brackets: '\e929';
|
||||
$glyph-icon-crosshair: '\e930';
|
||||
$glyph-icon-grippy: '\e931';
|
||||
$glyph-icon-arrow-right-equilateral: '\e932';
|
||||
$glyph-icon-arrows-out: '\e1000';
|
||||
$glyph-icon-arrows-right-left: '\e1001';
|
||||
$glyph-icon-arrows-up-down: '\e1002';
|
||||
$glyph-icon-bullet: '\e1004';
|
||||
$glyph-icon-calendar: '\e1005';
|
||||
$glyph-icon-chain-links: '\e1006';
|
||||
$glyph-icon-collapse-pane-left: '\e1007';
|
||||
$glyph-icon-collapse-pane-right: '\e1008';
|
||||
$glyph-icon-download: '\e1009';
|
||||
$glyph-icon-duplicate: '\e1010';
|
||||
$glyph-icon-folder-new: '\e1011';
|
||||
$glyph-icon-fullscreen-collapse: '\e1012';
|
||||
$glyph-icon-fullscreen-expand: '\e1013';
|
||||
$glyph-icon-layers: '\e1014';
|
||||
$glyph-icon-line-horz: '\e1015';
|
||||
$glyph-icon-magnify: '\e1016';
|
||||
$glyph-icon-magnify-in: '\e1017';
|
||||
$glyph-icon-magnify-out: '\e1018';
|
||||
$glyph-icon-menu-hamburger: '\e1019';
|
||||
$glyph-icon-move: '\e1020';
|
||||
$glyph-icon-new-window: '\e1021';
|
||||
$glyph-icon-paint-bucket: '\e1022';
|
||||
$glyph-icon-pause: '\e1023';
|
||||
$glyph-icon-pencil: '\e1024';
|
||||
$glyph-icon-play: '\e1025';
|
||||
$glyph-icon-plot-resource: '\e1026';
|
||||
$glyph-icon-pointer-left: '\e1027';
|
||||
$glyph-icon-pointer-right: '\e1028';
|
||||
$glyph-icon-refresh: '\e1029';
|
||||
$glyph-icon-save: '\e1030';
|
||||
$glyph-icon-sine: '\e1031';
|
||||
$glyph-icon-T: '\e1032';
|
||||
$glyph-icon-thumbs-strip: '\e1033';
|
||||
$glyph-icon-two-parts-both: '\e1034';
|
||||
$glyph-icon-two-parts-one-only: '\e1035';
|
||||
$glyph-icon-resync: '\e1036';
|
||||
$glyph-icon-reset: '\e1037';
|
||||
$glyph-icon-x-in-circle: '\e1038';
|
||||
$glyph-icon-brightness: '\e1039';
|
||||
$glyph-icon-contrast: '\e1040';
|
||||
$glyph-icon-expand: '\e1041';
|
||||
$glyph-icon-list-view: '\e1042';
|
||||
$glyph-icon-grid-snap-to: '\e1043';
|
||||
$glyph-icon-grid-snap-no: '\e1044';
|
||||
$glyph-icon-frame-show: '\e1045';
|
||||
$glyph-icon-frame-hide: '\e1046';
|
||||
$glyph-icon-import: '\e1047';
|
||||
$glyph-icon-export: '\e1048';
|
||||
$glyph-icon-minimize: '\e1049'; // 12px only
|
||||
$glyph-icon-activity: '\e1100';
|
||||
$glyph-icon-activity-mode: '\e1101';
|
||||
$glyph-icon-autoflow-tabular: '\e1102';
|
||||
$glyph-icon-clock: '\e1103';
|
||||
$glyph-icon-database: '\e1104';
|
||||
$glyph-icon-database-query: '\e1105';
|
||||
$glyph-icon-dataset: '\e1106';
|
||||
$glyph-icon-datatable: '\e1107';
|
||||
$glyph-icon-dictionary: '\e1108';
|
||||
$glyph-icon-folder: '\e1109';
|
||||
$glyph-icon-image: '\e1110';
|
||||
$glyph-icon-layout: '\e1111';
|
||||
$glyph-icon-object: '\e1112';
|
||||
$glyph-icon-object-unknown: '\e1113';
|
||||
$glyph-icon-packet: '\e1114';
|
||||
$glyph-icon-page: '\e1115';
|
||||
$glyph-icon-plot-overlay: '\e1116';
|
||||
$glyph-icon-plot-stacked: '\e1117';
|
||||
$glyph-icon-session: '\e1118';
|
||||
$glyph-icon-tabular: '\e1119';
|
||||
$glyph-icon-tabular-lad: '\e1120';
|
||||
$glyph-icon-tabular-lad-set: '\e1121';
|
||||
$glyph-icon-tabular-realtime: '\e1122';
|
||||
$glyph-icon-tabular-scrolling: '\e1123';
|
||||
$glyph-icon-telemetry: '\e1124';
|
||||
$glyph-icon-telemetry-panel: '\e1125';
|
||||
$glyph-icon-timeline: '\e1126';
|
||||
$glyph-icon-timer: '\e1127';
|
||||
$glyph-icon-topic: '\e1128';
|
||||
$glyph-icon-box-with-dashed-lines: '\e1129';
|
||||
$glyph-icon-summary-widget: '\e1130';
|
||||
$glyph-icon-notebook: '\e1131';
|
||||
|
||||
/************************** GLYPHS AS DATA URI */
|
||||
// Only objects have been converted, for use in Create menu and folder views
|
||||
$bg-icon-activity: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M288 32H160l160 160H174.872C152.74 153.742 111.377 128 64 128H0v256h64c47.377 0 88.74-25.742 110.872-64H320L160 480h128l224-224L288 32z'/%3e%3c/svg%3e");
|
||||
$bg-icon-activity-mode: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 0C148.6 0 56.6 66.2 18.6 160H64c28.4 0 54 12.4 71.5 32H256l-96-96h128l160 160-160 160H160l96-96H135.5C118 339.6 92.4 352 64 352H18.6c38 93.8 129.9 160 237.4 160 141.4 0 256-114.6 256-256S397.4 0 256 0z'/%3e%3c/svg%3e");
|
||||
$bg-icon-autoflow-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h32V0H96zM192 0h128v512H192zM416 0h-32v352h128V96c0-52.8-43.2-96-96-96z'/%3e%3c/svg%3e");
|
||||
$bg-icon-clock: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 0C114.62 0 0 114.62 0 256s114.62 256 256 256 256-114.62 256-256S397.38 0 256 0zm135 345a36 36 0 0 1-31.21 18 35.83 35.83 0 0 1-18-4.83l-110.85-64-.14-.08q-.6-.35-1.19-.73l-.43-.28-.93-.64-.63-.45-.63-.48-.85-.67-.32-.27c-.36-.3-.72-.61-1.07-.92a35.76 35.76 0 0 1-6.52-7.9c-.14-.23-.27-.47-.41-.7s-.29-.49-.43-.74a35.75 35.75 0 0 1-3.58-9.59v-.06c-.1-.46-.19-.92-.27-1.38 0-.14-.05-.28-.08-.42-.06-.35-.11-.71-.15-1.07s-.07-.52-.1-.79-.05-.51-.07-.77l-.09-1.12v-.52-1.39V81a36 36 0 0 1 36-36 36 36 0 0 1 36 36v161.22l92.85 53.61A36 36 0 0 1 391 345z' fill='%2300a14b'/%3e%3c/svg%3e");
|
||||
$bg-icon-database: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 256C114.615 256 0 213.019 0 160v256c0 53.019 114.615 96 256 96s256-42.981 256-96V160c0 53.019-114.615 96-256 96z'/%3e%3cellipse fill='%23666666' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
|
||||
$bg-icon-database-query: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M341.76 409.643C316.369 423.871 287.118 432 256 432c-97.047 0-176-78.953-176-176S158.953 80 256 80s176 78.953 176 176c0 31.118-8.129 60.369-22.357 85.76l95.846 95.846C509.747 430.661 512 423.429 512 416V96c0-53.019-114.615-96-256-96S0 42.981 0 96v320c0 53.019 114.615 96 256 96 63.055 0 120.774-8.554 165.388-22.73l-79.628-79.627z'/%3e%3cpath fill='%23666666' d='M176 256c0 44.112 35.888 80 80 80s80-35.888 80-80-35.888-80-80-80-80 35.888-80 80z'/%3e%3c/svg%3e");
|
||||
$bg-icon-dataset: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64zM160 448H96V288h64v160zm128 0h-64V288h64v160zm128 0h-64V288h64v160z'/%3e%3c/svg%3e");
|
||||
$bg-icon-datatable: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 256C114.6 256 0 213 0 160v256c0 53 114.6 96 256 96s256-43 256-96V160c0 53-114.6 96-256 96zm192 31.5v128c-18.3 7.8-39.9 14.4-64 19.7v-128c24.1-5.3 45.7-11.9 64-19.7zm-320 19.7v128c-24.1-5.2-45.7-11.9-64-19.7v-128c18.3 7.8 39.9 14.4 64 19.7zM192 445V317c20.5 2 41.9 3 64 3s43.5-1.1 64-3v128c-20.5 2-41.9 3-64 3s-43.5-1.1-64-3z'/%3e%3cellipse fill='%23666666' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
|
||||
$bg-icon-dictionary: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M416 320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96v160l-64-32-64 32V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96v-96c0 52.8-43.2 96-96 96H96v-96h320z'/%3e%3c/svg%3e");
|
||||
$bg-icon-folder: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z'/%3e%3c/svg%3e");
|
||||
$bg-icon-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zm0 448H64V64h384v384z'/%3e%3cpath fill='%23666666' d='M160 128l-64 64v224h320V256l-64-64-64 64z'/%3e%3c/svg%3e");
|
||||
$bg-icon-layout: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M224 0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h128V0zM416 0H288v288.832h224V96c0-52.8-43.2-96-96-96zM288 512h128c52.8 0 96-43.2 96-96v-64.832H288V512z'/%3e%3c/svg%3e");
|
||||
$bg-icon-object: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='none' d='M256 96L76.8 208 256 320l179.2-112z'/%3e%3cpath fill='%23666666' d='M256 512l256-160V160L255.99 0 0 160v192l256 160zm0-416l179.2 112L256 320 76.8 208 256 96z'/%3e%3c/svg%3e");
|
||||
$bg-icon-object-unknown: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M255-1L-1 159v192l256 160 256-160V159L255-1zm37.7 430.6c-10.6 10.4-23 15.4-38 15.4-15.6 0-28.1-4.9-38.1-14.8-10-10-14.8-22.4-14.8-38.1 0-15.2 5.1-27.6 15.5-38.1s22.6-15.6 37.4-15.6c14.8 0 27.1 5.2 37.8 16 10.7 10.8 15.9 23.2 15.9 38-.1 14.5-5.4 27-15.7 37.2zm26.4-156.3c-11.8 5.9-18.7 11-21.7 16.2-1.8 3.1-3 7.4-3.7 13.4v20.5H213v-22.1c0-20.1 2.2-34.9 6.5-44 4-8.6 11.3-15.1 22.4-20l17.4-7.7c16-7.1 24.1-17.6 24.1-31.4 0-8-3-15.2-8.6-20.9-5.6-5.6-12.8-8.6-20.8-8.6-12 0-27.2 5-31.4 28.7l-1.1 6.1H148l.7-8.1c2-22.3 8.5-41.2 19.4-56.1 9.8-13.5 22.8-24.3 38.5-32.3 15.7-8 32.3-12 49.1-12 30.3 0 55.1 9.7 75.7 29.8 20.6 20 30.6 44 30.6 73.6 0 35.4-14.4 60.7-42.9 74.9z'/%3e%3c/svg%3e");
|
||||
$bg-icon-packet: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='none' d='M256 96L76.8 208 256 320l179.2-112z'/%3e%3cpath fill='%23666666' d='M256 0L0 160v256c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V160L256 0zm0 96l179.2 112L256 320 76.8 208 256 96z'/%3e%3c/svg%3e");
|
||||
$bg-icon-page: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M352 256c-52.8 0-96-43.2-96-96V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V256H352z'/%3e%3cpath fill='%23666666' d='M384 192h128L320 0v128c0 35.2 28.8 64 64 64z'/%3e%3c/svg%3e");
|
||||
$bg-icon-plot-overlay: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M415 0H97C43.65 0 0 43.65 0 97v203.41c7.09 9.32 12.83 14.17 16 15.42 7.14-2.81 27.22-23.77 46.48-73C83.71 188.64 120.64 124 176 124c26.2 0 50.71 14.58 72.85 43.34 18.67 24.25 32.42 54.46 40.67 75.54 19.26 49.19 39.34 70.15 46.48 73 7.14-2.81 27.22-23.77 46.48-73C403.71 188.64 440.64 124 496 124a69.55 69.55 0 0 1 16 1.87V97c0-53.35-43.65-97-97-97z'/%3e%3cpath fill='%23666666' d='M496 196.17c-7.14 2.81-27.22 23.76-46.48 73C428.29 323.36 391.36 388 336 388c-26.2 0-50.71-14.58-72.85-43.34-18.67-24.25-32.42-54.46-40.67-75.54-19.26-49.19-39.34-70.15-46.48-73-7.14 2.81-27.22 23.76-46.48 73C108.29 323.36 71.36 388 16 388a69.56 69.56 0 0 1-16-1.87V415c0 53.35 43.65 97 97 97h318c53.35 0 97-43.65 97-97V211.59c-7.09-9.32-12.83-14.17-16-15.42z'/%3e%3c/svg%3e");
|
||||
$bg-icon-plot-stacked: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M44.8 156c12.49 0 24.48-13.26 42.76-35.09 22.71-27.14 51-60.91 98-60.91 22.32 0 43.31 7.73 62.4 23 14.34 11.45 25.58 25.21 36.46 38.53C303.63 145 314 156 326.4 156H512V97c0-53.35-43.65-97-97-97H97C43.65 0 0 43.65 0 97v59h44.8z'/%3e%3cpath fill='%23666666' d='M264.75 205.2c-14.12-11.32-25.26-25-36-38.14C211 145.32 199.37 132 185.6 132c-12.53 0-24.54 13.27-42.83 35.12-22.7 27.12-51 60.88-98 60.88H0v56h185.6c22 0 42.77 7.67 61.65 22.8 14.12 11.32 25.26 25 36 38.14C301 366.68 312.63 380 326.4 380c12.53 0 24.54-13.27 42.83-35.12 22.7-27.12 51-60.88 98-60.88H512v-56H326.4c-22.03 0-42.77-7.67-61.65-22.8z'/%3e%3cpath fill='%23666666' d='M467.2 356c-12.49 0-24.48 13.26-42.76 35.09-22.71 27.14-51 60.91-98 60.91-22.32 0-43.31-7.73-62.4-23-14.34-11.45-25.58-25.21-36.46-38.53C208.37 367 198 356 185.6 356H0v59c0 53.35 43.65 97 97 97h318c53.35 0 97-43.65 97-97v-59h-44.8z'/%3e%3c/svg%3e");
|
||||
$bg-icon-session: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M317.8 262.2c3.3 2.1 6.6 4.3 9.6 6.8l60.2 48.2c14.8 11.9 41.9 11.9 56.7 0l67.6-54c.1-2.4.1-4.7.1-7.1 0-26.1-3.9-51.2-11.1-74.9L423.5 243c-29.1 23.3-70.1 29.6-105.7 19.2zM124.3 317.1l60.2-48.2c29-23.2 70-29.6 105.6-19.2-3.3-2.1-6.6-4.3-9.6-6.8l-60.2-48.2c-14.8-11.9-41.9-11.9-56.7 0L103.5 243c-20 16-45.7 24-71.5 24-10.8 0-21.5-1.4-31.9-4.2v.8c2.5 1.7 5 3.4 7.3 5.3l60.2 48.2c14.9 11.9 41.9 11.9 56.7 0z'/%3e%3cpath fill='%23666666' d='M60.3 189.1l60.2-48.2c40.1-32.1 102.8-32.1 142.9 0l60.2 48.2c14.8 11.9 41.9 11.9 56.7 0l90.5-72.4C425.2 46.5 346 0 256 0 136.7 0 36.4 81.6 8 192.1c15.4 8.8 38.9 7.8 52.3-3zM344.5 371l-60.2-48.2c-14.8-11.9-41.9-11.9-56.7 0L167.5 371c-20 16-45.7 24-71.5 24-23.9 0-47.7-6.9-67.1-20.7C71.7 456.1 157.3 512 256 512s184.3-55.9 227.1-137.7c-40.2 28.7-99.9 27.6-138.6-3.3z'/%3e%3c/svg%3e");
|
||||
$bg-icon-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zM320 224H192v-96h128v96zm-128 32h128v96H192v-96zm-32 96H32v-96h128v96zm0-224v96H32v-96h128zM64 480c-8.5 0-16.5-3.3-22.6-9.4S32 456.5 32 448v-64h128v96H64zm128 0v-96h128v96H192zm288-32c0 8.5-3.3 16.5-9.4 22.6S456.5 480 448 480h-96v-96h128v64zm0-96H352v-96h128v96zm0-128H352v-96h128v96z'/%3e%3c/svg%3e");
|
||||
$bg-icon-tabular-lad: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M448 0H64C28.7.1.1 28.7 0 64v384c.1 35.3 28.7 63.9 64 64h384c35.3-.1 63.9-28.7 64-64V64c-.1-35.3-28.7-63.9-64-64zM32 128h128v96H32v-96zm0 128h128v96H32v-96zm32 224c-17.6-.1-31.9-14.4-32-32v-64h128v96H64zm128 0v-96h128v96H192zm288-32c-.1 17.6-14.4 31.9-32 32h-96v-96h128v64zm0-192v96H192v-96h32v-32h-32v-96h288v96h-32v32h32z'/%3e%3cpath fill='%23666666' d='M391.2 273.7L336 246.1V160c0-8.8-7.2-16-16-16s-16 7.2-16 16v105.9l72.8 36.4c7.9 4 17.5.8 21.5-7.2 4-7.8.8-17.5-7.1-21.4z'/%3e%3c/svg%3e");
|
||||
$bg-icon-tabular-lad-set: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M64 384V96c-35.3.1-63.9 28.7-64 64v288c.1 35.3 28.7 63.9 64 64h288c35.3-.1 63.9-28.7 64-64H128c-35.3-.1-63.9-28.7-64-64z'/%3e%3cpath fill='%23666666' d='M448 0H160c-35.3.1-63.9 28.7-64 64v288c.1 35.3 28.7 63.9 64 64h288c35.3-.1 63.9-28.7 64-64V64c-.1-35.3-28.7-63.9-64-64zM128 96h96v64h-96V96zm0 96h96v96h-96v-96zm32 192c-17.6-.1-31.9-14.4-32-32v-32h96v64h-64zm96 0v-64h96v64h-96zm224-32c-.1 17.6-14.4 31.9-32 32h-64v-64h96v32zm0-64H256V96h224v192z'/%3e%3cpath fill='%23666666' d='M416 240c8.8 0 16-7.2 16-16 0-6.9-4.4-13-10.9-15.2L384 196.5V144c0-8.8-7.2-16-16-16s-16 7.2-16 16v75.5l58.9 19.6c1.7.6 3.4.9 5.1.9z'/%3e%3c/svg%3e");
|
||||
$bg-icon-tabular-realtime: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M0 64v384c0 35.2 28.8 64 64 64h288c35.2 0 64-28.8 64-64V340c-19.8 7.8-41.4 12-64 12-35.4 0-68.4-10.5-96-28.6V352h-96v-96h35.3c-5.2-10.1-9.4-20.8-12.6-32H160v-96h22.7C203.6 54.2 271.6 0 352 0H64C28.8 0 0 28.8 0 64zm288 320h96v64c0 8.5-3.3 16.5-9.4 22.6S360.5 480 352 480h-64v-96zm-160 96H64c-8.5 0-16.5-3.3-22.6-9.4S32 456.5 32 448v-64h96v96zm0-128H32v-96h96v96zm32 32h96v96h-96v-96zm-32-160H32v-96h96v96z'/%3e%3cpath fill='%23666666' d='M192 160c0 88.4 71.6 160 160 160s160-71.6 160-160S440.4 0 352 0 192 71.6 192 160zm49.7 39.8L227 187.5c-1.4-6.4-2.3-12.9-2.7-19.6 15.1-.1 30.1-5 41.9-14.8l39.6-33c7.5-6.2 21.1-6.2 28.6 0l39.6 33c2.8 2.3 5.7 4.3 8.8 6.1-23-11.7-52.7-9.2-72.8 7.5l-39.6 33c-7.6 6.3-21.2 6.3-28.7.1zM352 288c-36.7 0-69.7-15.4-93-40.1 14.2-.6 28.1-5.5 39.2-14.7l39.6-33c7.5-6.2 21.1-6.2 28.6 0l39.6 33c11 9.2 25 14.1 39.2 14.7-23.5 24.7-56.5 40.1-93.2 40.1zm125.9-151.3c1.4 7.5 2.1 15.3 2.1 23.3 0 9.4-1 18.6-3 27.5l-14.7 12.3c-7.5 6.2-21.1 6.2-28.6 0l-39.6-33c-2.8-2.3-5.7-4.3-8.8-6.1 23 11.7 52.7 9.2 72.8-7.5l19.8-16.5zM352 32c46.4 0 87.1 24.7 109.5 61.7l-31.2 26c-7.5 6.2-21.1 6.2-28.6 0l-39.6-33c-23.6-19.7-60.6-19.7-84.3 0l-39.6 33c-2.5 2.1-5.7 3.5-9.1 4.2C244.7 70.8 293.8 32 352 32z'/%3e%3c/svg%3e");
|
||||
$bg-icon-tabular-scrolling: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M32 0C14.4 0 0 14.4 0 32v96h224V0H32zM512 128V32c0-17.6-14.4-32-32-32H288v128h224zM0 192v96c0 17.6 14.4 32 32 32h192V192H0zM480 320c17.6 0 32-14.4 32-32v-96H288v128h192zM256 512L128 384h256z'/%3e%3c/svg%3e");
|
||||
$bg-icon-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M16 315.83c7.14-2.81 27.22-23.77 46.48-73C83.71 188.64 120.64 124 176 124c26.2 0 50.71 14.58 72.85 43.34 18.67 24.25 32.42 54.46 40.67 75.54 19.26 49.19 39.34 70.15 46.48 73 7.14-2.81 27.22-23.77 46.48-73 18.7-47.75 49.57-103.57 94.47-116.23A255.87 255.87 0 0 0 256 0C114.62 0 0 114.62 0 256a257.18 257.18 0 0 0 5 50.52c4.77 5.39 8.61 8.37 11 9.31z'/%3e%3cpath fill='%23666666' d='M496 196.17c-7.14 2.81-27.22 23.76-46.48 73C428.29 323.36 391.36 388 336 388c-26.2 0-50.71-14.58-72.85-43.34-18.67-24.25-32.42-54.46-40.67-75.54-19.26-49.19-39.34-70.15-46.48-73-7.14 2.81-27.22 23.76-46.48 73-18.7 47.75-49.57 103.57-94.47 116.23A255.87 255.87 0 0 0 256 512c141.38 0 256-114.62 256-256a257.18 257.18 0 0 0-5-50.52c-4.77-5.39-8.61-8.37-11-9.31z'/%3e%3c/svg%3e");
|
||||
$bg-icon-timeline: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M128 128h192v64H128zM192 224h192v64H192zM160 320h192v64H160z'/%3e%3cpath fill='%23666666' d='M416 0h-64v96h63.8c.1 0 .1.1.2.2v319.7c0 .1-.1.1-.2.2H352v96h64c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96zM96 415.8V96.2c0-.1.1-.1.2-.2H160V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h64v-96H96.2c-.1 0-.2-.1-.2-.2z'/%3e%3c/svg%3e");
|
||||
$bg-icon-timer: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M288 73.3V32.01a32 32 0 0 0-32-32h-64a32 32 0 0 0-32 32V73.3C67.48 100.84 0 186.54 0 288.01c0 123.71 100.29 224 224 224s224-100.29 224-224c0-101.48-67.5-187.2-160-214.71zm-54 224.71l-131.88 105.5A167.4 167.4 0 0 1 56 288.01c0-92.64 75.36-168 168-168 3.36 0 6.69.11 10 .31v177.69z'/%3e%3c/svg%3e");
|
||||
$bg-icon-topic: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M227.18 238.32l43.15-43.15a25.18 25.18 0 0 1 35.36 0l43.15 43.15a94.42 94.42 0 0 0 35.18 22.25V174.5l-28.82-28.82a95.11 95.11 0 0 0-134.35 0l-43.15 43.15a25.18 25.18 0 0 1-35.36 0L128 174.5v86.07a95.11 95.11 0 0 0 99.18-22.25z'/%3e%3cpath fill='%23666666' d='M252.82 273.68l-43.15 43.15a25.18 25.18 0 0 1-35.36 0l-43.15-43.15c-1-1-2.1-2-3.18-3v98.68a95.11 95.11 0 0 0 131.18-3l43.15-43.15a25.18 25.18 0 0 1 35.36 0l43.15 43.15c1 1 2.1 2 3.18 3v-98.68a95.11 95.11 0 0 0-131.18 3z'/%3e%3cpath fill='%23666666' d='M416 0h-64v96h63.83l.17.17v319.66l-.17.17H352v96h64c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96zM160 416H96.17l-.17-.17V96.17l.17-.17H160V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h64v-96z'/%3e%3c/svg%3e");
|
||||
$bg-icon-box-with-dashed-lines: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M0 192h64v128H0zM64 64.11l.11-.11H160V0H64A64.19 64.19 0 0 0 0 64v96h64V64.11zM64 447.89V352H0v96a64.19 64.19 0 0 0 64 64h96v-64H64.11zM192 0h128v64H192zM448 447.89l-.11.11H352v64h96a64.19 64.19 0 0 0 64-64v-96h-64v95.89zM448 0h-96v64h95.89l.11.11V160h64V64a64.19 64.19 0 0 0-64-64zM448 192h64v128h-64zM192 448h128v64H192zM128 128h256v256H128z'/%3e%3c/svg%3e");
|
||||
$bg-icon-summary-widget: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zm-24.1 305.2l-41.3 71.6-94.8-65.8 9.6 115h-82.7l9.6-115-94.8 65.8-41.3-71.6L192.5 256 88.1 206.8l41.3-71.6 94.8 65.8-9.6-115h82.7l-9.6 115 94.8-65.8 41.3 71.6L319.5 256l104.4 49.2z'/%3e%3c/svg%3e");
|
||||
$bg-icon-notebook: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M448 55.4c0-39.9-27.7-63.7-61.5-52.7L0 128h448V55.4zM448 160H0v288c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64zm-32 256H224V256h192v160z'/%3e%3c/svg%3e");
|
356
src/styles-new/_controls.scss
Normal file
356
src/styles-new/_controls.scss
Normal file
@ -0,0 +1,356 @@
|
||||
/******************************************************** BUTTONS */
|
||||
%c-control {
|
||||
@include userSelectNone();
|
||||
$fs: 1em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: $fs;
|
||||
justify-content: start;
|
||||
cursor: pointer;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
font-family: symbolsfont;
|
||||
display: block;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&:after {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
[class*="__label"] {
|
||||
line-height: $fs; // Remove effect on top and bottom padding
|
||||
font-size: $fs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
%c-button {
|
||||
@extend %c-control;
|
||||
background: $colorBtnBg;
|
||||
border-radius: $controlCr;
|
||||
color: $colorBtnFg;
|
||||
padding: nth($btnPad, 1) nth($btnPad, 2);
|
||||
|
||||
&:hover {
|
||||
background: $colorBtnBgHov;
|
||||
color: $colorBtnFgHov;
|
||||
}
|
||||
|
||||
&[class*="--major"] {
|
||||
background: $colorBtnMajorBg;
|
||||
color: $colorBtnMajorFg;
|
||||
|
||||
&:hover {
|
||||
background: $colorBtnMajorBgHov;
|
||||
color: $colorBtnMajorFgHov;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/********* Buttons */
|
||||
// Optionally can include icon in :before via markup
|
||||
.c-button,
|
||||
button {
|
||||
@extend %c-button;
|
||||
}
|
||||
|
||||
/********* Icon Buttons */
|
||||
.c-icon-button {
|
||||
// A clickable element that just includes the icon, no background
|
||||
// Padding is included to facilitate a bigger hit area
|
||||
// Make the icon bigger relative to its container
|
||||
@extend %c-control;
|
||||
$pLR: 3px;
|
||||
$pTB: 3px;
|
||||
border-radius: $controlCr;
|
||||
color: $colorKey;
|
||||
font-size: $fontBaseSize * 1.2;
|
||||
padding: $pTB $pLR ;
|
||||
|
||||
&:hover {
|
||||
background: rgba($colorKey, 0.2);
|
||||
}
|
||||
|
||||
&:before {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
|
||||
/********* Button Sets */
|
||||
.c-button-set {
|
||||
// Buttons are smashed together with minimal margin
|
||||
// c-buttons don't have border-radius between buttons, creates a tool-button-strip look
|
||||
// c-icon-buttons get grouped more closely together
|
||||
// When one set is adjacent to another, provides a divider between
|
||||
|
||||
display: inline-flex;
|
||||
|
||||
> * {
|
||||
// Assume buttons are immediate descendants
|
||||
flex: 0 0 auto;
|
||||
|
||||
&[class^="c-button"] {
|
||||
// Only apply the following to buttons that have background, eg. c-button
|
||||
border-radius: 0;
|
||||
|
||||
+ * {
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: $controlCr;
|
||||
border-bottom-left-radius: $controlCr;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: $controlCr;
|
||||
border-bottom-right-radius: $controlCr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ .c-button-set {
|
||||
$m: $interiorMarginSm;
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: $m;
|
||||
border-right: 1px solid $colorInteriorBorder;
|
||||
margin-right: $m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/********* Menu Buttons */
|
||||
// Always includes :after dropdown arrow
|
||||
// Optionally can include icon in :before
|
||||
// Default menu position is down and to the right
|
||||
// Apply c-menu-button--menus-up and c-menu-button--menus-left to --w wrapper element to modify menu position
|
||||
.c-menu-button {
|
||||
$m: $interiorMarginSm;
|
||||
@extend %c-button;
|
||||
|
||||
&:before {
|
||||
margin-right: $m;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: $glyph-icon-arrow-down;
|
||||
font-family: symbolsfont;
|
||||
margin-left: $m;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&--w {
|
||||
// Wraps c-menu-button, contains button and menu
|
||||
.c-menu {
|
||||
// Default position
|
||||
top: 100%; left: 0;
|
||||
}
|
||||
|
||||
&.c-menu-button--menus-up {
|
||||
.c-menu {
|
||||
top: auto; bottom: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.c-menu-button--menus-left {
|
||||
.c-menu {
|
||||
left: auto; right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** DISCLOSURE CONTROLS */
|
||||
/********* Disclosure Button */
|
||||
// Provides a downward arrow icon that when clicked displays a context menu
|
||||
// Always placed AFTER an element
|
||||
.c-disclosure-button {
|
||||
@extend .c-icon-button;
|
||||
margin-left: $interiorMarginSm;
|
||||
|
||||
&:before {
|
||||
content: $glyph-icon-arrow-down;
|
||||
font-family: symbolsfont;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
||||
/********* Disclosure Triangle */
|
||||
// Provides an arrow icon that when clicked expands an element to reveal its contents.
|
||||
// Used in tree items. Always placed BEFORE an element.
|
||||
.c-disclosure-triangle {
|
||||
$d: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
width: $d;
|
||||
position: relative;
|
||||
|
||||
&.is-enabled:before {
|
||||
$s: .65;
|
||||
content: $glyph-icon-arrow-right-equilateral;
|
||||
display: block;
|
||||
font-family: symbolsfont;
|
||||
font-size: 1rem * $s;
|
||||
position: absolute;
|
||||
transform-origin: floor(($d / 2) * $s); // This is slightly better than 'center'
|
||||
transition: transform 100ms ease-in-out;
|
||||
}
|
||||
|
||||
&--expanded {
|
||||
&:before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************************************************** FORM ELEMENTS */
|
||||
/********* Inline inputs */
|
||||
// A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus
|
||||
.c-input-inline {
|
||||
@include input-base();
|
||||
border: 1px solid transparent;
|
||||
display: block !important;
|
||||
min-width: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
overflow: hidden;
|
||||
transition: all 250ms ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&:not(:focus) {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
padding-left: $inputTextPLeftRight;
|
||||
padding-right: $inputTextPLeftRight;
|
||||
}
|
||||
&:hover {
|
||||
border-color: rgba($colorBodyFg, 0.2);
|
||||
}
|
||||
&:focus {
|
||||
@include nice-input($shdw: rgba(0, 0, 0, 0.6) 0 1px 3px);
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** MENUS */
|
||||
@mixin menuOuter() {
|
||||
border-radius: $basicCr;
|
||||
background: $colorMenuBg;
|
||||
text-shadow: $shdwMenuText;
|
||||
padding: $interiorMarginSm;
|
||||
box-shadow: $shdwMenu;
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 70;
|
||||
}
|
||||
|
||||
@mixin menuInner() {
|
||||
color: $colorMenuFg;
|
||||
li {
|
||||
@extend %c-control;
|
||||
color: $colorMenuFg;
|
||||
display: flex;
|
||||
padding: nth($menuItemPad, 1) nth($menuItemPad, 2);
|
||||
transition: $transIn;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: $colorMenuHovBg;
|
||||
color: $colorMenuHovFg;
|
||||
&:before {
|
||||
color: $colorMenuHovIc;
|
||||
}
|
||||
}
|
||||
&:before {
|
||||
color: $colorMenuIc;
|
||||
font-size: 1em;
|
||||
margin-right: $interiorMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-menu {
|
||||
@include menuOuter();
|
||||
@include menuInner();
|
||||
li {
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid pullForward($colorMenuBg, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-super-menu {
|
||||
// Two column layout, menu items on left with detail of hover element on right
|
||||
@include menuOuter();
|
||||
display: flex;
|
||||
padding: $interiorMarginLg;
|
||||
flex-direction: row;
|
||||
|
||||
> [class*="__"] {
|
||||
$m: $interiorMarginLg;
|
||||
flex: 1 1 50%;
|
||||
&:first-child {
|
||||
margin-right: $m;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-left: 1px solid $colorInteriorBorder;
|
||||
padding-left: $m;
|
||||
}
|
||||
}
|
||||
|
||||
&__menu {
|
||||
@include menuInner();
|
||||
overflow: auto;
|
||||
|
||||
ul {
|
||||
margin-right: $interiorMarginSm; // Fend off scrollbar
|
||||
}
|
||||
|
||||
li {
|
||||
border-radius: $controlCr;
|
||||
}
|
||||
}
|
||||
|
||||
&__item-description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.l-item-description {
|
||||
&__icon,
|
||||
&__description {
|
||||
//flex: 1 1 50%;
|
||||
}
|
||||
|
||||
&__name,
|
||||
&__description {
|
||||
margin-top: $interiorMarginLg;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
min-height: 20%;
|
||||
margin: 10% 25%;
|
||||
}
|
||||
|
||||
&__name {
|
||||
flex: 0 0 auto;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: $fontBaseSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
382
src/styles-new/_global.scss
Normal file
382
src/styles-new/_global.scss
Normal file
@ -0,0 +1,382 @@
|
||||
/************************** FONTS */
|
||||
@font-face {
|
||||
/*
|
||||
* Use https://icomoon.io/app with icomoon-project-openmct-symbols-16px.json to generate font files
|
||||
*/
|
||||
font-family: 'symbolsfont';
|
||||
src: url($dirFonts + 'openmct-symbols-16px.eot');
|
||||
src: url($dirFonts + 'openmct-symbols-16px.eot?#iefix') format('embedded-opentype'),
|
||||
url($dirFonts + 'openmct-symbols-16px.woff') format('woff'),
|
||||
url($dirFonts + 'openmct-symbols-16px.ttf') format('truetype'),
|
||||
url($dirFonts + 'openmct-symbols-16px.svg') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
/*
|
||||
* Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json to generate font files
|
||||
*/
|
||||
font-family: 'symbolsfont-12px';
|
||||
src: url($dirFonts + 'openmct-symbols-12px.eot');
|
||||
src: url($dirFonts + 'openmct-symbols-12px.eot?#iefix') format('embedded-opentype'),
|
||||
url($dirFonts + 'openmct-symbols-12px.woff') format('woff'),
|
||||
url($dirFonts + 'openmct-symbols-12px.ttf') format('truetype'),
|
||||
url($dirFonts + 'openmct-symbols-12px.svg') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/******************************* RESETS */
|
||||
*,
|
||||
:before,
|
||||
:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/******************************* UTILITIES */
|
||||
.u-contents {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.u-space {
|
||||
// Provides a separator space between elements
|
||||
&--right {
|
||||
+ [class*="__"] {
|
||||
margin-left: $interiorMarginLg !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************* BROWSER ELEMENTS */
|
||||
body.desktop {
|
||||
::-webkit-scrollbar {
|
||||
box-sizing: border-box;
|
||||
box-shadow: inset $scrollbarTrackShdw;
|
||||
background-color: $scrollbarTrackColorBg;
|
||||
height: $scrollbarTrackSize;
|
||||
width: $scrollbarTrackSize;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
box-sizing: border-box;
|
||||
background: $scrollbarThumbColor;
|
||||
&:hover {
|
||||
background: $scrollbarThumbColorHov;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay ::-webkit-scrollbar-thumb {
|
||||
background: $scrollbarThumbColorOverlay;
|
||||
&:hover {
|
||||
background: $scrollbarThumbColorOverlayHov;
|
||||
}
|
||||
}
|
||||
|
||||
.menu ::-webkit-scrollbar-thumb {
|
||||
background: $scrollbarThumbColorMenu;
|
||||
&:hover {
|
||||
background: $scrollbarThumbColorMenuHov;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/************************** HTML ENTITIES */
|
||||
a {
|
||||
color: $colorA;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
color: $colorAHov;
|
||||
}
|
||||
}
|
||||
|
||||
body, html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: $fontBaseSize;
|
||||
font-weight: normal;
|
||||
background-color: $colorBodyBg;
|
||||
color: $colorBodyFg;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
letter-spacing: inherit;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=search],
|
||||
input[type=number] {
|
||||
@include nice-input();
|
||||
padding: $inputTextP;
|
||||
&.numeric {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
letter-spacing: 0.04em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1em;
|
||||
font-weight: normal !important;
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 120%;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************** HAS */
|
||||
// Local Controls: Controls placed in proximity to or overlaid on components and views
|
||||
body.desktop .has-local-controls {
|
||||
// Provides hover ability to show local controls
|
||||
&:hover [class*="local-controls--hidden"] {
|
||||
transition: opacity 50ms ease-in-out;
|
||||
opacity: 1;
|
||||
pointer-events: inherit;
|
||||
}
|
||||
|
||||
[class*="local-controls--hidden"] {
|
||||
transition: opacity 500ms ease-in-out;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
//[class*="local-controls"] {
|
||||
// // An explicit outer holder for controls. Typically placed in upper right.
|
||||
// //font-size: 0.7rem;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: flex-end;
|
||||
//
|
||||
//
|
||||
// &.h-local-controls-overlay-content {
|
||||
// // Imagery controls
|
||||
// $p: $interiorMargin;
|
||||
// position: absolute;
|
||||
// top: $p; right: $p;
|
||||
// z-index: 2;
|
||||
// }
|
||||
|
||||
//.l-btn-set,
|
||||
//.s-button {
|
||||
// &:not(:first-child) {
|
||||
// margin-left: $interiorMargin;
|
||||
// }
|
||||
//}
|
||||
//}
|
||||
|
||||
/************************** LEGACY */
|
||||
|
||||
mct-container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.abs {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.code {
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-size: 0.7em;
|
||||
line-height: 150%;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.codehilite {
|
||||
@extend .code;
|
||||
background-color: rgba($colorBodyFg, 0.1);
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.disabled,
|
||||
a.disabled {
|
||||
opacity: $controlDisabledOpacity;
|
||||
pointer-events: none !important;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.s-status-missing {
|
||||
// Labels. Expects .s-status-missing to be applied to mct-representation that contains
|
||||
.t-object-label .t-item-icon:before {
|
||||
content: $glyph-icon-object-unknown;
|
||||
}
|
||||
|
||||
// Item, grid item. Expects .s-status-missing to be applied to mct-representation that contains .item.grid-item
|
||||
.item .t-item-icon-glyph:before {
|
||||
content: $glyph-icon-object-unknown;
|
||||
}
|
||||
|
||||
// Object header. Expects .s-status-missing to be applied to mct-representation.object-header
|
||||
&.object-header {
|
||||
.type-icon:before {
|
||||
content: $glyph-icon-object-unknown;
|
||||
}
|
||||
}
|
||||
|
||||
// Tree item. Expects .s-status-missing to be applied to .tree-item,
|
||||
// and mct-representation.search-item
|
||||
&.tree-item,
|
||||
&.search-item {
|
||||
> .rep-object-label .t-item-icon:before {
|
||||
content: $glyph-icon-object-unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-selection {
|
||||
// aka selection = "None". Used in palettes and their menu buttons.
|
||||
$c: red;
|
||||
$s: 48%;
|
||||
$e: 52%;
|
||||
background-image: linear-gradient(-45deg,
|
||||
transparent $s - 5%,
|
||||
$c $s,
|
||||
$c $e,
|
||||
transparent $e + 5%
|
||||
);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.scrolling,
|
||||
.scroll {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.vscroll {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
&.scroll-pad {
|
||||
padding-right: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
.vscroll--persist {
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.slidable {
|
||||
cursor: move; // Fallback
|
||||
cursor: grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: -webkit-grab;
|
||||
&.horz {
|
||||
cursor: col-resize;
|
||||
}
|
||||
&.vert {
|
||||
cursor: row-resize;
|
||||
}
|
||||
}
|
||||
|
||||
.no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ds {
|
||||
box-shadow: rgba(#000, 0.7) 0 4px 10px 2px;
|
||||
}
|
||||
|
||||
.capitalize {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.hide,
|
||||
.hidden,
|
||||
.t-main-view .hide-in-t-main-view {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hide-nice {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0 !important;
|
||||
transform: scale(0);
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.sep {
|
||||
color: rgba(#fff, 0.2);
|
||||
}
|
||||
|
||||
.comma-list span {
|
||||
&:not(:first-child) {
|
||||
&:before {
|
||||
content: ', ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************** TEMP LEGACY FIXES */
|
||||
.t-imagery {
|
||||
display: contents;
|
||||
}
|
181
src/styles-new/_glyphs.scss
Normal file
181
src/styles-new/_glyphs.scss
Normal file
@ -0,0 +1,181 @@
|
||||
@mixin glyphBefore($unicode, $family: 'symbolsfont') {
|
||||
&:before {
|
||||
content: $unicode;
|
||||
font-family: $family;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin glyphAfter($unicode, $family: 'symbolsfont') {
|
||||
&:after {
|
||||
content: $unicode;
|
||||
font-family: $family;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin glyphBg($glyphUrl) {
|
||||
background-image: $glyphUrl;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
[class*="icon-"] {
|
||||
&:before, &:after
|
||||
{
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
}
|
||||
|
||||
/************************** 16 PX CLASSES */
|
||||
.icon-alert-rect { @include glyphBefore($glyph-icon-alert-rect); }
|
||||
.icon-alert-triangle { @include glyphBefore($glyph-icon-alert-triangle); }
|
||||
.icon-arrow-down { @include glyphBefore($glyph-icon-arrow-down); }
|
||||
.icon-arrow-left { @include glyphBefore($glyph-icon-arrow-left); }
|
||||
.icon-arrow-right { @include glyphBefore($glyph-icon-arrow-right); }
|
||||
.icon-arrow-double-up { @include glyphBefore($glyph-icon-arrow-double-up); }
|
||||
.icon-arrow-tall-up { @include glyphBefore($glyph-icon-arrow-tall-up); }
|
||||
.icon-arrow-tall-down { @include glyphBefore($glyph-icon-arrow-tall-down); }
|
||||
.icon-arrow-double-down { @include glyphBefore($glyph-icon-arrow-double-down); }
|
||||
.icon-arrow-up { @include glyphBefore($glyph-icon-arrow-up); }
|
||||
.icon-asterisk { @include glyphBefore($glyph-icon-asterisk); }
|
||||
.icon-bell { @include glyphBefore($glyph-icon-bell); }
|
||||
.icon-box { @include glyphBefore($glyph-icon-box); }
|
||||
.icon-box-with-arrow { @include glyphBefore($glyph-icon-box-with-arrow); }
|
||||
.icon-check { @include glyphBefore($glyph-icon-check); }
|
||||
.icon-connectivity { @include glyphBefore($glyph-icon-connectivity); }
|
||||
.icon-database-in-brackets { @include glyphBefore($glyph-icon-database-in-brackets); }
|
||||
.icon-eye-open { @include glyphBefore($glyph-icon-eye-open); }
|
||||
.icon-gear { @include glyphBefore($glyph-icon-gear); }
|
||||
.icon-hourglass { @include glyphBefore($glyph-icon-hourglass); }
|
||||
.icon-info { @include glyphBefore($glyph-icon-info); }
|
||||
.icon-link { @include glyphBefore($glyph-icon-link); }
|
||||
.icon-lock { @include glyphBefore($glyph-icon-lock); }
|
||||
.icon-minus { @include glyphBefore($glyph-icon-minus); }
|
||||
.icon-people { @include glyphBefore($glyph-icon-people); }
|
||||
.icon-person { @include glyphBefore($glyph-icon-person); }
|
||||
.icon-plus { @include glyphBefore($glyph-icon-plus); }
|
||||
.icon-trash { @include glyphBefore($glyph-icon-trash); }
|
||||
.icon-x { @include glyphBefore($glyph-icon-x); }
|
||||
.icon-brackets { @include glyphBefore($glyph-icon-brackets); }
|
||||
.icon-crosshair { @include glyphBefore($glyph-icon-crosshair); }
|
||||
.icon-grippy { @include glyphBefore($glyph-icon-grippy); }
|
||||
.icon-arrow-right-equilateral { @include glyphBefore($glyph-icon-arrow-right-equilateral); }
|
||||
.icon-arrows-out { @include glyphBefore($glyph-icon-arrows-out); }
|
||||
.icon-arrows-right-left { @include glyphBefore($glyph-icon-arrows-right-left); }
|
||||
.icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); }
|
||||
.icon-bullet { @include glyphBefore($glyph-icon-bullet); }
|
||||
.icon-calendar { @include glyphBefore($glyph-icon-calendar); }
|
||||
.icon-chain-links { @include glyphBefore($glyph-icon-chain-links); }
|
||||
.icon-collapse-pane-left { @include glyphBefore($glyph-icon-collapse-pane-left); }
|
||||
.icon-collapse-pane-right { @include glyphBefore($glyph-icon-collapse-pane-right); }
|
||||
.icon-download { @include glyphBefore($glyph-icon-download); }
|
||||
.icon-duplicate { @include glyphBefore($glyph-icon-duplicate); }
|
||||
.icon-folder-new { @include glyphBefore($glyph-icon-folder-new); }
|
||||
.icon-fullscreen-collapse { @include glyphBefore($glyph-icon-fullscreen-collapse); }
|
||||
.icon-fullscreen-expand { @include glyphBefore($glyph-icon-fullscreen-expand); }
|
||||
.icon-layers { @include glyphBefore($glyph-icon-layers); }
|
||||
.icon-line-horz { @include glyphBefore($glyph-icon-line-horz); }
|
||||
.icon-magnify { @include glyphBefore($glyph-icon-magnify); }
|
||||
.icon-magnify-in { @include glyphBefore($glyph-icon-magnify-in); }
|
||||
.icon-magnify-out { @include glyphBefore($glyph-icon-magnify-out); }
|
||||
.icon-menu-hamburger { @include glyphBefore($glyph-icon-menu-hamburger); }
|
||||
.icon-move { @include glyphBefore($glyph-icon-move); }
|
||||
.icon-new-window { @include glyphBefore($glyph-icon-new-window); }
|
||||
.icon-paint-bucket { @include glyphBefore($glyph-icon-paint-bucket); }
|
||||
.icon-pause { @include glyphBefore($glyph-icon-pause); }
|
||||
.icon-pencil { @include glyphBefore($glyph-icon-pencil); }
|
||||
.icon-play { @include glyphBefore($glyph-icon-play); }
|
||||
.icon-plot-resource { @include glyphBefore($glyph-icon-plot-resource); }
|
||||
.icon-pointer-left { @include glyphBefore($glyph-icon-pointer-left); }
|
||||
.icon-pointer-right { @include glyphBefore($glyph-icon-pointer-right); }
|
||||
.icon-refresh { @include glyphBefore($glyph-icon-refresh); }
|
||||
.icon-save { @include glyphBefore($glyph-icon-save); }
|
||||
.icon-sine { @include glyphBefore($glyph-icon-sine); }
|
||||
.icon-T { @include glyphBefore($glyph-icon-T); }
|
||||
.icon-thumbs-strip { @include glyphBefore($glyph-icon-thumbs-strip); }
|
||||
.icon-two-parts-both { @include glyphBefore($glyph-icon-two-parts-both); }
|
||||
.icon-two-parts-one-only { @include glyphBefore($glyph-icon-two-parts-one-only); }
|
||||
.icon-resync { @include glyphBefore($glyph-icon-resync); }
|
||||
.icon-reset { @include glyphBefore($glyph-icon-reset); }
|
||||
.icon-x-in-circle { @include glyphBefore($glyph-icon-x-in-circle); }
|
||||
.icon-brightness { @include glyphBefore($glyph-icon-brightness); }
|
||||
.icon-contrast { @include glyphBefore($glyph-icon-contrast); }
|
||||
.icon-expand { @include glyphBefore($glyph-icon-expand); }
|
||||
.icon-list-view { @include glyphBefore($glyph-icon-list-view); }
|
||||
.icon-grid-snap-to { @include glyphBefore($glyph-icon-grid-snap-to); }
|
||||
.icon-grid-snap-no { @include glyphBefore($glyph-icon-grid-snap-no); }
|
||||
.icon-frame-show { @include glyphBefore($glyph-icon-frame-show); }
|
||||
.icon-frame-hide { @include glyphBefore($glyph-icon-frame-hide); }
|
||||
.icon-import { @include glyphBefore($glyph-icon-import); }
|
||||
.icon-export { @include glyphBefore($glyph-icon-export); }
|
||||
.icon-activity { @include glyphBefore($glyph-icon-activity); }
|
||||
.icon-activity-mode { @include glyphBefore($glyph-icon-activity-mode); }
|
||||
.icon-autoflow-tabular { @include glyphBefore($glyph-icon-autoflow-tabular); }
|
||||
.icon-clock { @include glyphBefore($glyph-icon-clock); }
|
||||
.icon-database { @include glyphBefore($glyph-icon-database); }
|
||||
.icon-database-query { @include glyphBefore($glyph-icon-database-query); }
|
||||
.icon-dataset { @include glyphBefore($glyph-icon-dataset); }
|
||||
.icon-datatable { @include glyphBefore($glyph-icon-datatable); }
|
||||
.icon-dictionary { @include glyphBefore($glyph-icon-dictionary); }
|
||||
.icon-folder { @include glyphBefore($glyph-icon-folder); }
|
||||
.icon-image { @include glyphBefore($glyph-icon-image); }
|
||||
.icon-layout { @include glyphBefore($glyph-icon-layout); }
|
||||
.icon-object { @include glyphBefore($glyph-icon-object); }
|
||||
.icon-object-unknown { @include glyphBefore($glyph-icon-object-unknown); }
|
||||
.icon-packet { @include glyphBefore($glyph-icon-packet); }
|
||||
.icon-page { @include glyphBefore($glyph-icon-page); }
|
||||
.icon-plot-overlay { @include glyphBefore($glyph-icon-plot-overlay); }
|
||||
.icon-plot-stacked { @include glyphBefore($glyph-icon-plot-stacked); }
|
||||
.icon-session { @include glyphBefore($glyph-icon-session); }
|
||||
.icon-tabular { @include glyphBefore($glyph-icon-tabular); }
|
||||
.icon-tabular-lad { @include glyphBefore($glyph-icon-tabular-lad); }
|
||||
.icon-tabular-lad-set { @include glyphBefore($glyph-icon-tabular-lad-set); }
|
||||
.icon-tabular-realtime { @include glyphBefore($glyph-icon-tabular-realtime); }
|
||||
.icon-tabular-scrolling { @include glyphBefore($glyph-icon-tabular-scrolling); }
|
||||
.icon-telemetry { @include glyphBefore($glyph-icon-telemetry); }
|
||||
.icon-telemetry-panel { @include glyphBefore($glyph-icon-telemetry-panel); }
|
||||
.icon-timeline { @include glyphBefore($glyph-icon-timeline); }
|
||||
.icon-timer { @include glyphBefore($glyph-icon-timer); }
|
||||
.icon-topic { @include glyphBefore($glyph-icon-topic); }
|
||||
.icon-box-with-dashed-lines { @include glyphBefore($glyph-icon-box-with-dashed-lines); }
|
||||
.icon-summary-widget { @include glyphBefore($glyph-icon-summary-widget); }
|
||||
.icon-notebook { @include glyphBefore($glyph-icon-notebook); }
|
||||
|
||||
/************************** 12 PX CLASSES */
|
||||
.icon-crosshair-12px { @include glyphBefore($glyph-icon-crosshair,'symbolsfont-12px'); }
|
||||
.icon-folder-12px { @include glyphBefore($glyph-icon-folder,'symbolsfont-12px'); }
|
||||
.icon-list-view-12px { @include glyphBefore($glyph-icon-list-view,'symbolsfont-12px'); }
|
||||
.icon-grippy-12px { @include glyphBefore($glyph-icon-grippy,'symbolsfont-12px'); }
|
||||
|
||||
/************************** GLYPH BG CLASSES */
|
||||
.bg-icon-activity { @include glyphBg($bg-icon-activity); }
|
||||
.bg-icon-activity-mode { @include glyphBg($bg-icon-activity-mode); }
|
||||
.bg-icon-autoflow-tabular { @include glyphBg($bg-icon-autoflow-tabular); }
|
||||
.bg-icon-clock { @include glyphBg($bg-icon-clock); }
|
||||
.bg-icon-database { @include glyphBg($bg-icon-database); }
|
||||
.bg-icon-database-query { @include glyphBg($bg-icon-database-query); }
|
||||
.bg-icon-dataset { @include glyphBg($bg-icon-dataset); }
|
||||
.bg-icon-datatable { @include glyphBg($bg-icon-datatable); }
|
||||
.bg-icon-dictionary { @include glyphBg($bg-icon-dictionary); }
|
||||
.bg-icon-folder { @include glyphBg($bg-icon-folder); }
|
||||
.bg-icon-image { @include glyphBg($bg-icon-image); }
|
||||
.bg-icon-layout { @include glyphBg($bg-icon-layout); }
|
||||
.bg-icon-object { @include glyphBg($bg-icon-object); }
|
||||
.bg-icon-object-unknown { @include glyphBg($bg-icon-object-unknown); }
|
||||
.bg-icon-packet { @include glyphBg($bg-icon-packet); }
|
||||
.bg-icon-page { @include glyphBg($bg-icon-page); }
|
||||
.bg-icon-plot-overlay { @include glyphBg($bg-icon-plot-overlay); }
|
||||
.bg-icon-plot-stacked { @include glyphBg($bg-icon-plot-stacked); }
|
||||
.bg-icon-session { @include glyphBg($bg-icon-session); }
|
||||
.bg-icon-tabular { @include glyphBg($bg-icon-tabular); }
|
||||
.bg-icon-tabular-lad { @include glyphBg($bg-icon-tabular-lad); }
|
||||
.bg-icon-tabular-lad-set { @include glyphBg($bg-icon-tabular-lad-set); }
|
||||
.bg-icon-tabular-realtime { @include glyphBg($bg-icon-tabular-realtime); }
|
||||
.bg-icon-tabular-scrolling { @include glyphBg($bg-icon-tabular-scrolling); }
|
||||
.bg-icon-telemetry { @include glyphBg($bg-icon-telemetry); }
|
||||
.bg-icon-timeline { @include glyphBg($bg-icon-timeline); }
|
||||
.bg-icon-timer { @include glyphBg($bg-icon-timer); }
|
||||
.bg-icon-topic { @include glyphBg($bg-icon-topic); }
|
||||
.bg-icon-box-with-dashed-lines { @include glyphBg($bg-icon-box-with-dashed-lines); }
|
||||
.bg-icon-summary-widget { @include glyphBg($bg-icon-summary-widget); }
|
||||
.bg-icon-notebook { @include glyphBg($bg-icon-notebook); }
|
164
src/styles-new/_mixins.scss
Normal file
164
src/styles-new/_mixins.scss
Normal file
@ -0,0 +1,164 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/************************** VISUALS */
|
||||
@mixin ancillaryIcon($d, $c) {
|
||||
// Used for small icons used in combination with larger icons,
|
||||
// like the link and alert icons in tree items.
|
||||
color: $c;
|
||||
font-size: $d;
|
||||
line-height: $d;
|
||||
height: $d;
|
||||
width: $d;
|
||||
}
|
||||
|
||||
@mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {
|
||||
background-image: linear-gradient(-45deg,
|
||||
rgba($c, $a) 25%, transparent 25%,
|
||||
transparent 50%, rgba($c, $a) 50%,
|
||||
rgba($c, $a) 75%, transparent 75%,
|
||||
transparent 100%
|
||||
);
|
||||
background-repeat: repeat;
|
||||
background-size: $d $d;
|
||||
}
|
||||
|
||||
@mixin bgStripes($c: yellow, $a: 0.1, $bgsize: 5px, $angle: 90deg) {
|
||||
background-image: linear-gradient($angle,
|
||||
rgba($c, $a) 25%, transparent 25%,
|
||||
transparent 50%, rgba($c, $a) 50%,
|
||||
rgba($c, $a) 75%, transparent 75%,
|
||||
transparent 100%
|
||||
);
|
||||
background-repeat: repeat;
|
||||
background-size: $bgsize $bgsize;
|
||||
}
|
||||
|
||||
@mixin bgVertStripes($c: yellow, $a: 0.1, $d: 40px) {
|
||||
@include background-image(linear-gradient(-90deg,
|
||||
rgba($c, $a) 0%, rgba($c, $a) 50%,
|
||||
transparent 50%, transparent 100%
|
||||
));
|
||||
background-repeat: repeat;
|
||||
background-size: $d $d;
|
||||
}
|
||||
|
||||
/************************** LAYOUT */
|
||||
@mixin gridTwoColumn() {
|
||||
display: grid;
|
||||
grid-row-gap: 0;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
align-items: start;
|
||||
|
||||
[class*="header"] {
|
||||
border-radius: $smallCr;
|
||||
background-color: $colorInspectorSectionHeaderBg;
|
||||
color: $colorInspectorSectionHeaderFg;
|
||||
margin: 0 0 $interiorMarginSm 0;
|
||||
padding: $interiorMarginSm $interiorMargin;
|
||||
|
||||
&:not(:first-child) {
|
||||
// Allow multiple headers within a component
|
||||
margin-top: $interiorMarginLg;
|
||||
}
|
||||
}
|
||||
|
||||
[class*="span-all"],
|
||||
[class*="header"] {
|
||||
@include gridTwoColumnSpanCols();
|
||||
}
|
||||
}
|
||||
|
||||
@mixin gridTwoColumnSpanCols() {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
|
||||
/************************** TEXT */
|
||||
@mixin ellipsize() {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@mixin reverseEllipsis() {
|
||||
@include ellipsize();
|
||||
direction: rtl;
|
||||
unicode-bidi:bidi-override;
|
||||
}
|
||||
|
||||
@mixin test($c: #ffcc00, $a: 0.2) {
|
||||
background-color: rgba($c, $a) !important;
|
||||
}
|
||||
|
||||
/************************** CONTROLS, BUTTONS */
|
||||
@mixin input-base() {
|
||||
appearance: none;
|
||||
border: none;
|
||||
border-radius: $controlCr;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: $colorFormFieldErrorBg;
|
||||
color: $colorFormFieldErrorFg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0px 2px) {
|
||||
@include input-base();
|
||||
background: $bg;
|
||||
color: $fg;
|
||||
box-shadow: inset $shdw;
|
||||
}
|
||||
|
||||
@mixin button($bg: $colorBtnBg, $fg: $colorBtnFg, $radius: $controlCr, $shdw: none) {
|
||||
background: $bg;
|
||||
color: $fg;
|
||||
border-radius: $radius;
|
||||
box-shadow: $shdw;
|
||||
}
|
||||
|
||||
/************************** MATH */
|
||||
@function percentToDecimal($p) {
|
||||
@return $p / 100%;
|
||||
}
|
||||
|
||||
@function decimalToPercent($d) {
|
||||
@return percentage($d);
|
||||
}
|
||||
|
||||
/************************** UTILITIES */
|
||||
@mixin browserPrefix($prop, $val) {
|
||||
#{$prop}: $val;
|
||||
-ms-#{$prop}: $val;
|
||||
-moz-#{$prop}: $val;
|
||||
-webkit-#{$prop}: $val;
|
||||
}
|
||||
|
||||
@mixin userSelectNone() {
|
||||
@include browserPrefix(user-select, none);
|
||||
}
|
13
src/styles-new/core.scss
Normal file
13
src/styles-new/core.scss
Normal file
@ -0,0 +1,13 @@
|
||||
@import "vendor/normalize.min.css";
|
||||
@import "sass-base.scss";
|
||||
|
||||
/******************** RENDERS CSS */
|
||||
@import "glyphs";
|
||||
@import "global";
|
||||
@import "controls";
|
||||
|
||||
/******************** LEGACY CSS */
|
||||
$output-bourbon-deprecation-warnings: false;
|
||||
@import "bourbon";
|
||||
@import "legacy-styles";
|
||||
|
233
src/styles-new/fonts/icomoon-project-openmct-symbols-12px.json
Normal file
233
src/styles-new/fonts/icomoon-project-openmct-symbols-12px.json
Normal file
@ -0,0 +1,233 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "openmct-symbols-12px",
|
||||
"lastOpened": 0,
|
||||
"created": 1527031065005
|
||||
},
|
||||
"iconSets": [
|
||||
{
|
||||
"selection": [
|
||||
{
|
||||
"order": 9,
|
||||
"id": 6,
|
||||
"name": "icon12-crosshair",
|
||||
"prevSize": 12,
|
||||
"code": 59696,
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 11,
|
||||
"id": 8,
|
||||
"name": "icon12-grippy",
|
||||
"prevSize": 12,
|
||||
"code": 59697,
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 10,
|
||||
"id": 7,
|
||||
"name": "icon12-list-view",
|
||||
"prevSize": 12,
|
||||
"code": 921666,
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 6,
|
||||
"id": 3,
|
||||
"prevSize": 12,
|
||||
"code": 921865,
|
||||
"name": "icon12-folder",
|
||||
"tempChar": ""
|
||||
}
|
||||
],
|
||||
"id": 0,
|
||||
"metadata": {
|
||||
"name": "openmct-symbols-12px",
|
||||
"importSize": {
|
||||
"width": 279,
|
||||
"height": 384
|
||||
},
|
||||
"designer": "Charles Hacskaylo"
|
||||
},
|
||||
"height": 1024,
|
||||
"prevSize": 12,
|
||||
"icons": [
|
||||
{
|
||||
"id": 6,
|
||||
"paths": [
|
||||
"M597.333 0h-170.667v256h170.667v-256z",
|
||||
"M1024 426.667h-256v170.667h256v-170.667z",
|
||||
"M597.333 768h-170.667v256h170.667v-256z",
|
||||
"M256 426.667h-256v170.667h256v-170.667z"
|
||||
],
|
||||
"attrs": [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"grid": 0,
|
||||
"tags": [
|
||||
"icon12-crosshair"
|
||||
],
|
||||
"colorPermutations": {
|
||||
"1161751": [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"paths": [
|
||||
"M186.347 232.64c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z",
|
||||
"M186.347 511.867c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z",
|
||||
"M186.347 791.36c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z",
|
||||
"M465.573 93.173c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z",
|
||||
"M465.573 372.4c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z",
|
||||
"M379.028 558.728c51.328 3.652 89.978 48.223 86.325 99.551s-48.223 89.978-99.551 86.325c-51.328-3.652-89.978-48.223-86.325-99.551s48.223-89.978 99.551-86.325z",
|
||||
"M379.017 837.96c51.328 3.652 89.978 48.223 86.325 99.551s-48.223 89.978-99.551 86.325c-51.328-3.652-89.978-48.223-86.325-99.551s48.223-89.978 99.551-86.325z",
|
||||
"M744.773 232.64c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z",
|
||||
"M744.773 511.867c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z",
|
||||
"M744.773 791.36c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z"
|
||||
],
|
||||
"attrs": [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"width": 745,
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"grid": 0,
|
||||
"tags": [
|
||||
"icon12-grippy"
|
||||
],
|
||||
"colorPermutations": {
|
||||
"1161751": [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"paths": [
|
||||
"M0 0h1024v170.667h-1024v-170.667z",
|
||||
"M0 426.667h1024v170.667h-1024v-170.667z",
|
||||
"M0 853.333h1024v170.667h-1024v-170.667z"
|
||||
],
|
||||
"attrs": [
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"grid": 0,
|
||||
"tags": [
|
||||
"icon12-list-view"
|
||||
],
|
||||
"colorPermutations": {
|
||||
"1161751": [
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"paths": [
|
||||
"M938.667 170.667h-341.333l-110.32-110.32c-33.2-33.2-98.667-60.347-145.68-60.347h-256c-47.073 0.136-85.197 38.26-85.333 85.32l-0 341.346c0.136-47.073 38.26-85.197 85.32-85.333l853.346-0c47.073 0.136 85.197 38.26 85.333 85.32l0-170.654c-0.136-47.073-38.26-85.197-85.32-85.333z",
|
||||
"M85.333 426.667h853.333c47.128 0 85.333 38.205 85.333 85.333v426.667c0 47.128-38.205 85.333-85.333 85.333h-853.333c-47.128 0-85.333-38.205-85.333-85.333v-426.667c0-47.128 38.205-85.333 85.333-85.333z"
|
||||
],
|
||||
"attrs": [],
|
||||
"isMulticolor": false,
|
||||
"grid": 0,
|
||||
"tags": [
|
||||
"icon12-folder"
|
||||
],
|
||||
"colorPermutations": {
|
||||
"1161751": [
|
||||
{
|
||||
"f": 0
|
||||
},
|
||||
{
|
||||
"f": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"invisible": false,
|
||||
"colorThemes": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
[
|
||||
0,
|
||||
161,
|
||||
75,
|
||||
1
|
||||
]
|
||||
]
|
||||
],
|
||||
"colorThemeIdx": 0
|
||||
}
|
||||
],
|
||||
"preferences": {
|
||||
"showGlyphs": true,
|
||||
"showCodes": true,
|
||||
"showQuickUse": true,
|
||||
"showQuickUse2": true,
|
||||
"showSVGs": true,
|
||||
"fontPref": {
|
||||
"prefix": "icon-",
|
||||
"metadata": {
|
||||
"fontFamily": "openmct-symbols-12px",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 0
|
||||
},
|
||||
"metrics": {
|
||||
"emSize": 1024,
|
||||
"baseline": 6.25,
|
||||
"whitespace": 50
|
||||
},
|
||||
"embed": false
|
||||
},
|
||||
"imagePref": {
|
||||
"prefix": "icon-",
|
||||
"png": true,
|
||||
"useClassSelector": true,
|
||||
"color": 0,
|
||||
"bgColor": 16777215
|
||||
},
|
||||
"historySize": 100,
|
||||
"gridSize": 16
|
||||
},
|
||||
"uid": -1
|
||||
}
|
3760
src/styles-new/fonts/icomoon-project-openmct-symbols-16px.json
Normal file
3760
src/styles-new/fonts/icomoon-project-openmct-symbols-16px.json
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user