Compare commits

..

14 Commits

Author SHA1 Message Date
akhenry
24fcad8152 Continuing work on table tests 2018-09-11 10:09:59 -07:00
akhenry
a6f9e0420c Fixed telemetry table object initialization 2018-09-10 17:50:19 +01:00
akhenry
ccfd9eed00 Defined test spec for Telemetry Table plugin 2018-09-10 17:48:26 +01:00
akhenry
df0ee1f99b Added spec for BoundedTableRowCollection 2018-09-10 17:13:21 +01:00
akhenry
dacbf928a1 Shortcut sortedIndex in insert if value is outside of first or last value in collection 2018-09-05 13:31:54 +01:00
akhenry
6153dce261 CSVExporter now only exports visible columns. Updated CSVExporter to ES6 exports / imports 2018-09-04 17:24:36 +01:00
akhenry
0fcddb3547 Allow 'editable' property on view providers to optionally be a function 2018-09-04 12:08:52 +01:00
charlesh88
c6628b6e72 Convert CSS to BEM - done
- Cleanup and organization;
2018-08-31 18:34:45 -07:00
charlesh88
ec7889e5ff Convert CSS to BEM - WIP!
- Near done, converted tabular-holder from legacy;
- Unit testing in main view and in Layout frames;
- Modded legacy CSS to properly hide control-bar with new naming
when in Layout frame;
2018-08-31 18:16:27 -07:00
charlesh88
416d8f60fe Convert CSS to BEM - WIP!
- All in progress;
- Table body divorced from legacy;
2018-08-31 17:47:40 -07:00
charlesh88
74293d4fda Convert CSS to BEM - WIP!
- All in progress;
- Sizing table divorced from legacy;
2018-08-31 16:52:35 -07:00
charlesh88
73fc686851 Reset legacy file, undo unintended change commit 2018-08-31 16:40:07 -07:00
charlesh88
d21abd95b1 Convert CSS to BEM - WIP!
- All in progress;
- Headers table divorced from old;
- Sizing working properly at this point;
2018-08-31 16:38:36 -07:00
Pete Richards
eb5ef28a73 [Table] Use Vue SFCs
Use Vue SFCs.  Use inject/provide to pass services to components
instead of wrapping components in closures.
2018-08-31 13:15:11 -07:00
124 changed files with 5481 additions and 5965 deletions

2
app.js
View File

@@ -16,7 +16,7 @@ const request = require('request');
// Defaults
options.port = options.port || options.p || 8080;
options.host = options.host || options.h || 'localhost';
options.host = options.host || options.h || 'localhost'
options.directory = options.directory || options.D || '.';
// Show command line options

View File

@@ -75,7 +75,6 @@
}));
openmct.install(openmct.plugins.SummaryWidget());
openmct.install(openmct.plugins.Notebook());
openmct.install(openmct.plugins.FolderView());
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
openmct.time.timeSystem('utc');
openmct.start();

View File

@@ -66,4 +66,5 @@
</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>

View File

@@ -319,12 +319,6 @@ define([
]
}
],
"templates": [
{
key: "elementsPool",
template: elementsTemplate
}
],
"components": [
{
"type": "decorator",

View File

@@ -56,7 +56,7 @@ define([
};
DurationFormat.prototype.validate = function (text) {
return moment.utc(text, DATE_FORMATS, true).isValid();
return moment.utc(text, DATE_FORMATS).isValid();
};
return DurationFormat;

View File

@@ -29,7 +29,6 @@ define([
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
DATE_FORMATS = [
DATE_FORMAT,
DATE_FORMAT + "Z",
"YYYY-MM-DD HH:mm:ss",
"YYYY-MM-DD HH:mm",
"YYYY-MM-DD"
@@ -53,14 +52,70 @@ define([
this.key = "utc";
}
/**
* Returns an appropriate time format based on the provided value and
* the threshold required.
* @private
*/
function getScaledFormat(d) {
var momentified = moment.utc(d);
/**
* Uses logic from d3 Time-Scales, v3 of the API. See
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
*
* Licensed
*/
var format = [
[".SSS", function (m) {
return m.milliseconds();
}],
[":ss", function (m) {
return m.seconds();
}],
["HH:mm", function (m) {
return m.minutes();
}],
["HH", function (m) {
return m.hours();
}],
["ddd DD", function (m) {
return m.days() &&
m.date() !== 1;
}],
["MMM DD", function (m) {
return m.date() !== 1;
}],
["MMMM", function (m) {
return m.month();
}],
["YYYY", function () {
return true;
}]
].filter(function (row) {
return row[1](momentified);
})[0][0];
if (format !== undefined) {
return moment.utc(d).format(format);
}
}
/**
* @param {number} value The value to format.
* @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor
* and plot axes. Specifies the smallest number on the scale.
* @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor
* and plot axes. Specifies the largest number on the scale
* @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor
* and plot axes. The number of labels on the scale.
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
* in the array.
*/
UTCTimeFormat.prototype.format = function (value) {
if (value !== undefined) {
if (arguments.length > 1) {
return getScaledFormat(value);
} else if (value !== undefined) {
return moment.utc(value).format(DATE_FORMAT) + "Z";
} else {
return value;
@@ -75,7 +130,7 @@ define([
};
UTCTimeFormat.prototype.validate = function (text) {
return moment.utc(text, DATE_FORMATS, true).isValid();
return moment.utc(text, DATE_FORMATS).isValid();
};
return UTCTimeFormat;

View File

@@ -0,0 +1,62 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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/UTCTimeFormat",
"moment"
], function (
UTCTimeFormat,
moment
) {
describe("The UTCTimeFormat class", function () {
var format;
var scale;
beforeEach(function () {
format = new UTCTimeFormat();
scale = {min: 0, max: 0};
});
it("Provides an appropriately scaled time format based on the input" +
" time", function () {
var TWO_HUNDRED_MS = 200;
var THREE_SECONDS = 3000;
var FIVE_MINUTES = 5 * 60 * 1000;
var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000);
var TEN_HOURS = (10 * 60 * 60 * 1000);
var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD");
var APRIL = moment.utc("2016-04", "YYYY-MM");
var TWENTY_SIXTEEN = moment.utc("2016", "YYYY");
expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200");
expect(format.format(THREE_SECONDS, scale)).toBe(":03");
expect(format.format(FIVE_MINUTES, scale)).toBe("00:05");
expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20");
expect(format.format(TEN_HOURS, scale)).toBe("10");
expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03");
expect(format.format(APRIL, scale)).toBe("April");
expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016");
});
});
});

View File

@@ -0,0 +1,43 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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/ConductorRepresenter",
'legacyRegistry'
], function (
ConductorRepresenter,
legacyRegistry
) {
legacyRegistry.register("platform/features/conductor/compatibility", {
"extensions": {
"representers": [
{
"implementation": ConductorRepresenter,
"depends": [
"openmct"
]
}
]
}
});
});

View File

@@ -0,0 +1,95 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 () {
/**
* Representer that provides a compatibility layer between the new
* time conductor and existing representations / views. Listens to
* the v2 time conductor API and generates v1 style events using the
* Angular event bus. This is transitional code code and will be
* removed.
*
* Deprecated immediately as this is temporary code
*
* @deprecated
* @constructor
*/
function ConductorRepresenter(
openmct,
scope,
element
) {
this.timeAPI = openmct.time;
this.scope = scope;
this.element = element;
this.boundsListener = this.boundsListener.bind(this);
this.timeSystemListener = this.timeSystemListener.bind(this);
this.followListener = this.followListener.bind(this);
}
ConductorRepresenter.prototype.boundsListener = function (bounds) {
var timeSystem = this.timeAPI.timeSystem();
this.scope.$broadcast('telemetry:display:bounds', {
start: bounds.start,
end: bounds.end,
domain: timeSystem.key
}, this.timeAPI.clock() !== undefined);
};
ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) {
var bounds = this.timeAPI.bounds();
this.scope.$broadcast('telemetry:display:bounds', {
start: bounds.start,
end: bounds.end,
domain: timeSystem.key
}, this.timeAPI.clock() !== undefined);
};
ConductorRepresenter.prototype.followListener = function () {
this.boundsListener(this.timeAPI.bounds());
};
// Handle a specific representation of a specific domain object
ConductorRepresenter.prototype.represent = function represent(representation) {
if (representation.key === 'browse-object') {
this.destroy();
this.timeAPI.on("bounds", this.boundsListener);
this.timeAPI.on("timeSystem", this.timeSystemListener);
this.timeAPI.on("follow", this.followListener);
}
};
ConductorRepresenter.prototype.destroy = function destroy() {
this.timeAPI.off("bounds", this.boundsListener);
this.timeAPI.off("timeSystem", this.timeSystemListener);
this.timeAPI.off("follow", this.followListener);
};
return ConductorRepresenter;
}
);

View File

@@ -0,0 +1,148 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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/ui/TimeConductorController",
"./src/ui/ConductorAxisController",
"./src/ui/ConductorTOIController",
"./src/ui/ConductorTOIDirective",
"./src/ui/TimeOfInterestController",
"./src/ui/ConductorAxisDirective",
"./src/ui/NumberFormat",
"./src/ui/StringFormat",
"./res/templates/time-conductor.html",
"./res/templates/mode-selector/mode-selector.html",
"./res/templates/mode-selector/mode-menu.html",
"./res/templates/time-of-interest.html",
"legacyRegistry"
], function (
TimeConductorController,
ConductorAxisController,
ConductorTOIController,
ConductorTOIDirective,
TimeOfInterestController,
ConductorAxisDirective,
NumberFormat,
StringFormat,
timeConductorTemplate,
modeSelectorTemplate,
modeMenuTemplate,
timeOfInterest,
legacyRegistry
) {
legacyRegistry.register("platform/features/conductor/core", {
"extensions": {
"controllers": [
{
"key": "TimeConductorController",
"implementation": TimeConductorController,
"depends": [
"$scope",
"$window",
"openmct",
"formatService",
"CONDUCTOR_CONFIG"
]
},
{
"key": "ConductorTOIController",
"implementation": ConductorTOIController,
"depends": [
"$scope",
"openmct",
"formatService"
]
},
{
"key": "TimeOfInterestController",
"implementation": TimeOfInterestController,
"depends": [
"$scope",
"openmct",
"formatService"
]
}
],
"directives": [
{
"key": "conductorAxis",
"implementation": ConductorAxisDirective,
"depends": [
"openmct",
"formatService"
]
},
{
"key": "conductorToi",
"implementation": ConductorTOIDirective
}
],
"templates": [
{
"key": "conductor",
"template": timeConductorTemplate
},
{
"key": "mode-menu",
"template": modeMenuTemplate
},
{
"key": "mode-selector",
"template": modeSelectorTemplate
},
{
"key": "time-of-interest",
"template": timeOfInterest
}
],
"representations": [
{
"key": "time-conductor",
"template": timeConductorTemplate
}
],
"licenses": [
{
"name": "D3: Data-Driven Documents",
"version": "4.1.0",
"author": "Mike Bostock",
"description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.",
"website": "https://d3js.org/",
"copyright": "Copyright 2010-2016 Mike Bostock",
"license": "BSD-3-Clause",
"link": "https://github.com/d3/d3/blob/master/LICENSE"
}
],
"formats": [
{
"key": "number",
"implementation": NumberFormat
},
{
"key": "string",
"implementation": StringFormat
}
]
}
});
});

View File

@@ -0,0 +1,46 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web 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 Web 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.
-->
<div class="w-menu">
<div class="col menu-items">
<ul>
<li ng-repeat="metadata in ngModel.options"
ng-click="ngModel.select(metadata)">
<a ng-mouseover="ngModel.activeMetadata = metadata"
ng-mouseleave="ngModel.activeMetadata = undefined"
class="menu-item-a {{metadata.cssClass}}">
{{metadata.name}}
</a>
</li>
</ul>
</div>
<div class="col menu-item-description">
<div class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div>
<div class="w-title-desc">
<div class="desc-area title">
{{ngModel.activeMetadata.name}}
</div>
<div class="desc-area description">
{{ngModel.activeMetadata.description}}
</div>
</div>
</div>
</div>

View File

@@ -1,9 +1,9 @@
<!--
Open MCT, Copyright (c) 2014-2018, United States Government
Open MCT Web, Copyright (c) 2014-2015, 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
Open MCT Web 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.
@@ -14,8 +14,20 @@
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
-->
<span ng-controller="ClickAwayController as modeController">
<div class="s-menu-button"
ng-click="modeController.toggle()">
<span class="title-label">{{ngModel.selected.name}}</span>
</div>
<div class="menu super-menu mini l-mode-selector-menu"
ng-show="modeController.isActive()">
<mct-include key="'mode-menu'"
ng-model="ngModel">
</mct-include>
</div>
</span>

View File

@@ -0,0 +1,117 @@
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
<div ng-controller="TimeConductorController as tcController"
class="holder grows flex-elem l-flex-row l-time-conductor {{tcController.isFixed ? 'fixed-mode' : 'realtime-mode'}} {{timeSystemModel.selected.metadata.key}}-time-system"
ng-class="{'status-panning': tcController.panning}">
<div class="flex-elem holder time-conductor-icon">
<div class="hand-little"></div>
<div class="hand-big"></div>
</div>
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
<!-- Holds inputs and ticks -->
<div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin">
<form class="l-time-conductor-inputs-holder"
ng-submit="tcController.isFixed ? tcController.setBoundsFromView(boundsModel) : tcController.setOffsetsFromView(boundsModel)">
<span class="l-time-range-w start-w">
<span class="l-time-conductor-inputs">
<span class="l-time-range-input-w start-date">
<span class="title"></span>
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.format,
validate: tcController.validation.validateStart
}"
ng-model="boundsModel"
ng-blur="tcController.setBoundsFromView(boundsModel)"
field="'start'"
class="time-range-input">
</mct-control>
</span>
<span class="l-time-range-input-w time-delta start-delta"
ng-class="{'hide':tcController.isFixed}">
-
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.durationFormat,
validate: tcController.validation.validateStartOffset
}"
ng-model="boundsModel"
ng-blur="tcController.setOffsetsFromView(boundsModel)"
field="'startOffset'"
class="s-input-inline hrs-min-input">
</mct-control>
</span>
</span>
</span>
<span class="l-time-range-w end-w">
<span class="l-time-conductor-inputs">
<span class="l-time-range-input-w end-date"
ng-controller="ToggleController as t2">
<span class="title"></span>
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.format,
validate: tcController.validation.validateEnd
}"
ng-model="boundsModel"
ng-blur="tcController.setBoundsFromView(boundsModel)"
ng-disabled="!tcController.isFixed"
field="'end'"
class="time-range-input">
</mct-control>
</span>
<span class="l-time-range-input-w time-delta end-delta"
ng-class="{'hide': tcController.isFixed}">
+
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.durationFormat,
validate: tcController.validation.validateEndOffset
}"
ng-model="boundsModel"
ng-blur="tcController.setOffsetsFromView(boundsModel)"
field="'endOffset'"
class="s-input-inline hrs-min-input">
</mct-control>
</span>
</span>
</span>
<input type="submit" class="invisible">
</form>
<conductor-axis class="mobile-hide" view-service="tcController.conductorViewService"></conductor-axis>
</div>
<!-- Holds time system and session selectors, and zoom control -->
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
<mct-include
key="'mode-selector'"
ng-model="tcController.menu"
class="holder flex-elem menus-up mode-selector">
</mct-include>
<mct-control
key="'menu-button'"
class="holder flex-elem menus-up time-system"
structure="{
text: timeSystemModel.selected.name,
click: tcController.setTimeSystemFromView,
options: tcController.timeSystemsForClocks[tcController.menu.selected.key]
}">
</mct-control>
<!-- Zoom control -->
<div ng-if="tcController.zoom"
class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
{{currentZoom}}
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span>
<input class="time-conductor-zoom flex-elem" type="range"
ng-model="tcController.currentZoom"
ng-mouseUp="tcController.onZoomStop(tcController.currentZoom)"
ng-change="tcController.onZoom(tcController.currentZoom)"
min="0.01"
step="0.01"
max="0.99" />
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,12 @@
<div class="abs angular-controller"
ng-controller="TimeOfInterestController as toi">
<div class="l-flex-row l-toi">
<span class="flex-elem l-flex-row l-toi-buttons">
<a class="flex-elem t-button-resync icon-button" title="Re-sync Time of Interest"
ng-click="toi.resync()"></a>
<a class="flex-elem t-button-unpin icon-button" title="Unset Time of Interest"
ng-click="toi.dismiss()"></a>
</span>
<span class="flex-elem l-toi-val">{{toi.toiText}}</span>
</div>
</div>

View File

@@ -0,0 +1,236 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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(
[
"d3-selection",
"d3-scale",
"d3-axis"
],
function (d3Selection, d3Scale, d3Axis) {
var PADDING = 1;
/**
* Controller that renders a horizontal time scale spanning the current bounds defined in the time conductor.
* Used by the mct-conductor-axis directive
* @constructor
*/
function ConductorAxisController(openmct, formatService, scope, element) {
// Dependencies
this.formatService = formatService;
this.timeAPI = openmct.time;
this.scope = scope;
this.bounds = this.timeAPI.bounds();
//Bind all class functions to 'this'
Object.keys(ConductorAxisController.prototype).filter(function (key) {
return typeof ConductorAxisController.prototype[key] === 'function';
}).forEach(function (key) {
this[key] = ConductorAxisController.prototype[key].bind(this);
}.bind(this));
this.initialize(element);
}
/**
* @private
*/
ConductorAxisController.prototype.destroy = function () {
this.timeAPI.off('timeSystem', this.changeTimeSystem);
this.timeAPI.off('bounds', this.changeBounds);
this.viewService.off("zoom", this.onZoom);
this.viewService.off("zoom-stop", this.onZoomStop);
};
/**
* @private
*/
ConductorAxisController.prototype.initialize = function (element) {
this.target = element[0].firstChild;
var height = this.target.offsetHeight;
var vis = d3Selection.select(this.target)
.append("svg:svg")
.attr("width", "100%")
.attr("height", height);
this.xAxis = d3Axis.axisTop();
// draw x axis with labels. CSS is used to position them.
this.axisElement = vis.append("g");
if (this.timeAPI.timeSystem() !== undefined) {
this.changeTimeSystem(this.timeAPI.timeSystem());
this.setScale();
}
//Respond to changes in conductor
this.timeAPI.on("timeSystem", this.changeTimeSystem);
this.timeAPI.on("bounds", this.changeBounds);
this.scope.$on("$destroy", this.destroy);
this.viewService.on("zoom", this.onZoom);
this.viewService.on("zoom-stop", this.onZoomStop);
};
/**
* @private
*/
ConductorAxisController.prototype.changeBounds = function (bounds) {
this.bounds = bounds;
if (!this.zooming) {
this.setScale();
}
};
/**
* Set the scale of the axis, based on current conductor bounds.
*/
ConductorAxisController.prototype.setScale = function () {
var width = this.target.offsetWidth;
var timeSystem = this.timeAPI.timeSystem();
var bounds = this.bounds;
if (timeSystem.isUTCBased) {
this.xScale = this.xScale || d3Scale.scaleUtc();
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
} else {
this.xScale = this.xScale || d3Scale.scaleLinear();
this.xScale.domain([bounds.start, bounds.end]);
}
this.xAxis.scale(this.xScale);
this.xScale.range([PADDING, width - PADDING * 2]);
this.axisElement.call(this.xAxis);
this.msPerPixel = (bounds.end - bounds.start) / width;
};
/**
* When the time system changes, update the scale and formatter used for showing times.
* @param timeSystem
*/
ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) {
var key = timeSystem.timeFormat;
if (key !== undefined) {
var format = this.formatService.getFormat(key);
var bounds = this.timeAPI.bounds();
//The D3 scale used depends on the type of time system as d3
// supports UTC out of the box.
if (timeSystem.isUTCBased) {
this.xScale = d3Scale.scaleUtc();
} else {
this.xScale = d3Scale.scaleLinear();
}
this.xAxis.scale(this.xScale);
//Define a custom format function
this.xAxis.tickFormat(function (tickValue) {
// Normalize date representations to numbers
if (tickValue instanceof Date) {
tickValue = tickValue.getTime();
}
return format.format(tickValue, {
min: bounds.start,
max: bounds.end
});
});
this.axisElement.call(this.xAxis);
}
};
/**
* The user has stopped panning the time conductor scale element.
* @event panStop
*/
/**
* Called on release of mouse button after dragging the scale left or right.
* @fires platform.features.conductor.ConductorAxisController~panStop
*/
ConductorAxisController.prototype.panStop = function () {
//resync view bounds with time conductor bounds
this.viewService.emit("pan-stop");
this.timeAPI.bounds(this.bounds);
};
/**
* Rescales the axis when the user zooms. Although zoom ultimately results in a bounds change once the user
* releases the zoom slider, dragging the slider will not immediately change the conductor bounds. It will
* however immediately update the scale and the bounds displayed in the UI.
* @private
* @param {ZoomLevel}
*/
ConductorAxisController.prototype.onZoom = function (zoom) {
this.zooming = true;
this.bounds = zoom.bounds;
this.setScale();
};
/**
* @private
*/
ConductorAxisController.prototype.onZoomStop = function (zoom) {
this.zooming = false;
};
/**
* @event platform.features.conductor.ConductorAxisController~pan
* Fired when the time conductor is panned
*/
/**
* Initiate panning via a click + drag gesture on the time conductor
* scale. Panning triggers a "pan" event
* @param {number} delta the offset from the original click event
* @see TimeConductorViewService#
* @fires platform.features.conductor.ConductorAxisController~pan
*/
ConductorAxisController.prototype.pan = function (delta) {
if (this.timeAPI.clock() === undefined) {
var deltaInMs = delta[0] * this.msPerPixel;
var bounds = this.timeAPI.bounds();
var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000;
var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
this.bounds = {
start: start,
end: end
};
this.setScale();
this.viewService.emit("pan", this.bounds);
}
};
/**
* Invoked on element resize. Will rebuild the scale based on the new dimensions of the element.
*/
ConductorAxisController.prototype.resize = function () {
this.setScale();
};
return ConductorAxisController;
}
);

View File

@@ -0,0 +1,169 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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([
'./ConductorAxisController',
'zepto',
'd3-selection',
'd3-scale'
], function (
ConductorAxisController,
$,
d3Selection,
d3Scale
) {
describe("The ConductorAxisController", function () {
var controller,
mockConductor,
mockConductorViewService,
mockFormatService,
mockScope,
mockBounds,
element,
mockTimeSystem,
mockFormat;
function getCallback(target, name) {
return target.calls.all().filter(function (call) {
return call.args[0] === name;
})[0].args[1];
}
beforeEach(function () {
mockScope = jasmine.createSpyObj("scope", [
"$on"
]);
//Add some HTML elements
mockBounds = {
start: 100,
end: 200
};
mockConductor = jasmine.createSpyObj("conductor", [
"timeSystem",
"bounds",
"on",
"off",
"clock"
]);
mockConductor.bounds.and.returnValue(mockBounds);
mockFormatService = jasmine.createSpyObj("formatService", [
"getFormat"
]);
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
"on",
"off",
"emit"
]);
spyOn(d3Scale, 'scaleUtc').and.callThrough();
spyOn(d3Scale, 'scaleLinear').and.callThrough();
element = $('<div style="width: 100px;"><div style="width: 100%;"></div></div>');
$(document).find('body').append(element);
ConductorAxisController.prototype.viewService = mockConductorViewService;
controller = new ConductorAxisController({time: mockConductor}, mockFormatService, mockScope, element);
mockTimeSystem = {};
mockFormat = jasmine.createSpyObj("format", [
"format"
]);
mockTimeSystem.timeFormat = "mockFormat";
mockFormatService.getFormat.and.returnValue(mockFormat);
mockConductor.timeSystem.and.returnValue(mockTimeSystem);
mockTimeSystem.isUTCBased = false;
});
it("listens for changes to time system and bounds", function () {
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
expect(mockConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
});
it("on scope destruction, deregisters listeners", function () {
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
controller.destroy();
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
});
describe("when the time system changes", function () {
it("uses a UTC scale for UTC time systems", function () {
mockTimeSystem.isUTCBased = true;
controller.changeTimeSystem(mockTimeSystem);
expect(d3Scale.scaleUtc).toHaveBeenCalled();
expect(d3Scale.scaleLinear).not.toHaveBeenCalled();
});
it("uses a linear scale for non-UTC time systems", function () {
mockTimeSystem.isUTCBased = false;
controller.changeTimeSystem(mockTimeSystem);
expect(d3Scale.scaleLinear).toHaveBeenCalled();
expect(d3Scale.scaleUtc).not.toHaveBeenCalled();
});
it("sets axis domain to time conductor bounds", function () {
mockTimeSystem.isUTCBased = false;
controller.setScale();
expect(controller.xScale.domain()).toEqual([mockBounds.start, mockBounds.end]);
});
it("uses the format specified by the time system to format tick" +
" labels", function () {
controller.changeTimeSystem(mockTimeSystem);
expect(mockFormat.format).toHaveBeenCalled();
});
it('responds to zoom events', function () {
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", controller.onZoom);
var cb = getCallback(mockConductorViewService.on, "zoom");
spyOn(controller, 'setScale').and.callThrough();
cb({bounds: {start: 0, end: 100}});
expect(controller.setScale).toHaveBeenCalled();
});
it('adjusts scale on pan', function () {
spyOn(controller, 'setScale').and.callThrough();
controller.pan(100);
expect(controller.setScale).toHaveBeenCalled();
});
it('emits event on pan', function () {
spyOn(controller, 'setScale').and.callThrough();
controller.pan(100);
expect(mockConductorViewService.emit).toHaveBeenCalledWith("pan", jasmine.any(Object));
});
it('cleans up listeners on destruction', function () {
controller.destroy();
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", controller.onZoom);
});
});
});
});

View File

@@ -0,0 +1,56 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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(['./ConductorAxisController'], function (ConductorAxisController) {
function ConductorAxisDirective() {
/**
* The mct-conductor-axis renders a horizontal axis with regular
* labelled 'ticks'. It requires 'start' and 'end' integer values to
* be specified as attributes.
*/
return {
controller: [
'openmct',
'formatService',
'$scope',
'$element',
ConductorAxisController
],
controllerAs: 'axis',
scope: {
viewService: "="
},
bindToController: true,
restrict: 'E',
priority: 1000,
template: '<div class="l-axis-holder" ' +
' mct-drag-down="axis.panStart()"' +
' mct-drag-up="axis.panStop(delta)"' +
' mct-drag="axis.pan(delta)"' +
' mct-resize="axis.resize()"></div>'
};
}
return ConductorAxisDirective;
});

View File

@@ -0,0 +1,123 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 ($) {
/**
* Controller for the Time of Interest indicator in the conductor itself. Sets the horizontal position of the
* TOI indicator based on the current value of the TOI, and the width of the TOI conductor.
* @memberof platform.features.conductor
*/
function ConductorTOIController($scope, openmct) {
this.timeAPI = openmct.time;
//Bind all class functions to 'this'
Object.keys(ConductorTOIController.prototype).filter(function (key) {
return typeof ConductorTOIController.prototype[key] === 'function';
}).forEach(function (key) {
this[key] = ConductorTOIController.prototype[key].bind(this);
}.bind(this));
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
this.viewService.on('zoom', this.setOffsetFromZoom);
this.viewService.on('pan', this.setOffsetFromBounds);
var timeOfInterest = this.timeAPI.timeOfInterest();
if (timeOfInterest) {
this.changeTimeOfInterest(timeOfInterest);
}
$scope.$on('$destroy', this.destroy);
}
/**
* @private
*/
ConductorTOIController.prototype.destroy = function () {
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
this.viewService.off('zoom', this.setOffsetFromZoom);
this.viewService.off('pan', this.setOffsetFromBounds);
};
/**
* Given some bounds, set horizontal position of TOI indicator based
* on current conductor TOI value. Bounds are provided so that
* ephemeral bounds from zoom and pan events can be used as well
* as current conductor bounds, allowing TOI to be updated in
* realtime during scroll and zoom.
* @param {TimeConductorBounds} bounds
*/
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
var toi = this.timeAPI.timeOfInterest();
if (toi !== undefined) {
var offset = toi - bounds.start;
var duration = bounds.end - bounds.start;
this.left = offset / duration * 100;
this.pinned = true;
} else {
this.left = 0;
this.pinned = false;
}
};
/**
* @private
*/
ConductorTOIController.prototype.setOffsetFromZoom = function (zoom) {
return this.setOffsetFromBounds(zoom.bounds);
};
/**
* Invoked when time of interest changes. Will set the horizontal offset of the TOI indicator.
* @private
*/
ConductorTOIController.prototype.changeTimeOfInterest = function () {
var bounds = this.timeAPI.bounds();
if (bounds) {
this.setOffsetFromBounds(bounds);
}
};
/**
* On a mouse click event within the TOI element, convert position within element to a time of interest, and
* set the time of interest on the conductor.
* @param e The angular $event object
*/
ConductorTOIController.prototype.setTOIFromPosition = function (e) {
//TOI is set using the alt key modified + primary click
if (e.altKey) {
var element = $(e.currentTarget);
var width = element.width();
var relativeX = e.pageX - element.offset().left;
var percX = relativeX / width;
var bounds = this.timeAPI.bounds();
var timeRange = bounds.end - bounds.start;
this.timeAPI.timeOfInterest(timeRange * percX + bounds.start);
}
};
return ConductorTOIController;
}
);

View File

@@ -0,0 +1,153 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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([
'./ConductorTOIController'
], function (
ConductorTOIController
) {
var mockConductor;
var mockConductorViewService;
var mockScope;
var mockAPI;
var conductorTOIController;
function getNamedCallback(thing, name) {
return thing.calls.all().filter(function (call) {
return call.args[0] === name;
}).map(function (call) {
return call.args;
})[0][1];
}
describe("The ConductorTOIController", function () {
beforeEach(function () {
mockConductor = jasmine.createSpyObj("conductor", [
"bounds",
"timeOfInterest",
"on",
"off"
]);
mockAPI = {time: mockConductor};
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
"on",
"off"
]);
mockScope = jasmine.createSpyObj("openMCT", [
"$on"
]);
ConductorTOIController.prototype.viewService = mockConductorViewService;
conductorTOIController = new ConductorTOIController(mockScope, mockAPI);
});
it("listens to changes in the time of interest on the conductor", function () {
expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", jasmine.any(Function));
});
describe("when responding to changes in the time of interest", function () {
var toiCallback;
beforeEach(function () {
var bounds = {
start: 0,
end: 200
};
mockConductor.bounds.and.returnValue(bounds);
toiCallback = getNamedCallback(mockConductor.on, "timeOfInterest");
});
it("calculates the correct horizontal offset based on bounds and current TOI", function () {
//Expect time of interest position to be 50% of element width
mockConductor.timeOfInterest.and.returnValue(100);
toiCallback();
expect(conductorTOIController.left).toBe(50);
//Expect time of interest position to be 25% of element width
mockConductor.timeOfInterest.and.returnValue(50);
toiCallback();
expect(conductorTOIController.left).toBe(25);
//Expect time of interest position to be 0% of element width
mockConductor.timeOfInterest.and.returnValue(0);
toiCallback();
expect(conductorTOIController.left).toBe(0);
//Expect time of interest position to be 100% of element width
mockConductor.timeOfInterest.and.returnValue(200);
toiCallback();
expect(conductorTOIController.left).toBe(100);
});
it("renders the TOI indicator visible", function () {
expect(conductorTOIController.pinned).toBeFalsy();
mockConductor.timeOfInterest.and.returnValue(100);
toiCallback();
expect(conductorTOIController.pinned).toBe(true);
});
});
it("responds to zoom events", function () {
var mockZoom = {
bounds: {
start: 500,
end: 1000
}
};
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", jasmine.any(Function));
// Should correspond to horizontal offset of 50%
mockConductor.timeOfInterest.and.returnValue(750);
var zoomCallback = getNamedCallback(mockConductorViewService.on, "zoom");
zoomCallback(mockZoom);
expect(conductorTOIController.left).toBe(50);
});
it("responds to pan events", function () {
var mockPanBounds = {
start: 1000,
end: 3000
};
expect(mockConductorViewService.on).toHaveBeenCalledWith("pan", jasmine.any(Function));
// Should correspond to horizontal offset of 25%
mockConductor.timeOfInterest.and.returnValue(1500);
var panCallback = getNamedCallback(mockConductorViewService.on, "pan");
panCallback(mockPanBounds);
expect(conductorTOIController.left).toBe(25);
});
it("Cleans up all listeners when controller destroyed", function () {
var zoomCB = getNamedCallback(mockConductorViewService.on, "zoom");
var panCB = getNamedCallback(mockConductorViewService.on, "pan");
var toiCB = getNamedCallback(mockConductor.on, "timeOfInterest");
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
getNamedCallback(mockScope.$on, "$destroy")();
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", zoomCB);
expect(mockConductorViewService.off).toHaveBeenCalledWith("pan", panCB);
expect(mockConductor.off).toHaveBeenCalledWith("timeOfInterest", toiCB);
});
});
});

View File

@@ -0,0 +1,63 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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(['./ConductorTOIController'], function (ConductorTOIController) {
/**
* A directive that encapsulates the TOI specific behavior of the Time Conductor UI.
* @constructor
*/
function ConductorTOIDirective() {
/**
* The mct-conductor-axis renders a horizontal axis with regular
* labelled 'ticks'. It requires 'start' and 'end' integer values to
* be specified as attributes.
*/
return {
controller: [
'$scope',
'openmct',
ConductorTOIController
],
controllerAs: 'toi',
scope: {
viewService: "="
},
bindToController: true,
restrict: 'E',
priority: 1000,
template:
'<div class="l-data-visualization-holder l-row-elem flex-elem">' +
' <a class="l-page-button s-icon-button icon-pointer-left"></a>' +
' <div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">' +
' <mct-include key="\'time-of-interest\'" class="l-toi-holder show-val" ' +
' ng-class="{ pinned: toi.pinned, \'val-to-left\': toi.left > 80 }" ' +
' ng-style="{\'left\': toi.left + \'%\'}"></mct-include>' +
' </div>' +
' <a class="l-page-button align-right s-icon-button icon-pointer-right"></a>' +
'</div>'
};
}
return ConductorTOIDirective;
});

View File

@@ -20,47 +20,35 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import moment from 'moment';
define([], function () {
export default function multiFormat(date) {
var momentified = moment.utc(date);
/**
* Uses logic from d3 Time-Scales, v3 of the API. See
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
* Formatter for basic numbers. Provides basic support for non-UTC
* numbering systems
*
* Licensed
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
var format = [
[".SSS", function (m) {
return m.milliseconds();
}],
[":ss", function (m) {
return m.seconds();
}],
["HH:mm", function (m) {
return m.minutes();
}],
["HH:mm", function (m) {
return m.hours();
}],
["ddd DD", function (m) {
return m.days() &&
m.date() !== 1;
}],
["MMM DD", function (m) {
return m.date() !== 1;
}],
["MMMM", function (m) {
return m.month();
}],
["YYYY", function () {
return true;
}]
].filter(function (row) {
return row[1](momentified);
})[0][0];
if (format !== undefined) {
return moment.utc(date).format(format);
function NumberFormat() {
this.key = 'number';
}
}
NumberFormat.prototype.format = function (value) {
if (isNaN(value)) {
return '';
} else {
return '' + value;
}
};
NumberFormat.prototype.parse = function (text) {
return parseFloat(text);
};
NumberFormat.prototype.validate = function (text) {
return !isNaN(text);
};
return NumberFormat;
});

View File

@@ -0,0 +1,49 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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(['./NumberFormat'], function (NumberFormat) {
describe("The NumberFormat class", function () {
var format;
beforeEach(function () {
format = new NumberFormat();
});
it("The format function takes a string and produces a number", function () {
var text = format.format(1);
expect(text).toBe("1");
expect(typeof text).toBe("string");
});
it("The parse function takes a string and produces a number", function () {
var number = format.parse("1");
expect(number).toBe(1);
expect(typeof number).toBe("number");
});
it("validates that the input is a number", function () {
expect(format.validate("1")).toBe(true);
expect(format.validate(1)).toBe(true);
expect(format.validate("1.1")).toBe(true);
expect(format.validate("abc")).toBe(false);
});
});
});

View File

@@ -18,4 +18,36 @@
* 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 () {
/**
* Formatter for basic strings.
*
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
function StringFormat() {
this.key = 'string';
}
StringFormat.prototype.format = function (string) {
if (typeof string === 'string') {
return string;
} else {
return '' + string;
}
};
StringFormat.prototype.parse = function (string) {
return string;
};
StringFormat.prototype.validate = function (string) {
return typeof string === 'string';
};
return StringFormat;
});

View File

@@ -0,0 +1,554 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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',
'./TimeConductorValidation',
'./TimeConductorViewService'
],
function (
moment,
TimeConductorValidation,
TimeConductorViewService
) {
var timeUnitsMegastructure = [
["Decades", function (r) {
return r.years() > 15;
}],
["Years", function (r) {
return r.years() > 1;
}],
["Months", function (r) {
return r.years() === 1 || r.months() > 1;
}],
["Days", function (r) {
return r.months() === 1 || r.days() > 1;
}],
["Hours", function (r) {
return r.days() === 1 || r.hours() > 1;
}],
["Minutes", function (r) {
return r.hours() === 1 || r.minutes() > 1;
}],
["Seconds", function (r) {
return r.minutes() === 1 || r.seconds() > 1;
}],
["Milliseconds", function (r) {
return true;
}]
];
/**
* Controller for the Time Conductor UI element. The Time Conductor
* includes form fields for specifying time bounds and relative time
* offsets for queries, as well as controls for selection mode,
* time systems, and zooming.
* @memberof platform.features.conductor
* @constructor
*/
function TimeConductorController(
$scope,
$window,
openmct,
formatService,
config
) {
//Bind functions that are used as callbacks to 'this'.
[
"selectMenuOption",
"onPan",
"onPanStop",
"setViewFromBounds",
"setViewFromClock",
"setViewFromOffsets",
"setViewFromTimeSystem",
"setTimeSystemFromView",
"destroy"
].forEach(function (name) {
this[name] = this[name].bind(this);
}.bind(this));
this.$scope = $scope;
this.$window = $window;
this.timeAPI = openmct.time;
this.conductorViewService = new TimeConductorViewService(openmct);
this.validation = new TimeConductorValidation(this.timeAPI);
this.formatService = formatService;
this.config = config;
this.timeSystemsForClocks = {};
this.$scope.timeSystemModel = {};
this.$scope.boundsModel = {};
this.timeSystems = this.timeAPI.getAllTimeSystems().reduce(function (map, timeSystem) {
map[timeSystem.key] = timeSystem;
return map;
}, {});
this.isFixed = this.timeAPI.clock() === undefined;
var options = this.optionsFromConfig(config);
this.menu = {
selected: undefined,
options: options,
select: this.selectMenuOption
};
//Set the initial state of the UI from the conductor state
var timeSystem = this.timeAPI.timeSystem();
if (timeSystem) {
this.setViewFromTimeSystem(timeSystem);
}
this.setViewFromClock(this.timeAPI.clock());
var offsets = this.timeAPI.clockOffsets();
if (offsets) {
this.setViewFromOffsets(offsets);
}
var bounds = this.timeAPI.bounds();
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
this.setViewFromBounds(bounds);
}
this.conductorViewService.on('pan', this.onPan);
this.conductorViewService.on('pan-stop', this.onPanStop);
//Respond to any subsequent conductor changes
this.timeAPI.on('bounds', this.setViewFromBounds);
this.timeAPI.on('timeSystem', this.setViewFromTimeSystem);
this.timeAPI.on('clock', this.setViewFromClock);
this.timeAPI.on('clockOffsets', this.setViewFromOffsets);
this.$scope.$on('$destroy', this.destroy);
}
/**
* Given a key for a clock, retrieve the clock object.
* @private
* @param key
* @returns {Clock}
*/
TimeConductorController.prototype.getClock = function (key) {
return this.timeAPI.getAllClocks().filter(function (clock) {
return clock.key === key;
})[0];
};
/**
* Activate the selected menu option. Menu options correspond to clocks.
* A distinction is made to avoid confusion between the menu options and
* their metadata, and actual {@link Clock} objects.
*
* @private
* @param newOption
*/
TimeConductorController.prototype.selectMenuOption = function (newOption) {
if (this.menu.selected.key === newOption.key) {
return;
}
this.menu.selected = newOption;
var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock);
if (!config) {
// Clock does not support this timeSystem, fallback to first
// option provided for clock.
config = this.config.menuOptions.filter(function (menuOption) {
return menuOption.clock === (newOption.clock && newOption.clock.key);
})[0];
}
if (config.clock) {
this.timeAPI.clock(config.clock, config.clockOffsets);
this.timeAPI.timeSystem(config.timeSystem);
} else {
this.timeAPI.stopClock();
this.timeAPI.timeSystem(config.timeSystem, config.bounds);
}
};
/**
* From the provided configuration, build the available menu options.
* @private
* @param config
* @returns {*[]}
*/
TimeConductorController.prototype.optionsFromConfig = function (config) {
/*
* "Fixed Mode" is always the first available option.
*/
var options = [{
key: 'fixed',
name: 'Fixed Timespan Mode',
description: 'Query and explore data that falls between two fixed datetimes.',
cssClass: 'icon-calendar'
}];
var clocks = {};
var timeSystemsForClocks = this.timeSystemsForClocks;
(config.menuOptions || []).forEach(function (menuOption) {
var clockKey = menuOption.clock || 'fixed';
var clock = this.getClock(clockKey);
if (clock !== undefined) {
clocks[clock.key] = clock;
}
var timeSystem = this.timeSystems[menuOption.timeSystem];
if (timeSystem !== undefined) {
timeSystemsForClocks[clockKey] = timeSystemsForClocks[clockKey] || [];
timeSystemsForClocks[clockKey].push(timeSystem);
}
}, this);
/*
* Populate the clocks menu with metadata from the available clocks
*/
Object.values(clocks).forEach(function (clock) {
options.push({
key: clock.key,
name: clock.name,
description: "Monitor streaming data in real-time. The Time " +
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
cssClass: clock.cssClass || 'icon-clock',
clock: clock
});
}.bind(this));
return options;
};
/**
* When bounds change, set UI values from the new bounds.
* @param {TimeBounds} bounds the bounds
*/
TimeConductorController.prototype.setViewFromBounds = function (bounds) {
if (!this.zooming && !this.panning) {
this.$scope.boundsModel.start = bounds.start;
this.$scope.boundsModel.end = bounds.end;
if (this.supportsZoom()) {
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
this.currentZoom = this.toSliderValue(bounds.end - bounds.start, config.zoomOutLimit, config.zoomInLimit);
this.toTimeUnits(bounds.end - bounds.start);
}
/*
Ensure that a digest occurs, capped at the browser's refresh
rate.
*/
if (!this.pendingUpdate) {
this.pendingUpdate = true;
this.$window.requestAnimationFrame(function () {
this.pendingUpdate = false;
this.$scope.$digest();
}.bind(this));
}
}
};
/**
* Retrieve any configuration defined for the provided time system and
* clock
* @private
* @param timeSystem
* @param clock
* @returns {object} The Time Conductor configuration corresponding to
* the provided combination of time system and clock
*/
TimeConductorController.prototype.getConfig = function (timeSystem, clock) {
var clockKey = clock && clock.key;
var timeSystemKey = timeSystem && timeSystem.key;
var option = this.config.menuOptions.filter(function (menuOption) {
return menuOption.timeSystem === timeSystemKey && menuOption.clock === clockKey;
})[0];
return option;
};
/**
* When the clock offsets change, update the values in the UI
* @param {ClockOffsets} offsets
* @private
*/
TimeConductorController.prototype.setViewFromOffsets = function (offsets) {
this.$scope.boundsModel.startOffset = Math.abs(offsets.start);
this.$scope.boundsModel.endOffset = offsets.end;
};
/**
* When form values for bounds change, update the bounds in the Time API
* to trigger an application-wide bounds change.
* @param {object} boundsModel
*/
TimeConductorController.prototype.setBoundsFromView = function (boundsModel) {
var bounds = this.timeAPI.bounds();
if (boundsModel.start !== bounds.start || boundsModel.end !== bounds.end) {
this.timeAPI.bounds({
start: boundsModel.start,
end: boundsModel.end
});
}
};
/**
* When form values for bounds change, update the bounds in the Time API
* to trigger an application-wide bounds change.
* @param {object} formModel
*/
TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) {
if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) {
var offsets = {
start: 0 - boundsModel.startOffset,
end: boundsModel.endOffset
};
var existingOffsets = this.timeAPI.clockOffsets();
if (offsets.start !== existingOffsets.start || offsets.end !== existingOffsets.end) {
//Sychronize offsets between form and time API
this.timeAPI.clockOffsets(offsets);
}
}
};
/**
* @private
* @returns {boolean}
*/
TimeConductorController.prototype.supportsZoom = function () {
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
return config && (config.zoomInLimit !== undefined && config.zoomOutLimit !== undefined);
};
/**
* Update the UI state to reflect a change in clock. Provided conductor
* configuration will be checked for compatibility between the new clock
* and the currently selected time system. If configuration is not available,
* an attempt will be made to default to a time system that is compatible
* with the new clock
*
* @private
* @param {Clock} clock
*/
TimeConductorController.prototype.setViewFromClock = function (clock) {
var newClockKey = clock ? clock.key : 'fixed';
var timeSystems = this.timeSystemsForClocks[newClockKey];
var menuOption = this.menu.options.filter(function (option) {
return option.key === (newClockKey);
})[0];
this.menu.selected = menuOption;
//Try to find currently selected time system in time systems for clock
var selectedTimeSystem = timeSystems.filter(function (timeSystem) {
return timeSystem.key === this.$scope.timeSystemModel.selected.key;
}.bind(this))[0];
var config = this.getConfig(selectedTimeSystem, clock);
if (selectedTimeSystem === undefined) {
selectedTimeSystem = timeSystems[0];
config = this.getConfig(selectedTimeSystem, clock);
if (clock === undefined) {
this.timeAPI.timeSystem(selectedTimeSystem, config.bounds);
} else {
//When time system changes, some start bounds need to be provided
this.timeAPI.timeSystem(selectedTimeSystem, {
start: clock.currentValue() + config.clockOffsets.start,
end: clock.currentValue() + config.clockOffsets.end
});
}
}
this.isFixed = clock === undefined;
if (clock === undefined) {
this.setViewFromBounds(this.timeAPI.bounds());
}
this.zoom = this.supportsZoom();
this.$scope.timeSystemModel.options = timeSystems;
};
/**
* Respond to time system selection from UI
*
* Allows time system to be changed by key. This supports selection
* from the menu. Resolves a TimeSystem object and then invokes
* TimeConductorController#setTimeSystem
* @param key
* @see TimeConductorController#setTimeSystem
*/
TimeConductorController.prototype.setTimeSystemFromView = function (key) {
var clock = this.menu.selected.clock;
var timeSystem = this.timeSystems[key];
var config = this.getConfig(timeSystem, clock);
this.$scope.timeSystemModel.selected = timeSystem;
this.$scope.timeSystemModel.format = timeSystem.timeFormat;
if (clock === undefined) {
this.timeAPI.timeSystem(timeSystem, config.bounds);
} else {
this.timeAPI.clock(clock, config.clockOffsets);
this.timeAPI.timeSystem(timeSystem);
}
};
/**
* Handles time system change from time conductor
*
* Sets the selected time system. Will populate form with the default
* bounds and offsets defined in the selected time system.
*
* @param newTimeSystem
*/
TimeConductorController.prototype.setViewFromTimeSystem = function (timeSystem) {
var oldKey = (this.$scope.timeSystemModel.selected || {}).key;
var timeSystemModel = this.$scope.timeSystemModel;
if (timeSystem && (timeSystem.key !== oldKey)) {
var config = this.getConfig(timeSystem, this.timeAPI.clock());
timeSystemModel.selected = timeSystem;
timeSystemModel.format = timeSystem.timeFormat;
timeSystemModel.durationFormat = timeSystem.durationFormat;
if (this.supportsZoom()) {
timeSystemModel.minZoom = config.zoomOutLimit;
timeSystemModel.maxZoom = config.zoomInLimit;
}
}
this.zoom = this.supportsZoom();
};
/**
* Takes a time span and calculates a slider increment value, used
* to set the horizontal offset of the slider.
* @private
* @param {number} timeSpan a duration of time, in ms
* @returns {number} a value between 0.01 and 0.99, in increments of .01
*/
TimeConductorController.prototype.toSliderValue = function (timeSpan, zoomOutLimit, zoomInLimit) {
var perc = timeSpan / (zoomOutLimit - zoomInLimit);
return 1 - Math.pow(perc, 1 / 4);
};
/**
* Given a time span, set a label for the units of time that it,
* roughly, represents. Leverages
* @param {TimeSpan} timeSpan
*/
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
var timeSystem = this.timeAPI.timeSystem();
if (timeSystem && timeSystem.isUTCBased) {
var momentified = moment.duration(timeSpan);
this.$scope.timeUnits = timeUnitsMegastructure.filter(function (row) {
return row[1](momentified);
})[0][0];
}
};
/**
* Zooming occurs when the user manipulates the zoom slider.
* Zooming updates the scale and bounds fields immediately, but does
* not trigger a bounds change to other views until the mouse button
* is released.
* @param bounds
*/
TimeConductorController.prototype.onZoom = function (sliderValue) {
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit);
var zoom = this.conductorViewService.zoom(timeSpan);
this.zooming = true;
this.$scope.boundsModel.start = zoom.bounds.start;
this.$scope.boundsModel.end = zoom.bounds.end;
this.toTimeUnits(zoom.bounds.end - zoom.bounds.start);
if (zoom.offsets) {
this.setViewFromOffsets(zoom.offsets);
}
};
/**
* Fired when user has released the zoom slider
* @event platform.features.conductor.TimeConductorController~zoomStop
*/
/**
* Invoked when zoom slider is released by user. Will update the time conductor with the new bounds, triggering
* a global bounds change event.
* @fires platform.features.conductor.TimeConductorController~zoomStop
*/
TimeConductorController.prototype.onZoomStop = function () {
if (this.timeAPI.clock() !== undefined) {
this.setOffsetsFromView(this.$scope.boundsModel);
}
this.setBoundsFromView(this.$scope.boundsModel);
this.zooming = false;
this.conductorViewService.emit('zoom-stop');
};
/**
* Panning occurs when the user grabs the conductor scale and drags
* it left or right to slide the window of time represented by the
* conductor. Panning updates the scale and bounds fields
* immediately, but does not trigger a bounds change to other views
* until the mouse button is released.
* @param {TimeConductorBounds} bounds
*/
TimeConductorController.prototype.onPan = function (bounds) {
this.panning = true;
this.$scope.boundsModel.start = bounds.start;
this.$scope.boundsModel.end = bounds.end;
};
/**
* Called when the user releases the mouse button after panning.
*/
TimeConductorController.prototype.onPanStop = function () {
this.panning = false;
};
/**
* @private
*/
TimeConductorController.prototype.destroy = function () {
this.timeAPI.off('bounds', this.setViewFromBounds);
this.timeAPI.off('timeSystem', this.setViewFromTimeSystem);
this.timeAPI.off('clock', this.setViewFromClock);
this.timeAPI.off('follow', this.setFollow);
this.timeAPI.off('clockOffsets', this.setViewFromOffsets);
this.conductorViewService.off('pan', this.onPan);
this.conductorViewService.off('pan-stop', this.onPanStop);
};
return TimeConductorController;
}
);

View File

@@ -0,0 +1,513 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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(['./TimeConductorController'], function (TimeConductorController) {
xdescribe("The time conductor controller", function () {
var mockScope;
var mockWindow;
var mockTimeConductor;
var mockConductorViewService;
var mockTimeSystems;
var controller;
var mockFormatService;
var mockFormat;
var mockLocation;
beforeEach(function () {
mockScope = jasmine.createSpyObj("$scope", [
"$watch",
"$on"
]);
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
mockTimeConductor = jasmine.createSpyObj(
"TimeConductor",
[
"bounds",
"timeSystem",
"on",
"off"
]
);
mockTimeConductor.bounds.and.returnValue({start: undefined, end: undefined});
mockConductorViewService = jasmine.createSpyObj(
"ConductorViewService",
[
"availableModes",
"mode",
"availableTimeSystems",
"deltas",
"deltas",
"on",
"off"
]
);
mockConductorViewService.availableModes.and.returnValue([]);
mockConductorViewService.availableTimeSystems.and.returnValue([]);
mockFormatService = jasmine.createSpyObj('formatService', [
'getFormat'
]);
mockFormat = jasmine.createSpyObj('format', [
'format'
]);
mockFormatService.getFormat.and.returnValue(mockFormat);
mockLocation = jasmine.createSpyObj('location', [
'search'
]);
mockLocation.search.and.returnValue({});
mockTimeSystems = [];
});
function getListener(target, event) {
return target.calls.all().filter(function (call) {
return call.args[0] === event;
})[0].args[1];
}
describe("when time conductor state changes", function () {
var mockDeltaFormat;
var defaultBounds;
var defaultDeltas;
var mockDefaults;
var timeSystem;
var tsListener;
beforeEach(function () {
mockFormat = {};
mockDeltaFormat = {};
defaultBounds = {
start: 2,
end: 3
};
defaultDeltas = {
start: 10,
end: 20
};
mockDefaults = {
deltas: defaultDeltas,
bounds: defaultBounds
};
timeSystem = {
metadata: {
key: 'mock'
},
formats: function () {
return [mockFormat];
},
deltaFormat: function () {
return mockDeltaFormat;
},
defaults: function () {
return mockDefaults;
}
};
controller = new TimeConductorController(
mockScope,
mockWindow,
mockLocation,
{conductor: mockTimeConductor},
mockConductorViewService,
mockFormatService,
'fixed',
true
);
tsListener = getListener(mockTimeConductor.on, "timeSystem");
});
it("listens for changes to conductor state", function () {
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
});
it("deregisters conductor listens when scope is destroyed", function () {
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
controller.destroy();
expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
});
it("when time system changes, sets time system on scope", function () {
expect(tsListener).toBeDefined();
tsListener(timeSystem);
expect(mockScope.timeSystemModel).toBeDefined();
expect(mockScope.timeSystemModel.selected).toBe(timeSystem);
expect(mockScope.timeSystemModel.format).toBe(mockFormat);
expect(mockScope.timeSystemModel.deltaFormat).toBe(mockDeltaFormat);
});
it("when time system changes, sets defaults on scope", function () {
mockDefaults.zoom = {
min: 100,
max: 10
};
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
tsListener(timeSystem);
expect(mockScope.boundsModel.start).toEqual(defaultBounds.start);
expect(mockScope.boundsModel.end).toEqual(defaultBounds.end);
expect(mockScope.boundsModel.startDelta).toEqual(defaultDeltas.start);
expect(mockScope.boundsModel.endDelta).toEqual(defaultDeltas.end);
expect(mockScope.timeSystemModel.minZoom).toBe(mockDefaults.zoom.min);
expect(mockScope.timeSystemModel.maxZoom).toBe(mockDefaults.zoom.max);
});
it("supports zoom if time system defines zoom defaults", function () {
mockDefaults.zoom = undefined;
tsListener(timeSystem);
expect(controller.supportsZoom).toBe(false);
mockDefaults.zoom = {
min: 100,
max: 10
};
var anotherTimeSystem = Object.create(timeSystem);
timeSystem.defaults = function () {
return mockDefaults;
};
tsListener(anotherTimeSystem);
expect(controller.supportsZoom).toBe(true);
});
it("when bounds change, sets the correct zoom slider value", function () {
var bounds = {
start: 0,
end: 50
};
mockDefaults.zoom = {
min: 100,
max: 0
};
function exponentializer(rawValue) {
return 1 - Math.pow(rawValue, 1 / 4);
}
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
//Set zoom defaults
tsListener(timeSystem);
controller.changeBounds(bounds);
expect(controller.currentZoom).toEqual(exponentializer(0.5));
});
it("when bounds change, sets them on scope", function () {
var bounds = {
start: 1,
end: 2
};
var boundsListener = getListener(mockTimeConductor.on, "bounds");
expect(boundsListener).toBeDefined();
boundsListener(bounds);
expect(mockScope.boundsModel).toBeDefined();
expect(mockScope.boundsModel.start).toEqual(bounds.start);
expect(mockScope.boundsModel.end).toEqual(bounds.end);
});
});
describe("when user makes changes from UI", function () {
var mode = "realtime";
var ts1Metadata;
var ts2Metadata;
var ts3Metadata;
beforeEach(function () {
mode = "realtime";
ts1Metadata = {
'key': 'ts1',
'name': 'Time System One',
'cssClass': 'cssClassOne'
};
ts2Metadata = {
'key': 'ts2',
'name': 'Time System Two',
'cssClass': 'cssClassTwo'
};
ts3Metadata = {
'key': 'ts3',
'name': 'Time System Three',
'cssClass': 'cssClassThree'
};
mockTimeSystems = [
{
metadata: ts1Metadata
},
{
metadata: ts2Metadata
},
{
metadata: ts3Metadata
}
];
//Wrap in mock constructors
mockConductorViewService.systems = mockTimeSystems;
controller = new TimeConductorController(
mockScope,
mockWindow,
mockLocation,
{conductor: mockTimeConductor},
mockConductorViewService,
mockFormatService,
"fixed",
true
);
});
it("sets the mode on scope", function () {
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
controller.setMode(mode);
expect(mockScope.modeModel.selectedKey).toEqual(mode);
});
it("sets available time systems on scope when mode changes", function () {
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
controller.setMode(mode);
expect(mockScope.timeSystemModel.options.length).toEqual(3);
expect(mockScope.timeSystemModel.options[0]).toEqual(ts1Metadata);
expect(mockScope.timeSystemModel.options[1]).toEqual(ts2Metadata);
expect(mockScope.timeSystemModel.options[2]).toEqual(ts3Metadata);
});
it("sets bounds on the time conductor", function () {
var formModel = {
start: 1,
end: 10
};
controller.setBounds(formModel);
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel);
});
it("applies deltas when they change in form", function () {
var deltas = {
start: 1000,
end: 2000
};
var formModel = {
startDelta: deltas.start,
endDelta: deltas.end
};
controller.setDeltas(formModel);
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas);
});
it("sets the time system on the time conductor", function () {
var defaultBounds = {
start: 5,
end: 6
};
var timeSystem = {
metadata: {
key: 'testTimeSystem'
},
defaults: function () {
return {
bounds: defaultBounds
};
}
};
controller.timeSystems = [timeSystem];
controller.selectTimeSystemByKey('testTimeSystem');
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
});
it("updates form bounds during pan events", function () {
var testBounds = {
start: 10,
end: 20
};
expect(controller.$scope.boundsModel.start).not.toBe(testBounds.start);
expect(controller.$scope.boundsModel.end).not.toBe(testBounds.end);
expect(controller.conductorViewService.on).toHaveBeenCalledWith("pan",
controller.onPan);
getListener(controller.conductorViewService.on, "pan")(testBounds);
expect(controller.$scope.boundsModel.start).toBe(testBounds.start);
expect(controller.$scope.boundsModel.end).toBe(testBounds.end);
});
});
describe("when the URL defines conductor state", function () {
var urlBounds;
var urlTimeSystem;
var urlDeltas;
var mockDeltaFormat;
var defaultBounds;
var defaultDeltas;
var mockDefaults;
var timeSystem;
var otherTimeSystem;
var mockSearchObject;
beforeEach(function () {
mockFormat = {};
mockDeltaFormat = {};
defaultBounds = {
start: 2,
end: 3
};
defaultDeltas = {
start: 10,
end: 20
};
mockDefaults = {
deltas: defaultDeltas,
bounds: defaultBounds
};
timeSystem = {
metadata: {
key: 'mockTimeSystem'
},
formats: function () {
return [mockFormat];
},
deltaFormat: function () {
return mockDeltaFormat;
},
defaults: function () {
return mockDefaults;
}
};
otherTimeSystem = {
metadata: {
key: 'otherTimeSystem'
},
formats: function () {
return [mockFormat];
},
deltaFormat: function () {
return mockDeltaFormat;
},
defaults: function () {
return mockDefaults;
}
};
mockConductorViewService.systems = [timeSystem, otherTimeSystem];
urlBounds = {
start: 100,
end: 200
};
urlTimeSystem = "otherTimeSystem";
urlDeltas = {
start: 300,
end: 400
};
mockSearchObject = {
"tc.startBound": urlBounds.start,
"tc.endBound": urlBounds.end,
"tc.startDelta": urlDeltas.start,
"tc.endDelta": urlDeltas.end,
"tc.timeSystem": urlTimeSystem
};
mockLocation.search.and.returnValue(mockSearchObject);
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
controller = new TimeConductorController(
mockScope,
mockWindow,
mockLocation,
{conductor: mockTimeConductor},
mockConductorViewService,
mockFormatService,
"fixed",
true
);
spyOn(controller, "setMode");
spyOn(controller, "selectTimeSystemByKey");
});
it("sets conductor state from URL", function () {
mockSearchObject["tc.mode"] = "fixed";
controller.setStateFromSearchParams(mockSearchObject);
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(urlBounds);
});
it("sets mode from URL", function () {
mockTimeConductor.bounds.reset();
mockSearchObject["tc.mode"] = "realtime";
controller.setStateFromSearchParams(mockSearchObject);
expect(controller.setMode).toHaveBeenCalledWith("realtime");
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(urlDeltas);
expect(mockTimeConductor.bounds).not.toHaveBeenCalled();
});
describe("when conductor state changes", function () {
it("updates the URL with the mode", function () {
controller.setMode("realtime", "fixed");
expect(mockLocation.search).toHaveBeenCalledWith("tc.mode", "fixed");
});
it("updates the URL with the bounds", function () {
mockConductorViewService.mode.and.returnValue("fixed");
controller.changeBounds({start: 500, end: 600});
expect(mockLocation.search).toHaveBeenCalledWith("tc.startBound", 500);
expect(mockLocation.search).toHaveBeenCalledWith("tc.endBound", 600);
});
it("updates the URL with the deltas", function () {
controller.setDeltas({startDelta: 700, endDelta: 800});
expect(mockLocation.search).toHaveBeenCalledWith("tc.startDelta", 700);
expect(mockLocation.search).toHaveBeenCalledWith("tc.endDelta", 800);
});
it("updates the URL with the time system", function () {
controller.changeTimeSystem(otherTimeSystem);
expect(mockLocation.search).toHaveBeenCalledWith("tc.timeSystem", "otherTimeSystem");
});
});
});
});
});

View File

@@ -0,0 +1,69 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 () {
/**
* Form validation for the TimeConductorController.
* @param conductor
* @constructor
*/
function TimeConductorValidation(timeAPI) {
var self = this;
this.timeAPI = timeAPI;
/*
* Bind all class functions to 'this'
*/
Object.keys(TimeConductorValidation.prototype).filter(function (key) {
return typeof TimeConductorValidation.prototype[key] === 'function';
}).forEach(function (key) {
self[key] = self[key].bind(self);
});
}
/**
* Validation methods below are invoked directly from controls in the TimeConductor form
*/
TimeConductorValidation.prototype.validateStart = function (start) {
var bounds = this.timeAPI.bounds();
return this.timeAPI.validateBounds({start: start, end: bounds.end}) === true;
};
TimeConductorValidation.prototype.validateEnd = function (end) {
var bounds = this.timeAPI.bounds();
return this.timeAPI.validateBounds({start: bounds.start, end: end}) === true;
};
TimeConductorValidation.prototype.validateStartOffset = function (startOffset) {
return !isNaN(startOffset) && startOffset > 0;
};
TimeConductorValidation.prototype.validateEndOffset = function (endOffset) {
return !isNaN(endOffset) && endOffset >= 0;
};
return TimeConductorValidation;
}
);

View File

@@ -0,0 +1,73 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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(['./TimeConductorValidation'], function (TimeConductorValidation) {
describe("The Time Conductor Validation class", function () {
var timeConductorValidation,
mockTimeConductor;
beforeEach(function () {
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
"validateBounds",
"bounds"
]);
timeConductorValidation = new TimeConductorValidation(mockTimeConductor);
});
describe("Validates start and end values using Time Conductor", function () {
beforeEach(function () {
var mockBounds = {
start: 10,
end: 20
};
mockTimeConductor.bounds.and.returnValue(mockBounds);
});
it("Validates start values using Time Conductor", function () {
var startValue = 30;
timeConductorValidation.validateStart(startValue);
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
});
it("Validates end values using Time Conductor", function () {
var endValue = 40;
timeConductorValidation.validateEnd(endValue);
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
});
});
it("Validates that start Offset is valid number > 0", function () {
expect(timeConductorValidation.validateStartOffset(-1)).toBe(false);
expect(timeConductorValidation.validateStartOffset("abc")).toBe(false);
expect(timeConductorValidation.validateStartOffset("1")).toBe(true);
expect(timeConductorValidation.validateStartOffset(1)).toBe(true);
});
it("Validates that end Offset is valid number >= 0", function () {
expect(timeConductorValidation.validateEndOffset(-1)).toBe(false);
expect(timeConductorValidation.validateEndOffset("abc")).toBe(false);
expect(timeConductorValidation.validateEndOffset("1")).toBe(true);
expect(timeConductorValidation.validateEndOffset(0)).toBe(true);
expect(timeConductorValidation.validateEndOffset(1)).toBe(true);
});
});
});

View File

@@ -0,0 +1,97 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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'
],
function (EventEmitter) {
/**
* The TimeConductorViewService acts as an event bus between different
* elements of the Time Conductor UI. Zooming and panning occur via this
* service, as they are specific behaviour of the UI, and not general
* functions of the time API.
*
* Synchronization of conductor state between the Time API and the URL
* also occurs from the conductor view service, whose lifecycle persists
* between view changes.
*
* @memberof platform.features.conductor
* @param conductor
* @constructor
*/
function TimeConductorViewService(openmct) {
EventEmitter.call(this);
this.timeAPI = openmct.time;
}
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
/**
* An event to indicate that zooming is taking place
* @event platform.features.conductor.TimeConductorViewService~zoom
* @property {ZoomLevel} zoom the new zoom level.
*/
/**
* Zoom to given time span. Will fire a zoom event with new zoom
* bounds. Zoom bounds emitted in this way are considered ephemeral
* and should be overridden by any time conductor bounds events. Does
* not set bounds globally.
* @param {number} zoom A time duration in ms
* @fires platform.features.conductor.TimeConductorViewService~zoom
* @see module:openmct.TimeConductor#bounds
*/
TimeConductorViewService.prototype.zoom = function (timeSpan) {
var zoom = {};
// If a tick source is defined, then the concept of 'now' is
// important. Calculate zoom based on 'now'.
if (this.timeAPI.clock() !== undefined) {
zoom.offsets = {
start: -timeSpan,
end: this.timeAPI.clockOffsets().end
};
var currentVal = this.timeAPI.clock().currentValue();
zoom.bounds = {
start: currentVal + zoom.offsets.start,
end: currentVal + zoom.offsets.end
};
} else {
var bounds = this.timeAPI.bounds();
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
bounds.start = center - timeSpan / 2;
bounds.end = center + timeSpan / 2;
zoom.bounds = bounds;
}
this.emit("zoom", zoom);
return zoom;
};
return TimeConductorViewService;
}
);

View File

@@ -0,0 +1,109 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 () {
/**
* Controller for the Time of Interest element used in various views to display the TOI. Responsible for setting
* the text label for the current TOI, and for toggling the (un)pinned state which determines whether the TOI
* indicator is visible.
* @constructor
*/
function TimeOfInterestController($scope, openmct, formatService) {
this.timeAPI = openmct.time;
this.formatService = formatService;
this.format = undefined;
this.toiText = undefined;
this.$scope = $scope;
//Bind all class functions to 'this'
Object.keys(TimeOfInterestController.prototype).filter(function (key) {
return typeof TimeOfInterestController.prototype[key] === 'function';
}).forEach(function (key) {
this[key] = TimeOfInterestController.prototype[key].bind(this);
}.bind(this));
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
this.timeAPI.on('timeSystem', this.changeTimeSystem);
if (this.timeAPI.timeSystem() !== undefined) {
this.changeTimeSystem(this.timeAPI.timeSystem());
var toi = this.timeAPI.timeOfInterest();
if (toi) {
this.changeTimeOfInterest(toi);
}
}
$scope.$on('$destroy', this.destroy);
}
/**
* Called when the time of interest changes on the conductor. Will pin (display) the TOI indicator, and set the
* text using the default formatter of the currently active Time System.
* @private
* @param {integer} toi Current time of interest in ms
*/
TimeOfInterestController.prototype.changeTimeOfInterest = function (toi) {
if (toi !== undefined) {
this.$scope.pinned = true;
this.toiText = this.format.format(toi);
} else {
this.$scope.pinned = false;
}
};
/**
* When time system is changed, update the formatter used to
* display the current TOI label
*/
TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) {
this.format = this.formatService.getFormat(timeSystem.timeFormat);
};
/**
* @private
*/
TimeOfInterestController.prototype.destroy = function () {
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
this.timeAPI.off('timeSystem', this.changeTimeSystem);
};
/**
* Will unpin (hide) the TOI indicator. Has the effect of setting the time of interest to `undefined` on the
* Time Conductor
*/
TimeOfInterestController.prototype.dismiss = function () {
this.timeAPI.timeOfInterest(undefined);
};
/**
* Sends out a time of interest event with the effect of resetting
* the TOI displayed in views.
*/
TimeOfInterestController.prototype.resync = function () {
this.timeAPI.timeOfInterest(this.timeAPI.timeOfInterest());
};
return TimeOfInterestController;
}
);

View File

@@ -0,0 +1,294 @@
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
}
]
}
});
});

View File

@@ -25,5 +25,6 @@
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>

View File

@@ -0,0 +1,118 @@
<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>

View File

@@ -0,0 +1,8 @@
<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>

View File

@@ -0,0 +1,34 @@
<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>

View File

@@ -26,7 +26,6 @@
define(
["painterro", "zepto"],
function (Painterro, $) {
var annotationStruct = {
title: "Annotate Snapshot",
template: "annotate-snapshot",
@@ -108,6 +107,9 @@ define(
done(true);
}
}).show(snapshot);
$(document.body).find('.ptro-icon-btn').addClass('s-button');
$(document.body).find('.ptro-input').addClass('s-button');
});
}];

View File

@@ -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')) {

View File

@@ -0,0 +1,72 @@
/*****************************************************************************
* 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;
}
);

View File

@@ -0,0 +1,74 @@
/*****************************************************************************
* 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;
}
);

View File

@@ -0,0 +1,132 @@
/*****************************************************************************
* 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;
}
);

View File

@@ -0,0 +1,50 @@
/*****************************************************************************
* 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 () {
/**
* 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;
}
);

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -20,47 +20,35 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* This bundle implements object types and associated views for
* display-building.
*/
define(
[],
function () {
define([
'vue',
'../../res/templates/viewSnapshot.html'
], function (
Vue,
snapshotOverlayTemplate
) {
function SnapshotOverlay (embedObject, formatTime) {
this.embedObject = embedObject;
/**
* The LayoutNotebookController is responsible for supporting the
* notebook feature creation on theLayout view.
**/
this.snapshotOverlayVue = new Vue({
template: snapshotOverlayTemplate,
data: function () {
return {
embed: embedObject
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();
};
},
methods: {
close: this.close.bind(this),
formatTime: formatTime
}
});
}
this.open();
return LayoutNotebookController;
}
);
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;
});

View File

@@ -31,7 +31,8 @@ define(
$scope.snapshot = undefined;
$scope.snapToggle = true;
$scope.entryText = '';
var annotateAction = $rootScope.selObj.getCapability('action').getActions({key: 'annotate-snapshot'})[0];
var annotateAction = $rootScope.selObj.getCapability('action').getActions(
{category: 'embed'})[1];
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = $rootScope.selObj;
$scope.objectName = $rootScope.selObj.getModel().name;

View File

@@ -0,0 +1,367 @@
/*****************************************************************************
* 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;
});

View File

@@ -31,34 +31,32 @@ 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': ''
}]
};
});
}
@@ -77,15 +75,13 @@ 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();

View File

@@ -0,0 +1,44 @@
/******************************************************************************
* 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.
*****************************************************************************/
/**
* 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;
}
return true;
};
return CompositionPolicy;
}
);

View File

@@ -31,14 +31,13 @@ define([
'./plugins/plugins',
'./adapter/indicators/legacy-indicators-plugin',
'./plugins/buildInfo/plugin',
'./adapter/vue-adapter/install',
'./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 (
@@ -52,14 +51,13 @@ define([
plugins,
LegacyIndicatorsPlugin,
buildInfoPlugin,
installVueAdapter,
ViewRegistry,
InspectorViewRegistry,
ToolbarRegistry,
ApplicationRouter,
Browse,
Main,
coreStyles,
NotebookStyles,
Layout,
Vue
) {
@@ -277,7 +275,6 @@ define([
}.bind(this)
});
// TODO: remove with legacy types.
this.types.listKeys().forEach(function (typeKey) {
var type = this.types.get(typeKey);
var legacyDefinition = type.toLegacyDefinition();
@@ -285,6 +282,26 @@ define([
this.legacyExtension('types', legacyDefinition);
}.bind(this));
// TODO: move this to adapter bundle.
this.legacyExtension('runs', {
depends: ['types[]'],
implementation: (types) => {
this.types.importLegacyTypes(types);
}
});
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');
@@ -309,6 +326,8 @@ define([
// something has depended upon objectService. Cool, right?
this.$injector.get('objectService');
console.log('Rendering app layout.');
var appLayout = new Vue({
mixins: [Layout.default],
provide: {
@@ -316,8 +335,10 @@ define([
}
});
domElement.appendChild(appLayout.$mount().$el);
this.layout = appLayout;
Browse(this);
console.log('Attaching adapter');
installVueAdapter(appLayout, this);
this.router.start();
this.emit('start');
}.bind(this));

View File

@@ -34,9 +34,7 @@ define([
'./runs/TimeSettingsURLHandler',
'./runs/TypeDeprecationChecker',
'./runs/LegacyTelemetryProvider',
'./runs/RegisterLegacyTypes',
'./services/LegacyObjectAPIInterceptor',
'./views/installLegacyViews'
'./services/LegacyObjectAPIInterceptor'
], function (
legacyRegistry,
ActionDialogDecorator,
@@ -51,9 +49,7 @@ define([
TimeSettingsURLHandler,
TypeDeprecationChecker,
LegacyTelemetryProvider,
RegisterLegacyTypes,
LegacyObjectAPIInterceptor,
installLegacyViews
LegacyObjectAPIInterceptor
) {
legacyRegistry.register('src/adapter', {
"extensions": {
@@ -153,21 +149,6 @@ define([
"openmct",
"instantiate"
]
},
{
implementation: installLegacyViews,
depends: [
"openmct",
"views[]",
"instantiate"
]
},
{
implementation: RegisterLegacyTypes,
depends: [
"types[]",
"openmct"
]
}
],
licenses: [

View File

@@ -1,17 +0,0 @@
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;
});

View File

@@ -1,110 +0,0 @@
define([
], function (
) {
const DEFAULT_VIEW_PRIORITY = 100;
const PRIORITY_LEVELS = {
"fallback": Number.NEGATIVE_INFINITY,
"default": -100,
"none": 0,
"optional": DEFAULT_VIEW_PRIORITY,
"preferred": 1000,
"mandatory": Number.POSITIVE_INFINITY
};
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();
}
}
},
priority: function () {
let priority = legacyView.priority || DEFAULT_VIEW_PRIORITY;
if (typeof priority === 'string') {
priority = PRIORITY_LEVELS[priority];
}
return priority;
}
};
};
return LegacyViewProvider;
});

View File

@@ -1,22 +0,0 @@
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;
});

View File

@@ -0,0 +1,41 @@
define([
], function (
) {
class InspectorAdapter {
constructor(layout, openmct) {
console.log('installing inspector adapter');
this.openmct = openmct;
this.layout = layout;
this.$injector = openmct.$injector;
this.angular = openmct.$angular;
this.objectService = this.$injector.get('objectService');
this.templateLinker = this.$injector.get('templateLinker');
this.$timeout = this.$injector.get('$timeout');
this.templateMap = {};
this.$injector.get('templates[]').forEach((t) => {
this.templateMap[t.key] = this.templateMap[t.key] || t;
});
var $rootScope = this.$injector.get('$rootScope');
this.scope = $rootScope.$new();
this.templateLinker.link(
this.scope,
angular.element(layout.$refs.inspector.$refs.properties),
this.templateMap["inspectorRegion"]
);
this.$timeout(function () {
//hello!
});
}
}
return InspectorAdapter;
});

View File

@@ -0,0 +1,16 @@
define([
'./main-adapter',
'./tree-adapter',
'./inspector-adapter'
], function (
MainAdapter,
TreeAdapter,
InspectorAdapter
) {
return function install(layout, openmct) {
let main = new MainAdapter(layout, openmct);
let tree = new TreeAdapter(layout, openmct);
let inspector = new InspectorAdapter(layout, openmct);
}
});

View File

@@ -0,0 +1,153 @@
define([
], function (
) {
// Find an object in an array of objects.
function findObject(domainObjects, id) {
var i;
for (i = 0; i < domainObjects.length; i += 1) {
if (domainObjects[i].getId() === id) {
return domainObjects[i];
}
}
}
// recursively locate and return an object inside of a container
// via a path. If at any point in the recursion it fails to find
// the next object, it will return the parent.
function findViaComposition(containerObject, path) {
var nextId = path.shift();
if (!nextId) {
return containerObject;
}
return containerObject.useCapability('composition')
.then(function (composees) {
var nextObject = findObject(composees, nextId);
if (!nextObject) {
return containerObject;
}
if (!nextObject.hasCapability('composition')) {
return nextObject;
}
return findViaComposition(nextObject, path);
});
}
function getLastChildIfRoot(object) {
if (object.getId() !== 'ROOT') {
return object;
}
return object.useCapability('composition')
.then(function (composees) {
return composees[composees.length - 1];
});
}
function pathForObject(domainObject) {
var context = domainObject.getCapability('context'),
objectPath = context ? context.getPath() : [],
ids = objectPath.map(function (domainObj) {
return domainObj.getId();
});
return "/browse/" + ids.slice(1).join("/");
}
class MainAdapter {
constructor(layout, openmct) {
this.openmct = openmct;
this.layout = layout;
this.$injector = openmct.$injector;
this.angular = openmct.$angular;
this.objectService = this.$injector.get('objectService');
this.templateLinker = this.$injector.get('templateLinker');
this.navigationService = this.$injector.get('navigationService');
this.$timeout = this.$injector.get('$timeout');
this.templateMap = {};
this.$injector.get('templates[]').forEach((t) => {
this.templateMap[t.key] = this.templateMap[t.key] || t;
});
var $rootScope = this.$injector.get('$rootScope');
this.scope = $rootScope.$new();
this.scope.representation = {};
openmct.router.route(/^\/browse\/(.*)$/, (path, results) => {
let navigatePath = results[1];
if (!navigatePath) {
navigatePath = 'mine';
}
this.navigateToPath(navigatePath);
});
this.navigationService.addListener(o => this.navigateToObject(o));
}
navigateToPath(path) {
if (!Array.isArray(path)) {
path = path.split('/');
}
return this.getObject('ROOT')
.then(root => {
return findViaComposition(root, path);
})
.then(getLastChildIfRoot)
.then(object => {
this.setMainViewObject(object);
});
}
setMainViewObject(object) {
this.scope.domainObject = object;
this.scope.navigatedObject = object;
this.templateLinker.link(
this.scope,
angular.element(this.layout.$refs.mainContainer),
this.templateMap["browseObject"]
);
document.title = object.getModel().name;
this.scheduleDigest();
}
idsForObject(domainObject) {
return this.urlService
.urlForLocation("", domainObject)
.replace('/', '');
}
navigateToObject(object) {
let path = pathForObject(object);
let views = object.useCapability('view');
let params = this.openmct.router.getParams();
let currentViewIsValid = views.some(v => v.key === params['view']);
if (!currentViewIsValid) {
this.scope.representation = {
selected: views[0]
}
this.openmct.router.update(path, {
view: views[0].key
});
} else {
this.openmct.router.setPath(path);
}
}
scheduleDigest() {
this.$timeout(function () {
// digest done!
});
}
getObject(id) {
return this.objectService.getObjects([id])
.then(function (results) {
return results[id];
});
}
}
return MainAdapter;
});

View File

@@ -0,0 +1,15 @@
define([
], function (
) {
class TreeAdapter {
constructor(layout, openmct) {
}
}
return TreeAdapter;
});

View File

@@ -89,13 +89,12 @@ define(function () {
}
}
}
if (legacyDefinition.features && legacyDefinition.features.includes("creation")) {
if (Array.isArray(legacyDefinition.creatable) && 'creation' in legacyDefinition.creatable) {
definition.creatable = true;
}
return definition;
};
}
return Type;
});

View File

@@ -55,6 +55,8 @@ define([
'../platform/exporters/bundle',
'../platform/features/clock/bundle',
'../platform/features/fixed/bundle',
'../platform/features/conductor/core/bundle',
'../platform/features/conductor/compatibility/bundle',
'../platform/features/imagery/bundle',
'../platform/features/layout/bundle',
'../platform/features/listview/bundle',

View File

@@ -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([
'./components/GridView.vue',
'vue'
], function (
GridViewComponent,
Vue
) {
function FolderGridView(openmct) {
return {
key: 'grid',
name: 'Grid Vue',
cssClass: 'icon-thumbs-strip',
canView: function (domainObject) {
return domainObject.type === 'folder';
},
view: function (domainObject) {
let component;
return {
show: function (element) {
component = new Vue({
components: {
gridViewComponent: GridViewComponent.default
},
provide: {
openmct,
domainObject
},
el: element,
template: '<grid-view-component></grid-view-component>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}
return FolderGridView;
});

View File

@@ -1,70 +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([
'./components/ListView.vue',
'vue',
'moment'
], function (
ListViewComponent,
Vue,
Moment
) {
function FolderListView(openmct) {
return {
key: 'list-view',
name: 'List Vue',
cssClass: 'icon-list-view',
canView: function (domainObject) {
return domainObject.type === 'folder';
},
view: function (domainObject) {
let component;
return {
show: function (element) {
component = new Vue({
components: {
listViewComponent: ListViewComponent.default
},
provide: {
openmct,
domainObject,
Moment
},
el: element,
template: '<list-view-component></list-view-component>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}
return FolderListView;
});

View File

@@ -1,207 +0,0 @@
<template>
<div class="l-grid-view">
<div v-for="(item, index) in items"
v-bind:key="index"
class="l-grid-view__item c-grid-item"
:class="{ 'is-alias': item.isAlias === true }"
@click="navigate(item.model.identifier.key)">
<div class="c-grid-item__type-icon"
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'">
</div>
<div class="c-grid-item__details">
<!-- Name and metadata -->
<div class="c-grid-item__name"
:title="item.model.name">{{item.model.name}}</div>
<div class="c-grid-item__metadata"
:title="item.type.name">
<span>{{item.type.name}}</span>
<span v-if="item.model.composition !== undefined">
- {{item.model.composition.length}} item<span v-if="item.model.composition.length !== 1">s</span>
</span>
</div>
</div>
<div class="c-grid-item__controls">
<div class="icon-people" title='Shared'></div>
<div class="c-click-icon icon-info c-info-button" title='More Info'></div>
<div class="icon-pointer-right c-pointer-icon"></div>
</div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
/******************************* GRID VIEW */
.l-grid-view {
display: flex;
flex-flow: column nowrap;
&__item {
flex: 0 0 auto;
+ .l-grid-view__item { margin-top: $interiorMargin; }
}
body.desktop & {
flex-flow: row wrap;
&__item {
height: $ueBrowseGridItemLg;
width: $ueBrowseGridItemLg;
margin: 0 $interiorMargin $interiorMargin 0;
}
}
}
/******************************* GRID ITEMS */
.c-grid-item {
// Mobile-first
@include button($bg: $colorItemBg, $fg: $colorItemFg);
cursor: pointer;
display: flex;
padding: $interiorMarginLg;
&__type-icon {
filter: $colorKeyFilter;
flex: 0 0 32px;
margin-right: $interiorMarginLg;
}
&.is-alias {
// Object is an alias to an original.
[class*='__type-icon'] {
&:before {
color: $colorIconAliasForKeyFilter;
content: $glyph-icon-link;
display: block;
font-family: symbolsfont;
font-size: 2.5em;
position: absolute;
text-shadow: rgba(black, 0.5) 0 1px 4px;
top: auto; left: 0; bottom: 10px; right: auto;
}
}
}
&__details {
display: flex;
flex-flow: column nowrap;
flex: 1 1 auto;
}
&__name {
@include ellipsize();
color: $colorItemFg;
font-size: 1.3em;
font-weight: 400;
margin-bottom: $interiorMarginSm;
}
&__metadata {
color: $colorItemFgDetails;
}
&__controls {
color: $colorItemFgDetails;
flex: 0 0 64px;
font-size: 1.2em;
display: flex;
align-items: center;
justify-content: flex-end;
> * + * {
margin-left: $interiorMargin;
}
}
body.desktop & {
$transOutMs: 300ms;
flex-flow: column nowrap;
transition: background $transOutMs ease-in-out;
&:hover {
background: $colorItemBgHov;
transition: $transIn;
.c-grid-item__type-icon {
filter: $colorKeyFilterHov;
transform: scale(1);
transition: $transInBounce;
}
}
> * {
margin: 0; // Reset from mobile
}
&__controls {
align-items: start;
flex: 0 0 auto;
order: 1;
.c-info-button,
.c-pointer-icon { display: none; }
}
&__type-icon {
flex: 1 1 auto;
margin: $interiorMargin 22.5%;
order: 2;
transform: scale(0.9);
transform-origin: center;
transition: all $transOutMs ease-in-out;
}
&__details {
flex: 0 0 auto;
justify-content: flex-end;
order: 3;
}
}
}
</style>
<script>
export default {
inject: ['openmct', 'domainObject'],
data() {
var items = [],
unknownObjectType = {
definition: {
cssClass: 'icon-object-unknown',
name: 'Unknown Type'
}
};
var composition = this.openmct.composition.get(this.domainObject);
if (composition) {
composition.load().then((array) => {
if (Array.isArray(array)) {
array.forEach((model) => {
var type = this.openmct.types.get(model.type) || unknownObjectType;
items.push({
model: model,
type: type.definition,
isAlias: this.domainObject.identifier.key !== model.location
});
});
}
});
}
return {
items: items
}
},
methods: {
navigate(identifier) {
let currentLocation = this.openmct.router.currentLocation.path,
navigateToPath = `${currentLocation}/${identifier}`;
this.openmct.router.setPath(navigateToPath);
}
}
}
</script>

View File

@@ -1,215 +0,0 @@
<template>
<div class="c-table c-table--sortable c-list-view">
<table class="c-table__body">
<thead class="c-table__header">
<tr>
<th class="is-sortable"
v-bind:class="[orderByField == 'name' ? 'is-sorting' : '', sortClass]"
@click="sortTrigger('name', 'asc')">
Name
</th>
<th class="is-sortable"
v-bind:class="[orderByField == 'type' ? 'is-sorting' : '', sortClass]"
@click="sortTrigger('type', 'asc')">
Type
</th>
<th class="is-sortable"
v-bind:class="[orderByField == 'createdDate' ? 'is-sorting' : '', sortClass]"
@click="sortTrigger('createdDate', 'desc')">
Created Date
</th>
<th class="is-sortable"
v-bind:class="[orderByField == 'updatedDate' ? 'is-sorting' : '', sortClass]"
@click="sortTrigger('updatedDate', 'desc')">
Updated Date
</th>
<th class="is-sortable"
v-bind:class="[orderByField == 'items' ? 'is-sorting' : '', sortClass]"
@click="sortTrigger('items', 'asc')">
Items
</th>
</tr>
</thead>
<tbody>
<tr class="c-list-item"
v-for="(item,index) in sortedItems"
v-bind:key="index"
:class="{ 'is-alias': item.isAlias === true }"
@click="navigate(item.identifier)">
<td class="c-list-item__name">
<div class="c-list-item__type-icon" :class="(item.cssClass != undefined) ? item.cssClass : 'icon-object-unknown'"></div>
{{item.name}}
</td>
<td class="c-list-item__type">{{ item.type }}</td>
<td class="c-list-item__date-created">{{ formatTime(item.createdDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
<td class="c-list-item__date-updated">{{ formatTime(item.updatedDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
<td class="c-list-item__items">{{ item.items }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
/******************************* LIST VIEW */
.c-list-view {
overflow-x: auto !important;
overflow-y: auto;
tbody tr {
background: $colorListItemBg;
transition: $transOut;
}
body.desktop & {
tbody tr {
cursor: pointer;
&:hover {
background: $colorListItemBgHov;
transition: $transIn;
}
}
}
td {
$p: floor($interiorMargin * 1.5);
font-size: 1.1em;
padding-top: $p;
padding-bottom: $p;
&:not(.c-list-item__name) {
color: $colorItemFgDetails;
}
}
}
.c-list-item {
&__name {
@include ellipsize();
}
&__type-icon {
color: $colorKey;
display: inline-block;
width: 1em;
margin-right:$interiorMarginSm;
}
&.is-alias {
// Object is an alias to an original.
[class*='__type-icon'] {
&:after {
color: $colorIconAlias;
content: $glyph-icon-link;
font-family: symbolsfont;
display: block;
position: absolute;
text-shadow: rgba(black, 0.5) 0 1px 2px;
top: auto; left: -1px; bottom: 1px; right: auto;
transform-origin: bottom left;
transform: scale(0.65);
}
}
}
}
/******************************* LIST ITEM */
</style>
<script>
export default {
inject: ['openmct', 'domainObject', 'Moment'],
data() {
var items = [],
unknownObjectType = {
definition: {
cssClass: 'icon-object-unknown',
name: 'Unknown Type'
}
},
composition = this.openmct.composition.get(this.domainObject);
if (composition) {
composition.load().then((array) => {
if (Array.isArray(array)) {
array.forEach(model => {
var type = this.openmct.types.get(model.type) || unknownObjectType;
items.push({
name: model.name,
identifier: model.identifier.key,
type: type.definition.name,
isAlias: false,
cssClass: type.definition.cssClass,
createdDate: model.persisted,
updatedDate: model.modified,
items: model.composition ? model.composition.length : 0,
isAlias: this.domainObject.identifier.key !== model.location
});
});
}
});
}
return {
items: items,
orderByField: 'name',
sortClass: 'asc',
}
},
computed: {
sortedItems () {
if (this.sortClass === 'asc') {
return this.items.sort(this.ascending.bind(this));
} else if (this.sortClass === 'desc') {
return this.items.sort(this.descending.bind(this));
}
},
formatTime () {
return function (timestamp, format) {
return this.Moment(timestamp).format(format);
}
}
},
methods: {
navigate(identifier) {
let currentLocation = this.openmct.router.currentLocation.path,
navigateToPath = `${currentLocation}/${identifier}`;
this.openmct.router.setPath(navigateToPath);
},
sortTrigger(field, sortOrder) {
if (this.orderByField === field) {
this.sortClass = (this.sortClass === 'asc') ? 'desc' : 'asc';
} else {
this.sortClass = sortOrder;
}
this.orderByField = field;
},
ascending(first, second) {
if (first[this.orderByField] < second[this.orderByField]) {
return -1;
} else if (first[this.orderByField] > second[this.orderByField]) {
return 1;
} else {
return 0;
}
},
descending(first, second) {
if (first[this.orderByField] > second[this.orderByField]) {
return -1;
} else if (first[this.orderByField] < second[this.orderByField]) {
return 1;
} else {
return 0;
}
}
}
}
</script>

View File

@@ -51,12 +51,64 @@ define([
function LocalTimeFormat() {
}
/**
* Returns an appropriate time format based on the provided value and
* the threshold required.
* @private
*/
function getScaledFormat(d) {
var momentified = moment.utc(d);
/**
* Uses logic from d3 Time-Scales, v3 of the API. See
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
*
* Licensed
*/
return [
[".SSS", function (m) {
return m.milliseconds();
}],
[":ss", function (m) {
return m.seconds();
}],
["hh:mma", function (m) {
return m.minutes();
}],
["hha", function (m) {
return m.hours();
}],
["ddd DD", function (m) {
return m.days() &&
m.date() !== 1;
}],
["MMM DD", function (m) {
return m.date() !== 1;
}],
["MMMM", function (m) {
return m.month();
}],
["YYYY", function () {
return true;
}]
].filter(function (row) {
return row[1](momentified);
})[0][0];
}
/**
*
* @param value
* @param {Scale} [scale] Optionally provides context to the
* format request, allowing for scale-appropriate formatting.
* @returns {string} the formatted date
*/
LocalTimeFormat.prototype.format = function (value, scale) {
if (scale !== undefined) {
var scaledFormat = getScaledFormat(value, scale);
if (scaledFormat) {
return moment.utc(value).format(scaledFormat);
}
}
return moment(value).format(DATE_FORMAT);
};

View File

@@ -41,7 +41,7 @@ define([], function () {
this.timeFormat = 'local-format';
this.durationFormat = 'duration';
this.isUTCBased = false;
this.isUTCBased = true;
}
return LocalTimeSystem;

View File

@@ -1,215 +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/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;
});

View File

@@ -1,32 +0,0 @@
<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>

View File

@@ -1,35 +0,0 @@
<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-click-icon icon-trash"
title="Delete this entry"
v-on:click="deleteEntry"></a>
</div>
</li>

View File

@@ -1,37 +0,0 @@
<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>

View File

@@ -1,50 +0,0 @@
<div class="abs overlay l-large-view">
<div class="abs blocker" v-on:click="close"></div>
<div class="abs outer-holder">
<a
class="close icon-x-in-circle"
v-on:click="close">
</a>
<div class="abs inner-holder l-flex-col">
<div class="t-contents flex-elem holder grows">
<div class="t-snapshot abs l-view-header">
<div class="abs object-browse-bar l-flex-row">
<div class="left flex-elem l-flex-row grows">
<div class="object-header flex-elem l-flex-row grows">
<div class="type-icon flex-elem embed-icon holder" v-bind:class="embed.cssClass"></div>
<div class="title-label flex-elem holder flex-can-shrink">{{embed.name}}</div>
</div>
</div>
</div>
</div>
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div>
<a class="s-button icon-pencil" title="Annotate">
<span class="title-label">Annotate</span>
</a>
</div>
<div class="abs object-holder t-image-holder s-image-holder">
<div
class="image-main s-image-main"
v-bind:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }">
</div>
</div>
</div>
<div
class="bottom-bar flex-elem holder"
v-on:click="close">
<a class="t-done s-button major">Done</a>
</div>
</div>
</div>
</div>

View File

@@ -1,130 +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'],
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;
}
);

View File

@@ -1,198 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'moment',
'zepto',
'../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;
});

View File

@@ -1,150 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'moment'
],
function (
Moment
) {
function EntryController (openmct, domainObject) {
this.openmct = openmct;
this.domainObject = domainObject;
this.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;
});

View File

@@ -1,177 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'vue',
'./EntryController',
'./EmbedController',
'../../res/templates/notebook.html',
'../../res/templates/entry.html',
'../../res/templates/embed.html',
'../../../../ui/components/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;
});

View File

@@ -27,15 +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',
'./telemetryTable/plugin',
'./staticRootPlugin/plugin',
'./notebook/plugin',
'./folderView/plugin'
'./staticRootPlugin/plugin'
], function (
_,
UTCTimeSystem,
@@ -43,19 +42,19 @@ define([
AutoflowPlugin,
TimeConductorPlugin,
ExampleImagery,
Notebook,
ImportExport,
SummaryWidget,
URLIndicatorPlugin,
TelemetryMean,
PlotPlugin,
TelemetryTablePlugin,
StaticRootPlugin,
Notebook,
FolderView
StaticRootPlugin
) {
var bundleMap = {
LocalStorage: 'platform/persistence/local',
MyItems: 'platform/features/my-items'
MyItems: 'platform/features/my-items',
Notebook: 'platform/features/notebook'
};
var plugins = _.mapValues(bundleMap, function (bundleName, pluginName) {
@@ -103,7 +102,7 @@ define([
*/
plugins.AutoflowView = AutoflowPlugin;
plugins.Conductor = TimeConductorPlugin.default;
plugins.Conductor = TimeConductorPlugin;
plugins.CouchDB = function (url) {
return function (openmct) {
@@ -156,12 +155,10 @@ define([
plugins.ExampleImagery = ExampleImagery;
plugins.Plot = PlotPlugin;
plugins.TelemetryTable = TelemetryTablePlugin;
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin;
plugins.Notebook = Notebook;
plugins.FolderView = FolderView;
return plugins;
});

View File

@@ -8,15 +8,14 @@ define([
objectUtils
) {
const DEFAULT_VIEW_PRIORITY = 100;
/**
*
*/
function SummaryWidgetViewProvider(openmct) {
return {
key: 'summary-widget-viewer',
name: 'Summary View',
cssClass: 'icon-summary-widget',
name: 'Widget View',
canView: function (domainObject) {
return domainObject.type === 'summary-widget';
},
@@ -34,11 +33,7 @@ define([
},
editable: true,
priority: function (domainObject) {
if (domainObject.type === 'summary-widget') {
return Number.MAX_VALUE;
} else {
return DEFAULT_VIEW_PRIORITY;
}
return 1;
}
};
}

View File

@@ -0,0 +1,123 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import _ from 'lodash';
import MCT from '../../MCT.js';
import ObjectViewsRegistry from '../../ui/registries/ViewRegistry.js';
import InspectorViewsRegistry from '../../ui/registries/InspectorViewRegistry.js';
import TelemetryTableViewProvider from './TelemetryTableViewProvider.js';
import TableConfigurationViewProvider from './TableConfigurationViewProvider.js';
fdescribe('The TelemetryTable plugin', function() {
let openmct;
let tableType;
let objectViewsSpy;
let inspectorViewsSpy;
beforeEach(function () {
objectViewsSpy = spyOn(ObjectViewsRegistry.prototype, 'addProvider');
inspectorViewsSpy = spyOn(InspectorViewsRegistry.prototype, 'addProvider');
openmct = new MCT();
tableType = openmct.types.get('table');
});
describe('defines a telemetry object type', function () {
it('that is registered with the type registry.', function () {
expect(tableType).toBeDefined();
});
it('that is createable.', function () {
expect(tableType.definition.creatable).toBe(true);
});
describe('that initializes new table object.', function () {
let tableObject;
beforeEach(function () {
tableObject = {};
tableType.definition.initialize(tableObject);
});
it('with valid default configuration.', function () {
expect(tableObject.configuration.hiddenColumns).toBeDefined();
});
it('to support composition.', function () {
expect(tableObject.composition).toBeDefined();
});
});
});
it('registers the table view provider', function () {
expect(objectViewsSpy).toHaveBeenCalledWith(new TelemetryTableViewProvider(openmct));
});
it('registers the table configuration view provider', function () {
expect(inspectorViewsSpy).toHaveBeenCalledWith(new TableConfigurationViewProvider(openmct));
});
/*
it('defines a view for telemetry objects', function() {
let tableObject = createTableObject();
let views = openmct.objectViews.get(tableObject);
expect(findTableView(views)).toBeDefined();
});
it('defines a table view for telemetry objects', function() {
let telemetryObject = createTelemetryObject();
let views = openmct.objectViews.get(telemetryObject);
expect(findTableView(views)).toBeDefined();
});
it('defines a configuration view for table objects', function() {
let tableObject = createTableObject();
let selection = createSelection(tableObject);
let views = openmct.inspectorViews.get(selection);
expect(views).toBeDefined();
expect(findTableView(views)).toBeDefined();
});
function findTableView(views) {
return views.find(view => view.key === 'table');
}
function createTableObject() {
let tableObject = {};
tableType.definition.initialize(tableObject);
return tableObject;
}
function createTelemetryObject() {
return {
telemetry: {}
};
}
function createSelection(object) {
return [{
context: {
item: object
}
}];
}
*/
});

View File

@@ -19,18 +19,24 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import _ from 'lodash';
import MCT from '../../MCT.js';
import TelemetryTablePlugin from './plugin.js';
define([
'./FolderGridView',
'./FolderListView'
], function (
FolderGridView,
FolderListView
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new FolderGridView(openmct));
openmct.objectViews.addProvider(new FolderListView(openmct));
describe('The TelemetryTable view', function() {
let mockTimeSystem;
let openmct;
let tablePlugin;
beforeEach(function () {
openmct = new MCT();
mockTimeSystem = {
key: 'utc'
};
};
});
spyOn(openmct.time, 'timeSystem');
openmct.time.timeSystem.and.returnValue(mockTimeSystem);
});
//it('allows editing of table objects');
//it('does not allow editing of telemetry objects');
});

View File

@@ -48,12 +48,7 @@ define(function () {
}
getFormattedValue(telemetryDatum) {
let formattedValue = this.formatter.format(telemetryDatum);
if (typeof formattedValue !== 'string') {
return formattedValue.toString();
} else {
return formattedValue;
}
return this.formatter.format(telemetryDatum);
}
};

View File

@@ -39,7 +39,7 @@ define([], function () {
getFormattedValue(key) {
let column = this.columns[key];
return column && column.getFormattedValue(this.datum[key]);
return column.getFormattedValue(this.datum[key]);
}
getRowLimitClass() {

View File

@@ -30,7 +30,7 @@ define(function () {
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
columns: {}
hiddenColumns: {}
};
}
}

View File

@@ -35,7 +35,6 @@ define([
return {
key: 'table',
name: 'Telemetry Table',
cssClass: 'icon-tabular-realtime',
editable: function(domainObject) {
return domainObject.type === 'table';
},

View File

@@ -0,0 +1,111 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import _ from 'lodash';
import MCT from '../../../MCT.js';
import SortedTableRowCollection from './SortedTableRowCollection.js';
describe('The SortedTableRowCollection', function() {
let mockTimeSystem;
let openmct;
let rows;
let mockSortedIndex;
beforeEach(function () {
openmct = new MCT();
mockTimeSystem = {
key: 'utc'
};
spyOn(openmct.time, 'timeSystem');
openmct.time.timeSystem.and.returnValue(mockTimeSystem);
rows = new BoundedTableRowCollection(openmct);
});
describe('Shortcut behavior', function() {
let testTelemetry;
beforeEach(function() {
testTelemetry = [
{
datum: {utc: 100}
}, {
datum: {utc: 200}
}, {
datum: {utc: 300}
}, {
datum: {utc: 400}
}
];
rows.add(testTelemetry);
mockSortedIndex = spyOn(_, 'sortedIndex');
mockSortedIndex.and.callThrough();
});
describe('when sorted ascending', function () {
it('Uses lodash sortedIndex to find insertion point when test value is between first and last values', function () {
rows.add({
datum: {utc: 250}
});
expect(mockSortedIndex).toHaveBeenCalled();
});
it('shortcuts insertion point search when test value is greater than last value', function() {
rows.add({
datum: {utc: 500}
});
expect(mockSortedIndex).not.toHaveBeenCalled();
});
it('shortcuts insertion point search when test value is less than or equal to first value', function () {
rows.add({
datum: {utc: 100}
});
rows.add({
datum: {utc: 50}
});
expect(mockSortedIndex).not.toHaveBeenCalled();
});
});
describe('when sorted descending', function () {
it('Uses lodash sortedIndex to find insertion point when test value is between first and last values', function () {
rows.add({
datum: {utc: 250}
});
expect(mockSortedIndex).toHaveBeenCalled();
});
it('shortcuts insertion point search when test value is greater than last value', function() {
rows.add({
datum: {utc: 500}
});
expect(mockSortedIndex).not.toHaveBeenCalled();
});
it('shortcuts insertion point search when test value is less than or equal to first value', function () {
rows.add({
datum: {utc: 100}
});
rows.add({
datum: {utc: 50}
});
expect(mockSortedIndex).not.toHaveBeenCalled();
});
});
it('Evicts old telemetry on bounds change');
it('Does not drop data that falls ahead of end bounds');
});
});

View File

@@ -32,9 +32,7 @@ export default {
columnWidths: {
type: Array,
required: false,
default() {
return [];
},
default: [],
},
rowIndex: {
type: Number,
@@ -50,6 +48,10 @@ export default {
type: Number,
required: false,
default: 0
},
configuration: {
type: Object,
required: true
}
},
methods: {

View File

@@ -9,7 +9,7 @@
</a>
</div>
<!-- Headers table -->
<div class="c-telemetry-table__headers-w js-table__headers-w">
<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>
@@ -19,16 +19,16 @@
:class="['is-sortable', sortOptions.key === key ? 'is-sorting' : '', sortOptions.direction].join(' ')"
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}">{{title}}</th>
</tr>
<tr>
<tr class="s-filters">
<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)" />
<div class="holder l-filter flex-elem grows" :class="{active: filters[key]}">
<input type="text" v-model="filters[key]" v-on:input="filterChanged(key)" />
<a class="clear-icon clear-input icon-x-in-circle" :class="{show: filters[key]}" @click="clearFilter(key)"></a>
</div>
</th>
</tr>
</thead>
@@ -68,22 +68,46 @@
<style lang="scss">
@import "~styles/sass-base";
@import "~styles/table";
.c-telemetry-table {
// Table that displays telemetry in a scrolling body area
.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;
> [class*="__"] + [class*="__"] {
// Don't allow top level elements to grow or shrink
flex: 0 0 auto;
}
/******************************* ELEMENTS */
th, td {
display: block;
flex: 1 0 auto;
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
flex: 0 0 auto;
background: $colorTabHeaderBg;
overflow: hidden;
}
@@ -109,6 +133,65 @@
}
}
&__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
@@ -120,7 +203,7 @@
/******************************* WRAPPERS */
&__body-w {
// Wraps __body table provides scrolling
flex: 1 1 100%;
flex: 1 1 100% !important; // TODO: temp override on tabular-holder > * { style which sets this to 0 0 auto
overflow-x: auto;
overflow-y: scroll;
}
@@ -150,7 +233,7 @@
z-index: -1;
visibility: hidden;
pointer-events: none;
position: absolute;
position: absolute !important; // TODO: fix tabular-holder > * { which sets this to pos: relative
//Add some padding to allow for decorations such as limits indicator
tr {
@@ -166,6 +249,10 @@
}
}
.c-table__control-bar {
margin-bottom: $interiorMarginSm;
}
/******************************* LEGACY */
.s-status-taking-snapshot,
.overlay.snapshot {
@@ -177,7 +264,6 @@
<script>
import TelemetryTableRow from './table-row.vue';
import search from '../../../ui/components/controls/search.vue';
import _ from 'lodash';
const VISIBLE_ROW_COUNT = 100;
@@ -187,8 +273,7 @@ const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
export default {
components: {
TelemetryTableRow,
search
TelemetryTableRow
},
inject: ['table', 'openmct', 'csvExporter'],
props: ['configuration'],

View File

@@ -1,368 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-conductor"
:class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode', panning ? 'status-panning' : '']">
<form class="u-contents" ref="conductorForm"
@submit="isFixed ? setBoundsFromView($event) : setOffsetsFromView($event)">
<ConductorModeIcon class="c-conductor__mode-icon"></ConductorModeIcon>
<div class="c-conductor__start-input">
<!-- Start input and controls -->
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start__fixed"
v-if="isFixed">
<!-- Fixed input -->
<div class="c-conductor__start__fixed__label">Start</div>
<input class="c-input--datetime"
type="text" autocorrect="off" spellcheck="false"
ref="startDate"
v-model="formattedBounds.start"
@change="validateBounds('start', $event.target); setBoundsFromView()" />
<date-picker
:default-date-time="formattedBounds.start"
:formatter="timeFormatter"
@date-selected="startDateSelected"></date-picker>
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start__delta"
v-if="!isFixed">
<!-- RT input -->
<div class="c-direction-indicator icon-minus"></div>
<input class="c-input--hrs-min-sec"
type="text" autocorrect="off"
spellcheck="false"
v-model="offsets.start"
@change="validateOffsets($event); setOffsetsFromView()">
</div>
</div>
<div class="c-conductor__end-input">
<!-- End input and controls -->
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end__fixed"
v-if="isFixed">
<!-- Fixed input -->
<div class="c-conductor__end__fixed__label">End</div>
<input class="c-input--datetime"
type="text" autocorrect="off" spellcheck="false"
v-model="formattedBounds.end"
:disabled="!isFixed"
ref="endDate"
@change="validateBounds('end', $event.target); setBoundsFromView()">
<date-picker
class="c-ctrl-wrapper--menus-left"
:default-date-time="formattedBounds.end"
:formatter="timeFormatter"
@date-selected="endDateSelected"></date-picker>
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end__delta"
v-if="!isFixed">
<!-- RT input -->
<div class="c-direction-indicator icon-plus"></div>
<input class="c-input--hrs-min-sec"
type="text"
autocorrect="off"
spellcheck="false"
v-model="offsets.end"
@change="validateOffsets($event); setOffsetsFromView()">
</div>
</div>
<conductor-axis
class="c-conductor__ticks"
:bounds="rawBounds"
@panAxis="setViewFromBounds"></conductor-axis>
<div class="c-conductor__controls">
<!-- Mode, time system menu buttons and duration slider -->
<ConductorMode></ConductorMode>
<ConductorTimeSystem></ConductorTimeSystem>
</div>
<input type="submit" class="invisible">
</form>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
/*********************************************** CONDUCTOR LAYOUT */
.c-conductor {
display: grid;
grid-column-gap: $interiorMargin;
grid-row-gap: $interiorMargin;
grid-template-rows: 1fr 1fr;
grid-template-columns: 20px auto 1fr auto;
grid-template-areas:
"tc-mode-icon tc-start tc-ticks tc-end"
"tc-controls tc-controls tc-controls tc-controls";
align-items: center;
/* grid-template-columns: 20px 160px 1fr 180px;
grid-template-areas:
"tc-mode-icon tc-controls tc-controls tc-controls"
"tc-start tc-start tc-ticks tc-end";*/
&__mode-icon {
grid-area: tc-mode-icon;
}
&__start-input,
&__end-input {
display: flex;
}
&__start-input {
grid-area: tc-start;
}
&__end-input {
grid-area: tc-end;
display: flex;
justify-content: flex-end;
}
&__ticks {
grid-area: tc-ticks;
}
&__controls {
grid-area: tc-controls;
display: flex;
align-items: center;
> * + * {
margin-left: $interiorMargin;
}
}
[class*='__delta'] {
&:before {
content: $glyph-icon-clock;
font-family: symbolsfont;
}
}
}
.c-conductor-input {
color: $colorInputFg;
display: flex;
align-items: center;
justify-content: flex-start;
> * + * {
margin-left: $interiorMarginSm;
}
&:before {
// Realtime-mode clock icon symbol
margin-right: $interiorMarginSm;
}
.c-direction-indicator {
// Holds realtime-mode + and - symbols
font-size: 0.7em;
}
input:invalid {
background: rgba($colorFormInvalid, 0.3);
}
}
.is-realtime-mode {
.c-conductor-input {
&:before {
color: $colorTime;
}
}
}
</style>
<script>
import moment from 'moment';
import ConductorMode from './ConductorMode.vue';
import ConductorTimeSystem from './ConductorTimeSystem.vue';
import DatePicker from './DatePicker.vue';
import ConductorAxis from './ConductorAxis.vue';
import ConductorModeIcon from './ConductorModeIcon.vue';
const DEFAULT_DURATION_FORMATTER = 'duration';
const SECONDS = 1000;
const DAYS = 24 * 60 * 60 * SECONDS;
const YEARS = 365 * DAYS;
const RESIZE_POLL_INTERVAL = 200;
export default {
inject: ['openmct', 'configuration'],
components: {
ConductorMode,
ConductorTimeSystem,
DatePicker,
ConductorAxis,
ConductorModeIcon
},
data() {
let bounds = this.openmct.time.bounds();
let offsets = this.openmct.time.clockOffsets();
let timeSystem = this.openmct.time.timeSystem();
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
return {
timeFormatter: timeFormatter,
durationFormatter: durationFormatter,
offsets: {
start: offsets && durationFormatter.format(Math.abs(offsets.start)),
end: offsets && durationFormatter.format(Math.abs(offsets.end)),
},
formattedBounds: {
start: timeFormatter.format(bounds.start),
end: timeFormatter.format(bounds.end)
},
rawBounds: {
start: bounds.start,
end: bounds.end
},
isFixed: this.openmct.time.clock() === undefined,
isUTCBased: timeSystem.isUTCBased,
showDatePicker: false
}
},
methods: {
setTimeSystem(timeSystem) {
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
this.durationFormatter = this.getFormatter(
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
this.isUTCBased = timeSystem.isUTCBased;
},
setOffsetsFromView($event) {
if (this.$refs.conductorForm.checkValidity()){
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
let endOffset = this.durationFormatter.parse(this.offsets.end);
this.openmct.time.clockOffsets({
start: startOffset,
end: endOffset
});
}
if ($event) {
$event.preventDefault();
return false;
}
},
setBoundsFromView($event) {
if (this.$refs.conductorForm.checkValidity()){
let start = this.timeFormatter.parse(this.formattedBounds.start);
let end = this.timeFormatter.parse(this.formattedBounds.end);
this.openmct.time.bounds({
start: start,
end: end
});
}
if ($event) {
$event.preventDefault();
return false;
}
},
setViewFromClock(clock) {
this.isFixed = clock === undefined;
},
setViewFromBounds(bounds) {
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
this.rawBounds.start = bounds.start;
this.rawBounds.end = bounds.end;
},
setViewFromOffsets(offsets) {
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
this.offsets.end = this.durationFormatter.format(Math.abs(offsets.end));
},
validateBounds(startOrEnd, input) {
let validationResult = true;
if (!this.timeFormatter.validate(input.value)){
validationResult = 'Invalid date value';
} else {
let boundsValues = {
start: this.timeFormatter.parse(this.formattedBounds.start),
end: this.timeFormatter.parse(this.formattedBounds.end)
};
validationResult = this.openmct.time.validateBounds(boundsValues);
}
if (validationResult !== true){
input.setCustomValidity(validationResult);
} else {
input.setCustomValidity('');
}
},
validateOffsets(event) {
let input = event.target;
let validationResult = true;
if (!this.durationFormatter.validate(input.value)) {
validationResult = 'Invalid offset value';
} else {
let offsetValues = {
start: 0 - this.durationFormatter.parse(this.offsets.start),
end: this.durationFormatter.parse(this.offsets.end)
};
validationResult = this.openmct.time.validateOffsets(offsetValues);
}
if (validationResult !== true){
input.setCustomValidity(validationResult);
} else {
input.setCustomValidity('');
}
},
getFormatter(key) {
return this.openmct.telemetry.getValueFormatter({
format: key
}).formatter;
},
startDateSelected(date){
this.formattedBounds.start = this.timeFormatter.format(date);
this.validateBounds('start', this.$refs.startDate);
this.setBoundsFromView();
},
endDateSelected(date){
this.formattedBounds.end = this.timeFormatter.format(date);
this.validateBounds('end', this.$refs.endDate);
this.setBoundsFromView();
},
},
mounted() {
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
this.openmct.time.on('bounds', this.setViewFromBounds);
this.openmct.time.on('timeSystem', this.setTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
}
}
</script>

View File

@@ -1,261 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-conductor-axis"
ref="axisHolder"
@mousedown="dragStart($event)">
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-conductor-axis {
$h: 18px;
$tickYPos: ($h / 2) + 12px;
@include userSelectNone();
@include bgTicks($c: rgba($colorBodyFg, 0.4));
background-position: 0 50%;
height: $h;
svg {
text-rendering: geometricPrecision;
width: 100%;
height: 100%;
> g {
// Overall Tick holder
transform: translateY($tickYPos);
path {
// Domain line
display: none;
}
g {
// Each tick. These move on drag.
line {
// Line beneath ticks
display: none;
}
}
}
text {
// Tick labels
font-size: 1em;
paint-order: stroke;
font-weight: bold;
stroke-linecap: butt;
stroke-linejoin: bevel;
stroke-width: 6px;
}
}
.is-fixed-mode & {
@include cursorGrab();
background-size: 3px 30%;
border-radius: $controlCr;
background-color: $colorBodyBgSubtle;
box-shadow: inset rgba(black, 0.2) 0 1px 1px;
svg text {
fill: $colorBodyFg;
stroke: $colorBodyBgSubtle;
}
&:hover,
&:active {
$c: $colorKeySubtle;
background-color: $c;
svg text {
stroke: $c;
}
}
}
.is-realtime-mode & {
background-size: 5px 2px;
svg text {
fill: $colorTime;
stroke: $colorBodyBg;
}
}
}
</style>
<script>
import * as d3Selection from 'd3-selection';
import * as d3Axis from 'd3-axis';
import * as d3Scale from 'd3-scale';
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
const PADDING = 1;
const DEFAULT_DURATION_FORMATTER = 'duration';
const RESIZE_POLL_INTERVAL = 200;
const PIXELS_PER_TICK = 100;
const PIXELS_PER_TICK_WIDE = 200;
export default {
inject: ['openmct'],
props: {
bounds: Object
},
methods: {
setScale() {
let timeSystem = this.openmct.time.timeSystem();
let bounds = this.bounds;
if (timeSystem.isUTCBased) {
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
} else {
this.xScale.domain([bounds.start, bounds.end]);
}
this.xAxis.scale(this.xScale);
this.xScale.range([PADDING, this.width - PADDING * 2]);
this.axisElement.call(this.xAxis);
if (this.width > 1800) {
this.xAxis.ticks(this.width / PIXELS_PER_TICK_WIDE);
} else {
this.xAxis.ticks(this.width / PIXELS_PER_TICK);
}
this.msPerPixel = (bounds.end - bounds.start) / this.width;
},
setViewFromTimeSystem(timeSystem) {
let format = this.getActiveFormatter();
let bounds = this.openmct.time.bounds();
//The D3 scale used depends on the type of time system as d3
// supports UTC out of the box.
if (timeSystem.isUTCBased) {
this.xScale = d3Scale.scaleUtc();
} else {
this.xScale = d3Scale.scaleLinear();
}
this.xAxis.scale(this.xScale);
this.xAxis.tickFormat(utcMultiTimeFormat);
this.axisElement.call(this.xAxis);
},
getActiveFormatter() {
let timeSystem = this.openmct.time.timeSystem();
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed) {
return this.getFormatter(timeSystem.timeFormat);
} else {
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
}
},
getFormatter(key) {
return this.openmct.telemetry.getValueFormatter({
format: key
}).formatter;
},
dragStart($event){
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed){
this.dragStartX = $event.clientX;
document.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.dragEnd, {
once: true
});
}
},
drag($event) {
if (!this.dragging){
this.dragging = true;
requestAnimationFrame(()=>{
let deltaX = $event.clientX - this.dragStartX;
let percX = deltaX / this.width;
let bounds = this.openmct.time.bounds();
let deltaTime = bounds.end - bounds.start;
let newStart = bounds.start - percX * deltaTime;
this.bounds = {
start: newStart,
end: newStart + deltaTime
};
this.$emit('panAxis', this.bounds);
this.dragging = false;
})
} else {
console.log('Rejected drag due to RAF cap');
}
},
dragEnd() {
document.removeEventListener('mousemove', this.drag);
this.openmct.time.bounds({
start: this.bounds.start,
end: this.bounds.end
});
},
resize() {
if (this.$refs.axisHolder.clientWidth !== this.width) {
this.width = this.$refs.axisHolder.clientWidth;
this.setScale();
}
}
},
watch: {
bounds: {
handler(bounds) {
this.setScale();
},
deep: true
}
},
mounted() {
let axisHolder = this.$refs.axisHolder;
let height = axisHolder.offsetHeight;
let vis = d3Selection.select(axisHolder)
.append("svg:svg")
.attr("width", "100%")
.attr("height", height);
this.width = this.$refs.axisHolder.clientWidth;
this.xAxis = d3Axis.axisTop();
this.dragging = false;
// draw x axis with labels. CSS is used to position them.
this.axisElement = vis.append("g");
this.setViewFromTimeSystem(this.openmct.time.timeSystem());
this.setScale();
//Respond to changes in conductor
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
setInterval(this.resize, RESIZE_POLL_INTERVAL);
},
destroyed() {
}
}
</script>

View File

@@ -1,213 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
<div class="c-button--menu c-mode-button"
@click="toggleMenu($event)">
<span class="c-button__label">{{selectedMode.name}}</span>
</div>
<div class="c-menu c-super-menu c-conductor__mode-menu"
v-if="showMenu">
<div class="c-super-menu__menu">
<ul>
<li v-for="mode in modes"
:key="mode.key"
@click="setOption(mode)"
@mouseover="hoveredMode = mode"
@mouseleave="hoveredMode = {}"
class="menu-item-a"
:class="mode.cssClass">
{{mode.name}}
</li>
</ul>
</div>
<div class="c-super-menu__item-description">
<div :class="['l-item-description__icon', 'bg-' + hoveredMode.cssClass]"></div>
<div class="l-item-description__name">{{hoveredMode.name}}</div>
<div class="l-item-description__description">{{hoveredMode.description}}</div>
</div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-conductor__mode-menu {
max-height: 80vh;
max-width: 500px;
min-height: 250px;
z-index: 70;
[class*="__icon"] {
filter: $colorKeyFilter;
}
[class*="__item-description"] {
min-width: 200px;
}
}
.is-realtime-mode {
.c-mode-button {
background: $colorTimeBg;
&:hover {
background: $colorTimeHov;
}
}
}
</style>
<script>
export default {
inject: ['openmct', 'configuration'],
data: function () {
let activeClock = this.openmct.time.clock();
if (activeClock !== undefined) {
//Create copy of active clock so the time API does not get reactified.
activeClock = Object.create(activeClock);
}
return {
selectedMode: this.getModeOptionForClock(activeClock),
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())),
modes: [],
hoveredMode: {},
showMenu: false
};
},
methods: {
loadClocksFromConfiguration() {
let clocks = this.configuration.menuOptions
.map(menuOption => menuOption.clock)
.filter(isDefinedAndUnique)
.map(this.getClock);
/*
* Populate the modes menu with metadata from the available clocks
* "Fixed Mode" is always first, and has no defined clock
*/
this.modes = [undefined]
.concat(clocks)
.map(this.getModeOptionForClock);
function isDefinedAndUnique(key, index, array) {
return key!== undefined && array.indexOf(key) === index;
}
},
getModeOptionForClock(clock) {
if (clock === undefined) {
return {
key: 'fixed',
name: 'Fixed Timespan Mode',
description: 'Query and explore data that falls between two fixed datetimes.',
cssClass: 'icon-calendar'
}
} else {
return {
key: clock.key,
name: clock.name,
description: "Monitor streaming data in real-time. The Time " +
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
cssClass: clock.cssClass || 'icon-clock'
}
}
},
getClock(key) {
return this.openmct.time.getAllClocks().filter(function (clock) {
return clock.key === key;
})[0];
},
setOption(option) {
let clockKey = option.key;
if (clockKey === 'fixed') {
clockKey = undefined;
}
let configuration = this.getMatchingConfig({
clock: clockKey,
timeSystem: this.openmct.time.timeSystem().key
});
if (configuration === undefined) {
configuration = this.getMatchingConfig({
clock: clockKey
});
this.openmct.time.timeSystem(configuration.timeSystem, configuration.bounds);
}
if (clockKey === undefined) {
this.openmct.time.stopClock();
} else {
this.openmct.time.clock(clockKey, configuration.clockOffsets);
}
},
getMatchingConfig(options) {
const matchers = {
clock(config) {
return options.clock === config.clock
},
timeSystem(config) {
return options.timeSystem === config.timeSystem
}
};
function configMatches(config) {
return Object.keys(options).reduce((match, option) => {
return match && matchers[option](config);
}, true);
}
return this.configuration.menuOptions.filter(configMatches)[0];
},
setViewFromClock(clock) {
this.selectedMode = this.getModeOptionForClock(clock);
},
toggleMenu(event) {
this.showMenu = !this.showMenu;
if (this.showMenu) {
document.addEventListener('click', this.toggleMenu, true);
} else {
document.removeEventListener('click', this.toggleMenu, true);
}
},
},
mounted: function () {
this.loadClocksFromConfiguration();
this.openmct.time.on('clock', this.setViewFromClock);
},
destroyed: function () {
this.openmct.time.off('clock', this.setViewFromClock);
}
}
</script>

View File

@@ -1,118 +0,0 @@
<template>
<div class="c-clock-symbol">
<div class="hand-little"></div>
<div class="hand-big"></div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
@keyframes clock-hands {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
@keyframes clock-hands-sticky {
0% { transform: translate(-50%, -50%) rotate(0deg); }
7% { transform: translate(-50%, -50%) rotate(0deg); }
8% { transform: translate(-50%, -50%) rotate(30deg); }
15% { transform: translate(-50%, -50%) rotate(30deg); }
16% { transform: translate(-50%, -50%) rotate(60deg); }
24% { transform: translate(-50%, -50%) rotate(60deg); }
25% { transform: translate(-50%, -50%) rotate(90deg); }
32% { transform: translate(-50%, -50%) rotate(90deg); }
33% { transform: translate(-50%, -50%) rotate(120deg); }
40% { transform: translate(-50%, -50%) rotate(120deg); }
41% { transform: translate(-50%, -50%) rotate(150deg); }
49% { transform: translate(-50%, -50%) rotate(150deg); }
50% { transform: translate(-50%, -50%) rotate(180deg); }
57% { transform: translate(-50%, -50%) rotate(180deg); }
58% { transform: translate(-50%, -50%) rotate(210deg); }
65% { transform: translate(-50%, -50%) rotate(210deg); }
66% { transform: translate(-50%, -50%) rotate(240deg); }
74% { transform: translate(-50%, -50%) rotate(240deg); }
75% { transform: translate(-50%, -50%) rotate(270deg); }
82% { transform: translate(-50%, -50%) rotate(270deg); }
83% { transform: translate(-50%, -50%) rotate(300deg); }
90% { transform: translate(-50%, -50%) rotate(300deg); }
91% { transform: translate(-50%, -50%) rotate(330deg); }
99% { transform: translate(-50%, -50%) rotate(330deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
.c-clock-symbol {
$c: $colorBtnBg; //$colorObjHdrIc;
$d: 18px;
height: $d;
width: $d;
position: relative;
&:before {
font-family: symbolsfont;
color: $c;
content: $glyph-icon-brackets;
font-size: $d;
line-height: normal;
display: block;
width: 100%;
height: 100%;
z-index: 1;
}
// Clock hands
div[class*="hand"] {
$handW: 2px;
$handH: $d * 0.4;
animation-iteration-count: infinite;
animation-timing-function: linear;
transform-origin: bottom;
position: absolute;
height: $handW;
width: $handW;
left: 50%;
top: 50%;
z-index: 2;
&:before {
background: $c;
content: '';
display: block;
position: absolute;
width: 100%;
bottom: -1px;
}
&.hand-little {
z-index: 2;
animation-duration: 12s;
transform: translate(-50%, -50%) rotate(120deg);
&:before {
height: ceil($handH * 0.6);
}
}
&.hand-big {
z-index: 1;
animation-duration: 1s;
transform: translate(-50%, -50%);
&:before {
height: $handH;
}
}
}
// Modes
.is-realtime-mode &,
.is-lad-mode & {
&:before {
// Brackets icon
color: $colorTime;
}
div[class*="hand"] {
animation-name: clock-hands;
&:before {
background: $colorTime;
}
}
}
}
</style>

View File

@@ -1,140 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="holder flex-elem time-system c-ctrl-wrapper c-ctrl-wrapper--menus-up"
v-if="selectedTimeSystem.name">
<div class="c-button--menu c-time-system-button"
:class="selectedTimeSystem.cssClass"
@click="toggleMenu($event)">
<span class="c-button__label">{{selectedTimeSystem.name}}</span>
</div>
<div class="c-menu" v-if="showMenu">
<ul>
<li @click="setTimeSystemFromView(timeSystem)"
v-for="timeSystem in timeSystems"
:key="timeSystem.key"
:class="timeSystem.cssClass">
{{timeSystem.name}}
</li>
</ul>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.is-realtime-mode {
.c-time-system-button {
background: $colorTimeBg;
&:hover {
background: $colorTimeHov;
}
}
}
</style>
<script>
export default {
inject: ['openmct', 'configuration'],
data: function () {
let activeClock = this.openmct.time.clock();
return {
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())),
timeSystems: this.getValidTimesystemsForClock(activeClock),
showMenu: false
};
},
methods: {
getValidTimesystemsForClock(clock) {
return this.configuration.menuOptions
.filter(menuOption => menuOption.clock === (clock && clock.key))
.map(menuOption => JSON.parse(JSON.stringify(this.openmct.time.timeSystems.get(menuOption.timeSystem))));
},
setTimeSystemFromView(timeSystem) {
if (timeSystem.key !== this.selectedTimeSystem.key) {
let activeClock = this.openmct.time.clock();
let configuration = this.getMatchingConfig({
clock: activeClock && activeClock.key,
timeSystem: timeSystem.key
});
if (activeClock === undefined) {
this.openmct.time.timeSystem(timeSystem.key, configuration.bounds);
} else {
this.openmct.time.timeSystem(timeSystem.key);
this.openmct.time.clockOffsets(configuration.clockOffsets);
}
}
},
getMatchingConfig(options) {
const matchers = {
clock(config) {
return options.clock === config.clock
},
timeSystem(config) {
return options.timeSystem === config.timeSystem
}
};
function configMatches(config) {
return Object.keys(options).reduce((match, option) => {
return match && matchers[option](config);
}, true);
}
return this.configuration.menuOptions.filter(configMatches)[0];
},
toggleMenu(event) {
this.showMenu = !this.showMenu;
if (this.showMenu) {
document.addEventListener('click', this.toggleMenu, true);
} else {
document.removeEventListener('click', this.toggleMenu, true);
}
},
setViewFromTimeSystem(timeSystem) {
this.selectedTimeSystem = timeSystem;
},
setViewFromClock(clock) {
let activeClock = this.openmct.time.clock();
this.timeSystems = this.getValidTimesystemsForClock(activeClock);
}
},
mounted: function () {
this.openmct.time.on('timeSystem', this.setViewFromTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
},
destroyed: function () {
this.openmct.time.off('timeSystem', this.setViewFromTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
}
}
</script>

View File

@@ -1,312 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<!-- TODOS: changeMonth doesn't appear to work, was ng-click -->
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up" ref="calendarHolder">
<a class="c-click-icon icon-calendar"
@click="togglePicker()"></a>
<div class="c-menu c-datetime-picker"
v-if="showPicker">
<div class="c-datetime-picker__month-year-pager c-pager l-month-year-pager">
<div class="c-pager__prev c-click-icon icon-arrow-left"
@click="changeMonth(-1)"></div>
<div class="c-pager__month-year">{{model.month}} {{model.year}}</div>
<div class="c-pager__next c-click-icon icon-arrow-right"
@click="changeMonth(1)"></div>
</div>
<div class="c-datetime-picker__calendar c-calendar">
<ul class="c-calendar__row--header l-cal-row">
<li v-for="day in ['Su','Mo','Tu','We','Th','Fr','Sa']"
:key="day">{{day}}</li>
</ul>
<ul class="c-calendar__row--body"
v-for="(row, index) in table"
:key="index">
<li v-for="(cell, index) in row"
:key="index"
@click="select(cell)"
:class="{ 'is-in-month': isInCurrentMonth(cell), selected: isSelected(cell) }">
<div class="c-calendar__day--prime">{{cell.day}}</div>
<div class="c-calendar__day--sub">{{cell.dayOfYear}}</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
/******************************************************** PICKER */
.c-datetime-picker {
@include userSelectNone();
padding: $interiorMarginLg !important;
display: flex;
flex-direction: column;
> * + * {
border-top: 1px solid $colorInteriorBorder;
margin-top: $interiorMargin;
}
}
.c-pager {
display: grid;
grid-column-gap: $interiorMargin;
grid-template-rows: 1fr;
grid-template-columns: auto 1fr auto;
align-items: center;
.c-click-icon {
font-size: 0.8em;
}
&__month-year {
text-align: center;
}
}
/******************************************************** CALENDAR */
.c-calendar {
display: grid;
grid-template-columns: repeat(7, min-content);
grid-template-rows: auto;
grid-gap: 1px;
$mutedOpacity: 0.7;
ul {
display: contents;
&[class*='--header'] {
pointer-events: none;
li {
opacity: $mutedOpacity;
}
}
}
li {
display: flex;
flex-direction: column;
padding: $interiorMargin;
&.is-in-month {
background: rgba($colorBodyFg, 0.1);
}
}
&__day {
&--sub {
opacity: $mutedOpacity;
font-size: 0.8em;
}
}
}
</style>
<script>
import moment from 'moment';
const TIME_NAMES = {
'hours': "Hour",
'minutes': "Minute",
'seconds': "Second"
};
const MONTHS = moment.months();
const TIME_OPTIONS = (function makeRanges() {
let arr = [];
while (arr.length < 60) {
arr.push(arr.length);
}
return {
hours: arr.slice(0, 24),
minutes: arr,
seconds: arr
};
}());
export default {
inject: ['openmct'],
props: {
defaultDateTime: String,
formatter: Object
},
data: function () {
return {
showPicker: false,
picker: {
year: undefined,
month: undefined,
interacted: false
},
model: {
year: undefined,
month: undefined,
},
table: undefined,
date: undefined,
time: undefined
}
},
methods: {
generateTable() {
let m = moment.utc({ year: this.picker.year, month: this.picker.month }).day(0),
table = [],
row,
col;
for (row = 0; row < 6; row += 1) {
table.push([]);
for (col = 0; col < 7; col += 1) {
table[row].push({
year: m.year(),
month: m.month(),
day: m.date(),
dayOfYear: m.dayOfYear()
});
m.add(1, 'days'); // Next day!
}
}
return table;
},
updateViewForMonth() {
this.model.month = MONTHS[this.picker.month];
this.model.year = this.picker.year;
this.table = this.generateTable();
},
updateFromModel(defaultDateTime) {
let m;
m = moment.utc(defaultDateTime);
this.date = {
year: m.year(),
month: m.month(),
day: m.date()
};
this.time = {
hours: m.hour(),
minutes: m.minute(),
seconds: m.second()
};
// Zoom to that date in the picker, but
// only if the user hasn't interacted with it yet.
if (!this.picker.interacted) {
this.picker.year = m.year();
this.picker.month = m.month();
this.updateViewForMonth();
}
},
updateFromView() {
let m = moment.utc({
year: this.date.year,
month: this.date.month,
day: this.date.day,
hour: this.time.hours,
minute: this.time.minutes,
second: this.time.seconds
});
this.$emit('date-selected', m.valueOf());
},
isInCurrentMonth(cell) {
return cell.month === this.picker.month;
},
isSelected(cell) {
let date = this.date || {};
return cell.day === date.day &&
cell.month === date.month &&
cell.year === date.year;
},
select(cell) {
this.date = this.date || {};
this.date.month = cell.month;
this.date.year = cell.year;
this.date.day = cell.day;
this.updateFromView();
this.showPicker = false;
},
dateEquals(d1, d2) {
return d1.year === d2.year &&
d1.month === d2.month &&
d1.day === d2.day;
},
changeMonth(delta) {
this.picker.month += delta;
if (this.picker.month > 11) {
this.picker.month = 0;
this.picker.year += 1;
}
if (this.picker.month < 0) {
this.picker.month = 11;
this.picker.year -= 1;
}
this.picker.interacted = true;
this.updateViewForMonth();
},
nameFor(key) {
return TIME_NAMES[key];
},
optionsFor(key) {
return TIME_OPTIONS[key];
},
hidePicker(event) {
let path = event.composedPath();
if (path.indexOf(this.$refs.calendarHolder) === -1) {
this.showPicker = false;
}
},
togglePicker() {
this.showPicker = !this.showPicker;
if (this.showPicker) {
document.addEventListener('click', this.hidePicker, {
capture: true
});
}
}
},
mounted: function () {
this.updateFromModel(this.defaultDateTime);
this.updateViewForMonth();
},
destroyed: function () {
document.addEventListener('click', this.hidePicker, {
capture: true
});
}
}
</script>

View File

@@ -20,108 +20,110 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Conductor from './Conductor.vue';
import Vue from 'vue';
define([], function () {
function isTruthy(a) {
return !!a;
}
function isTruthy(a) {
return !!a;
}
function validateMenuOption(menuOption, index) {
if (menuOption.clock && !menuOption.clockOffsets) {
return "clock-based menuOption at index " + index + " is " +
"missing required property 'clockOffsets'.";
function validateMenuOption(menuOption, index) {
if (menuOption.clock && !menuOption.clockOffsets) {
return "clock-based menuOption at index " + index + " is " +
"missing required property 'clockOffsets'.";
}
if (!menuOption.timeSystem) {
return "menuOption at index " + index + " is missing " +
"required property 'timeSystem'.";
}
if (!menuOption.bounds && !menuOption.clock) {
return "fixed-bounds menuOption at index " + index + " is " +
"missing required property 'bounds'";
}
}
if (!menuOption.timeSystem) {
return "menuOption at index " + index + " is missing " +
"required property 'timeSystem'.";
}
if (!menuOption.bounds && !menuOption.clock) {
return "fixed-bounds menuOption at index " + index + " is " +
"missing required property 'bounds'";
}
}
function validateConfiguration(config) {
if (config === undefined ||
config.menuOptions === undefined ||
config.menuOptions.length === 0) {
return "You must specify one or more 'menuOptions'.";
function validateConfiguration(config) {
if (config === undefined ||
config.menuOptions === undefined ||
config.menuOptions.length === 0) {
return "You must specify one or more 'menuOptions'.";
}
if (config.menuOptions.some(validateMenuOption)) {
return config.menuOptions.map(validateMenuOption)
.filter(isTruthy)
.join('\n');
}
return undefined;
}
if (config.menuOptions.some(validateMenuOption)) {
return config.menuOptions.map(validateMenuOption)
function validateRuntimeConfiguration(config, openmct) {
var systems = openmct.time.getAllTimeSystems()
.reduce(function (m, ts) {
m[ts.key] = ts;
return m;
}, {});
var clocks = openmct.time.getAllClocks()
.reduce(function (m, c) {
m[c.key] = c;
return m;
}, {});
return config.menuOptions.map(function (menuOption, index) {
if (menuOption.timeSystem && !systems[menuOption.timeSystem]) {
return "menuOption at index " + index + " specifies a " +
"timeSystem that does not exist: " + menuOption.timeSystem;
}
if (menuOption.clock && !clocks[menuOption.clock]) {
return "menuOption at index " + index + " specifies a " +
"clock that does not exist: " + menuOption.clock;
}
})
.filter(isTruthy)
.join('\n');
}
return undefined;
}
function validateRuntimeConfiguration(config, openmct) {
var systems = openmct.time.getAllTimeSystems()
.reduce(function (m, ts) {
m[ts.key] = ts;
return m;
}, {});
var clocks = openmct.time.getAllClocks()
.reduce(function (m, c) {
m[c.key] = c;
return m;
}, {});
return config.menuOptions.map(function (menuOption, index) {
if (menuOption.timeSystem && !systems[menuOption.timeSystem]) {
return "menuOption at index " + index + " specifies a " +
"timeSystem that does not exist: " + menuOption.timeSystem;
function throwConfigErrorIfExists(error) {
if (error) {
throw new Error("Invalid Time Conductor Configuration: \n" +
error + '\n' +
"https://github.com/nasa/openmct/blob/master/API.md#the-time-conductor");
}
if (menuOption.clock && !clocks[menuOption.clock]) {
return "menuOption at index " + index + " specifies a " +
"clock that does not exist: " + menuOption.clock;
}
}).filter(isTruthy).join('\n');
}
function throwIfError(configResult) {
if (configResult) {
throw new Error("Invalid Time Conductor Configuration: \n" +
configResult + '\n' +
"https://github.com/nasa/openmct/blob/master/API.md#the-time-conductor");
}
}
function mountComponent(openmct, configuration) {
openmct.layout.conductorComponent = Object.create({
components: {
Conductor
},
template: "<conductor></conductor>",
provide: {
openmct: openmct,
configuration: configuration
}
});
}
return function (config) {
export default function (config){
throwConfigErrorIfExists(validateConfiguration(config));
let configResult = validateConfiguration(config);
throwIfError(configResult);
return function (openmct) {
return function (openmct) {
openmct.legacyExtension('constants', {
key: 'CONDUCTOR_CONFIG',
value: config,
priority: 'mandatory'
});
openmct.on('start', function () {
configResult = validateRuntimeConfiguration(config, openmct);
throwIfError(configResult);
openmct.legacyRegistry.enable('platform/features/conductor/core');
openmct.legacyRegistry.enable('platform/features/conductor/compatibility');
var defaults = config.menuOptions[0];
if (defaults.clock) {
openmct.time.clock(defaults.clock, defaults.clockOffsets);
openmct.time.timeSystem(defaults.timeSystem, openmct.time.bounds());
} else {
openmct.time.timeSystem(defaults.timeSystem, defaults.bounds);
}
openmct.on('start', function () {
mountComponent(openmct, config);
throwConfigErrorIfExists(validateRuntimeConfiguration(config, openmct));
});
/*
On app startup, default the conductor if not already set.
*/
if (openmct.time.timeSystem() !== undefined) {
return;
}
var defaults = config.menuOptions[0];
if (defaults.clock) {
openmct.time.clock(defaults.clock, defaults.clockOffsets);
openmct.time.timeSystem(defaults.timeSystem, openmct.time.bounds());
} else {
openmct.time.timeSystem(defaults.timeSystem, defaults.bounds);
}
});
};
};
};
});

View File

@@ -1,6 +1,6 @@
@import "constants";
// Functions
// Mixins
@function pullForward($c: $colorBodyBg, $p: 20%) {
// For dark interfaces, lighter things come forward - opposite for light interfaces
@return darken($c, $p);
@@ -12,7 +12,6 @@
}
// Global
$fontBaseSize: 12px;
$colorBodyBg: #fcfcfc;
$colorBodyFg: #666;
$colorGenBg: #fff;
@@ -20,13 +19,11 @@ $colorStatusBarBg: #000;
$colorStatusBarFg: #999;
$colorStatusBarFgHov: #aaa;
$colorKey: #0099cc;
$colorKeyFilter: brightness(0.9) sepia(1) hue-rotate(145deg) saturate(6);
$colorKeyFilterHov: brightness(1) sepia(1) hue-rotate(145deg) saturate(7);
$colorKeySelectedBg: $colorKey;
$colorKeyFg: #fff;
$colorKeyHov: #00c0f6;
$colorEditAreaBg: #eafaff; // Deprecated, use $editColor instead
$colorEditAreaFg: #4bb1c7; // Deprecated, use $editColor instead
$colorEditAreaBg: #eafaff;
$colorEditAreaFg: #4bb1c7;
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #999;
$colorAHov: $colorKey;
@@ -38,19 +35,8 @@ $smallCr: 2px;
$overlayCr: 11px;
$shdwTextSubtle: rgba(black, 0.2) 0 1px 2px;
// Variations
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
$colorBodyBgSubtleHov: pushBack($colorKey, 50%);
$colorKeySubtle: pushBack($colorKey, 50%);
$colorTime: #618cff;
$colorTimeBg: $colorTime;
$colorTimeFg: $colorBodyBg;
$colorTimeHov: pushBack($colorTime, 5%);
$colorTimeSubtle: pushBack($colorTime, 20%);
// Buttons and Controls
$btnPad: $interiorMargin, $interiorMargin * 1.25;
$colorBtnBg: #aaaaaa;
$colorBtnBg: pullForward($colorBodyBg, $contrastRatioPercent);
$colorBtnBgHov: pullForward($colorBtnBg, $hoverRatioPercent);
$colorBtnFg: #fff;
$colorBtnFgHov: $colorBtnFg;
@@ -60,9 +46,6 @@ $colorBtnMajorBg: $colorKey;
$colorBtnMajorBgHov: $colorKeyHov;
$colorBtnMajorFg: $colorKeyFg;
$colorBtnMajorFgHov: pushBack($colorBtnMajorFg, $hoverRatioPercent);
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnFg;
$colorClickIcon: $colorKey;
$colorClickIconHov: $colorKeyHov;
$colorToggleIcon: rgba($colorClickIcon, 0.5);
@@ -83,9 +66,8 @@ $sliderColorRangeValHovFg: $colorBodyFg;
$sliderKnobW: 15px;
$sliderKnobR: 2px;
$timeControllerToiLineColor: $colorBodyFg;
$timeControllerToiLineColorHov: $colorTime;
$timeControllerToiLineColorHov: #0052b5;
$colorTransLucBg: #666; // Used as a visual blocking element over variable backgrounds, like imagery
$createBtnTextTransform: uppercase;
// Foundation Colors
$colorAlt1: #776ba2;
@@ -96,43 +78,29 @@ $colorDiagnostic: #a4b442;
$colorCommand: #3693bd;
$colorInfo: #2294a2;
$colorOk: #33cc33;
$colorIconAlias: #4af6f3;
$colorIconAliasForKeyFilter: #aaa;
$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);
$editColor: #00c7c3;
$browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2);
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
$editBorderSelectable: 1px dotted rgba($editColor, 1);
$editBorderSelectableHov: 1px dashed rgba($editColor, 1);
$editBorderSelected: 1px solid $editColor;
$editBorderDrilledIn: 1px dashed #ff4d9a;
$colorGridLines: rgba($editColor, 0.2);
// Menus
// Menu colors
$colorMenuBg: pushBack($colorBodyBg, 10%);
$colorMenuFg: pullForward($colorMenuBg, 70%);
$colorMenuIc: $colorKey;
$colorMenuHovBg: $colorMenuIc; //pullForward($colorMenuBg, $hoverRatioPercent);
$colorMenuHovFg: $colorMenuBg;
$colorMenuHovIc: $colorMenuBg;
$colorMenuHovBg: pullForward($colorMenuBg, $hoverRatioPercent);
$colorMenuHovFg: $colorMenuFg;
$colorMenuHovIc: $colorMenuIc;
$shdwMenu: rgba(black, 0.5) 0 1px 5px;
$shdwMenuText: none;
$colorCreateMenuLgIcon: $colorKey;
$colorCreateMenuText: $colorBodyFg;
$menuItemPad: ($interiorMargin, nth($btnPad, 2));
// Palettes and Swatches
$paletteItemBorderOuterColorSelected: black;
$paletteItemBorderInnerColorSelected: white;
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
// Form colors
$colorCheck: $colorKey;
@@ -150,9 +118,6 @@ $colorInputPlaceholder: pushBack($colorBodyFg, 20%);
$colorFormText: pushBack($colorBodyFg, 10%);
$colorInputIcon: pushBack($colorBodyFg, 25%);
$colorFieldHint: pullForward($colorBodyFg, 40%);
$shdwInput: inset rgba(black, 0.4) 0 0 1px;
$shdwInputHov: inset rgba(black, 0.7) 0 0 1px;
$shdwInputFoc: inset rgba(black, 0.7) 0 0 3px;
// Inspector
$colorInspectorBg: pullForward($colorBodyBg, 5%);
@@ -212,9 +177,7 @@ $durLargeViewExpand: 250ms;
// Items
$colorItemBg: #ddd;
$colorItemBgHov: rgba($colorKey, 0.1); //pushBack($colorItemBg, $hoverRatioPercent * 0.4);
$colorListItemBg: transparent;
$colorListItemBgHov: rgba($colorKey, 0.1);
$colorItemBgHov: pullForward($colorItemBg, $hoverRatioPercent * 0.7);
$colorItemFg: $colorBodyFg;
$colorItemFgDetails: pushBack($colorItemFg, 15%);
$colorItemIc: $colorKey;
@@ -275,14 +238,13 @@ $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
$splitterBtnD: (16px, 35px);
$splitterBtnColorBg: #eee;
$splitterBtnColorFg: #999;
$splitterBtnColorHoverBg: rgba($colorKey, 1);
@@ -310,6 +272,12 @@ $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;
@@ -318,20 +286,5 @@ $colorLoadingFg: $colorAlt1;
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions
$transIn: all 50ms ease-in-out;
$transOut: all 250ms ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
// 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; }
$transIn: all 50ms ease-in;
$transOut: all 250ms ease-out;

View File

@@ -15,10 +15,8 @@ $interiorMarginLg: 10px;
$inputTextPTopBtm: 2px;
$inputTextPLeftRight: 5px;
$inputTextP: $inputTextPTopBtm $inputTextPLeftRight;
$menuLineH: 1.5rem;
$treeItemIndent: 16px;
$treeTypeIconW: 18px;
/*************** Items */
$itemPadLR: 5px;
$ueBrowseGridItemLg: 200px;
@@ -113,7 +111,6 @@ $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';
@@ -145,38 +142,4 @@ $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");
$glyph-icon-notebook: '\e1131';

View File

@@ -1,590 +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.
*****************************************************************************/
// VERSION MANUALLY MIGRATED FROM VUE-TOOLBAR
/******************************************************** PLACEHOLDERS */
@mixin cControl() {
$fs: 1em;
@include userSelectNone();
display: inline-flex;
align-items: center;
justify-content: center;
overflow: hidden;
&:before,
&:after {
font-family: symbolsfont;
display: block;
flex: 0 0 auto;
}
&:after {
font-size: 0.8em;
}
[class*="__label"] {
@include ellipsize();
display: block;
line-height: $fs; // Remove effect on top and bottom padding
font-size: $fs;
}
}
@mixin cButton() {
@include cControl();
background: $colorBtnBg;
border-radius: $controlCr;
color: $colorBtnFg;
cursor: pointer;
padding: nth($btnPad, 1) nth($btnPad, 2);
&:hover {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
&[class*="--major"] {
background: $colorBtnMajorBg;
color: $colorBtnMajorFg;
&:hover {
background: $colorBtnMajorBgHov;
color: $colorBtnMajorFgHov;
}
}
&[class*='--caution'] {
background: $colorBtnCautionBg;
color: $colorBtnCautionFg;
&:hover {
background: $colorBtnCautionBgHov;
}
}
}
@mixin cClickIcon() {
// 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
@include cControl();
$pLR: 3px;
$pTB: 3px;
border-radius: $controlCr;
color: $colorKey;
cursor: pointer;
padding: $pTB $pLR ;
&:hover {
background: rgba($colorKey, 0.2);
}
&:before,
*:before {
// *:before handles any nested containers that may contain glyph elements
// Needed for c-togglebutton.
font-size: 1.3em;
}
}
@mixin cCtrlWrapper {
// Provides a wrapper around buttons and other controls
// Contains control and provides positioning context for contained menu/palette.
// Wraps --menu elements, contains button and menu
overflow: visible;
.c-menu {
// Default position of contained menu
top: 100%; left: 0;
}
&[class*='--menus-up'] {
.c-menu {
top: auto; bottom: 100%;
}
}
&[class*='--menus-left'] {
.c-menu {
left: auto; right: 0;
}
}
}
/********* Buttons */
// Optionally can include icon in :before via markup
.c-button,
.c-button--menu,
button {
@include cButton();
}
.c-button--menu {
$m: $interiorMargin;
&:before,
> * {
margin-right: $m;
}
&:after {
content: $glyph-icon-arrow-down;
font-family: symbolsfont;
opacity: 0.5;
}
}
/********* Icon Buttons */
.c-click-icon {
@include cClickIcon();
&--menu {
&:after {
content: $glyph-icon-arrow-down;
font-family: symbolsfont;
font-size: 0.6em;
margin-left: floor($interiorMarginSm * 0.8);
opacity: 0.5;
}
}
&--swatched {
// Color control, show swatch element
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
> [class*='swatch'] {
box-shadow: inset rgba(black, 0.2) 0 0 1px;
flex: 0 0 auto;
height: 4px;
width: 100%;
margin-top: 1px;
}
&:before {
// Reduce size of icon to make a bit of room
flex: 1 1 auto;
font-size: 1.1em;
}
}
}
/******************************************************** DISCLOSURE CONTROLS */
/********* Disclosure Button */
// Provides a downward arrow icon that when clicked displays a context menu
// Always placed AFTER an element
.c-disclosure-button {
@include cClickIcon();
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 */
input, textarea {
font-family: inherit;
font-weight: inherit;
letter-spacing: inherit;
}
input[type=text],
input[type=search],
input[type=number] {
@include reactive-input();
padding: $inputTextP;
&.numeric {
text-align: right;
}
}
.c-input {
&--datetime {
// Sized for values such as 2018-09-28 22:32:33.468Z
width: 160px;
}
&--hrs-min-sec {
// Sized for values such as 00:25:00
width: 60px;
}
&-inline,
&--inline {
// A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus
@include reactive-input();
box-shadow: none;
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;
}
}
&--labeled {
// TODO: replace .c-labeled-input with this
// An input used in the Toolbar
// Assumes label is before the input
@include cControl();
input {
margin-left: $interiorMarginSm;
}
}
}
.c-labeled-input {
// An input used in the Toolbar
// Assumes label is before the input
@include cControl();
input {
margin-left: $interiorMarginSm;
}
}
/******************************************************** HYPERLINKS AND HYPERLINK BUTTONS */
.c-hyperlink {
&--link {
color: $colorKey;
}
&--button {
@include cButton();
}
}
/******************************************************** 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 {
@include cControl();
justify-content: start;
color: $colorMenuFg;
cursor: pointer;
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();
}
.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;
}
}
}
}
/******************************************************** PALETTES */
.c-palette {
display: flex;
flex-flow: column nowrap;
&__items {
flex: 1 1 auto;
display: grid;
grid-template-columns: repeat(10, [col] auto );
grid-gap: 1px;
}
&__item {
$d: 16px;
border: 1px solid transparent;
cursor: pointer;
width: 16px; height: 16px;
transition: $transOut;
&:hover {
transition: $transIn;
$o: 0.7;
border-color: rgba($paletteItemBorderOuterColorSelected, $o);
box-shadow: inset rgba($paletteItemBorderInnerColorSelected, $o) 0 0 0 1px;
}
&.is-selected {
border-color: $paletteItemBorderOuterColorSelected !important;
border-width: 2px;
box-shadow: inset rgba($paletteItemBorderInnerColorSelected, 1) 0 0 0 1px;
}
}
&__item-none {
flex: 0 0 auto;
display: flex;
align-items: center;
margin-bottom: $interiorMarginSm;
.c-palette__item {
@include noColor();
border-color: $paletteItemBorderInnerColor;
margin-right: $interiorMarginSm;
}
}
}
/******************************************************** TOOLBAR */
.c-ctrl-wrapper {
@include cCtrlWrapper();
}
.c-toolbar,
.c-toolbar .c-ctrl-wrapper {
display: flex;
align-items: stretch;
}
.c-toolbar {
height: 24px; // Need to standardize the height
.c-click-icon {
@include cControl();
$pLR: $interiorMargin - 1;
$pTB: 2px;
color: $colorBodyFg;
padding: $pTB $pLR;
&--swatched {
padding-bottom: floor($pTB / 2);
width: 2em; // Standardize the width
}
&[class*='--caution'] {
&:before {
color: $colorBtnCautionBg;
}
&:hover {
background: rgba($colorBtnCautionBgHov, 0.2);
:before {
color: $colorBtnCautionBgHov;
}
}
}
}
.c-labeled-input {
font-size: 0.9em;
input[type='number'] {
width: 40px; // Number input sucks and must have size set using this method
}
+ .c-labeled-input {
margin-left: $interiorMargin;
}
}
}
/********* Button Sets */
.c-button-set {
// When one set is adjacent to another, provides a divider between
display: inline-flex;
flex: 0 0 auto;
> * {
// Assume buttons are immediate descendants
flex: 0 0 auto;
+ * {
// margin-left: $interiorMarginSm;
}
}
+ .c-button-set {
$m: $interiorMargin;
$b: 1px;
&:before {
content: '';
display: block;
width: $m + $b; // Allow for border
border-right: $b solid $colorInteriorBorder;
margin-right: $m;
}
}
&[class*='--strip'] {
// Buttons are smashed together with minimal margin
// c-buttons don't have border-radius between buttons, creates a tool-button-strip look
// c-click-icons get grouped more closely together
&[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;
}
}
}
}
/***************************************************** SLIDERS */
.c-slider {
@include cControl();
> * + * { margin-left: $interiorMargin; }
}

View File

@@ -28,9 +28,7 @@
}
/******************************* RESETS */
*,
:before,
:after {
* {
box-sizing: border-box;
}
@@ -43,15 +41,6 @@ div {
display: contents;
}
.u-space {
// Provides a separator space between elements
&--right {
+ [class*="__"] {
margin-left: $interiorMarginLg !important;
}
}
}
/******************************* BROWSER ELEMENTS */
body.desktop {
::-webkit-scrollbar {
@@ -106,9 +95,8 @@ body, html {
body {
-webkit-font-smoothing: subpixel-antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: $fontBaseSize;
font-size: 13px;
font-weight: normal;
background-color: $colorBodyBg;
color: $colorBodyFg;
@@ -118,6 +106,22 @@ 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;
@@ -147,54 +151,6 @@ table {
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 {

View File

@@ -1,3 +1,24 @@
/*****************************************************************************
* 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.
*****************************************************************************/
@mixin glyphBefore($unicode, $family: 'symbolsfont') {
&:before {
content: $unicode;
@@ -12,21 +33,16 @@
}
}
@mixin glyphBg($glyphUrl) {
background-image: $glyphUrl;
background-position: center;
background-size: contain;
background-repeat: no-repeat;
}
[class*="icon-"] {
&:before, &:after
{
-webkit-font-smoothing: antialiased;
[class*="icon-"].labeled {
// Moved from .s-button and generalized
&:before {
// Fend off label from icon when it's included
margin-right: $interiorMarginSm;
}
}
/************************** 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); }
@@ -146,36 +162,3 @@
.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); }

View File

@@ -20,8 +20,6 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
// VERSION MANUALLY MIGRATED FROM VUE-TOOLBAR
/************************** VISUALS */
@mixin ancillaryIcon($d, $c) {
// Used for small icons used in combination with larger icons,
@@ -55,37 +53,6 @@
background-size: $bgsize $bgsize;
}
@mixin noColor() {
// A "no fill/stroke" selection option. Used in palettes.
$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;
}
@mixin bgTicks($c: $colorBodyFg, $repeatDir: 'x') {
$deg: 90deg;
@if ($repeatDir != 'x') {
$deg: 0deg;
$repeatDir: repeat-y;
} @else {
$repeatDir: repeat-x;
}
background-image: linear-gradient($deg,
$c 1px, transparent 1px,
transparent 100%
);
background-repeat: $repeatDir;
}
@mixin bgVertStripes($c: yellow, $a: 0.1, $d: 40px) {
@include background-image(linear-gradient(-90deg,
rgba($c, $a) 0%, rgba($c, $a) 50%,
@@ -95,41 +62,6 @@
background-size: $d $d;
}
/************************** LAYOUT */
@mixin abs($m: 0) {
position: absolute;
top: $m; right: $m; bottom: $m; left: $m;
}
@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;
@@ -148,10 +80,13 @@
}
/************************** CONTROLS, BUTTONS */
@mixin input-base() {
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0px 2px) {
appearance: none;
background: $bg;
border: none;
color: $fg;
border-radius: $controlCr;
box-shadow: inset $shdw;
outline: none;
&:focus {
@@ -164,28 +99,6 @@
}
}
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0 2px) {
@include input-base();
background: $bg;
color: $fg;
box-shadow: inset $shdw;
}
@mixin reactive-input($bg: $colorInputBg, $fg: $colorInputFg) {
@include input-base();
background: $bg;
box-shadow: $shdwInput;
color: $fg;
&:hover {
box-shadow: $shdwInputHov;
}
&:focus {
box-shadow: $shdwInputFoc;
}
}
@mixin button($bg: $colorBtnBg, $fg: $colorBtnFg, $radius: $controlCr, $shdw: none) {
background: $bg;
color: $fg;
@@ -193,63 +106,6 @@
box-shadow: $shdw;
}
@mixin wrappedInput() {
// An input that is wrapped. Optionally includes a __label or icon element.
// Based on .c-search.
@include nice-input();
display: flex;
align-items: center;
padding-left: 4px;
padding-right: 4px;
&:before,
[class*='__label'] {
opacity: 0.5;
}
&:before {
// Adds an icon. Content defined in class.
direction: rtl; // Aligns glyph to right-hand side of container, for transition
display: block;
font-family: symbolsfont;
flex: 0 0 auto;
overflow: hidden;
padding: 2px 0; // Prevents clipping
transition: width 250ms ease;
width: 1em;
}
&:hover {
box-shadow: inset rgba(black, 0.8) 0 0px 2px;
&:before {
opacity: 0.9;
}
}
&--major {
padding: 4px;
}
&__input,
input[type='text'],
input[type='search'],
input[type='number'] {
background: none !important;
box-shadow: none !important; // !important needed to override default for [input]
flex: 1 1 auto;
padding-left: 2px !important;
padding-right: 2px !important;
min-width: 10px; // Must be set to allow input to collapse below browser min
}
&.is-active {
&:before {
padding: 2px 0px;
width: 0px;
}
}
}
/************************** MATH */
@function percentToDecimal($p) {
@return $p / 100%;
@@ -269,13 +125,4 @@
@mixin userSelectNone() {
@include browserPrefix(user-select, none);
}
@mixin cursorGrab() {
cursor: grab;
cursor: -webkit-grab;
&:active {
cursor: grabbing;
cursor: -webkit-grabbing;
}
}
}

Some files were not shown because too many files have changed in this diff Show More