diff --git a/API.md b/API.md index e681378b56..a76fc53c5d 100644 --- a/API.md +++ b/API.md @@ -52,7 +52,6 @@ - [The URL Status Indicator](#the-url-status-indicator) - [Creating a Simple Indicator](#creating-a-simple-indicator) - [Custom Indicators](#custom-indicators) - - [Included Plugins](#included-plugins) @@ -109,15 +108,13 @@ script loaders are also supported. Open MCT - + @@ -128,9 +125,6 @@ The Open MCT library included above requires certain assets such as html templates, images, and css. If you installed Open MCT from GitHub as described in the section on [Building from Source](#building-from-source) then these assets will have been downloaded along with the Open MCT javascript library. -You can specify the location of these assets by calling `openmct.setAssetPath()`. -Typically this will be the same location as the `openmct.js` library is -included from. There are some plugins bundled with the application that provide UI, persistence, and other default configuration which are necessary to be able to @@ -429,7 +423,7 @@ attribute | type | flags | notes ###### Value Hints -Each telemetry value description has an object defining hints. Keys in this this object represent the hint itself, and the value represents the weight of that hint. A lower weight means the hint has a higher priority. For example, multiple values could be hinted for use as the y axis of a plot (raw, engineering), but the highest priority would be the default choice. Likewise, a table will use hints to determine the default order of columns. +Each telemetry value description has an object defining hints. Keys in this this object represent the hint itself, and the value represents the weight of that hint. A lower weight means the hint has a higher priority. For example, multiple values could be hinted for use as the y-axis of a plot (raw, engineering), but the highest priority would be the default choice. Likewise, a table will use hints to determine the default order of columns. Known hints: @@ -511,7 +505,7 @@ example: } ``` -This strategy says "I want the lastest data point in this time range". A provider which recognizes this request should return only one value-- the latest-- in the requested time range. Depending on your back-end implementation, performing these queries in bulk can be a large performance increase. These are generally issued by views that are only capable of displaying a single value and only need to show the latest value. +This strategy says "I want the latest data point in this time range". A provider which recognizes this request should return only one value-- the latest-- in the requested time range. Depending on your back-end implementation, performing these queries in bulk can be a large performance increase. These are generally issued by views that are only capable of displaying a single value and only need to show the latest value. ##### `minmax` request strategy @@ -606,7 +600,7 @@ evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`. ### Telemetry Consumer APIs **draft** -The APIs for requesting telemetry from Open MCT -- e.g. for use in custom views -- are currently in draft state and are being revised. If you'd like to experiement with them before they are finalized, please contact the team via the contact-us link on our website. +The APIs for requesting telemetry from Open MCT -- e.g. for use in custom views -- are currently in draft state and are being revised. If you'd like to experiment with them before they are finalized, please contact the team via the contact-us link on our website. ## Time API @@ -994,7 +988,7 @@ A common use case for indicators is to convey the state of some external system persistence backend or HTTP server. So long as this system is accessible via HTTP request, Open MCT provides a general purpose indicator to show whether the server is available and returing a 2xx status code. The URL Status Indicator is made available as a default plugin. See -[Included Plugins](#included-plugins) below for details on how to install and configure the +the [documentation](./src/plugins/URLIndicatorPlugin) for details on how to install and configure the URL Status Indicator. ### Creating a Simple Indicator @@ -1033,7 +1027,7 @@ different colors to indicate status. ### Custom Indicators -A completely custom indicator can be added by simple providing a DOM element to place alongside other indicators. +A completely custom indicator can be added by simply providing a DOM element to place alongside other indicators. ``` javascript var domNode = document.createElement('div'); @@ -1046,59 +1040,3 @@ A completely custom indicator can be added by simple providing a DOM element to element: domNode }); ``` - -## Included Plugins - -Open MCT is packaged along with a few general-purpose plugins: - -* `openmct.plugins.Conductor` provides a user interface for working with time -within the application. If activated, configuration must be provided. This is -detailed in the section on [Time Conductor Configuration](#time-conductor-configuration). -* `openmct.plugins.CouchDB` is an adapter for using CouchDB for persistence - of user-created objects. This is a constructor that takes the URL for the - CouchDB database as a parameter, e.g. -```javascript -openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct')) -``` -* `openmct.plugins.Elasticsearch` is an adapter for using Elasticsearch for - persistence of user-created objects. This is a - constructor that takes the URL for the Elasticsearch instance as a - parameter. eg. -```javascript -openmct.install(openmct.plugins.CouchDB('http://localhost:9200')) -``` -* `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different - themes (dark and light) available for Open MCT. Note that at least one - of these themes must be installed for Open MCT to appear correctly. -* `openmct.plugins.URLIndicator` adds an indicator which shows the -availability of a URL with the following options: - - `url` : URL to indicate the status of - - `iconClass`: Icon to show in the status bar, defaults to `icon-database`, [list of all icons](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home?view=items) - - `interval`: Interval between checking the connection, defaults to `10000` - - `label` Name showing up as text in the status bar, defaults to url -```javascript -openmct.install(openmct.plugins.URLIndicator({ - url: 'http://localhost:8080', - iconClass: 'check', - interval: 10000, - label: 'Localhost' - }) -); -``` -* `openmct.plugins.LocalStorage` provides persistence of user-created - objects in browser-local storage. This is particularly useful in - development environments. -* `openmct.plugins.MyItems` adds a top-level folder named "My Items" - when the application is first started, providing a place for a - user to store created items. -* `openmct.plugins.UTCTimeSystem` provides a default time system for Open MCT. - -Generally, you will want to either install these plugins, or install -different plugins that provide persistence and an initial folder -hierarchy. - -eg. -```javascript -openmct.install(openmct.plugins.LocalStorage()); -openmct.install(openmct.plugins.MyItems()); -``` diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000..053c731a17 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +# Open MCT License + +Open MCT, Copyright (c) 2014-2019, 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. diff --git a/LICENSES.md b/LICENSES.md deleted file mode 100644 index b95375aefd..0000000000 --- a/LICENSES.md +++ /dev/null @@ -1,691 +0,0 @@ -# Open MCT Licenses - -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 as follows. - -## Software Components Licenses - -This software includes components released under the following licenses: - ---- - -### SuperSocket - -#### Info - -* Link: https://supersocket.codeplex.com/ - -* Version: 0.9.0.2 - -* Author: Kerry Jiang - -* Description: Supports SuperWebSocket - -#### License - -Copyright 2012 Kerry Jiang (kerry-jiang@hotmail.com) - -SuperSocket 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. - ---- - -### SuperWebSocket - -#### Info - -* Link: https://superswebocket.codeplex.com/ - -* Version: 0.9.0.2 - -* Author: Kerry Jiang - -* Description: WebSocket implementation for client-server communication - -#### License - -Copyright 2010-2013 Kerry Jiang (kerry-jiang@hotmail.com) - -SuperWebSocket 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. - - ---- - -### log4net - -#### Info - -* Link: http://logging.apache.org/log4net/ - -* Version: 1.2.13 - -* Author: Apache Software Foundation - -* Description: Logging. - -#### License - -Copyright © 2004-2015 Apache Software Foundation. - -log4net 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. - ---- - -### Blanket.js - -#### Info - -* Link: http://blanketjs.org/ - -* Version: 1.1.5 - -* Author: Alex Seville - -* Description: Code coverage measurement and reporting - -#### License - -Copyright (c) 2013 Alex Seville - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### Jasmine - -#### Info - -* Link: http://jasmine.github.io/ - -* Version: 1.3.1 - -* Author: Pivotal Labs - -* Description: Unit testing - -#### License - -Copyright (c) 2008-2011 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### RequireJS - -#### Info - -* Link: http://requirejs.org/ - -* Version: 2.1.22 - -* Author: The Dojo Foundation - -* Description: Script loader - -#### License - -Copyright (c) 2010-2015, The Dojo Foundation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### requirejs-text - -#### Info - -* Link: https://github.com/requirejs/text - -* Version: 2.0.14 - -* Author: The Dojo Foundation - -* Description: Text loading plugin for RequireJS - -#### License - -Copyright (c) 2010-2014, The Dojo Foundation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ---- - -### AngularJS - -#### Info - -* Link: http://angularjs.org/ - -* Version: 1.4.4 - -* Author: Google - -* Description: Client-side web application framework - -#### License - -Copyright (c) 2010-2015 Google, Inc. http://angularjs.org - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### Angular-Route - -#### Info - -* Link: http://angularjs.org/ - -* Version: 1.4.4 - -* Author: Google - -* Description: Client-side view routing - -#### License - -Copyright (c) 2010-2015 Google, Inc. http://angularjs.org - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### ES6-Promise - -#### Info - -* Link: https://github.com/jakearchibald/es6-promise - -* Version: 3.0.2 - -* Authors: Yehuda Katz, Tom Dale, Stefan Penner and contributors - -* Description: Promise polyfill for pre-ECMAScript 6 browsers - -#### License - -Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### screenfull.js - -#### Info - -* Link: https://github.com/sindresorhus/screenfull.js/ - -* Version: 3.0.0 - -* Author: Sindre Sorhus - -* Description: Wrapper for cross-browser usage of fullscreen API - -#### License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### Math.uuid.js - -#### Info - -* Link: https://github.com/broofa/node-uuid - -* Version: 1.4.7 - -* Author: Robert Kieffer - -* Description: Unique identifer generation. - -#### License - -Copyright (c) 2010-2012 Robert Kieffer - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### Normalize.css - -#### Info - -* Link: https://github.com/necolas/normalize.css - -* Version: 1.1.2 - -* Authors: Nicolas Gallagher, Jonathan Neal - -* Description: Browser style normalization - -#### License - -Copyright (c) Nicolas Gallagher and Jonathan Neal - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### Moment.js - -#### Info - -* Link: http://momentjs.com - -* Version: 2.11.1 - -* Authors: Tim Wood, Iskren Chernev, Moment.js contributors - -* Description: Time/date parsing/formatting - -#### License - -Copyright (c) 2011-2014 Tim Wood, Iskren Chernev, Moment.js contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### moment-duration-format - -#### Info - -* Link: https://github.com/jsmreese/moment-duration-format - -* Version: 1.3.0 - -* Authors: John Madhavan-Reese - -* Description: Duration parsing/formatting - -#### License - -Copyright 2014 John Madhavan-Reese - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### CSV.js - -#### Info - -* Link: https://github.com/knrz/CSV.js - -* Version: 3.6.4 - -* Authors: Kash Nouroozi - -* Description: Encoder for CSV (comma separated values) export - -#### License - -Copyright (c) 2014 Kash Nouroozi - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ---- - -### FileSaver.js - -#### Info - -* Link: https://github.com/eligrey/FileSaver.js/ - -* Version: 0.0.2 - -* Authors: Eli Grey - -* Description: File download initiator (for file exports) - -#### License - -Copyright © 2015 Eli Grey. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### Zepto - -#### Info - -* Link: http://zeptojs.com/ - -* Version: 1.1.6 - -* Authors: Thomas Fuchs - -* Description: DOM manipulation - -#### License - -Copyright (c) 2010-2016 Thomas Fuchs -http://zeptojs.com/ - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### Json.NET - -#### Info - -* Link: http://www.newtonsoft.com/json - -* Version: 6.0.8 - -* Author: Newtonsoft - -* Description: JSON serialization/deserialization - -#### License - -Copyright (c) 2007 James Newton-King - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### Nancy - -#### Info - -* Link: http://nancyfx.org - -* Version: 0.23.2 - -* Author: Andreas Håkansson, Steven Robbins and contributors - -* Description: Embedded web server - -#### License - -Copyright © 2010 Andreas Håkansson, Steven Robbins and contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### Nancy.Hosting.Self - -#### Info - -* Link: http://nancyfx.org - -* Version: 0.23.2 - -* Author: Andreas Håkansson, Steven Robbins and contributors - -* Description: Embedded web server - -#### License - -Copyright © 2010 Andreas Håkansson, Steven Robbins and contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -### Almond - -* Link: https://github.com/requirejs/almond - -* Version: 0.3.3 - -* Author: jQuery Foundation - -* Description: Lightweight RequireJS replacement for builds - -#### License - -Copyright jQuery Foundation and other contributors, https://jquery.org/ - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/requirejs/almond - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules directory, and certain utilities used -to build or test the software in the test and dist directories, are -externally maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - - -### Lodash - -* Link: https://lodash.com - -* Version: 3.10.1 - -* Author: Dojo Foundation - -* Description: Utility functions - -#### License - -Copyright 2012-2015 The Dojo Foundation -Based on Underscore.js, copyright 2009-2015 Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -### EventEmitter3 - -* Link: https://github.com/primus/eventemitter3 - -* Version: 1.2.0 - -* Author: Arnout Kazemier - -* Description: Event-driven programming support - -#### License - -The MIT License (MIT) - -Copyright (c) 2014 Arnout Kazemier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 30666a7a30..d5bdf1fe02 100644 --- a/README.md +++ b/README.md @@ -9,26 +9,6 @@ Please visit our [Official Site](https://nasa.github.io/openmct/) and [Getting S Try Open MCT now with our [live demo](https://openmct-demo.herokuapp.com/). ![Demo](https://nasa.github.io/openmct/static/res/images/Open-MCT.Browse.Layout.Mars-Weather-1.jpg) -## New API - -A simpler, [easier-to-use API](https://nasa.github.io/openmct/docs/api/) -has been added to Open MCT. Changes in this -API include a move away from a declarative system of JSON configuration files -towards an imperative system based on function calls. Developers will be able -to extend and build on Open MCT by making direct function calls to a public -API. Open MCT is also being refactored to minimize the dependencies that using -Open MCT imposes on developers, such as the current requirement to use -AngularJS. - -This new API has not yet been heavily used and is likely to contain defects. -You can help by trying it out, and reporting any issues you encounter -using our GitHub issue tracker. Such issues may include bugs, suggestions, -missing documentation, or even just requests for help if you're having -trouble. - -We want Open MCT to be as easy to use, install, run, and develop for as -possible, and your feedback will help us get there! - ## Building and Running Open MCT Locally Building and running Open MCT in your local dev environment is very easy. Be sure you have [Git](https://git-scm.com/downloads) and [Node.js](https://nodejs.org/) installed, then follow the directions below. Need additional information? Check out the [Getting Started](https://nasa.github.io/openmct/getting-started/) page on our website. @@ -48,9 +28,14 @@ Building and running Open MCT in your local dev environment is very easy. Be sur Open MCT is now running, and can be accessed by pointing a web browser at [http://localhost:8080/](http://localhost:8080/) +## Open MCT v1.0.0 +This represents a major overhaul of Open MCT with significant changes under the hood. We aim to maintain backward compatibility but if you do find compatibility issues, please let us know by filing an issue in this repository. If you are having major issues with v1.0.0 please check-out the v0.14.0 tag until we can resolve them for you. + +If you are migrating an application built with Open MCT as a dependency to v1.0.0 from an earlier version, please refer to [our migration guide](https://nasa.github.io/openmct/documentation/migration-guide). + ## Documentation -Documentation is available on the [Open MCT website](https://nasa.github.io/openmct/documentation/). The documentation can also be built locally. +Documentation is available on the [Open MCT website](https://nasa.github.io/openmct/documentation/). ### Examples @@ -58,48 +43,29 @@ The clearest examples for developing Open MCT plugins are in the [tutorials](https://github.com/nasa/openmct-tutorial) provided in our documentation. -For a practical example of a telemetry adapter, see David Hudson's -[Kerbal Space Program plugin](https://github.com/hudsonfoo/kerbal-openmct), -which allows [Kerbal Space Program](https://kerbalspaceprogram.com) players -to build and use displays for their own missions in Open MCT. +We want Open MCT to be as easy to use, install, run, and develop for as +possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues), or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov). -Additional examples are available in the `examples` hierarchy of this -repository; however, be aware that these examples are -[not fully-documented](https://github.com/nasa/openmct/issues/846), so -the tutorials will likely serve as a better starting point. +## Building Applications With Open MCT -### Building the Open MCT Documentation Locally -Open MCT's documentation is generated by an -[npm](https://www.npmjs.com/)-based build. It has additional dependencies that -may not be available on every platform and thus is not covered in the standard -npm install. Ensure your system has [libcairo](http://cairographics.org/) -installed and then run the following commands: +Open MCT is built using [`npm`](http://npmjs.com/) and [`webpack`](https://webpack.js.org/). -* `npm install` -* `npm install canvas nomnoml` -* `npm run docs` +See our documentation for a guide on [building Applications with Open MCT](https://github.com/nasa/openmct/blob/master/API.md#starting-an-open-mct-application). -Documentation will be generated in `target/docs`. +## Plugins -## Deploying Open MCT +Open MCT can be extended via plugins that make calls to the Open MCT API. A plugin is a group +of software components (including source code and resources such as images and HTML templates) +that is intended to be added or removed as a single unit. -Open MCT is built using [`npm`](http://npmjs.com/) +As well as providing an extension mechanism, most of the core Open MCT codebase is also +written as plugins. -To build Open MCT for deployment: - -`npm run prepare` - -This will compile and minify JavaScript sources, as well as copy over assets. -The contents of the `dist` folder will contain a runnable Open MCT -instance (e.g. by starting an HTTP server in that directory), including: - -* `openmct.js` - Open MCT source code. -* `openmct.css` - Basic styles to load to prevent a FOUC. -* `index.html`, an example to run Open MCT in the basic configuration. +For information on writing plugins, please see [our API documentation](https://github.com/nasa/openmct/blob/master/API.md#plugins). ## Tests -Tests are written for [Jasmine 3](http://jasmine.github.io/) +Tests are written for [Jasmine 3](https://jasmine.github.io/api/3.1/global) and run by [Karma](http://karma-runner.github.io). To run: `npm test` @@ -115,7 +81,7 @@ naming convention is otherwise the same.) ### Test Reporting When `npm test` is run, test results will be written as HTML to -`target/tests`. Code coverage information is written to `target/coverage`. +`dist/reports/tests/`. Code coverage information is written to `dist/reports/coverage`. # Glossary @@ -125,11 +91,8 @@ addressed (either by updating this glossary or changing code to reflect correct usage.) Other developer documentation, particularly in-line documentation, may presume an understanding of these terms. -* _bundle_: A bundle is a removable, reusable grouping of software elements. - The application is composed of bundles. Plug-ins are bundles. For more - information, refer to framework documentation (under `platform/framework`.) -* _capability_: An object which exposes dynamic behavior or non-persistent - state associated with a domain object. +* _plugin_: A plugin is a removable, reusable grouping of software elements. + The application is composed of plugins. * _composition_: In the context of a domain object, this refers to the set of other domain objects that compose or are contained by that object. A domain object's composition is the set of domain objects that should appear @@ -144,13 +107,8 @@ documentation, may presume an understanding of these terms. * _domain object_: A meaningful object to the user; a distinct thing in the work support by Open MCT. Anything that appears in the left-hand tree is a domain object. -* _extension_: An extension is a unit of functionality exposed to the - platform in a declarative fashion by a bundle. For more - information, refer to framework documentation (under `platform/framework`.) -* _id_: A string which uniquely identifies a domain object. -* _key_: When used as an object property, this refers to the machine-readable - identifier for a specific thing in a set of things. (Most often used in the - context of extensions or other similar application-specific object sets.) +* _identifier_: A tuple consisting of a namespace and a key, which together uniquely + identifies a domain object. * _model_: The persistent state associated with a domain object. A domain object's model is a JavaScript object which can be converted to JSON without losing information (that is, it contains no methods.) @@ -162,7 +120,5 @@ documentation, may presume an understanding of these terms. a user clicks on a domain object in the tree, they are _navigating_ to it, and it is thereafter considered the _navigated_ object (until the user makes another such choice.) -* _space_: A name used to identify a persistence store. Interactions with - persistence will generally involve a `space` parameter in some form, to - distinguish multiple persistence stores from one another (for cases - where there are multiple valid persistence locations available.) +* _namespace_: A name used to identify a persistence store. A running open MCT +application could potentially use multiple persistence stores, with the diff --git a/app.js b/app.js index 476f2bd2c5..5d98b5b964 100644 --- a/app.js +++ b/app.js @@ -15,8 +15,8 @@ const fs = require('fs'); const request = require('request'); // Defaults -options.port = options.port || options.p || 8070; -options.host = options.host || options.h || 'localhost'; +options.port = options.port || options.p || 8080; +options.host = options.host || 'localhost'; options.directory = options.directory || options.D || '.'; // Show command line options diff --git a/docs/src/index.md b/docs/src/index.md index 1d523657f4..316fb6e944 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -33,5 +33,5 @@ As we transition to a new API, the following documentation for the old API * The [Developer's Guide](guide/) goes into more detail about how to use the platform and the functionality that it provides. - * The [Tutorials](tutorials/) give examples of extending the platform to add + * The [Tutorials](https://github.com/nasa/openmct-tutorial) give examples of extending the platform to add functionality, and integrate with data sources. diff --git a/docs/src/tutorials/images/add-task.png b/docs/src/tutorials/images/add-task.png deleted file mode 100644 index 7780365c5a..0000000000 Binary files a/docs/src/tutorials/images/add-task.png and /dev/null differ diff --git a/docs/src/tutorials/images/bar-plot-2.png b/docs/src/tutorials/images/bar-plot-2.png deleted file mode 100644 index a32c2b76f1..0000000000 Binary files a/docs/src/tutorials/images/bar-plot-2.png and /dev/null differ diff --git a/docs/src/tutorials/images/bar-plot-3.png b/docs/src/tutorials/images/bar-plot-3.png deleted file mode 100644 index 0899984a33..0000000000 Binary files a/docs/src/tutorials/images/bar-plot-3.png and /dev/null differ diff --git a/docs/src/tutorials/images/bar-plot-4.png b/docs/src/tutorials/images/bar-plot-4.png deleted file mode 100644 index 50a9091fa7..0000000000 Binary files a/docs/src/tutorials/images/bar-plot-4.png and /dev/null differ diff --git a/docs/src/tutorials/images/bar-plot.png b/docs/src/tutorials/images/bar-plot.png deleted file mode 100644 index 2f113d4c6d..0000000000 Binary files a/docs/src/tutorials/images/bar-plot.png and /dev/null differ diff --git a/docs/src/tutorials/images/chrome.png b/docs/src/tutorials/images/chrome.png deleted file mode 100644 index 1b9b7b80d2..0000000000 Binary files a/docs/src/tutorials/images/chrome.png and /dev/null differ diff --git a/docs/src/tutorials/images/remove-task.png b/docs/src/tutorials/images/remove-task.png deleted file mode 100644 index 015ec95ac4..0000000000 Binary files a/docs/src/tutorials/images/remove-task.png and /dev/null differ diff --git a/docs/src/tutorials/images/telemetry-1.png b/docs/src/tutorials/images/telemetry-1.png deleted file mode 100644 index 2a606e83c7..0000000000 Binary files a/docs/src/tutorials/images/telemetry-1.png and /dev/null differ diff --git a/docs/src/tutorials/images/telemetry-2.png b/docs/src/tutorials/images/telemetry-2.png deleted file mode 100644 index 0b34dd90f5..0000000000 Binary files a/docs/src/tutorials/images/telemetry-2.png and /dev/null differ diff --git a/docs/src/tutorials/images/telemetry-3.png b/docs/src/tutorials/images/telemetry-3.png deleted file mode 100644 index c235b1d543..0000000000 Binary files a/docs/src/tutorials/images/telemetry-3.png and /dev/null differ diff --git a/docs/src/tutorials/images/todo-edit.png b/docs/src/tutorials/images/todo-edit.png deleted file mode 100644 index 3c1ba3f5cc..0000000000 Binary files a/docs/src/tutorials/images/todo-edit.png and /dev/null differ diff --git a/docs/src/tutorials/images/todo-list.png b/docs/src/tutorials/images/todo-list.png deleted file mode 100644 index 48c84c63e4..0000000000 Binary files a/docs/src/tutorials/images/todo-list.png and /dev/null differ diff --git a/docs/src/tutorials/images/todo-restyled.png b/docs/src/tutorials/images/todo-restyled.png deleted file mode 100644 index 9fd7008c2f..0000000000 Binary files a/docs/src/tutorials/images/todo-restyled.png and /dev/null differ diff --git a/docs/src/tutorials/images/todo-selection.png b/docs/src/tutorials/images/todo-selection.png deleted file mode 100644 index a0ff87514d..0000000000 Binary files a/docs/src/tutorials/images/todo-selection.png and /dev/null differ diff --git a/docs/src/tutorials/images/todo.png b/docs/src/tutorials/images/todo.png deleted file mode 100644 index 44a7b7b2ec..0000000000 Binary files a/docs/src/tutorials/images/todo.png and /dev/null differ diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md deleted file mode 100644 index c60e05a0f3..0000000000 --- a/docs/src/tutorials/index.md +++ /dev/null @@ -1,3309 +0,0 @@ -# Open MCT Tutorials - -Victor Woeltjen -victor.woeltjen@nasa.gov - -October 14, 2015 -Document Version 2.2 - -Date | Version | Summary of Changes | Author ----------------- | ------- | --------------------------------- | --------------- -May 12, 2015 | 0 | Initial Draft | Victor Woeltjen -June 4, 2015 | 1.0 | Name changes | Victor Woeltjen -July 28, 2015 | 2.0 | Telemetry adapter tutorial | Victor Woeltjen -July 31, 2015 | 2.1 | Clarify telemetry adapter details | Victor Woeltjen -October 14, 2015 | 2.2 | Conversion to markdown | Andrew Henry - -# Introduction - -## This document -This document contains a number of code examples in formatted code blocks. In -many cases these code blocks are repeated in order to highlight code that has -been added or removed as part of the tutorial. In these cases, any lines added -will be indicated with a '+' at the start of the line. Any lines removed will -be indicated with a '-'. - -## Setting Up Open MCT - -In this section, we will cover the steps necessary to get a minimal Open MCT -developer environment up and running. Once we have this, we will be able to -proceed with writing plugins as described in this tutorial. - -### Prerequisites - -This tutorial assumes you have the following software installed. Version numbers -record what was used in writing this tutorial; the same steps should work with -more recent versions, but this cannot be guaranteed. - -* Node.js v0.12.2: https://nodejs.org/ -* git v1.8.3.4: http://git-scm.com/ -* Google Chrome v42: https://www.google.com/chrome/ -* A text editor. - -Open MCT can be run without any of these tools, provided suitable -alternatives are taken; see the [Open MCT Developer Guide](../guide/index.md) -for a more general overview of how to run and deploy a Open MCT application. - -### Check out Open MCT Sources - -First step is to check out Open MCT from the source repository. - -`git clone https://github.com/nasa/openmct.git openmct` - -This will create a copy of the Open MCT source code repository in the folder -`openmct` (relative to the path from which you ran the command.) -If you have a repository URL, use that as the "path to repo" above. Alternately, -if you received Open MCT as a git bundle, the path to that bundle on the -local filesystem can be used instead. -At this point, it will also be useful to branch off of Open MCT v0.6.2 -(which was used when writing these tutorials) to begin adding plugins. - - cd openmct - git branch open-v0.6.2 - git checkout - - -### Building Open MCT -Once downloaded, Open MCT can be built with the following command: - - npm install - -This will install various dependencies, build CSS from Sass files, run tests, -and lint the source code. - -It's not necessary to do this after every code change, unless you are making -changes to stylesheets, or you are running the minified version of the app -(under `dist`). - -### Run a Web Server - -The next step is to run a web server so that you can view the Open MCT -client (including the plugins you add to it) in browser. Any web server can -be used for hosting Open MCT, and a trivial web server is provided in this -package for the purposes of running the tutorials. The provided web server -should not be used in a production environment - -To run the tutorial web server - - npm start - -### Viewing in Browser - -Once running, you should be able to view Open MCT from your browser at -http://localhost:8080/ (assuming the web server is running on port 8080, -and Open MCT is installed at the server's root path). -[Google Chrome](https://www.google.com/chrome/) is recommended for these -tutorials, as Chrome is Open MCT's "test-to" browser. The browser cache -can sometimes interfere with development (masking changes by -using older versions of sources); to avoid this, it is easiest to run Chrome -with Developer Tools expanded, and "Disable cache" selected from the Network -tab, as shown below. - -![Chrome Developer Tools](images/chrome.png) - -# Tutorials - -These tutorials cover three of the common tasks in Open MCT: - -* The "to-do list" tutorial illustrates how to add a new application feature. -* The "bar graph" tutorial illustrates how to add a new telemetry visualization. -* The "data set reader" tutorial illustrates how to integrate with a telemetry -backend. - -## To-do List - -The goal of this tutorial is to add a new application feature to Open MCT: -To-do lists. Users should be able to create and manage these to track items that -they need to do. This is modelled after the to-do lists at http://todomvc.com/. - -### Step 1-Create the Plugin - -The first step to adding a new feature to Open MCT is to create the plugin -which will expose that feature. A plugin in Open MCT is represented by what -is called a bundle; a bundle, in turn, is a directory which contains a file -bundle.js, which in turn describes where other relevant sources & resources -will be. The syntax of this file is described in more detail in the Open MCT -Developer Guide. - -We will create this file in the directory tutorials/todo (we can hereafter refer -to this plugin as tutorials/todo as well.) We will start with an "empty bundle", -one which exposes no extensions - which looks like: - -```js -define([ - 'openmct' -], function ( - openmct -) { - openmct.legacyRegistry.register("tutorials/todo", { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": - { - } - }); -}); -``` -__tutorials/todo/bundle.js__ - -With the new bundle defined, it is now necessary to register the bundle with -the application. The details of how a new bundle is defined are in the -process of changing. The Open MCT codebase has started to shift from a -declarative registration style toward an imperative registration style. -The tutorials will be updated with the new bundle registration mechanism once it -has been finalized. - -#### Before -```html - - - - - - - - - - - - - - - - - -
-
-
- - - -``` -__index.html__ - -#### After - -```html - - - - - - - - - - - - - - - - - -
-
-
- - - -``` -__index.html__ - -At this point, we can reload Open MCT. We haven't introduced any new -functionality, so we don't see anything different, but if we run with logging -enabled ( http://localhost:8080/?log=info ) and check the browser console, we -should see: - -`Resolving extensions for bundle tutorials/todo(To-do Plugin)` - -...which shows that our plugin has loaded. - -### Step 2-Add a Domain Object Type - -Features in a Open MCT application are most commonly expressed as domain -objects and/or views thereof. A domain object is some thing that is relevant to -the work that the Open MCT application is meant to support. Domain objects -can be created, organized, edited, placed in layouts, and so forth. (For a -deeper explanation of domain objects, see the Open MCT Developer Guide.) - -In the case of our to-do list feature, the to-do list itself is the thing we'll -want users to be able to create and edit. So, we will add that as a new type in -our bundle definition: - -```js -define([ - 'openmct' -], function ( - openmct -) { - openmct.legacyRegistry.register("tutorials/todo", { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": - { -+ "types": [ -+ { -+ "key": "example.todo", -+ "name": "To-Do List", -+ "cssClass": "icon-check", -+ "description": "A list of things that need to be done.", -+ "features": ["creation"] -+ } -+ ]} - }); -}); -``` -__tutorials/todo/bundle.js__ - -What have we done here? We've stated that this bundle includes extensions of the -category _types_, which is used to describe domain object types. Then, we've -included a definition for one such extension, which is the to-do list object. - -Going through the properties we've defined: - -* The `key` of `example.todo` will be stored as the machine-readable name for -domain objects of this type. -* The `name` of "To-Do List" is the human-readable name for this type, and will -be shown to users. -* The `cssClass` maps to an icon that will be shown for each To-Do List. The icons -are defined in our [custom open MCT icon set](https://github.com/nasa/openmct/blob/master/platform/commonUI/general/res/sass/_glyphs.scss). -A complete list of available icons will be provided in the future. -* The `description` is also human-readable, and will be used whenever a longer -explanation of what this type is should be shown. -* Finally, the `features` property describes some special features of objects of -this type. Including `creation` here means that we want users to be able to -create this (in other cases, we may wish to expose things as domain objects -which aren't user-created, in which case we would omit this.) - -If we reload Open MCT, we see that our new domain object type appears in the -Create menu: - -![To-Do List](images/todo.png) - -At this point, our to-do list doesn't do much of anything; we can create them -and give them names, but they don't have any specific functionality attached, -because we haven't defined any yet. - -### Step 3-Add a View - -In order to allow a to-do list to be used, we need to define and display its -contents. In Open MCT, the pattern that the user expects is that they'll -click on an object in the left-hand tree, and see a visualization of it to the -right; in Open MCT, these visualizations are called views. -A view in Open MCT is defined by an Angular template. We'll add that in the -directory `tutorials/todo/res/templates` (`res` is, by default, the directory -where bundle-related resources are kept, and `templates` is where HTML templates -are stored by convention.) - -```html - - -
    -
  • - - {{task.description}} -
  • -
-``` -__tutorials/todo/res/templates/todo.html__ - -A summary of what's included: - -* At the top, we have some buttons that we will later wire in to allow the user -to filter down to either complete or incomplete tasks. -* After that, we have a list of tasks. The scope variable `model` is the model -of the domain object being viewed; this contains all of the persistent state -associated with that object. This model is effectively just a JSON document, so -we can choose what goes into it (so long as we take care not to collide with -platform-defined properties; see the Open MCT Developer Guide.) Here, we -assume that all tasks will be stored in a property `tasks`, and that each will be -an object containing a `description` (the readable summary of the task) and a -boolean `completed` flag. - -To expose this view in Open MCT, we need to declare it in our bundle -definition: - -```js -define([ - 'openmct' -], function ( - openmct -) { - openmct.legacyRegistry.register("tutorials/todo", { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "cssClass": "icon-check", - "description": "A list of things that need to be done.", - "features": ["creation"] - } - ], -+ "views": [ -+ { -+ "key": "example.todo", -+ "type": "example.todo", -+ "cssClass": "icon-check", -+ "name": "List", -+ "templateUrl": "templates/todo.html", -+ "editable": true -+ } -+ ] - } - }); -}); -``` -__tutorials/todo/bundle.js__ - -Here, we've added another extension, this time belonging to category `views`. It -contains the following properties: - -* Its `key` is its machine-readable name; we've given it the same name here as -the domain object type, but could have chosen any unique name. - -* The `type` property tells Open MCT that this view is only applicable to -domain objects of that type. This means that we'll see this view for To-do Lists -that we create, but not for other domain objects (such as Folders.) - -* The `cssClass` and `name` properties describe the icon and human-readable name -for this view to display in the UI where needed (if multiple views are available -for To-do Lists, the user will be able to choose one.) - -* Finally, the `templateUrl` points to the Angular template we wrote; this path is -relative to the bundle's `res` folder. - -This template looks like it should display tasks, but we don't have any way for -the user to create these yet. As a temporary workaround to test the view, we -will specify an initial state for To-do List domain object models in the -definition of that type. - -```js -define([ - 'openmct' -], function ( - openmct -) { - openmct.legacyRegistry.register("tutorials/todo", { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "cssClass": "icon-check", - "description": "A list of things that need to be done.", - "features": ["creation"], -+ "model": { -+ "tasks": [ -+ { "description": "Add a type", "completed": true }, -+ { "description": "Add a view" } -+ ] - } - } - ], - "views": [ - { - "key": "example.todo", - "type": "example.todo", - "cssClass": "icon-check", - "name": "List", - "templateUrl": "templates/todo.html", - "editable": true - } - ] - } - }); -}); -``` -__tutorials/todo/bundle.js__ - -Now, when To-do List objects are created in Open MCT, they will initially -have the state described by that model property. - -If we reload Open MCT, create a To-do List, and navigate to it in the tree, -we should now see: - -![To-Do List](images/todo-list.png) - -This looks roughly like what we want. We'll handle styling later, so let's work -on adding functionality. Currently, the filter choices do nothing, and while the -checkboxes can be checked/unchecked, we're not actually making the changes in -the domain object - if we click over to My Items and come back to our -To-Do List, for instance, we'll see that those check boxes have returned to -their initial state. - -### Step 4-Add a Controller - -We need to do some scripting to add dynamic behavior to that view. In -particular, we want to: - -* Filter by complete/incomplete status. -* Change the completion state of tasks in the model. - -To do this, we will support this by adding an Angular controller. (See -https://docs.angularjs.org/guide/controller for an overview of controllers.) -We will define that in an AMD module (see http://requirejs.org/docs/whyamd.html) -in the directory `tutorials/todo/src/controllers` (`src` is, by default, the -directory where bundle-related source code is kept, and controllers is where -Angular controllers are stored by convention.) - -```js -define(function () { - function TodoController($scope) { - var showAll = true, - showCompleted; - - // Persist changes made to a domain object's model - function persist() { - var persistence = - $scope.domainObject.getCapability('persistence'); - return persistence && persistence.persist(); - } - - // Change which tasks are visible - $scope.setVisibility = function (all, completed) { - showAll = all; - showCompleted = completed; - }; - - // Toggle the completion state of a task - $scope.toggleCompletion = function (taskIndex) { - $scope.domainObject.useCapability('mutation', function (model) { - var task = model.tasks[taskIndex]; - task.completed = !task.completed; - }); - persist(); - }; - - // Check whether a task should be visible - $scope.showTask = function (task) { - return showAll || (showCompleted === !!(task.completed)); - }; - } - - return TodoController; -}); -``` -__tutorials/todo/src/controllers/TodoController.js__ - -Here, we've defined three new functions and placed them in our `$scope`, which -will make them available from the template: - -* `setVisibility` changes which tasks are meant to be visible. The first argument -is a boolean, which, if true, means we want to show everything; the second -argument is the completion state we want to show (which is only relevant if the -first argument is falsy.) - -* `toggleCompletion` changes whether or not a task is complete. We make the -change via the domain object's `mutation` capability, and then persist the -change via its `persistence` capability. See the Open MCT Developer Guide -for more information on these capabilities. - -* `showTask` is meant to be used to help decide if a task should be shown, based -on the current visibility settings. It is true when we have decided to show -everything, or when the completion state matches the state we've chosen. (Note -the use of the double-not !! to coerce the completed flag to a boolean, for -equality testing.) - -Note that these functions make reference to `$scope.domainObject;` this is the -domain object being viewed, which is passed into the scope by Open MCT -prior to our template being utilized. - -On its own, this controller merely exposes these functions; the next step is to -use them from our template: - -```html -+
-
-+ All -+ Incomplete -+ Complete -
- -
    -
  • - - {{task.description}} -
  • -
-+
-``` -__tutorials/todo/res/templates/todo.html__ - -Summary of changes here: - -* First, we surround everything in a `div` which we use to utilize our -`TodoController`. This `div` will also come in handy later for styling. -* From our filters at the top, we change the visibility settings when a different -option is clicked. -* When showing tasks, we check with `showTask` to see if the task matches current -filter settings. -* Finally, when the checkbox for a task is clicked, we make the change in the -model via `toggleCompletion`. - -If we were to try to run at this point, we'd run into problems because the -`TodoController` has not been registered with Angular. We need to first declare -it in our bundle definition, as an extension of category `controllers`: - -```js -define([ - 'openmct', -+ './src/controllers/TodoController' -], function ( - openmct, -+ TodoController -) { - openmct.legacyRegistry.register("tutorials/todo", { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "cssClass": "icon-check", - "description": "A list of things that need to be done.", - "features": ["creation"], - "model": { - "tasks": [ - { "description": "Add a type", "completed": true }, - { "description": "Add a view" } - ] - } - } - ], - "views": [ - { - "key": "example.todo", - "type": "example.todo", - "cssClass": "icon-check", - "name": "List", - "templateUrl": "templates/todo.html", - "editable": true - } - ], -+ "controllers": [ -+ { -+ "key": "TodoController", -+ "implementation": TodoController, -+ "depends": [ "$scope" ] -+ } -+ ] - } - }); -}); -``` -__tutorials/todo/bundle.js__ - -In this extension definition we have: - -* A `key`, which again is a machine-readable identifier. This is the name that -templates will reference. -* An `implementation`, which refers to an AMD module. The path is relative to the -`src` directory within the bundle. -* The `depends` property declares the dependencies of this controller. Here, we -want Angular to inject `$scope`, the current Angular scope (which, going back -to our controller, is expected as our first argument.) - -If we reload the browser now, our To-do List looks much the same, but now we are -able to filter down the visible list, and the changes we make will stick around -if we go to My Items and come back. - - -### Step 5-Support Editing - -We now have a somewhat-functional view of our To-Do List, but we're still -missing some important functionality: Adding and removing tasks! - -This is a good place to discuss the user interface style of Open MCT. Open -MCT Web draws a distinction between "using" and "editing" a domain object; in -general, you can only make changes to a domain object while in Edit mode, which -is reachable from the button with a pencil icon. This distinction helps users -keep these tasks separate. - -The distinction between "using" and "editing" may vary depending on what domain -objects or views are being used. While it may be convenient for a developer to -think of "editing" as "any changes made to a domain object," in practice some of -these activities will be thought of as "using." - -For this tutorial we'll consider checking/unchecking tasks as "using" To-Do -Lists, and adding/removing tasks as "editing." We've already implemented the -"using" part, in this case, so let's focus on editing. - -There are two new pieces of functionality we'll want out of this step: - -* The ability to add new tasks. -* The ability to remove existing tasks. - -An Editing user interface is typically handled in a tool bar associated with a -view. The contents of this tool bar are defined declaratively in a view's -extension definition. - -```js -define([ - 'openmct', - './src/controllers/TodoController' -], function ( - openmct, - TodoController -) { - openmct.legacyRegistry.register("tutorials/todo", { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "cssClass": "icon-check", - "description": "A list of things that need to be done.", - "features": ["creation"], - "model": { - "tasks": [ - { "description": "Add a type", "completed": true }, - { "description": "Add a view" } - ] - } - } - ], - "views": [ - { - "key": "example.todo", - "type": "example.todo", - "cssClass": "icon-check", - "name": "List", - "templateUrl": "templates/todo.html", - "editable": true, -+ "toolbar": { -+ "sections": [ -+ { -+ "items": [ -+ { -+ "text": "Add Task", -+ "cssClass": "icon-plus", -+ "method": "addTask", -+ "control": "button" -+ } -+ ] -+ }, -+ { -+ "items": [ -+ { -+ "cssClass": "icon-trash", -+ "method": "removeTask", -+ "control": "button" -+ } -+ ] -+ } -+ ] -+ } - } - ], - "controllers": [ - { - "key": "TodoController", - "implementation": TodoController, - "depends": [ "$scope" ] - } - ] - } - }); -}); -``` -__tutorials/todo/bundle.js__ - -What we've stated here is that the To-Do List's view will have a toolbar which -contains two sections (which will be visually separated by a divider), each of -which contains one button. The first is a button labelled "Add Task" that will -invoke an `addTask` method; the second is a button with a glyph (which will appear -as a trash can in Open MCT's custom font set) which will invoke a `removeTask` -method. For more information on forms and tool bars in Open MCT, see the -Open MCT Developer Guide. - -If we reload and run Open MCT, we won't see any tool bar when we switch over -to Edit mode. This is because the aforementioned methods are expected to be -found on currently-selected elements; we haven't done anything with selections -in our view yet, so the Open MCT platform will filter this tool bar down to -all the applicable controls, which means no controls at all. - -To support selection, we will need to make some changes to our controller: - -```js -define(function () { -+ // Form to display when adding new tasks -+ var NEW_TASK_FORM = { -+ name: "Add a Task", -+ sections: [{ -+ rows: [{ -+ name: 'Description', -+ key: 'description', -+ control: 'textfield', -+ required: true -+ }] -+ }] -+ }; - -+ function TodoController($scope, dialogService) { - var showAll = true, - showCompleted; - - // Persist changes made to a domain object's model - function persist() { - var persistence = - $scope.domainObject.getCapability('persistence'); - return persistence && persistence.persist(); - } - -+ // Remove a task -+ function removeTaskAtIndex(taskIndex) { -+ $scope.domainObject.useCapability('mutation', function -+ (model) { -+ model.tasks.splice(taskIndex, 1); -+ }); -+ persist(); -+ } - -+ // Add a task -+ function addNewTask(task) { -+ $scope.domainObject.useCapability('mutation', function -+ (model) { -+ model.tasks.push(task); -+ }); -+ persist(); -+ } - - // Change which tasks are visible - $scope.setVisibility = function (all, completed) { - showAll = all; - showCompleted = completed; - }; - - // Toggle the completion state of a task - $scope.toggleCompletion = function (taskIndex) { - $scope.domainObject.useCapability('mutation', function (model) { - var task = model.tasks[taskIndex]; - task.completed = !task.completed; - }); - persist(); - }; - - // Check whether a task should be visible - $scope.showTask = function (task) { - return showAll || (showCompleted === !!(task.completed)); - }; - - // Handle selection state in edit mode -+ if ($scope.selection) { -+ // Expose the ability to select tasks -+ $scope.selectTask = function (taskIndex) { -+ $scope.selection.select({ -+ removeTask: function () { -+ removeTaskAtIndex(taskIndex); -+ $scope.selection.deselect(); -+ } -+ }); -+ }; - -+ // Expose a view-level selection proxy -+ $scope.selection.proxy({ -+ addTask: function () { -+ dialogService.getUserInput(NEW_TASK_FORM, {}) -+ .then(addNewTask); -+ } -+ }); -+ } - } - - return TodoController; -}); -``` -__tutorials/todo/src/controllers/TodoController.js__ - -There are a few changes to pay attention to here. Let's review them: - -* At the top, we describe the form that should be shown to the user when they -click the _Add Task_ button. This form is described declaratively, and populates -an object that has the same format as tasks in the `tasks` array of our -To-Do List's model. -* We've added an argument to the `TodoController`: The `dialogService`, which is -exposed by the Open MCT platform to handle showing dialogs. -* Some utility functions for handling the actual adding and removing of tasks. -These use the `mutation` capability to modify the tasks in the To-Do List's -model. -* Finally, we check for the presence of a `selection` object in our scope. This -object is provided by Edit mode to manage current selections for editing. When -it is present, we expose a `selectTask` function to our scope to allow selecting -individual tasks; when this occurs, we expose an object to `selection` which has -a `removeTask` method, as expected by the tool bar we've defined. We additionally -expose a view proxy, to handle view-level changes (e.g. not associated with any -specific selected object); this has an `addTask` method, which again is expected -by the tool bar we've defined. - -Additionally, we need to make changes to our template to select specific tasks -in response to some user gesture. Here, we will select tasks when a user clicks -the description. - -```html -
- - -
    -
  • - -+ - {{task.description}} -+ -
  • -
-
-``` -__tutorials/todo/res/templates/todo.html__ - -Finally, the `TodoController` uses the `dialogService` now, so we need to -declare that dependency in its extension definition: - -```js -define([ - 'openmct', - './src/controllers/TodoController' -], function ( - openmct, - TodoController -) { - openmct.legacyRegistry.register("tutorials/todo", { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "cssClass": "icon-check", - "description": "A list of things that need to be done.", - "features": ["creation"], - "model": { - "tasks": [ - { "description": "Add a type", "completed": true }, - { "description": "Add a view" } - ] - } - } - ], - "views": [ - { - "key": "example.todo", - "type": "example.todo", - "cssClass": "icon-check", - "name": "List", - "templateUrl": "templates/todo.html", - "editable": true, - "toolbar": { - "sections": [ - { - "items": [ - { - "text": "Add Task", - "cssClass": "icon-plus", - "method": "addTask", - "control": "button" - } - ] - }, - { - "items": [ - { - "cssClass": "icon-trash", - "method": "removeTask", - "control": "button" - } - ] - } - ] - } - } - ], - "controllers": [ - { - "key": "TodoController", - "implementation": TodoController, -+ "depends": [ "$scope", "dialogService" ] - } - ] - } - }); -}); -``` -__tutorials/todo/bundle.js__ - -If we now reload Open MCT, we'll be able to see the new functionality we've -added. If we Create a new To-Do List, navigate to it, and click the button with -the Pencil icon in the top-right, we'll be in edit mode. We see, first, that our -"Add Task" button appears in the tool bar: - -![Edit](images/todo-edit.png) - -If we click on this, we'll get a dialog allowing us to add a new task: - -![Add task](images/add-task.png) - -Finally, if we click on the description of a specific task, we'll see a new -button appear, which we can then click on to remove that task: - -![Remove task](images/remove-task.png) - -As always in Edit mode, the user will be able to Save or Cancel any changes they have made. -In terms of functionality, our To-Do List can do all the things we want, but the appearance is still lacking. In particular, we can't distinguish our current filter choice or our current selection state. - -### Step 6-Customizing Look and Feel - -In this section, our goal is to: - -* Display the current filter choice. -* Display the current task selection (when in Edit mode.) -* Tweak the general aesthetics to our liking. -* Get rid of those default tasks (we can create our own now.) - -To support the first two, we'll need to expose some methods for checking these -states in the controller: - -```js -define(function () { - // Form to display when adding new tasks - var NEW_TASK_FORM = { - name: "Add a Task", - sections: [{ - rows: [{ - name: 'Description', - key: 'description', - control: 'textfield', - required: true - }] - }] - }; - - function TodoController($scope, dialogService) { - var showAll = true, - showCompleted; - - // Persist changes made to a domain object's model - function persist() { - var persistence = - $scope.domainObject.getCapability('persistence'); - return persistence && persistence.persist(); - } - - // Remove a task - function removeTaskAtIndex(taskIndex) { - $scope.domainObject.useCapability('mutation', function (model) { - model.tasks.splice(taskIndex, 1); - }); - persist(); - } - - // Add a task - function addNewTask(task) { - $scope.domainObject.useCapability('mutation', function (model) { - model.tasks.push(task); - }); - persist(); - } - - // Change which tasks are visible - $scope.setVisibility = function (all, completed) { - showAll = all; - showCompleted = completed; - }; - -+ // Check if current visibility settings match -+ $scope.checkVisibility = function (all, completed) { -+ return showAll ? all : (completed === showCompleted); -+ }; - - // Toggle the completion state of a task - $scope.toggleCompletion = function (taskIndex) { - $scope.domainObject.useCapability('mutation', function (model) { - var task = model.tasks[taskIndex]; - task.completed = !task.completed; - }); - persist(); - }; - - // Check whether a task should be visible - $scope.showTask = function (task) { - return showAll || (showCompleted === !!(task.completed)); - }; - - // Handle selection state in edit mode - if ($scope.selection) { - // Expose the ability to select tasks - $scope.selectTask = function (taskIndex) { - $scope.selection.select({ - removeTask: function () { - removeTaskAtIndex(taskIndex); - $scope.selection.deselect(); - }, -+ taskIndex: taskIndex - }); - }; - -+ // Expose a check for current selection state -+ $scope.isSelected = function (taskIndex) { -+ return ($scope.selection.get() || {}).taskIndex === -+ taskIndex; -+ }; - - // Expose a view-level selection proxy - $scope.selection.proxy({ - addTask: function () { - dialogService.getUserInput(NEW_TASK_FORM, {}) - .then(addNewTask); - } - }); - } - } - - return TodoController; -}); -``` -__tutorials/todo/src/controllers/TodoController.js__ - -A summary of these changes: - -* `checkVisibility` has the same arguments as `setVisibility`, but instead of -making a change, it simply returns a boolean true/false indicating whether those -settings are in effect. The logic reflects the fact that the second parameter is -ignored when showing all. -* To support checking for selection, the index of the currently-selected task is -tracked as part of the selection object. -* Finally, an isSelected function is exposed which checks if the indicated task -is currently selected, using the index from above. - -Additionally, we will want to define some CSS rules in order to reflect these -states visually, and to generally improve the appearance of our view. We add -another file to the res directory of our bundle; this time, it is `css/todo.css` -(with the `css` directory again being a convention.) - -```css -.example-todo div.example-button-group { - margin-top: 12px; - margin-bottom: 12px; -} - -.example-todo .example-button-group a { - padding: 3px; - margin: 3px; -} - -.example-todo .example-button-group a.selected { - border: 1px gray solid; - border-radius: 3px; - background: #444; -} - -.example-todo .example-task-completed .example-task-description { - text-decoration: line-through; - opacity: 0.75; -} - -.example-todo .example-task-description.selected { - background: #46A; - border-radius: 3px; -} - -.example-todo .example-message { - font-style: italic; -} -``` -__tutorials/todo/res/css/todo.css__ - -Here, we have defined classes and appearances for: - -* Our filter choosers (`example-button-group`). -* Our selected and/or completed tasks (`example-task-description`). -* A message, which we will add next, to display when there are no tasks -(`example-message`). - -To include this CSS file in our running instance of Open MCT, we need to -declare it in our bundle definition, this time as an extension of category -`stylesheets`: - -```js -define([ - 'openmct', - './src/controllers/TodoController' -], function ( - openmct, - TodoController -) { - openmct.legacyRegistry.register("tutorials/todo", { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "cssClass": "icon-check", - "description": "A list of things that need to be done.", - "features": ["creation"], - "model": { - "tasks": [] - } - } - ], - "views": [ - { - "key": "example.todo", - "type": "example.todo", - "cssClass": "icon-check", - "name": "List", - "templateUrl": "templates/todo.html", - "editable": true, - "toolbar": { - "sections": [ - { - "items": [ - { - "text": "Add Task", - "cssClass": "icon-plus", - "method": "addTask", - "control": "button" - } - ] - }, - { - "items": [ - { - "cssClass": "icon-trash", - "method": "removeTask", - "control": "button" - } - ] - } - ] - } - } - ], - "controllers": [ - { - "key": "TodoController", - "implementation": TodoController, - "depends": [ "$scope", "dialogService" ] - } - ], -+ "stylesheets": [ -+ { -+ "stylesheetUrl": "css/todo.css" -+ } -+ ] - } - }); -}); -``` -__tutorials/todo/bundle.js__ - -Note that we've also removed our placeholder tasks from the `model` of the -To-Do List's type above; now To-Do Lists will start off empty. - -Finally, let's utilize these changes from our view's template: - -```html -+
-+
-+ All -+ Incomplete -+ Complete -
- -
    -
  • - - - {{task.description}} - -
  • -
-+
-+ There are no tasks to show. -+
-+
-``` -__tutorials/todo/res/templates/todo.html__ - -Now, if we reload our page and create a new To-Do List, we will initially see: - -![Todo Restyled](images/todo-restyled.png) - -If we then go into Edit mode, add some tasks, and select one, it will now be -much clearer what the current selection is (e.g. before we hit the remove button -in the toolbar): - -![Todo Restyled](images/todo-selection.png) - -## Bar Graph - -In this tutorial, we will look at creating a bar graph plugin for visualizing -telemetry data. Specifically, we want some bars that raise and lower to match -the observed state of real-time telemetry; this is particularly useful for -monitoring things like battery charge levels. -It is recommended that the reader completes (or is familiar with) the To-Do -List tutorial before completing this tutorial, as certain concepts discussed -there will be addressed in more brevity here. - -### Step 1-Define the View - -Since the goal is to introduce a new view and expose it from a plugin, we will -want to create a new bundle which declares an extension of category `views`. -We'll also be defining some custom styles, so we'll include that extension as -well. We'll be creating this plugin in `tutorials/bargraph`, so our initial -bundle definition looks like: - -```js -define([ - 'openmct' -], function ( - openmct -) { - openmct.legacyRegistry.register("tutorials/bargraph", { - "name": "Bar Graph", - "description": "Provides the Bar Graph view of telemetry elements.", - "extensions": { - "views": [ - { - "name": "Bar Graph", - "key": "example.bargraph", - "cssClass": "icon-autoflow-tabular", - "templateUrl": "templates/bargraph.html", - "needs": [ "telemetry" ], - "delegation": true - } - ], - "stylesheets": [ - { - "stylesheetUrl": "css/bargraph.css" - } - ] - } - }); -}); -``` -__tutorials/bargraph/bundle.js__ - -The view definition should look familiar after the To-Do List tutorial, with -some additions: - -* The `needs` property indicates that this view is only applicable to domain -objects with a `telemetry` capability. This ensures that this view is available -for telemetry points, but not for other objects (like folders.) -* The `delegation` property indicates that the above constraint can be satisfied -via capability delegation; that is, by domain objects which delegate the -`telemetry` capability to their contained objects. This allows this view to be -used for Telemetry Panel objects as well as for individual telemetry-providing -domain objects. - -For this tutorial, we'll assume that we've sketched out our template and CSS -file ahead of time to describe the general look we want for the view. These -look like: - -```html -
-
-
High
-
Middle
-
Low
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- Label A -
-
- Label B -
-
- Label C -
-
-
-``` -__tutorials/bargraph/res/templates/bargraph.html__ - -Here, three regions are defined. The first will be for tick labels along the -vertical axis, showing the numeric value that certain heights correspond to. The -second will be for the actual bar graphs themselves; three are included here. -The third is for labels along the horizontal axis, which will indicate which -bar corresponds to which telemetry point. Inline `style` attributes are used -wherever dynamic positioning (handled by a script) is anticipated. -The corresponding CSS file which styles and positions these elements: - -```css -.example-bargraph { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; - mid-width: 160px; - min-height: 160px; -} - -.example-bargraph .example-tick-labels { - position: absolute; - left: 0; - top: 24px; - bottom: 32px; - width: 72px; - font-size: 75%; -} - -.example-bargraph .example-tick-label { - position: absolute; - right: 0; - height: 1em; - margin-bottom: -0.5em; - padding-right: 6px; - text-align: right; -} - -.example-bargraph .example-graph-area { - position: absolute; - border: 1px gray solid; - left: 72px; - top: 24px; - bottom: 32px; - right: 0; -} - -.example-bargraph .example-bar-labels { - position: absolute; - left: 72px; - bottom: 0; - right: 0; - height: 32px; -} - -.example-bargraph .example-bar-holder { - position: absolute; - top: 0; - bottom: 0; -} - -.example-bargraph .example-graph-tick { - position: absolute; - width: 100%; - height: 1px; - border-bottom: 1px gray dashed; -} - -.example-bargraph .example-bar { - position: absolute; - background: darkcyan; - right: 4px; - left: 4px; -} - -.example-bargraph .example-label { - text-align: center; - font-size: 85%; - padding-top: 6px; -} -``` -__tutorials/bargraph/res/css/bargraph.css__ - -This is already enough that, if we add `"tutorials/bargraph"` to `index.html`, -we should be able to run Open MCT and see our Bar Graph as an available view -for domain objects which provide telemetry (such as the example -_Sine Wave Generator_) as well as for _Telemetry Panel_ objects: - -![Bar Plot](images/bar-plot.png) - -This means that our remaining work will be to populate and position these -elements based on the actual contents of the domain object. - -### Step 2-Add a Controller - -Our next step will be to begin dynamically populating this template's contents. -Specifically, our goals for this step will be to: - -* Show one bar per telemetry-providing domain object (for which we'll be getting -actual telemetry data in subsequent steps.) -* Show correct labels for these objects at the bottom. -* Show numeric labels on the left-hand side. - -Notably, we will not try to show telemetry data after this step. - -To support this, we will add a new controller which supports our Bar Graph view: - -```js -define(function () { - function BarGraphController($scope, telemetryHandler) { - var handle; - - // Add min/max defaults - $scope.low = -1; - $scope.middle = 0; - $scope.high = 1; - - // Convert value to a percent between 0-100, keeping values in points - $scope.toPercent = function (value) { - var pct = 100 * (value - $scope.low) / ($scope.high - $scope.low); - return Math.min(100, Math.max(0, pct)); - }; - - // Use the telemetryHandler to get telemetry objects here - handle = telemetryHandler.handle($scope.domainObject, function () { - $scope.telemetryObjects = handle.getTelemetryObjects(); - $scope.barWidth = - 100 / Math.max(($scope.telemetryObjects).length, 1); - }); - - // Release subscriptions when scope is destroyed - $scope.$on('$destroy', handle.unsubscribe); - } - - return BarGraphController; -}); -``` -__tutorials/bargraph/src/controllers/BarGraphController.js__ - -A summary of what we've done here: - -* We're exposing some numeric values that will correspond to the _low_, _middle_, -and _high_ end of the graph. (The `medium` attribute will be useful for -positioning the middle line, which are graphs will ultimately descend down or -push up from.) -* Add a utility function which converts from numeric values to percentages. This -will help support some positioning in the template. -* Utilize the `telemetryHandler`, provided by the platform, to start listening -to real-time telemetry updates. This will deal with most of the complexity of -dealing with telemetry (e.g. differentiating between individual telemetry points -and telemetry panels, monitoring latest values) and provide us with a useful -interface for populating our view. The the Open MCT Developer Guide for more -information on dealing with telemetry. - -Whenever the telemetry handler invokes its callbacks, we update the set of -telemetry objects in view, as well as the width for each bar. - -We will also utilize this from our template: - -```html -+
-
-+
-+ {{value}} -+
-
- -
-+
-
-
-+
-+
-
-
- -
-+
-+ -+ -+
-
-
-``` -__tutorials/bargraph/res/templates/bargraph.html__ - -Summarizing these changes: - -* Utilize the exposed `low`, `middle`, and `high` values to populate our labels -along the vertical axis. Additionally, use the `toPercent` function to position -these from the bottom. -* Replace our three hard-coded bars with a repeater that looks at the -`telemetryObjects` exposed by the controller and adds one bar each. -* Position the dashed tick-line using the `middle` value and the `toPercent` -function, lining it up with its label to the left. -* At the bottom, repeat a set of labels for the telemetry-providing domain -objects, with matching alignment to the bars above. We use an existing -representation, `label`, to make this easier. - -Finally, we expose our controller from our bundle definition. Note that the -depends declaration includes both `$scope` as well as the `telemetryHandler` -service we made use of. - -```js -define([ - 'openmct', - './src/controllers/BarGraphController' -], function ( - openmct, - BarGraphController -) { - openmct.legacyRegistry.register("tutorials/bargraph", { - "name": "Bar Graph", - "description": "Provides the Bar Graph view of telemetry elements.", - "extensions": { - "views": [ - { - "name": "Bar Graph", - "key": "example.bargraph", - "cssClass": "icon-autoflow-tabular", - "templateUrl": "templates/bargraph.html", - "needs": [ "telemetry" ], - "delegation": true - } - ], - "stylesheets": [ - { - "stylesheetUrl": "css/bargraph.css" - } - ], -+ "controllers": [ -+ { -+ "key": "BarGraphController", -+ "implementation": BarGraphController, -+ "depends": [ "$scope", "telemetryHandler" ] -+ } -+ ] - } - }); -}); -``` -__tutorials/bargraph/bundle.js__ - -When we reload Open MCT, we are now able to see that our bar graph view -correctly labels one bar per telemetry-providing domain object, as shown for -this Telemetry Panel containing four Sine Wave Generators. - -![Bar Plot](images/bar-plot-2.png) - -### Step 3-Using Telemetry Data - -Now that our bar graph is labeled correctly, it's time to start putting data -into the view. - -First, let's add expose some more functionality from our controller. To make it -simple, we'll expose the top and bottom for a bar graph for a given -telemetry-providing domain object, as percentages. - -```js -define(function () { - function BarGraphController($scope, telemetryHandler) { - var handle; - - // Add min/max defaults - $scope.low = -1; - $scope.middle = 0; - $scope.high = 1; - - // Convert value to a percent between 0-100, keeping values in points - $scope.toPercent = function (value) { - var pct = 100 * (value - $scope.low) / ($scope.high - $scope.low); - return Math.min(100, Math.max(0, pct)); - }; - - // Get bottom and top (as percentages) for current value -+ $scope.getBottom = function (telemetryObject) { -+ var value = handle.getRangeValue(telemetryObject); -+ return $scope.toPercent(Math.min($scope.middle, value)); -+ } -+ $scope.getTop = function (telemetryObject) { -+ var value = handle.getRangeValue(telemetryObject); -+ return 100 - $scope.toPercent(Math.max($scope.middle, value)); -+ } - - // Use the telemetryHandler to get telemetry objects here - handle = telemetryHandler.handle($scope.domainObject, function () { - $scope.telemetryObjects = handle.getTelemetryObjects(); - $scope.barWidth = - 100 / Math.max(($scope.telemetryObjects).length, 1); - }); - - // Release subscriptions when scope is destroyed - $scope.$on('$destroy', handle.unsubscribe); - } - - return BarGraphController; -}); -``` -__tutorials/bargraph/src/controllers/BarGraphController.js__ - -The `telemetryHandler` exposes a method to provide us with our latest data value -(the `getRangeValue` method), and we already have a function to convert from a -numeric value to a percentage within the view, so we just use those. The only -slight complication is that we want our bar to move up or down from the middle -value, so either of our top or bottom position for the bar itself could be -either the middle line, or the data value. We let `Math.min` and `Math.max` -decide this. - -Next, we utilize this functionality from the template: - -```html -
-
-
- {{value}} -
-
- -
-
-
-
-
-
-
-
- -
-
- - -
-
-
-``` -__tutorials/bargraph/res/templates/bargraph.html__ - -Here, we utilize the functions we just provided from the controller to position -the bar, using an ng-style attribute. - -When we reload Open MCT, our bar graph view now looks like: - -![Bar Plot](images/bar-plot-3.png) - -### Step 4-View Configuration - -The default minimum and maximum values we've provided happen to make sense for -sine waves, but what about other values? We want to provide the user with a -means of configuring these boundaries. - -This is normally done via Edit mode. Since view configuration is a common -problem, the Open MCT platform exposes a configuration object - called -`configuration` - into our view's scope. We can populate it as we please, and -when we return to our view later, those changes will be persisted. - -First, let's add a tool bar for changing these three values in Edit mode: - -```js -define([ - 'openmct', - './src/controllers/BarGraphController' -], function ( - openmct, - BarGraphController -) { - openmct.legacyRegistry.register("tutorials/bargraph", { - "name": "Bar Graph", - "description": "Provides the Bar Graph view of telemetry elements.", - "extensions": { - "views": [ - { - "name": "Bar Graph", - "key": "example.bargraph", - "cssClass": "icon-autoflow-tabular", - "templateUrl": "templates/bargraph.html", - "needs": [ "telemetry" ], - "delegation": true, -+ "toolbar": { -+ "sections": [ -+ { -+ "items": [ -+ { -+ "name": "Low", -+ "property": "low", -+ "required": true, -+ "control": "textfield", -+ "size": 4 -+ }, -+ { -+ "name": "Middle", -+ "property": "middle", -+ "required": true, -+ "control": "textfield", -+ "size": 4 -+ }, -+ { -+ "name": "High", -+ "property": "high", -+ "required": true, -+ "control": "textfield", -+ "size": 4 -+ } -+ ] -+ } - ] - } - } - ], - "stylesheets": [ - { - "stylesheetUrl": "css/bargraph.css" - } - ], - "controllers": [ - { - "key": "BarGraphController", - "implementation": BarGraphController, - "depends": [ "$scope", "telemetryHandler" ] - } - ] - } - }); -}); -``` -__tutorials/bargraph/bundle.js__ - -As we saw in to To-Do List plugin, a tool bar needs either a selected object or -a view proxy to work from. We will add this to our controller, and additionally -will start reading/writing those properties to the view's `configuration` -object. - -```js -define(function () { - function BarGraphController($scope, telemetryHandler) { - var handle; - -+ // Expose configuration constants directly in scope -+ function exposeConfiguration() { -+ $scope.low = $scope.configuration.low; -+ $scope.middle = $scope.configuration.middle; -+ $scope.high = $scope.configuration.high; -+ } - -+ // Populate a default value in the configuration -+ function setDefault(key, value) { -+ if ($scope.configuration[key] === undefined) { -+ $scope.configuration[key] = value; -+ } -+ } - -+ // Getter-setter for configuration properties (for view proxy) -+ function getterSetter(property) { -+ return function (value) { -+ value = parseFloat(value); -+ if (!isNaN(value)) { -+ $scope.configuration[property] = value; -+ exposeConfiguration(); -+ } -+ return $scope.configuration[property]; -+ }; - } - -+ // Add min/max defaults -+ setDefault('low', -1); -+ setDefault('middle', 0); -+ setDefault('high', 1); -+ exposeConfiguration($scope.configuration); - -+ // Expose view configuration options -+ if ($scope.selection) { -+ $scope.selection.proxy({ -+ low: getterSetter('low'), -+ middle: getterSetter('middle'), -+ high: getterSetter('high') -+ }); -+ } - - // Convert value to a percent between 0-100 - $scope.toPercent = function (value) { - var pct = 100 * (value - $scope.low) / - ($scope.high - $scope.low); - return Math.min(100, Math.max(0, pct)); - }; - - // Get bottom and top (as percentages) for current value - $scope.getBottom = function (telemetryObject) { - var value = handle.getRangeValue(telemetryObject); - return $scope.toPercent(Math.min($scope.middle, value)); - } - $scope.getTop = function (telemetryObject) { - var value = handle.getRangeValue(telemetryObject); - return 100 - $scope.toPercent(Math.max($scope.middle, value)); - } - - // Use the telemetryHandler to get telemetry objects here - handle = telemetryHandler.handle($scope.domainObject, function () { - $scope.telemetryObjects = handle.getTelemetryObjects(); - $scope.barWidth = - 100 / Math.max(($scope.telemetryObjects).length, 1); - }); - - // Release subscriptions when scope is destroyed - $scope.$on('$destroy', handle.unsubscribe); - } - - return BarGraphController; -}); -``` -__tutorials/bargraph/src/controllers/BarGraphController.js__ - -A summary of these changes: - -* First, read `low`, `middle`, and `high` from the view configuration instead of -initializing them to explicit values. This is placed into its own function, -since it will be called a lot. -* The function `setDefault` is included; it will be used to set the default -values for `low`, `middle`, and `high` in the view configuration, but only if -they aren't present. -* The tool bar will treat properties in a view proxy as getter-setters if -they are functions; that is, they will be called with an argument to be used -as a setter, and with no argument to use as a getter. We provide ourselves a -function for making these getter-setters (since we'll need three) that -additionally handles some checking to ensure that these are actually numbers. -* After that, we actually initialize both the view `configuration` object with -defaults (if needed), and expose its state into the scope. -* Finally, we expose a view proxy which will handle changes to `low`, `middle`, -and `high` as entered by the user from the tool bar. This uses the -getter-setters we defined previously. - -If we reload Open MCT and go to a Bar Graph view in Edit mode, we now see -that we can change these bounds from the tool bar. - -![Bar plot](images/bar-plot-4.png) - -## Telemetry Adapter - -The goal of this tutorial is to demonstrate how to integrate Open MCT -with an existing telemetry system. - -A summary of the steps we will take: - -* Expose the telemetry dictionary within the user interface. -* Support subscription/unsubscription to real-time streaming data. -* Support historical retrieval of telemetry data. - -### Step 0-Expose Your Telemetry - -As a precondition to integrating telemetry data into Open MCT, this -information needs to be available over web-based interfaces. In practice, -this will most likely mean exposing data over HTTP, or over WebSockets. -For purposes of this tutorial, a simple node server is provided to stand -in place of this existing telemetry system. It generates real-time data -and exposes it over a WebSocket connection. - -```js -/*global require,process,console*/ - -var CONFIG = { - port: 8081, - dictionary: "dictionary.json", - interval: 1000 -}; - -(function () { - "use strict"; - - var WebSocketServer = require('ws').Server, - fs = require('fs'), - wss = new WebSocketServer({ port: CONFIG.port }), - dictionary = JSON.parse(fs.readFileSync(CONFIG.dictionary, "utf8")), - spacecraft = { - "prop.fuel": 77, - "prop.thrusters": "OFF", - "comms.recd": 0, - "comms.sent": 0, - "pwr.temp": 245, - "pwr.c": 8.15, - "pwr.v": 30 - }, - histories = {}, - listeners = []; - - function updateSpacecraft() { - spacecraft["prop.fuel"] = Math.max( - 0, - spacecraft["prop.fuel"] - - (spacecraft["prop.thrusters"] === "ON" ? 0.5 : 0) - ); - spacecraft["pwr.temp"] = spacecraft["pwr.temp"] * 0.985 - + Math.random() * 0.25 + Math.sin(Date.now()); - spacecraft["pwr.c"] = spacecraft["pwr.c"] * 0.985; - spacecraft["pwr.v"] = 30 + Math.pow(Math.random(), 3); - } - - function generateTelemetry() { - var timestamp = Date.now(), sent = 0; - Object.keys(spacecraft).forEach(function (id) { - var state = { timestamp: timestamp, value: spacecraft[id] }; - histories[id] = histories[id] || []; // Initialize - histories[id].push(state); - spacecraft["comms.sent"] += JSON.stringify(state).length; - }); - listeners.forEach(function (listener) { - listener(); - }); - } - - function update() { - updateSpacecraft(); - generateTelemetry(); - } - - function handleConnection(ws) { - var subscriptions = {}, // Active subscriptions for this connection - handlers = { // Handlers for specific requests - dictionary: function () { - ws.send(JSON.stringify({ - type: "dictionary", - value: dictionary - })); - }, - subscribe: function (id) { - subscriptions[id] = true; - }, - unsubscribe: function (id) { - delete subscriptions[id]; - }, - history: function (id) { - ws.send(JSON.stringify({ - type: "history", - id: id, - value: histories[id] - })); - } - }; - - function notifySubscribers() { - Object.keys(subscriptions).forEach(function (id) { - var history = histories[id]; - if (history) { - ws.send(JSON.stringify({ - type: "data", - id: id, - value: history[history.length - 1] - })); - } - }); - } - - // Listen for requests - ws.on('message', function (message) { - var parts = message.split(' '), - handler = handlers[parts[0]]; - if (handler) { - handler.apply(handlers, parts.slice(1)); - } - }); - - // Stop sending telemetry updates for this connection when closed - ws.on('close', function () { - listeners = listeners.filter(function (listener) { - return listener !== notifySubscribers; - }); - }); - - // Notify subscribers when telemetry is updated - listeners.push(notifySubscribers); - } - - update(); - setInterval(update, CONFIG.interval); - - wss.on('connection', handleConnection); - - console.log("Example spacecraft running on port "); - console.log("Press Enter to toggle thruster state."); - process.stdin.on('data', function (data) { - spacecraft['prop.thrusters'] = - (spacecraft['prop.thrusters'] === "OFF") ? "ON" : "OFF"; - console.log("Thrusters " + spacecraft["prop.thrusters"]); - }); -}()); -``` -__tutorial-server/app.js__ - -For purposes of this tutorial, how this server has been implemented is -not important; it has just enough functionality to resemble a WebSocket -interface to a real telemetry system, and niceties such as error-handling -have been omitted. (For more information on using WebSockets, both in the -client and on the server, -https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API is an -excellent starting point.) - -What does matter for this tutorial is the interfaces that are exposed. Once a -WebSocket connection has been established to this server, it accepts plain text -messages in the following formats, and issues JSON-formatted responses. - -The requests it handles are: - -* `dictionary`: Responds with a JSON response with the following fields: - * `type`: "dictionary" - * `value`: … the telemetry dictionary (see below) … -* `subscribe `: Subscribe to new telemetry data for the measurement with -the provided identifier. The server will begin sending messages of the -following form: - * `type`: "data" - * `id`: The identifier for the measurement. - * `value`: An object containing the actual measurement, in two fields: - * `timestamp`: A UNIX timestamp (in milliseconds) for the "measurement" - * `value`: The data value for the measurement (either a number, or a - string) -* `unsubscribe `: Stop receiving new data for the identified measurement. -* `history `: Request a history of all telemetry data for the identified -measurement. - * `type`: "history" - * `id`: The identifier for the measurement. - * `value`: An array of objects containing the actual measurement, each of - which having two fields: - * `timestamp`: A UNIX timestamp (in milliseconds) for the "measurement" - * `value`: The data value for the measurement (either a number, or - a string) - -(Note that the term "measurement" is used to describe a distinct data series -within this system; in other systems, these have been called channels, -mnemonics, telemetry points, or other names. No preference is made here; -Open MCT is easily adapted to use the terminology appropriate to your -system.) -Additionally, while running the server from the terminal we can toggle the -state of the "spacecraft" by hitting enter; this will turn the "thrusters" -on and off, having observable changes in telemetry. - -The telemetry dictionary referenced previously is contained in a separate file, -used by the server. It uses a custom format and, for purposes of example, -contains three "subsystems" containing a mix of numeric and string-based -telemetry. - -```json -{ - "name": "Example Spacecraft", - "identifier": "sc", - "subsystems": [ - { - "name": "Propulsion", - "identifier": "prop", - "measurements": [ - { - "name": "Fuel", - "identifier": "prop.fuel", - "units": "kilograms", - "type": "float" - }, - { - "name": "Thrusters", - "identifier": "prop.thrusters", - "units": "None", - "type": "string" - } - ] - }, - { - "name": "Communications", - "identifier": "comms", - "measurements": [ - { - "name": "Received", - "identifier": "comms.recd", - "units": "bytes", - "type": "integer" - }, - { - "name": "Sent", - "identifier": "comms.sent", - "units": "bytes", - "type": "integer" - } - ] - }, - { - "name": "Power", - "identifier": "pwr", - "measurements": [ - { - "name": "Generator Temperature", - "identifier": "pwr.temp", - "units": "\u0080C", - "type": "float" - }, - { - "name": "Generator Current", - "identifier": "pwr.c", - "units": "A", - "type": "float" - }, - { - "name": "Generator Voltage", - "identifier": "pwr.v", - "units": "V", - "type": "float" - } - ] - } - ] -} -``` -__tutorial-server/dictionary.json__ - -It should be noted that neither the interface for the example server nor the -dictionary format are expected by Open MCT; rather, these are intended to -stand in for some existing source of telemetry data to which we wish to adapt -Open MCT. - -We can run this example server by: - - cd tutorial-server - npm install ws - node app.js - -To verify that this is running and try out its interface, we can use a tool -like https://www.npmjs.com/package/wscat : - - wscat -c ws://localhost:8081 - connected (press CTRL+C to quit) - > dictionary - < {"type":"dictionary","value":{"name":"Example Spacecraft","identifier":"sc","subsystems":[{"name":"Propulsion","identifier":"prop","measurements":[{"name":"Fuel","identifier":"prop.fuel","units":"kilograms","type":"float"},{"name":"Thrusters","identifier":"prop.thrusters","units":"None","type":"string"}]},{"name":"Communications","identifier":"comms","measurements":[{"name":"Received","identifier":"comms.recd","units":"bytes","type":"integer"},{"name":"Sent","identifier":"comms.sent","units":"bytes","type":"integer"}]},{"name":"Power","identifier":"pwr","measurements":[{"name":"Generator Temperature","identifier":"pwr.temp","units":"€C","type":"float"},{"name":"Generator Current","identifier":"pwr.c","units":"A","type":"float"},{"name":"Generator Voltage","identifier":"pwr.v","units":"V","type":"float"}]}]}} - -Now that the example server's interface is reasonably well-understood, a plugin -can be written to adapt Open MCT to utilize it. - -### Step 1-Add a Top-level Object - -Since Open MCT uses an "object-first" approach to accessing data, before -we'll be able to do anything with this new data source, we'll need to have a -way to explore the available measurements in the tree. In this step, we will -add a top-level object which will serve as a container; in the next step, we -will populate this with the contents of the telemetry dictionary (which we -will retrieve from the server.) - -```diff -define([ - 'openmct' -], function ( - openmct -) { - openmct.legacyRegistry.register("tutorials/telemetry", { - "name": "Example Telemetry Adapter", - "extensions": { - "types": [ - { - "name": "Spacecraft", - "key": "example.spacecraft", - "cssClass": "icon-object" - } - ], - "roots": [ - { - "id": "example:sc", - "priority": "preferred" - } - ], - "models": [ - { - "id": "example:sc", - "model": { - "type": "example.spacecraft", - "name": "My Spacecraft", - "location": "ROOT", - "composition": [] - } - } - ] - } - }); -}); -``` -__tutorials/telemetry/bundle.js__ - -Here, we've created our initial telemetry plugin. This exposes a new domain -object type (the "Spacecraft", which will be represented by the contents of the -telemetry dictionary) and also adds one instance of it as a root-level object -(by declaring an extension of category roots.) We have also set priority to -preferred so that this shows up near the top, instead of below My Items. - -If we include this in our set of active bundles: - -```html - - - - - - - - - - - - - - - - - -
-
-
- - - -``` -__index.html__ - -...we will be able to reload Open MCT and see that it is present: - -![Telemetry](images/telemetry-1.png) - -Now, we have somewhere in the UI to put the contents of our telemetry -dictionary. - -### Step 2-Expose the Telemetry Dictionary - -In order to expose the telemetry dictionary, we first need to read it from the -server. Our first step will be to add a service that will handle interactions -with the server; this will not be used by Open MCT directly, but will be -used by subsequent components we add. - -```js -/*global define,WebSocket*/ - -define( - [], - function () { - "use strict"; - - function ExampleTelemetryServerAdapter($q, wsUrl) { - var ws = new WebSocket(wsUrl), - dictionary = $q.defer(); - - // Handle an incoming message from the server - ws.onmessage = function (event) { - var message = JSON.parse(event.data); - - switch (message.type) { - case "dictionary": - dictionary.resolve(message.value); - break; - } - }; - - // Request dictionary once connection is established - ws.onopen = function () { - ws.send("dictionary"); - }; - - return { - dictionary: function () { - return dictionary.promise; - } - }; - } - - return ExampleTelemetryServerAdapter; - } -); -``` -__tutorials/telemetry/src/ExampleTelemetryServerAdapter.js__ - -When created, this service initiates a connection to the server, and begins -loading the dictionary. This will occur asynchronously, so the `dictionary()` -method it exposes returns a `Promise` for the loaded dictionary -(`dictionary.json` from above), using Angular's `$q` -(see https://docs.angularjs.org/api/ng/service/$q .) Note that error- and -close-handling for this WebSocket connection have been omitted for brevity. - -Once the dictionary has been loaded, we will want to represent its contents -as domain objects. Specifically, we want subsystems to appear as objects -under My Spacecraft, and measurements to appear as objects within those -subsystems. This means that we need to convert the data from the dictionary -into domain object models, and expose these to Open MCT via a -`modelService`. - -```js -/*global define*/ - -define( - function () { - "use strict"; - - var PREFIX = "example_tlm:", - FORMAT_MAPPINGS = { - float: "number", - integer: "number", - string: "string" - }; - - function ExampleTelemetryModelProvider(adapter, $q) { - var modelPromise, empty = $q.when({}); - - // Check if this model is in our dictionary (by prefix) - function isRelevant(id) { - return id.indexOf(PREFIX) === 0; - } - - // Build a domain object identifier by adding a prefix - function makeId(element) { - return PREFIX + element.identifier; - } - - // Create domain object models from this dictionary - function buildTaxonomy(dictionary) { - var models = {}; - - // Create & store a domain object model for a measurement - function addMeasurement(measurement) { - var format = FORMAT_MAPPINGS[measurement.type]; - models[makeId(measurement)] = { - type: "example.measurement", - name: measurement.name, - telemetry: { - key: measurement.identifier, - ranges: [{ - key: "value", - name: "Value", - units: measurement.units, - format: format - }] - } - }; - } - - // Create & store a domain object model for a subsystem - function addSubsystem(subsystem) { - var measurements = - (subsystem.measurements || []); - models[makeId(subsystem)] = { - type: "example.subsystem", - name: subsystem.name, - composition: measurements.map(makeId) - }; - measurements.forEach(addMeasurement); - } - - (dictionary.subsystems || []).forEach(addSubsystem); - - return models; - } - - // Begin generating models once the dictionary is available - modelPromise = adapter.dictionary().then(buildTaxonomy); - - return { - getModels: function (ids) { - // Return models for the dictionary only when they - // are relevant to the request. - return ids.some(isRelevant) ? modelPromise : empty; - } - }; - } - - return ExampleTelemetryModelProvider; - } -); -``` -__tutorials/telemetry/src/ExampleTelemetryModelProvider.js__ - -This script implements a `provider` for `modelService`; the `modelService` is a -composite service, meaning that multiple such services can exist side by side. -(For example, there is another `provider` for `modelService` that reads domain -object models from the persistence store.) - -Here, we read the dictionary using the server adapter from above; since this -will be loaded asynchronously, we use promise-chaining (see -https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then#Chaining ) -to take that result and build up an object mapping identifiers to new domain -object models. This is returned from our `modelService`, but only when the -request actually calls for identifiers that look like they're from the -dictionary. This means that loading other models is not blocked by loading the -dictionary. (Note that the `modelService` contract allows us to return either a -sub- or superset of the requested models, so it is fine to always return the -whole dictionary.) - -Some notable points to call out here: - -* Every subsystem and every measurement from the dictionary has an `identifier` -field declared. We use this as part of the domain object identifier, but we -also prefix it with `example_tlm`:. This accomplishes a few things: - * We can easily tell whether an identifier is expected to be in the - dictionary or not. - * We avoid naming collisions with other model providers. - * Finally, Open MCT uses the colon prefix as a hint that this domain - object will not be in the persistence store. -* A couple of new types are introduced here (in the `type` field of the domain -object models we create); we will need to define these as extensions as well in -order for them to display correctly. -* The `composition` field of each subsystem contained the Open MCT -identifiers of all the measurements in that subsystem. This `composition` field -will be used by Open MCT to determine what domain objects contain other -domain objects (e.g. to populate the tree.) -* The `telemetry` field of each measurement will be used by Open MCT to -understand how to request and interpret telemetry data for this object. The -`key` is the machine-readable identifier for this measurement within the -telemetry system; the `ranges` provide metadata about the values for this data. -(A separate field, `domains`, provides metadata about timestamps or other -ordering properties of the data, but this will be the same for all -measurements, so we will define that later at the type level.) - * This field (whose contents will be merged atop the telemetry property we -define at the type-level) will serve as a template for later `telemetry` -requests to the `telemetryService`, so we'll see the properties we define here -again later in Steps 3 and 4. - -This allows our telemetry dictionary to be expressed as domain object models -(and, in turn, as domain objects), but these objects still aren't reachable. To -fix this, we will need another script which will add these subsystems to the -root-level object we added in Step 1. - -```js -/*global define*/ - -define( - function () { - "use strict"; - - var TAXONOMY_ID = "example:sc", - PREFIX = "example_tlm:"; - - function ExampleTelemetryInitializer(adapter, objectService) { - // Generate a domain object identifier for a dictionary element - function makeId(element) { - return PREFIX + element.identifier; - } - - // When the dictionary is available, add all subsystems - // to the composition of My Spacecraft - function initializeTaxonomy(dictionary) { - // Get the top-level container for dictionary objects - // from a group of domain objects. - function getTaxonomyObject(domainObjects) { - return domainObjects[TAXONOMY_ID]; - } - - // Populate - function populateModel(taxonomyObject) { - return taxonomyObject.useCapability( - "mutation", - function (model) { - model.name = - dictionary.name; - model.composition = - dictionary.subsystems.map(makeId); - } - ); - } - - // Look up My Spacecraft, and populate it accordingly. - objectService.getObjects([TAXONOMY_ID]) - .then(getTaxonomyObject) - .then(populateModel); - } - - adapter.dictionary().then(initializeTaxonomy); - } - - return ExampleTelemetryInitializer; - } -); -``` -__tutorials/telemetry/src/ExampleTelemetryInitializer.js__ - -At the conclusion of Step 1, the top-level My Spacecraft object was empty. This -script will wait for the dictionary to be loaded, then load My Spacecraft (by -its identifier), and "mutate" it. The `mutation` capability allows changes to be -made to a domain object's model. Here, we take this top-level object, update its -name to match what was in the dictionary, and set its `composition` to an array -of domain object identifiers for all subsystems contained in the dictionary -(using the same identifier prefix as before.) - -Finally, we wire in these changes by modifying our plugin's `bundle.js` to -provide metadata about how these pieces interact (both with each other, and -with the platform): - -```js -define([ - 'openmct', -+ './src/ExampleTelemetryServerAdapter', -+ './src/ExampleTelemetryInitializer', -+ './src/ExampleTelemetryModelProvider' -], function ( - openmct, -+ ExampleTelemetryServerAdapter, -+ ExampleTelemetryInitializer, -+ ExampleTelemetryModelProvider -) { - openmct.legacyRegistry.register("tutorials/telemetry", { - "name": "Example Telemetry Adapter", - "extensions": { - "types": [ - { - "name": "Spacecraft", - "key": "example.spacecraft", - "cssClass": "icon-object" - }, -+ { -+ "name": "Subsystem", -+ "key": "example.subsystem", -+ "cssClass": "icon-object", -+ "model": { "composition": [] } -+ }, -+ { -+ "name": "Measurement", -+ "key": "example.measurement", -+ "cssClass": "icon-telemetry", -+ "model": { "telemetry": {} }, -+ "telemetry": { -+ "source": "example.source", -+ "domains": [ -+ { -+ "name": "Time", -+ "key": "timestamp" -+ } -+ ] -+ } -+ } - ], - "roots": [ - { - "id": "example:sc", - "priority": "preferred", - } - ], - "models": [ - { - "id": "example:sc", - "model": { - "type": "example.spacecraft", - "name": "My Spacecraft", - "location": "ROOT", - "composition": [] - } - } - ], -+ "services": [ -+ { -+ "key": "example.adapter", -+ "implementation": ExampleTelemetryServerAdapter, -+ "depends": [ "$q", "EXAMPLE_WS_URL" ] -+ } -+ ], -+ "constants": [ -+ { -+ "key": "EXAMPLE_WS_URL", -+ "priority": "fallback", -+ "value": "ws://localhost:8081" -+ } -+ ], -+ "runs": [ -+ { -+ "implementation": ExampleTelemetryInitializer, -+ "depends": [ "example.adapter", "objectService" ] -+ } -+ ], -+ "components": [ -+ { -+ "provides": "modelService", -+ "type": "provider", -+ "implementation": ExampleTelemetryModelProvider, -+ "depends": [ "example.adapter", "$q" ] -+ } -+ ] - } - }); -}); -``` -__tutorials/telemetry/bundle.js__ - -A summary of what we've added here: - -* New type definitions have been added to represent Subsystems and Measurements, -respectively. - * Measurements have a `telemetry` field; this is similar to the `telemetry` - field added in the model, but contains properties that will be common among - all Measurements. In particular, the `source` field will be used later as a - symbolic identifier for the telemetry data source. - * We have also added some "initial models" for these two types using the - `model` field. While domain objects of these types cannot be created via the - Create menu, some policies will look at initial models to predict what - capabilities domain objects of certain types would have, so we want to - ensure that Subsystems and Measurements will be recognized as having - `composition` and `telemetry` capabilities, respectively. -* The adapter to the WebSocket server has been added as a service with the -symbolic name `example.adapter`; it is depended-upon elsewhere within this -plugin. -* A constant, `EXAMPLE_WS_URL`, is defined, and depended-upon by -`example.server`. Setting `priority` to `fallback` means this constant will be -overridden if defined anywhere else, allowing configuration bundles to specify -different URLs for the WebSocket connection. -* The initializer script is registered using the `runs` category of extension, -to ensure that this executes (and populates the contents of the top-level My -Spacecraft object) once Open MCT is started. - * This depends upon the `example.adapter` service we exposed above, as well - as Angular's `$q`; these services will be made available in the constructor - call. -* Finally, the `modelService` provider which presents dictionary elements as -domain object models is exposed. Since `modelService` is a composite service, -this is registered under the extension category `components`. - * As with the initializer, this depends upon the `example.adapter` service - we exposed above, as well as Angular's `$q`; these services will be made - available in the constructor call. - -Now if we run Open MCT (assuming our example telemetry server is also -running) and expand our top-level node completely, we see the contents of our -dictionary: - -![Telemetry 2](images/telemetry-2.png) - - -Note that "My Spacecraft" has changed its name to "Example Spacecraft", which -is the name it had in the dictionary. - -### Step 3-Historical Telemetry - -After Step 2, we are able to see our dictionary in the user interface and click -around our different measurements, but we don't see any data. We need to give -ourselves the ability to retrieve this data from the server. In this step, we -will do so for the server's historical telemetry. - -Our first step will be to add a method to our server adapter which allows us to -send history requests to the server: - -```js -/*global define,WebSocket*/ - -define( - [], - function () { - "use strict"; - - function ExampleTelemetryServerAdapter($q, wsUrl) { - var ws = new WebSocket(wsUrl), -+ histories = {}, - dictionary = $q.defer(); - - // Handle an incoming message from the server - ws.onmessage = function (event) { - var message = JSON.parse(event.data); - - switch (message.type) { - case "dictionary": - dictionary.resolve(message.value); - break; -+ case "history": -+ histories[message.id].resolve(message); -+ delete histories[message.id]; -+ break; - } - }; - - // Request dictionary once connection is established - ws.onopen = function () { - ws.send("dictionary"); - }; - - return { - dictionary: function () { - return dictionary.promise; - }, -+ history: function (id) { -+ histories[id] = histories[id] || $q.defer(); -+ ws.send("history " + id); -+ return histories[id].promise; -+ } - }; - } - - return ExampleTelemetryServerAdapter; - } -); -``` -__tutorials/telemetry/src/ExampleTelemetryServerAdapter.js__ - -When the `history` method is called, a new request is issued to the server for -historical telemetry, _unless_ a request for the same historical telemetry is -still pending. Similarly, when historical telemetry arrives for a given -identifier, the pending promise is resolved. - -This `history` method will be used by a `telemetryService` provider which we -will implement: - -```js -/*global define*/ - -define( - ['./ExampleTelemetrySeries'], - function (ExampleTelemetrySeries) { - "use strict"; - - var SOURCE = "example.source"; - - function ExampleTelemetryProvider(adapter, $q) { - // Used to filter out requests for telemetry - // from some other source - function matchesSource(request) { - return (request.source === SOURCE); - } - - return { - requestTelemetry: function (requests) { - var packaged = {}, - relevantReqs = requests.filter(matchesSource); - - // Package historical telemetry that has been received - function addToPackage(history) { - packaged[SOURCE][history.id] = - new ExampleTelemetrySeries(history.value); - } - - // Retrieve telemetry for a specific measurement - function handleRequest(request) { - var key = request.key; - return adapter.history(key).then(addToPackage); - } - - packaged[SOURCE] = {}; - return $q.all(relevantReqs.map(handleRequest)) - .then(function () { return packaged; }); - }, - subscribe: function (callback, requests) { - return function () {}; - } - }; - } - - return ExampleTelemetryProvider; - } -); -``` -__tutorials/telemetry/src/ExampleTelemetryProvider.js__ - -The `requestTelemetry` method of a `telemetryService` is expected to take an -array of requests (each with `source` and `key` parameters, identifying the -general source of data and the specific element within that source, respectively) and -return a Promise for any telemetry data it knows of which satisfies those -requests, packaged in a specific way. This packaging is as an object containing -key-value pairs, where keys correspond to `source` properties of requests and -values are key-value pairs, where keys correspond to `key` properties of requests -and values are `TelemetrySeries` objects. (We will see our implementation -below.) - -To do this, we create a container for our telemetry source, and consult the -adapter to get telemetry histories for any relevant requests, then package -them as they come in. The `$q.all` method is used to return a single Promise -that will resolve only when all histories have been packaged. Promise-chaining -is used to ensure that the resolved value will be the fully-packaged data. - -It is worth mentioning here that the `requests` we receive should look a little -familiar. When Open MCT generates a `request` object associated with a -domain object, it does so by merging together three JavaScript objects: - -* First, the `telemetry` property from that domain object's type definition. -* Second, the `telemetry` property from that domain object's model. -* Finally, the `request` object that was passed in via that domain object's -`telemetry` capability. - -As such, the `source` and `key` properties we observe here will come from the -type definition and domain object model, respectively, as we specified them -during Step 2. (Or, they might come from somewhere else entirely, if we have -other telemetry-providing domain objects in our system; that is something we -check for using the `source` property.) - -Finally, note that we also have a `subscribe` method, to satisfy the interface of -`telemetryService`, but this `subscribe` method currently does nothing. - -This script uses an `ExampleTelemetrySeries` class, which looks like: - -```js -/*global define*/ - -define( - function () { - "use strict"; - - function ExampleTelemetrySeries(data) { - return { - getPointCount: function () { - return data.length; - }, - getDomainValue: function (index) { - return (data[index] || {}).timestamp; - }, - getRangeValue: function (index) { - return (data[index] || {}).value; - } - }; - } - - return ExampleTelemetrySeries; - } -); -``` -__tutorials/telemetry/src/ExampleTelemetrySeries.js__ - -This takes the array of telemetry values (as returned by the server) and wraps -it with the interface expected by the platform (the methods shown.) - -Finally, we expose this `telemetryService` provider declaratively: - -```js -define([ - 'openmct', - './src/ExampleTelemetryServerAdapter', - './src/ExampleTelemetryInitializer', - './src/ExampleTelemetryModelProvider' -], function ( - openmct, - ExampleTelemetryServerAdapter, - ExampleTelemetryInitializer, - ExampleTelemetryModelProvider -) { - openmct.legacyRegistry.register("tutorials/telemetry", { - "name": "Example Telemetry Adapter", - "extensions": { - "types": [ - { - "name": "Spacecraft", - "key": "example.spacecraft", - "cssClass": "icon-object" - }, - { - "name": "Subsystem", - "key": "example.subsystem", - "cssClass": "icon-object", - "model": { "composition": [] } - }, - { - "name": "Measurement", - "key": "example.measurement", - "cssClass": "icon-telemetry", - "model": { "telemetry": {} }, - "telemetry": { - "source": "example.source", - "domains": [ - { - "name": "Time", - "key": "timestamp" - } - ] - } - } - ], - "roots": [ - { - "id": "example:sc", - "priority": "preferred" - } - ], - "models": [ - { - "id": "example:sc", - "model": { - "type": "example.spacecraft", - "name": "My Spacecraft", - "location": "ROOT", - "composition": [] - } - } - ], - "services": [ - { - "key": "example.adapter", - "implementation": "ExampleTelemetryServerAdapter.js", - "depends": [ "$q", "EXAMPLE_WS_URL" ] - } - ], - "constants": [ - { - "key": "EXAMPLE_WS_URL", - "priority": "fallback", - "value": "ws://localhost:8081" - } - ], - "runs": [ - { - "implementation": "ExampleTelemetryInitializer.js", - "depends": [ "example.adapter", "objectService" ] - } - ], - "components": [ - { - "provides": "modelService", - "type": "provider", - "implementation": "ExampleTelemetryModelProvider.js", - "depends": [ "example.adapter", "$q" ] - }, -+ { -+ "provides": "telemetryService", -+ "type": "provider", -+ "implementation": "ExampleTelemetryProvider.js", -+ "depends": [ "example.adapter", "$q" ] -+ } - ] - } - }); -}); -``` -__tutorials/telemetry/bundle.js__ - -Now, if we navigate to one of our numeric measurements, we should see a plot of -its historical telemetry: - -![Telemetry](images/telemetry-3.png) - -We can now visualize our data, but it doesn't update over time - we know the -server is continually producing new data, but we have to click away and come -back to see it. We can fix this by adding support for telemetry subscriptions. - -### Step 4-Real-time Telemetry - -Finally, we want to utilize the server's ability to subscribe to telemetry -from Open MCT. To do this, first we want to expose some new methods for -this from our server adapter: - -```js -/*global define,WebSocket*/ - -define( - [], - function () { - "use strict"; - - function ExampleTelemetryServerAdapter($q, wsUrl) { - var ws = new WebSocket(wsUrl), - histories = {}, -+ listeners = [], - dictionary = $q.defer(); - - // Handle an incoming message from the server - ws.onmessage = function (event) { - var message = JSON.parse(event.data); - - switch (message.type) { - case "dictionary": - dictionary.resolve(message.value); - break; - case "history": - histories[message.id].resolve(message); - delete histories[message.id]; - break; -+ case "data": -+ listeners.forEach(function (listener) { -+ listener(message); -+ }); -+ break; - } - }; - - // Request dictionary once connection is established - ws.onopen = function () { - ws.send("dictionary"); - }; - - return { - dictionary: function () { - return dictionary.promise; - }, - history: function (id) { - histories[id] = histories[id] || $q.defer(); - ws.send("history " + id); - return histories[id].promise; - }, -+ subscribe: function (id) { -+ ws.send("subscribe " + id); -+ }, -+ unsubscribe: function (id) { -+ ws.send("unsubscribe " + id); -+ }, -+ listen: function (callback) { -+ listeners.push(callback); -+ } - }; - } - - return ExampleTelemetryServerAdapter; - } -); -``` -__tutorials/telemetry/src/ExampleTelemetryServerAdapter.js__ - -Here, we have added `subscribe` and `unsubscribe` methods which issue the -corresponding requests to the server. Separately, we introduce the ability to -listen for `data` messages as they come in: These will contain the data associated -with these subscriptions. - -We then need only to utilize these methods from our `telemetryService`: - -```js -/*global define*/ - -define( - ['./ExampleTelemetrySeries'], - function (ExampleTelemetrySeries) { - "use strict"; - - var SOURCE = "example.source"; - - function ExampleTelemetryProvider(adapter, $q) { -+ var subscribers = {}; - - // Used to filter out requests for telemetry - // from some other source - function matchesSource(request) { - return (request.source === SOURCE); - } - -+ // Listen for data, notify subscribers -+ adapter.listen(function (message) { -+ var packaged = {}; -+ packaged[SOURCE] = {}; -+ packaged[SOURCE][message.id] = -+ new ExampleTelemetrySeries([message.value]); -+ (subscribers[message.id] || []).forEach(function (cb) { -+ cb(packaged); -+ }); -+ }); - - return { - requestTelemetry: function (requests) { - var packaged = {}, - relevantReqs = requests.filter(matchesSource); - - // Package historical telemetry that has been received - function addToPackage(history) { - packaged[SOURCE][history.id] = - new ExampleTelemetrySeries(history.value); - } - - // Retrieve telemetry for a specific measurement - function handleRequest(request) { - var key = request.key; - return adapter.history(key).then(addToPackage); - } - - packaged[SOURCE] = {}; - return $q.all(relevantReqs.map(handleRequest)) - .then(function () { return packaged; }); - }, - subscribe: function (callback, requests) { -+ var keys = requests.filter(matchesSource) -+ .map(function (req) { return req.key; }); -+ -+ function notCallback(cb) { -+ return cb !== callback; -+ } -+ -+ function unsubscribe(key) { -+ subscribers[key] = -+ (subscribers[key] || []).filter(notCallback); -+ if (subscribers[key].length < 1) { -+ adapter.unsubscribe(key); -+ } -+ } -+ -+ keys.forEach(function (key) { -+ subscribers[key] = subscribers[key] || []; -+ adapter.subscribe(key); -+ subscribers[key].push(callback); -+ }); -+ -+ return function () { -+ keys.forEach(unsubscribe); -+ }; - } - }; - } - - return ExampleTelemetryProvider; - } -); -``` -__tutorials/telemetry/src/ExampleTelemetryProvider.js__ - -A quick summary of these changes: - -* First, we maintain current subscribers (callbacks) in an object containing -key-value pairs, where keys are request key properties, and values are callback -arrays. -* We listen to new data coming in from the server adapter, and invoke any -relevant callbacks when this happens. We package the data in the same manner -that historical telemetry is packaged (even though in this case we are -providing single-element series objects.) -* Finally, in our `subscribe` method we add callbacks to the lists of active -subscribers. This method is expected to return a function which terminates the -subscription when called, so we do some work to remove subscribers in this -situations. When our subscriber count for a given measurement drops to zero, -we issue an unsubscribe request. (We don't take any care to avoid issuing -multiple subscribe requests to the server, because we happen to know that the -server can handle this.) - -Running Open MCT again, we can still plot our historical telemetry - but -now we also see that it updates in real-time as more data comes in from the -server. - diff --git a/index.html b/index.html index f73024f69e..095e80b594 100644 --- a/index.html +++ b/index.html @@ -38,11 +38,12 @@ const THIRTY_MINUTES = 30 * 60 * 1000; [ - 'example/eventGenerator', - 'example/styleguide' + 'example/eventGenerator' ].forEach( openmct.legacyRegistry.enable.bind(openmct.legacyRegistry) ); + + openmct.install(openmct.plugins.Espresso()); openmct.install(openmct.plugins.MyItems()); openmct.install(openmct.plugins.LocalStorage()); openmct.install(openmct.plugins.Generator()); diff --git a/package.json b/package.json index 2c50d0dafa..50a8d53cd9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openmct", - "version": "1.0.0-beta", + "version": "1.0.0-snapshot", "description": "The Open MCT core platform", "dependencies": {}, "devDependencies": { @@ -11,6 +11,7 @@ "comma-separated-values": "^3.6.4", "concurrently": "^3.6.1", "copy-webpack-plugin": "^4.5.2", + "cross-env": "^6.0.3", "css-loader": "^1.0.0", "d3-array": "1.2.x", "d3-axis": "1.0.x", @@ -62,7 +63,7 @@ "raw-loader": "^0.5.1", "request": "^2.69.0", "split": "^1.0.0", - "style-loader": "^0.21.0", + "style-loader": "^1.0.1", "v8-compile-cache": "^1.1.0", "vue": "2.5.6", "vue-loader": "^15.2.6", @@ -77,11 +78,11 @@ "start": "node app.js", "lint": "eslint platform example src/**/*.{js,vue} openmct.js", "lint:fix": "eslint platform example src/**/*.{js,vue} openmct.js --fix", - "build:prod": "NODE_ENV=production webpack", + "build:prod": "cross-env NODE_ENV=production webpack", "build:dev": "webpack", "build:watch": "webpack --watch", "test": "karma start --single-run", - "test-debug": "NODE_ENV=debug karma start --no-single-run", + "test-debug": "cross-env NODE_ENV=debug karma start --no-single-run", "test:watch": "karma start --no-single-run", "verify": "concurrently 'npm:test' 'npm:lint'", "jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api", diff --git a/platform/core/src/actions/ActionCapability.js b/platform/core/src/actions/ActionCapability.js index 48cfa0bdf8..3fa38e3820 100644 --- a/platform/core/src/actions/ActionCapability.js +++ b/platform/core/src/actions/ActionCapability.js @@ -102,14 +102,14 @@ define( * @returns {Action[]} an array of matching actions * @memberof platform/core.ActionCapability# */ - ActionCapability.prototype.perform = function (context) { + ActionCapability.prototype.perform = function (context, flag) { // Alias to getActions(context)[0].perform, with a // check for empty arrays. var actions = this.getActions(context); return this.$q.when( (actions && actions.length > 0) ? - actions[0].perform() : + actions[0].perform(flag) : undefined ); }; diff --git a/platform/entanglement/src/services/MoveService.js b/platform/entanglement/src/services/MoveService.js index 32c597bebf..1f9ac18b04 100644 --- a/platform/entanglement/src/services/MoveService.js +++ b/platform/entanglement/src/services/MoveService.js @@ -91,7 +91,7 @@ define( .then(function () { return object .getCapability('action') - .perform('remove'); + .perform('remove', true); }); }; diff --git a/platform/entanglement/test/services/MoveServiceSpec.js b/platform/entanglement/test/services/MoveServiceSpec.js index 43255a44f5..a9ae1b0848 100644 --- a/platform/entanglement/test/services/MoveServiceSpec.js +++ b/platform/entanglement/test/services/MoveServiceSpec.js @@ -227,10 +227,11 @@ define( locationPromise.resolve(); }); - it("removes object from parent", function () { + it("removes object from parent without user warning dialog", function () { expect(actionCapability.perform) - .toHaveBeenCalledWith('remove'); + .toHaveBeenCalledWith('remove', true); }); + }); }); @@ -247,9 +248,9 @@ define( .toHaveBeenCalled(); }); - it("removes object from parent", function () { + it("removes object from parent without user warning dialog", function () { expect(actionCapability.perform) - .toHaveBeenCalledWith('remove'); + .toHaveBeenCalledWith('remove', true); }); }); diff --git a/platform/features/imagery/bundle.js b/platform/features/imagery/bundle.js deleted file mode 100644 index ce0745519f..0000000000 --- a/platform/features/imagery/bundle.js +++ /dev/null @@ -1,86 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/policies/ImageryViewPolicy", - "./src/controllers/ImageryController", - "./src/directives/MCTBackgroundImage", - "./res/templates/imagery.html" -], function ( - ImageryViewPolicy, - ImageryController, - MCTBackgroundImage, - imageryTemplate -) { - - return { - name:"platform/features/imagery", - definition: { - "name": "Plot view for telemetry", - "extensions": { - "views": [ - { - "name": "Imagery", - "key": "imagery", - "cssClass": "icon-image", - "template": imageryTemplate, - "priority": "preferred", - "needs": [ - "telemetry" - ], - "editable": false - } - ], - "policies": [ - { - "category": "view", - "implementation": ImageryViewPolicy, - "depends": [ - "openmct" - ] - } - ], - "controllers": [ - { - "key": "ImageryController", - "implementation": ImageryController, - "depends": [ - "$scope", - "$window", - "$element", - "openmct" - ] - } - ], - "directives": [ - { - "key": "mctBackgroundImage", - "implementation": MCTBackgroundImage, - "depends": [ - "$document" - ] - } - ] - } - } - }; -}); diff --git a/platform/features/imagery/res/templates/imagery.html b/platform/features/imagery/res/templates/imagery.html deleted file mode 100644 index 8aedff7163..0000000000 --- a/platform/features/imagery/res/templates/imagery.html +++ /dev/null @@ -1,58 +0,0 @@ -
- -
-
- - - - - - - -
- -
-
-
-
- -
-
- - {{imagery.getTime()}} -
-
- - -
-
-
- -
-
- -
{{imagery.getTime(image)}}
-
-
-
-
diff --git a/platform/features/imagery/src/controllers/ImageryController.js b/platform/features/imagery/src/controllers/ImageryController.js deleted file mode 100644 index ef69903304..0000000000 --- a/platform/features/imagery/src/controllers/ImageryController.js +++ /dev/null @@ -1,284 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle implements views of image telemetry. - * @namespace platform/features/imagery - */ - -define( - [ - 'zepto', - 'lodash' - ], - function ($, _) { - - /** - * Controller for the "Imagery" view of a domain object which - * provides image telemetry. - * @constructor - * @memberof platform/features/imagery - */ - - function ImageryController($scope, $window, element, openmct) { - this.$scope = $scope; - this.$window = $window; - this.openmct = openmct; - this.date = ""; - this.time = ""; - this.zone = ""; - this.imageUrl = ""; - this.requestCount = 0; - this.scrollable = $(".l-image-thumbs-wrapper"); - this.autoScroll = openmct.time.clock() ? true : false; - this.$scope.imageHistory = []; - this.$scope.filters = { - brightness: 100, - contrast: 100 - }; - - this.subscribe = this.subscribe.bind(this); - this.stopListening = this.stopListening.bind(this); - this.updateValues = this.updateValues.bind(this); - this.updateHistory = this.updateHistory.bind(this); - this.onBoundsChange = this.onBoundsChange.bind(this); - this.onScroll = this.onScroll.bind(this); - this.setSelectedImage = this.setSelectedImage.bind(this); - - this.subscribe(this.$scope.domainObject); - - this.$scope.$on('$destroy', this.stopListening); - this.openmct.time.on('bounds', this.onBoundsChange); - this.scrollable.on('scroll', this.onScroll); - } - - ImageryController.prototype.subscribe = function (domainObject) { - this.date = ""; - this.imageUrl = ""; - this.openmct.objects.get(domainObject.getId()) - .then(function (object) { - this.domainObject = object; - var metadata = this.openmct - .telemetry - .getMetadata(this.domainObject); - this.timeKey = this.openmct.time.timeSystem().key; - this.timeFormat = this.openmct - .telemetry - .getValueFormatter(metadata.value(this.timeKey)); - this.imageFormat = this.openmct - .telemetry - .getValueFormatter(metadata.valuesForHints(['image'])[0]); - this.unsubscribe = this.openmct.telemetry - .subscribe(this.domainObject, function (datum) { - this.updateHistory(datum); - this.updateValues(datum); - }.bind(this)); - - this.requestHistory(this.openmct.time.bounds()); - }.bind(this)); - }; - - ImageryController.prototype.requestHistory = function (bounds) { - this.requestCount++; - this.$scope.imageHistory = []; - var requestId = this.requestCount; - this.openmct.telemetry - .request(this.domainObject, bounds) - .then(function (values) { - if (this.requestCount > requestId) { - return Promise.resolve('Stale request'); - } - - values.forEach(function (datum) { - this.updateHistory(datum); - }, this); - - this.updateValues(values[values.length - 1]); - }.bind(this)); - }; - - ImageryController.prototype.stopListening = function () { - this.openmct.time.off('bounds', this.onBoundsChange); - this.scrollable.off('scroll', this.onScroll); - if (this.unsubscribe) { - this.unsubscribe(); - delete this.unsubscribe; - } - }; - - /** - * Responds to bound change event be requesting new - * historical data if the bound change was manual. - * @private - * @param {object} [newBounds] new bounds object - * @param {boolean} [tick] true when change is automatic - */ - ImageryController.prototype.onBoundsChange = function (newBounds, tick) { - if (this.domainObject && !tick) { - this.requestHistory(newBounds); - } - }; - - /** - * Updates displayable values to match those of the most - * recently received datum. - * @param {object} [datum] the datum - * @private - */ - ImageryController.prototype.updateValues = function (datum) { - if (this.isPaused) { - this.nextDatum = datum; - return; - } - this.time = this.timeFormat.format(datum); - this.imageUrl = this.imageFormat.format(datum); - - }; - - /** - * Appends given imagery datum to running history. - * @private - * @param {object} [datum] target telemetry datum - * @returns {boolean} falsy when a duplicate datum is given - */ - ImageryController.prototype.updateHistory = function (datum) { - if (!this.datumMatchesMostRecent(datum)) { - var index = _.sortedIndex(this.$scope.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat)); - this.$scope.imageHistory.splice(index, 0, datum); - return true; - } else { - return false; - } - }; - - /** - * Checks to see if the given datum is the same as the most recent in history. - * @private - * @param {object} [datum] target telemetry datum - * @returns {boolean} true if datum is most recent in history, false otherwise - */ - ImageryController.prototype.datumMatchesMostRecent = function (datum) { - if (this.$scope.imageHistory.length !== 0) { - var datumTime = this.timeFormat.format(datum); - var datumURL = this.imageFormat.format(datum); - var lastHistoryTime = this.timeFormat.format(this.$scope.imageHistory.slice(-1)[0]); - var lastHistoryURL = this.imageFormat.format(this.$scope.imageHistory.slice(-1)[0]); - - return datumTime === lastHistoryTime && datumURL === lastHistoryURL; - } - return false; - }; - - ImageryController.prototype.onScroll = function (event) { - this.$window.requestAnimationFrame(function () { - var thumbnailWrapperHeight = this.scrollable[0].offsetHeight; - var thumbnailWrapperWidth = this.scrollable[0].offsetWidth; - if (this.scrollable[0].scrollLeft < - (this.scrollable[0].scrollWidth - this.scrollable[0].clientWidth) - (thumbnailWrapperWidth) || - this.scrollable[0].scrollTop < - (this.scrollable[0].scrollHeight - this.scrollable[0].clientHeight) - (thumbnailWrapperHeight)) { - this.autoScroll = false; - } else { - this.autoScroll = true; - } - }.bind(this)); - }; - - /** - * Force history imagery div to scroll to bottom. - */ - ImageryController.prototype.scrollToBottom = function () { - if (this.autoScroll) { - this.scrollable[0].scrollTop = this.scrollable[0].scrollHeight; - } - }; - - - /** - * Get the time portion (hours, minutes, seconds) of the - * timestamp associated with the incoming image telemetry - * if no parameter is given, or of a provided datum. - * @param {object} [datum] target telemetry datum - * @returns {string} the time - */ - ImageryController.prototype.getTime = function (datum) { - return datum ? - this.timeFormat.format(datum) : - this.time; - }; - - /** - * Get the URL of the most recent image telemetry if no - * parameter is given, or of a provided datum. - * @param {object} [datum] target telemetry datum - * @returns {string} URL for telemetry image - */ - ImageryController.prototype.getImageUrl = function (datum) { - return datum ? - this.imageFormat.format(datum) : - this.imageUrl; - }; - - /** - * Getter-setter for paused state of the view (true means - * paused, false means not.) - * @param {boolean} [state] the state to set - * @returns {boolean} the current state - */ - ImageryController.prototype.paused = function (state) { - if (arguments.length > 0 && state !== this.isPaused) { - this.unselectAllImages(); - this.isPaused = state; - if (this.nextDatum) { - this.updateValues(this.nextDatum); - delete this.nextDatum; - } else { - this.updateValues(this.$scope.imageHistory[this.$scope.imageHistory.length - 1]); - } - this.autoScroll = true; - } - return this.isPaused; - }; - - /** - * Set the selected image on the state for the large imagery div to use. - * @param {object} [image] the image object to get url from. - */ - ImageryController.prototype.setSelectedImage = function (image) { - this.imageUrl = this.getImageUrl(image); - this.time = this.getTime(image); - this.paused(true); - this.unselectAllImages(); - image.selected = true; - }; - - /** - * Loop through the history imagery data to set all images to unselected. - */ - ImageryController.prototype.unselectAllImages = function () { - for (var i = 0; i < this.$scope.imageHistory.length; i++) { - this.$scope.imageHistory[i].selected = false; - } - }; - return ImageryController; - } -); diff --git a/platform/features/imagery/src/directives/MCTBackgroundImage.js b/platform/features/imagery/src/directives/MCTBackgroundImage.js deleted file mode 100644 index ddae082fa0..0000000000 --- a/platform/features/imagery/src/directives/MCTBackgroundImage.js +++ /dev/null @@ -1,110 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * Defines the `mct-background-image` directive. - * - * Used as an attribute, this will set the `background-image` - * property to the URL given in its value, but only after that - * image has loaded; this avoids "flashing" as images change. - * - * If the value of `mct-background-image`is falsy, no image - * will be displayed (immediately.) - * - * Optionally, a `filters` attribute may be specified as an - * object with `brightness` and/or `contrast` properties, - * whose values are percentages. A value of 100 will make - * no changes to the image's brightness or contrast. - * - * @constructor - * @memberof platform/features/imagery - */ - function MCTBackgroundImage($document) { - function link(scope, element) { - // General strategy here: - // - Keep count of how many images have been requested; this - // counter will be used as an internal identifier or sorts - // for each image that loads. - // - As the src attribute changes, begin loading those images. - // - When images do load, update the background-image property - // of the element, but only if a more recently - // requested image has not already been loaded. - // The order in which URLs are passed in and the order - // in which images are actually loaded may be different, so - // some strategy like this is necessary to ensure that images - // do not display out-of-order. - var requested = 0, loaded = 0; - - function updateFilters(filters) { - var styleValue = filters ? - Object.keys(filters).map(function (k) { - return k + "(" + filters[k] + "%)"; - }).join(' ') : - ""; - element.css('filter', styleValue); - element.css('webkitFilter', styleValue); - } - - function nextImage(url) { - var myCounter = requested, - image; - - function useImage() { - if (loaded <= myCounter) { - loaded = myCounter; - element.css('background-image', "url('" + url + "')"); - } - } - - if (!url) { - loaded = myCounter; - element.css('background-image', 'none'); - } else { - image = $document[0].createElement('img'); - image.src = url; - image.onload = useImage; - } - - requested += 1; - } - - scope.$watch('mctBackgroundImage', nextImage); - scope.$watchCollection('filters', updateFilters); - } - - return { - restrict: "A", - scope: { - mctBackgroundImage: "=", - filters: "=" - }, - link: link - }; - } - - return MCTBackgroundImage; - } -); - diff --git a/platform/features/imagery/src/policies/ImageryViewPolicy.js b/platform/features/imagery/src/policies/ImageryViewPolicy.js deleted file mode 100644 index 43421e4da4..0000000000 --- a/platform/features/imagery/src/policies/ImageryViewPolicy.js +++ /dev/null @@ -1,59 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - '../../../../../src/api/objects/object-utils' -], function ( - objectUtils -) { - /** - * Policy preventing the Imagery view from being made available for - * domain objects which do not have associated image telemetry. - * @implements {Policy.} - * @constructor - */ - function ImageryViewPolicy(openmct) { - this.openmct = openmct; - } - - ImageryViewPolicy.prototype.hasImageTelemetry = function (domainObject) { - var newDO = objectUtils.toNewFormat( - domainObject.getModel(), - domainObject.getId() - ); - - var metadata = this.openmct.telemetry.getMetadata(newDO); - var values = metadata.valuesForHints(['image']); - return values.length >= 1; - }; - - ImageryViewPolicy.prototype.allow = function (view, domainObject) { - if (view.key === 'imagery' || view.key === 'historical-imagery') { - return this.hasImageTelemetry(domainObject); - } - - return true; - }; - - return ImageryViewPolicy; -}); - diff --git a/platform/features/imagery/test/controllers/ImageryControllerSpec.js b/platform/features/imagery/test/controllers/ImageryControllerSpec.js deleted file mode 100644 index 7edc1c6710..0000000000 --- a/platform/features/imagery/test/controllers/ImageryControllerSpec.js +++ /dev/null @@ -1,271 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - "zepto", - "../../src/controllers/ImageryController" - ], - function ($, ImageryController) { - - var MOCK_ELEMENT_TEMPLATE = - '
'; - - xdescribe("The Imagery controller", function () { - var $scope, - openmct, - oldDomainObject, - newDomainObject, - unsubscribe, - metadata, - prefix, - controller, - requestPromise, - mockWindow, - mockElement; - - beforeEach(function () { - $scope = jasmine.createSpyObj('$scope', ['$on', '$watch']); - oldDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId'] - ); - newDomainObject = { name: 'foo' }; - oldDomainObject.getId.and.returnValue('testID'); - openmct = { - objects: jasmine.createSpyObj('objectAPI', [ - 'get' - ]), - time: jasmine.createSpyObj('timeAPI', [ - 'timeSystem', - 'clock', - 'on', - 'off', - 'bounds' - ]), - telemetry: jasmine.createSpyObj('telemetryAPI', [ - 'subscribe', - 'request', - 'getValueFormatter', - 'getMetadata' - ]) - }; - metadata = jasmine.createSpyObj('metadata', [ - 'value', - 'valuesForHints' - ]); - metadata.value.and.returnValue("timestamp"); - metadata.valuesForHints.and.returnValue(["value"]); - - prefix = "formatted "; - unsubscribe = jasmine.createSpy('unsubscribe'); - openmct.telemetry.subscribe.and.returnValue(unsubscribe); - openmct.time.timeSystem.and.returnValue({ - key: 'testKey' - }); - $scope.domainObject = oldDomainObject; - openmct.objects.get.and.returnValue(Promise.resolve(newDomainObject)); - openmct.telemetry.getMetadata.and.returnValue(metadata); - openmct.telemetry.getValueFormatter.and.callFake(function (property) { - var formatter = - jasmine.createSpyObj("formatter-" + property, ['format']); - var isTime = (property === "timestamp"); - formatter.format.and.callFake(function (datum) { - return (isTime ? prefix : "") + datum[property]; - }); - return formatter; - }); - - requestPromise = new Promise(function (resolve) { - setTimeout(function () { - resolve([{ - timestamp: 1434600258123, - value: 'some/url' - }]); - }, 10); - }); - - openmct.telemetry.request.and.returnValue(requestPromise); - mockElement = $(MOCK_ELEMENT_TEMPLATE); - mockWindow = jasmine.createSpyObj('$window', ['requestAnimationFrame']); - mockWindow.requestAnimationFrame.and.callFake(function (f) { - return f(); - }); - - controller = new ImageryController( - $scope, - mockWindow, - mockElement, - openmct - ); - }); - - describe("when loaded", function () { - var callback, - boundsListener, - bounds; - - beforeEach(function () { - return requestPromise.then(function () { - openmct.time.on.calls.all().forEach(function (call) { - if (call.args[0] === "bounds") { - boundsListener = call.args[1]; - } - }); - callback = - openmct.telemetry.subscribe.calls.mostRecent().args[1]; - }); - }); - - it("requests history", function () { - expect(openmct.telemetry.request).toHaveBeenCalledWith( - newDomainObject, bounds - ); - expect(controller.getTime()).toEqual(prefix + 1434600258123); - expect(controller.getImageUrl()).toEqual('some/url'); - }); - - - it("exposes the latest telemetry values", function () { - callback({ - timestamp: 1434600259456, - value: "some/other/url" - }); - - expect(controller.getTime()).toEqual(prefix + 1434600259456); - expect(controller.getImageUrl()).toEqual("some/other/url"); - }); - - it("allows updates to be paused and unpaused", function () { - var newTimestamp = 1434600259456, - newUrl = "some/other/url", - initialTimestamp = controller.getTime(), - initialUrl = controller.getImageUrl(); - - expect(initialTimestamp).not.toBe(prefix + newTimestamp); - expect(initialUrl).not.toBe(newUrl); - expect(controller.paused()).toBeFalsy(); - - controller.paused(true); - expect(controller.paused()).toBeTruthy(); - callback({ timestamp: newTimestamp, value: newUrl }); - - expect(controller.getTime()).toEqual(initialTimestamp); - expect(controller.getImageUrl()).toEqual(initialUrl); - - controller.paused(false); - expect(controller.paused()).toBeFalsy(); - expect(controller.getTime()).toEqual(prefix + newTimestamp); - expect(controller.getImageUrl()).toEqual(newUrl); - }); - - it("forwards large image view to latest image in history on un-pause", function () { - $scope.imageHistory = [ - { utc: 1434600258122, url: 'some/url1', selected: false}, - { utc: 1434600258123, url: 'some/url2', selected: false} - ]; - controller.paused(true); - controller.paused(false); - - expect(controller.getImageUrl()).toEqual(controller.getImageUrl($scope.imageHistory[1])); - }); - - it("subscribes to telemetry", function () { - expect(openmct.telemetry.subscribe).toHaveBeenCalledWith( - newDomainObject, - jasmine.any(Function) - ); - }); - - it("requests telemetry", function () { - expect(openmct.telemetry.request).toHaveBeenCalledWith( - newDomainObject, - bounds - ); - }); - - it("unsubscribes and unlistens when scope is destroyed", function () { - expect(unsubscribe).not.toHaveBeenCalled(); - - $scope.$on.calls.all().forEach(function (call) { - if (call.args[0] === '$destroy') { - call.args[1](); - } - }); - expect(unsubscribe).toHaveBeenCalled(); - expect(openmct.time.off) - .toHaveBeenCalledWith('bounds', jasmine.any(Function)); - }); - - it("listens for bounds event and responds to tick and manual change", function () { - var mockBounds = {start: 1434600000000, end: 1434600500000}; - expect(openmct.time.on).toHaveBeenCalled(); - openmct.telemetry.request.calls.reset(); - boundsListener(mockBounds, true); - expect(openmct.telemetry.request).not.toHaveBeenCalled(); - boundsListener(mockBounds, false); - expect(openmct.telemetry.request).toHaveBeenCalledWith(newDomainObject, mockBounds); - }); - - it ("doesnt append duplicate datum", function () { - var mockDatum = {value: 'image/url', timestamp: 1434700000000}; - var mockDatum2 = {value: 'image/url', timestamp: 1434700000000}; - var mockDatum3 = {value: 'image/url', url: 'someval', timestamp: 1434700000000}; - expect(controller.updateHistory(mockDatum)).toBe(true); - expect(controller.updateHistory(mockDatum)).toBe(false); - expect(controller.updateHistory(mockDatum)).toBe(false); - expect(controller.updateHistory(mockDatum2)).toBe(false); - expect(controller.updateHistory(mockDatum3)).toBe(false); - }); - - describe("when user clicks on imagery thumbnail", function () { - var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false}; - - it("pauses and adds selected class to imagery thumbnail", function () { - controller.setSelectedImage(mockDatum); - expect(controller.paused()).toBeTruthy(); - expect(mockDatum.selected).toBeTruthy(); - }); - - it("unselects previously selected image", function () { - $scope.imageHistory = [{ utc: 1434600258123, url: 'some/url', selected: true}]; - controller.unselectAllImages(); - expect($scope.imageHistory[0].selected).toBeFalsy(); - }); - - it("updates larger image url and time", function () { - controller.setSelectedImage(mockDatum); - expect(controller.getImageUrl()).toEqual(controller.getImageUrl(mockDatum)); - expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc)); - }); - }); - - }); - - it("initially shows an empty string for date/time", function () { - expect(controller.getTime()).toEqual(""); - expect(controller.getImageUrl()).toEqual(""); - }); - }); - } -); - diff --git a/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js b/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js deleted file mode 100644 index dd6182be54..0000000000 --- a/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js +++ /dev/null @@ -1,126 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/directives/MCTBackgroundImage"], - function (MCTBackgroundImage) { - - describe("The mct-background-image directive", function () { - var mockDocument, - mockScope, - mockElement, - testImage, - directive; - - beforeEach(function () { - mockDocument = [ - jasmine.createSpyObj('document', ['createElement']) - ]; - mockScope = jasmine.createSpyObj('scope', [ - '$watch', - '$watchCollection' - ]); - mockElement = jasmine.createSpyObj('element', ['css']); - testImage = {}; - - mockDocument[0].createElement.and.returnValue(testImage); - - directive = new MCTBackgroundImage(mockDocument); - }); - - it("is applicable as an attribute", function () { - expect(directive.restrict).toEqual("A"); - }); - - it("two-way-binds its own value", function () { - expect(directive.scope.mctBackgroundImage).toEqual("="); - }); - - describe("once linked", function () { - beforeEach(function () { - directive.link(mockScope, mockElement, {}); - }); - - it("watches for changes to the URL", function () { - expect(mockScope.$watch).toHaveBeenCalledWith( - 'mctBackgroundImage', - jasmine.any(Function) - ); - }); - - it("updates images in-order, even when they load out-of-order", function () { - var firstOnload; - - mockScope.$watch.calls.mostRecent().args[1]("some/url/0"); - firstOnload = testImage.onload; - - mockScope.$watch.calls.mostRecent().args[1]("some/url/1"); - - // Resolve in a different order - testImage.onload(); - firstOnload(); - - // Should still have taken the more recent value - expect(mockElement.css.calls.mostRecent().args).toEqual([ - "background-image", - "url('some/url/1')" - ]); - }); - - it("clears the background image when undefined is passed in", function () { - mockScope.$watch.calls.mostRecent().args[1]("some/url/0"); - testImage.onload(); - mockScope.$watch.calls.mostRecent().args[1](undefined); - - expect(mockElement.css.calls.mostRecent().args).toEqual([ - "background-image", - "none" - ]); - }); - - it("updates filters on change", function () { - var filters = { brightness: 123, contrast: 21 }; - mockScope.$watchCollection.calls.all().forEach(function (call) { - if (call.args[0] === 'filters') { - call.args[1](filters); - } - }); - expect(mockElement.css).toHaveBeenCalledWith( - 'filter', - 'brightness(123%) contrast(21%)' - ); - }); - - it("clears filters when none are present", function () { - mockScope.$watchCollection.calls.all().forEach(function (call) { - if (call.args[0] === 'filters') { - call.args[1](undefined); - } - }); - expect(mockElement.css) - .toHaveBeenCalledWith('filter', ''); - }); - }); - }); - } -); - diff --git a/platform/features/imagery/test/policies/ImageryViewPolicySpec.js b/platform/features/imagery/test/policies/ImageryViewPolicySpec.js deleted file mode 100644 index de61a91100..0000000000 --- a/platform/features/imagery/test/policies/ImageryViewPolicySpec.js +++ /dev/null @@ -1,84 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/policies/ImageryViewPolicy"], - function (ImageryViewPolicy) { - - describe("Imagery view policy", function () { - var testView, - openmct, - mockDomainObject, - mockTelemetry, - mockMetadata, - policy; - - beforeEach(function () { - testView = { key: "imagery" }; - mockMetadata = jasmine.createSpyObj('metadata', [ - "valuesForHints" - ]); - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId', 'getModel', 'getCapability'] - ); - mockTelemetry = jasmine.createSpyObj( - 'telemetry', - ['getMetadata'] - ); - mockDomainObject.getCapability.and.callFake(function (c) { - return c === 'telemetry' ? mockTelemetry : undefined; - }); - mockDomainObject.getId.and.returnValue("some-id"); - mockDomainObject.getModel.and.returnValue({ name: "foo" }); - mockTelemetry.getMetadata.and.returnValue(mockMetadata); - mockMetadata.valuesForHints.and.returnValue(["bar"]); - - openmct = { telemetry: mockTelemetry }; - - policy = new ImageryViewPolicy(openmct); - }); - - it("checks for hints indicating image telemetry", function () { - policy.allow(testView, mockDomainObject); - expect(mockMetadata.valuesForHints) - .toHaveBeenCalledWith(["image"]); - }); - - it("allows the imagery view for domain objects with image telemetry", function () { - expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); - }); - - it("disallows the imagery view for domain objects without image telemetry", function () { - mockMetadata.valuesForHints.and.returnValue([]); - expect(policy.allow(testView, mockDomainObject)).toBeFalsy(); - }); - - it("allows other views", function () { - testView.key = "somethingElse"; - expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); - }); - - }); - } -); - diff --git a/platform/features/my-items/README.md b/platform/features/my-items/README.md new file mode 100644 index 0000000000..df61bacf6c --- /dev/null +++ b/platform/features/my-items/README.md @@ -0,0 +1,8 @@ +# My Items plugin +Defines top-level folder named "My Items" to store user-created items. Enabled by default, this can be disabled in a +read-only deployment with no user-editable objects. + +## Installation +```js +openmct.install(openmct.plugins.MyItems()); +``` \ No newline at end of file diff --git a/platform/import-export/README.md b/platform/import-export/README.md new file mode 100644 index 0000000000..e8d903ec68 --- /dev/null +++ b/platform/import-export/README.md @@ -0,0 +1,14 @@ +# Import / Export Plugin +The Import/Export plugin allows objects to be exported as JSON files. This allows for sharing of objects between users +who are not using a shared persistence store. It also allows object trees to be backed up. Additionally, object trees +exported using this tool can then be exposed as read-only static root trees using the +[Static Root Plugin](../../src/plugins/staticRootPlugin/README.md). + +Upon installation it will add two new context menu actions to allow import and export of objects. Initiating the Export +action on an object will produce a JSON file that includes the object and all of its composed children. Selecting Import +on an object will allow the user to import a previously exported object tree as a child of the selected object. + +## Installation +```js +openmct.install(openmct.plugins.ImportExport()) +``` \ No newline at end of file diff --git a/platform/import-export/src/actions/ExportAsJSONAction.js b/platform/import-export/src/actions/ExportAsJSONAction.js index 342198adb7..a0b37c054f 100644 --- a/platform/import-export/src/actions/ExportAsJSONAction.js +++ b/platform/import-export/src/actions/ExportAsJSONAction.js @@ -133,7 +133,7 @@ define(['lodash'], function (_) { copyOfChild.location = parentId; parent.composition[index] = copyOfChild.identifier; this.tree[newIdString] = copyOfChild; - this.tree[parentId].composition[index] = newIdString; + this.tree[parentId].composition[index] = copyOfChild.identifier; return copyOfChild; }; diff --git a/platform/import-export/src/actions/ImportAsJSONAction.js b/platform/import-export/src/actions/ImportAsJSONAction.js index bce8aada06..8411d12dad 100644 --- a/platform/import-export/src/actions/ImportAsJSONAction.js +++ b/platform/import-export/src/actions/ImportAsJSONAction.js @@ -136,6 +136,10 @@ define(['zepto', '../../../../src/api/objects/object-utils.js'], function ($, ob return tree; }; + ImportAsJSONAction.prototype.getKeyString = function (identifier) { + return this.openmct.objects.makeKeyString(identifier); + }; + /** * Rewrites all instances of a given id in the tree with a newly generated * replacement to prevent collision. diff --git a/platform/import-export/test/actions/ImportAsJSONActionSpec.js b/platform/import-export/test/actions/ImportAsJSONActionSpec.js index 913883cd37..a2dc5b0392 100644 --- a/platform/import-export/test/actions/ImportAsJSONActionSpec.js +++ b/platform/import-export/test/actions/ImportAsJSONActionSpec.js @@ -47,7 +47,12 @@ define( uniqueId = 0; newObjects = []; openmct = { - $injector: jasmine.createSpyObj('$injector', ['get']) + $injector: jasmine.createSpyObj('$injector', ['get']), + objects: { + makeKeyString: function (identifier) { + return identifier.key; + } + } }; mockInstantiate = jasmine.createSpy('instantiate').and.callFake( function (model, id) { @@ -153,7 +158,7 @@ define( body: JSON.stringify({ "openmct": { "infiniteParent": { - "composition": ["infinteChild"], + "composition": [{key: "infinteChild", namespace: ""}], "name": "1", "type": "folder", "modified": 1503598129176, @@ -161,7 +166,7 @@ define( "persisted": 1503598129176 }, "infinteChild": { - "composition": ["infiniteParent"], + "composition": [{key: "infinteParent", namespace: ""}], "name": "2", "type": "folder", "modified": 1503598132428, diff --git a/platform/persistence/couch/README.md b/platform/persistence/couch/README.md index beef1faef5..15dd758b2d 100644 --- a/platform/persistence/couch/README.md +++ b/platform/persistence/couch/README.md @@ -1,2 +1,8 @@ -This bundle implements a connection to an external CouchDB persistence -store in Open MCT. +# Couch DB Persistence Plugin +An adapter for using CouchDB for persistence of user-created objects. The plugin installation function takes the URL +for the CouchDB database as a parameter. + +## Installation +```js +openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct')) +``` \ No newline at end of file diff --git a/platform/persistence/elastic/README.md b/platform/persistence/elastic/README.md index 52a425ce3d..413501d9ca 100644 --- a/platform/persistence/elastic/README.md +++ b/platform/persistence/elastic/README.md @@ -1,2 +1,8 @@ -This bundle implements a connection to an external ElasticSearch persistence -store in Open MCT. +# Elasticsearch Persistence Provider +An adapter for using Elastic for persistence of user-created objects. The installation function takes the URL for an +Elasticsearch server as a parameter. + +## Installation +```js +openmct.install(openmct.plugins.Elasticsearch('http://localhost:9200')) +``` \ No newline at end of file diff --git a/platform/persistence/local/README.md b/platform/persistence/local/README.md new file mode 100644 index 0000000000..553fa499a3 --- /dev/null +++ b/platform/persistence/local/README.md @@ -0,0 +1,9 @@ +# Local Storage Plugin +Provides persistence of user-created objects in browser Local Storage. Objects persisted in this way will only be +available from the browser and machine on which they were persisted. For shared persistence, consider the +[Elasticsearch](../elastic/) and [CouchDB](../couch/) persistence plugins. + +## Installation +```js +openmct.install(openmct.plugins.LocalStorage()); +``` \ No newline at end of file diff --git a/scripts/migrate-for-jshint.js b/scripts/migrate-for-jshint.js deleted file mode 100644 index a31b061f30..0000000000 --- a/scripts/migrate-for-jshint.js +++ /dev/null @@ -1,49 +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. - *****************************************************************************/ - -// Converts all templateUrl references in bundle.js files to -// plain template references, loading said templates with the -// RequireJS text plugin. - -var glob = require('glob'), - fs = require('fs'); - -function migrate(file) { - var sourceCode = fs.readFileSync(file, 'utf8'), - lines = sourceCode.split('\n') - .filter(function (line) { - return !(/^\W*['"]use strict['"];\W*$/.test(line)); - }) - .filter(function (line) { - return line.indexOf("/*global") !== 0; - }); - fs.writeFileSync(file, lines.join('\n')); -} - -glob('@(src|platform)/**/*.js', {}, function (err, files) { - if (err) { - console.log(err); - return; - } - - files.forEach(migrate); -}); diff --git a/scripts/migrate-templates.js b/scripts/migrate-templates.js deleted file mode 100644 index 4071688b62..0000000000 --- a/scripts/migrate-templates.js +++ /dev/null @@ -1,106 +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. - *****************************************************************************/ - -// Converts all templateUrl references in bundle.js files to -// plain template references, loading said templates with the -// RequireJS text plugin. - -var glob = require('glob'), - fs = require('fs'), - path = require('path'), - _ = require('lodash'); - -function toTemplateName(templateUrl) { - var parts = templateUrl.split('/'); - return _.camelCase(parts[parts.length - 1].replace(".html", "")) + - "Template"; -} - -function getTemplateUrl(sourceLine) { - return _.trim(sourceLine.split(":")[1], "\", "); -} - -function hasTemplateUrl(sourceLine) { - return sourceLine.indexOf("templateUrl") !== -1; -} - -function findTemplateURLs(sourceCode) { - return sourceCode.split('\n') - .map(_.trim) - .filter(hasTemplateUrl) - .map(getTemplateUrl); -} - -function injectRequireArgument(sourceCode, templateUrls) { - var lines = sourceCode.split('\n'), - index; - - templateUrls = _.uniq(templateUrls); - - // Add arguments for source paths... - index = lines.map(_.trim).indexOf("'legacyRegistry'"); - lines = lines.slice(0, index).concat(templateUrls.map(function (url) { - return " \"text!./res/" + url + "\","; - }).concat(lines.slice(index))); - - /// ...and for arguments - index = lines.map(_.trim).indexOf("legacyRegistry"); - lines = lines.slice(0, index).concat(templateUrls.map(function (url) { - return " " + toTemplateName(url) + ","; - }).concat(lines.slice(index))); - - return lines.join('\n'); -} - -function rewriteUrl(sourceLine) { - return [ - sourceLine.substring(0, sourceLine.indexOf(sourceLine.trim())), - "\"template\": " + toTemplateName(getTemplateUrl(sourceLine)), - _.endsWith(sourceLine, ",") ? "," : "" - ].join(''); -} - -function rewriteLine(sourceLine) { - return hasTemplateUrl(sourceLine) ? - rewriteUrl(sourceLine.replace("templateUrl", "template")) : - sourceLine; -} - -function rewriteTemplateUrls(sourceCode) { - return sourceCode.split('\n').map(rewriteLine).join('\n'); -} - -function migrate(file) { - var sourceCode = fs.readFileSync(file, 'utf8'); - fs.writeFileSync(file, rewriteTemplateUrls( - injectRequireArgument(sourceCode, findTemplateURLs(sourceCode)) - ), 'utf8'); -} - -glob('platform/**/bundle.js', {}, function (err, files) { - if (err) { - console.log(err); - return; - } - - files.forEach(migrate); -}); diff --git a/scripts/rebundle-template.txt b/scripts/rebundle-template.txt deleted file mode 100644 index deb754d69e..0000000000 --- a/scripts/rebundle-template.txt +++ /dev/null @@ -1,34 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ -/*global define*/ - -define([ - <%= implPaths %> - 'legacyRegistry' -], function ( - <%= implNames %> - legacyRegistry -) { - "use strict"; - - legacyRegistry.register("<%= bundleName %>", <%= bundleContents %>); -}); diff --git a/scripts/rebundle.js b/scripts/rebundle.js deleted file mode 100644 index bd7cf83b35..0000000000 --- a/scripts/rebundle.js +++ /dev/null @@ -1,72 +0,0 @@ -// Temporary utility script to rewrite bundle.json -// files as bundle.js files. - -var glob = require('glob'), - fs = require('fs'), - path = require('path'), - _ = require('lodash'), - template = _.template( - fs.readFileSync(path.resolve(__dirname, 'rebundle-template.txt'), 'utf8') - ); - -function indent(str, depth) { - return _.trimLeft(str.split('\n').map(function (line) { - return _.repeat(' ', depth || 1) + line; - }).filter(function (line) { - return line.trim().length > 0; - }).join('\n')); -} - -function findImpls(bundleContents) { - return _(bundleContents.extensions || {}) - .map() - .flatten() - .pluck('implementation') - .filter() - .uniq() - .value(); -} - -function toIdentifier(impl) { - var parts = impl.replace(".js", "").split('/'); - return parts[parts.length - 1]; -} - -function toPath(impl) { - return "\"./src/" + impl.replace(".js", "") + "\""; -} - -function replaceImpls(bundleText) { - var rx = /"implementation": "([^"]*)"/; - return bundleText.split('\n').map(function (line) { - var m = line.match(rx); - return m !== null ? - line.replace(rx, '"implementation": ' + toIdentifier(m[1])) : - line; - }).join('\n'); -} - -function rebundle(file) { - var plainJson = fs.readFileSync(file, 'utf8'), - bundleContents = JSON.parse(plainJson), - impls = findImpls(bundleContents), - bundleName = file.replace("/bundle.json", ""), - outputFile = file.replace(".json", ".js"), - contents = template({ - bundleName: bundleName, - implPaths: indent(impls.map(toPath).concat([""]).join(",\n")), - implNames: indent(impls.map(toIdentifier).concat([""]).join(",\n")), - bundleContents: indent(replaceImpls(JSON.stringify(bundleContents, null, 4))) - }); - fs.writeFileSync(outputFile, contents, 'utf8'); -} - -glob('**/bundle.json', {}, function (err, files) { - if (err) { - console.log(err); - return; - } - - files.forEach(rebundle); -}); - diff --git a/src/MCT.js b/src/MCT.js index 4c9fdfe7dd..c045b6e561 100644 --- a/src/MCT.js +++ b/src/MCT.js @@ -33,13 +33,12 @@ define([ './adapter/indicators/legacy-indicators-plugin', './plugins/buildInfo/plugin', './ui/registries/ViewRegistry', + './plugins/imagery/plugin', './ui/registries/InspectorViewRegistry', './ui/registries/ToolbarRegistry', './ui/router/ApplicationRouter', './ui/router/Browse', '../platform/framework/src/Main', - './styles/core.scss', - './styles/notebook.scss', './ui/layout/Layout.vue', '../platform/core/src/objects/DomainObjectImpl', '../platform/core/src/capabilities/ContextualDomainObject', @@ -61,13 +60,12 @@ define([ LegacyIndicatorsPlugin, buildInfoPlugin, ViewRegistry, + ImageryPlugin, InspectorViewRegistry, ToolbarRegistry, ApplicationRouter, Browse, Main, - coreStyles, - NotebookStyles, Layout, DomainObjectImpl, ContextualDomainObject, @@ -261,6 +259,7 @@ define([ this.install(RemoveActionPlugin.default()); this.install(this.plugins.FolderView()); this.install(this.plugins.Tabs()); + this.install(ImageryPlugin.default()); this.install(this.plugins.FlexibleLayout()); this.install(this.plugins.GoToOriginalAction()); this.install(this.plugins.ImportExport()); @@ -319,11 +318,26 @@ define([ * @memberof module:openmct.MCT# * @method setAssetPath */ - MCT.prototype.setAssetPath = function (path) { - this.legacyExtension('constants', { - key: "ASSETS_PATH", - value: path - }); + MCT.prototype.setAssetPath = function (assetPath) { + this._assetPath = assetPath; + }; + + /** + * Get path to where assets are hosted. + * @memberof module:openmct.MCT# + * @method getAssetPath + */ + MCT.prototype.getAssetPath = function () { + const assetPathLength = this._assetPath && this._assetPath.length; + if (!assetPathLength) { + return '/'; + } + + if (this._assetPath[assetPathLength - 1] !== '/') { + return this._assetPath + '/'; + } + + return this._assetPath; }; /** diff --git a/src/adapter/policies/README.md b/src/adapter/policies/README.md new file mode 100644 index 0000000000..f00ed0476f --- /dev/null +++ b/src/adapter/policies/README.md @@ -0,0 +1,7 @@ +# Espresso Theme +Dark theme for the Open MCT user interface. + +## Installation +```js +openmct.install(openmct.plugins.Espresso()); +``` \ No newline at end of file diff --git a/src/api/overlays/components/DialogComponent.vue b/src/api/overlays/components/DialogComponent.vue index 23991c0bf6..9193d302bd 100644 --- a/src/api/overlays/components/DialogComponent.vue +++ b/src/api/overlays/components/DialogComponent.vue @@ -33,92 +33,6 @@ - - diff --git a/src/plugins/imagery/components/imagery-view-layout.scss b/src/plugins/imagery/components/imagery-view-layout.scss new file mode 100644 index 0000000000..0a86127853 --- /dev/null +++ b/src/plugins/imagery/components/imagery-view-layout.scss @@ -0,0 +1,32 @@ +.c-imagery-layout { + display: flex; + flex-direction: column; + overflow: auto; + + .main-image-wrapper { + display: flex; + flex-direction: column; + height: 100%; + padding-bottom: 5px; + } + + .main-image { + background-position: center; + background-repeat: no-repeat; + background-size: contain; + height: 100%; + + &.unnsynced{ + @include sUnsynced(); + } + } + + .l-image-controller { + padding: 5px 0 0 0; + } + + .thumbs-layout { + margin-top: 5px; + overflow: auto; + } +} diff --git a/src/plugins/imagery/plugin.js b/src/plugins/imagery/plugin.js new file mode 100644 index 0000000000..fdbaeba18c --- /dev/null +++ b/src/plugins/imagery/plugin.js @@ -0,0 +1,8 @@ +import ImageryViewProvider from './ImageryViewProvider'; + +export default function () { + return function install(openmct) { + openmct.objectViews.addProvider(new ImageryViewProvider(openmct)); + }; +} + diff --git a/src/plugins/notebook/res/templates/snapshotTemplate.html b/src/plugins/notebook/res/templates/snapshotTemplate.html index 8ca8116ca6..5bd3220409 100644 --- a/src/plugins/notebook/res/templates/snapshotTemplate.html +++ b/src/plugins/notebook/res/templates/snapshotTemplate.html @@ -1,29 +1,29 @@ -
- -
-
-
-
-
-
{{embed.name}}
-
+
+ +
+
+
+ + {{embed.name}} +
-
-
-
- SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}} -
- - Annotate - -
- -
-
+
+
+ SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}} +
+ + Annotate +
-
\ No newline at end of file + +
+
+
+
diff --git a/src/plugins/plot/README.md b/src/plugins/plot/README.md new file mode 100644 index 0000000000..4243e3cb99 --- /dev/null +++ b/src/plugins/plot/README.md @@ -0,0 +1,10 @@ +# Plot Plugin + +Enables plot visualization of telemetry data. This plugin adds a plot view that is available from the view switcher for +all telemetry objects. Two user createble objects are also added by this plugin, for Overlay and Stacked Plots. +Telemetry objects can be added to Overlay and Stacked Plots via drag and drop. + +## Installation +``` js +openmct.install(openmct.plugins.Plot()); +``` \ No newline at end of file diff --git a/src/plugins/plot/res/templates/mct-plot.html b/src/plugins/plot/res/templates/mct-plot.html index 7f7800d114..335548f0f7 100644 --- a/src/plugins/plot/res/templates/mct-plot.html +++ b/src/plugins/plot/res/templates/mct-plot.html @@ -225,7 +225,7 @@ left: (100 * (tick.value - min) / interval) + '%' }" ng-title=":: tick.fullText || tick.text"> - {{:: tick.text | reverse}} + {{:: tick.text }}
diff --git a/src/plugins/plot/src/configuration/PlotSeries.js b/src/plugins/plot/src/configuration/PlotSeries.js index 204b5a98e6..9cd30b97b7 100644 --- a/src/plugins/plot/src/configuration/PlotSeries.js +++ b/src/plugins/plot/src/configuration/PlotSeries.js @@ -140,8 +140,14 @@ define([ * @returns {Promise} */ fetch: function (options) { - const strategy = options.shouldUseMinMax ? 'minMax' : undefined; + let strategy; + + if (this.model.interpolate !== 'none') { + strategy = 'minMax'; + } + options = _.extend({}, { size: 1000, strategy, filters: this.filters }, options || {}); + if (!this.unsubscribe) { this.unsubscribe = this.openmct .telemetry @@ -378,19 +384,6 @@ define([ delete this.unsubscribe; } this.fetch(); - }, - - /** - * Clears the plot series, unsubscribes and resubscribes - * @public - */ - refresh: function () { - this.reset(); - if (this.unsubscribe) { - this.unsubscribe(); - delete this.unsubscribe; - } - this.fetch(); } }); diff --git a/src/plugins/plot/src/inspector/PlotYAxisFormController.js b/src/plugins/plot/src/inspector/PlotYAxisFormController.js index ac2b84e08f..fa3b2ce231 100644 --- a/src/plugins/plot/src/inspector/PlotYAxisFormController.js +++ b/src/plugins/plot/src/inspector/PlotYAxisFormController.js @@ -46,7 +46,7 @@ define([ }, { modelProp: 'range', - objectPath: 'form.yAxis.range', + objectPath: 'configuration.yAxis.range', coerce: function coerceRange(range) { if (!range) { return { diff --git a/src/plugins/plot/src/telemetry/PlotController.js b/src/plugins/plot/src/telemetry/PlotController.js index 36b457afbd..50dd9e945b 100644 --- a/src/plugins/plot/src/telemetry/PlotController.js +++ b/src/plugins/plot/src/telemetry/PlotController.js @@ -102,8 +102,7 @@ define([ this.startLoading(); var options = { size: this.$element[0].offsetWidth, - domain: this.config.xAxis.get('key'), - shouldUseMinMax: this.shouldUseMinMax(series) + domain: this.config.xAxis.get('key') }; series.load(options) @@ -161,10 +160,6 @@ define([ return config; }; - PlotController.prototype.shouldUseMinMax = function (series) { - return series.model.interpolate !== 'none'; - }; - PlotController.prototype.onTimeSystemChange = function (timeSystem) { this.config.xAxis.set('key', timeSystem.key); }; diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js index dcfe24dbd9..f6e7203d9d 100644 --- a/src/plugins/plugins.js +++ b/src/plugins/plugins.js @@ -28,6 +28,7 @@ define([ './autoflow/AutoflowTabularPlugin', './timeConductor/plugin', '../../example/imagery/plugin', + './imagery/plugin', '../../platform/import-export/bundle', './summaryWidget/plugin', './URLIndicatorPlugin/URLIndicatorPlugin', @@ -47,6 +48,9 @@ define([ './clearData/plugin', './webPage/plugin', './condition/plugin' + './themes/espresso', + './themes/maelstrom', + './themes/snow' ], function ( _, UTCTimeSystem, @@ -55,6 +59,7 @@ define([ AutoflowPlugin, TimeConductorPlugin, ExampleImagery, + ImageryPlugin, ImportExport, SummaryWidget, URLIndicatorPlugin, @@ -74,6 +79,9 @@ define([ ClearData, WebPagePlugin, ConditionPlugin + Espresso, + Maelstrom, + Snow ) { var bundleMap = { LocalStorage: 'platform/persistence/local', @@ -158,6 +166,7 @@ define([ }; plugins.ExampleImagery = ExampleImagery; + plugins.ImageryPlugin = ImageryPlugin; plugins.Plot = PlotPlugin; plugins.TelemetryTable = TelemetryTablePlugin; @@ -176,6 +185,9 @@ define([ plugins.ClearData = ClearData; plugins.WebPage = WebPagePlugin.default; plugins.Condition = ConditionPlugin.default; + plugins.Espresso = Espresso.default; + plugins.Maelstrom = Maelstrom.default; + plugins.Snow = Snow.default; return plugins; }); diff --git a/src/plugins/staticRootPlugin/README.md b/src/plugins/staticRootPlugin/README.md new file mode 100644 index 0000000000..57bced8471 --- /dev/null +++ b/src/plugins/staticRootPlugin/README.md @@ -0,0 +1,19 @@ +# Static Root Plugin + +This plugin takes an object tree as JSON and exposes it as a non-editable root level tree. This can be useful if you +have static non-editable content that you wish to expose, such as a standard set of displays that should not be edited +(but which can be copied and then modified if desired). + +Any object tree in Open MCT can be exported as JSON after installing the +[Import/Export plugin](../../../platform/import-export/README.md). + +## Installation +``` js +openmct.install(openmct.plugins.StaticRootPlugin('mission', 'data/static-objects.json')); +``` + +## Parameters +The StaticRootPlugin takes two parameters: +1. __namespace__: This should be a name that uniquely identifies this collection of objects. +2. __path__: The file that the static tree should be exposed from. This will need to be a path that is reachable by a web +browser, ie not a path on the local file system. \ No newline at end of file diff --git a/src/plugins/summaryWidget/README.md b/src/plugins/summaryWidget/README.md new file mode 100644 index 0000000000..1301bbd643 --- /dev/null +++ b/src/plugins/summaryWidget/README.md @@ -0,0 +1,10 @@ +# Summary Widget Plugin +Summary widgets can be used to provide visual indication of state based on telemetry data. They allow rules to be +defined that can then be used to change the appearance of the summary widget element based on data. For example, a +summary widget could be defined that is green when a temparature reading is between `0` and `100` centigrade, red when +it's above `100`, and orange when it's below `0`. + +## Installation +```js +openmct.install(openmct.plugins.SummaryWidget()); +``` \ No newline at end of file diff --git a/src/plugins/tabs/README.md b/src/plugins/tabs/README.md new file mode 100644 index 0000000000..88854c57dc --- /dev/null +++ b/src/plugins/tabs/README.md @@ -0,0 +1,7 @@ +# Espresso Theme +A light colored theme for the Open MCT user interface. + +## Installation +```js +openmct.install(openmct.plugins.Snow()); +``` \ No newline at end of file diff --git a/src/plugins/tabs/components/tabs.scss b/src/plugins/tabs/components/tabs.scss new file mode 100644 index 0000000000..eb7bfbb410 --- /dev/null +++ b/src/plugins/tabs/components/tabs.scss @@ -0,0 +1,58 @@ +.c-tabs-view { + $h: 20px; + @include abs(); + display: flex; + flex-flow: column nowrap; + + > * + * { + margin-top: $interiorMargin; + } + + &__tabs-holder { + min-height: $h; + } + + &__tab { + &:before { + margin-right: $interiorMarginSm; + opacity: 0.7; + } + } + + &__object-holder { + flex: 1 1 auto; + display: flex; + flex-direction: column; + + &--hidden { + height: 1000px; + width: 1000px; + position: absolute; + left: -9999px; + top: -9999px; + } + } + + &__object-name { + flex: 0 0 auto; + @include headerFont(); + font-size: 1.2em !important; + margin: $interiorMargin 0 $interiorMarginLg 0; + } + + &__object { + display: flex; + flex-flow: column nowrap; + flex: 1 1 auto; + height: 0; // Chrome 73 oveflow bug fix + } + + &__empty-message { + background: rgba($colorBodyFg, 0.1); + color: rgba($colorBodyFg, 0.7); + font-style: italic; + text-align: center; + line-height: $h; + width: 100%; + } +} diff --git a/src/plugins/tabs/components/tabs.vue b/src/plugins/tabs/components/tabs.vue index 3db1607b3e..262f1bb217 100644 --- a/src/plugins/tabs/components/tabs.vue +++ b/src/plugins/tabs/components/tabs.vue @@ -55,69 +55,6 @@
- - - - diff --git a/src/ui/layout/CreateButton.vue b/src/ui/layout/CreateButton.vue index 60ecb1d6d6..e29a5ee4ad 100644 --- a/src/ui/layout/CreateButton.vue +++ b/src/ui/layout/CreateButton.vue @@ -37,40 +37,6 @@
- -