Commit 727efa5a by Torkel Ödegaard

Merge branch 'pane-edit-ux-tabs' into develop

parents e0feb726 a0620ac8
import React, { SFC } from 'react';
import Transition from 'react-transition-group/Transition';
interface Props {
duration: number;
children: JSX.Element;
in: boolean;
}
export const FadeIn: SFC<Props> = props => {
const defaultStyle = {
transition: `opacity ${props.duration}ms linear`,
opacity: 0,
};
const transitionStyles = {
exited: { opacity: 0, display: 'none' },
entering: { opacity: 0 },
entered: { opacity: 1 },
exiting: { opacity: 0 },
};
return (
<Transition in={props.in} timeout={props.duration}>
{state => (
<div
style={{
...defaultStyle,
...transitionStyles[state],
}}
>
{props.children}
</div>
)}
</Transition>
);
};
...@@ -23,7 +23,7 @@ export default ({ children, in: inProp, maxHeight = defaultMaxHeight, style = de ...@@ -23,7 +23,7 @@ export default ({ children, in: inProp, maxHeight = defaultMaxHeight, style = de
const transitionStyles = { const transitionStyles = {
exited: { maxHeight: 0 }, exited: { maxHeight: 0 },
entering: { maxHeight: maxHeight }, entering: { maxHeight: maxHeight },
entered: { maxHeight: maxHeight, overflow: 'visible' }, entered: { maxHeight: 'unset', overflow: 'visible' },
exiting: { maxHeight: 0 }, exiting: { maxHeight: 0 },
}; };
......
...@@ -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 $ from 'jquery';
import _ from 'lodash'; import _ from 'lodash';
import coreModule from '../core_module'; import coreModule from '../core_module';
...@@ -5,18 +6,20 @@ import coreModule from '../core_module'; ...@@ -5,18 +6,20 @@ import coreModule from '../core_module';
function dashClass($timeout) { function dashClass($timeout) {
return { return {
link: ($scope, elem) => { link: ($scope, elem) => {
const body = $('body');
$scope.ctrl.dashboard.events.on('view-mode-changed', panel => { $scope.ctrl.dashboard.events.on('view-mode-changed', panel => {
console.log('view-mode-changed', panel.fullscreen); console.log('view-mode-changed', panel.fullscreen);
if (panel.fullscreen) { if (panel.fullscreen) {
elem.addClass('panel-in-fullscreen'); body.addClass('panel-in-fullscreen');
} else { } else {
$timeout(() => { $timeout(() => {
elem.removeClass('panel-in-fullscreen'); body.removeClass('panel-in-fullscreen');
}); });
} }
}); });
elem.toggleClass('panel-in-fullscreen', $scope.ctrl.dashboard.meta.fullscreen === true); body.toggleClass('panel-in-fullscreen', $scope.ctrl.dashboard.meta.fullscreen === true);
$scope.$watch('ctrl.dashboardViewState.state.editview', newValue => { $scope.$watch('ctrl.dashboardViewState.state.editview', newValue => {
if (newValue) { if (newValue) {
......
...@@ -18,6 +18,7 @@ export const locationReducer = (state = initialState, action: Action): LocationS ...@@ -18,6 +18,7 @@ export const locationReducer = (state = initialState, action: Action): LocationS
if (action.payload.partial) { if (action.payload.partial) {
query = _.defaults(query, state.query); query = _.defaults(query, state.query);
query = _.omitBy(query, _.isNull);
} }
return { return {
......
...@@ -8,6 +8,7 @@ import classNames from 'classnames'; ...@@ -8,6 +8,7 @@ import classNames from 'classnames';
import sizeMe from 'react-sizeme'; import sizeMe from 'react-sizeme';
let lastGridWidth = 1200; let lastGridWidth = 1200;
let ignoreNextWidthChange = false;
function GridWrapper({ function GridWrapper({
size, size,
...@@ -24,8 +25,12 @@ function GridWrapper({ ...@@ -24,8 +25,12 @@ function GridWrapper({
isFullscreen, isFullscreen,
}) { }) {
const width = size.width > 0 ? size.width : lastGridWidth; const width = size.width > 0 ? size.width : lastGridWidth;
// logic to ignore width changes (optimization)
if (width !== lastGridWidth) { if (width !== lastGridWidth) {
if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) { if (ignoreNextWidthChange) {
ignoreNextWidthChange = false;
} else if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) {
onWidthChange(); onWidthChange();
lastGridWidth = width; lastGridWidth = width;
} }
...@@ -138,6 +143,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> { ...@@ -138,6 +143,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
} }
onViewModeChanged(payload) { onViewModeChanged(payload) {
ignoreNextWidthChange = true;
this.setState({ animated: !payload.fullscreen }); this.setState({ animated: !payload.fullscreen });
} }
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import config from 'app/core/config'; import config from 'app/core/config';
import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model';
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { DashboardRow } from './DashboardRow';
import { AddPanelPanel } from './AddPanelPanel';
import { importPluginModule } from 'app/features/plugins/plugin_loader'; import { importPluginModule } from 'app/features/plugins/plugin_loader';
import { PluginExports, PanelPlugin } from 'app/types/plugins';
import { AddPanelPanel } from './AddPanelPanel';
import { DashboardRow } from './DashboardRow';
import { PanelPlugin } from 'app/types/plugins';
import { PanelChrome } from './PanelChrome'; import { PanelChrome } from './PanelChrome';
import { PanelEditor } from './PanelEditor'; import { PanelEditor } from './PanelEditor';
import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model';
export interface Props { export interface Props {
panel: PanelModel; panel: PanelModel;
dashboard: DashboardModel; dashboard: DashboardModel;
...@@ -18,20 +21,19 @@ export interface Props { ...@@ -18,20 +21,19 @@ export interface Props {
} }
export interface State { export interface State {
pluginExports: PluginExports; plugin: PanelPlugin;
} }
export class DashboardPanel extends PureComponent<Props, State> { export class DashboardPanel extends PureComponent<Props, State> {
element: any; element: any;
angularPanel: AngularComponent; angularPanel: AngularComponent;
pluginInfo: any;
specialPanels = {}; specialPanels = {};
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
pluginExports: null, plugin: null,
}; };
this.specialPanels['row'] = this.renderRow.bind(this); this.specialPanels['row'] = this.renderRow.bind(this);
...@@ -64,20 +66,22 @@ export class DashboardPanel extends PureComponent<Props, State> { ...@@ -64,20 +66,22 @@ export class DashboardPanel extends PureComponent<Props, State> {
return; return;
} }
const { panel } = this.props;
// handle plugin loading & changing of plugin type // handle plugin loading & changing of plugin type
if (!this.pluginInfo || this.pluginInfo.id !== this.props.panel.type) { if (!this.state.plugin || this.state.plugin.id !== panel.type) {
this.pluginInfo = config.panels[this.props.panel.type]; const plugin = config.panels[panel.type];
if (this.pluginInfo.exports) { if (plugin.exports) {
this.cleanUpAngularPanel(); this.cleanUpAngularPanel();
this.setState({ pluginExports: this.pluginInfo.exports }); this.setState({ plugin: plugin });
} else { } else {
importPluginModule(this.pluginInfo.module).then(pluginExports => { importPluginModule(plugin.module).then(pluginExports => {
this.cleanUpAngularPanel(); this.cleanUpAngularPanel();
// cache plugin exports (saves a promise async cycle next time) // cache plugin exports (saves a promise async cycle next time)
this.pluginInfo.exports = pluginExports; plugin.exports = pluginExports;
// update panel state // update panel state
this.setState({ pluginExports: pluginExports }); this.setState({ plugin: plugin });
}); });
} }
} }
...@@ -113,7 +117,9 @@ export class DashboardPanel extends PureComponent<Props, State> { ...@@ -113,7 +117,9 @@ export class DashboardPanel extends PureComponent<Props, State> {
} }
renderReactPanel() { renderReactPanel() {
const { pluginExports } = this.state; const { dashboard, panel } = this.props;
const { plugin } = this.state;
const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper'; const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper';
const panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper'; const panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper';
// this might look strange with these classes that change when edit, but // this might look strange with these classes that change when edit, but
...@@ -121,37 +127,30 @@ export class DashboardPanel extends PureComponent<Props, State> { ...@@ -121,37 +127,30 @@ export class DashboardPanel extends PureComponent<Props, State> {
return ( return (
<div className={containerClass}> <div className={containerClass}>
<div className={panelWrapperClass}> <div className={panelWrapperClass}>
<PanelChrome <PanelChrome component={plugin.exports.PanelComponent} panel={panel} dashboard={dashboard} />
component={pluginExports.PanelComponent}
panel={this.props.panel}
dashboard={this.props.dashboard}
/>
</div>
{this.props.panel.isEditing && (
<div className="panel-editor-container__editor">
<PanelEditor
panel={this.props.panel}
panelType={this.props.panel.type}
dashboard={this.props.dashboard}
onTypeChanged={this.onPluginTypeChanged}
pluginExports={pluginExports}
/>
</div> </div>
{panel.isEditing && (
<PanelEditor panel={panel} plugin={plugin} dashboard={dashboard} onTypeChanged={this.onPluginTypeChanged} />
)} )}
</div> </div>
); );
} }
render() { render() {
const { panel } = this.props;
const { plugin } = this.state;
if (this.isSpecial()) { if (this.isSpecial()) {
return this.specialPanels[this.props.panel.type](); return this.specialPanels[panel.type]();
} }
if (!this.state.pluginExports) { // if we have not loaded plugin exports yet, wait
if (!plugin || !plugin.exports) {
return null; return null;
} }
if (this.state.pluginExports.PanelComponent) { // if exporting PanelComponent it must be a react panel
if (plugin.exports.PanelComponent) {
return this.renderReactPanel(); return this.renderReactPanel();
} }
......
import React, { PureComponent } from 'react';
import classNames from 'classnames';
import _ from 'lodash';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { DataSourceSelectItem } from 'app/types';
interface Props {}
interface State {
datasources: DataSourceSelectItem[];
searchQuery: string;
}
export class DataSourcePicker extends PureComponent<Props, State> {
searchInput: HTMLElement;
constructor(props) {
super(props);
this.state = {
datasources: getDatasourceSrv().getMetricSources(),
searchQuery: '',
};
}
getDataSources() {
const { datasources, searchQuery } = this.state;
const regex = new RegExp(searchQuery, 'i');
const filtered = datasources.filter(item => {
return regex.test(item.name) || regex.test(item.meta.name);
});
return _.sortBy(filtered, 'sort');
}
renderDataSource = (ds: DataSourceSelectItem, index) => {
const cssClass = classNames({
'ds-picker-list__item': true,
});
return (
<div key={index} className={cssClass} title={ds.name}>
<img className="ds-picker-list__img" src={ds.meta.info.logos.small} />
<div className="ds-picker-list__name">{ds.name}</div>
</div>
);
};
componentDidMount() {
setTimeout(() => {
this.searchInput.focus();
}, 300);
}
renderFilters() {
return (
<>
<label className="gf-form--has-input-icon">
<input
type="text"
className="gf-form-input width-13"
placeholder=""
ref={elem => (this.searchInput = elem)}
/>
<i className="gf-form-input-icon fa fa-search" />
</label>
<div className="p-l-1">
<button className="btn toggle-btn gf-form-btn active">All</button>
<button className="btn toggle-btn gf-form-btn">Favorites</button>
</div>
</>
);
}
render() {
return (
<>
<div className="cta-form__bar">
{this.renderFilters()}
<div className="gf-form--grow" />
</div>
<div className="ds-picker-list">{this.getDataSources().map(this.renderDataSource)}</div>
</>
);
}
}
import React, { PureComponent } from 'react';
import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
import { FadeIn } from 'app/core/components/Animations/FadeIn';
interface Props {
children: JSX.Element;
main: EditorToolBarView;
toolbarItems: EditorToolBarView[];
}
export interface EditorToolBarView {
title: string;
imgSrc?: string;
icon?: 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 });
};
renderMainSelection(view: EditorToolBarView) {
return (
<div className="toolbar__main" onClick={() => this.onToggleToolBarView(view)} key={view.title}>
<img className="toolbar__main-image" src={view.imgSrc} />
<div className="toolbar__main-name">{view.title}</div>
<i className="fa fa-caret-down" />
</div>
);
}
renderButton(view: EditorToolBarView) {
return (
<div className="nav-buttons" key={view.title}>
<button className="btn navbar-button" onClick={() => this.onToggleToolBarView(view)}>
{view.icon && <i className={view.icon} />} {view.title}
</button>
</div>
);
}
renderOpenView(view: EditorToolBarView) {
return (
<div className="toolbar-subview">
<button className="toolbar-subview__close" onClick={this.onCloseOpenView}>
<i className="fa fa-chevron-up" />
</button>
{view.render()}
</div>
);
}
render() {
const { children, toolbarItems, main } = this.props;
const { openView } = this.state;
return (
<>
<div className="toolbar">
{this.renderMainSelection(main)}
<div className="gf-form--grow" />
{toolbarItems.map(item => this.renderButton(item))}
</div>
<div className="panel-editor__scroll">
<CustomScrollbar>
<div className="panel-editor__content">
<FadeIn in={openView !== null} duration={200}>
{openView && this.renderOpenView(openView)}
</FadeIn>
{children}
</div>
</CustomScrollbar>
</div>
</>
);
}
}
...@@ -2,20 +2,19 @@ import React, { PureComponent } from 'react'; ...@@ -2,20 +2,19 @@ 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 { store } from 'app/store/store'; import { store } from 'app/store/store';
import { updateLocation } from 'app/core/actions'; import { updateLocation } from 'app/core/actions';
import { PanelModel } from '../panel_model'; import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model'; import { DashboardModel } from '../dashboard_model';
import { PanelPlugin, PluginExports } from 'app/types/plugins'; import { PanelPlugin } from 'app/types/plugins';
interface PanelEditorProps { interface PanelEditorProps {
panel: PanelModel; panel: PanelModel;
dashboard: DashboardModel; dashboard: DashboardModel;
panelType: string; plugin: PanelPlugin;
pluginExports: PluginExports;
onTypeChanged: (newType: PanelPlugin) => void; onTypeChanged: (newType: PanelPlugin) => void;
} }
...@@ -34,43 +33,10 @@ export class PanelEditor extends PureComponent<PanelEditorProps> { ...@@ -34,43 +33,10 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
this.tabs = [ this.tabs = [
{ id: 'queries', text: 'Queries', icon: 'fa fa-database' }, { id: 'queries', text: 'Queries', icon: 'fa fa-database' },
{ id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' }, { id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' },
{ id: 'alert', text: 'Alert', icon: 'gicon gicon-alert' },
]; ];
} }
renderQueriesTab() {
return <QueriesTab panel={this.props.panel} dashboard={this.props.dashboard} />;
}
renderPanelOptions() {
const { pluginExports, panel } = this.props;
if (pluginExports.PanelOptionsComponent) {
const OptionsComponent = pluginExports.PanelOptionsComponent;
return <OptionsComponent 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">
<div className="viz-editor-col1">
<VizTypePicker currentType={this.props.panel.type} onTypeChanged={this.props.onTypeChanged} />
</div>
<div className="viz-editor-col2">
<h5 className="page-heading">Options</h5>
{this.renderPanelOptions()}
</div>
</div>
);
}
onChangeTab = (tab: PanelEditorTab) => { onChangeTab = (tab: PanelEditorTab) => {
store.dispatch( store.dispatch(
updateLocation({ updateLocation({
...@@ -81,28 +47,44 @@ export class PanelEditor extends PureComponent<PanelEditorProps> { ...@@ -81,28 +47,44 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
this.forceUpdate(); this.forceUpdate();
}; };
onClose = () => {
store.dispatch(
updateLocation({
query: { tab: null, fullscreen: null, edit: null },
partial: true,
})
);
};
render() { render() {
const { panel, dashboard, onTypeChanged, plugin } = this.props;
const { location } = store.getState(); const { location } = store.getState();
const activeTab = location.query.tab || 'queries'; const activeTab = location.query.tab || 'queries';
return ( return (
<div className="tabbed-view tabbed-view--new"> <div className="panel-editor-container__editor">
<div className="tabbed-view-header"> <div className="panel-editor-resizer">
<div className="panel-editor-resizer__handle">
<div className="panel-editor-resizer__handle-dots" />
</div>
</div>
<div className="panel-editor-tabs">
<ul className="gf-tabs"> <ul className="gf-tabs">
{this.tabs.map(tab => { {this.tabs.map(tab => {
return <TabItem 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" ng-click="ctrl.exitFullscreen();"> <button className="panel-editor-tabs__close" onClick={this.onClose}>
<i className="fa fa-remove" /> <i className="fa fa-reply" />
</button> </button>
</div> </div>
<div className="tabbed-view-body"> {activeTab === 'queries' && <QueriesTab panel={panel} dashboard={dashboard} />}
{activeTab === 'queries' && this.renderQueriesTab()} {activeTab === 'visualization' && (
{activeTab === 'visualization' && this.renderVizTab()} <VisualizationTab panel={panel} dashboard={dashboard} plugin={plugin} onTypeChanged={onTypeChanged} />
</div> )}
</div> </div>
); );
} }
...@@ -121,8 +103,8 @@ function TabItem({ tab, activeTab, onClick }: TabItemParams) { ...@@ -121,8 +103,8 @@ function TabItem({ tab, activeTab, onClick }: TabItemParams) {
}); });
return ( return (
<li className="gf-tabs-item" key={tab.id}> <li className="gf-tabs-item" onClick={() => onClick(tab)}>
<a className={tabClasses} onClick={() => onClick(tab)}> <a className={tabClasses}>
<i className={tab.icon} /> {tab.text} <i className={tab.icon} /> {tab.text}
</a> </a>
</li> </li>
......
// Libraries
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Services & utils
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { EditorTabBody } from './EditorTabBody';
import { DataSourcePicker } from './DataSourcePicker';
// Types
import { PanelModel } from '../panel_model'; import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model'; import { DashboardModel } from '../dashboard_model';
...@@ -48,6 +47,27 @@ export class QueriesTab extends PureComponent<Props> { ...@@ -48,6 +47,27 @@ 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: () => <DataSourcePicker />,
};
const queryInspector = {
title: 'Query Inspector',
render: () => <h2>hello</h2>,
};
const dsHelp = {
title: '',
icon: 'fa fa-question',
render: () => <h2>hello</h2>,
};
return (
<EditorTabBody main={currentDataSource} toolbarItems={[queryInspector, dsHelp]}>
<div ref={element => (this.element = element)} style={{ width: '100%' }} />
</EditorTabBody>
);
} }
} }
import React, { PureComponent } from 'react';
import { EditorTabBody } from './EditorTabBody';
import { VizTypePicker } from './VizTypePicker';
import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model';
import { PanelPlugin } from 'app/types/plugins';
interface Props {
panel: PanelModel;
dashboard: DashboardModel;
plugin: PanelPlugin;
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 } = this.props;
const panelSelection = {
title: plugin.name,
imgSrc: plugin.info.logos.small,
render: () => {
// the needs to be scoped inside this closure
const { plugin, onTypeChanged } = this.props;
return <VizTypePicker current={plugin} onTypeChanged={onTypeChanged} />;
},
};
return (
<EditorTabBody main={panelSelection} toolbarItems={[]}>
{this.renderPanelOptions()}
</EditorTabBody>
);
}
}
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import _ from 'lodash';
import config from 'app/core/config'; import config from 'app/core/config';
import { PanelPlugin } from 'app/types/plugins'; import { PanelPlugin } from 'app/types/plugins';
import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
import _ from 'lodash';
interface Props { interface Props {
currentType: string; current: PanelPlugin;
onTypeChanged: (newType: PanelPlugin) => void; onTypeChanged: (newType: PanelPlugin) => void;
} }
...@@ -15,6 +15,8 @@ interface State { ...@@ -15,6 +15,8 @@ interface State {
} }
export class VizTypePicker extends PureComponent<Props, State> { export class VizTypePicker extends PureComponent<Props, State> {
searchInput: HTMLElement;
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -36,34 +38,55 @@ export class VizTypePicker extends PureComponent<Props, State> { ...@@ -36,34 +38,55 @@ export class VizTypePicker extends PureComponent<Props, State> {
renderVizPlugin = (plugin, index) => { renderVizPlugin = (plugin, index) => {
const cssClass = classNames({ const cssClass = classNames({
'viz-picker__item': true, 'viz-picker__item': true,
'viz-picker__item--selected': plugin.id === this.props.currentType, 'viz-picker__item--selected': plugin.id === this.props.current.id,
}); });
return ( return (
<div key={index} className={cssClass} onClick={() => this.props.onTypeChanged(plugin)} title={plugin.name}> <div key={index} className={cssClass} onClick={() => this.props.onTypeChanged(plugin)} title={plugin.name}>
<img className="viz-picker__item-img" src={plugin.info.logos.small} />
<div className="viz-picker__item-name">{plugin.name}</div> <div className="viz-picker__item-name">{plugin.name}</div>
<img className="viz-picker__item-img" src={plugin.info.logos.small} />
</div> </div>
); );
}; };
render() { componentDidMount() {
setTimeout(() => {
this.searchInput.focus();
}, 300);
}
renderFilters() {
return ( return (
<div className="viz-picker"> <>
<div className="viz-picker__search"> <label className="gf-form--has-input-icon">
<div className="gf-form gf-form--grow"> <input
<label className="gf-form--has-input-icon gf-form--grow"> type="text"
<input type="text" className="gf-form-input" placeholder="Search type" /> className="gf-form-input width-13"
placeholder=""
ref={elem => (this.searchInput = elem)}
/>
<i className="gf-form-input-icon fa fa-search" /> <i className="gf-form-input-icon fa fa-search" />
</label> </label>
<div className="p-l-1">
<button className="btn toggle-btn gf-form-btn active">Basic Types</button>
<button className="btn toggle-btn gf-form-btn">Master Types</button>
</div> </div>
</>
);
}
render() {
const { pluginList } = this.state;
return (
<>
<div className="cta-form__bar">
{this.renderFilters()}
<div className="gf-form--grow" />
</div> </div>
<div className="viz-picker__items">
<CustomScrollbar> <div className="viz-picker">{pluginList.map(this.renderVizPlugin)}</div>
<div className="scroll-margin-helper">{this.state.pluginList.map(this.renderVizPlugin)}</div> </>
</CustomScrollbar>
</div>
</div>
); );
} }
} }
...@@ -44,8 +44,8 @@ const panelTemplate = ` ...@@ -44,8 +44,8 @@ const panelTemplate = `
</li> </li>
</ul> </ul>
<button class="tabbed-view-close-btn" ng-click="ctrl.exitFullscreen();"> <button class="panel-editor-tabs__close" ng-click="ctrl.exitFullscreen();">
<i class="fa fa-remove"></i> <i class="fa fa-reply"></i>
</button> </button>
</div> </div>
......
// Libraries
import _ from 'lodash'; import _ from 'lodash';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
// Utils
import config from 'app/core/config'; import config from 'app/core/config';
import { importPluginModule } from './plugin_loader'; import { importPluginModule } from './plugin_loader';
// Types
import { DataSourceApi } from 'app/types/series'; import { DataSourceApi } from 'app/types/series';
import { DataSource } from 'app/types'; import { DataSource, DataSourceSelectItem } from 'app/types';
export class DatasourceSrv { export class DatasourceSrv {
datasources: { [name: string]: DataSource }; datasources: { [name: string]: DataSource };
...@@ -102,8 +99,8 @@ export class DatasourceSrv { ...@@ -102,8 +99,8 @@ export class DatasourceSrv {
return _.sortBy(es, ['name']); return _.sortBy(es, ['name']);
} }
getMetricSources(options) { getMetricSources(options?) {
const metricSources = []; const metricSources: DataSourceSelectItem[] = [];
_.each(config.datasources, (value, key) => { _.each(config.datasources, (value, key) => {
if (value.meta && value.meta.metrics) { if (value.meta && value.meta.metrics) {
......
...@@ -61,12 +61,27 @@ export class GraphOptions extends PureComponent<PanelOptionsProps<Options>> { ...@@ -61,12 +61,27 @@ export class GraphOptions extends PureComponent<PanelOptionsProps<Options>> {
return ( return (
<div> <div>
<div className="form-option-box">
<div className="form-option-box__header">Display Options</div>
<div className="section gf-form-group"> <div className="section gf-form-group">
<h5 className="page-heading">Draw Modes</h5> <h5 className="section-heading">Draw Modes</h5>
<Switch label="Lines" labelClass="width-5" checked={showLines} onChange={this.onToggleLines} /> <Switch label="Lines" labelClass="width-5" checked={showLines} onChange={this.onToggleLines} />
<Switch label="Bars" labelClass="width-5" checked={showBars} onChange={this.onToggleBars} /> <Switch label="Bars" labelClass="width-5" checked={showBars} onChange={this.onToggleBars} />
<Switch label="Points" labelClass="width-5" checked={showPoints} onChange={this.onTogglePoints} /> <Switch label="Points" labelClass="width-5" checked={showPoints} onChange={this.onTogglePoints} />
</div> </div>
<div className="section gf-form-group">
<h5 className="section-heading">Test Options</h5>
<Switch label="Lines" labelClass="width-5" checked={showLines} onChange={this.onToggleLines} />
<Switch label="Bars" labelClass="width-5" checked={showBars} onChange={this.onToggleBars} />
<Switch label="Points" labelClass="width-5" checked={showPoints} onChange={this.onTogglePoints} />
</div>
</div>
<div className="form-option-box">
<div className="form-option-box__header">Axes</div>
</div>
<div className="form-option-box">
<div className="form-option-box__header">Thresholds</div>
</div>
</div> </div>
); );
} }
......
...@@ -25,6 +25,13 @@ export interface DataSource { ...@@ -25,6 +25,13 @@ export interface DataSource {
testDatasource?: () => Promise<any>; testDatasource?: () => Promise<any>;
} }
export interface DataSourceSelectItem {
name: string;
value: string | null;
meta: PluginMeta;
sort: string;
}
export interface DataSourcesState { export interface DataSourcesState {
dataSources: DataSource[]; dataSources: DataSource[];
searchQuery: string; searchQuery: string;
......
...@@ -7,7 +7,7 @@ import { DashboardState } from './dashboard'; ...@@ -7,7 +7,7 @@ import { DashboardState } from './dashboard';
import { DashboardAcl, OrgRole, PermissionLevel } from './acl'; import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys'; import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys';
import { Invitee, OrgUser, User, UsersState, UserState } from './user'; import { Invitee, OrgUser, User, UsersState, UserState } from './user';
import { DataSource, DataSourcesState } from './datasources'; import { DataSource, DataSourceSelectItem, DataSourcesState } from './datasources';
import { import {
TimeRange, TimeRange,
LoadingState, LoadingState,
...@@ -55,6 +55,7 @@ export { ...@@ -55,6 +55,7 @@ export {
OrgRole, OrgRole,
PermissionLevel, PermissionLevel,
DataSource, DataSource,
DataSourceSelectItem,
PluginMeta, PluginMeta,
ApiKey, ApiKey,
ApiKeysState, ApiKeysState,
......
...@@ -97,7 +97,8 @@ ...@@ -97,7 +97,8 @@
@import 'components/form_select_box'; @import 'components/form_select_box';
@import 'components/user-picker'; @import 'components/user-picker';
@import 'components/description-picker'; @import 'components/description-picker';
@import 'components/viz_editor'; @import 'components/panel_editor';
@import 'components/toolbar';
@import 'components/delete_button'; @import 'components/delete_button';
@import 'components/add_data_source.scss'; @import 'components/add_data_source.scss';
@import 'components/page_loader'; @import 'components/page_loader';
......
...@@ -77,6 +77,7 @@ $brand-gradient: linear-gradient( ...@@ -77,6 +77,7 @@ $brand-gradient: linear-gradient(
rgba(255, 68, 0, 0.7) 99%, rgba(255, 68, 0, 0.7) 99%,
rgba(255, 68, 0, 0.7) 100% rgba(255, 68, 0, 0.7) 100%
); );
$page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px); $page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px);
// Links // Links
...@@ -110,7 +111,6 @@ $divider-border-color: #555; ...@@ -110,7 +111,6 @@ $divider-border-color: #555;
// Graphite Target Editor // Graphite Target Editor
$tight-form-bg: $dark-3; $tight-form-bg: $dark-3;
$tight-form-func-bg: #333334; $tight-form-func-bg: #333334;
$tight-form-func-highlight-bg: #444445; $tight-form-func-highlight-bg: #444445;
...@@ -128,6 +128,7 @@ $list-item-bg: $card-background; ...@@ -128,6 +128,7 @@ $list-item-bg: $card-background;
$list-item-hover-bg: lighten($gray-blue, 2%); $list-item-hover-bg: lighten($gray-blue, 2%);
$list-item-link-color: $text-color; $list-item-link-color: $text-color;
$list-item-shadow: $card-shadow; $list-item-shadow: $card-shadow;
$empty-list-cta-bg: $gray-blue; $empty-list-cta-bg: $gray-blue;
// Scrollbars // Scrollbars
...@@ -152,8 +153,8 @@ $table-bg-hover: $dark-3; ...@@ -152,8 +153,8 @@ $table-bg-hover: $dark-3;
$btn-primary-bg: #ff6600; $btn-primary-bg: #ff6600;
$btn-primary-bg-hl: #bc3e06; $btn-primary-bg-hl: #bc3e06;
$btn-secondary-bg: $blue-dark;
$btn-secondary-bg-hl: lighten($blue-dark, 5%); $btn-secondary-bg-hl: lighten($blue-dark, 5%);
$btn-secondary-bg: $blue-dark;
$btn-success-bg: $green; $btn-success-bg: $green;
$btn-success-bg-hl: darken($green, 6%); $btn-success-bg-hl: darken($green, 6%);
...@@ -266,6 +267,11 @@ $menu-dropdown-shadow: 5px 5px 20px -5px $black; ...@@ -266,6 +267,11 @@ $menu-dropdown-shadow: 5px 5px 20px -5px $black;
// ------------------------- // -------------------------
$tab-border-color: $dark-4; $tab-border-color: $dark-4;
// Toolbar
$toolbar-bg: $page-header-bg;
$toolbar-shadow: 0 0 20px black;
$toolbar-tab-bg: $gray-blue;
// Pagination // Pagination
// ------------------------- // -------------------------
......
...@@ -31,6 +31,7 @@ $white: #fff; ...@@ -31,6 +31,7 @@ $white: #fff;
// Accent colors // Accent colors
// ------------------------- // -------------------------
$blue: #0083b3; $blue: #0083b3;
$blue-dark: #005f81;
$blue-light: #00a8e6; $blue-light: #00a8e6;
$green: #3aa655; $green: #3aa655;
$red: #d44939; $red: #d44939;
...@@ -213,6 +214,11 @@ $menu-dropdown-shadow: 5px 5px 10px -5px $gray-1; ...@@ -213,6 +214,11 @@ $menu-dropdown-shadow: 5px 5px 10px -5px $gray-1;
// ------------------------- // -------------------------
$tab-border-color: $gray-5; $tab-border-color: $gray-5;
// Toolbar
$toolbar-bg: linear-gradient(90deg, #ffffff, #e6eef9);
$toolbar-shadow: 1px 1px 3px #c7d0d8;
$toolbar-tab-bg: $white;
// search // search
$search-shadow: 0 5px 30px 0 $gray-4; $search-shadow: 0 5px 30px 0 $gray-4;
$search-filter-box-bg: $gray-7; $search-filter-box-bg: $gray-7;
......
...@@ -120,8 +120,8 @@ ...@@ -120,8 +120,8 @@
// Info appears as a neutral blue // Info appears as a neutral blue
.btn-secondary { .btn-secondary {
@include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl); @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl);
// Inverse appears as dark gray
} }
// Inverse appears as dark gray
.btn-inverse { .btn-inverse {
@include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl, $btn-inverse-text-color, $btn-inverse-text-shadow); @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl, $btn-inverse-text-color, $btn-inverse-text-shadow);
//background: $card-background; //background: $card-background;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.panel-in-fullscreen { .panel-in-fullscreen {
.react-grid-layout { .react-grid-layout {
height: 100% !important; height: calc(100% - 20px) !important;
} }
.react-grid-item { .react-grid-item {
...@@ -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;
......
...@@ -415,7 +415,27 @@ select.gf-form-input ~ .gf-form-help-icon { ...@@ -415,7 +415,27 @@ select.gf-form-input ~ .gf-form-help-icon {
} }
.cta-form__close { .cta-form__close {
background: transparent;
padding: 4px 8px 4px 9px;
border: none;
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: -2px;
font-size: $font-size-md;
&:hover {
color: $text-color-strong;
}
}
.cta-form__bar {
display: flex;
align-items: center;
align-content: center;
margin-bottom: 20px;
}
.cta-form__bar-header {
font-size: $font-size-h4;
padding-right: 20px;
} }
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
.panel-in-fullscreen { .panel-in-fullscreen {
.navbar { .navbar {
@include navbar-alt-look(); padding-left: 15px;
} }
.navbar-button--add-panel, .navbar-button--add-panel,
...@@ -50,10 +50,6 @@ ...@@ -50,10 +50,6 @@
.navbar-page-btn .fa-caret-down { .navbar-page-btn .fa-caret-down {
display: none; display: none;
} }
.navbar-buttons--close {
display: flex;
}
} }
.navbar-page-btn { .navbar-page-btn {
...@@ -98,7 +94,7 @@ ...@@ -98,7 +94,7 @@
} }
.navbar-buttons { .navbar-buttons {
height: $navbarHeight; // height: $navbarHeight;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
......
.panel-editor-container {
display: flex;
flex-direction: column;
height: 100%;
}
.panel-editor-container__panel {
flex: 1 1 0;
}
.panel-editor-container__editor {
margin-top: $panel-margin*2;
display: flex;
flex-direction: column;
height: 65%;
position: relative;
}
.panel-editor__scroll {
flex-grow: 1;
min-width: 0;
display: flex;
padding: 0 5px;
}
.panel-editor__content {
padding: 40px 15px;
}
.panel-in-fullscreen {
.sidemenu {
display: none;
}
.dashboard-container {
padding: 0;
}
.submenu-controls {
padding: 0 $dashboard-padding $panel-margin $dashboard-padding;
}
.panel-editor-container__panel {
margin: 0 $dashboard-padding;
}
}
.panel-editor-resizer {
position: absolute;
height: 2px;
width: 100%;
top: -23px;
text-align: center;
border-bottom: 2px dashed transparent;
&:hover {
transition: border-color 0.2s ease-in 0.4s;
transition-delay: 0.2s;
border-color: $text-color-faint;
}
}
.panel-editor-resizer__handle {
display: inline-block;
width: 180px;
position: relative;
border-radius: 2px;
height: 10px;
cursor: grabbing;
background: $input-label-bg;
top: -8px;
&:hover {
transition: background 0.2s ease-in 0.4s;
transition-delay: 0.2s;
background: linear-gradient(90deg, $orange, $red);
.panel-editor-resizer__handle-dots {
transition: opacity 0.2s ease-in;
opacity: 0;
}
}
}
.panel-editor-resizer__handle-dots {
border-top: 2px dashed $text-color-faint;
position: relative;
top: 4px;
}
.viz-picker {
display: flex;
flex-wrap: wrap;
margin-bottom: 13px;
}
.viz-picker__item {
background: $card-background;
box-shadow: $card-shadow;
border-radius: 3px;
height: 90px;
width: 150px;
flex-shrink: 0;
flex-direction: column;
text-align: center;
cursor: pointer;
display: flex;
margin-right: 10px;
margin-bottom: 10px;
border: 1px solid transparent;
align-items: center;
&:hover {
background: $card-background-hover;
}
&--selected {
box-shadow: 0 0 12px #ff4d00;
}
}
.viz-picker__item-name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-size: $font-size-sm;
display: flex;
flex-direction: column;
align-self: center;
height: 23px;
}
.viz-picker__item-img {
height: 55px;
}
.panel-editor-tabs {
position: relative;
z-index: 2;
box-shadow: $page-header-shadow;
border-bottom: 1px solid $page-header-border-color;
padding: 0 $dashboard-padding;
@include clearfix();
.active.gf-tabs-link {
background: $toolbar-tab-bg;
}
}
.panel-editor-tabs__close {
padding: 5px 9px;
border-radius: $border-radius;
float: right;
@include buttonBackground($btn-primary-bg, $btn-primary-bg-hl);
}
.ds-picker-list {
display: flex;
flex-wrap: wrap;
margin-bottom: 13px;
flex-direction: column;
}
.ds-picker-list__item {
background: $card-background;
box-shadow: $card-shadow;
border-radius: 3px;
display: flex;
cursor: pointer;
margin-bottom: 3px;
padding: 5px 15px;
align-items: center;
&:hover {
background: $card-background-hover;
}
&--selected {
.ds-picker-list__name {
color: $text-color;
}
}
}
.ds-picker-list__name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-size: $font-size-md;
padding-left: 15px;
}
.ds-picker-list__img {
width: 30px;
}
.form-option-box {
margin-bottom: 20px;
}
.form-option-box__header {
border-bottom: 2px solid $dark-4;
padding: 5px 0px;
font-size: $font-size-md;
margin-bottom: 20px;
}
...@@ -106,78 +106,78 @@ ...@@ -106,78 +106,78 @@
opacity: 0.9; opacity: 0.9;
} }
// Scrollbars // // Scrollbars
// //
//
// ::-webkit-scrollbar {
// width: 8px;
// height: 8px;
// }
//
// ::-webkit-scrollbar:hover {
// height: 8px;
// }
//
// ::-webkit-scrollbar-button:start:decrement,
// ::-webkit-scrollbar-button:end:increment {
// display: none;
// }
// ::-webkit-scrollbar-button:horizontal:decrement {
// display: none;
// }
// ::-webkit-scrollbar-button:horizontal:increment {
// display: none;
// }
// ::-webkit-scrollbar-button:vertical:decrement {
// display: none;
// }
// ::-webkit-scrollbar-button:vertical:increment {
// display: none;
// }
// ::-webkit-scrollbar-button:horizontal:decrement:active {
// background-image: none;
// }
// ::-webkit-scrollbar-button:horizontal:increment:active {
// background-image: none;
// }
// ::-webkit-scrollbar-button:vertical:decrement:active {
// background-image: none;
// }
// ::-webkit-scrollbar-button:vertical:increment:active {
// background-image: none;
// }
// ::-webkit-scrollbar-track-piece {
// background-color: transparent;
// }
//
// ::-webkit-scrollbar-thumb:vertical {
// height: 50px;
// background: -webkit-gradient(
// linear,
// left top,
// right top,
// color-stop(0%, $scrollbarBackground),
// color-stop(100%, $scrollbarBackground2)
// );
// border: 1px solid $scrollbarBorder;
// border-top: 1px solid $scrollbarBorder;
// border-left: 1px solid $scrollbarBorder;
// }
//
// ::-webkit-scrollbar-thumb:horizontal {
// width: 50px;
// background: -webkit-gradient(
// linear,
// left top,
// left bottom,
// color-stop(0%, $scrollbarBackground),
// color-stop(100%, $scrollbarBackground2)
// );
// border: 1px solid $scrollbarBorder;
// border-top: 1px solid $scrollbarBorder;
// border-left: 1px solid $scrollbarBorder;
// }
// //
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar:hover {
height: 8px;
}
::-webkit-scrollbar-button:start:decrement,
::-webkit-scrollbar-button:end:increment {
display: none;
}
::-webkit-scrollbar-button:horizontal:decrement {
display: none;
}
::-webkit-scrollbar-button:horizontal:increment {
display: none;
}
::-webkit-scrollbar-button:vertical:decrement {
display: none;
}
::-webkit-scrollbar-button:vertical:increment {
display: none;
}
::-webkit-scrollbar-button:horizontal:decrement:active {
background-image: none;
}
::-webkit-scrollbar-button:horizontal:increment:active {
background-image: none;
}
::-webkit-scrollbar-button:vertical:decrement:active {
background-image: none;
}
::-webkit-scrollbar-button:vertical:increment:active {
background-image: none;
}
::-webkit-scrollbar-track-piece {
background-color: transparent;
}
::-webkit-scrollbar-thumb:vertical {
height: 50px;
background: -webkit-gradient(
linear,
left top,
right top,
color-stop(0%, $scrollbarBackground),
color-stop(100%, $scrollbarBackground2)
);
border: 1px solid $scrollbarBorder;
border-top: 1px solid $scrollbarBorder;
border-left: 1px solid $scrollbarBorder;
}
::-webkit-scrollbar-thumb:horizontal {
width: 50px;
background: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0%, $scrollbarBackground),
color-stop(100%, $scrollbarBackground2)
);
border: 1px solid $scrollbarBorder;
border-top: 1px solid $scrollbarBorder;
border-left: 1px solid $scrollbarBorder;
}
// Baron styles // Baron styles
.baron { .baron {
......
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
flex-wrap: wrap; flex-wrap: wrap;
align-content: flex-start; align-content: flex-start;
align-items: flex-start; align-items: flex-start;
padding: 0 0 $panel-margin 0;
margin: 0 0 $panel-margin 0;
} }
.annotation-disabled, .annotation-disabled,
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
flex-grow: 1;
&.tabbed-view--new { &.tabbed-view--new {
padding: 25px 0 0 0; padding: 0 0 0 0;
height: 100%; height: 100%;
} }
} }
...@@ -12,13 +13,14 @@ ...@@ -12,13 +13,14 @@
.tabbed-view-header { .tabbed-view-header {
box-shadow: $page-header-shadow; box-shadow: $page-header-shadow;
border-bottom: 1px solid $page-header-border-color; border-bottom: 1px solid $page-header-border-color;
padding: 0 $dashboard-padding;
@include clearfix(); @include clearfix();
} }
.tabbed-view-title { .tabbed-view-title {
float: left; float: left;
padding-top: 0.5rem; padding-top: 0.5rem;
margin: 0 $spacer*3 0 $spacer*1; margin: 0 $spacer*3 0 0;
} }
.tabbed-view-panel-title { .tabbed-view-panel-title {
......
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
background-image: linear-gradient(to right, #ffd500 0%, #ff4400 99%, #ff4400 100%); background-image: linear-gradient(to right, #ffd500 0%, #ff4400 99%, #ff4400 100%);
} }
} }
&.active--panel { &.active--panel {
background: $panel-bg !important; background: $panel-bg !important;
} }
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
background-color: $page-bg; background-color: $page-bg;
border-radius: 0 0 0 4px; border-radius: 0 0 0 4px;
box-shadow: $search-shadow; box-shadow: $search-shadow;
z-index: $zindex-dropdown;
} }
.gf-timepicker-absolute-section { .gf-timepicker-absolute-section {
......
.toolbar {
display: flex;
align-content: center;
align-items: center;
background: $toolbar-bg;
box-shadow: $toolbar-shadow;
padding: 7px 20px 7px 20px;
position: relative;
z-index: 1;
flex: 0 0 auto;
}
.toolbar__main {
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;
cursor: pointer;
.fa {
margin-left: 20px;
display: inline-block;
position: relative;
}
}
.toolbar__main-image {
margin-right: 10px;
display: inline-block;
width: 20px;
height: 20px;
}
.toolbar-subview {
position: relative;
padding: 20px 20px;
background-color: $empty-list-cta-bg;
top: -45px;
margin: 0 30px 20px 0px;
}
.toolbar-subview__close {
background: transparent;
padding: 4px 8px 4px 9px;
border: none;
position: absolute;
right: 15px;
top: 20px;
font-size: $font-size-md;
&:hover {
color: $text-color-strong;
}
}
.viz-editor {
display: flex;
height: 100%;
}
.viz-editor-col1 {
width: 210px;
height: 100%;
margin-right: 40px;
}
.viz-editor-col2 {
flex-grow: 1;
}
.viz-picker {
display: flex;
flex-direction: column;
height: 100%;
}
.viz-picker__search {
flex-grow: 0;
}
.viz-picker__items {
flex-grow: 1;
height: calc(100% - 50px);
}
.viz-picker__item {
background: $card-background;
box-shadow: $card-shadow;
border-radius: 3px;
padding: $spacer;
width: 100%;
height: 60px;
text-align: center;
margin-bottom: 6px;
cursor: pointer;
display: flex;
flex-shrink: 0;
border: 1px solid transparent;
@include left-brand-border;
&:hover {
background: $card-background-hover;
}
&--selected {
// border: 1px solid $orange;
@include left-brand-border-gradient();
.viz-picker__item-name {
color: $text-color;
}
.viz-picker__item-img {
filter: saturate(100%);
}
}
}
.viz-picker__item-name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-size: $font-size-h5;
display: flex;
flex-direction: column;
align-self: center;
padding-left: $spacer;
font-size: $font-size-md;
color: $text-muted;
}
.viz-picker__item-img {
height: 100%;
filter: saturate(30%);
}
...@@ -37,20 +37,6 @@ div.flot-text { ...@@ -37,20 +37,6 @@ div.flot-text {
height: 100%; height: 100%;
} }
.panel-editor-container {
display: flex;
flex-direction: column;
height: 100%;
}
.panel-editor-container__panel {
height: 35%;
}
.panel-editor-container__editor {
height: 65%;
}
.panel-container { .panel-container {
background-color: $panel-bg; background-color: $panel-bg;
border: $panel-border; border: $panel-border;
......
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