Commit a7daf58b by Torkel Ödegaard

wip: progress on edit mode ux with tabs

parent 8a9d721c
...@@ -15,7 +15,7 @@ interface Props { ...@@ -15,7 +15,7 @@ interface Props {
class CustomScrollbar extends PureComponent<Props> { class CustomScrollbar extends PureComponent<Props> {
static defaultProps: Partial<Props> = { static defaultProps: Partial<Props> = {
customClassName: 'custom-scrollbars', customClassName: 'custom-scrollbars',
autoHide: true, autoHide: false,
autoHideTimeout: 200, autoHideTimeout: 200,
autoHideDuration: 200, autoHideDuration: 200,
hideTracksWhenNotNeeded: false, hideTracksWhenNotNeeded: false,
......
import React, { PureComponent } from 'react';
import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
import { FadeIn } from 'app/core/components/Animations/FadeIn';
interface Props {
selectedText?: string;
selectedImage?: string;
children: JSX.Element;
toolbarItems: EditorToolBarView[];
}
export interface EditorToolBarView {
title: string;
imgSrc: string;
render: () => JSX.Element;
}
interface State {
openView?: EditorToolBarView;
}
export class EditorTabBody extends PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = {
openView: null,
};
}
onToggleToolBarView = (item: EditorToolBarView) => {
this.setState({
openView: item === this.state.openView ? null : item,
});
}
onCloseOpenView = () => {
this.setState({ openView: null });
}
renderToolBarViewToggle(item: EditorToolBarView) {
return (
<div className="edit-section__selected" onClick={() => this.onToggleToolBarView(item)} key={item.title}>
<img className="edit-section__selected-image" src={item.imgSrc} />
<div className="edit-section__selected-name">{item.title}</div>
<i className="fa fa-caret-down" />
</div>
);
}
renderOpenView(view: EditorToolBarView) {
return (
<div className="editor-toolbar-view">
<button className="editor-toolbar-view__close" onClick={this.onCloseOpenView}>
<i className="fa fa-remove" />
</button>
{view.render()}
</div>
);
}
render() {
const { children, toolbarItems} = this.props;
const { openView } = this.state;
return (
<>
<div className="edit-section">
<div className="edit-section__header">{toolbarItems.map(item => this.renderToolBarViewToggle(item))}</div>
</div>
<div className="panel-editor__scroll">
<CustomScrollbar>
<div className="panel-editor__content">
<FadeIn in={openView !== null} duration={300}>
{openView && this.renderOpenView(openView)}
</FadeIn>
{children}
</div>
</CustomScrollbar>
</div>
</>
);
}
}
...@@ -2,8 +2,7 @@ import React, { PureComponent } from 'react'; ...@@ -2,8 +2,7 @@ import React, { PureComponent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { QueriesTab } from './QueriesTab'; import { QueriesTab } from './QueriesTab';
import { VizTypePicker } from './VizTypePicker'; import { VisualizationTab } from './VisualizationTab';
import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
import { store } from 'app/store/configureStore'; import { store } from 'app/store/configureStore';
import { updateLocation } from 'app/core/actions'; import { updateLocation } from 'app/core/actions';
...@@ -38,35 +37,6 @@ export class PanelEditor extends PureComponent<PanelEditorProps> { ...@@ -38,35 +37,6 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
]; ];
} }
renderQueriesTab() {
return <QueriesTab panel={this.props.panel} dashboard={this.props.dashboard} />;
}
renderPanelOptions() {
const { plugin, panel } = this.props;
const { PanelOptionsComponent } = plugin.exports;
if (PanelOptionsComponent) {
return <PanelOptionsComponent options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
} else {
return <p>Visualization has no options</p>;
}
}
onPanelOptionsChanged = (options: any) => {
this.props.panel.updateOptions(options);
this.forceUpdate();
};
renderVizTab() {
return (
<div className="viz-editor">
<VizTypePicker current={this.props.plugin} onTypeChanged={this.props.onTypeChanged} />
{this.renderPanelOptions()}
</div>
);
}
onChangeTab = (tab: PanelEditorTab) => { onChangeTab = (tab: PanelEditorTab) => {
store.dispatch( store.dispatch(
updateLocation({ updateLocation({
...@@ -87,50 +57,8 @@ export class PanelEditor extends PureComponent<PanelEditorProps> { ...@@ -87,50 +57,8 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
}; };
render() { render() {
return this.renderAsTabs(); const { panel, dashboard, onTypeChanged, plugin } = this.props;
// return this.renderAsBoxes();
// const { location } = store.getState();
// const activeTab = location.query.tab || 'queries';
//
// return (
// <div className="panel-editor-container__editor">
// <div className="panel-editor-resizer">
// <div className="panel-editor-resizer__handle">
// <div className="panel-editor-resizer__handle-dots" />
// </div>
// </div>
// <div className="panel-editor__aside">
// <h2 className="panel-editor__aside-header">
// <i className="fa fa-cog" />
// Edit Panel
// </h2>
//
// {this.tabs.map(tab => {
// return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
// })}
//
// <div className="panel-editor__aside-actions">
// <button className="btn btn-secondary" onClick={this.onClose}>
// Back to dashboard
// </button>
// <button className="btn btn-inverse" onClick={this.onClose}>
// Discard changes
// </button>
// </div>
// </div>
// <div className="panel-editor__content">
// <CustomScrollbar>
// {activeTab === 'queries' && this.renderQueriesTab()}
// {activeTab === 'visualization' && this.renderVizTab()}
// </CustomScrollbar>
// </div>
// </div>
// );
}
renderAsTabs() {
const { location } = store.getState(); const { location } = store.getState();
const { panel } = this.props;
const activeTab = location.query.tab || 'queries'; const activeTab = location.query.tab || 'queries';
return ( return (
...@@ -141,49 +69,22 @@ export class PanelEditor extends PureComponent<PanelEditorProps> { ...@@ -141,49 +69,22 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
</div> </div>
</div> </div>
<div className="tabbed-view tabbed-view--new"> <div className="panel-editor-tabs">
<div className="tabbed-view-header">
<h3 className="tabbed-view-panel-title">{panel.title}</h3>
<ul className="gf-tabs"> <ul className="gf-tabs">
{this.tabs.map(tab => { {this.tabs.map(tab => {
return <OldTabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />; return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
})} })}
</ul> </ul>
<button className="tabbed-view-close-btn" onClick={this.onClose}> <button className="panel-editor-tabs__close" onClick={this.onClose}>
<i className="fa fa-remove" /> <i className="fa fa-remove" />
</button> </button>
</div> </div>
<div className="tabbed-view-body"> {activeTab === 'queries' && <QueriesTab panel={panel} dashboard={dashboard} />}
<CustomScrollbar> {activeTab === 'visualization' && (
{activeTab === 'queries' && this.renderQueriesTab()} <VisualizationTab panel={panel} dashboard={dashboard} plugin={plugin} onTypeChanged={onTypeChanged} />
{activeTab === 'visualization' && this.renderVizTab()} )}
</CustomScrollbar>
</div>
</div>
</div>
);
}
renderAsBoxes() {
const { location } = store.getState();
const { panel } = this.props;
const activeTab = location.query.tab || 'queries';
return (
<div className="panel-editor-container__editor">
<div className="panel-editor-resizer">
<div className="panel-editor-resizer__handle">
<div className="panel-editor-resizer__handle-dots" />
</div>
</div>
<CustomScrollbar>
{this.renderQueriesTab()}
{this.renderVizTab()}
</CustomScrollbar>
</div> </div>
); );
} }
...@@ -197,26 +98,15 @@ interface TabItemParams { ...@@ -197,26 +98,15 @@ interface TabItemParams {
function TabItem({ tab, activeTab, onClick }: TabItemParams) { function TabItem({ tab, activeTab, onClick }: TabItemParams) {
const tabClasses = classNames({ const tabClasses = classNames({
'panel-editor__aside-item': true,
active: activeTab === tab.id,
});
return (
<a className={tabClasses} onClick={() => onClick(tab)}>
<i className={tab.icon} /> {tab.text}
</a>
);
}
function OldTabItem({ tab, activeTab, onClick }: TabItemParams) {
const tabClasses = classNames({
'gf-tabs-link': true, 'gf-tabs-link': true,
active: activeTab === tab.id, active: activeTab === tab.id,
}); });
return ( return (
<li className="gf-tabs-item" onClick={() => onClick(tab)}> <li className="gf-tabs-item" onClick={() => onClick(tab)}>
<a className={tabClasses}>{tab.text}</a> <a className={tabClasses}>
<i className={tab.icon} /> {tab.text}
</a>
</li> </li>
); );
} }
...@@ -3,6 +3,7 @@ import React, { PureComponent } from 'react'; ...@@ -3,6 +3,7 @@ import React, { PureComponent } from 'react';
// Services & utils // Services & utils
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { EditorTabBody } from './EditorTabBody';
// Types // Types
import { PanelModel } from '../panel_model'; import { PanelModel } from '../panel_model';
...@@ -48,6 +49,20 @@ export class QueriesTab extends PureComponent<Props> { ...@@ -48,6 +49,20 @@ export class QueriesTab extends PureComponent<Props> {
} }
render() { render() {
return <div ref={element => (this.element = element)} className="panel-height-helper" />; const currentDataSource = {
title: 'ProductionDB',
imgSrc: 'public/app/plugins/datasource/prometheus/img/prometheus_logo.svg',
render: () => {
return (
<h2>Hello</h2>
);
},
};
return (
<EditorTabBody toolbarItems={[currentDataSource]}>
<div ref={element => (this.element = element)} style={{ width: '100%' }} />
</EditorTabBody>
);
} }
} }
import React, { PureComponent } from 'react';
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { EditorTabBody } from './EditorTabBody';
import { VizTypePicker } from './VizTypePicker';
import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model';
interface Props {
panel: PanelModel;
dashboard: DashboardModel;
plugin: PluginModel;
onTypeChanged: (newType: PanelPlugin) => void;
}
export class VisualizationTab extends PureComponent<Props> {
constructor(props) {
super(props);
}
renderPanelOptions() {
const { plugin, panel } = this.props;
const { PanelOptionsComponent } = plugin.exports;
if (PanelOptionsComponent) {
return <PanelOptionsComponent options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
} else {
return <p>Visualization has no options</p>;
}
}
onPanelOptionsChanged = (options: any) => {
this.props.panel.updateOptions(options);
this.forceUpdate();
};
render() {
const {plugin, onTypeChanged} = this.props;
const panelSelection = {
title: plugin.name,
imgSrc: plugin.info.logos.small,
render: () => {
return <VizTypePicker current={plugin} onTypeChanged={onTypeChanged} />;
},
};
return (
<EditorTabBody toolbarItems={[panelSelection]}>
{this.renderPanelOptions()}
</EditorTabBody>
);
}
}
...@@ -2,7 +2,6 @@ import React, { PureComponent } from 'react'; ...@@ -2,7 +2,6 @@ import React, { PureComponent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import _ from 'lodash'; import _ from 'lodash';
import { FadeIn } from 'app/core/components/Animations/FadeIn';
import config from 'app/core/config'; import config from 'app/core/config';
import { PanelPlugin } from 'app/types/plugins'; import { PanelPlugin } from 'app/types/plugins';
...@@ -13,7 +12,6 @@ interface Props { ...@@ -13,7 +12,6 @@ interface Props {
interface State { interface State {
pluginList: PanelPlugin[]; pluginList: PanelPlugin[];
isOpen: boolean;
} }
export class VizTypePicker extends PureComponent<Props, State> { export class VizTypePicker extends PureComponent<Props, State> {
...@@ -22,7 +20,6 @@ export class VizTypePicker extends PureComponent<Props, State> { ...@@ -22,7 +20,6 @@ export class VizTypePicker extends PureComponent<Props, State> {
this.state = { this.state = {
pluginList: this.getPanelPlugins(''), pluginList: this.getPanelPlugins(''),
isOpen: false,
}; };
} }
...@@ -65,43 +62,12 @@ export class VizTypePicker extends PureComponent<Props, State> { ...@@ -65,43 +62,12 @@ export class VizTypePicker extends PureComponent<Props, State> {
); );
} }
onToggleOpen = () => {
this.setState({ isOpen: !this.state.isOpen });
};
render() { render() {
const { current } = this.props; const { current } = this.props;
const { pluginList, isOpen } = this.state; const { pluginList } = this.state;
return ( return (
<div className="viz-picker"> <>
<div className="viz-picker__bar">
<div className="gf-form-inline">
<div className="gf-form">
<label className="gf-form-label">Visualization</label>
<label className="gf-form-input width-10" onClick={this.onToggleOpen}>
<span>{current.name}</span>
{isOpen && <i className="fa fa-caret-down pull-right" />}
{!isOpen && <i className="fa fa-caret-left pull-right" />}
</label>
</div>
<div className="gf-form gf-form--grow">
<label className="gf-form-label gf-form-label--grow" />
</div>
<div className="gf-form">
<label className="gf-form-label">
<i className="fa fa-fw fa-caret-down" /> Help
</label>
</div>
</div>
</div>
<FadeIn in={isOpen} duration={300}>
<div className="cta-form">
<button className="cta-form__close" onClick={this.onToggleOpen}>
<i className="fa fa-remove" />
</button>
<div className="cta-form__bar"> <div className="cta-form__bar">
<div className="cta-form__bar-header">Select visualization</div> <div className="cta-form__bar-header">Select visualization</div>
{this.renderFilters()} {this.renderFilters()}
...@@ -109,9 +75,7 @@ export class VizTypePicker extends PureComponent<Props, State> { ...@@ -109,9 +75,7 @@ export class VizTypePicker extends PureComponent<Props, State> {
</div> </div>
<div className="viz-picker__items">{pluginList.map(this.renderVizPlugin)}</div> <div className="viz-picker__items">{pluginList.map(this.renderVizPlugin)}</div>
</div> </>
</FadeIn>
</div>
); );
} }
} }
<div class="gf-form-group"> <div class="gf-form-group hide">
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label">Data Source</label> <label class="gf-form-label">Data Source</label>
......
...@@ -19,6 +19,10 @@ ...@@ -19,6 +19,10 @@
transform: translate(0px, 0px) !important; transform: translate(0px, 0px) !important;
} }
.panel {
margin: 0 !important;
}
// Disable grid interaction indicators in fullscreen panels // Disable grid interaction indicators in fullscreen panels
.panel-header:hover { .panel-header:hover {
background-color: inherit; background-color: inherit;
......
...@@ -416,7 +416,7 @@ select.gf-form-input ~ .gf-form-help-icon { ...@@ -416,7 +416,7 @@ select.gf-form-input ~ .gf-form-help-icon {
position: absolute; position: absolute;
right: 0; right: 0;
top: -2px; top: -2px;
font-size: $font-size-lg; font-size: $font-size-md;
&:hover { &:hover {
color: $text-color-strong; color: $text-color-strong;
......
...@@ -11,16 +11,20 @@ ...@@ -11,16 +11,20 @@
.panel-editor-container__editor { .panel-editor-container__editor {
margin-top: $panel-margin*2; margin-top: $panel-margin*2;
display: flex; display: flex;
flex-direction: row; flex-direction: column;
height: 65%; height: 65%;
position: relative; position: relative;
} }
.panel-editor__content { .panel-editor__scroll {
flex-grow: 1; flex-grow: 1;
min-width: 0; min-width: 0;
height: 100%; display: flex;
padding: 0 20px; padding: 0 5px;
}
.panel-editor__content {
padding: 40px 15px;
} }
.panel-in-fullscreen { .panel-in-fullscreen {
...@@ -107,66 +111,6 @@ ...@@ -107,66 +111,6 @@
height: 55px; height: 55px;
} }
.panel-editor__aside {
background: $panel-bg;
display: flex;
flex-direction: column;
}
.panel-editor__aside-item {
padding: 8px 20px;
color: $text-color;
font-size: $font-size-md;
@include left-brand-border;
&.active {
@include left-brand-border-gradient();
background: $page-bg;
}
i {
width: 23px;
}
.gicon {
margin-bottom: 2px;
}
.fa {
font-size: 17px;
}
}
.panel-editor__aside-actions {
display: flex;
flex-direction: column;
height: 100%;
flex-grow: 1;
padding: 60px 15px;
button {
margin-bottom: 10px;
}
}
.panel-editor__aside-header {
color: $text-muted;
font-size: $font-size-h3;
padding: 20px 30px 10px 20px;
white-space: nowrap;
i {
font-size: 25px;
position: relative;
top: 1px;
padding-right: 5px;
}
}
.viz-picker__bar {
margin-bottom: $spacer;
}
.panel-editor-resizer { .panel-editor-resizer {
position: absolute; position: absolute;
height: 2px; height: 2px;
...@@ -223,3 +167,99 @@ ...@@ -223,3 +167,99 @@
font-size: $font-size-lg; font-size: $font-size-lg;
margin-bottom: 20px; margin-bottom: 20px;
} }
.edit-section {
position: relative;
}
.edit-section__header {
display: flex;
align-content: center;
align-items: center;
background: linear-gradient(90deg, #292a2d, black);
padding: 7px 30px 7px 20px;
box-shadow: 0 0 20px black;
cursor: pointer;
}
.edit-section__selected {
padding: $input-padding-y $input-padding-x;
font-size: $font-size-md;
line-height: $input-line-height;
color: $input-color;
background-color: $input-bg;
border: $input-border;
border-radius: $input-border-radius;
display: flex;
align-items: center;
.fa {
margin-left: 20px;
display: inline-block;
position: relative;
}
}
.edit-section__title {
font-size: $font-size-lg;
padding-right: 20px;
width: 150px;
}
.edit-section__selected-image {
margin-right: 10px;
display: inline-block;
width: 20px;
height: 20px;
}
.panel-editor-tabs {
z-index: 1;
box-shadow: $page-header-shadow;
border-bottom: 1px solid $page-header-border-color;
padding: 0 $dashboard-padding;
@include clearfix();
.active.gf-tabs-link {
background: #242427;
}
}
.panel-editor-tabs__close {
float: right;
padding: 0;
margin: 0;
background-color: transparent;
border: none;
padding: $tabs-padding;
color: $text-color;
i {
font-size: 120%;
}
&:hover {
color: $text-color-strong;
}
}
.editor-toolbar-view {
position: relative;
padding: 10px 20px;
background-color: $empty-list-cta-bg;
border-top: 3px solid $orange;
margin: 0 100px;
}
.editor-toolbar-view__close {
background: transparent;
padding: 4px 8px 4px 9px;
border: none;
position: absolute;
right: 0;
top: -2px;
font-size: $font-size-md;
&:hover {
color: $text-color-strong;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment