mirror of
https://github.com/nasa/openmct.git
synced 2025-01-18 02:39:56 +00:00
Tab view (#2197)
* first cut, working for objects with explicit height * working for all objects by setting min-height explicitly to 70vh * add composition listeners * Tabs view WIP - Markup and CSS BEMized; - Stubbed in type icon in tab elements; - TODO: refine styling on tabs; * Tabs view WIP - Layout enhancements; * add types and header * remove static icon-layout class * move Tabs into its own plugin folder: * simplify on composition add listener callback * fix icon rendering * add document dragstart and dragend listeners, with v-if div for drop target * remove third argument from document listeners, move drop target to tab container * Sanding and shimming on Tabs View - WIP * Tabs View styling - Shippable to Deep; - Added new c-drop-hint element in _controls.scss; - Added new theme constants; - Added 'empty' tabs view message; - TODOs: add listener for dragover event for is-mouse-over styling, integrate forthcoming changes to symbolsfont; * add is-mouse-over class when drag enters dropHint div, add and remove listeners * .c-drop-hint styling refined - Refined hover effects; - Bg icon instead of glyph character; - TODOs: Change $bg-icon to plus sign; * fix bug regarding persisting drop-hint styling even after drop is ended
This commit is contained in:
parent
7c54ec4f9f
commit
35d1b894e2
@ -77,6 +77,7 @@
|
||||
openmct.install(openmct.plugins.SummaryWidget());
|
||||
openmct.install(openmct.plugins.Notebook());
|
||||
openmct.install(openmct.plugins.FolderView());
|
||||
openmct.install(openmct.plugins.Tabs());
|
||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||
openmct.time.timeSystem('utc');
|
||||
openmct.start();
|
||||
|
@ -37,6 +37,7 @@ define([
|
||||
'./notebook/plugin',
|
||||
'./displayLayout/plugin',
|
||||
'./folderView/plugin',
|
||||
'./tabs/plugin',
|
||||
'../../platform/features/fixed/plugin'
|
||||
], function (
|
||||
_,
|
||||
@ -55,6 +56,7 @@ define([
|
||||
Notebook,
|
||||
DisplayLayoutPlugin,
|
||||
FolderView,
|
||||
Tabs,
|
||||
FixedView
|
||||
) {
|
||||
var bundleMap = {
|
||||
@ -167,6 +169,7 @@ define([
|
||||
plugins.Notebook = Notebook;
|
||||
plugins.DisplayLayout = DisplayLayoutPlugin.default;
|
||||
plugins.FolderView = FolderView;
|
||||
plugins.Tabs = Tabs;
|
||||
plugins.FixedView = FixedView;
|
||||
|
||||
return plugins;
|
||||
|
177
src/plugins/tabs/components/tabs.vue
Normal file
177
src/plugins/tabs/components/tabs.vue
Normal file
@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="c-tabs-view">
|
||||
<div class="c-tabs-view__tabs-holder c-compact-button-holder"
|
||||
:class="{
|
||||
'is-dragging': isDragging,
|
||||
'is-mouse-over': allowDrop
|
||||
}">
|
||||
<div class="c-drop-hint"
|
||||
@drop="onDrop"
|
||||
ref="dropHint">
|
||||
</div>
|
||||
<div class="c-tabs-view__empty-message"
|
||||
v-if="!tabsList.length > 0">Drag objects here to add them to this view.</div>
|
||||
<button class="c-tabs-view__tab c-compact-button"
|
||||
v-for="(tab,index) in tabsList"
|
||||
:key="index"
|
||||
:class="[
|
||||
{'is-current': tab=== currentTab},
|
||||
tab.type.definition.cssClass
|
||||
]"
|
||||
@click="showTab(tab)">
|
||||
<span class="c-button__label">{{tab.model.name}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="c-tabs-view__object-holder"
|
||||
v-for="(object, index) in tabsList"
|
||||
:key="index"
|
||||
:class="{'invisible': object !== currentTab}">
|
||||
<div class="c-tabs-view__object-name l-browse-bar__object-name--w"
|
||||
:class="currentTab.type.definition.cssClass">
|
||||
<div class="l-browse-bar__object-name">
|
||||
{{currentTab.model.name}}
|
||||
</div>
|
||||
</div>
|
||||
<object-view class="c-tabs-view__object"
|
||||
:object="object.model">
|
||||
</object-view>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~styles/sass-base.scss';
|
||||
|
||||
.c-tabs-view {
|
||||
$h: 20px;
|
||||
@include abs();
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
> * + * {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
|
||||
&__tabs-holder {
|
||||
@include userSelectNone();
|
||||
flex: 0 0 auto;
|
||||
min-height: $h;
|
||||
}
|
||||
|
||||
&__object-holder {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__object-name {
|
||||
flex: 0 0 auto;
|
||||
font-size: 1.2em !important;
|
||||
margin: $interiorMargin 0 $interiorMarginLg 0;
|
||||
}
|
||||
|
||||
&__object {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__empty-message {
|
||||
color: rgba($colorBodyFg, 0.7);
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
line-height: $h;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ObjectView from '../../../ui/components/layout/ObjectView.vue';
|
||||
|
||||
var unknownObjectType = {
|
||||
definition: {
|
||||
cssClass: 'icon-object-unknown',
|
||||
name: 'Unknown Type'
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
inject: ['openmct','domainObject', 'composition'],
|
||||
components: {
|
||||
ObjectView
|
||||
},
|
||||
mounted () {
|
||||
if (this.composition) {
|
||||
this.composition.on('add', this.addItem, this);
|
||||
this.composition.load();
|
||||
}
|
||||
|
||||
document.addEventListener('dragstart', this.dragstart);
|
||||
document.addEventListener('dragend', this.dragend);
|
||||
|
||||
let dropHint = this.$refs.dropHint;
|
||||
|
||||
if (dropHint) {
|
||||
dropHint.addEventListener('dragenter', this.dragenter);
|
||||
dropHint.addEventListener('dragleave', this.dragleave);
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
currentTab: {},
|
||||
tabsList: [],
|
||||
setCurrentTab: true,
|
||||
isDragging: false,
|
||||
allowDrop: false
|
||||
};
|
||||
},
|
||||
methods:{
|
||||
showTab (tab) {
|
||||
this.currentTab = tab;
|
||||
},
|
||||
addItem (model) {
|
||||
let type = this.openmct.types.get(model.type) || unknownObjectType,
|
||||
tabItem = {
|
||||
model,
|
||||
type: type
|
||||
};
|
||||
|
||||
this.tabsList.push(tabItem);
|
||||
|
||||
if (this.setCurrentTab) {
|
||||
this.currentTab = tabItem;
|
||||
this.setCurrentTab = false;
|
||||
}
|
||||
},
|
||||
onDrop (e) {
|
||||
this.setCurrentTab = true;
|
||||
},
|
||||
dragstart (e) {
|
||||
if (e.dataTransfer.getData('domainObject')) {
|
||||
this.isDragging = true;
|
||||
}
|
||||
},
|
||||
dragend (e) {
|
||||
this.isDragging = false;
|
||||
this.allowDrop = false;
|
||||
},
|
||||
dragenter () {
|
||||
this.allowDrop = true;
|
||||
},
|
||||
dragleave() {
|
||||
this.allowDrop = false;
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
this.composition.off('add', this.addItem, this);
|
||||
|
||||
document.removeEventListener('dragstart', this.dragstart);
|
||||
document.removeEventListener('dragend', this.dragend);
|
||||
},
|
||||
beforeDestroy() {
|
||||
let dropHint = this.$refs.dropHint;
|
||||
|
||||
dropHint.removeEventListener('dragenter', this.dragenter);
|
||||
dropHint.removeEventListener('dragleave', this.dragleave);
|
||||
}
|
||||
}
|
||||
</script>
|
42
src/plugins/tabs/plugin.js
Normal file
42
src/plugins/tabs/plugin.js
Normal 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./tabs'
|
||||
], function (
|
||||
Tabs
|
||||
) {
|
||||
return function plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new Tabs(openmct));
|
||||
|
||||
openmct.types.addType('tabs', {
|
||||
name: "Tabs View",
|
||||
creatable: true,
|
||||
cssClass: 'icon-tabs-view',
|
||||
initialize(domainObject) {
|
||||
domainObject.composition = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
68
src/plugins/tabs/tabs.js
Normal file
68
src/plugins/tabs/tabs.js
Normal file
@ -0,0 +1,68 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./components/tabs.vue',
|
||||
'vue'
|
||||
], function (
|
||||
TabsComponent,
|
||||
Vue
|
||||
) {
|
||||
function Tabs(openmct) {
|
||||
return {
|
||||
key: 'tabs',
|
||||
name: 'Tabs',
|
||||
cssClass: 'icon-list-view',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'tabs';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
TabsComponent: TabsComponent.default
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
composition: openmct.composition.get(domainObject)
|
||||
},
|
||||
el: element,
|
||||
template: '<tabs-component></tabs-component>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
return Tabs;
|
||||
});
|
@ -104,10 +104,15 @@ $colorGridLines: rgba($editColor, 0.2);
|
||||
$colorIconAlias: #4af6f3;
|
||||
$colorIconAliasForKeyFilter: #aaa;
|
||||
|
||||
// Holders
|
||||
$colorTabsHolderBg: rgba(black, 0.2);
|
||||
|
||||
// Buttons and Controls
|
||||
$colorBtnBg: lighten($colorBodyBg, 10%); // !
|
||||
$colorBtnBgHov: lighten($colorBtnBg, 10%); // !
|
||||
$colorBtnFg: lighten($colorBodyFg, 10%); // !
|
||||
$colorBtnBg: lighten($colorBodyBg, 10%);
|
||||
$colorBtnBgHov: lighten($colorBtnBg, 10%);
|
||||
$colorBtnFg: lighten($colorBodyFg, 10%);
|
||||
$colorBtnReverseFg: lighten($colorBtnFg, 10%);
|
||||
$colorBtnReverseBg: lighten($colorBtnBg, 10%);
|
||||
$colorBtnFgHov: $colorBtnFg;
|
||||
$colorBtnMajorBg: $colorKey;
|
||||
$colorBtnMajorBgHov: $colorKeyHov;
|
||||
@ -119,6 +124,10 @@ $colorBtnCautionFg: $colorBtnFg;
|
||||
$colorClickIcon: $colorKey;
|
||||
$colorClickIconBgHov: rgba($colorKey, 0.6);
|
||||
$colorClickIconFgHov: $colorKeyHov;
|
||||
$colorDropHint: $colorKey;
|
||||
$colorDropHintBg: darken($colorDropHint, 10%);
|
||||
$colorDropHintBgHov: $colorDropHint;
|
||||
$colorDropHintFg: lighten($colorDropHint, 40%);
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: lighten($colorBodyBg, 15%);
|
||||
|
@ -104,10 +104,15 @@ $colorGridLines: rgba($editColor, 0.2);
|
||||
$colorIconAlias: #4af6f3;
|
||||
$colorIconAliasForKeyFilter: #aaa;
|
||||
|
||||
// Holders
|
||||
$colorTabsHolderBg: rgba($colorBodyFg, 0.2);
|
||||
|
||||
// Buttons and Controls
|
||||
$colorBtnBg: #aaaaaa;
|
||||
$colorBtnBg: #aaa;
|
||||
$colorBtnBgHov: darken($colorBtnBg, 10%);
|
||||
$colorBtnFg: #fff;
|
||||
$colorBtnReverseFg: $colorBodyBg;
|
||||
$colorBtnReverseBg: $colorBodyFg;
|
||||
$colorBtnFgHov: $colorBtnFg;
|
||||
$colorBtnMajorBg: $colorKey;
|
||||
$colorBtnMajorBgHov: $colorKeyHov;
|
||||
@ -119,6 +124,10 @@ $colorBtnCautionFg: $colorBtnFg;
|
||||
$colorClickIcon: $colorKey;
|
||||
$colorClickIconBgHov: rgba($colorKey, 0.2);
|
||||
$colorClickIconFgHov: $colorKeyHov;
|
||||
$colorDropHint: $colorKey;
|
||||
$colorDropHintBg: darken($colorDropHint, 10%);
|
||||
$colorDropHintBgHov: $colorDropHint;
|
||||
$colorDropHintFg: lighten($colorDropHint, 40%);
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: lighten($colorBodyBg, 10%);
|
||||
|
@ -32,13 +32,6 @@ button {
|
||||
}
|
||||
|
||||
.c-button--menu {
|
||||
$m: $interiorMargin;
|
||||
|
||||
&:before,
|
||||
> * {
|
||||
margin-right: $m;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: $glyph-icon-arrow-down;
|
||||
font-family: symbolsfont;
|
||||
@ -46,6 +39,10 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.c-compact-button {
|
||||
@include cCompactButtons($bg: $colorBtnBg, $fg: $colorBtnFg, $bgHov: $colorBtnBgHov);
|
||||
}
|
||||
|
||||
/********* Icon Buttons */
|
||||
.c-click-icon {
|
||||
@include cClickIcon();
|
||||
@ -487,3 +484,48 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
@include cControl();
|
||||
> * + * { margin-left: $interiorMargin; }
|
||||
}
|
||||
|
||||
/***************************************************** DRAG AND DROP */
|
||||
.c-drop-hint {
|
||||
// Used in Tabs View, Flexible Grid Layouts
|
||||
@include abs();
|
||||
background-color: $colorDropHintBg;
|
||||
color: $colorDropHintFg;
|
||||
border-radius: $basicCr;
|
||||
border: 1px dashed $colorDropHintFg;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: $transOut;
|
||||
z-index: 50;
|
||||
|
||||
opacity: 0; // Must use this (rather than display: none) to enable transition effects
|
||||
pointer-events: none;
|
||||
|
||||
&:before {
|
||||
$h: 80%;
|
||||
$mh: 50px;
|
||||
background: $bg-icon-activity; // TODO: change to $bg-icon-plus
|
||||
background-size: contain;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
display: block;
|
||||
filter: $colorKeyFilterHov;
|
||||
height: $h; width: $h;
|
||||
max-height: $mh; max-width: $mh;
|
||||
}
|
||||
|
||||
.is-dragging & {
|
||||
pointer-events: inherit;
|
||||
transition: $transIn;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.is-mouse-over & {
|
||||
transition: $transIn;
|
||||
background-color: $colorDropHintBgHov;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +172,7 @@
|
||||
unicode-bidi:bidi-override;
|
||||
}
|
||||
|
||||
/************************** CONTROLS, BUTTONS */
|
||||
/************************** CONTROLS, BUTTONS, INPUTS */
|
||||
@mixin hover {
|
||||
body.desktop & {
|
||||
&:hover {
|
||||
@ -236,6 +236,15 @@
|
||||
box-shadow: $shdw;
|
||||
}
|
||||
|
||||
@mixin buttonBehavior() {
|
||||
// Assign transition timings
|
||||
transition: $transOut;
|
||||
|
||||
@include hover() {
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin cControl() {
|
||||
$fs: 1em;
|
||||
@include userSelectNone();
|
||||
@ -266,11 +275,17 @@
|
||||
@mixin cButton() {
|
||||
@include cControl();
|
||||
@include themedButton();
|
||||
//@include buttonBehavior();
|
||||
border-radius: $controlCr;
|
||||
color: $colorBtnFg;
|
||||
cursor: pointer;
|
||||
padding: $interiorMargin floor($interiorMargin * 1.25);
|
||||
|
||||
&:after,
|
||||
> * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
@include hover() {
|
||||
background: $colorBtnBgHov;
|
||||
color: $colorBtnFgHov;
|
||||
@ -347,6 +362,50 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin cCompactButtons($bg, $fg, $bgHov) {
|
||||
// Used in Tab view
|
||||
// To be used in indicators popups?
|
||||
|
||||
$m: 1px;
|
||||
$btnM: $m;
|
||||
|
||||
&-holder {
|
||||
background: $colorTabsHolderBg;
|
||||
border-radius: $controlCr;
|
||||
padding: $m ($m - $btnM) ($m - $btnM) $m;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: stretch;
|
||||
|
||||
> * {
|
||||
margin: 0 $btnM $btnM 0;
|
||||
}
|
||||
}
|
||||
|
||||
background: $colorBtnBg;
|
||||
color: $colorBtnFg;
|
||||
flex: 1 1 auto;
|
||||
padding: $interiorMargin;
|
||||
|
||||
&:before {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
> * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
@include hover() {
|
||||
background: $bgHov;
|
||||
}
|
||||
|
||||
&.is-current {
|
||||
background: $colorBtnReverseBg;
|
||||
color: $colorBtnReverseFg;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin wrappedInput() {
|
||||
// An input that is wrapped. Optionally includes a __label or icon element.
|
||||
// Based on .c-search.
|
||||
|
@ -64,6 +64,7 @@
|
||||
}
|
||||
|
||||
&__drag-area {
|
||||
// TODO: recast this element to use c-drop-hint element
|
||||
background: rgba($colorKey, 0.1);
|
||||
border: 1px dashed rgba($colorKey, 0.7);
|
||||
border-radius: $controlCr;
|
||||
|
Loading…
Reference in New Issue
Block a user