Compare commits

..

15 Commits

Author SHA1 Message Date
485e948abe Context Menu WIP 2018-10-11 10:50:09 -07:00
64b9d4c24a Vue status bar (#2188)
* Implemented indicators

* WIP

* Fixed templates from notifications example

* Message bar implemented

* Implemented notifications

* Fixed bug with destruction of notifications

* Renamed MessageBanner to NotificationBanner

* Add save success message

* Removed NotificationServiceSpec

* Removed legacy constants from bundle
2018-10-10 17:35:11 -07:00
88bcb6078e Conductor fixes (#2189)
* Conductor fixes

- Restore RT update time field;
- Colors tweaked;
- Much better mobile layout;

* Significant fixes in Conductor markup and styling

- Markup/CSS simplified and clearer;
- Better coloring in both Themes;
- Better clarity for axis UI element;
- Fixed hover and focus styles on inputs;
2018-10-10 17:03:52 -07:00
5f9f3cd8e8 Topic themes (#2187)
* Bringing over in-progress changes from topic-core-css

- Adds _espresso-constants.scss;
- Cleanup colors and naming;
- Remove conflict res leftover 'domainObject' in mct-tree.vue;
- Still WIP!

* Various

- Remove pushBack / pullForward functions;
- Fix c-input-inline, remove bg until hover;
- TODO: input bg colors
- Increased margin in main-pane;

* Themeing WIP

- Conductor markup: convert to buttons for accessibility;
- Conductor styles consolidated and changed for better theme support;

* Themeing WIP; significant rewrite of pane headers

- Pane headers restructured for better semantics and clarity;
- Espresso design refined and tightened;
- Grid Vue changes for better themeing support;
- TODO: fix mobile version, collapse icon is whack;

* Restored Number-type input styling for correct positioning of spinner
button;

* Themeing mods for click-icon styles

* Bring Snow theme into style parity with Espresso

- TODO: refine Snow colors;

* Mobile styling fixed

- Mobile menu icon significant fixes;
- Hover only applied to desktop;
- Reorg of mixins;

* Bring Snow theme constants into parity with Espresso

- Refined Snow styles;
- Fixed missing scroll and padding in tree;
- Pane collapse button now uses proper color;
- Item Grid view refinement;
- Cleaned up code;

* Color fixes

- Super-menu description;
- Conductor time buttons hover;
- Datepicker "in-month" items color;
- Espresso colorKeyFilter brightened;
2018-10-10 12:45:46 -07:00
814b404614 Fixes for Inspector Plot properties, and more
- Fixed overflow problem when Inspector collapsed;
- Temp legacy styling for Plot inspection, series options, etc.
- Factored out non-useful gridTwoColumn mixin;
- Brought back legacy tree.scss file in legacy-styles.scss;
2018-10-05 15:13:38 -07:00
ba2bb2180b Merge branch 'topic-core-refactor' of https://github.com/nasa/openmct into topic-core-refactor 2018-10-05 12:15:30 -07:00
72cdb352f0 Fix s-selected, edit grid displays 2018-10-05 12:15:20 -07:00
cedf942c0c apply is-editing only when editing 2018-10-05 11:58:22 -07:00
27506a3757 Remove selectable from display layut 2018-10-05 11:39:41 -07:00
acc4e03c88 Merge duplicate data method, correct is-editing class 2018-10-05 11:25:30 -07:00
9a6090cd02 Legacy inspector view support 2018-10-05 10:41:06 -07:00
f40c9fa6f9 Overlay Service re-written in Vue (#2186)
* working overlayService

* wire to snapshot, and add onDestroy Callback

* increment overlayId

* New branch from topic-core-refactor to use as central point for common
CSS work

- Manually migrated changes from vue-toolbar, expect conflicts there and
 in vue-layout;

* Manually update constants-snow from vue-toolbar branch

* Update markup to use latest button classnames

- c-menu-button > c-button--menu;
- c-icon-button > c-click-icon;

* Various from vue-conductor-style

- Mods to input styling;
- Input[] styles moved to _controls;
- New/revised constants vals;

* Resolve bizarre merge conflict when applying stash

* Code cleanup

* remove duplicate div

* Alias and type-icon fixes

- More robust approach to alias indicators;
- Added alias indication to tree-item.vue;
- TODO: wire up alias indication tree-item.vue;

* Accessibility mods, convert elements to <button>

- Better reset styles for htmlInputReset mixin to allow use of <button>
without browser default styling;
- Create button;
- BrowseBar action buttons;
- c-click-icons;
- Removed Preview button from BrowseBar.vue;

* Overlay styling WIP

- Base markup pretty solid;
- Stubbed in temp values for overlayTypeCssClass in overlayService.js;
- TODO: styling for contents area;

* add options object, scope variables to show function, add destroy callback to active Overlay Object (for easier retrieval
2018-10-04 15:59:58 -07:00
e7cdb334de Reimplementation of Display Layout in Vue (#2185)
* Saving work

* Fix conflict

* Position the panels by setting the style

* Put the div back with height set to 100% in ObjectView.
Add markup for  drag handles.

* Use default position and dimensions if the layout panel is missing those values. Set s-status-editing on the main div to get the drag handles appear for now.

* Display Layout and frames major improvements

- Moved Toolbar out of Layout.vue and into DisplayLayout.vue;
- Styles for object view, Layout, Frame, etc.
- Major refactor of markup for frame;
- Added abs() mixin;
- Styles for is-editing done;
- Styles for
- TODO: styles for selectable, moveable, etc.

* Implement drill in gesture.

* Hide the background grid when a frame is drilled in

* Edit styling and toolbar WIP

- c-search styles moved mostly into mixin;
- New .c-labeled-input class;
- Browser overrides for number-type input spinners in webkit;

* Toolbar WIP

- Custom wrapped number input added;
- Toolbar buttons WIP;

* New toolbar buttons WIP

* Define a computed property for the css class object.

* Frame edit handles markup and styling

* Toolbar, editing and selection style refinements

- Moved toolbar back into Layout.vue;
- Hard-coded 'is-editing' onto __pane-main for now,
removed from DisplayLayout.vue;
- Styles for frame in LayoutFrame.vue:
-- editing default (dotted border)
-- editing .s-selected
-- .s-drilled-in (renamed .is-drilled-in)

* Refactoring button classes

- Lots of stuff broken right now;
- TODO: lots of renaming (c-menu-button, c-icon-button, etc.);
- Removed import of _controls in search.vue;

* Fixes for selection on nested selected elements

* Fix conflict

* Significant refactoring of button and click-icon classes

- Markup and CSS updated;
- Toolbar in good shape, prior to merge of vue-layout;

* Fix issues with relative font-sizing

* Add color palette markup and CSS

- Also added Layers menu example;

* Font, font-size glyphs and size menu, and more

- Added art for font glyph and renamed from .icon-T;
- Added new glyph for font-size;
- Fixed font-sizing in controls;
- Added font-size menu;
- Re-org'd toolbar items;

* Styling tweak for c-labeled-input

- Code cleanup as well;

* Fixes for toolbar toggleMenus and labeledNumberInput

* Implement resize and move for frames.
Added stub for drag 'n drop.

* Add custom checkbox control.

- Also, code cleanup.

* Add toggleButton component

- Code and examples

* Custom checkbox code cleanups, sanding

* - Persist new position/dimensions on the domain object when frames are moved/resized.
- Bypass the selection of the layout when dragging a frame is finished to keep the frame selected.
- Set the grid size to layoutGrid from domain object or use default if it's not specified.

* Fix conflict

* Implement resize and move for frames.
Added stub for drag 'n drop.

* Remove old layout view, was triggering View Switcher
and massive confusion when viewing Layouts

- TODO: add view provider for new Layout

* Enable drag and drop

* Changed example toggle-button

- Now uses show/hide frame as toggle-button example;

* Added pseudocode for handling drag/drop composition change

* Add copyright notice

* Layout frame and contained components styling

- Hyperlinks, Hyperlink buttons, Summary Widgets now use `.u-links`
which disables their pointer-events when editing;
- Hyperlink buttons, Summary Widgets now expand to fill their
containers in a Layout;
- Markup and JS for Hyperlinks, Hyperlink buttons, Summary Widgets
somewhat
modded to use new classing, applied in legacy scss files;

* Fix icon sizing error in BrowseBar

* Edit and selecting styling for Layouts

- WIP!

* Edit and selecting styling for Layout frames

- Color vars more standardized;
- Hover and *-selected styles;

* Getting vue-toolbar reverted back to latest

- Merging this branch with vue-layout may cause conflicts;

* - Implement drag ’n drop.
- Set hasFrame to a default value if it’s not set on the configuration.
- Emit an event to the parent wrapper component to update the ‘newDomainObject’ prop whenever the domain object is mutated in the display layout child component.

* Revert emitting 'update:object' event to the parent.

* New branch from topic-core-refactor to use as central point for common
CSS work

- Manually migrated changes from vue-toolbar, expect conflicts there and
 in vue-layout;

* Manually update constants-snow from vue-toolbar branch

* Update markup to use latest button classnames

- c-menu-button > c-button--menu;
- c-icon-button > c-click-icon;

* Various from vue-conductor-style

- Mods to input styling;
- Input[] styles moved to _controls;
- New/revised constants vals;

* Resolve bizarre merge conflict when applying stash

* Code cleanup

* Alias and type-icon fixes

- More robust approach to alias indicators;
- Added alias indication to tree-item.vue;
- TODO: wire up alias indication tree-item.vue;

* Accessibility mods, convert elements to <button>

- Better reset styles for htmlInputReset mixin to allow use of <button>
without browser default styling;
- Create button;
- BrowseBar action buttons;
- c-click-icons;
- Removed Preview button from BrowseBar.vue;

* Fix styles that were affected during resolving conflicts

* Moved draggable into __label element rather than whole <li>

* Change the priority to 100 to get the view working properly

* Code cleanup

* Remove angular layout

* Display the object name in the frame header

* Tweaks to __header in LayoutFrame

- Name now does not overflow frame edge;
- Layout strategy now in parity with similar elements in main view;

* Remove test()

* Add a type for display layout to make it appear in the Create menu.

* Change the key type to 'layout'

* Clean up code and hide toolbar

* Enable toolbar, and revert changes in webpack config

* Remove commented code

* revert to the original code
2018-10-04 15:59:23 -07:00
afca6cd2e9 Review and merge latest topic-core-css (#2183)
* New branch from topic-core-refactor to use as central point for common
CSS work

- Manually migrated changes from vue-toolbar, expect conflicts there and
 in vue-layout;

* Manually update constants-snow from vue-toolbar branch

* Update markup to use latest button classnames

- c-menu-button > c-button--menu;
- c-icon-button > c-click-icon;

* Various from vue-conductor-style

- Mods to input styling;
- Input[] styles moved to _controls;
- New/revised constants vals;

* Resolve bizarre merge conflict when applying stash

* Code cleanup

* Alias and type-icon fixes

- More robust approach to alias indicators;
- Added alias indication to tree-item.vue;
- TODO: wire up alias indication tree-item.vue;

* Accessibility mods, convert elements to <button>

- Better reset styles for htmlInputReset mixin to allow use of <button>
without browser default styling;
- Create button;
- BrowseBar action buttons;
- c-click-icons;
- Removed Preview button from BrowseBar.vue;

* Add copyright to .scss files; code cleanup

* Improved click area and draggable styling for tree-item

* Removed injection of domainObject

* Removed injection of domainObject

* Remove _constants-mobile.scss

- Moved used var to _constants;
- Normalize mobile detection approach in Layout.vue;

* Mobile styling for Time Conductor

- WIP!

* Mobile styling for Time Conductor

- New modalFullScreen mixin;
- Datepicker now a fullscreen modal element in body.phone;
2018-10-04 14:47:12 -07:00
3c324cbea0 Initial implementation of Edit mode (#2181)
* Adding edit mode API and buttons

* Select navigated object by default. Enable table configuration in edit mode.

* Fixed issue with Table configuration

* Removed debugging code

* Added basic documentation to Editor API

* use selectable, table is selection[0], inspector cleans up

Update browse to set selection using openmct.selection.selectable
on navation so that selection contexts can be determined correctly.

Update table configuration to reference the first item in selection
instead of the last item in selection when displaying.

Update inspector code to remove display node from document on destroy.

* properly remove capturing handler

* inspector views respond to editing

InspectorViews respond to editing instead of openmct rerendering
the inspector on edit.
2018-10-04 12:35:03 -07:00
84 changed files with 3999 additions and 2804 deletions

View File

@ -26,12 +26,16 @@ define([
"./src/NotificationLaunchController",
"./src/DialogLaunchIndicator",
"./src/NotificationLaunchIndicator",
"./res/dialog-launch.html",
"./res/notification-launch.html",
'legacyRegistry'
], function (
DialogLaunchController,
NotificationLaunchController,
DialogLaunchIndicator,
NotificationLaunchIndicator,
DialogLaunch,
NotificationLaunch,
legacyRegistry
) {
"use strict";
@ -41,11 +45,11 @@ define([
"templates": [
{
"key": "dialogLaunchTemplate",
"templateUrl": "dialog-launch.html"
"template": DialogLaunch
},
{
"key": "notificationLaunchTemplate",
"templateUrl": "notification-launch.html"
"template": NotificationLaunch
}
],
"controllers": [

View File

@ -51,76 +51,26 @@ define(
return actionTexts[Math.floor(Math.random()*3)];
}
function getExampleActions() {
var actions = [
{
label: "Try Again",
callback: function () {
$log.debug("Try Again pressed");
}
},
{
label: "Remove",
callback: function () {
$log.debug("Remove pressed");
}
},
{
label: "Cancel",
callback: function () {
$log.debug("Cancel pressed");
}
}
];
// Randomly remove some actions off the top; leave at least one
actions.splice(0,Math.floor(Math.random() * actions.length));
return actions;
}
function getExampleSeverity() {
var severities = [
"info",
"alert",
"error"
];
return severities[Math.floor(Math.random() * severities.length)];
}
/**
* Launch a new notification with a severity level of 'Error'.
*/
$scope.newError = function(){
$scope.newError = function () {
notificationService.notify({
title: "Example error notification " + messageCounter++,
hint: "An error has occurred",
severity: "error",
primaryOption: {
label: 'Retry',
callback: function() {
$log.info('Retry clicked');
}
},
options: getExampleActions()});
severity: "error"
});
};
/**
* Launch a new notification with a severity of 'Alert'.
*/
$scope.newAlert = function(){
$scope.newAlert = function () {
notificationService.notify({
title: "Alert notification " + (messageCounter++),
hint: "This is an alert message",
severity: "alert",
primaryOption: {
label: 'Retry',
callback: function() {
$log.info('Retry clicked');
}
},
options: getExampleActions()});
autoDismiss: true
});
};
@ -128,39 +78,38 @@ define(
* Launch a new notification with a progress bar that is updated
* periodically, tracking an ongoing process.
*/
$scope.newProgress = function(){
$scope.newProgress = function () {
var notificationModel = {
title: "Progress notification example",
severity: "info",
progress: 0,
actionText: getExampleActionText(),
unknownProgress: false
actionText: getExampleActionText()
};
/**
* Simulate an ongoing process and update the progress bar.
* @param notification
*/
function incrementProgress(notificationModel) {
function incrementProgress() {
notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30));
notificationModel.progressText = ["Estimated time" +
" remaining:" +
" about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" ");
if (notificationModel.progress < 100) {
$timeout(function(){incrementProgress(notificationModel);}, 1000);
$timeout(function () {
incrementProgress(notificationModel);
}, 1000);
}
}
notificationService.notify(notificationModel);
incrementProgress(notificationModel);
incrementProgress();
};
/**
* Launch a new notification with severity level of INFO.
*/
$scope.newInfo = function(){
$scope.newInfo = function () {
notificationService.info({
title: "Example Info notification " + messageCounter++
});

View File

@ -58,7 +58,7 @@ define([], function () {
function checkNavigation() {
var navigatedObject = navigationService.getNavigation();
if (navigatedObject.hasCapability('context')) {
if (navigatedObject && navigatedObject.hasCapability('context')) {
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
preventOrphanNavigation(navigatedObject);
}

View File

@ -405,7 +405,8 @@ define([
"description": "Provides transactional editing capabilities",
"implementation": EditorCapability,
"depends": [
"transactionService"
"transactionService",
"openmct"
]
}
],

View File

@ -36,9 +36,11 @@ define(
*/
function EditorCapability(
transactionService,
openmct,
domainObject
) {
this.transactionService = transactionService;
this.openmct = openmct;
this.domainObject = domainObject;
}
@ -48,27 +50,19 @@ define(
* or finish() are called.
*/
EditorCapability.prototype.edit = function () {
this.transactionService.startTransaction();
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
this.openmct.editor.edit();
this.domainObject.getCapability('status').set('editing', true);
};
function isEditContextRoot(domainObject) {
return domainObject.getCapability('status').get('editing');
}
function isEditing(domainObject) {
return isEditContextRoot(domainObject) ||
domainObject.hasCapability('context') &&
isEditing(domainObject.getCapability('context').getParent());
}
/**
* Determines whether this object, or any of its ancestors are
* currently being edited.
* @returns boolean
*/
EditorCapability.prototype.inEditContext = function () {
return isEditing(this.domainObject);
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
return this.openmct.editor.isEditing();
};
/**
@ -77,7 +71,8 @@ define(
* @returns {*}
*/
EditorCapability.prototype.isEditContextRoot = function () {
return isEditContextRoot(this.domainObject);
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
return this.openmct.editor.isEditing();
};
/**
@ -86,10 +81,7 @@ define(
* @returns {*}
*/
EditorCapability.prototype.save = function () {
var transactionService = this.transactionService;
return transactionService.commit().then(function () {
transactionService.startTransaction();
});
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
};
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
@ -100,16 +92,7 @@ define(
* @returns {*}
*/
EditorCapability.prototype.finish = function () {
var domainObject = this.domainObject;
if (this.transactionService.isActive()) {
return this.transactionService.cancel().then(function () {
domainObject.getCapability("status").set("editing", false);
return domainObject;
});
} else {
return Promise.resolve(domainObject);
}
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
};
/**

View File

@ -60,7 +60,7 @@ define(
};
//If the notification is dismissed by the user, close
// the dialog.
notification.onDismiss(function () {
notification.on('dismiss', function () {
dialog.dismiss();
});

View File

@ -23,33 +23,17 @@
define([
"./src/NotificationIndicatorController",
"./src/NotificationIndicator",
"./src/NotificationService",
"./res/notification-indicator.html",
'legacyRegistry'
], function (
NotificationIndicatorController,
NotificationIndicator,
NotificationService,
notificationIndicatorTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/commonUI/notification", {
"extensions": {
"constants": [
{
"key": "DEFAULT_AUTO_DISMISS",
"value": 3000
},
{
"key": "FORCE_AUTO_DISMISS",
"value": 1000
},
{
"key": "MINIMIZE_TIMEOUT",
"value": 300
}
],
"templates": [
{
"key": "notificationIndicatorTemplate",
@ -76,12 +60,11 @@ define([
"services": [
{
"key": "notificationService",
"implementation": NotificationService,
"implementation": function (openmct) {
return openmct.notifications;
},
"depends": [
"$timeout",
"topic",
"DEFAULT_AUTO_DISMISS",
"MINIMIZE_TIMEOUT"
"openmct"
]
}
]

View File

@ -1,437 +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 the notification service, which can be used to
* show banner notifications to the user. Banner notifications
* are used to inform users of events in a non-intrusive way. As
* much as possible, notifications share a model with blocking
* dialogs so that the same information can be provided in a dialog
* and then minimized to a banner notification if needed.
*
* @namespace platform/commonUI/notification
*/
define(
['moment'],
function (moment) {
/**
* A representation of a user action. Options are provided to
* dialogs and notifications and are shown as buttons.
*
* @typedef {object} NotificationOption
* @property {string} label the label to appear on the button for
* this action
* @property {function} callback a callback function to be invoked
* when the button is clicked
*/
/**
* A representation of a banner notification. Banner notifications
* are used to inform users of events in a non-intrusive way. As
* much as possible, notifications share a model with blocking
* dialogs so that the same information can be provided in a dialog
* and then minimized to a banner notification if needed, or vice-versa.
*
* @typedef {object} NotificationModel
* @property {string} title The title of the message
* @property {string} severity The importance of the message (one of
* 'info', 'alert', or 'error' where info < alert <error)
* @property {number} [progress] The completion status of a task
* represented numerically
* @property {boolean} [unknownProgress] a boolean indicating that the
* progress of the underlying task is unknown. This will result in a
* visually distinct progress bar.
* @property {boolean} [autoDismiss] If truthy, dialog will
* be automatically minimized or dismissed (depending on severity).
* Additionally, if the provided value is a number, it will be used
* as the delay period before being dismissed.
* @property {boolean} [dismissable=true] If true, notification will
* include an option to dismiss it completely.
* @property {NotificationOption} [primaryOption] the default user
* response to
* this message. Will be represented as a button with the provided
* label and action. May be used by banner notifications to display
* only the most important option to users.
* @property {NotificationOption[]} [options] any additional
* actions the user can take. Will be represented as additional buttons
* that may or may not be available from a banner.
* @see DialogModel
*/
/**
* A wrapper object that is returned as a handle to a newly created
* notification. Wraps the notifications model and decorates with
* functions to dismiss or minimize the notification.
*
* @typedef {object} Notification
* @property {function} dismiss This method is added to the object
* returned by {@link NotificationService#notify} and can be used to
* dismiss this notification. Dismissing a notification will remove
* it completely and it will not appear in the notification indicator
* @property {function} minimize This method is added to the object
* returned by {@link NotificationService#notify} and can be used to
* minimize this notification. Minimizing a notification will send
* it to the notification indicator
* @property {function} dismissOrMinimize This method is added to the
* object returned by {@link NotificationService#notify}. It will
* hide the notification by either dismissing or minimizing it,
* depending on severity. Typically this is the method that should
* be used for dismissing a notification. If more control is
* required, then the minimize or dismiss functions can be called
* individually.
* @property {function} onDismiss Allows listening for on dismiss
* events. This allows cleanup etc. when the notification is dismissed.
*/
/**
* The notification service is responsible for informing the user of
* events via the use of banner notifications.
* @memberof platform/commonUI/notification
* @constructor
* @param $timeout the Angular $timeout service
* @param defaultAutoDismissTimeout The period of time that an
* auto-dismissed message will be displayed for.
* @param minimizeAnimationTimeout When notifications are minimized, a brief
* animation is shown. This animation requires some time to execute,
* so a timeout is required before the notification is hidden
*/
function NotificationService($timeout, topic, defaultAutoDismissTimeout, minimizeAnimationTimeout) {
this.notifications = [];
this.$timeout = $timeout;
this.highest = { severity: "info" };
this.AUTO_DISMISS_TIMEOUT = defaultAutoDismissTimeout;
this.MINIMIZE_ANIMATION_TIMEOUT = minimizeAnimationTimeout;
this.topic = topic;
/*
* A context in which to hold the active notification and a
* handle to its timeout.
*/
this.active = {};
}
/**
* Minimize a notification. The notification will still be available
* from the notification list. Typically notifications with a
* severity of 'info' should not be minimized, but rather
* dismissed. If you're not sure which is appropriate,
* use {@link Notification#dismissOrMinimize}
*
* @private
*/
NotificationService.prototype.minimize = function (service, notification) {
//Check this is a known notification
var index = service.notifications.indexOf(notification);
if (service.active.timeout) {
/*
Method can be called manually (clicking dismiss) or
automatically from an auto-timeout. this.active.timeout
acts as a semaphore to prevent race conditions. Cancel any
timeout in progress (for the case where a manual dismiss
has shortcut an active auto-dismiss), and clear the
semaphore.
*/
service.$timeout.cancel(service.active.timeout);
delete service.active.timeout;
}
if (index >= 0) {
notification.model.minimized = true;
//Add a brief timeout before showing the next notification
// in order to allow the minimize animation to run through.
service.$timeout(function () {
service.setActiveNotification(service.selectNextNotification());
}, service.MINIMIZE_ANIMATION_TIMEOUT);
}
};
/**
* Completely removes a notification. This will dismiss it from the
* message banner and remove it from the list of notifications.
* Typically only notifications with a severity of info should be
* dismissed. If you're not sure whether to dismiss or minimize a
* notification, use {@link Notification#dismissOrMinimize}.
* dismiss
*
* @private
*/
NotificationService.prototype.dismiss = function (service, notification) {
//Check this is a known notification
var index = service.notifications.indexOf(notification);
if (service.active.timeout) {
/* Method can be called manually (clicking dismiss) or
* automatically from an auto-timeout. this.active.timeout
* acts as a semaphore to prevent race conditions. Cancel any
* timeout in progress (for the case where a manual dismiss
* has shortcut an active auto-dismiss), and clear the
* semaphore.
*/
service.$timeout.cancel(service.active.timeout);
delete service.active.timeout;
}
if (index >= 0) {
service.notifications.splice(index, 1);
}
service.setActiveNotification(service.selectNextNotification());
this.setHighestSeverity();
};
/**
* Depending on the severity of the notification will selectively
* dismiss or minimize where appropriate.
*
* @private
*/
NotificationService.prototype.dismissOrMinimize = function (notification) {
var model = notification.model;
if (model.severity === "info") {
if (model.autoDismiss === false) {
notification.minimize();
} else {
notification.dismiss();
}
} else {
notification.minimize();
}
};
/**
* Returns the notification that is currently visible in the banner area
* @returns {Notification}
*/
NotificationService.prototype.getActiveNotification = function () {
return this.active.notification;
};
/**
* A convenience method for info notifications. Notifications
* created via this method will be auto-dismissed after a default
* wait period unless explicitly forbidden by the caller through
* the {autoDismiss} property on the {NotificationModel}, in which
* case the notification will be minimized after the wait.
* @param {NotificationModel | string} message either a string for
* the title of the notification message, or a {@link NotificationModel}
* defining the options notification to display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
NotificationService.prototype.info = function (message) {
var notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "info";
return this.notify(notificationModel);
};
/**
* A convenience method for alert notifications. Notifications
* created via this method will will have severity of "alert" enforced
* @param {NotificationModel | string} message either a string for
* the title of the alert message with default options, or a
* {@link NotificationModel} defining the options notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
NotificationService.prototype.alert = function (message) {
var notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "alert";
return this.notify(notificationModel);
};
/**
* A convenience method for error notifications. Notifications
* created via this method will will have severity of "error" enforced
* @param {NotificationModel | string} message either a string for
* the title of the error message with default options, or a
* {@link NotificationModel} defining the options notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
NotificationService.prototype.error = function (message) {
var notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "error";
return this.notify(notificationModel);
};
/**
* @private
*/
NotificationService.prototype.setHighestSeverity = function () {
var severity = {
"info": 1,
"alert": 2,
"error": 3
};
this.highest.severity = this.notifications.reduce(function (previous, notification) {
if (severity[notification.model.severity] > severity[previous]) {
return notification.model.severity;
} else {
return previous;
}
}, "info");
};
/**
* Notifies the user of an event. If there is a banner notification
* already active, then it will be dismissed or minimized automatically,
* and the provided notification displayed in its place.
*
* @param {NotificationModel} notificationModel The notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to {@link Notification#dismiss} or {@link Notification#minimize}
*/
NotificationService.prototype.notify = function (notificationModel) {
var self = this,
notification,
activeNotification = self.active.notification,
topic = this.topic();
notificationModel.severity = notificationModel.severity || "info";
notificationModel.timestamp = moment.utc().format('YYYY-MM-DD hh:mm:ss.ms');
notification = {
model: notificationModel,
minimize: function () {
self.minimize(self, notification);
},
dismiss: function () {
self.dismiss(self, notification);
topic.notify();
},
dismissOrMinimize: function () {
self.dismissOrMinimize(notification);
},
onDismiss: function (callback) {
topic.listen(callback);
}
};
//Notifications support a 'dismissable' attribute. This is a
// convenience to support adding a 'dismiss' option to the
// notification for the common case of dismissing a
// notification. Could also be done manually by specifying an
// option on the model
if (notificationModel.dismissable !== false) {
notificationModel.options = notificationModel.options || [];
notificationModel.options.unshift({
label: "Dismiss",
callback: function () {
notification.dismiss();
}
});
}
this.notifications.push(notification);
this.setHighestSeverity();
/*
Check if there is already an active (ie. visible) notification
*/
if (!this.active.notification) {
this.setActiveNotification(notification);
} else if (!this.active.timeout) {
/*
If there is already an active notification, time it out. If it's
already got a timeout in progress (either because it has had
timeout forced because of a queue of messages, or it had an
autodismiss specified), leave it to run. Otherwise force a
timeout.
This notification has been added to queue and will be
serviced as soon as possible.
*/
this.active.timeout = this.$timeout(function () {
activeNotification.dismissOrMinimize();
}, this.AUTO_DISMISS_TIMEOUT);
}
return notification;
};
/**
* Used internally by the NotificationService
* @private
*/
NotificationService.prototype.setActiveNotification = function (notification) {
var shouldAutoDismiss;
this.active.notification = notification;
if (!notification) {
delete this.active.timeout;
return;
}
if (notification.model.severity === "info") {
shouldAutoDismiss = true;
} else {
shouldAutoDismiss = notification.model.autoDismiss;
}
if (shouldAutoDismiss || this.selectNextNotification()) {
this.active.timeout = this.$timeout(function () {
notification.dismissOrMinimize();
}, this.AUTO_DISMISS_TIMEOUT);
} else {
delete this.active.timeout;
}
};
/**
* Used internally by the NotificationService
*
* @private
*/
NotificationService.prototype.selectNextNotification = function () {
var notification,
i = 0;
/*
Loop through the notifications queue and find the first one that
has not already been minimized (manually or otherwise).
*/
for (; i < this.notifications.length; i++) {
notification = this.notifications[i];
if (!notification.model.minimized &&
notification !== this.active.notification) {
return notification;
}
}
};
return NotificationService;
}
);

View File

@ -1,275 +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 describe,it,expect,beforeEach,jasmine*/
define(
['../src/NotificationService'],
function (NotificationService) {
describe("The notification service ", function () {
var notificationService,
mockTimeout,
mockAutoDismiss,
mockMinimizeTimeout,
mockTopicFunction,
mockTopicObject,
infoModel,
alertModel,
errorModel;
function elapseTimeout() {
mockTimeout.calls.mostRecent().args[0]();
}
beforeEach(function () {
mockTimeout = jasmine.createSpy("$timeout");
mockTopicFunction = jasmine.createSpy("topic");
mockTopicObject = jasmine.createSpyObj("topicObject", ["listen", "notify"]);
mockTopicFunction.and.returnValue(mockTopicObject);
mockAutoDismiss = mockMinimizeTimeout = 1000;
notificationService = new NotificationService(mockTimeout, mockTopicFunction, mockAutoDismiss, mockMinimizeTimeout);
infoModel = {
title: "Mock Info Notification",
severity: "info"
};
alertModel = {
title: "Mock Alert Notification",
severity: "alert"
};
errorModel = {
title: "Mock Error Notification",
severity: "error"
};
});
it("notifies listeners on dismissal of notification", function () {
var dismissListener = jasmine.createSpy("ondismiss");
var notification = notificationService.notify(infoModel);
notification.onDismiss(dismissListener);
expect(mockTopicObject.listen).toHaveBeenCalled();
notification.dismiss();
expect(mockTopicObject.notify).toHaveBeenCalled();
mockTopicObject.listen.calls.mostRecent().args[0]();
expect(dismissListener).toHaveBeenCalled();
});
it("dismisses a notification when the notification's dismiss method is used", function () {
var notification = notificationService.info(infoModel);
notification.dismiss();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(0);
});
it("minimizes a notification when the notification's minimize method is used", function () {
var notification = notificationService.info(infoModel);
notification.minimize();
elapseTimeout(); // needed for the minimize animation timeout
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
describe("when receiving info notifications", function () {
it("minimizes info notifications if the caller disables auto-dismiss", function () {
infoModel.autoDismiss = false;
var notification = notificationService.info(infoModel);
elapseTimeout();
// 2nd elapse for the minimize animation timeout
elapseTimeout();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
it("dismisses info notifications if the caller ignores auto-dismiss", function () {
notificationService.info(infoModel);
elapseTimeout();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(0);
});
it("dismisses info notifications if the caller requests auto-dismiss", function () {
infoModel.autoDismiss = true;
notificationService.info(infoModel);
elapseTimeout();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(0);
});
});
describe("when receiving alert notifications", function () {
it("minimizes alert notifications if the caller enables auto-dismiss", function () {
alertModel.autoDismiss = true;
var notification = notificationService.alert(alertModel);
elapseTimeout();
elapseTimeout();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
it("keeps alert notifications active if the caller disables auto-dismiss", function () {
mockTimeout.and.callFake(function (callback, time) {
callback();
});
alertModel.autoDismiss = false;
var notification = notificationService.alert(alertModel);
expect(notificationService.getActiveNotification()).toEqual(notification);
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
it("keeps alert notifications active if the caller ignores auto-dismiss", function () {
mockTimeout.and.callFake(function (callback, time) {
callback();
});
var notification = notificationService.alert(alertModel);
expect(notificationService.getActiveNotification()).toEqual(notification);
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
});
describe("when receiving error notifications", function () {
it("minimizes error notifications if the caller enables auto-dismiss", function () {
errorModel.autoDismiss = true;
var notification = notificationService.error(errorModel);
elapseTimeout();
elapseTimeout();
expect(notificationService.getActiveNotification()).toBeUndefined();
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
it("keeps error notifications active if the caller disables auto-dismiss", function () {
mockTimeout.and.callFake(function (callback, time) {
callback();
});
errorModel.autoDismiss = false;
var notification = notificationService.error(errorModel);
expect(notificationService.getActiveNotification()).toEqual(notification);
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
it("keeps error notifications active if the caller ignores auto-dismiss", function () {
mockTimeout.and.callFake(function (callback, time) {
callback();
});
var notification = notificationService.error(errorModel);
expect(notificationService.getActiveNotification()).toEqual(notification);
expect(notificationService.notifications.length).toEqual(1);
expect(notificationService.notifications[0]).toEqual(notification);
});
});
describe("when called with multiple notifications", function () {
it("auto-dismisses the previously active notification, making the new notification active", function () {
var activeNotification;
infoModel.autoDismiss = false;
//First pre-load with a info message
notificationService.notify(infoModel);
activeNotification = notificationService.getActiveNotification();
//Initially expect the active notification to be info
expect(activeNotification.model).toBe(infoModel);
//Then notify of an error
notificationService.notify(errorModel);
//But it should be auto-dismissed and replaced with the
// error notification
elapseTimeout();
//Two timeouts, one is to force minimization after
// displaying the message for a minimum period, the
// second is to allow minimization animation to take place.
elapseTimeout();
activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(errorModel);
});
it("auto-minimizes an active error notification", function () {
var activeNotification;
//First pre-load with an error message
notificationService.notify(errorModel);
//Then notify of info
notificationService.notify(infoModel);
expect(notificationService.notifications.length).toEqual(2);
//Mock the auto-minimize
elapseTimeout();
//Two timeouts, one is to force minimization after
// displaying the message for a minimum period, the
// second is to allow minimization animation to take place.
elapseTimeout();
//Previous error message should be minimized, not
// dismissed
expect(notificationService.notifications.length).toEqual(2);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(infoModel);
expect(errorModel.minimized).toEqual(true);
});
it("auto-minimizes errors when a number of them arrive in short succession", function () {
var activeNotification,
error2 = {
title: "Second Mock Error Notification",
severity: "error"
},
error3 = {
title: "Third Mock Error Notification",
severity: "error"
};
//First pre-load with a info message
notificationService.notify(errorModel);
//Then notify of a third error
notificationService.notify(error2);
notificationService.notify(error3);
expect(notificationService.notifications.length).toEqual(3);
//Mock the auto-minimize
elapseTimeout();
//Two timeouts, one is to force minimization after
// displaying the message for a minimum period, the
// second is to allow minimization animation to take place.
elapseTimeout();
//Previous error message should be minimized, not
// dismissed
expect(notificationService.notifications.length).toEqual(3);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(error2);
expect(errorModel.minimized).toEqual(true);
//Mock the second auto-minimize
elapseTimeout();
//Two timeouts, one is to force minimization after
// displaying the message for a minimum period, the
// second is to allow minimization animation to take place.
elapseTimeout();
activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(error3);
expect(error2.minimized).toEqual(true);
});
});
});
}
);

View File

@ -19,10 +19,10 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<a class="l-hyperlink s-hyperlink" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
<a class="c-hyperlink u-links" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}"
ng-class="{
's-button': hyperlink.isButton()
}">
<span class="label">{{domainObject.getModel().displayText}}</span>
'c-hyperlink--button u-fills-container' : hyperlink.isButton(),
'c-hyperlink--link' : !hyperlink.isButton() }">
<span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span>
</a>

View File

@ -1,83 +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.
-->
<div class="abs l-layout {{ domainObject.getModel().layoutAdvancedCss }}"
ng-controller="LayoutController as controller"
ng-click="controller.bypassSelection($event)">
<!-- Background grid -->
<div class="l-grid-holder"
ng-show="!controller.drilledIn"
ng-click="controller.bypassSelection($event)">
<div class="l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
<div class="l-grid l-grid-y"
ng-if="!controller.getGridSize()[1] < 3"
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
</div>
<div class="frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}"
data-layout-id="{{childObject.getId() + '-' + $id}}"
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
ng-repeat="childObject in composition"
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
mct-selectable="controller.getContext(childObject)"
ng-dblclick="controller.drill($event, childObject)"
ng-style="controller.getFrameStyle(childObject.getId())">
<mct-representation key="'frame'"
class="t-rep-frame holder contents abs"
mct-object="childObject">
</mct-representation>
<!-- Drag handles -->
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
<span class="edit-handle edit-move"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span class="edit-corner edit-resize-nw"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [-1,-1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span class="edit-corner edit-resize-ne"
mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [1,-1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span class="edit-corner edit-resize-sw"
mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span class="edit-corner edit-resize-se"
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
</span>
</div>
</div>

View File

@ -1,524 +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 object types and associated views for
* display-building.
* @namespace platform/features/layout
*/
define(
[
'zepto',
'./LayoutDrag'
],
function (
$,
LayoutDrag
) {
var DEFAULT_DIMENSIONS = [12, 8],
DEFAULT_GRID_SIZE = [32, 32],
MINIMUM_FRAME_SIZE = [320, 180];
var DEFAULT_HIDDEN_FRAME_TYPES = [
'hyperlink'
];
/**
* The LayoutController is responsible for supporting the
* Layout view. It arranges frames according to saved configuration
* and provides methods for updating these based on mouse
* movement.
* @memberof platform/features/layout
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function LayoutController($scope, $element, openmct) {
var self = this,
callbackCount = 0;
this.$element = $element;
// Update grid size when it changed
function updateGridSize(layoutGrid) {
var oldSize = self.gridSize;
self.gridSize = layoutGrid || DEFAULT_GRID_SIZE;
// Only update panel positions if this actually changed things
if (self.gridSize[0] !== oldSize[0] ||
self.gridSize[1] !== oldSize[1]) {
self.layoutPanels(Object.keys(self.positions));
}
}
// Position a panel after a drop event
function handleDrop(e, id, position) {
if (e.defaultPrevented) {
return;
}
$scope.configuration = $scope.configuration || {};
$scope.configuration.panels = $scope.configuration.panels || {};
self.openmct.objects.get(id).then(function (object) {
$scope.configuration.panels[id] = {
position: [
Math.floor(position.x / self.gridSize[0]),
Math.floor(position.y / self.gridSize[1])
],
dimensions: self.defaultDimensions(),
hasFrame: self.getDefaultFrame(object.type)
};
// Store the id so that the newly-dropped object
// gets selected during refresh composition
self.droppedIdToSelectAfterRefresh = id;
self.commit();
// Populate template-facing position for this id
self.rawPositions[id] = $scope.configuration.panels[id];
self.populatePosition(id);
refreshComposition();
});
// Layout may contain embedded views which will
// listen for drops, so call preventDefault() so
// that they can recognize that this event is handled.
e.preventDefault();
}
//Will fetch fully contextualized composed objects, and populate
// scope with them.
function refreshComposition() {
//Keep a track of how many composition callbacks have been made
var thisCount = ++callbackCount;
$scope.domainObject.useCapability('composition').then(function (composition) {
var ids;
//Is this callback for the most recent composition
// request? If not, discard it. Prevents race condition
if (thisCount === callbackCount) {
ids = composition.map(function (object) {
return object.getId();
}) || [];
$scope.composition = composition;
self.layoutPanels(ids);
self.setFrames(ids);
if (self.selectedId &&
self.selectedId !== $scope.domainObject.getId() &&
composition.indexOf(self.selectedId) === -1) {
// Click triggers selection of layout parent.
self.$element[0].click();
}
}
});
}
// End drag; we don't want to put $scope into this
// because it triggers "cpws" (copy window or scope)
// errors in Angular.
this.endDragInScope = function () {
// Write to configuration; this is watched and
// saved by the EditRepresenter.
$scope.configuration =
$scope.configuration || {};
$scope.configuration.panels =
$scope.configuration.panels || {};
$scope.configuration.panels[self.activeDragId] =
$scope.configuration.panels[self.activeDragId] || {};
$scope.configuration.panels[self.activeDragId].position =
self.rawPositions[self.activeDragId].position;
$scope.configuration.panels[self.activeDragId].dimensions =
self.rawPositions[self.activeDragId].dimensions;
self.commit();
};
// Sets the selectable object in response to the selection change event.
function setSelection(selectable) {
var selection = selectable[0];
if (!selection) {
delete self.selectedId;
return;
}
self.selectedId = selection.context.oldItem.getId();
self.drilledIn = undefined;
self.selectable = selectable;
}
this.positions = {};
this.rawPositions = {};
this.gridSize = DEFAULT_GRID_SIZE;
this.$scope = $scope;
this.drilledIn = undefined;
this.openmct = openmct;
// Watch for changes to the grid size in the model
$scope.$watch("model.layoutGrid", updateGridSize);
// Update composed objects on screen, and position panes
$scope.$watchCollection("model.composition", refreshComposition);
openmct.selection.on('change', setSelection);
$scope.$on("$destroy", function () {
openmct.selection.off("change", setSelection);
self.unlisten();
});
$scope.$on("mctDrop", handleDrop);
self.unlisten = self.$scope.domainObject.getCapability('mutation').listen(function (model) {
$scope.configuration = model.configuration.layout;
$scope.model = model;
var panels = $scope.configuration.panels;
Object.keys(panels).forEach(function (key) {
if (self.frames && self.frames.hasOwnProperty(key)) {
self.frames[key] = panels[key].hasFrame;
}
});
});
}
// Utility function to copy raw positions from configuration,
// without writing directly to configuration (to avoid triggering
// persistence from watchers during drags).
function shallowCopy(obj, keys) {
var copy = {};
keys.forEach(function (k) {
copy[k] = obj[k];
});
return copy;
}
/**
* Set the frames value. If a configuration panel has "hasFrame' property,
* use that value, otherwise set a default value. A 'hyperlink' object should
* have no frame by default.
*
* @param {string[]} ids the object ids
* @private
*/
LayoutController.prototype.setFrames = function (ids) {
var panels = shallowCopy(this.$scope.configuration.panels || {}, ids);
this.frames = {};
this.$scope.composition.forEach(function (object) {
var id = object.getId();
panels[id] = panels[id] || {};
if (panels[id].hasOwnProperty('hasFrame')) {
this.frames[id] = panels[id].hasFrame;
} else {
this.frames[id] = this.getDefaultFrame(object.getModel().type);
}
}, this);
};
/**
* Gets the default value for frame.
*
* @param type the domain object type
* @return {boolean} true if the object should have
* frame by default, false, otherwise
*/
LayoutController.prototype.getDefaultFrame = function (type) {
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
};
// Convert from { positions: ..., dimensions: ... } to an
// appropriate ng-style argument, to position frames.
LayoutController.prototype.convertPosition = function (raw) {
var gridSize = this.gridSize;
// Multiply position/dimensions by grid size
return {
left: (gridSize[0] * raw.position[0]) + 'px',
top: (gridSize[1] * raw.position[1]) + 'px',
width: (gridSize[0] * raw.dimensions[0]) + 'px',
height: (gridSize[1] * raw.dimensions[1]) + 'px',
minWidth: (gridSize[0] * raw.dimensions[0]) + 'px',
minHeight: (gridSize[1] * raw.dimensions[1]) + 'px'
};
};
// Generate default positions for a new panel
LayoutController.prototype.defaultDimensions = function () {
var gridSize = this.gridSize;
return MINIMUM_FRAME_SIZE.map(function (min, i) {
return Math.max(
Math.ceil(min / gridSize[i]),
DEFAULT_DIMENSIONS[i]
);
});
};
// Generate a default position (in its raw format) for a frame.
// Use an index to ensure that default positions are unique.
LayoutController.prototype.defaultPosition = function (index) {
return {
position: [index, index],
dimensions: this.defaultDimensions()
};
};
// Store a computed position for a contained frame by its
// domain object id. Called in a forEach loop, so arguments
// are as expected there.
LayoutController.prototype.populatePosition = function (id, index) {
this.rawPositions[id] =
this.rawPositions[id] || this.defaultPosition(index || 0);
this.positions[id] =
this.convertPosition(this.rawPositions[id]);
};
/**
* Get a style object for a frame with the specified domain
* object identifier, suitable for use in an `ng-style`
* directive to position a frame as configured for this layout.
* @param {string} id the object identifier
* @returns {Object.<string, string>} an object with
* appropriate left, width, etc fields for positioning
*/
LayoutController.prototype.getFrameStyle = function (id) {
// Called in a loop, so just look up; the "positions"
// object is kept up to date by a watch.
return this.positions[id];
};
/**
* Start a drag gesture to move/resize a frame.
*
* The provided position and dimensions factors will determine
* whether this is a move or a resize, and what type it
* will be. For instance, a position factor of [1, 1]
* will move a frame along with the mouse as the drag
* proceeds, while a dimension factor of [0, 0] will leave
* dimensions unchanged. Combining these in different
* ways results in different handles; a position factor of
* [1, 0] and a dimensions factor of [-1, 0] will implement
* a left-edge resize, as the horizontal position will move
* with the mouse while the horizontal dimensions shrink in
* kind (and vertical properties remain unmodified.)
*
* @param {string} id the identifier of the domain object
* in the frame being manipulated
* @param {number[]} posFactor the position factor
* @param {number[]} dimFactor the dimensions factor
*/
LayoutController.prototype.startDrag = function (id, posFactor, dimFactor) {
this.activeDragId = id;
this.activeDrag = new LayoutDrag(
this.rawPositions[id],
posFactor,
dimFactor,
this.gridSize
);
};
/**
* Continue an active drag gesture.
* @param {number[]} delta the offset, in pixels,
* of the current pointer position, relative
* to its position when the drag started
*/
LayoutController.prototype.continueDrag = function (delta) {
if (this.activeDrag) {
this.rawPositions[this.activeDragId] =
this.activeDrag.getAdjustedPosition(delta);
this.populatePosition(this.activeDragId);
}
};
/**
* Compute panel positions based on the layout's object model.
* Defined as member function to facilitate testing.
* @private
*/
LayoutController.prototype.layoutPanels = function (ids) {
var configuration = this.$scope.configuration || {},
self = this;
// Pull panel positions from configuration
this.rawPositions =
shallowCopy(configuration.panels || {}, ids);
// Clear prior computed positions
this.positions = {};
// Update width/height that we are tracking
this.gridSize =
(this.$scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE;
// Compute positions and add defaults where needed
ids.forEach(function (id, index) {
self.populatePosition(id, index);
});
};
/**
* End the active drag gesture. This will update the
* view configuration.
*/
LayoutController.prototype.endDrag = function () {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
this.endDragInScope();
};
/**
* Checks if the object is currently selected.
*
* @param {string} obj the object to check for selection
* @returns {boolean} true if selected, otherwise false
*/
LayoutController.prototype.selected = function (obj) {
var sobj = this.openmct.selection.get()[0];
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
};
/**
* Bypasses selection if drag is in progress.
*
* @param event the angular event object
*/
LayoutController.prototype.bypassSelection = function (event) {
if (this.dragInProgress) {
if (event) {
event.stopPropagation();
}
return;
}
};
/**
* Checks if the domain object is drilled in.
*
* @param domainObject the domain object
* @return true if the object is drilled in, false otherwise
*/
LayoutController.prototype.isDrilledIn = function (domainObject) {
return this.drilledIn === domainObject.getId();
};
/**
* Puts the given object in the drilled-in mode.
*
* @param event the angular event object
* @param domainObject the domain object
*/
LayoutController.prototype.drill = function (event, domainObject) {
if (event) {
event.stopPropagation();
}
if (!domainObject.getCapability('editor').inEditContext()) {
return;
}
if (!domainObject.hasCapability('composition')) {
return;
}
// Disable since fixed position doesn't use the selection API yet
if (domainObject.getModel().type === 'telemetry.fixed') {
return;
}
this.drilledIn = domainObject.getId();
};
/**
* Check if the object has frame.
*
* @param {object} obj the object
* @return {boolean} true if object has frame, otherwise false
*/
LayoutController.prototype.hasFrame = function (obj) {
return this.frames[obj.getId()];
};
/**
* Get the size of the grid, in pixels. The returned array
* is in the form `[x, y]`.
* @returns {number[]} the grid size
*/
LayoutController.prototype.getGridSize = function () {
return this.gridSize;
};
/**
* Gets the selection context.
*
* @param domainObject the domain object
* @returns {object} the context object which includes item and oldItem
*/
LayoutController.prototype.getContext = function (domainObject) {
return {
item: domainObject.useCapability('adapter'),
oldItem: domainObject
};
};
LayoutController.prototype.commit = function () {
var model = this.$scope.model;
model.configuration = model.configuration || {};
model.configuration.layout = this.$scope.configuration;
this.$scope.domainObject.useCapability('mutation', function () {
return model;
});
};
/**
* Selects a newly-dropped object.
*
* @param classSelector the css class selector
* @param domainObject the domain object
*/
LayoutController.prototype.selectIfNew = function (selector, domainObject) {
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
setTimeout(function () {
$('[data-layout-id="' + selector + '"]')[0].click();
delete this.droppedIdToSelectAfterRefresh;
}.bind(this), 0);
}
};
return LayoutController;
}
);

View File

@ -1,479 +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/LayoutController",
"zepto"
],
function (
LayoutController,
$
) {
describe("The Layout controller", function () {
var mockScope,
mockEvent,
testModel,
testConfiguration,
controller,
mockCompositionCapability,
mockComposition,
mockCompositionObjects,
mockOpenMCT,
mockSelection,
mockDomainObjectCapability,
mockObjects,
unlistenFunc,
$element = [],
selectable = [];
function mockPromise(value) {
return {
then: function (thenFunc) {
return mockPromise(thenFunc(value));
}
};
}
function mockDomainObject(id) {
return {
getId: function () {
return id;
},
useCapability: function () {
return mockCompositionCapability;
},
getModel: function () {
if (id === 'b') {
return {
type : 'hyperlink'
};
} else {
return {};
}
},
getCapability: function () {
return mockDomainObjectCapability;
},
hasCapability: function (param) {
if (param === 'composition') {
return id !== 'b';
}
},
type: "testType"
};
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
["$watch", "$watchCollection", "$on"]
);
mockEvent = jasmine.createSpyObj(
'event',
['preventDefault', 'stopPropagation']
);
testModel = {};
mockComposition = ["a", "b", "c"];
mockCompositionObjects = mockComposition.map(mockDomainObject);
testConfiguration = {
panels: {
a: {
position: [20, 10],
dimensions: [5, 5]
}
}
};
unlistenFunc = jasmine.createSpy("unlisten");
mockDomainObjectCapability = jasmine.createSpyObj('capability',
['inEditContext', 'listen']
);
mockDomainObjectCapability.listen.and.returnValue(unlistenFunc);
mockCompositionCapability = mockPromise(mockCompositionObjects);
mockScope.domainObject = mockDomainObject("mockDomainObject");
mockScope.model = testModel;
mockScope.configuration = testConfiguration;
selectable[0] = {
context: {
oldItem: mockScope.domainObject
}
};
mockSelection = jasmine.createSpyObj("selection", [
'select',
'on',
'off',
'get'
]);
mockSelection.get.and.returnValue(selectable);
mockObjects = jasmine.createSpyObj('objects', [
'get'
]);
mockObjects.get.and.returnValue(mockPromise(mockDomainObject("mockObject")));
mockOpenMCT = {
selection: mockSelection,
objects: mockObjects
};
$element = $('<div></div>');
$(document).find('body').append($element);
spyOn($element[0], 'click');
spyOn(mockScope.domainObject, "useCapability").and.callThrough();
controller = new LayoutController(mockScope, $element, mockOpenMCT);
spyOn(controller, "layoutPanels").and.callThrough();
spyOn(controller, "commit");
jasmine.clock().install();
});
afterEach(function () {
$element.remove();
jasmine.clock().uninstall();
});
it("listens for selection change events", function () {
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
it("cleans up on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.calls.all()[0].args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
// Model changes will indicate that panel positions
// may have changed, for instance.
it("watches for changes to composition", function () {
expect(mockScope.$watchCollection).toHaveBeenCalledWith(
"model.composition",
jasmine.any(Function)
);
});
it("Retrieves updated composition from composition capability", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
expect(mockScope.domainObject.useCapability).toHaveBeenCalledWith(
"composition"
);
expect(controller.layoutPanels).toHaveBeenCalledWith(
mockComposition
);
});
it("Is robust to concurrent changes to composition", function () {
var secondMockComposition = ["a", "b", "c", "d"],
secondMockCompositionObjects = secondMockComposition.map(mockDomainObject),
firstCompositionCB,
secondCompositionCB;
spyOn(mockCompositionCapability, "then");
mockScope.$watchCollection.calls.mostRecent().args[1]();
mockScope.$watchCollection.calls.mostRecent().args[1]();
firstCompositionCB = mockCompositionCapability.then.calls.all()[0].args[0];
secondCompositionCB = mockCompositionCapability.then.calls.all()[1].args[0];
//Resolve promises in reverse order
secondCompositionCB(secondMockCompositionObjects);
firstCompositionCB(mockCompositionObjects);
//Expect the promise call that was initiated most recently to
// be the one used to populate scope, irrespective of order that
// it was eventually resolved
expect(mockScope.composition).toBe(secondMockCompositionObjects);
});
it("provides styles for frames, from configuration", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
expect(controller.getFrameStyle("a")).toEqual({
top: "320px",
left: "640px",
width: "160px",
height: "160px",
minWidth : '160px',
minHeight : '160px'
});
});
it("provides default styles for frames", function () {
var styleB, styleC;
// b and c do not have configured positions
mockScope.$watchCollection.calls.mostRecent().args[1]();
styleB = controller.getFrameStyle("b");
styleC = controller.getFrameStyle("c");
// Should have a position, but we don't care what
expect(styleB.left).toBeDefined();
expect(styleB.top).toBeDefined();
expect(styleC.left).toBeDefined();
expect(styleC.top).toBeDefined();
// Should have ensured some difference in position
expect(styleB).not.toEqual(styleC);
});
it("allows panels to be dragged", function () {
// Populate scope
mockScope.$watchCollection.calls.mostRecent().args[1]();
// Verify precondition
expect(testConfiguration.panels.b).not.toBeDefined();
// Do a drag
controller.startDrag("b", [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
// We do not look closely at the details here;
// that is tested in LayoutDragSpec. Just make sure
// that a configuration for b has been defined.
expect(testConfiguration.panels.b).toBeDefined();
});
it("invokes commit after drag", function () {
// Populate scope
mockScope.$watchCollection.calls.mostRecent().args[1]();
// Do a drag
controller.startDrag("b", [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
expect(controller.commit).toHaveBeenCalled();
});
it("listens for drop events", function () {
// Layout should position panels according to
// where the user dropped them, so it needs to
// listen for drop events.
expect(mockScope.$on).toHaveBeenCalledWith(
'mctDrop',
jasmine.any(Function)
);
// Verify precondition
expect(testConfiguration.panels.d).not.toBeDefined();
// Notify that a drop occurred
mockScope.$on.calls.mostRecent().args[1](
mockEvent,
'd',
{ x: 300, y: 100 }
);
expect(testConfiguration.panels.d).toBeDefined();
expect(mockEvent.preventDefault).toHaveBeenCalled();
expect(controller.commit).toHaveBeenCalled();
});
it("ignores drops when default has been prevented", function () {
// Avoids redundant drop-handling, WTD-1233
mockEvent.defaultPrevented = true;
// Notify that a drop occurred
mockScope.$on.calls.mostRecent().args[1](
mockEvent,
'd',
{ x: 300, y: 100 }
);
expect(testConfiguration.panels.d).not.toBeDefined();
});
it("ensures a minimum frame size", function () {
var styleB;
// Start with a very small frame size
testModel.layoutGrid = [1, 1];
// White-boxy; we know which watch is which
mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid);
mockScope.$watchCollection.calls.all()[0].args[1](testModel.composition);
styleB = controller.getFrameStyle("b");
// Resulting size should still be reasonably large pixel-size
expect(parseInt(styleB.width, 10)).toBeGreaterThan(63);
expect(parseInt(styleB.width, 10)).toBeGreaterThan(31);
});
it("ensures a minimum frame size on drop", function () {
var style;
// Start with a very small frame size
testModel.layoutGrid = [1, 1];
mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid);
// Add a new object to the composition
mockComposition = ["a", "b", "c", "d"];
mockCompositionObjects = mockComposition.map(mockDomainObject);
mockCompositionCapability = mockPromise(mockCompositionObjects);
// Notify that a drop occurred
mockScope.$on.calls.mostRecent().args[1](
mockEvent,
'd',
{ x: 300, y: 100 }
);
style = controller.getFrameStyle("d");
// Resulting size should still be reasonably large pixel-size
expect(parseInt(style.width, 10)).toBeGreaterThan(63);
expect(parseInt(style.height, 10)).toBeGreaterThan(31);
});
it("updates positions of existing objects on a drop", function () {
var oldStyle;
mockScope.$watchCollection.calls.mostRecent().args[1]();
oldStyle = controller.getFrameStyle("b");
expect(oldStyle).toBeDefined();
// ...drop event...
mockScope.$on.calls.mostRecent()
.args[1](mockEvent, 'b', { x: 300, y: 100 });
expect(controller.getFrameStyle("b"))
.not.toEqual(oldStyle);
});
it("allows objects to be selected", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
var childObj = mockCompositionObjects[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(controller.selected(childObj)).toBe(true);
});
it("prevents event bubbling while drag is in progress", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
var childObj = mockCompositionObjects[0];
// Do a drag
controller.startDrag(childObj.getId(), [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
// Because mouse position could cause the parent object to be selected, this should be ignored.
controller.bypassSelection(mockEvent);
expect(mockEvent.stopPropagation).toHaveBeenCalled();
// Shoud be able to select another object when dragging is done.
jasmine.clock().tick(0);
mockEvent.stopPropagation.calls.reset();
controller.bypassSelection(mockEvent);
expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
});
it("shows frames by default", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
expect(controller.hasFrame(mockCompositionObjects[0])).toBe(true);
});
it("hyperlinks hide frame by default", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
expect(controller.hasFrame(mockCompositionObjects[1])).toBe(false);
});
it("selects the parent object when selected object is removed", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
var childObj = mockCompositionObjects[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
var composition = ["b", "c"];
mockScope.$watchCollection.calls.mostRecent().args[1](composition);
expect($element[0].click).toHaveBeenCalled();
});
it("allows objects to be drilled-in only when editing", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
var childObj = mockCompositionObjects[0];
childObj.getCapability().inEditContext.and.returnValue(false);
controller.drill(mockEvent, childObj);
expect(controller.isDrilledIn(childObj)).toBe(false);
});
it("allows objects to be drilled-in only if it has sub objects", function () {
mockScope.$watchCollection.calls.mostRecent().args[1]();
var childObj = mockCompositionObjects[1];
childObj.getCapability().inEditContext.and.returnValue(true);
controller.drill(mockEvent, childObj);
expect(controller.isDrilledIn(childObj)).toBe(false);
});
it("selects a newly-dropped object", function () {
mockScope.$on.calls.mostRecent().args[1](
mockEvent,
'd',
{ x: 300, y: 100 }
);
var childObj = mockDomainObject("d");
var testElement = $("<div data-layout-id='some-id'></div>");
$element.append(testElement);
spyOn(testElement[0], 'click');
controller.selectIfNew('some-id', childObj);
jasmine.clock().tick(0);
expect(testElement[0].click).toHaveBeenCalled();
});
});
}
);

View File

@ -40,6 +40,7 @@ define([
'./styles-new/core.scss',
'./styles-new/notebook.scss',
'./ui/components/layout/Layout.vue',
'./ui/overlayService/overlayService',
'vue'
], function (
EventEmitter,
@ -61,6 +62,7 @@ define([
coreStyles,
NotebookStyles,
Layout,
OverlayService,
Vue
) {
/**
@ -221,11 +223,18 @@ define([
*/
this.indicators = new api.IndicatorAPI(this);
this.notifications = new api.NotificationAPI();
this.Dialog = api.Dialog;
this.editor = new api.EditorAPI.default(this);
this.OverlayService = new OverlayService();
this.legacyRegistry = defaultRegistry;
this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable());
this.install(this.plugins.DisplayLayout());
if (typeof BUILD_CONSTANTS !== 'undefined') {
this.install(buildInfoPlugin(BUILD_CONSTANTS));
@ -310,13 +319,17 @@ define([
this.$injector.get('objectService');
var appLayout = new Vue({
mixins: [Layout.default],
components: {
'Layout': Layout.default
},
provide: {
openmct: this
}
},
template: '<Layout ref="layout"></Layout>'
});
domElement.appendChild(appLayout.$mount().$el);
this.layout = appLayout;
this.layout = appLayout.$refs.layout;
Browse(this);
this.router.start();
this.emit('start');

View File

@ -0,0 +1,95 @@
define([
], function (
) {
const DEFAULT_VIEW_PRIORITY = 100;
const PRIORITY_LEVELS = {
"fallback": Number.NEGATIVE_INFINITY,
"default": -100,
"none": 0,
"optional": DEFAULT_VIEW_PRIORITY,
"preferred": 1000,
"mandatory": Number.POSITIVE_INFINITY
};
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
console.warn(`DEPRECATION WARNING: Migrate ${typeDefinition.key} from ${typeDefinition.bundle.path} to use the new Inspector View APIs. Legacy Inspector view support will be removed soon.`);
let representation = openmct.$injector.get('representations[]')
.filter((r) => r.key === typeDefinition.inspector)[0];
return {
key: representation.key,
name: representation.name,
cssClass: representation.cssClass,
description: representation.description,
canView: function (selection) {
if (!selection[0] || !selection[0].context.item) {
return false;
}
let domainObject = selection[0].context.item;
return domainObject.type === typeDefinition.key;
},
view: function (selection) {
let domainObject = selection[0].context.item;
let $rootScope = openmct.$injector.get('$rootScope');
let templateLinker = openmct.$injector.get('templateLinker');
let scope = $rootScope.$new();
let legacyObject = convertToLegacyObject(domainObject);
let isDestroyed = false;
scope.domainObject = legacyObject;
scope.model = legacyObject.getModel();
return {
show: function (container) {
// TODO: implement "gestures" support ?
let uses = representation.uses || [];
let promises = [];
let results = uses.map(function (capabilityKey, i) {
let result = legacyObject.useCapability(capabilityKey);
if (result.then) {
promises.push(result.then(function (r) {
results[i] = r;
}));
}
return result;
});
function link() {
if (isDestroyed) {
return;
}
uses.forEach(function (key, i) {
scope[key] = results[i];
});
templateLinker.link(
scope,
openmct.$angular.element(container),
representation
);
container.style.height = '100%';
}
if (promises.length) {
Promise.all(promises)
.then(function () {
link();
scope.$digest();
});
} else {
link();
}
},
destroy: function () {
scope.$destroy();
}
}
}
};
};
return TypeInspectorViewProvider;
});

View File

@ -1,8 +1,10 @@
define([
'./LegacyViewProvider',
'./TypeInspectorViewProvider',
'../../api/objects/object-utils'
], function (
LegacyViewProvider,
TypeInspectorViewProvider,
objectUtils
) {
function installLegacyViews(openmct, legacyViews, instantiate) {
@ -16,6 +18,13 @@ define([
legacyViews.forEach(function (legacyView) {
openmct.objectViews.addProvider(new LegacyViewProvider(legacyView, openmct, convertToLegacyObject));
});
let inspectorTypes = openmct.$injector.get('types[]')
.filter((t) => t.hasOwnProperty('inspector'));
inspectorTypes.forEach(function (typeDefinition) {
openmct.inspectorViews.addProvider(new TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject));
});
}
return installLegacyViews;

83
src/api/Editor.js Normal file
View File

@ -0,0 +1,83 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
export default class Editor extends EventEmitter {
constructor(openmct) {
super();
this.editing = false;
this.openmct = openmct;
}
/**
* Initiate an editing session. This will start a transaction during
* which any persist operations will be deferred until either save()
* or finish() are called.
*/
edit() {
this.editing = true;
this.getTransactionService().startTransaction();
this.emit('isEditing', true);
}
/**
* @returns true if the application is in edit mode, false otherwise.
*/
isEditing() {
return this.editing;
}
/**
* Save any unsaved changes from this editing session. This will
* end the current transaction.
*/
save() {
return this.getTransactionService().commit().then((result)=>{
this.editing = false;
this.emit('isEditing', false);
return result
}).catch((error)=>{
throw error;
});
}
/**
* End the currently active transaction and discard unsaved changes.
*/
cancel() {
this.getTransactionService().cancel();
this.editing = false;
this.emit('isEditing', false);
}
/**
* @private
*/
getTransactionService() {
if (!this.transactionService) {
this.transactionService = this.openmct.$injector.get('transactionService');
}
return this.transactionService;
}
}

View File

@ -28,7 +28,10 @@ define([
'./ui/Dialog',
'./ui/GestureAPI',
'./telemetry/TelemetryAPI',
'./indicators/IndicatorAPI'
'./indicators/IndicatorAPI',
'./notifications/NotificationAPI',
'./Editor'
], function (
TimeAPI,
ObjectAPI,
@ -37,7 +40,9 @@ define([
Dialog,
GestureAPI,
TelemetryAPI,
IndicatorAPI
IndicatorAPI,
NotificationAPI,
EditorAPI
) {
return {
TimeAPI: TimeAPI,
@ -47,6 +52,8 @@ define([
TypeRegistry: TypeRegistry,
GestureAPI: GestureAPI,
TelemetryAPI: TelemetryAPI,
IndicatorAPI: IndicatorAPI
IndicatorAPI: IndicatorAPI,
NotificationAPI: NotificationAPI.default,
EditorAPI: EditorAPI
};
});

View File

@ -28,7 +28,7 @@ define([
) {
function IndicatorAPI(openmct) {
this.openmct = openmct;
this.indicatorElements = [];
this.indicatorObjects = [];
}
IndicatorAPI.prototype.simpleIndicator = function () {
@ -55,12 +55,7 @@ define([
*
*/
IndicatorAPI.prototype.add = function (indicator) {
// So that we can consistently position indicator elements,
// guarantee that they are wrapped in an element we control
var wrapperNode = document.createElement('div');
wrapperNode.className = 'h-indicator';
wrapperNode.appendChild(indicator.element);
this.indicatorElements.push(wrapperNode);
this.indicatorObjects.push(indicator);
};
return IndicatorAPI;

View File

@ -0,0 +1,48 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
export default class MCTNotification extends EventEmitter {
constructor(notificationModel, notificationAPI) {
super();
this.notifications = notificationAPI;
this.model = notificationModel;
this.initializeModel();
}
minimize() {
this.notifications.minimize(this);
}
dismiss() {
this.notifications.dismiss(this)
}
dismissOrMinimize() {
this.notifications.dismissOrMinimize(this);
}
initializeModel() {
this.model.minimized = this.model.minimized || false;
}
}

View File

@ -0,0 +1,353 @@
/*****************************************************************************
* 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 the notification service, which can be used to
* show banner notifications to the user. Banner notifications
* are used to inform users of events in a non-intrusive way. As
* much as possible, notifications share a model with blocking
* dialogs so that the same information can be provided in a dialog
* and then minimized to a banner notification if needed.
*
* @namespace platform/api/notifications
*/
import moment from 'moment';
import EventEmitter from 'EventEmitter';
import MCTNotification from './MCTNotification.js';
/**
* A representation of a banner notification. Banner notifications
* are used to inform users of events in a non-intrusive way. As
* much as possible, notifications share a model with blocking
* dialogs so that the same information can be provided in a dialog
* and then minimized to a banner notification if needed, or vice-versa.
*
* @typedef {object} NotificationModel
* @property {string} title The title of the message
* @property {string} severity The importance of the message (one of
* 'info', 'alert', or 'error' where info < alert <error)
* @property {number} [progress] The completion status of a task
* represented numerically
* @property {boolean} [unknownProgress] a boolean indicating that the
* progress of the underlying task is unknown. This will result in a
* visually distinct progress bar.
* @property {boolean} [autoDismiss] If truthy, dialog will
* be automatically minimized or dismissed (depending on severity).
* Additionally, if the provided value is a number, it will be used
* as the delay period before being dismissed.
* @property {boolean} [dismissable=true] If true, notification will
* include an option to dismiss it completely.
* @see DialogModel
*/
const DEFAULT_AUTO_DISMISS_TIMEOUT = 3000;
const MINIMIZE_ANIMATION_TIMEOUT = 300;
/**
* The notification service is responsible for informing the user of
* events via the use of banner notifications.
* @memberof platform/commonUI/notification
* @constructor
* @param defaultAutoDismissTimeout The period of time that an
* auto-dismissed message will be displayed for.
* @param minimizeAnimationTimeout When notifications are minimized, a brief
* animation is shown. This animation requires some time to execute,
* so a timeout is required before the notification is hidden
*/
export default class NotificationAPI extends EventEmitter {
constructor() {
super();
this.notifications = [];
this.highest = { severity: "info" };
/*
* A context in which to hold the active notification and a
* handle to its timeout.
*/
this.activeNotification = undefined;
}
/**
* Minimize a notification. The notification will still be available
* from the notification list. Typically notifications with a
* severity of 'info' should not be minimized, but rather
* dismissed. If you're not sure which is appropriate,
* use {@link Notification#dismissOrMinimize}
*
* @private
*/
minimize(notification) {
//Check this is a known notification
let index = this.notifications.indexOf(notification);
if (this.activeTimeout) {
/*
Method can be called manually (clicking dismiss) or
automatically from an auto-timeout. this.activeTimeout
acts as a semaphore to prevent race conditions. Cancel any
timeout in progress (for the case where a manual dismiss
has shortcut an active auto-dismiss), and clear the
semaphore.
*/
clearTimeout(this.activeTimeout);
delete this.activeTimeout;
}
if (index >= 0) {
notification.model.minimized = true;
//Add a brief timeout before showing the next notification
// in order to allow the minimize animation to run through.
setTimeout(() => {
notification.emit('destroy');
this.setActiveNotification(this.selectNextNotification());
}, MINIMIZE_ANIMATION_TIMEOUT);
}
}
/**
* Completely removes a notification. This will dismiss it from the
* message banner and remove it from the list of notifications.
* Typically only notifications with a severity of info should be
* dismissed. If you're not sure whether to dismiss or minimize a
* notification, use {@link Notification#dismissOrMinimize}.
* dismiss
*
* @private
*/
dismiss(notification) {
//Check this is a known notification
let index = this.notifications.indexOf(notification);
if (this.activeTimeout) {
/* Method can be called manually (clicking dismiss) or
* automatically from an auto-timeout. this.activeTimeout
* acts as a semaphore to prevent race conditions. Cancel any
* timeout in progress (for the case where a manual dismiss
* has shortcut an active auto-dismiss), and clear the
* semaphore.
*/
clearTimeout(this.activeTimeout);
delete this.activeTimeout;
}
if (index >= 0) {
this.notifications.splice(index, 1);
}
this.setActiveNotification(this.selectNextNotification());
this.setHighestSeverity();
notification.emit('destroy');
}
/**
* Depending on the severity of the notification will selectively
* dismiss or minimize where appropriate.
*
* @private
*/
dismissOrMinimize(notification) {
let model = notification.model;
if (model.severity === "info") {
if (model.autoDismiss === false) {
this.minimize(notification);
} else {
this.dismiss(notification);
}
} else {
this.minimize(notification);
}
}
/**
* Returns the notification that is currently visible in the banner area
* @returns {Notification}
*/
getActiveNotification() {
return this.activeNotification;
}
/**
* A convenience method for info notifications. Notifications
* created via this method will be auto-destroy after a default
* wait period unless explicitly forbidden by the caller through
* the {autoDismiss} property on the {NotificationModel}, in which
* case the notification will be minimized after the wait.
* @param {NotificationModel | string} message either a string for
* the title of the notification message, or a {@link NotificationModel}
* defining the options notification to display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
info(message) {
let notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "info";
return this.notify(notificationModel);
}
/**
* A convenience method for alert notifications. Notifications
* created via this method will will have severity of "alert" enforced
* @param {NotificationModel | string} message either a string for
* the title of the alert message with default options, or a
* {@link NotificationModel} defining the options notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
alert(message) {
let notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "alert";
return this.notify(notificationModel);
}
/**
* A convenience method for error notifications. Notifications
* created via this method will will have severity of "error" enforced
* @param {NotificationModel | string} message either a string for
* the title of the error message with default options, or a
* {@link NotificationModel} defining the options of the notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
error(message) {
let notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "error";
return this.notify(notificationModel);
}
/**
* @private
*/
setHighestSeverity() {
let severity = {
"info": 1,
"alert": 2,
"error": 3
};
this.highest.severity = this.notifications.reduce((previous, notification) => {
if (severity[notification.model.severity] > severity[previous]) {
return notification.model.severity;
} else {
return previous;
}
}, "info");
}
/**
* Notifies the user of an event. If there is a banner notification
* already active, then it will be dismissed or minimized automatically,
* and the provided notification displayed in its place.
*
* @param {NotificationModel} notificationModel The notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to {@link Notification#dismiss} or {@link Notification#minimize}
*/
notify(notificationModel) {
let notification;
let activeNotification = this.activeNotification;
notificationModel.severity = notificationModel.severity || "info";
notificationModel.timestamp = moment.utc().format('YYYY-MM-DD hh:mm:ss.ms');
notification = new MCTNotification(notificationModel, this);
this.notifications.push(notification);
this.setHighestSeverity();
/*
Check if there is already an active (ie. visible) notification
*/
if (!this.activeNotification) {
this.setActiveNotification(notification);
} else if (!this.activeTimeout) {
/*
If there is already an active notification, time it out. If it's
already got a timeout in progress (either because it has had
timeout forced because of a queue of messages, or it had an
autodismiss specified), leave it to run. Otherwise force a
timeout.
This notification has been added to queue and will be
serviced as soon as possible.
*/
this.activeTimeout = setTimeout(() => {
this.dismissOrMinimize(activeNotification);
}, DEFAULT_AUTO_DISMISS_TIMEOUT);
}
return notification;
}
/**
* Used internally by the NotificationService
* @private
*/
setActiveNotification(notification) {
let shouldAutoDismiss;
this.activeNotification = notification;
if (!notification) {
delete this.activeTimeout;
return;
}
this.emit('notification', notification);
if (notification.model.severity === "info") {
shouldAutoDismiss = true;
} else {
shouldAutoDismiss = notification.model.autoDismiss;
}
if (shouldAutoDismiss || this.selectNextNotification()) {
this.activeTimeout = setTimeout(() => {
this.dismissOrMinimize(notification);
}, DEFAULT_AUTO_DISMISS_TIMEOUT);
} else {
delete this.activeTimeout;
}
}
/**
* Used internally by the NotificationService
*
* @private
*/
selectNextNotification() {
let notification;
let i = 0;
/*
Loop through the notifications queue and find the first one that
has not already been minimized (manually or otherwise).
*/
for (; i < this.notifications.length; i++) {
notification = this.notifications[i];
if (!notification.model.minimized &&
notification !== this.activeNotification) {
return notification;
}
}
}
}

View File

@ -56,7 +56,6 @@ define([
'../platform/features/clock/bundle',
'../platform/features/fixed/bundle',
'../platform/features/imagery/bundle',
'../platform/features/layout/bundle',
'../platform/features/my-items/bundle',
'../platform/features/pages/bundle',
'../platform/features/hyperlink/bundle',
@ -99,7 +98,6 @@ define([
'platform/features/clock',
'platform/features/fixed',
'platform/features/imagery',
'platform/features/layout',
'platform/features/pages',
'platform/features/hyperlink',
'platform/features/timeline',

View File

@ -0,0 +1,327 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template>
<div class="l-layout"
@dragover="handleDragOver"
@click="bypassSelection"
@drop="handleDrop">
<div class="l-layout__object">
<!-- Background grid -->
<div class="l-layout__grid-holder c-grid"
v-if="!drilledIn">
<div class="c-grid__x l-grid l-grid-x"
v-if="gridSize[0] >= 3"
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
</div>
<div class="c-grid__y l-grid l-grid-y"
v-if="gridSize[1] >= 3"
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
</div>
<layout-frame v-for="item in frameItems"
class="l-layout__frame"
:key="item.id"
:item="item"
:gridSize="gridSize"
@drilledIn="updateDrilledInState"
@dragInProgress="updatePosition"
@endDrag="endDrag">
</layout-frame>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.l-layout,
.c-grid,
.c-grid__x,
.c-grid__y {
@include abs();
}
.l-layout {
display: flex;
flex-direction: column;
&__grid-holder {
display: none;
}
&__object {
flex: 1 1 auto;
overflow: auto;
}
&__frame {
position: absolute;
}
}
.c-grid {
pointer-events: none;
&__x { @include bgTicks($colorGridLines, 'x'); }
&__y { @include bgTicks($colorGridLines, 'y'); }
}
.is-editing {
.l-shell__main-container > .l-layout {
// Target the top-most layout container and color its background
background: rgba($editColor, 0.1);
}
.s-selected,
.s-selected-parent {
.l-layout {
// Show the layout grid for the top-most child of the current selection,
// and hide the grid for deeper nested levels.
[class*="__grid-holder"] {
display: block;
}
.l-layout [class*="__grid-holder"] {
display: none;
}
}
}
}
</style>
<script>
import LayoutFrame from './LayoutFrame.vue';
const DEFAULT_GRID_SIZE = [32, 32],
DEFAULT_DIMENSIONS = [12, 8],
DEFAULT_POSITION = [0, 0],
MINIMUM_FRAME_SIZE = [320, 180],
DEFAULT_HIDDEN_FRAME_TYPES = [
'hyperlink'
];
export default {
data() {
return {
gridSize: [],
frameItems: [],
frames: [],
frameStyles: [],
rawPositions: {},
initSelect: true,
drilledIn: undefined
}
},
inject: ['openmct'],
props: ['domainObject'],
components: {
LayoutFrame
},
created: function () {
this.newDomainObject = this.domainObject;
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;
this.composition = this.openmct.composition.get(this.newDomainObject);
let panels = (((this.newDomainObject.configuration || {}).layout || {}).panels || {});
if (this.composition !== undefined) {
this.composition.load().then((composition) => {
composition.forEach(function (domainObject) {
this.readLayoutConfiguration(domainObject, panels);
this.makeFrameItem(domainObject, false);
}.bind(this));
this.composition.on('add', this.onAddComposition);
this.composition.on('remove', this.onRemoveComposition);
});
}
this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) {
this.newDomainObject = JSON.parse(JSON.stringify(obj));
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;;
}.bind(this));
},
methods: {
readLayoutConfiguration(domainObject, panels) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
this.rawPositions[id] = {
position: panels[id].position || DEFAULT_POSITION,
dimensions: panels[id].dimensions || this.defaultDimensions()
};
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
this.frames[id] = panels[id].hasOwnProperty('hasFrame') ?
panels[id].hasFrame :
this.hasFrameByDefault(domainObject.type);
},
makeFrameItem(domainObject, initSelect) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
this.frameItems.push({
id: id,
hasFrame: this.frames[id],
domainObject,
style: this.frameStyles[id],
drilledIn: this.isDrilledIn(id),
initSelect: initSelect,
rawPosition: this.rawPositions[id]
});
},
onAddComposition(domainObject) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
this.rawPositions[id] = {
position: [
Math.floor(this.droppedObjectPosition.x / this.gridSize[0]),
Math.floor(this.droppedObjectPosition.y / this.gridSize[1])
],
dimensions: this.defaultDimensions()
};
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
this.frames[id] = this.hasFrameByDefault(domainObject.type);
let newPanel = this.rawPositions[id];
newPanel.hasFrame = this.frames[id];
this.mutate("configuration.layout.panels[" + id + "]", newPanel);
this.makeFrameItem(domainObject, true);
},
onRemoveComposition(identifier) {
// TODO: remove the object from frameItems
},
defaultDimensions() {
let gridSize = this.gridSize;
return MINIMUM_FRAME_SIZE.map(function (min, i) {
return Math.max(
Math.ceil(min / gridSize[i]),
DEFAULT_DIMENSIONS[i]
);
});
},
convertPosition(raw) {
return {
left: (this.gridSize[0] * raw.position[0]) + 'px',
top: (this.gridSize[1] * raw.position[1]) + 'px',
width: (this.gridSize[0] * raw.dimensions[0]) + 'px',
height: (this.gridSize[1] * raw.dimensions[1]) + 'px',
minWidth: (this.gridSize[0] * raw.dimensions[0]) + 'px',
minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px'
};
},
/**
* Checks if the frame should be hidden or not.
*
* @param type the domain object type
* @return {boolean} true if the object should have
* frame by default, false, otherwise
*/
hasFrameByDefault(type) {
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
},
setSelection(selection) {
if (selection.length === 0) {
return;
}
this.updateDrilledInState();
},
updateDrilledInState(id) {
this.drilledIn = id;
this.frameItems.forEach(function (item) {
item.drilledIn = item.id === id;
});
},
isDrilledIn(id) {
return this.drilledIn === id;
},
updatePosition(id, newPosition) {
let newStyle = this.convertPosition(newPosition);
this.frameStyles[id] = newStyle;
this.rawPositions[id] = newPosition;
this.frameItems.forEach(function (item) {
if (item.id === id) {
item.style = newStyle;
item.rawPosition = newPosition;
}
});
},
bypassSelection($event) {
if (this.dragInProgress) {
if ($event) {
$event.stopImmediatePropagation();
}
return;
}
},
endDrag(id) {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
let path = "configuration.layout.panels[" + id + "]";
this.mutate(path + ".dimensions", this.rawPositions[id].dimensions);
this.mutate(path + ".position", this.rawPositions[id].position);
},
mutate(path, value) {
this.openmct.objects.mutate(this.newDomainObject, path, value);
},
handleDrop($event) {
$event.preventDefault();
let child = JSON.parse($event.dataTransfer.getData('domainObject'));
let duplicates = [];
let composition = this.newDomainObject.composition;
composition.forEach((object) => {
if (this.openmct.objects.makeKeyString(JSON.parse(JSON.stringify(object))) ===
this.openmct.objects.makeKeyString(child.identifier)) {
duplicates.push(object);
}
});
// Disallow adding a duplicate object to the composition
if (duplicates.length !== 0) {
return;
}
let elementRect = this.$el.getBoundingClientRect();
this.droppedObjectPosition = {
x: $event.pageX - elementRect.left,
y: $event.pageY - elementRect.top
}
// TODO: use the composition API to add child once the default composition
// provider supports it instead of mutating the composition directly.
// this.composition.add(child).
composition.push(child.identifier);
this.mutate('composition', composition);
},
handleDragOver($event){
$event.preventDefault();
}
},
mounted() {
this.openmct.selection.on('change', this.setSelection);
},
destroyed: function () {
this.composition.off('add', this.onAddComposition);
this.composition.off('remove', this.onRemoveComposition);
this.openmct.off('change', this.selection);
this.unlisten();
}
}
</script>

View File

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

View File

@ -0,0 +1,115 @@
/*****************************************************************************
* 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 () {
/**
* Handles drag interactions on frames in layouts. This will
* provides new positions/dimensions for frames based on
* relative pixel positions provided; these will take into account
* the grid size (in a snap-to sense) and will enforce some minimums
* on both position and dimensions.
*
* The provided position and dimensions factors will determine
* whether this is a move or a resize, and what type of resize it
* will be. For instance, a position factor of [1, 1]
* will move a frame along with the mouse as the drag
* proceeds, while a dimension factor of [0, 0] will leave
* dimensions unchanged. Combining these in different
* ways results in different handles; a position factor of
* [1, 0] and a dimensions factor of [-1, 0] will implement
* a left-edge resize, as the horizontal position will move
* with the mouse while the horizontal dimensions shrink in
* kind (and vertical properties remain unmodified.)
*
* @param {object} rawPosition the initial position/dimensions
* of the frame being interacted with
* @param {number[]} posFactor the position factor
* @param {number[]} dimFactor the dimensions factor
* @param {number[]} the size of each grid element, in pixels
* @constructor
* @memberof platform/features/layout
*/
function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) {
this.rawPosition = rawPosition;
this.posFactor = posFactor;
this.dimFactor = dimFactor;
this.gridSize = gridSize;
}
// Convert a delta from pixel coordinates to grid coordinates,
// rounding to whole-number grid coordinates.
function toGridDelta(gridSize, pixelDelta) {
return pixelDelta.map(function (v, i) {
return Math.round(v / gridSize[i]);
});
}
// Utility function to perform element-by-element multiplication
function multiply(array, factors) {
return array.map(function (v, i) {
return v * factors[i];
});
}
// Utility function to perform element-by-element addition
function add(array, other) {
return array.map(function (v, i) {
return v + other[i];
});
}
// Utility function to perform element-by-element max-choosing
function max(array, other) {
return array.map(function (v, i) {
return Math.max(v, other[i]);
});
}
/**
* Get a new position object in grid coordinates, with
* position and dimensions both offset appropriately
* according to the factors supplied in the constructor.
* @param {number[]} pixelDelta the offset from the
* original position, in pixels
*/
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
var gridDelta = toGridDelta(this.gridSize, pixelDelta);
return {
position: max(add(
this.rawPosition.position,
multiply(gridDelta, this.posFactor)
), [0, 0]),
dimensions: max(add(
this.rawPosition.dimensions,
multiply(gridDelta, this.dimFactor)
), [1, 1])
};
};
return LayoutDrag;
}
);

View File

@ -0,0 +1,338 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template>
<div class="c-frame has-local-controls is-selectable is-moveable"
:style="item.style"
:class="classObject"
@dblclick="drill(item.id, $event)">
<div class="c-frame__header">
<div class="c-frame__header__start">
<div class="c-frame__name icon-object">{{ item.domainObject.name }}</div>
<div class="c-frame__context-actions c-disclosure-button"></div>
</div>
<div class="c-frame__header__end">
<div class="c-button icon-expand local-controls--hidden"></div>
</div>
</div>
<object-view class="c-frame__object-view"
:object="item.domainObject"></object-view>
<!-- Drag handles -->
<div class="c-frame-edit">
<div class="c-frame-edit__move"
@mousedown="startDrag([1,1], [0,0], $event)"></div>
<div class="c-frame-edit__handle --nw"
@mousedown="startDrag([1,1], [-1,-1], $event)"></div>
<div class="c-frame-edit__handle --ne"
@mousedown="startDrag([0,1], [1,-1], $event)"></div>
<div class="c-frame-edit__handle --sw"
@mousedown="startDrag([1,0], [-1,1], $event)"></div>
<div class="c-frame-edit__handle --se"
@mousedown="startDrag([0,0], [1,1], $event)"></div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
/******************************* FRAME */
.c-frame {
display: flex;
flex-direction: column;
border-width: 1px;
border-color: transparent;
/*************************** HEADER */
&__header {
display: flex;
align-items: center;
flex: 0 0 auto;
margin-bottom: $interiorMargin;
> [class*="__"] {
display: flex;
align-items: center;
}
> * + * {
margin-left: $interiorMargin;
}
[class*="__start"] {
flex: 1 1 auto;
overflow: hidden;
}
[class*="__end"] {
//justify-content: flex-end;
flex: 0 0 auto;
[class*="button"] {
font-size: 0.7em;
}
}
}
&__name {
@include ellipsize();
flex: 0 1 auto;
font-size: 1.2em;
&:before {
// Object type icon
flex: 0 0 auto;
margin-right: $interiorMarginSm;
}
}
/*************************** OBJECT VIEW */
&__object-view {
flex: 1 1 auto;
overflow: auto;
.c-object-view {
.u-fills-container {
// Expand component types that fill a container
@include abs();
}
}
}
/*************************** NO-FRAME */
&.no-frame {
> [class*="__header"] {
display: none;
}
}
&:not(.no-frame) {
background: $colorBodyBg;
border: 1px solid $colorInteriorBorder;
padding: $interiorMargin;
}
/*************************** SELECTION */
&.is-selectable {
&:hover {
box-shadow: $browseShdwSelectableHov;
}
}
&.s-selected, // LEGACY
&.is-selected {
border: $browseBorderSelected;
}
}
/*************************** EDITING */
.is-editing {
.c-frame {
&:not(.is-drilled-in).is-selectable {
border: $editBorderSelectable;
&:hover {
border: $editBorderSelectableHov;
}
&.s-selected,
&.is-selected {
border: $editBorderSelected;
> .c-frame-edit {
display: block; // Show the editing rect and handles
}
}
}
&.is-drilled-in {
border: $editBorderDrilledIn;
}
.u-links {
// Applied in markup to objects that provide links. Disable while editing.
pointer-events: none;
}
}
}
.c-frame-edit {
// The editing rect and handles
$z: 10;
@include abs();
box-shadow: rgba($editColor, 0.5) 0 0 10px;
display: none;
&__move {
@include abs();
cursor: move;
z-index: $z;
}
&__handle {
$d: 8px;
$o: floor($d * -0.5);
background: rgba($editColor, 0.3);
border: 1px solid $editColor;
position: absolute;
width: $d; height: $d;
top: auto; right: auto; bottom: auto; left: auto;
z-index: $z + 1;
&:before {
// Extended hit area
$m: -5px;
content: '';
display: block;
position: absolute;
top: $m; right: $m; bottom: $m; left: $m;
z-index: -1;
}
&:hover {
background: $editColor;
}
&.--nw {
cursor: nw-resize;
left: $o; top: $o;
}
&.--ne {
cursor: ne-resize;
right: $o; top: $o;
}
&.--se {
cursor: se-resize;
right: $o; bottom: $o;
}
&.--sw {
cursor: sw-resize;
left: $o; bottom: $o;
}
}
}
</style>
<script>
import ObjectView from '../../ui/components/layout/ObjectView.vue'
import LayoutDrag from './LayoutDrag'
export default {
inject: ['openmct'],
props: {
item: Object,
gridSize: Array
},
components: {
ObjectView
},
computed: {
classObject: function () {
return {
'is-drilled-in': this.item.drilledIn,
'no-frame': !this.item.hasFrame
}
}
},
methods: {
drill(id, $event) {
if ($event) {
$event.stopPropagation();
}
if (!this.isBeingEdited(this.item.domainObject)) {
return;
}
if (this.openmct.composition.get(this.item.domainObject) === undefined) {
return;
}
// Disable for fixed position.
if (this.item.domainObject.type === 'telemetry.fixed') {
return;
}
this.$emit('drilledIn', id);
},
isBeingEdited(object) {
// TODO: add logic when inEditContext() is implemented in Vue.
return true;
},
updatePosition(event) {
let currentPosition = [event.pageX, event.pageY];
this.initialPosition = this.initialPosition || currentPosition;
this.delta = currentPosition.map(function (value, index) {
return value - this.initialPosition[index];
}.bind(this));
},
startDrag(posFactor, dimFactor, event) {
document.body.addEventListener('mousemove', this.continueDrag);
document.body.addEventListener('mouseup', this.endDrag);
this.updatePosition(event);
this.activeDrag = new LayoutDrag(
this.item.rawPosition,
posFactor,
dimFactor,
this.gridSize
);
event.preventDefault();
},
continueDrag(event) {
event.preventDefault();
this.updatePosition(event);
if (this.activeDrag) {
this.$emit('dragInProgress', this.item.id, this.activeDrag.getAdjustedPosition(this.delta));
}
},
endDrag(event) {
document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endDrag);
this.continueDrag(event);
this.$emit('endDrag', this.item.id);
this.initialPosition = undefined;
event.preventDefault();
}
},
mounted() {
this.removeSelectable = this.openmct.selection.selectable(
this.$el,
{
item: this.item.domainObject
},
this.item.initSelect
);
},
destroyed() {
this.removeSelectable();
}
}
</script>

View File

@ -0,0 +1,67 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Layout from './DisplayLayout.vue'
import Vue from 'vue'
import objectUtils from '../../api/objects/object-utils.js'
import DisplayLayoutType from './DisplayLayoutType.js'
export default function () {
return function (openmct) {
openmct.objectViews.addProvider({
key: 'layout.view',
canView: function (domainObject) {
return domainObject.type === 'layout';
},
view: function (domainObject) {
let component;
return {
show(container) {
component = new Vue({
components: {
Layout
},
template: '<layout :domain-object="domainObject"></layout>',
provide: {
openmct,
objectUtils
},
el: container,
data () {
return {
domainObject: domainObject
}
}
});
},
destroy() {
component.$destroy();
}
};
},
priority() {
return 100;
}
});
openmct.types.addType('layout', DisplayLayoutType());
}
}

View File

@ -14,9 +14,9 @@
:title="item.model.name">{{item.model.name}}</div>
<div class="c-grid-item__metadata"
:title="item.type.name">
<span>{{item.type.name}}</span>
<span v-if="item.model.composition !== undefined">
- {{item.model.composition.length}} item<span v-if="item.model.composition.length !== 1">s</span>
<span class="c-grid-item__metadata__type">{{item.type.name}}</span>
<span class="c-grid-item__metadata__item-count" v-if="item.model.composition !== undefined">
{{item.model.composition.length}} item<span v-if="item.model.composition.length !== 1">s</span>
</span>
</div>
</div>
@ -45,8 +45,8 @@
body.desktop & {
flex-flow: row wrap;
&__item {
height: $ueBrowseGridItemLg;
width: $ueBrowseGridItemLg;
height: $gridItemDesk;
width: $gridItemDesk;
margin: 0 $interiorMargin $interiorMargin 0;
}
}
@ -62,8 +62,8 @@
&__type-icon {
filter: $colorKeyFilter;
flex: 0 0 32px;
font-size: 2em; // Drives the size of the alias indicator when present
flex: 0 0 $gridItemMobile;
font-size: floor($gridItemMobile / 2);
margin-right: $interiorMarginLg;
}
@ -84,13 +84,22 @@
&__name {
@include ellipsize();
color: $colorItemFg;
font-size: 1.3em;
font-size: 1.2em;
font-weight: 400;
margin-bottom: $interiorMarginSm;
}
&__metadata {
color: $colorItemFgDetails;
font-size: 0.9em;
body.mobile & {
[class*='__item-count'] {
&:before {
content: ' - ';
}
}
}
}
&__controls {
@ -136,8 +145,8 @@
&__type-icon {
flex: 1 1 auto;
font-size: 6em; // Drives the size of the alias indicator when present
margin: $interiorMargin 22.5%;
font-size: floor($gridItemDesk / 3);
margin: $interiorMargin 22.5% $interiorMargin * 3 22.5%;
order: 2;
transform: scale(0.9);
transform-origin: center;
@ -149,6 +158,20 @@
justify-content: flex-end;
order: 3;
}
&__metadata {
display: flex;
&__type {
flex: 1 1 auto;
@include ellipsize();
}
&__item-count {
opacity: 0.7;
flex: 0 0 auto;
}
}
}
}
</style>

View File

@ -8,8 +8,7 @@
<span>{{formatTime(entry.createdOn, 'HH:mm:ss')}}</span>
</div>
<div class="c-ne__content">
<!-- TODO: fix styling for c-input-inline when SCSS is merged and remove s-input-inline class here -->
<div class="c-ne__text c-input-inline s-input-inline"
<div class="c-ne__text c-input-inline"
contenteditable="true"
ref="contenteditable"
v-on:blur="textBlur($event, entry.id)"

View File

@ -0,0 +1,29 @@
<div class="u-contents">
<div class="t-snapshot abs l-view-header">
<div class="abs object-browse-bar l-flex-row">
<div class="left flex-elem l-flex-row grows">
<div class="object-header flex-elem l-flex-row grows">
<div class="type-icon flex-elem embed-icon holder" v-bind:class="embed.cssClass"></div>
<div class="title-label flex-elem holder flex-can-shrink">{{embed.name}}</div>
</div>
</div>
</div>
</div>
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div>
<a class="s-button icon-pencil" title="Annotate">
<span class="title-label">Annotate</span>
</a>
</div>
<div class="abs object-holder t-image-holder s-image-holder">
<div
class="image-main s-image-main"
v-bind:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }">
</div>
</div>
</div>

View File

@ -24,11 +24,15 @@ define([
'moment',
'zepto',
'../utils/SnapshotOverlay',
'../../res/templates/snapshotTemplate.html',
'vue'
],
function (
Moment,
$,
SnapshotOverlay
SnapshotOverlay,
SnapshotTemplate,
Vue
) {
function EmbedController (openmct, domainObject) {
this.openmct = openmct;
@ -53,11 +57,24 @@ function (
};
EmbedController.prototype.openSnapshot = function () {
if (!this.snapshotOverlay) {
this.snapShotOverlay = new SnapshotOverlay(this.embed, this.formatTime);
} else {
this.snapShotOverlay = undefined;
var self = this,
snapshot = new Vue({
template: SnapshotTemplate,
data: function () {
return {
embed: self.embed
};
},
methods: {
formatTime: self.formatTime
}
});
function onDestroyCallback() {
snapshot.$destroy(true);
}
this.openmct.OverlayService.show(snapshot.$mount().$el, {onDestroy: onDestroyCallback, cssClass: 'l-large-view'});
};
EmbedController.prototype.formatTime = function (unixTime, timeFormat) {

View File

@ -60,6 +60,7 @@ function (
this.container = container;
var notebookEmbed = {
inject:['openmct'],
props:['embed', 'entry'],
template: EmbedTemplate,
data: embedController.exposedData,
@ -80,6 +81,7 @@ function (
var notebookVue = Vue.extend({
template: NotebookTemplate,
provide: {openmct: self.openmct},
components: {
'notebook-entry': entryComponent,
'search': search.default

View File

@ -35,6 +35,7 @@ define([
'./telemetryTable/plugin',
'./staticRootPlugin/plugin',
'./notebook/plugin',
'./displayLayout/plugin',
'./folderView/plugin'
], function (
_,
@ -51,6 +52,7 @@ define([
TelemetryTablePlugin,
StaticRootPlugin,
Notebook,
DisplayLayoutPlugin,
FolderView
) {
var bundleMap = {
@ -161,6 +163,7 @@ define([
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin;
plugins.Notebook = Notebook;
plugins.DisplayLayout = DisplayLayoutPlugin.default;
plugins.FolderView = FolderView;
return plugins;

View File

@ -264,7 +264,7 @@ define([
this.applyStyle($('#widget', this.domElement), activeRule.getProperty('style'));
$('#widget', this.domElement).prop('title', activeRule.getProperty('message'));
$('#widgetLabel', this.domElement).html(activeRule.getProperty('label'));
$('#widgetLabel', this.domElement).removeClass().addClass('label widget-label ' + activeRule.getProperty('icon'));
$('#widgetLabel', this.domElement).removeClass().addClass('label widget-label c-summary-widget__label ' + activeRule.getProperty('icon'));
};
/**

View File

@ -18,7 +18,7 @@ define([
this.widget.title = datum.message;
this.label.title = datum.message;
this.label.innerHTML = datum.ruleLabel;
this.label.className = 'label widget-label ' + datum.icon;
this.label.className = 'label widget-label c-summary-widget__label ' + datum.icon;
};
SummaryWidgetView.prototype.render = function () {

View File

@ -1,5 +1,5 @@
<div class="w-summary-widget s-status-no-data">
<a class="t-summary-widget l-summary-widget s-summary-widget labeled">
<span class="label widget-label">Loading...</span>
<div class="w-summary-widget s-status-no-data c-widget-wrapper u-contents">
<a class="t-summary-widget c-button c-summary-widget u-links u-fills-container">
<span class="label widget-label c-summary-widget__label">Loading...</span>
</a>
</div>

View File

@ -33,30 +33,6 @@ define([
) {
function TableConfigurationViewProvider(openmct) {
let instantiateService;
function isBeingEdited(object) {
let oldStyleObject = getOldStyleObject(object);
return oldStyleObject.hasCapability('editor') &&
oldStyleObject.getCapability('editor').inEditContext();
}
function getOldStyleObject(object) {
let oldFormatModel = objectUtils.toOldFormat(object);
let oldFormatId = objectUtils.makeKeyString(object.identifier);
return instantiate(oldFormatModel, oldFormatId);
}
function instantiate(model, id) {
if (!instantiateService) {
instantiateService = openmct.$injector.get('instantiate');
}
return instantiateService(model, id);
}
return {
key: 'table-configuration',
name: 'Telemetry Table Configuration',
@ -65,8 +41,7 @@ define([
return false;
}
let object = selection[0].context.item;
return object.type === 'table' &&
isBeingEdited(object);
return object.type === 'table';
},
view: function (selection) {
let component;
@ -86,7 +61,7 @@ define([
el: element
});
},
destroy: function (element) {
destroy: function () {
component.$destroy();
component = undefined;
}

View File

@ -23,10 +23,10 @@
define([
'lodash',
'EventEmitter',
'./TelemetryTableColumn',
'./TelemetryTableColumn'
], function (_, EventEmitter, TelemetryTableColumn) {
class TelemetryTableConfiguration extends EventEmitter{
class TelemetryTableConfiguration extends EventEmitter {
constructor(domainObject, openmct) {
super();
@ -37,6 +37,8 @@ define([
this.addColumnsForObject = this.addColumnsForObject.bind(this);
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
this.objectMutated = this.objectMutated.bind(this);
//Make copy of configuration, otherwise change detection is impossible if shared instance is being modified.
this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration()));
this.unlistenFromMutation = openmct.objects.observe(domainObject, '*', this.objectMutated);
}
@ -56,11 +58,11 @@ define([
* @param {*} object
*/
objectMutated(object) {
let oldConfiguration = this.domainObject.configuration;
//Synchronize domain object reference. Duplicate object otherwise change detection becomes impossible.
this.domainObject = JSON.parse(JSON.stringify(object));
if (!_.eq(object.configuration, oldConfiguration)){
this.domainObject = object;
if (!_.eq(object.configuration, this.oldConfiguration)) {
//Make copy of configuration, otherwise change detection is impossible if shared instance is being modified.
this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration()));
this.emit('change', object.configuration);
}
}

View File

@ -1,14 +1,12 @@
<template>
<div class="grid-properties">
<!--form class="form" -->
<ul class="l-inspector-part">
<h2>Table Columns</h2>
<li class="grid-row" v-for="(title, key) in headers">
<div class="grid-cell label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
<div class="grid-cell value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
<div class="c-properties" v-if="isEditing">
<div class="c-properties__header">Table Columns</div>
<ul class="c-properties__section">
<li class="c-properties__row" v-for="(title, key) in headers">
<div class="c-properties__label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
<div class="c-properties__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
</li>
</ul>
<!--/form -->
</div>
</template>
@ -21,6 +19,7 @@ export default {
data() {
return {
headers: {},
isEditing: this.openmct.editor.isEditing(),
configuration: this.tableConfiguration.getConfiguration()
}
},
@ -41,11 +40,14 @@ export default {
removeObject(objectIdentifier) {
this.tableConfiguration.removeColumnsForObject(objectIdentifier, true);
this.updateHeaders(this.tableConfiguration.getAllHeaders());
},
toggleEdit(isEditing) {
this.isEditing = isEditing;
}
},
mounted() {
this.unlisteners = [];
this.openmct.editor.on('isEditing', this.toggleEdit);
let compositionCollection = this.openmct.composition.get(this.tableConfiguration.domainObject);
compositionCollection.load()
@ -62,6 +64,7 @@ export default {
},
destroyed() {
this.tableConfiguration.destroy();
this.openmct.editor.off('isEditing', this.toggleEdit);
this.unlisteners.forEach((unlisten) => unlisten());
}
}

View File

@ -27,12 +27,10 @@
<ConductorModeIcon class="c-conductor__mode-icon"></ConductorModeIcon>
<div class="c-conductor__start-input">
<!-- Start input and controls -->
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start__fixed"
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed"
v-if="isFixed">
<!-- Fixed input -->
<div class="c-conductor__start__fixed__label">Start</div>
<!-- Fixed start -->
<div class="c-conductor__start-fixed__label">Start</div>
<input class="c-input--datetime"
type="text" autocorrect="off" spellcheck="false"
ref="startDate"
@ -44,9 +42,9 @@
@date-selected="startDateSelected"></date-picker>
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start__delta"
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta"
v-if="!isFixed">
<!-- RT input -->
<!-- RT start -->
<div class="c-direction-indicator icon-minus"></div>
<input class="c-input--hrs-min-sec"
type="text" autocorrect="off"
@ -54,14 +52,12 @@
v-model="offsets.start"
@change="validateOffsets($event); setOffsetsFromView()">
</div>
</div>
<div class="c-conductor__end-input">
<!-- End input and controls -->
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end__fixed"
v-if="isFixed">
<!-- Fixed input -->
<div class="c-conductor__end__fixed__label">End</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
<!-- Fixed end and RT 'last update' display -->
<div class="c-conductor__end-fixed__label">
{{ isFixed ? 'End' : 'Updated' }}
</div>
<input class="c-input--datetime"
type="text" autocorrect="off" spellcheck="false"
v-model="formattedBounds.end"
@ -72,12 +68,13 @@
class="c-ctrl-wrapper--menus-left"
:default-date-time="formattedBounds.end"
:formatter="timeFormatter"
@date-selected="endDateSelected"></date-picker>
@date-selected="endDateSelected"
v-if="isFixed"></date-picker>
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end__delta"
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta"
v-if="!isFixed">
<!-- RT input -->
<!-- RT end -->
<div class="c-direction-indicator icon-plus"></div>
<input class="c-input--hrs-min-sec"
type="text"
@ -86,7 +83,6 @@
v-model="offsets.end"
@change="validateOffsets($event); setOffsetsFromView()">
</div>
</div>
<conductor-axis
class="c-conductor__ticks"
@ -112,65 +108,28 @@
grid-row-gap: $interiorMargin;
align-items: center;
// Default: fixed mode, desktop
grid-template-rows: 1fr 1fr;
grid-template-columns: 20px auto 1fr auto;
grid-template-areas:
"tc-mode-icon tc-start tc-ticks tc-end"
"tc-controls tc-controls tc-controls tc-controls";
.c-conductor__end-input {
justify-content: flex-end;
}
body.phone.portrait & {
&.is-fixed-mode {
grid-row-gap: $interiorMargin;
grid-template-rows: auto auto auto;
grid-template-columns: 20px auto;
grid-template-areas:
"tc-mode-icon tc-start"
"tc-mode-icon tc-end"
"tc-mode-icon tc-controls";
.c-conductor {
&__mode-icon {
grid-row: 1;
}
&__ticks,
&__zoom {
display: none;
}
&-input [class*='__label'] {
// Start and end are in separate columns; make the labels line up
width: 40px;
}
&__end-input {
justify-content: flex-start;
}
}
}
}
&__mode-icon {
grid-area: tc-mode-icon;
}
&__start-input,
&__end-input {
&__start-fixed,
&__start-delta {
grid-area: tc-start;
display: flex;
}
&__start-input {
grid-area: tc-start;
}
&__end-input {
&__end-fixed,
&__end-delta {
grid-area: tc-end;
display: flex;
justify-content: flex-end;
}
&__ticks {
@ -186,12 +145,68 @@
}
}
[class*='__delta'] {
[class*='-delta'] {
&:before {
content: $glyph-icon-clock;
font-family: symbolsfont;
}
}
&.is-realtime-mode {
grid-template-columns: 20px auto 1fr auto auto;
grid-template-areas:
"tc-mode-icon tc-start tc-ticks tc-updated tc-end"
"tc-controls tc-controls tc-controls tc-controls tc-controls";
.c-conductor__end-fixed {
grid-area: tc-updated;
}
}
body.phone.portrait & {
grid-row-gap: $interiorMargin;
grid-template-rows: auto auto auto;
grid-template-columns: 20px auto auto;
&__mode-icon {
grid-row: 1;
}
&__ticks,
&__zoom {
display: none;
}
&.is-fixed-mode {
[class*='__start-fixed'],
[class*='__end-fixed'] {
[class*='__label'] {
// Start and end are in separate columns; make the labels line up
width: 30px;
}
}
[class*='__end-input'] {
justify-content: flex-start;
}
grid-template-areas:
"tc-mode-icon tc-start tc-start"
"tc-mode-icon tc-end tc-end"
"tc-mode-icon tc-controls tc-controls";
}
&.is-realtime-mode {
grid-template-areas:
"tc-mode-icon tc-start tc-updated"
"tc-mode-icon tc-end tc-end"
"tc-mode-icon tc-controls tc-controls";
.c-conductor__end-fixed {
justify-content: flex-end;
}
}
}
}
.c-conductor-input {
@ -215,16 +230,39 @@
}
input:invalid {
background: rgba($colorFormInvalid, 0.3);
background: rgba($colorFormInvalid, 0.5);
}
}
.is-realtime-mode {
button {
@include themedButton($colorTimeBg);
color: $colorTimeFg;
&:hover {
background: $colorTimeHov !important;
color: $colorTimeFg !important;
}
}
.c-conductor-input {
&:before {
color: $colorTime;
}
}
.c-conductor__end-fixed {
// Displays last RT udpate
color: $colorTime;
input {
// Remove input look
background: none;
box-shadow: none;
color: $colorTime;
pointer-events: none;
}
}
}
</style>
@ -396,5 +434,3 @@ export default {
}
}
</script>

View File

@ -37,6 +37,7 @@
@include bgTicks($c: rgba($colorBodyFg, 0.4));
background-position: 0 50%;
background-size: 5px 2px;
border-radius: $controlCr;
height: $h;
svg {
@ -76,34 +77,37 @@
body.desktop .is-fixed-mode & {
@include cursorGrab();
background-size: 3px 30%;
border-radius: $controlCr;
background-color: $colorBodyBgSubtle;
box-shadow: inset rgba(black, 0.2) 0 1px 1px;
box-shadow: inset rgba(black, 0.4) 0 1px 1px;
transition: $transOut;
svg text {
fill: $colorBodyFg;
stroke: $colorBodyBgSubtle;
transition: $transOut;
}
&:hover,
&:active {
$c: $colorKeySubtle;
background-color: $c;
transition: $transIn;
svg text {
stroke: $c;
transition: $transIn;
}
}
}
.is-realtime-mode & {
$c: 1px solid rgba($colorTime, 0.7);
border-left: $c;
border-right: $c;
svg text {
fill: $colorTime;
}
}
}
</style>
<script>

View File

@ -21,10 +21,10 @@
*****************************************************************************/
<template>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
<div class="c-button--menu c-mode-button"
<button class="c-button--menu c-mode-button"
@click="toggleMenu($event)">
<span class="c-button__label">{{selectedMode.name}}</span>
</div>
</button>
<div class="c-menu c-super-menu c-conductor__mode-menu"
v-if="showMenu">
<div class="c-super-menu__menu">
@ -66,16 +66,6 @@
min-width: 200px;
}
}
.is-realtime-mode {
.c-mode-button {
background: $colorTimeBg;
&:hover {
background: $colorTimeHov;
}
}
}
</style>
<script>

View File

@ -22,11 +22,11 @@
<template>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
v-if="selectedTimeSystem.name">
<div class="c-button--menu c-time-system-button"
<button class="c-button--menu c-time-system-button"
:class="selectedTimeSystem.cssClass"
@click="toggleMenu($event)">
<span class="c-button__label">{{selectedTimeSystem.name}}</span>
</div>
</button>
<div class="c-menu" v-if="showMenu">
<ul>
<li @click="setTimeSystemFromView(timeSystem)"
@ -40,20 +40,6 @@
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.is-realtime-mode {
.c-time-system-button {
background: $colorTimeBg;
&:hover {
background: $colorTimeHov;
}
}
}
</style>
<script>
export default {
inject: ['openmct', 'configuration'],

View File

@ -108,7 +108,7 @@
grid-gap: 1px;
height: 100%;
$mutedOpacity: 0.7;
$mutedOpacity: 0.5;
ul {
display: contents;
@ -127,7 +127,7 @@
padding: $interiorMargin;
&.is-in-month {
background: rgba($colorBodyFg, 0.1);
background: $colorMenuElementHilite;
}
}

View File

@ -58,7 +58,7 @@ define(['EventEmitter'], function (EventEmitter) {
this.selected[0].element.classList.remove('s-selected');
}
if (this.selected[1]) {
if (this.selected[1] && this.selected[1].element) {
this.selected[1].element.classList.remove('s-selected-parent');
}
@ -66,7 +66,7 @@ define(['EventEmitter'], function (EventEmitter) {
selectable[0].element.classList.add('s-selected');
}
if (selectable[1]) {
if (selectable[1] && selectable[1].element) {
selectable[1].element.classList.add('s-selected-parent');
}
@ -132,7 +132,7 @@ define(['EventEmitter'], function (EventEmitter) {
}
return function () {
element.removeEventListener('click', capture);
element.removeEventListener('click', capture, true);
element.removeEventListener('click', selectCapture);
if (unlisten) {

View File

@ -0,0 +1,330 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/************************************************** ESPRESSO THEME CONSTANTS */
@import "constants";
// Functions
@function buttonBg($c: $colorBtnBg) {
@return linear-gradient(lighten($c, 5%), $c);
}
// Constants
$fontBaseSize: 12px;
$smallCr: 2px;
$controlCr: 3px;
$basicCr: 4px;
// Base colors
$colorBodyBg: #393939;
$colorBodyFg: #aaa;
$colorGenBg: #222;
$colorHeadBg: #262626;
$colorHeadFg: $colorBodyFg;
$colorStatusBarBg: $colorHeadBg;
$colorStatusBarFg: $colorBodyFg;
$colorStatusBarFgHov: #aaa;
$colorKey: #0099cc;
$colorKeyFg: #fff;
$colorKeyHov: #00c0f6;
$colorKeyFilter: brightness(1) sepia(1) hue-rotate(145deg) saturate(6);
$colorKeyFilterHov: brightness(1) sepia(1) hue-rotate(145deg) saturate(7);
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #ccc;
$colorAHov: #fff;
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px; // TODO: Sync this with Snow theme
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #999;
$colorStatusDefault: #ccc;
$colorStatusInfo: #60ba7b;
$colorStatusAlert: #ffb66c;
$colorStatusError: #da0004;
$colorStatusBtnBg: #666;
// States
$colorPausedBg: #ff9900;
$colorPausedFg: #fff;
$colorOk: #33cc33;
// Base variations
$colorBodyBgSubtle: lighten($colorBodyBg, 5%);
$colorBodyBgSubtleHov: darken($colorKey, 50%);
$colorKeySubtle: darken($colorKey, 10%);
// Time Colors
$colorTime: #618cff;
$colorTimeBg: $colorTime;
$colorTimeFg: lighten($colorTimeBg, 30%);
$colorTimeHov: lighten($colorTime, 10%);
$colorTimeSubtle: darken($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
// Edit Colors
$editColor: #00c7c3;
$editColorFg: $colorBodyFg;
$browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2);
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
$editBorderSelectable: 1px dotted rgba($editColor, 1);
$editBorderSelectableHov: 1px dashed rgba($editColor, 1);
$editBorderSelected: 1px solid $editColor;
$editBorderDrilledIn: 1px dashed #ff4d9a;
$colorGridLines: rgba($editColor, 0.2);
// UI elements
$colorIconAlias: #4af6f3;
$colorIconAliasForKeyFilter: #aaa;
$colorProgressBarOuter: rgba(#000, 0.1);
$colorProgressBarAmt: #0a0;
// Buttons and Controls
$colorBtnBg: lighten($colorBodyBg, 10%); // !
$colorBtnBgHov: lighten($colorBtnBg, 10%); // !
$colorBtnFg: lighten($colorBodyFg, 10%); // !
$colorBtnFgHov: $colorBtnFg;
$colorBtnMajorBg: $colorKey;
$colorBtnMajorBgHov: $colorKeyHov;
$colorBtnMajorFg: $colorKeyFg;
$colorBtnMajorFgHov: darken($colorBtnMajorFg, 10%);
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnFg;
$colorClickIcon: $colorKey;
$colorClickIconBgHov: rgba($colorKey, 0.6);
$colorClickIconFgHov: $colorKeyHov;
// Menus
$colorMenuBg: lighten($colorBodyBg, 20%);
$colorMenuFg: lighten($colorBodyFg, 30%);
$colorMenuIc: lighten($colorKey, 15%);
$colorMenuHovBg: $colorMenuIc;
$colorMenuHovFg: lighten($colorMenuFg, 10%);
$colorMenuHovIc: $colorMenuHovFg;
$colorMenuElementHilite: lighten($colorMenuBg, 10%);
$shdwMenu: rgba(black, 0.5) 0 1px 5px;
$shdwMenuText: none;
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
// Palettes and Swatches
$paletteItemBorderOuterColorSelected: black;
$paletteItemBorderInnerColorSelected: white;
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
// Form colors
$colorCheck: $colorKey;
$colorFormRequired: $colorKey;
$colorFormValid: $colorOk;
$colorFormError: #990000;
$colorFormInvalid: #ff2200;
$colorFormFieldErrorBg: $colorFormError;
$colorFormFieldErrorFg: rgba(#fff, 0.6);
$colorFormLines: rgba(#000, 0.1);
$colorFormSectionHeader: rgba(#000, 0.05);
$colorInputBg: rgba(black, 0.2);
$colorInputFg: $colorBodyFg;
$colorInputPlaceholder: darken($colorBodyFg, 20%);
$colorFormText: darken($colorBodyFg, 10%);
$colorInputIcon: darken($colorBodyFg, 25%);
$colorFieldHint: lighten($colorBodyFg, 40%);
$shdwInput: inset rgba(black, 0.4) 0 0 1px;
$shdwInputHov: inset rgba(black, 0.7) 0 0 2px;
$shdwInputFoc: inset rgba(black, 0.8) 0 0.25px 3px;
// Inspector
$colorInspectorBg: lighten($colorBodyBg, 5%);
$colorInspectorFg: $colorBodyFg;
$colorInspectorPropName: darken($colorBodyFg, 20%);
$colorInspectorPropVal: lighten($colorInspectorFg, 15%);
$colorInspectorSectionHeaderBg: lighten($colorInspectorBg, 5%);
$colorInspectorSectionHeaderFg: lighten($colorInspectorBg, 40%);
// Overlay
$overlayColorBg: $colorMenuBg;
$overlayColorFg: $colorMenuFg;
$overlayButtonColorBg: lighten($overlayColorBg, 20%);
$overlayButtonColorFg: #fff;
$overlayCr: $interiorMarginLg;
// Indicator colors
$colorIndicatorAvailable: $colorKey;
$colorIndicatorDisabled: #444;
$colorIndicatorOn: $colorOk;
$colorIndicatorOff: #666;
// Limits and staleness colors//
$colorTelemFresh: lighten($colorBodyFg, 20%);
$colorTelemStale: darken($colorBodyFg, 20%);
$styleTelemStale: italic;
$colorLimitYellowBg: rgba(#ffaa00, 0.3);
$colorLimitYellowIc: #ffaa00;
$colorLimitRedBg: rgba(red, 0.3);
$colorLimitRedIc: red;
// Bubble colors
$colorInfoBubbleBg: $colorMenuBg;
$colorInfoBubbleFg: #666;
// Items
$colorItemBg: buttonBg($colorBtnBg);
$colorItemBgHov: buttonBg(lighten($colorBtnBg, 5%));
$colorListItemBg: transparent;
$colorListItemBgHov: rgba($colorKey, 0.1);
$colorItemFg: $colorBtnFg;
$colorItemFgDetails: darken($colorItemFg, 20%);
$shdwItemText: none;
// Tabular
$colorTabBorder: lighten($colorBodyBg, 10%);
$colorTabBodyBg: $colorBodyBg;
$colorTabBodyFg: lighten($colorBodyFg, 20%);
$colorTabHeaderBg: lighten($colorBodyBg, 10%);
$colorTabHeaderFg: lighten($colorBodyFg, 20%);
$colorTabHeaderBorder: $colorBodyBg;
// Plot
$colorPlotBg: rgba(black, 0.05);
$colorPlotFg: $colorBodyFg;
$colorPlotHash: black;
$opacityPlotHash: 0.2;
$stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: darken($colorPlotFg, 20%);
$legendCollapsedNameMaxW: 50%;
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
// Tree
$colorTreeBg: transparent;
$colorItemTreeHoverBg: lighten($colorBodyBg, 10%);
$colorItemTreeHoverFg: lighten($colorBodyFg, 10%);
$colorItemTreeIcon: $colorKey; // Used
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
$colorItemTreeFg: $colorBodyFg;
$colorItemTreeSelectedBg: darken($colorKey, 15%);
$colorItemTreeSelectedFg: $colorBodyBg;
$colorItemTreeEditingBg: $editColor;
$colorItemTreeEditingFg: $editColorFg;
$colorItemTreeVC: $colorBodyFg;
$colorItemTreeVCHover: $colorKey;
$shdwItemTreeIcon: none;
// Images
$colorThumbHoverBg: $colorItemTreeHoverBg;
// Scrollbar
$scrollbarTrackSize: 7px;
$scrollbarTrackShdw: rgba(#000, 0.2) 0 1px 2px;
$scrollbarTrackColorBg: rgba(#000, 0.2);
$scrollbarThumbColor: darken($colorBodyBg, 50%);
$scrollbarThumbColorHov: $colorKey;
$scrollbarThumbColorMenu: lighten($colorMenuBg, 10%);
$scrollbarThumbColorMenuHov: lighten($scrollbarThumbColorMenu, 2%);
// Splitter
$splitterHandleD: 2px;
$splitterHandleHitMargin: 4px;
$colorSplitterBaseBg: $colorBodyBg;
$colorSplitterBg: lighten($colorSplitterBaseBg, 10%);
$colorSplitterFg: $colorBodyBg;
$colorSplitterHover: $colorKey;
$colorSplitterActive: $colorKey;
$splitterBtnD: (16px, 35px); // height, width
$splitterBtnColorBg: $colorBtnBg;
$splitterBtnColorFg: #999;
$splitterBtnLabelColorFg: #666;
$splitterCollapsedBtnColorBg: #222;
$splitterCollapsedBtnColorFg: #666;
$splitterCollapsedBtnColorBgHov: $colorKey;
$splitterCollapsedBtnColorFgHov: $colorKeyFg;
// Mobile
$colorMobilePaneLeft: darken($colorBodyBg, 2%);
$colorMobilePaneLeftTreeItemBg: rgba($colorBodyFg, 0.1);
$colorMobilePaneLeftTreeItemFg: $colorItemTreeFg;
$colorMobileSelectListTreeItemBg: rgba(#000, 0.05);
// About Screen
$colorAboutLink: $colorKeySubtle;
// Loading
$colorLoadingFg: #776ba2;
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions
$transIn: all 50ms ease-in-out;
$transOut: all 250ms ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
// Discrete items, like Notebook entries, Widget rules
$createBtnTextTransform: uppercase;
@mixin discreteItem() {
background: rgba($colorBodyFg,0.1);
border: none;
border-radius: $controlCr;
.c-input-inline:hover {
background: $colorBodyBg;
}
}
@mixin discreteItemInnerElem() {
border: 1px solid rgba(#fff, 0.1);
border-radius: $controlCr;
}
@mixin themedButton($c: $colorBtnBg) {
background: linear-gradient(lighten($c, 5%), $c);
box-shadow: rgba(black, 0.5) 0 0 2px;
}
/**************************************************** NOT USED, LEAVE FOR NOW */
// Slider controls, not in use
/*
$sliderColorBase: $colorKey;
$sliderColorRangeHolder: rgba(black, 0.07);
$sliderColorRange: rgba($sliderColorBase, 0.2);
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
$sliderColorKnob: darken($sliderColorBase, 20%);
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
$sliderColorRangeValHovBg: $sliderColorRange;
$sliderColorRangeValHovFg: $colorBodyFg;
$sliderKnobW: 15px;
$sliderKnobR: 2px;
*/
// Content status
/*
$colorAlert: #ff3c00;
$colorWarningHi: #990000;
$colorWarningLo: #ff9900;
$colorDiagnostic: #a4b442;
$colorCommand: #3693bd;
$colorInfo: #2294a2;
$colorOk: #33cc33;
*/

View File

@ -20,115 +20,73 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/****************************************************** SNOW THEME CONSTANTS */
@import "constants";
// Functions
@function pullForward($c: $colorBodyBg, $p: 20%) {
// For dark interfaces, lighter things come forward - opposite for light interfaces
@return darken($c, $p);
@function buttonBg($c: $colorBtnBg) {
@return $c;
}
@function pushBack($c: $colorBodyBg, $p: 20%) {
// For dark interfaces, darker things move back - opposite for light interfaces
@return lighten($c, $p);
}
// Global
// Constants
$fontBaseSize: 12px;
$smallCr: 2px;
$controlCr: 3px;
$basicCr: 4px;
// Base colors
$colorBodyBg: #fcfcfc;
$colorBodyFg: #666;
$colorGenBg: #fff;
$colorHeadBg: #eee;
$colorHeadFg: $colorBodyFg;
$colorStatusBarBg: #000;
$colorStatusBarFg: #999;
$colorStatusBarFgHov: #aaa;
$colorKey: #0099cc;
$colorKeyFilter: brightness(0.9) sepia(1) hue-rotate(145deg) saturate(6);
$colorKeyFilterHov: brightness(1) sepia(1) hue-rotate(145deg) saturate(7);
$colorKeySelectedBg: $colorKey;
$colorKeyFg: #fff;
$colorKeyHov: #00c0f6;
$colorEditAreaBg: #eafaff; // Deprecated, use $editColor instead
$colorEditAreaFg: #4bb1c7; // Deprecated, use $editColor instead
$colorKeyFilter: brightness(0.9) sepia(1) hue-rotate(145deg) saturate(6);
$colorKeyFilterHov: brightness(1) sepia(1) hue-rotate(145deg) saturate(7);
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #999;
$colorAHov: $colorKey;
$contrastRatioPercent: 40%;
$hoverRatioPercent: 10%;
$basicCr: 4px;
$controlCr: 3px;
$smallCr: 2px;
$overlayCr: 11px;
$shdwTextSubtle: rgba(black, 0.2) 0 1px 2px;
// Variations
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
$colorBodyBgSubtleHov: pushBack($colorKey, 50%);
$colorKeySubtle: pushBack($colorKey, 50%);
// Layout
$shellMainPad: 4px 0;
$shellPanePad: 0 7px;
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #999;
$colorStatusDefault: #ccc;
$colorStatusInfo: #60ba7b;
$colorStatusAlert: #ffb66c;
$colorStatusError: #da0004;
$colorStatusBtnBg: #666;
// States
$colorPausedBg: #ff9900;
$colorPausedFg: #fff;
$colorOk: #33cc33;
// Base variations
$colorBodyBgSubtle: darken($colorBodyBg, 5%);
$colorBodyBgSubtleHov: lighten($colorKey, 50%);
$colorKeySubtle: lighten($colorKey, 50%);
// Time Colors
$colorTime: #618cff;
$colorTimeBg: $colorTime;
$colorTimeFg: $colorBodyBg;
$colorTimeHov: pushBack($colorTime, 5%);
$colorTimeSubtle: pushBack($colorTime, 20%);
// Buttons and Controls
$btnPad: $interiorMargin, $interiorMargin * 1.25;
$colorBtnBg: #aaaaaa;
$colorBtnBgHov: pullForward($colorBtnBg, $hoverRatioPercent);
$colorBtnFg: #fff;
$colorBtnFgHov: $colorBtnFg;
$colorBtnIcon: #eee;
$colorBtnIconHov: $colorBtnFgHov;
$colorBtnMajorBg: $colorKey;
$colorBtnMajorBgHov: $colorKeyHov;
$colorBtnMajorFg: $colorKeyFg;
$colorBtnMajorFgHov: pushBack($colorBtnMajorFg, $hoverRatioPercent);
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnFg;
$colorClickIcon: $colorKey;
$colorClickIconHov: $colorKeyHov;
$colorToggleIcon: rgba($colorClickIcon, 0.5);
$colorToggleIconActive: $colorKey;
$colorToggleIconHov: rgba($colorToggleIconActive, 0.5);
$colorInvokeMenu: #000;
$contrastInvokeMenuPercent: 40%;
$shdwBtns: none;
$shdwBtnsOverlay: none;
$sliderColorBase: $colorKey;
$sliderColorRangeHolder: rgba(black, 0.07);
$sliderColorRange: rgba($sliderColorBase, 0.2);
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
$sliderColorKnob: pushBack($sliderColorBase, 20%);
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
$sliderColorRangeValHovBg: $sliderColorRange;
$sliderColorRangeValHovFg: $colorBodyFg;
$sliderKnobW: 15px;
$sliderKnobR: 2px;
$timeControllerToiLineColor: $colorBodyFg;
$timeControllerToiLineColorHov: $colorTime;
$colorTransLucBg: #666; // Used as a visual blocking element over variable backgrounds, like imagery
$createBtnTextTransform: uppercase;
// Foundation Colors
$colorAlt1: #776ba2;
$colorAlert: #ff3c00;
$colorWarningHi: #990000;
$colorWarningLo: #ff9900;
$colorDiagnostic: #a4b442;
$colorCommand: #3693bd;
$colorInfo: #2294a2;
$colorOk: #33cc33;
$colorIconAlias: #4af6f3;
$colorIconAliasForKeyFilter: #aaa;
$colorPausedBg: #ff9900;
$colorPausedFg: #fff;
$colorCreateBtn: $colorKey;
$colorInvokeMenu: #fff;
$colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: lighten($colorObjHdrTxt, 30%);
$colorTick: rgba(black, 0.2);
$colorTimeHov: lighten($colorTime, 5%);
$colorTimeSubtle: lighten($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
// Edit Colors
$editColor: #00c7c3;
$editColorFg: $colorBodyFg;
$browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2);
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
@ -138,18 +96,39 @@ $editBorderSelected: 1px solid $editColor;
$editBorderDrilledIn: 1px dashed #ff4d9a;
$colorGridLines: rgba($editColor, 0.2);
// UI elements
$colorIconAlias: #4af6f3;
$colorIconAliasForKeyFilter: #aaa;
$colorProgressBarOuter: rgba(#000, 0.1);
$colorProgressBarAmt: #0a0;
// Buttons and Controls
$colorBtnBg: #aaaaaa;
$colorBtnBgHov: darken($colorBtnBg, 10%);
$colorBtnFg: #fff;
$colorBtnFgHov: $colorBtnFg;
$colorBtnMajorBg: $colorKey;
$colorBtnMajorBgHov: $colorKeyHov;
$colorBtnMajorFg: $colorKeyFg;
$colorBtnMajorFgHov: lighten($colorBtnMajorFg, 10%);
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnFg;
$colorClickIcon: $colorKey;
$colorClickIconBgHov: rgba($colorKey, 0.2);
$colorClickIconFgHov: $colorKeyHov;
// Menus
$colorMenuBg: pushBack($colorBodyBg, 10%);
$colorMenuFg: pullForward($colorMenuBg, 70%);
$colorMenuBg: lighten($colorBodyBg, 10%);
$colorMenuFg: darken($colorMenuBg, 70%);
$colorMenuIc: $colorKey;
$colorMenuHovBg: $colorMenuIc; //pullForward($colorMenuBg, $hoverRatioPercent);
$colorMenuHovBg: $colorMenuIc;
$colorMenuHovFg: $colorMenuBg;
$colorMenuHovIc: $colorMenuBg;
$colorMenuElementHilite: darken($colorMenuBg, 10%);
$shdwMenu: rgba(black, 0.5) 0 1px 5px;
$shdwMenuText: none;
$colorCreateMenuLgIcon: $colorKey;
$colorCreateMenuText: $colorBodyFg;
$menuItemPad: ($interiorMargin, nth($btnPad, 2));
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
// Palettes and Swatches
$paletteItemBorderOuterColorSelected: black;
@ -160,7 +139,7 @@ $paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
$colorCheck: $colorKey;
$colorFormRequired: $colorKey;
$colorFormValid: $colorOk;
$colorFormError: $colorWarningHi;
$colorFormError: #990000;
$colorFormInvalid: #ff2200;
$colorFormFieldErrorBg: $colorFormError;
$colorFormFieldErrorFg: rgba(#fff, 0.6);
@ -168,35 +147,28 @@ $colorFormLines: rgba(#000, 0.1);
$colorFormSectionHeader: rgba(#000, 0.05);
$colorInputBg: $colorGenBg;
$colorInputFg: $colorBodyFg;
$colorInputPlaceholder: pushBack($colorBodyFg, 20%);
$colorFormText: pushBack($colorBodyFg, 10%);
$colorInputIcon: pushBack($colorBodyFg, 25%);
$colorFieldHint: pullForward($colorBodyFg, 40%);
$shdwInput: inset rgba(black, 0.4) 0 0 1px;
$shdwInputHov: inset rgba(black, 0.7) 0 0 1px;
$shdwInputFoc: inset rgba(black, 0.7) 0 0 3px;
$colorInputPlaceholder: lighten($colorBodyFg, 20%);
$colorFormText: lighten($colorBodyFg, 10%);
$colorInputIcon: lighten($colorBodyFg, 25%);
$colorFieldHint: darken($colorBodyFg, 40%);
$shdwInput: inset rgba(black, 0.7) 0 0 1px;
$shdwInputHov: inset rgba(black, 0.7) 0 0 2px;
$shdwInputFoc: inset rgba(black, 0.8) 0 0.25px 3px;
// Inspector
$colorInspectorBg: pullForward($colorBodyBg, 5%);
$colorInspectorBg: darken($colorBodyBg, 5%);
$colorInspectorFg: $colorBodyFg;
$colorInspectorPropName: pushBack($colorBodyFg, 20%);
$colorInspectorPropVal: pullForward($colorInspectorFg, 15%);
$colorInspectorSectionHeaderBg: pullForward($colorInspectorBg, 5%);
$colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
$colorInspectorPropName: lighten($colorBodyFg, 20%);
$colorInspectorPropVal: darken($colorInspectorFg, 15%);
$colorInspectorSectionHeaderBg: darken($colorInspectorBg, 5%);
$colorInspectorSectionHeaderFg: darken($colorInspectorBg, 40%);
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #999;
$colorStatusDefault: #ccc;
$colorStatusInfo: #60ba7b;
$colorStatusAlert: #ffb66c;
$colorStatusError: #da0004;
$colorStatusBtnBg: #666;
$colorProgressBarOuter: rgba(#000, 0.1);
$colorProgressBarAmt: #0a0;
$progressBarHOverlay: 15px;
$progressBarStripeW: 20px;
$shdwStatusIc: rgba(white, 0.8) 0 0px 5px;
$animPausedPulseDur: 1s;
// Overlay
$overlayColorBg: $colorMenuBg;
$overlayColorFg: $colorMenuFg;
$overlayButtonColorBg: $colorBtnBg;
$overlayButtonColorFg: $colorBtnFg;
$overlayCr: $interiorMarginLg;
// Indicator colors
$colorIndicatorAvailable: $colorKey;
@ -204,13 +176,9 @@ $colorIndicatorDisabled: #444;
$colorIndicatorOn: $colorOk;
$colorIndicatorOff: #666;
// Selects
$colorSelectBg: $colorBtnBg;
$colorSelectFg: $colorBtnFg;
// Limits and staleness colors//
$colorTelemFresh: pullForward($colorBodyFg, 20%);
$colorTelemStale: pushBack($colorBodyFg, 20%);
$colorTelemFresh: darken($colorBodyFg, 20%);
$colorTelemStale: lighten($colorBodyFg, 20%);
$styleTelemStale: italic;
$colorLimitYellowBg: rgba(#ffaa00, 0.3);
$colorLimitYellowIc: #ffaa00;
@ -220,37 +188,22 @@ $colorLimitRedIc: red;
// Bubble colors
$colorInfoBubbleBg: $colorMenuBg;
$colorInfoBubbleFg: #666;
$colorThumbsBubbleFg: pullForward($colorBodyFg, 10%);
$colorThumbsBubbleBg: pullForward($colorBodyBg, 10%);
// Overlay
$colorOvrBlocker: rgba(black, 0.7);//
$colorOvrBg: $colorBodyBg;
$colorOvrFg: $colorBodyFg;
$colorOvrBtnBg: pullForward($colorOvrBg, 40%);
$colorOvrBtnFg: #fff;
$colorFieldHintOverlay: pullForward($colorOvrBg, 40%);
$durLargeViewExpand: 250ms;
// Items
$colorItemBg: #ddd;
$colorItemBgHov: rgba($colorKey, 0.1); //pushBack($colorItemBg, $hoverRatioPercent * 0.4);
$colorItemBg: lighten($colorBtnBg, 20%);
$colorItemBgHov: lighten($colorItemBg, 5%);
$colorListItemBg: transparent;
$colorListItemBgHov: rgba($colorKey, 0.1);
$colorItemFg: $colorBodyFg;
$colorItemFgDetails: pushBack($colorItemFg, 15%);
$colorItemIc: $colorKey;
$colorItemSubIcons: $colorItemFgDetails;
$colorItemOpenIcon: $colorItemFgDetails;
$colorItemFgDetails: lighten($colorItemFg, 15%);
$shdwItemText: none;
$colorItemBgSelected: $colorKey;
// Tabular
$colorTabBorder: pullForward($colorBodyBg, 10%);
$colorTabBorder: darken($colorBodyBg, 10%);
$colorTabBodyBg: $colorBodyBg;
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
$colorTabHeaderBg: pullForward($colorBodyBg, 10%);
$colorTabHeaderFg: pullForward($colorBodyFg, 20%);
$colorTabBodyFg: darken($colorBodyFg, 20%);
$colorTabHeaderBg: darken($colorBodyBg, 10%);
$colorTabHeaderFg: darken($colorBodyFg, 20%);
$colorTabHeaderBorder: $colorBodyBg;
// Plot
@ -260,24 +213,23 @@ $colorPlotHash: black;
$opacityPlotHash: 0.2;
$stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$colorPlotLabelFg: lighten($colorPlotFg, 20%);
$legendCollapsedNameMaxW: 50%;
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
// Tree
$colorTreeBg: #f0f0f0; // Used
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
$colorItemTreeHoverFg: pullForward($colorBodyFg, $hoverRatioPercent);
$colorTreeBg: transparent;
$colorItemTreeHoverBg: darken($colorBodyBg, 10%);
$colorItemTreeHoverFg: darken($colorBodyFg, 10%);
$colorItemTreeIcon: $colorKey; // Used
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
$colorItemTreeFg: $colorBodyFg;
$colorItemTreeSelectedBg: pushBack($colorKey, 15%);
$colorItemTreeSelectedBg: lighten($colorKey, 15%);
$colorItemTreeSelectedFg: $colorBodyBg;
$colorItemTreeEditingBg: #caf1ff;
$colorItemTreeEditingFg: $colorEditAreaFg;
$colorItemTreeEditingBg: $editColor;
$colorItemTreeEditingFg: $editColorFg;
$colorItemTreeVC: $colorBodyFg;
$colorItemTreeVCHover: $colorKey;
$colorItemTreeSelectedVC: $colorBodyBg;
$shdwItemTreeIcon: none;
// Images
@ -289,54 +241,37 @@ $scrollbarTrackShdw: rgba(#000, 0.2) 0 1px 2px;
$scrollbarTrackColorBg: rgba(#000, 0.2);
$scrollbarThumbColor: darken($colorBodyBg, 50%);
$scrollbarThumbColorHov: $colorKey;
$scrollbarThumbColorOverlay: darken($colorOvrBg, 50%);
$scrollbarThumbColorOverlayHov: $scrollbarThumbColorHov;
$scrollbarThumbColorMenu: pullForward($colorMenuBg, 10%);
$scrollbarThumbColorMenuHov: pullForward($scrollbarThumbColorMenu, 2%);
$scrollbarThumbColorMenu: darken($colorMenuBg, 10%);
$scrollbarThumbColorMenuHov: darken($scrollbarThumbColorMenu, 2%);
// Splitter
$splitterD: 7px;
$splitterHandleD: 2px;
$splitterHandleHitMargin: 4px;
$splitterGrippyD: ($splitterHandleD - 4, 75px, 50px); // thickness, length, min-length
$colorSplitterBaseBg: $colorBodyBg;
$colorSplitterBg: pullForward($colorSplitterBaseBg, 20%);
$colorSplitterBg: darken($colorSplitterBaseBg, 20%);
$colorSplitterFg: $colorBodyBg;
$colorSplitterHover: $colorKey; // pullForward($colorSplitterBg, $hoverRatioPercent * 2);
$colorSplitterHover: $colorKey;
$colorSplitterActive: $colorKey;
$splitterBtnD: (16px, 35px); // height, width
$splitterBtnColorBg: #eee;
$splitterBtnColorFg: #999;
$splitterBtnColorHoverBg: rgba($colorKey, 1);
$splitterBtnColorHoverFg: $colorBodyBg;
$colorSplitterGrippy: pullForward($colorSplitterBaseBg, 30%);
$splitterShdw: none;
$splitterEndCr: none;
// Minitabs
$colorMiniTabBg: $colorSplitterBg;
$colorMiniTabFg: pullForward($colorMiniTabBg, 30%);
$colorMiniTabBgHov: $colorSplitterHover;
$colorMiniTabFgHov: #fff;
$splitterBtnColorBg: $colorBtnBg;
$splitterBtnColorFg: #ddd;
$splitterBtnLabelColorFg: #999;
$splitterCollapsedBtnColorBg: #ccc;
$splitterCollapsedBtnColorFg: #666;
$splitterCollapsedBtnColorBgHov: $colorKey;
$splitterCollapsedBtnColorFgHov: $colorKeyFg;
// Mobile
$colorMobilePaneLeft: darken($colorBodyBg, 2%);
$colorMobilePaneLeftTreeItemBg: rgba($colorBodyFg, 0.1); //pullForward($colorMobilePaneLeft, 3%);
$colorMobilePaneLeftTreeItemBg: rgba($colorBodyFg, 0.1);
$colorMobilePaneLeftTreeItemFg: $colorItemTreeFg;
$colorMobileSelectListTreeItemBg: rgba(#000, 0.05);
// Datetime Picker, Calendar
$colorCalCellHovBg: $colorKey;
$colorCalCellHovFg: $colorKeyFg;
$colorCalCellSelectedBg: $colorItemTreeSelectedBg;
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
// About Screen
$colorAboutLink: #84b3ff;
$colorAboutLink: $colorKeySubtle;
// Loading
$colorLoadingFg: $colorAlt1;
$colorLoadingFg: #776ba2;
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions
@ -346,14 +281,50 @@ $transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
// Discrete items, like Notebook entries, Widget rules
$createBtnTextTransform: uppercase;
@mixin discreteItem() {
background: rgba($colorBodyFg,0.1);
border: 1px solid $colorInteriorBorder;
border-radius: $controlCr;
.c-input-inline:hover {
background: $colorBodyBg;
}
}
@mixin discreteItemInnerElem() {
border: 1px solid $colorBodyBg;
border-radius: $controlCr; }
border-radius: $controlCr;
}
@mixin themedButton($c: $colorBtnBg) {
background: $c;
}
/**************************************************** NOT USED, LEAVE FOR NOW */
// Slider controls, not in use
/*
$sliderColorBase: $colorKey;
$sliderColorRangeHolder: rgba(black, 0.07);
$sliderColorRange: rgba($sliderColorBase, 0.2);
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
$sliderColorKnob: lighten($sliderColorBase, 20%);
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
$sliderColorRangeValHovBg: $sliderColorRange;
$sliderColorRangeValHovFg: $colorBodyFg;
$sliderKnobW: 15px;
$sliderKnobR: 2px;
*/
// Content status
/*
$colorAlert: #ff3c00;
$colorWarningHi: #990000;
$colorWarningLo: #ff9900;
$colorDiagnostic: #a4b442;
$colorCommand: #3693bd;
$colorInfo: #2294a2;
$colorOk: #33cc33;
*/

View File

@ -40,17 +40,20 @@ $inputTextP: $inputTextPTopBtm $inputTextPLeftRight;
$menuLineH: 1.5rem;
$treeItemIndent: 16px;
$treeTypeIconW: 18px;
$overlayOuterMargin: 5%;
$overlayInnerMargin: 25px;
/*************** Items */
$itemPadLR: 5px;
$ueBrowseGridItemLg: 200px;
$gridItemDesk: 175px;
$gridItemMobile: 32px;
/*************** Tabular */
$tabularHeaderH: 22px;
$tabularTdPadLR: $itemPadLR;
$tabularTdPadTB: 2px;
/************************** MOBILE */
$mobileMenuIconD: 34px; // Used
$mobileMenuIconD: 24px; // Used
$mobileTreeItemH: 35px; // Used
/************************** VISUAL */
@ -121,7 +124,7 @@ $glyph-icon-pointer-right: '\e1028';
$glyph-icon-refresh: '\e1029';
$glyph-icon-save: '\e1030';
$glyph-icon-sine: '\e1031';
$glyph-icon-T: '\e1032';
$glyph-icon-font: '\e1032';
$glyph-icon-thumbs-strip: '\e1033';
$glyph-icon-two-parts-both: '\e1034';
$glyph-icon-two-parts-one-only: '\e1035';
@ -138,7 +141,7 @@ $glyph-icon-frame-show: '\e1045';
$glyph-icon-frame-hide: '\e1046';
$glyph-icon-import: '\e1047';
$glyph-icon-export: '\e1048';
$glyph-icon-minimize: '\e1049'; // 12px only
$glyph-icon-font-size: '\e1049';
$glyph-icon-activity: '\e1100';
$glyph-icon-activity-mode: '\e1101';
$glyph-icon-autoflow-tabular: '\e1102';

View File

@ -20,115 +20,6 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/******************************************************** PLACEHOLDERS */
@mixin cControl() {
$fs: 1em;
@include userSelectNone();
display: inline-flex;
align-items: center;
justify-content: center;
overflow: hidden;
&:before,
&:after {
font-family: symbolsfont;
display: block;
flex: 0 0 auto;
}
&:after {
font-size: 0.8em;
}
[class*="__label"] {
@include ellipsize();
display: block;
line-height: $fs; // Remove effect on top and bottom padding
font-size: $fs;
}
}
@mixin cButton() {
@include cControl();
background: $colorBtnBg;
border-radius: $controlCr;
color: $colorBtnFg;
cursor: pointer;
padding: nth($btnPad, 1) nth($btnPad, 2);
&:hover {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
&[class*="--major"] {
background: $colorBtnMajorBg;
color: $colorBtnMajorFg;
&:hover {
background: $colorBtnMajorBgHov;
color: $colorBtnMajorFgHov;
}
}
&[class*='--caution'] {
background: $colorBtnCautionBg;
color: $colorBtnCautionFg;
&:hover {
background: $colorBtnCautionBgHov;
}
}
}
@mixin cClickIcon() {
// A clickable element that just includes the icon, no background
// Padding is included to facilitate a bigger hit area
// Make the icon bigger relative to its container
@include cControl();
$pLR: 3px;
$pTB: 3px;
border-radius: $controlCr;
color: $colorKey;
cursor: pointer;
padding: $pTB $pLR ;
&:hover {
background: rgba($colorKey, 0.2);
}
&:before,
*:before {
// *:before handles any nested containers that may contain glyph elements
// Needed for c-togglebutton.
font-size: 1.3em;
}
}
@mixin cCtrlWrapper {
// Provides a wrapper around buttons and other controls
// Contains control and provides positioning context for contained menu/palette.
// Wraps --menu elements, contains button and menu
overflow: visible;
.c-menu {
// Default position of contained menu
top: 100%; left: 0;
}
&[class*='--menus-up'] {
.c-menu {
top: auto; bottom: 100%;
}
}
&[class*='--menus-left'] {
.c-menu {
left: auto; right: 0;
}
}
}
/********* Buttons */
// Optionally can include icon in :before via markup
button {
@ -206,7 +97,7 @@ button {
font-size: 0.7em;
}
}
/********* Disclosure Triangle */
/********* Disclosure Triangle */
// Provides an arrow icon that when clicked expands an element to reveal its contents.
// Used in tree items. Always placed BEFORE an element.
.c-disclosure-triangle {
@ -253,6 +144,12 @@ input[type=number] {
}
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
margin-right: -5px !important;
margin-top: -1px !important;
}
.c-input {
&--datetime {
// Sized for values such as 2018-09-28 22:32:33.468Z
@ -266,8 +163,8 @@ input[type=number] {
&-inline,
&--inline {
// A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus
@include reactive-input();
// A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus
@include reactive-input($bg: transparent);
box-shadow: none;
display: block !important;
min-width: 0;
@ -283,10 +180,11 @@ input[type=number] {
&:hover,
&:focus {
background: $colorInputBg;
padding-left: $inputTextPLeftRight;
padding-right: $inputTextPLeftRight;
}
}
}
&--labeled {
// TODO: replace .c-labeled-input with this
@ -297,7 +195,7 @@ input[type=number] {
input {
margin-left: $interiorMarginSm;
}
}
}
}
.c-labeled-input {
@ -334,7 +232,6 @@ input[type=number] {
}
@mixin menuInner() {
color: $colorMenuFg;
li {
@include cControl();
justify-content: start;
@ -404,11 +301,6 @@ input[type=number] {
justify-content: stretch;
.l-item-description {
&__icon,
&__description {
//flex: 1 1 50%;
}
&__name,
&__description {
margin-top: $interiorMarginLg;
@ -420,6 +312,7 @@ input[type=number] {
}
&__name {
color: $colorMenuFg;
flex: 0 0 auto;
font-size: 1.25em;
}

View File

@ -92,23 +92,16 @@ body.desktop {
}
}
.overlay ::-webkit-scrollbar-thumb {
background: $scrollbarThumbColorOverlay;
&:hover {
background: $scrollbarThumbColorOverlayHov;
}
::-webkit-scrollbar-corner {
background: transparent;
}
.menu ::-webkit-scrollbar-thumb {
.c-menu ::-webkit-scrollbar-thumb {
background: $scrollbarThumbColorMenu;
&:hover {
background: $scrollbarThumbColorMenuHov;
}
}
::-webkit-scrollbar-corner {
background: transparent;
}
}
/************************** HTML ENTITIES */
@ -175,7 +168,6 @@ li {
padding: 0;
}
/******************************************************** HAS */
// Local Controls: Controls placed in proximity to or overlaid on components and views
body.desktop .has-local-controls {
@ -386,3 +378,8 @@ a.disabled {
.t-imagery {
display: contents;
}
.t-frame-outer {
min-width: 200px;
min-height: 200px;
}

View File

@ -113,7 +113,7 @@
.icon-refresh { @include glyphBefore($glyph-icon-refresh); }
.icon-save { @include glyphBefore($glyph-icon-save); }
.icon-sine { @include glyphBefore($glyph-icon-sine); }
.icon-T { @include glyphBefore($glyph-icon-T); }
.icon-font { @include glyphBefore($glyph-icon-font); }
.icon-thumbs-strip { @include glyphBefore($glyph-icon-thumbs-strip); }
.icon-two-parts-both { @include glyphBefore($glyph-icon-two-parts-both); }
.icon-two-parts-one-only { @include glyphBefore($glyph-icon-two-parts-one-only); }
@ -130,6 +130,7 @@
.icon-frame-hide { @include glyphBefore($glyph-icon-frame-hide); }
.icon-import { @include glyphBefore($glyph-icon-import); }
.icon-export { @include glyphBefore($glyph-icon-export); }
.icon-font-size { @include glyphBefore($glyph-icon-font-size); }
.icon-activity { @include glyphBefore($glyph-icon-activity); }
.icon-activity-mode { @include glyphBefore($glyph-icon-activity-mode); }
.icon-autoflow-tabular { @include glyphBefore($glyph-icon-autoflow-tabular); }

View File

@ -113,33 +113,14 @@
top: $m; right: $m; bottom: $m; left: $m;
}
@mixin gridTwoColumn() {
display: grid;
grid-row-gap: 0;
grid-template-columns: 1fr 2fr;
align-items: start;
[class*="header"] {
@mixin propertiesHeader() {
border-radius: $smallCr;
background-color: $colorInspectorSectionHeaderBg;
color: $colorInspectorSectionHeaderFg;
font-weight: normal;
margin: 0 0 $interiorMarginSm 0;
padding: $interiorMarginSm $interiorMargin;
&:not(:first-child) {
// Allow multiple headers within a component
margin-top: $interiorMarginLg;
}
}
[class*="span-all"],
[class*="header"] {
@include gridTwoColumnSpanCols();
}
}
@mixin gridTwoColumnSpanCols() {
grid-column: 1 / 3;
text-transform: uppercase;
}
@mixin modalFullScreen() {
@ -163,10 +144,17 @@
}
/************************** CONTROLS, BUTTONS */
@mixin hover {
body.desktop & {
&:hover {
@content
}
}
}
@mixin htmlInputReset() {
appearance: none;
background: none;
background-color: transparent;
background: transparent;
border: none;
border-radius: 0;
outline: none;
@ -177,13 +165,12 @@
}
}
@mixin input-base() {
@include htmlInputReset();
border-radius: $controlCr;
&.error {
background-color: $colorFormFieldErrorBg;
background: $colorFormFieldErrorBg;
color: $colorFormFieldErrorFg;
}
}
@ -201,22 +188,145 @@
box-shadow: $shdwInput;
color: $fg;
&:hover {
box-shadow: $shdwInputHov;
}
&:focus {
box-shadow: $shdwInputFoc;
}
@include hover() {
&:not(:focus) {
box-shadow: $shdwInputHov;
}
}
}
@mixin button($bg: $colorBtnBg, $fg: $colorBtnFg, $radius: $controlCr, $shdw: none) {
// Is this being used? Remove if not.
background: $bg;
color: $fg;
border-radius: $radius;
box-shadow: $shdw;
}
@mixin cControl() {
$fs: 1em;
@include userSelectNone();
display: inline-flex;
align-items: center;
justify-content: center;
overflow: hidden;
&:before,
&:after {
font-family: symbolsfont;
display: block;
flex: 0 0 auto;
}
&:after {
font-size: 0.8em;
}
[class*="__label"] {
@include ellipsize();
display: block;
line-height: $fs; // Remove effect on top and bottom padding
font-size: $fs;
}
}
@mixin cButton() {
@include cControl();
@include themedButton();
border-radius: $controlCr;
color: $colorBtnFg;
cursor: pointer;
padding: $interiorMargin floor($interiorMargin * 1.25);
@include hover() {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
&[class*="--major"] {
background: $colorBtnMajorBg;
color: $colorBtnMajorFg;
@include hover() {
background: $colorBtnMajorBgHov;
color: $colorBtnMajorFgHov;
}
}
&[class*='--caution'] {
background: $colorBtnCautionBg;
color: $colorBtnCautionFg;
&:hover {
background: $colorBtnCautionBgHov;
}
}
}
@mixin cClickIcon() {
// A clickable element that just includes the icon, no background
// Padding is included to facilitate a bigger hit area
// Make the icon bigger relative to its container
@include cControl();
$pLR: 4px;
$pTB: 3px;
background: none;
box-shadow: none;
border-radius: $controlCr;
color: $colorKey;
cursor: pointer;
padding: $pTB $pLR ;
@include hover() {
background: $colorClickIconBgHov;
color: $colorClickIconFgHov;
}
&:before,
*:before {
// *:before handles any nested containers that may contain glyph elements
// Needed for c-togglebutton.
font-size: 1.2em;
}
}
@mixin cCtrlWrapper {
// Provides a wrapper around buttons and other controls
// Contains control and provides positioning context for contained menu/palette.
// Wraps --menu elements, contains button and menu
overflow: visible;
.c-menu {
// Default position of contained menu
top: 100%; left: 0;
}
&[class*='--menus-up'] {
.c-menu {
top: auto; bottom: 100%;
}
}
&[class*='--menus-left'] {
.c-menu {
left: auto; right: 0;
}
}
}
@mixin wrappedInput() {
// An input that is wrapped. Optionally includes a __label or icon element.
// Based on .c-search.

View File

@ -2,7 +2,7 @@
"metadata": {
"name": "openmct-symbols-16px",
"lastOpened": 0,
"created": 1529545133464
"created": 1537817705550
},
"iconSets": [
{
@ -525,7 +525,7 @@
"tempChar": ""
},
{
"order": 102,
"order": 149,
"prevSize": 24,
"name": "icon-T",
"id": 84,
@ -660,13 +660,21 @@
"code": 921672,
"tempChar": ""
},
{
"order": 150,
"id": 119,
"name": "icon-font-size-alt1",
"prevSize": 24,
"code": 921673,
"tempChar": ""
},
{
"order": 37,
"prevSize": 24,
"name": "icon-activity",
"id": 32,
"code": 921856,
"tempChar": ""
"tempChar": ""
},
{
"order": 36,
@ -674,7 +682,7 @@
"name": "icon-activity-mode",
"id": 31,
"code": 921857,
"tempChar": ""
"tempChar": ""
},
{
"order": 52,
@ -682,7 +690,7 @@
"name": "icon-autoflow-tabular",
"id": 47,
"code": 921858,
"tempChar": ""
"tempChar": ""
},
{
"order": 55,
@ -690,7 +698,7 @@
"name": "icon-clock",
"id": 50,
"code": 921859,
"tempChar": ""
"tempChar": ""
},
{
"order": 58,
@ -698,7 +706,7 @@
"name": "icon-database",
"id": 53,
"code": 921860,
"tempChar": ""
"tempChar": ""
},
{
"order": 57,
@ -706,7 +714,7 @@
"name": "icon-database-query",
"id": 52,
"code": 921861,
"tempChar": ""
"tempChar": ""
},
{
"order": 17,
@ -714,7 +722,7 @@
"name": "icon-dataset",
"id": 12,
"code": 921862,
"tempChar": ""
"tempChar": ""
},
{
"order": 22,
@ -722,7 +730,7 @@
"name": "icon-datatable",
"id": 17,
"code": 921863,
"tempChar": ""
"tempChar": ""
},
{
"order": 59,
@ -730,7 +738,7 @@
"name": "icon-dictionary",
"id": 54,
"code": 921864,
"tempChar": ""
"tempChar": ""
},
{
"order": 62,
@ -738,7 +746,7 @@
"name": "icon-folder",
"id": 57,
"code": 921865,
"tempChar": ""
"tempChar": ""
},
{
"order": 66,
@ -746,7 +754,7 @@
"name": "icon-image",
"id": 61,
"code": 921872,
"tempChar": ""
"tempChar": ""
},
{
"order": 68,
@ -754,7 +762,7 @@
"name": "icon-layout",
"id": 63,
"code": 921873,
"tempChar": ""
"tempChar": ""
},
{
"order": 77,
@ -762,7 +770,7 @@
"name": "icon-object",
"id": 72,
"code": 921874,
"tempChar": ""
"tempChar": ""
},
{
"order": 78,
@ -770,7 +778,7 @@
"name": "icon-object-unknown",
"id": 73,
"code": 921875,
"tempChar": ""
"tempChar": ""
},
{
"order": 79,
@ -778,7 +786,7 @@
"name": "icon-packet",
"id": 74,
"code": 921876,
"tempChar": ""
"tempChar": ""
},
{
"order": 80,
@ -786,7 +794,7 @@
"name": "icon-page",
"id": 75,
"code": 921877,
"tempChar": ""
"tempChar": ""
},
{
"order": 135,
@ -794,7 +802,7 @@
"name": "icon-plot-overlay",
"prevSize": 24,
"code": 921878,
"tempChar": ""
"tempChar": ""
},
{
"order": 113,
@ -802,7 +810,7 @@
"name": "icon-plot-stacked",
"prevSize": 24,
"code": 921879,
"tempChar": ""
"tempChar": ""
},
{
"order": 10,
@ -810,7 +818,7 @@
"name": "icon-session",
"id": 5,
"code": 921880,
"tempChar": ""
"tempChar": ""
},
{
"order": 24,
@ -818,7 +826,7 @@
"name": "icon-tabular",
"id": 19,
"code": 921881,
"tempChar": ""
"tempChar": ""
},
{
"order": 7,
@ -826,7 +834,7 @@
"name": "icon-tabular-lad",
"id": 2,
"code": 921888,
"tempChar": ""
"tempChar": ""
},
{
"order": 6,
@ -834,7 +842,7 @@
"name": "icon-tabular-lad-set",
"id": 1,
"code": 921889,
"tempChar": ""
"tempChar": ""
},
{
"order": 8,
@ -842,7 +850,7 @@
"name": "icon-tabular-realtime",
"id": 3,
"code": 921890,
"tempChar": ""
"tempChar": ""
},
{
"order": 23,
@ -850,7 +858,7 @@
"name": "icon-tabular-scrolling",
"id": 18,
"code": 921891,
"tempChar": ""
"tempChar": ""
},
{
"order": 112,
@ -858,7 +866,7 @@
"name": "icon-telemetry",
"id": 86,
"code": 921892,
"tempChar": ""
"tempChar": ""
},
{
"order": 90,
@ -866,7 +874,7 @@
"name": "icon-telemetry-panel",
"id": 85,
"code": 921893,
"tempChar": ""
"tempChar": ""
},
{
"order": 93,
@ -874,15 +882,15 @@
"name": "icon-timeline",
"id": 88,
"code": 921894,
"tempChar": ""
"tempChar": ""
},
{
"order": 116,
"id": 101,
"name": "icon-timer-v1.5",
"name": "icon-timer-v15",
"prevSize": 24,
"code": 921895,
"tempChar": ""
"tempChar": ""
},
{
"order": 11,
@ -890,7 +898,7 @@
"name": "icon-topic",
"id": 6,
"code": 921896,
"tempChar": ""
"tempChar": ""
},
{
"order": 115,
@ -898,7 +906,7 @@
"name": "icon-box-with-dashed-lines",
"id": 29,
"code": 921897,
"tempChar": ""
"tempChar": ""
},
{
"order": 126,
@ -906,7 +914,7 @@
"name": "icon-summary-widget",
"prevSize": 24,
"code": 921904,
"tempChar": ""
"tempChar": ""
},
{
"order": 139,
@ -914,13 +922,13 @@
"name": "icon-notebook",
"prevSize": 24,
"code": 921905,
"tempChar": ""
"tempChar": ""
}
],
"metadata": {
"name": "openmct-symbols-16px",
"importSize": {
"width": 512,
"width": 745,
"height": 512
},
"designer": "Charles Hacskaylo",
@ -2360,7 +2368,7 @@
},
{
"paths": [
"M0 0v256h128v-64h256v704h-192v128h640v-128h-192v-704h256v64h128v-256z"
"M800 1024h224l-384-1024h-256l-384 1024h224l84-224h408zM380 608l132-352 132 352z"
],
"grid": 16,
"tags": [
@ -2368,9 +2376,15 @@
],
"defaultCode": 228,
"id": 84,
"attrs": [],
"attrs": [
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"colorPermutations": {
"1161751207457516161751": []
"1161751207457516161751": [
{}
]
}
},
{
@ -2840,6 +2854,30 @@
]
}
},
{
"id": 119,
"paths": [
"M1226.4 320h-176l-76.22 203.24 77 205.34 87.22-232.58 90.74 242h-174.44l49.5 132h174.44l57.76 154h154l-264-704z",
"M384 0l-384 1024h224l84-224h408l84 224h224l-384-1024zM380 608l132-352 132 352z"
],
"attrs": [
{},
{}
],
"width": 1490,
"isMulticolor": false,
"isMulticolor2": false,
"grid": 16,
"tags": [
"icon-font-size-alt1"
],
"colorPermutations": {
"1161751207457516161751": [
{},
{}
]
}
},
{
"paths": [
"M576 64h-256l320 320h-290.256c-44.264-76.516-126.99-128-221.744-128h-128v512h128c94.754 0 177.48-51.484 221.744-128h290.256l-320 320h256l448-448-448-448z"
@ -3740,7 +3778,9 @@
"classSelector": ".ui-symbol",
"showMetrics": true,
"showMetadata": true,
"embed": false
"embed": false,
"noie8": true,
"ie7": false
},
"imagePref": {
"prefix": "icon-",

View File

@ -71,7 +71,7 @@
<glyph unicode="&#xe1029;" glyph-name="icon-refresh" d="M960 528v432l-164.8-164.8c-79.8 65.2-178.8 100.8-283.2 100.8-119.6 0-232.2-46.6-316.8-131.2s-131.2-197.2-131.2-316.8 46.6-232.2 131.2-316.8c84.6-84.6 197.2-131.2 316.8-131.2s232.2 46.6 316.8 131.2c69.4 69.4 113.2 157.4 126.6 252.8h-130c-29.8-145.8-159-256-313.6-256-176.4 0-320 143.6-320 320s143.8 320 320.2 320c72 0 138.4-23.8 192-64l-176-176h432z" />
<glyph unicode="&#xe1030;" glyph-name="icon-save" d="M192.2 384c-0.2 0-0.2 0 0 0l-0.2-448h640v447.8c0 0 0 0-0.2 0.2h-639.6zM978.8 749.2l-165.4 165.4c-25 25-74.2 45.4-109.4 45.4h-576c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128v448c0 35.2 28.8 64 64 64h640c35.2 0 64-28.8 64-64v-448c70.4 0 128 57.6 128 128v576c0 35.2-20.4 84.4-45.2 109.2zM704 704c0-35.2-28.8-64-64-64h-448c-35.2 0-64 28.8-64 64v192h320v-192h128v192h128v-192z" />
<glyph unicode="&#xe1031;" glyph-name="icon-sine" d="M1022.294 448c-1.746 7.196-3.476 14.452-5.186 21.786-20.036 85.992-53.302 208.976-98 306.538-22.42 48.938-45.298 86.556-69.946 115.006-48.454 55.93-98.176 67.67-131.356 67.67s-82.902-11.74-131.356-67.672c-24.648-28.45-47.528-66.068-69.948-115.006-44.696-97.558-77.962-220.544-98-306.538-21.646-92.898-46.444-175.138-71.71-237.836-16.308-40.46-30.222-66.358-40.6-82.604-10.378 16.246-24.292 42.142-40.6 82.604-23.272 57.75-46.144 132.088-66.524 216.052h-197.362c1.746-7.196 3.476-14.452 5.186-21.786 20.036-85.992 53.302-208.976 98-306.538 22.42-48.938 45.298-86.556 69.946-115.006 48.454-55.932 98.176-67.672 131.356-67.672s82.902 11.74 131.356 67.672c24.648 28.45 47.528 66.068 69.948 115.006 44.696 97.558 77.962 220.544 98 306.538 21.646 92.898 46.444 175.138 71.71 237.836 16.308 40.46 30.222 66.358 40.6 82.604 10.378-16.246 24.292-42.142 40.6-82.604 23.274-57.748 46.146-132.086 66.526-216.050h197.36z" />
<glyph unicode="&#xe1032;" glyph-name="icon-T" d="M0 960v-256h128v64h256v-704h-192v-128h640v128h-192v704h256v-64h128v256z" />
<glyph unicode="&#xe1032;" glyph-name="icon-T" d="M800-64h224l-384 1024h-256l-384-1024h224l84 224h408zM380 352l132 352 132-352z" />
<glyph unicode="&#xe1033;" glyph-name="icon-thumbs-strip" d="M448 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM448 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320z" />
<glyph unicode="&#xe1034;" glyph-name="icon-two-parts-both" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM128 832h320v-768h-320v768zM896 64h-320v768h320v-768z" />
<glyph unicode="&#xe1035;" glyph-name="icon-two-parts-one-only" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM896 64h-320v768h320v-768z" />
@ -88,6 +88,7 @@
<glyph unicode="&#xe1046;" glyph-name="icon-frame-hide" d="M128 770h420l104 128h-652v-802.4l128 157.4zM896 130h-420l-104-128h652v802.4l-128-157.4zM832 962l-832-1024h192l832 1024zM392 578l104 128h-304v-128z" />
<glyph unicode="&#xe1047;" glyph-name="icon-import" d="M832 767.6v-639.4c0-0.2-0.2-0.2-0.4-0.4h-319.6v-192h320c105.6 0 192 86.4 192 192v640.2c0 105.6-86.4 192-192 192h-320v-192h319.6c0.2 0 0.4-0.2 0.4-0.4zM192 256v-192l384 384-384 384v-192h-192v-384z" />
<glyph unicode="&#xe1048;" glyph-name="icon-export" d="M192 128.34v639.32l0.34 0.34h319.66v192h-320c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h320v192h-319.66zM1024 448l-384 384v-192h-192v-384h192v-192l384 384z" />
<glyph unicode="&#xe1049;" glyph-name="icon-font-size-alt1" horiz-adv-x="1490" d="M1226.4 640h-176l-76.22-203.24 77-205.34 87.22 232.58 90.74-242h-174.44l49.5-132h174.44l57.76-154h154l-264 704zM384 960l-384-1024h224l84 224h408l84-224h224l-384 1024zM380 352l132 352 132-352z" />
<glyph unicode="&#xe1100;" glyph-name="icon-activity" d="M576 896h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" />
<glyph unicode="&#xe1101;" glyph-name="icon-activity-mode" d="M512 960c-214.866 0-398.786-132.372-474.744-320h90.744c56.86 0 107.938-24.724 143.094-64h240.906l-192 192h256l320-320-320-320h-256l192 192h-240.906c-35.156-39.276-86.234-64-143.094-64h-90.744c75.958-187.628 259.878-320 474.744-320 282.77 0 512 229.23 512 512s-229.23 512-512 512z" />
<glyph unicode="&#xe1102;" glyph-name="icon-autoflow-tabular" d="M192 960c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 960h256v-1024h-256v1024zM832 960h-64v-704h256v512c0 105.6-86.4 192-192 192z" />
@ -115,7 +116,7 @@
<glyph unicode="&#xe1124;" glyph-name="icon-telemetry" d="M32 328.34c14.28 5.62 54.44 47.54 92.96 146 42.46 108.38 116.32 237.66 227.040 237.66 52.4 0 101.42-29.16 145.7-86.68 37.34-48.5 64.84-108.92 81.34-151.080 38.52-98.38 78.68-140.3 92.96-146 14.28 5.62 54.44 47.54 92.96 146 37.4 95.5 99.14 207.14 188.94 232.46-90.462 152.598-254.314 253.3-441.686 253.3-0.075 0-0.15 0-0.226 0-282.748 0-511.988-229.24-511.988-512 0-0.032 0-0.070 0-0.108 0-35.719 3.641-70.587 10.572-104.255 8.968-7.457 16.648-13.417 21.428-15.297zM992 567.66c-14.28-5.62-54.44-47.52-92.96-146-42.46-108.38-116.32-237.66-227.040-237.66-52.4 0-101.42 29.16-145.7 86.68-37.34 48.5-64.84 108.92-81.34 151.080-38.52 98.38-78.68 140.3-92.96 146-14.28-5.62-54.44-47.52-92.96-146-37.4-95.5-99.14-207.14-188.94-232.46 90.462-152.598 254.314-253.3 441.686-253.3 0.075 0 0.15 0 0.226 0 282.748 0 511.988 229.24 511.988 512 0 0.032 0 0.070 0 0.108 0 35.719-3.641 70.587-10.572 104.255-8.968 7.457-16.648 13.417-21.428 15.297z" />
<glyph unicode="&#xe1125;" glyph-name="icon-telemetry-panel" d="M169.2 512c14 56.4 33 122 56.6 176.8 15.4 35.8 31.2 63.2 48.2 84 18.4 22.4 49 49.2 91 49.2s72.6-26.8 91-49.2c17-20.6 32.6-48.2 48.2-84 23.6-54.8 42.8-120.4 56.6-176.8h461.2v256c0 105.6-86.4 192-192 192h-640c-105.6 0-192-86.4-192-192v-256h171.2zM718.6 384h-127.2c25-93.4 48.4-144.4 63.6-168.6 15.2 24.2 38.6 75.2 63.6 168.6zM301.4 512h127.2c-25 93.4-48.4 144.4-63.6 168.6-15.2-24.2-38.6-75.2-63.6-168.6zM850.8 384c-14-56.4-33-122-56.6-176.8-15.4-35.8-31.2-63.2-48.2-84-18.4-22.4-49-49.2-91-49.2s-72.6 26.8-91 49.2c-17 20.6-32.6 48.2-48.2 84-23.6 54.8-42.8 120.4-56.6 176.8h-461.2v-256c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v256h-171.2z" />
<glyph unicode="&#xe1126;" glyph-name="icon-timeline" d="M256 704h384v-128h-384v128zM384 512h384v-128h-384v128zM320 320h384v-128h-384v128zM832 960h-128v-192h127.6c0.2 0 0.2-0.2 0.4-0.4v-639.4c0-0.2-0.2-0.2-0.4-0.4h-127.6v-192h128c105.6 0 192 86.4 192 192v640.2c0 105.6-86.4 192-192 192zM192 128.4v639.2c0 0.2 0.2 0.2 0.4 0.4h127.6v192h-128c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h128v192h-127.6c-0.2 0-0.4 0.2-0.4 0.4z" />
<glyph unicode="&#xe1127;" glyph-name="icon-timer-v1.5" horiz-adv-x="896" d="M576 813.4v82.58c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-82.58c-185.040-55.080-320-226.48-320-429.42 0-247.42 200.58-448 448-448s448 200.58 448 448c0 202.96-135 374.4-320 429.42zM468 363.98l-263.76-211c-57.105 59.935-92.24 141.251-92.24 230.772 0 0.080 0 0.16 0 0.24 0 185.268 150.72 335.988 336 335.988 6.72 0 13.38-0.22 20-0.62v-355.38z" />
<glyph unicode="&#xe1127;" glyph-name="icon-timer-v15" horiz-adv-x="896" d="M576 813.4v82.58c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-82.58c-185.040-55.080-320-226.48-320-429.42 0-247.42 200.58-448 448-448s448 200.58 448 448c0 202.96-135 374.4-320 429.42zM468 363.98l-263.76-211c-57.105 59.935-92.24 141.251-92.24 230.772 0 0.080 0 0.16 0 0.24 0 185.268 150.72 335.988 336 335.988 6.72 0 13.38-0.22 20-0.62v-355.38z" />
<glyph unicode="&#xe1128;" glyph-name="icon-topic" d="M454.36 483.36l86.3 86.3c9.088 8.965 21.577 14.502 35.36 14.502s26.272-5.537 35.366-14.507l86.294-86.294c19.328-19.358 42.832-34.541 69.047-44.082l1.313 171.722-57.64 57.64c-34.407 34.33-81.9 55.558-134.35 55.558s-99.943-21.228-134.354-55.562l-86.296-86.297c-9.088-8.965-21.577-14.502-35.36-14.502s-26.272 5.537-35.366 14.507l-28.674 28.654v-172.14c19.045-7.022 41.040-11.084 63.984-11.084 52.463 0 99.966 21.239 134.379 55.587zM505.64 412.64l-86.3-86.3c-9.088-8.965-21.577-14.502-35.36-14.502s-26.272 5.537-35.366 14.507l-86.294 86.294c-2 2-4.2 4-6.36 6v-197.36c33.664-30.72 78.65-49.537 128.031-49.537 52.44 0 99.923 21.22 134.333 55.541l86.296 86.296c9.088 8.965 21.577 14.502 35.36 14.502s26.272-5.537 35.366-14.507l86.294-86.294c2-2 4.2-4 6.36-6v197.36c-33.664 30.72-78.65 49.537-128.031 49.537-52.44 0-99.923-21.22-134.333-55.541zM832 960h-128v-192h127.66l0.34-0.34v-639.32l-0.34-0.34h-127.66v-192h128c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM320 128h-127.66l-0.34 0.34v639.32l0.34 0.34h127.66v192h-128c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h128v192z" />
<glyph unicode="&#xe1129;" glyph-name="icon-box-with-dashed-lines" d="M0 576h128v-256h-128v256zM128 831.78l0.22 0.22h191.78v128h-192c-70.606-0.215-127.785-57.394-128-127.979v-192.021h128v191.78zM128 64.22v191.78h-128v-192c0.215-70.606 57.394-127.785 127.979-128h192.021v128h-191.78zM384 960h256v-128h-256v128zM896 64.22l-0.22-0.22h-191.78v-128h192c70.606 0.215 127.785 57.394 128 127.979v192.021h-128v-191.78zM896 960h-192v-128h191.78l0.22-0.22v-191.78h128v192c-0.215 70.606-57.394 127.785-127.979 128zM896 576h128v-256h-128v256zM384 64h256v-128h-256v128zM256 704h512v-512h-512v512z" />
<glyph unicode="&#xe1130;" glyph-name="icon-summary-widget" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM847.8 349.6l-82.6-143.2-189.6 131.6 19.2-230h-165.4l19.2 230-189.6-131.6-82.6 143.2 208.6 98.4-208.8 98.4 82.6 143.2 189.6-131.6-19.2 230h165.4l-19.2-230 189.6 131.6 82.6-143.2-208.6-98.4 208.8-98.4z" />

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -24,8 +24,8 @@
//!********************************* CONTROLS *!
//@import "../styles/controls/breadcrumb";
@import "../styles/controls/buttons";
@import "../styles/controls/palette";
@import "../styles/controls/controls";
//@import "../styles/controls/palette";
//@import "../styles/controls/controls";
@import "../styles/controls/lists";
@import "../styles/controls/menus";
@import "../styles/controls/messages";
@ -46,7 +46,7 @@
//@import "../styles/search/search";
//@import "../styles/mobile/search/search";
@import "../styles/overlay/overlay";
//@import "../styles/tree/tree";
@import "../styles/tree/tree"; // TEMP - NEED FOR TREE IN INSPECTOR
@import "../styles/object-label";
//@import "../styles/mobile/tree";
@import "../styles/user-environ/frame";

View File

@ -116,7 +116,6 @@
$p: $interiorMarginSm;
@include discreteItem();
display: flex;
//flex-wrap: wrap;
padding: $interiorMarginSm $interiorMarginSm $interiorMarginSm $interiorMargin;
&__time,
@ -156,7 +155,7 @@
}
&__text {
min-height: 24px; // Needed in Firefox when field is blank
min-height: 22px; // Needed in Firefox when field is blank
white-space: pre-wrap;
&.is-blank-notebook-entry {

View File

@ -24,5 +24,6 @@
// Meant for use as a single line import in Vue SFC's.
// Do not include anything that renders to CSS!
@import "constants";
@import "constants-snow"; // TEMP
@import "constants-espresso"; // TEMP
//@import "constants-snow"; // TEMP
@import "mixins";

View File

@ -21,6 +21,26 @@
*****************************************************************************/
/************************************************************* WIDGET OBJECT */
@mixin cSummaryWidget() {
@include boxShdw($shdwBtns);
border-radius: $basicCr;
border-style: solid;
border-width: 1px;
cursor: default;
&[href] {
cursor: pointer;
}
&__label {
&:before {
// Widget icon
font-size: 0.9em;
margin-right: $interiorMarginSm;
}
}
}
.l-summary-widget {
// Widget layout classes here
@include ellipsize();
@ -33,19 +53,9 @@
}
}
.s-summary-widget {
// Widget style classes here
@include boxShdw($shdwBtns);
border-radius: $basicCr;
border-style: solid;
border-width: 1px;
box-sizing: border-box;
cursor: default;
font-size: 0.8rem;
.c-summary-widget {
@include cSummaryWidget();
padding: $interiorMarginLg $interiorMarginLg * 2;
&[href] {
cursor: pointer;
}
}
.widget-edit-holder {
@ -267,7 +277,6 @@
.widget-thumb {
@include ellipsize();
@extend .s-summary-widget;
@extend .l-summary-widget;
@include cSummaryWidget();
padding: $interiorMarginSm $interiorMargin;
}

View File

@ -31,10 +31,6 @@
.c-create-button,
.c-create-menu {
&--w {
// Wrapper for Create button and menu
overflow: visible;
}
font-size: 1.1em;
}

View File

@ -0,0 +1,168 @@
<template>
<div class="c-custom-checkbox">
<input type="checkbox"
:id="id"
:name="name"
:value="value"
:required="required"
:disabled="disabled"
@change="onChange"
:checked="state">
<label :for="id">
<div class="c-custom-checkbox__box"></div>
<div class="c-custom-checkbox__label-text">
<slot></slot>
</div>
</label>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-custom-checkbox {
$d: 14px;
display: flex;
align-items: center;
label {
@include userSelectNone();
display: flex;
align-items: center;
}
&__box {
@include nice-input();
display: flex;
align-items: center;
justify-content: center;
line-height: $d;
width: $d;
height: $d;
margin-right: $interiorMarginSm;
}
input {
opacity: 0;
position: absolute;
&:checked + label > .c-custom-checkbox__box {
background: $colorKey;
&:before {
color: $colorKeyFg;
content: $glyph-icon-check;
font-family: symbolsfont;
font-size: 0.6em;
}
}
&:not(:disabled) + label {
cursor: pointer;
}
&:disabled + label {
opacity: 0.5;
}
}
}
</style>
<script>
/*
Custom checkbox control. Use just like a checkbox in HTML, except label string is passed within tag.
Supports value, true-value, false-value, checked and disabled attributes.
Example usage:
<checkbox checked>Enable markers</checkbox>
*/
export default {
model: {
prop: 'modelValue',
event: 'input'
},
props: {
id: {
type: String,
default: function () {
return 'checkbox-id-' + this._uid;
},
},
name: {
type: String,
default: null,
},
value: {
default: null,
},
modelValue: {
default: undefined,
},
checked: {
type: Boolean,
default: false,
},
required: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
model: {}
},
computed: {
state() {
if (this.modelValue === undefined) {
return this.checked;
}
if (Array.isArray(this.modelValue)) {
return this.modelValue.indexOf(this.value) > -1;
}
return !!this.modelValue;
}
},
methods: {
onChange() {
this.toggle();
},
toggle() {
let value;
if (Array.isArray(this.modelValue)) {
value = this.modelValue.slice(0);
if (this.state) {
value.splice(value.indexOf(this.value), 1);
} else {
value.push(this.value);
}
} else {
value = !this.state;
}
this.$emit('input', value);
}
},
watch: {
checked(newValue) {
if (newValue !== this.state) {
this.toggle();
}
}
},
mounted() {
if (this.checked && !this.state) {
this.toggle();
}
},
};
</script>

View File

@ -0,0 +1,51 @@
<template>
<div class="c-labeled-input"
:title="title">
<div class="c-labeled-input__label">{{ label }}</div>
<input type="number"
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"/>
</div>
</template>
<script>
/* Emits input and clear events */
export default {
inheritAttrs: false,
props: {
value: String,
label: String,
title: String
},
computed: {
inputListeners: function () {
let vm = this;
return Object.assign({},
this.$listeners,
{
input: function (event) {
vm.$emit('input', event.target.value);
},
change: function (event) {
vm.$emit('change', event.target.value);
}
}
)
}
},
data: function() {
return {
// active: false
}
},
methods: {
clearInput() {
// Clear the user's input and set 'active' to false
this.value = '';
this.$emit('clear','');
this.active = false;
}
}
}
</script>

View File

@ -5,7 +5,6 @@
'l-pane--horizontal-handle-after': type === 'horizontal' && handle === 'after',
'l-pane--vertical-handle-before': type === 'vertical' && handle === 'before',
'l-pane--vertical-handle-after': type === 'vertical' && handle === 'after',
'l-pane--collapsable' : collapsable,
'l-pane--collapsed': collapsed,
'l-pane--reacts': !handle,
'l-pane--resizing': resizing === true
@ -14,11 +13,13 @@
class="l-pane__handle"
@mousedown="start">
</div>
<button v-if="label"
class="l-pane__collapse-button"
@click="toggleCollapse">
<div class="l-pane__header"
v-if="label">
<span class="l-pane__label">{{ label }}</span>
</button>
<button class="l-pane__collapse-button c-button"
v-if="collapsable"
@click="toggleCollapse"></button>
</div>
<div class="l-pane__contents">
<slot></slot>
</div>
@ -62,13 +63,10 @@
display: none;
}
&__collapse-button {
position: absolute;
&__header {
display: flex;
align-items: center;
justify-content: center;
top: 0; right: 0; // Default
z-index: 1;
margin-bottom: $interiorMargin;
}
&--reacts {
@ -85,6 +83,7 @@
transition: opacity 150ms ease;
opacity: 0;
pointer-events: none;
overflow: hidden;
> * {
min-width: 0 !important;
@ -111,7 +110,6 @@
&__contents {
flex: 1 1 100%;
opacity: 1;
padding: $interiorMargin;
pointer-events: inherit;
transition: opacity 250ms ease 250ms;
@ -121,13 +119,10 @@
}
> [class*="__"] + [class*="__"] {
// Create margin between elements in a pane
// Doesn't match first elem, but will match all subsequent
margin-top: $interiorMargin;
}
}
/************************ DESKTOP STYLES */
/************************************************ DESKTOP STYLES */
body.desktop & {
&__handle {
background: $colorSplitterBg;
@ -150,52 +145,35 @@
}
}
&__collapse-button {
$m: 2px;
$h: 12px;
color: $splitterBtnColorFg;
flex: 0 0 nth($splitterBtnD, 1);
font-size: $h * .9;
position: relative;
justify-content: start;
transition: $transOut;
&__header {
font-size: floor(12px * .9);
}
&:after {
// Close icon
background: $colorBtnBg;
&__collapse-button {
box-shadow: none;
background: $splitterBtnColorBg;
color: $splitterBtnColorFg;
border-radius: $smallCr;
color: $colorBtnFg;
content: $glyph-icon-arrow-right-equilateral;
display: block;
font-family: symbolsfont;
font-size: 6px;
line-height: 90%;
padding: 3px 15px;
position: absolute;
right: $m;
top: $m;
transition: $transOut;
z-index: -1;
}
&:hover {
background: rgba(black, 0.1);
&:after {
background: $splitterBtnColorHoverBg;
color: $splitterBtnColorHoverFg;
transition: $transIn;
}
@include hover() {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
}
&__label {
// Name of the pane
@include ellipsize();
@include userSelectNone();
color: $splitterBtnLabelColorFg;
display: block;
padding-right: nth($splitterBtnD, 2) + $interiorMargin; // Force label to ellipsis
pointer-events: none;
text-transform: uppercase;
transform-origin: top left;
flex: 1 0 90%;
flex: 1 1 auto;
}
&--resizing {
@ -208,6 +186,7 @@
}
&[class*="--collapsed"] {
/********************************* STYLES FOR DESKTOP COLLAPSED PANES, ALL ORIENTATIONS */
$d: nth($splitterBtnD, 1);
flex-basis: $d;
min-width: $d !important;
@ -217,19 +196,24 @@
display: none;
}
> .l-pane__collapse-button {
background: $splitterBtnColorFg;
color: $splitterBtnColorBg;
.l-pane__header {
&:hover {
background: $splitterBtnColorHoverBg !important;
color: $splitterCollapsedBtnColorFgHov;
.l-pane__label {
color: inherit;
}
.l-pane__collapse-button {
background: $splitterCollapsedBtnColorBgHov;
color: inherit;
transition: $transIn;
}
}
}
> .l-pane__collapse-button {
height: nth($splitterBtnD, 1);
padding: $interiorMarginSm $interiorMarginSm;
.l-pane__collapse-button {
background: $splitterCollapsedBtnColorBg;
color: $splitterCollapsedBtnColorFg;
}
}
&[class*="--horizontal"] {
@ -248,31 +232,44 @@
}
}
.l-pane__collapse-button {
&:before {
content: $glyph-icon-arrow-right-equilateral;
}
}
&[class*="--collapsed"] {
> .l-pane__collapse-button {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
height: auto; width: 100%;
/************************ COLLAPSED HORIZONTAL SPLITTER, EITHER DIRECTION */
[class*="__header"] {
@include abs();
margin: 0;
}
[class*="label"] {
position: absolute;
transform: translate($interiorMarginLg + 1, 18px) rotate(90deg);
left: 3px;
top: 0;
z-index: 1;
}
&:after {
background: none;
.l-pane__collapse-button {
border-top-left-radius: 0;
border-bottom-left-radius: 0; // Only have to do this once, because of scaleX(-1) below.
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
height: auto; width: 100%;
padding: 0;
top: $interiorMargin;
left: 50%;
right: auto;
transform: translateX(-50%);
width: auto;
&:before {
position: absolute;
top: 5px;
}
}
}
/************************** Horizontal Splitter Before */
// Inspector pane
&[class*="-before"] {
> .l-pane__handle {
left: 0;
@ -280,15 +277,14 @@
}
&[class*="--collapsed"] {
> .l-pane__collapse-button {
&:after {
transform: translateX(-50%) scaleX(-1);
}
.l-pane__collapse-button {
transform: scaleX(-1);
}
}
}
/************************** Horizontal Splitter After */
// Tree pane
&[class*="-after"] {
> .l-pane__handle {
right: 0;
@ -296,7 +292,7 @@
}
&:not([class*="--collapsed"]) {
> .l-pane__collapse-button:after {
.l-pane__collapse-button {
transform: scaleX(-1);
}
}
@ -320,19 +316,19 @@
}
/************************** Vertical Splitter Before */
// Pane collapses downward
// Pane collapses downward. Used by Elements pool in Inspector
&[class*="-before"] {
> .l-pane__handle {
top: 0;
transform: translateY(floor($splitterHandleD / -1));
}
> .l-pane__collapse-button:after {
.l-pane__collapse-button:before {
content: $glyph-icon-arrow-down;
}
&.l-pane--collapsed {
> .l-pane__collapse-button:after {
> .l-pane__collapse-button {
transform: scaleY(-1);
}
}

View File

@ -15,45 +15,15 @@
<style lang="scss">
@import "~styles/sass-base";
/******************************* SEARCH */
.c-search {
@include nice-input();
display: flex;
align-items: center;
padding: 2px 4px;
@include wrappedInput();
padding-top: 2px;
padding-bottom: 2px;
&:before {
// Mag glass icon
content: $glyph-icon-magnify;
direction: rtl; // Aligns glyph to right-hand side of container, for transition
display: block;
font-family: symbolsfont;
flex: 0 0 auto;
opacity: 0.5;
overflow: hidden;
padding: 2px 0; // Prevents clipping
transition: width 250ms ease;
width: 1em;
}
&:hover {
box-shadow: inset rgba(black, 0.8) 0 0px 2px;
&:before {
opacity: 0.9;
}
}
&--major {
padding: 4px;
}
&__input {
background: none !important;
box-shadow: none !important; // !important needed to override default for [input]
flex: 1 1 auto;
padding-left: 2px !important;
padding-right: 2px !important;
min-width: 10px; // Must be set to allow input to collapse below browser min
}
&__clear-input {
@ -61,11 +31,6 @@
}
&.is-active {
&:before {
padding: 2px 0px;
width: 0px;
}
.c-search__clear-input {
display: block;
}

View File

@ -0,0 +1,176 @@
<template>
<div class="c-togglebutton">
<input type="checkbox"
:id="id"
:name="name"
:value="value"
:required="required"
:disabled="disabled"
@change="onChange"
:checked="state">
<label :for="id">
<div class="c-togglebutton__on"
:class="innerClassOn"></div>
<div class="c-togglebutton__off"
:class="innerClassOff"></div>
</label>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-togglebutton {
$d: 14px;
display: flex;
align-items: center;
label {
display: flex;
align-items: center;
justify-content: center;
.c-togglebutton__on {
display: none;
}
}
input {
opacity: 0;
position: absolute;
&:checked + label {
.c-togglebutton__on {
display: block;
}
.c-togglebutton__off {
display: none;
}
}
&:not(:disabled) + label {
cursor: pointer;
}
&:disabled + label {
opacity: 0.5;
}
}
}
</style>
<script>
/*
Toggle button control, based on checkboxCustom. Use just like a checkbox in HTML.
Requires inner-class-on and -off attributes to be passed.
Supports checked and disabled attributes.
Example usage:
<toggle-button checked
class="c-click-icon"
inner-class-on="icon-grid-snap-to"
inner-class-off="icon-grid-snap-no"></toggle-button>
*/
export default {
model: {
prop: 'modelValue',
event: 'input'
},
props: {
innerClassOn: {
type: String,
default: null,
required: true
},
innerClassOff: {
type: String,
default: null,
required: true
},
id: {
type: String,
default: function () {
return 'checkbox-id-' + this._uid;
},
},
name: {
type: String,
default: null,
},
value: {
default: null,
},
modelValue: {
default: undefined,
},
checked: {
type: Boolean,
default: false,
},
required: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
model: {}
},
computed: {
state() {
if (this.modelValue === undefined) {
return this.checked;
}
if (Array.isArray(this.modelValue)) {
return this.modelValue.indexOf(this.value) > -1;
}
return !!this.modelValue;
},
stateClass() {
return this.onClass;
}
},
methods: {
onChange() {
this.toggle();
},
toggle() {
let value;
if (Array.isArray(this.modelValue)) {
value = this.modelValue.slice(0);
if (this.state) {
value.splice(value.indexOf(this.value), 1);
} else {
value.push(this.value);
}
} else {
value = !this.state;
}
this.$emit('input', value);
}
},
watch: {
checked(newValue) {
if (newValue !== this.state) {
this.toggle();
}
}
},
mounted() {
if (this.checked && !this.state) {
this.toggle();
}
},
};
</script>

View File

@ -18,8 +18,6 @@
@import "~styles/sass-base";
.c-inspector {
min-width: 150px;
> [class*="__"] {
min-height: 50px;
@ -52,7 +50,7 @@
}
/************************************************************** LEGACY */
// TODO: refactor when markup can be converted
// TODO: refactor when legacy properties markup can be converted
.inspector-location {
display: inline-block;
@ -93,10 +91,45 @@
width: 4px;
}
}
.l-inspector-part {
display: contents;
}
h2 {
@include propertiesHeader();
font-size: 0.65rem;
grid-column: 1 / 3;
}
.tree .grid-properties {
margin-left: $treeItemIndent + $interiorMarginLg;
}
}
.c-properties {
@include gridTwoColumn();
display: grid;
grid-row-gap: 0;
grid-template-columns: 1fr 2fr;
align-items: start;
min-width: 150px;
[class*="header"] {
@include propertiesHeader();
&:not(:first-child) {
// Allow multiple headers within a component
margin-top: $interiorMarginLg;
}
}
[class*="span-all"],
[class*="header"] {
grid-column: 1 / 3;
}
+ .c-properties {
// Margin between components
@ -139,7 +172,7 @@
word-break: break-all;
&:first-child {
// If there is no preceding .label element, make value span columns
@include gridTwoColumnSpanCols();
grid-column: 1 / 3;
}
}
}

View File

@ -1,5 +1,6 @@
<template>
<div class="c-properties"></div>
<div>
</div>
</template>
<style>
@ -10,22 +11,26 @@
inject: ['openmct'],
mounted() {
this.openmct.selection.on('change', this.updateSelection);
this.updateSelection(this.openmct.selection.get());
this.updateSelection();
},
destroyed() {
this.openmct.selection.off('change', this.updateSelection);
},
methods: {
updateSelection(selection) {
updateSelection() {
let selection = this.openmct.selection.get();
if (this.selectedView && this.selectedView.destroy) {
this.selectedView.destroy();
}
delete this.viewContainer;
this.$el.innerHTML = '';
}
this.selectedView = this.openmct.inspectorViews.get(selection);
if (!this.selectedView) {
return;
}
this.selectedView.show(this.$el);
this.viewContainer = document.createElement('div');
this.$el.append(this.viewContainer)
this.selectedView.show(this.viewContainer);
}
}
}

View File

@ -39,8 +39,10 @@
</div>
<!-- Action buttons -->
<div class="l-browse-bar__actions">
<button class="l-browse-bar__actions__notebook-entry c-button icon-notebook" title="New Notebook entry"></button>
<button class="l-browse-bar__actions__edit c-button c-button--major icon-pencil" title="Edit"></button>
<button class="l-browse-bar__actions__edit c-button icon-notebook" title="New Notebook entry"></button>
<button class="l-browse-bar__actions__notebook-entry c-button c-button--major icon-pencil" title="Edit" v-if="!isEditing" @click="edit()"></button>
<button class="l-browse-bar__actions c-button c-button--major icon-save" title="Save and Finish Editing" v-if="isEditing" @click="saveAndFinishEditing()"></button>
<button class="l-browse-bar__actions c-button icon-x" title="Cancel Editing" v-if="isEditing" @click="cancelEditing()"></button>
</div>
</div>
</div>
@ -65,13 +67,28 @@
this.openmct.router.updateParams({
view: this.viewKey
});
},
edit() {
this.openmct.editor.edit();
},
cancelEditing() {
this.openmct.editor.cancel();
},
saveAndFinishEditing() {
this.openmct.editor.save().then(()=> {
this.openmct.notifications.info('Save successful');
}).catch((error) => {
this.openmct.notifications.error('Error saving objects');
console.error(error);
});
}
},
data: function () {
return {
showViewMenu: false,
domainObject: {},
viewKey: undefined
viewKey: undefined,
isEditing: this.openmct.editor.isEditing()
}
},
computed: {
@ -105,6 +122,10 @@
this.showViewMenu = false;
}
});
this.openmct.editor.on('isEditing', (isEditing) => {
this.isEditing = isEditing;
});
}
}
</script>
@ -126,7 +147,6 @@
display: flex;
align-items: center;
flex: 1 1 auto;
font-size: 1.4em;
margin-right: $interiorMargin;
min-width: 0; // Forces interior to compress when pushed on
}
@ -158,6 +178,7 @@
align-items: center;
display: flex;
flex: 0 1 auto;
font-size: 1.4em;
min-width: 0;
&:before {

View File

@ -1,5 +1,7 @@
<template>
<div class="l-shell">
<div class="l-shell" :class="{
'is-editing': isEditing
}">
<div class="l-shell__head">
<CreateButton class="l-shell__create-button"></CreateButton>
<div class="l-shell__controls">
@ -23,14 +25,13 @@
<div class="l-shell__search">
<search class="c-search--major" ref="shell-search"></search>
</div>
<div class="l-shell__tree">
<mct-tree></mct-tree>
</div>
<mct-tree class="l-shell__tree"></mct-tree>
</pane>
<pane class="l-shell__pane-main">
<browse-bar class="l-shell__main-view-browse-bar"
ref="browseBar">
</browse-bar>
<toolbar class="l-shell__toolbar"></toolbar>
<object-view class="l-shell__main-container"
ref="browseObject">
</object-view>
@ -46,7 +47,7 @@
</pane>
</multipane>
<div class="l-shell__status">
<MctStatus></MctStatus>
<StatusBar></StatusBar>
</div>
</div>
</template>
@ -60,47 +61,67 @@
top: 0; right: 0; bottom: 0; left: 0;
display: flex;
flex-flow: column nowrap;
overflow: hidden;
&__status {
background: $colorBodyFg;
color: $colorBodyBg;
border-top: 1px solid $colorInteriorBorder;
background: $colorStatusBarBg;
color: $colorStatusBarFg;
height: 24px;
padding: $interiorMarginSm;
}
&__pane-tree {
background: $colorTreeBg;
width: 40%;
[class*="collapse-button"] {
// For mobile, collapse button becomes menu icon
body.mobile & {
height: $mobileMenuIconD;
width: $mobileMenuIconD;
@include cClickIcon();
position: absolute;
right: -2 * nth($shellPanePad, 2); // Needs to be -1 * when pane is collapsed
top: 0;
transform: translateX(100%);
width: $mobileMenuIconD;
z-index: 2;
&:before {
color: $colorKey;
content: $glyph-icon-menu-hamburger;
font-family: symbolsfont;
font-size: 1.4em;
}
}
}
}
&__pane-main,
&__pane-tree {
> .l-pane__contents {
&__pane-tree,
&__pane-inspector,
&__pane-main {
.l-pane__contents {
display: flex;
flex-flow: column nowrap;
> * {
flex: 0 0 auto;
+ * {
margin-top: $interiorMarginLg;
}
}
}
}
body.mobile & {
&__pane-tree {
background: linear-gradient(90deg, transparent 70%, rgba(black, 0.2) 99%, rgba(black, 0.3));
&[class*="--collapsed"] {
[class*="collapse-button"] {
right: -1 * nth($shellPanePad, 2);
}
}
}
}
body.phone.portrait & {
&__pane-tree {
width: calc(100% - #{$mobileMenuIconD});
width: calc(100% - #{$mobileMenuIconD + (2 * nth($shellPanePad, 2))});
+ .l-pane {
// Hide pane-main when this pane is expanded
@ -136,13 +157,13 @@
}
body.mobile & .l-shell__main-view-browse-bar {
margin-left: $mobileMenuIconD - $interiorMarginLg; // Make room for the hamburger!
margin-left: $mobileMenuIconD; // Make room for the hamburger!
}
&__head {
align-items: center;
background: $colorHeadBg;
justify-content: space-between;
border-bottom: 1px solid $colorInteriorBorder;
padding: $interiorMargin;
> [class*="__"] + [class*="__"] {
@ -162,28 +183,41 @@
margin-right: 2.5%;
}
/********** MAIN AREA */
/******************************* MAIN AREA */
&__main-container {
// Wrapper for main views
flex: 1 1 auto;
flex: 1 1 auto !important;
overflow: auto;
//font-size: 16px; // TEMP FOR LEGACY STYLING
}
&__tree {
// Tree component within __pane-tree
flex: 1 1 100%;
overflow-y: auto;
flex: 1 1 auto !important;
}
&__time-conductor {
border-top: 1px solid $colorInteriorBorder;
flex: 0 0 auto;
padding-top: $interiorMargin;
}
&__main {
> .l-pane {
padding: nth($shellPanePad, 1) nth($shellPanePad, 2);
}
}
body.desktop & {
/********** HEAD AND STATUS */
&__main {
// Top and bottom padding in container that holds tree, __pane-main and Inspector
padding: $shellMainPad;
> .l-pane {
padding-top: 0;
padding-bottom: 0;
}
}
&__pane-tree,
&__pane-inspector {
max-width: 30%;
@ -202,7 +236,6 @@
<script>
import Inspector from '../inspector/Inspector.vue';
import MctStatus from './MctStatus.vue';
import MctTree from './mct-tree.vue';
import ObjectView from './ObjectView.vue';
import MctTemplate from '../legacy/mct-template.vue';
@ -212,6 +245,8 @@
import multipane from '../controls/multipane.vue';
import pane from '../controls/pane.vue';
import BrowseBar from './BrowseBar.vue';
import StatusBar from './status-bar/StatusBar.vue';
import Toolbar from './Toolbar.vue';
var enterFullScreen = () => {
var docElm = document.documentElement;
@ -242,9 +277,9 @@
}
export default {
inject: ['openmct'],
components: {
Inspector,
MctStatus,
MctTree,
ObjectView,
'mct-template': MctTemplate,
@ -253,12 +288,20 @@
search,
multipane,
pane,
BrowseBar
BrowseBar,
StatusBar,
Toolbar
},
mounted() {
this.openmct.editor.on('isEditing', (isEditing)=>{
this.isEditing = isEditing;
});
},
data: function () {
return {
fullScreen: false,
conductorComponent: {}
conductorComponent: {},
isEditing: false
}
},
methods: {

View File

@ -1,14 +0,0 @@
<template>
<span class="c-status">
[ Status ]
</span>
</template>
<style lang="scss">
</style>
<script>
export default {
}
</script>

View File

@ -2,7 +2,7 @@
</template>
<style lang="scss">
.l-object-view {
.c-object-view {
display: contents;
}
</style>
@ -33,6 +33,7 @@ export default {
this.debounceUpdateView = _.debounce(this.updateView, 10);
},
mounted() {
this.currentObject = this.object;
this.updateView();
},
methods: {
@ -50,7 +51,7 @@ export default {
return;
}
this.viewContainer = document.createElement('div');
this.viewContainer.classList.add('l-object-view');
this.viewContainer.classList.add('c-object-view');
this.$el.append(this.viewContainer);
let provider = this.openmct.objectViews.getByProviderKey(this.viewKey);
if (!provider) {

View File

@ -0,0 +1,291 @@
<template>
<div class="c-toolbar">
<!-- VERSION MANUALLY RESTORED FROM VUE-LAYOUT -->
<div class="c-button-set">
<div class="c-ctrl-wrapper">
<div class="c-button--menu js-add-button icon-plus"
@click="toggleMenus">
<div class="c-button__label">Add</div>
</div>
<div class="c-menu" v-if="showMenus">
<ul>
<li v-for="item in addMenuItems"
:class="item.class"
:title="item.title">
{{ item.name }}
</li>
</ul>
</div>
</div>
</div>
<div class="c-button-set"
v-if="toolsItemSelected">
<div class="c-ctrl-wrapper"
v-if="toolsItemSelected">
<div class="c-click-icon c-click-icon--menu js-layers icon-layers"
@click="toggleMenus"></div>
<div class="c-menu" v-if="showMenus">
<ul>
<li v-for="item in layersMenuItems"
:class="item.class"
:title="item.title">
{{ item.name }}
</li>
</ul>
</div>
</div>
<div class="c-ctrl-wrapper"
v-if="toolsColorFill">
<div class="c-click-icon c-click-icon--swatched js-color-fill icon-paint-bucket"
@click="toggleMenus">
<div class="c-swatch" style="background: #33ff00;"></div>
</div>
<div class="c-menu c-palette c-palette--color"
v-if="showMenus">
<div class="c-palette__item-none"
vif="this.palette.itemNone === true">
<div class="c-palette__item"
@click="this.setColor('no-color')"></div>
No fill
</div>
<div class="c-palette__items">
<div class="c-palette__item"
v-for="color in colorPalette"
:style="{ background: color.value }"
@click="this.setColor(color.value)"></div>
</div>
</div>
</div>
<div class="c-ctrl-wrapper"
v-if="toolsColorStroke">
<div class="c-click-icon c-click-icon--swatched js-color-stroke icon-pencil">
<div class="c-toolbar-button__swatch" style="background: #ffffff;"></div>
</div>
</div>
<div class="c-ctrl-wrapper"
v-if="toolsColorText">
<div class="c-click-icon c-click-icon--swatched js-color-text icon-font">
<div class="c-toolbar-button__swatch" style="background: #333333;"></div>
</div>
</div>
</div>
<div class="c-button-set"
v-if="toolsItemSelected && toolsFontSize">
<div class="c-ctrl-wrapper">
<div class="c-click-icon c-click-icon--menu js-font-size"
@click="toggleMenus">
<div class="c-button__label">11 px</div>
</div>
<div class="c-menu" v-if="showMenus">
<ul>
<li v-for="item in fontSizeMenuItems">
{{ item.name }}
</li>
</ul>
</div>
</div>
</div>
<div class="c-button-set"
v-if="toolsItemSelected && toolsEditProperties">
<div class="c-ctrl-wrapper">
<div class="c-click-icon js-image icon-gear"></div>
</div>
</div>
<div class="c-button-set"
v-if="toolsItemSelected">
<labeledNumberInput label="X" value=1 title="X position"></labeledNumberInput>
<labeledNumberInput label="Y" value=2 title="Y position"></labeledNumberInput>
<labeledNumberInput label="W" value=3 title="Width"></labeledNumberInput>
<labeledNumberInput label="H" value=4 title="Height"></labeledNumberInput>
</div>
<div class="c-button-set"
v-if="toolsItemSelected">
<div class="c-click-icon c-click-icon--caution icon-trash"></div>
</div>
<div class="c-button-set"
v-if="toolsItemSelected">
<checkbox checked title="This is a checkbox">Checkbox</checkbox>
</div>
<div class="c-button-set"
v-if="toolsItemSelected">
<toggle-button title="Toggle frame" checked
class="c-click-icon"
inner-class-on="icon-frame-show"
inner-class-off="icon-frame-hide"></toggle-button>
<toggle-button title="Snap to grid" checked
class="c-click-icon"
inner-class-on="icon-grid-snap-to"
inner-class-off="icon-grid-snap-no"></toggle-button>
<toggle-button title="Show label and value" checked
class="c-click-icon"
inner-class-on="icon-two-parts-both"
inner-class-off="icon-two-parts-one-only"></toggle-button>
</div>
</div>
</template>
<script>
import labeledNumberInput from '../controls/labeledNumberInput.vue';
import checkbox from '../controls/checkboxCustom.vue';
import toggleButton from '../controls/toggleButton.vue';
export default {
components: {
labeledNumberInput,
checkbox,
toggleButton
},
methods: {
toggleMenus: function () {
this.showMenus = !this.showMenus;
}
},
props: {
toolsItemSelected: { type: Boolean, default: true },
toolsColorFill: { type: Boolean, default: true },
toolsColorStroke: { type: Boolean, default: true },
toolsColorText: { type: Boolean, default: true },
toolsFontSize: { type: Boolean, default: true },
toolsEditProperties: { type: Boolean, default: true },
toolSetBox: ['toolsColorFill', 'toolsColorStroke'],
toolSetLine: ['toolsColorStroke'],
toolSetText: ['toolsColorFill', 'toolsColorStroke', 'toolsColorText', 'toolsFontSize', 'toolsEditProperties'],
toolSetImage: ['toolsColorStroke', 'toolsEditProperties'],
toolSetTelemetry: ['toolsColorFill', 'toolsColorStroke', 'toolsColorText', 'toolsFontSize', 'toolsLabelValue']
},
data: function () {
return {
showMenus: false,
addMenuItems: [
{ name: 'Box', class: 'icon-box', title: 'Add Box' },
{ name: 'Line', class: 'icon-line-horz', title: 'Add Line' },
{ name: 'Text', class: 'icon-font', title: 'Add Text' },
{ name: 'Image', class: 'icon-image', title: 'Add Image' }
],
layersMenuItems: [
{ name: 'Move to top', class: 'icon-arrow-double-up', title: 'Move to top' },
{ name: 'Move up', class: 'icon-arrow-up', title: 'Move up' },
{ name: 'Move down', class: 'icon-arrow-down', title: 'Move down' },
{ name: 'Move to bottom', class: 'icon-arrow-double-down', title: 'Move to bottom' }
],
fontSizeMenuItems: [
{ value: '9', name: '9 px' },
{ value: '10', name: '10 px' },
{ value: '11', name: '11 px' },
{ value: '12', name: '12 px' },
{ value: '13', name: '13 px' },
{ value: '14', name: '14 px' },
{ value: '16', name: '16 px' },
{ value: '18', name: '18 px' },
{ value: '20', name: '20 px' },
{ value: '24', name: '24 px' },
{ value: '28', name: '28 px' },
{ value: '32', name: '32 px' },
{ value: '40', name: '40 px' },
{ value: '48', name: '48 px' },
{ value: '56', name: '56 px' },
{ value: '64', name: '64 px' },
{ value: '72', name: '72 px' },
{ value: '80', name: '80 px' },
{ value: '88', name: '88 px' },
{ value: '96', name: '96 px' },
{ value: '128', name: '128 px' },
{ value: '160', name: '160 px' }
],
colorPalette: [
{ value: '#000000' },
{ value: '#434343' },
{ value: '#666666' },
{ value: '#999999' },
{ value: '#b7b7b7' },
{ value: '#cccccc' },
{ value: '#d9d9d9' },
{ value: '#efefef' },
{ value: '#f3f3f3' },
{ value: '#ffffff' },
{ value: '#980000' },
{ value: '#ff0000' },
{ value: '#ff9900' },
{ value: '#ffff00' },
{ value: '#00ff00' },
{ value: '#00ffff' },
{ value: '#4a86e8' },
{ value: '#0000ff' },
{ value: '#9900ff' },
{ value: '#ff00ff' },
{ value: '#e6b8af' },
{ value: '#f4cccc' },
{ value: '#fce5cd' },
{ value: '#fff2cc' },
{ value: '#d9ead3' },
{ value: '#d0e0e3' },
{ value: '#c9daf8' },
{ value: '#cfe2f3' },
{ value: '#d9d2e9' },
{ value: '#ead1dc' },
{ value: '#dd7e6b' },
{ value: '#dd7e6b' },
{ value: '#f9cb9c' },
{ value: '#ffe599' },
{ value: '#b6d7a8' },
{ value: '#a2c4c9' },
{ value: '#a4c2f4' },
{ value: '#9fc5e8' },
{ value: '#b4a7d6' },
{ value: '#d5a6bd' },
{ value: '#cc4125' },
{ value: '#e06666' },
{ value: '#f6b26b' },
{ value: '#ffd966' },
{ value: '#93c47d' },
{ value: '#76a5af' },
{ value: '#6d9eeb' },
{ value: '#6fa8dc' },
{ value: '#8e7cc3' },
{ value: '#c27ba0' },
{ value: '#a61c00' },
{ value: '#cc0000' },
{ value: '#e69138' },
{ value: '#f1c232' },
{ value: '#6aa84f' },
{ value: '#45818e' },
{ value: '#3c78d8' },
{ value: '#3d85c6' },
{ value: '#674ea7' },
{ value: '#a64d79' },
{ value: '#85200c' },
{ value: '#990000' },
{ value: '#b45f06' },
{ value: '#bf9000' },
{ value: '#38761d' },
{ value: '#134f5c' },
{ value: '#1155cc' },
{ value: '#0b5394' },
{ value: '#351c75' },
{ value: '#741b47' },
{ value: '#5b0f00' },
{ value: '#660000' },
{ value: '#783f04' },
{ value: '#7f6000' },
{ value: '#274e13' },
{ value: '#0c343d' },
{ value: '#1c4587' },
{ value: '#073763' },
{ value: '#20124d' },
{ value: '#4c1130' }
]
}
}
}
</script>

View File

@ -1,10 +1,12 @@
<template>
<div class="c-tree__wrapper">
<ul class="c-tree">
<tree-item v-for="child in children"
:key="child.id"
:node="child">
</tree-item>
</ul>
</div>
</template>
<style lang="scss">
@ -16,6 +18,11 @@
overflow-y: auto;
height: 100%;
&__wrapper {
overflow-y: auto;
padding-right: $interiorMarginSm;
}
.c-tree {
margin-left: 15px;
}

View File

@ -0,0 +1,41 @@
<!--
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.
-->
<template>
<span id='status' class='status-holder'></span>
</template>
<style lang="scss">
</style>
<script>
export default {
inject: ['openmct'],
mounted() {
this.openmct.indicators.indicatorObjects.forEach((indicator) => {
// So that we can consistently position indicator elements,
// guarantee that they are wrapped in an element we control
var wrapperNode = document.createElement('span');
wrapperNode.className = 'l-indicator';
wrapperNode.appendChild(indicator.element);
this.$el.appendChild(wrapperNode);
});
}
}
</script>

View File

@ -0,0 +1,98 @@
<!--
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.
-->
<template>
<div class="l-message-banner s-message-banner"
:class="[
activeModel.severity,
{
'minimized': activeModel.minimized,
'new': !activeModel.minimized
}]"
v-if="activeModel">
<span @click="maximize()" class="banner-elem label">{{activeModel.title}}</span>
<span @click="maximize()" v-if="activeModel.progress !== undefined || activeModel.unknownProgress">
<div class="banner-elem"><!-- was mct-include -->
<span class="l-progress-bar s-progress-bar"
:class="{'indeterminate': activeModel.unknownProgress }">
<span class="progress-amt-holder">
<span class="progress-amt" :style="progressWidth"></span>
</span>
</span>
<div class="progress-info hint" v-if="activeModel.progressText !== undefined">
<span class="progress-amt-text" v-if="activeModel.progress > 0">{{activeModel.progress}}% complete. </span>
{{activeModel.progressText}}
</div>
</div>
</span>
<a class="close icon-x" @click="dismiss()"></a>
</div>
</template>
<style lang="scss">
.l-message-banner {
display: inline;
left: 50%;
position: absolute;
}
.banner-elem {
display: inline;
}
</style>
<script>
let activeNotification = undefined;
let dialogService = undefined;
export default {
inject: ['openmct'],
data() {
return {
activeModel: undefined
}
},
methods: {
showNotification(notification) {
activeNotification = notification;
this.activeModel = notification.model;
activeNotification.once('destroy', () => {
if (this.activeModel === notification.model){
this.activeModel = undefined;
activeNotification = undefined;
}
});
},
dismiss() {
activeNotification.dismissOrMinimize();
},
maximize() {
//Not implemented yet.
}
},
computed: {
progressWidth() {
return {
width: this.activeModel.progress + '%'
};
}
},
mounted() {
openmct.notifications.on('notification', this.showNotification);
}
}
</script>

View File

@ -0,0 +1,42 @@
<!--
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.
-->
<template>
<span class="c-status">
<indicators></indicators>
<notification-banner></notification-banner>
</span>
</template>
<style lang="scss">
.c-status {
width: 100%;
}
</style>
<script>
import Indicators from './Indicators.vue';
import NotificationBanner from './NotificationBanner.vue';
export default {
components: {
Indicators,
NotificationBanner
}
}
</script>

View File

@ -8,6 +8,8 @@
@click="toggleChildren">
</view-control>
<a class="c-tree__item__label"
draggable="true"
@dragstart="dragStart"
:href="href">
<div class="c-tree__item__type-icon"
:class="cssClass"></div>
@ -28,7 +30,7 @@
import viewControl from '../controls/viewControl.vue'
export default {
name: 'tree-item',
inject: ['openmct'],
inject: ['openmct', 'domainObject'],
props: {
node: Object
},
@ -89,6 +91,9 @@
.then(() => this.loaded = true);
}
},
dragStart($event) {
$event.dataTransfer.setData("domainObject", JSON.stringify(this.node.object));
}
},
components: {
viewControl

View File

@ -11,6 +11,8 @@
</template>
<script>
import ContextMenuDirective from './ContextMenuDirective.js';
export default {
data: function () {
return {

View File

@ -0,0 +1,10 @@
import ContextMenuGesture from './ContextMenuGesture.js';
export default {
bind(element, binding) {
binding.vnode.context.destroy = new ContextMenuGesture(element, binding.value);
},
unbind(){
}
}

View File

@ -0,0 +1,31 @@
import ContextMenu from '../components/ContextMenu.vue';
import Vue from 'vue';
export default function ContextMenuGesture (element, object) {
let vm;
element.addEventListener('context', showContextMenu);
function showContextMenu(event){
vm = new Vue({
...ContextMenu
});
document.body.appendChild(vm.$el);
document.addEventListener('click', hideContextMenu, {
capture: true,
once: true
});
event.preventDefault();
event.stopPropagation();
}
function hideContextMenu() {
vm.destroy();
document.body.removeChild(vm.$el);
}
return function destroy() {
element.removeEventListener('context', this.showContextMenu);
document.removeEventListener('click', hideContextMenu);
}
}
}

View File

@ -0,0 +1,91 @@
<template>
<div class="c-overlay">
<div class="c-overlay__blocker"
v-on:click="destroy">
</div>
<div class="c-overlay__outer">
<button class="c-click-icon c-overlay__close-button icon-x-in-circle"
v-on:click="destroy">
</button>
<div class="c-overlay__contents" ref="element"></div>
<div class="c-overlay__button-bar">
<button class="c-button c-button--major"
v-on:click="destroy">Done</button>
</div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.l-overlay-wrapper {
// Created by overlayService.js, contains this template.
// Acts as an anchor for one or more overlays.
display: contents;
}
.c-overlay {
@include abs();
z-index: 100;
&__blocker {
display: none; // Mobile-first
}
&__outer {
@include abs();
background: $overlayColorBg;
color: $overlayColorFg;
display: flex;
flex-direction: column;
padding: $overlayInnerMargin;
}
&__close-button {
$p: $interiorMarginSm;
border-radius: 100%;
display: inline-block;
position: absolute;
top: $p; right: $p;
}
&__contents {
flex: 1 1 auto;
overflow: auto;
}
&__button-bar {
flex: 0 0 auto;
display: flex;
justify-content: flex-end;
margin-top: $interiorMargin;
}
body.desktop & {
&__blocker {
@include abs();
background: rgba(black, 0.7);
cursor: pointer;
display: block;
}
&__outer {
$m: $overlayOuterMargin;
top: $m; right: $m; bottom: $m; left: $m;
border-radius: $overlayCr;
box-shadow: rgba(black, 0.5) 0 2px 25px;
}
}
}
</style>
<script>
export default {
inject: ['destroy', 'element'],
mounted() {
this.$refs.element.appendChild(this.element);
}
}
</script>

View File

@ -0,0 +1,87 @@
/*****************************************************************************
* 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([
'./overlay.vue',
'vue'
], function (
OverlayComponent,
Vue
) {
function OverlayService() {
this.activeOverlays = [];
this.overlayId = 0;
}
OverlayService.prototype.show = function (element, options) {
if(this.activeOverlays.length) {
this.activeOverlays[this.activeOverlays.length - 1].overlay.classList.add('invisible');
}
let overlayTypeCssClass = options.cssClass, // Values could be l-large-view, l-dialog, l-message
overlay = document.createElement('div'),
component = new Vue({
provide: {
destroy: this.destroy.bind(this),
element: element
},
components: {
OverlayComponent: OverlayComponent.default
},
template: '<overlay-component></overlay-component>'
});
overlay.classList.add('l-overlay-wrapper', overlayTypeCssClass);
document.body.appendChild(overlay);
overlay.appendChild(component.$mount().$el);
this.activeOverlays.push({
overlay: overlay,
component: component,
onDestroy: options.onDestroy,
id: this.overlayId
});
this.overlayId++;
};
OverlayService.prototype.destroy = function () {
var lastActiveOverlayObject = this.activeOverlays.pop(),
lastActiveOverlay = lastActiveOverlayObject.overlay,
lastActiveComponent = lastActiveOverlayObject.component;
if (lastActiveOverlayObject.onDestroy && typeof lastActiveOverlayObject.onDestroy === 'function') {
lastActiveOverlayObject.onDestroy();
}
lastActiveComponent.$destroy(true);
document.body.removeChild(lastActiveOverlay);
if (this.activeOverlays.length) {
this.activeOverlays[this.activeOverlays.length - 1].overlay.classList.remove('invisible');
}
};
return OverlayService;
});

View File

@ -7,12 +7,24 @@ define([
return function install(openmct) {
let navigateCall = 0;
let browseObject;
let removeSelectable = undefined;
function viewObject(object, viewProvider) {
if (removeSelectable) {
removeSelectable();
removeSelectable = undefined;
}
openmct.layout.$refs.browseObject.show(object, viewProvider.key);
openmct.layout.$refs.browseBar.domainObject = object;
openmct.layout.$refs.browseBar.viewKey = viewProvider.key;
removeSelectable = openmct.selection.selectable(
openmct.layout.$refs.browseObject.$el,
{
item: object
},
true
);
};
function navigateToPath(path, currentViewKey) {
@ -22,16 +34,18 @@ define([
if (!Array.isArray(path)) {
path = path.split('/');
}
let keyString = path[path.length - 1];
// TODO: retain complete path in navigation.
return openmct.objects.get(keyString)
.then((object) => {
return Promise.all(path.map((keyString)=>{
return openmct.objects.get(keyString);
})).then((objects)=>{
if (currentNavigation !== navigateCall) {
return; // Prevent race.
}
openmct.layout.$refs.browseBar.domainObject = object;
browseObject = object;
if (!object) {
let navigatedObject = objects[objects.length - 1];
openmct.layout.$refs.browseBar.domainObject = navigatedObject;
browseObject = navigatedObject;
if (!navigatedObject) {
openmct.layout.$refs.browseObject.clear();
return;
}
@ -39,12 +53,12 @@ define([
.objectViews
.getByProviderKey(currentViewKey)
if (currentProvider && currentProvider.canView(object)) {
viewObject(object, currentProvider);
if (currentProvider && currentProvider.canView(navigatedObject)) {
viewObject(navigatedObject, currentProvider);
return;
}
let defaultProvider = openmct.objectViews.get(object)[0];
let defaultProvider = openmct.objectViews.get(navigatedObject)[0];
if (defaultProvider) {
openmct.router.updateParams({
view: defaultProvider.key