diff --git a/public/app/core/components/Animations/FadeIn.tsx b/public/app/core/components/Animations/FadeIn.tsx new file mode 100644 index 000000000000..98eb3b682c01 --- /dev/null +++ b/public/app/core/components/Animations/FadeIn.tsx @@ -0,0 +1,37 @@ +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 => { + 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 ( + + {state => ( +
+ {props.children} +
+ )} +
+ ); +}; diff --git a/public/app/core/components/Animations/SlideDown.tsx b/public/app/core/components/Animations/SlideDown.tsx index 70dacd73849e..6e8995298dc9 100644 --- a/public/app/core/components/Animations/SlideDown.tsx +++ b/public/app/core/components/Animations/SlideDown.tsx @@ -23,7 +23,7 @@ export default ({ children, in: inProp, maxHeight = defaultMaxHeight, style = de const transitionStyles = { exited: { maxHeight: 0 }, entering: { maxHeight: maxHeight }, - entered: { maxHeight: maxHeight, overflow: 'visible' }, + entered: { maxHeight: 'unset', overflow: 'visible' }, exiting: { maxHeight: 0 }, }; diff --git a/public/app/core/components/CustomScrollbar/CustomScrollbar.tsx b/public/app/core/components/CustomScrollbar/CustomScrollbar.tsx index 9b9a9c4d02a3..93add925095a 100644 --- a/public/app/core/components/CustomScrollbar/CustomScrollbar.tsx +++ b/public/app/core/components/CustomScrollbar/CustomScrollbar.tsx @@ -15,7 +15,7 @@ interface Props { class CustomScrollbar extends PureComponent { static defaultProps: Partial = { customClassName: 'custom-scrollbars', - autoHide: true, + autoHide: false, autoHideTimeout: 200, autoHideDuration: 200, hideTracksWhenNotNeeded: false, diff --git a/public/app/core/directives/dash_class.ts b/public/app/core/directives/dash_class.ts index 37124eb7d4bc..1fb93d29cf30 100644 --- a/public/app/core/directives/dash_class.ts +++ b/public/app/core/directives/dash_class.ts @@ -1,3 +1,4 @@ +import $ from 'jquery'; import _ from 'lodash'; import coreModule from '../core_module'; @@ -5,18 +6,20 @@ import coreModule from '../core_module'; function dashClass($timeout) { return { link: ($scope, elem) => { + const body = $('body'); + $scope.ctrl.dashboard.events.on('view-mode-changed', panel => { console.log('view-mode-changed', panel.fullscreen); if (panel.fullscreen) { - elem.addClass('panel-in-fullscreen'); + body.addClass('panel-in-fullscreen'); } else { $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 => { if (newValue) { diff --git a/public/app/core/reducers/location.ts b/public/app/core/reducers/location.ts index c9fe478dd9db..a42bd8137827 100644 --- a/public/app/core/reducers/location.ts +++ b/public/app/core/reducers/location.ts @@ -18,6 +18,7 @@ export const locationReducer = (state = initialState, action: Action): LocationS if (action.payload.partial) { query = _.defaults(query, state.query); + query = _.omitBy(query, _.isNull); } return { diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 12b4809f51ad..55249a4716f7 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -8,6 +8,7 @@ import classNames from 'classnames'; import sizeMe from 'react-sizeme'; let lastGridWidth = 1200; +let ignoreNextWidthChange = false; function GridWrapper({ size, @@ -24,8 +25,12 @@ function GridWrapper({ isFullscreen, }) { const width = size.width > 0 ? size.width : lastGridWidth; + + // logic to ignore width changes (optimization) if (width !== lastGridWidth) { - if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) { + if (ignoreNextWidthChange) { + ignoreNextWidthChange = false; + } else if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) { onWidthChange(); lastGridWidth = width; } @@ -138,6 +143,7 @@ export class DashboardGrid extends React.Component { } onViewModeChanged(payload) { + ignoreNextWidthChange = true; this.setState({ animated: !payload.fullscreen }); } diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 6853ade474bb..e423a96f28a1 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -1,15 +1,18 @@ import React, { PureComponent } from 'react'; 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 { DashboardRow } from './DashboardRow'; -import { AddPanelPanel } from './AddPanelPanel'; 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 { PanelEditor } from './PanelEditor'; +import { PanelModel } from '../panel_model'; +import { DashboardModel } from '../dashboard_model'; + export interface Props { panel: PanelModel; dashboard: DashboardModel; @@ -18,20 +21,19 @@ export interface Props { } export interface State { - pluginExports: PluginExports; + plugin: PanelPlugin; } export class DashboardPanel extends PureComponent { element: any; angularPanel: AngularComponent; - pluginInfo: any; specialPanels = {}; constructor(props) { super(props); this.state = { - pluginExports: null, + plugin: null, }; this.specialPanels['row'] = this.renderRow.bind(this); @@ -64,20 +66,22 @@ export class DashboardPanel extends PureComponent { return; } - // handle plugin loading & changing of plugin type - if (!this.pluginInfo || this.pluginInfo.id !== this.props.panel.type) { - this.pluginInfo = config.panels[this.props.panel.type]; + const { panel } = this.props; - if (this.pluginInfo.exports) { + // handle plugin loading & changing of plugin type + if (!this.state.plugin || this.state.plugin.id !== panel.type) { + const plugin = config.panels[panel.type]; + + if (plugin.exports) { this.cleanUpAngularPanel(); - this.setState({ pluginExports: this.pluginInfo.exports }); + this.setState({ plugin: plugin }); } else { - importPluginModule(this.pluginInfo.module).then(pluginExports => { + importPluginModule(plugin.module).then(pluginExports => { this.cleanUpAngularPanel(); // cache plugin exports (saves a promise async cycle next time) - this.pluginInfo.exports = pluginExports; + plugin.exports = pluginExports; // update panel state - this.setState({ pluginExports: pluginExports }); + this.setState({ plugin: plugin }); }); } } @@ -113,7 +117,9 @@ export class DashboardPanel extends PureComponent { } 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 panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper'; // this might look strange with these classes that change when edit, but @@ -121,37 +127,30 @@ export class DashboardPanel extends PureComponent { return (
- +
- {this.props.panel.isEditing && ( -
- -
+ {panel.isEditing && ( + )}
); } render() { + const { panel } = this.props; + const { plugin } = this.state; + 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; } - if (this.state.pluginExports.PanelComponent) { + // if exporting PanelComponent it must be a react panel + if (plugin.exports.PanelComponent) { return this.renderReactPanel(); } diff --git a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx new file mode 100644 index 000000000000..c40ca753a8eb --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx @@ -0,0 +1,88 @@ +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 { + 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 ( +
+ +
{ds.name}
+
+ ); + }; + + componentDidMount() { + setTimeout(() => { + this.searchInput.focus(); + }, 300); + } + + renderFilters() { + return ( + <> + +
+ + +
+ + ); + } + + render() { + return ( + <> +
+ {this.renderFilters()} +
+
+
{this.getDataSources().map(this.renderDataSource)}
+ + ); + } +} diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx new file mode 100644 index 000000000000..530603d1ad09 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -0,0 +1,96 @@ +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 { + 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 ( +
this.onToggleToolBarView(view)} key={view.title}> + +
{view.title}
+ +
+ ); + } + + renderButton(view: EditorToolBarView) { + return ( +
+ +
+ ); + } + + renderOpenView(view: EditorToolBarView) { + return ( +
+ + {view.render()} +
+ ); + } + + render() { + const { children, toolbarItems, main } = this.props; + const { openView } = this.state; + + return ( + <> +
+ {this.renderMainSelection(main)} +
+ {toolbarItems.map(item => this.renderButton(item))} +
+
+ +
+ + {openView && this.renderOpenView(openView)} + + {children} +
+
+
+ + ); + } +} diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 8f988ba2b863..09a8c7052e3d 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -2,20 +2,19 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import { QueriesTab } from './QueriesTab'; -import { VizTypePicker } from './VizTypePicker'; +import { VisualizationTab } from './VisualizationTab'; import { store } from 'app/store/store'; import { updateLocation } from 'app/core/actions'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; -import { PanelPlugin, PluginExports } from 'app/types/plugins'; +import { PanelPlugin } from 'app/types/plugins'; interface PanelEditorProps { panel: PanelModel; dashboard: DashboardModel; - panelType: string; - pluginExports: PluginExports; + plugin: PanelPlugin; onTypeChanged: (newType: PanelPlugin) => void; } @@ -34,43 +33,10 @@ export class PanelEditor extends PureComponent { this.tabs = [ { id: 'queries', text: 'Queries', icon: 'fa fa-database' }, { id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' }, + { id: 'alert', text: 'Alert', icon: 'gicon gicon-alert' }, ]; } - renderQueriesTab() { - return ; - } - - renderPanelOptions() { - const { pluginExports, panel } = this.props; - - if (pluginExports.PanelOptionsComponent) { - const OptionsComponent = pluginExports.PanelOptionsComponent; - return ; - } else { - return

Visualization has no options

; - } - } - - onPanelOptionsChanged = (options: any) => { - this.props.panel.updateOptions(options); - this.forceUpdate(); - }; - - renderVizTab() { - return ( -
-
- -
-
-
Options
- {this.renderPanelOptions()} -
-
- ); - } - onChangeTab = (tab: PanelEditorTab) => { store.dispatch( updateLocation({ @@ -81,28 +47,44 @@ export class PanelEditor extends PureComponent { this.forceUpdate(); }; + onClose = () => { + store.dispatch( + updateLocation({ + query: { tab: null, fullscreen: null, edit: null }, + partial: true, + }) + ); + }; + render() { + const { panel, dashboard, onTypeChanged, plugin } = this.props; const { location } = store.getState(); const activeTab = location.query.tab || 'queries'; return ( -
-
+
+
+
+
+
+
+ +
    {this.tabs.map(tab => { return ; })}
-
-
- {activeTab === 'queries' && this.renderQueriesTab()} - {activeTab === 'visualization' && this.renderVizTab()} -
+ {activeTab === 'queries' && } + {activeTab === 'visualization' && ( + + )}
); } @@ -121,8 +103,8 @@ function TabItem({ tab, activeTab, onClick }: TabItemParams) { }); return ( -
  • - onClick(tab)}> +
  • onClick(tab)}> + {tab.text}
  • diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index f13f212826ac..be8ffe30666c 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -1,10 +1,9 @@ -// Libraries import React, { PureComponent } from 'react'; -// Services & utils import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +import { EditorTabBody } from './EditorTabBody'; +import { DataSourcePicker } from './DataSourcePicker'; -// Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; @@ -48,6 +47,27 @@ export class QueriesTab extends PureComponent { } render() { - return
    (this.element = element)} className="panel-height-helper" />; + const currentDataSource = { + title: 'ProductionDB', + imgSrc: 'public/app/plugins/datasource/prometheus/img/prometheus_logo.svg', + render: () => , + }; + + const queryInspector = { + title: 'Query Inspector', + render: () =>

    hello

    , + }; + + const dsHelp = { + title: '', + icon: 'fa fa-question', + render: () =>

    hello

    , + }; + + return ( + +
    (this.element = element)} style={{ width: '100%' }} /> + + ); } } diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx new file mode 100644 index 000000000000..1bf03bb3b237 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -0,0 +1,57 @@ +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 { + constructor(props) { + super(props); + } + + renderPanelOptions() { + const { plugin, panel } = this.props; + const { PanelOptionsComponent } = plugin.exports; + + if (PanelOptionsComponent) { + return ; + } else { + return

    Visualization has no options

    ; + } + } + + 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 ; + }, + }; + + return ( + + {this.renderPanelOptions()} + + ); + } +} diff --git a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx index 9402133df344..4d886c86d746 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx @@ -1,12 +1,12 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; -import config from 'app/core/config'; -import { PanelPlugin } from 'app/types/plugins'; -import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar'; import _ from 'lodash'; +import config from 'app/core/config'; +import { PanelPlugin } from 'app/types/plugins'; + interface Props { - currentType: string; + current: PanelPlugin; onTypeChanged: (newType: PanelPlugin) => void; } @@ -15,6 +15,8 @@ interface State { } export class VizTypePicker extends PureComponent { + searchInput: HTMLElement; + constructor(props) { super(props); @@ -36,34 +38,55 @@ export class VizTypePicker extends PureComponent { renderVizPlugin = (plugin, index) => { const cssClass = classNames({ 'viz-picker__item': true, - 'viz-picker__item--selected': plugin.id === this.props.currentType, + 'viz-picker__item--selected': plugin.id === this.props.current.id, }); return (
    this.props.onTypeChanged(plugin)} title={plugin.name}> -
    {plugin.name}
    +
    ); }; - render() { + componentDidMount() { + setTimeout(() => { + this.searchInput.focus(); + }, 300); + } + + renderFilters() { return ( -
    -
    -
    - -
    + <> + +
    + +
    -
    - -
    {this.state.pluginList.map(this.renderVizPlugin)}
    -
    + + ); + } + + render() { + const { pluginList } = this.state; + + return ( + <> +
    + {this.renderFilters()} +
    -
    + +
    {pluginList.map(this.renderVizPlugin)}
    + ); } } diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index 77ebf754b3a7..561663d36d50 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -44,8 +44,8 @@ const panelTemplate = ` -
    diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index fed455472c90..d3c90c2ae435 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -1,14 +1,11 @@ -// Libraries import _ from 'lodash'; import coreModule from 'app/core/core_module'; -// Utils import config from 'app/core/config'; import { importPluginModule } from './plugin_loader'; -// Types import { DataSourceApi } from 'app/types/series'; -import { DataSource } from 'app/types'; +import { DataSource, DataSourceSelectItem } from 'app/types'; export class DatasourceSrv { datasources: { [name: string]: DataSource }; @@ -102,8 +99,8 @@ export class DatasourceSrv { return _.sortBy(es, ['name']); } - getMetricSources(options) { - const metricSources = []; + getMetricSources(options?) { + const metricSources: DataSourceSelectItem[] = []; _.each(config.datasources, (value, key) => { if (value.meta && value.meta.metrics) { diff --git a/public/app/plugins/panel/graph2/module.tsx b/public/app/plugins/panel/graph2/module.tsx index b132d3374f15..194f07823ad4 100644 --- a/public/app/plugins/panel/graph2/module.tsx +++ b/public/app/plugins/panel/graph2/module.tsx @@ -61,11 +61,26 @@ export class GraphOptions extends PureComponent> { return (
    -
    -
    Draw Modes
    - - - +
    +
    Display Options
    +
    +
    Draw Modes
    + + + +
    +
    +
    Test Options
    + + + +
    +
    +
    +
    Axes
    +
    +
    +
    Thresholds
    ); diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index 970ef4f11c88..a7b4ea7ac206 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -25,6 +25,13 @@ export interface DataSource { testDatasource?: () => Promise; } +export interface DataSourceSelectItem { + name: string; + value: string | null; + meta: PluginMeta; + sort: string; +} + export interface DataSourcesState { dataSources: DataSource[]; searchQuery: string; diff --git a/public/app/types/index.ts b/public/app/types/index.ts index fc176fed7e26..d6a6e73f06df 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -7,7 +7,7 @@ import { DashboardState } from './dashboard'; import { DashboardAcl, OrgRole, PermissionLevel } from './acl'; import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys'; import { Invitee, OrgUser, User, UsersState, UserState } from './user'; -import { DataSource, DataSourcesState } from './datasources'; +import { DataSource, DataSourceSelectItem, DataSourcesState } from './datasources'; import { TimeRange, LoadingState, @@ -55,6 +55,7 @@ export { OrgRole, PermissionLevel, DataSource, + DataSourceSelectItem, PluginMeta, ApiKey, ApiKeysState, diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index e4c7a9c59e13..55069163783e 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -97,7 +97,8 @@ @import 'components/form_select_box'; @import 'components/user-picker'; @import 'components/description-picker'; -@import 'components/viz_editor'; +@import 'components/panel_editor'; +@import 'components/toolbar'; @import 'components/delete_button'; @import 'components/add_data_source.scss'; @import 'components/page_loader'; diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 1896e74d6405..8aa01cf245d0 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -77,6 +77,7 @@ $brand-gradient: linear-gradient( rgba(255, 68, 0, 0.7) 99%, rgba(255, 68, 0, 0.7) 100% ); + $page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px); // Links @@ -110,7 +111,6 @@ $divider-border-color: #555; // Graphite Target Editor $tight-form-bg: $dark-3; - $tight-form-func-bg: #333334; $tight-form-func-highlight-bg: #444445; @@ -128,6 +128,7 @@ $list-item-bg: $card-background; $list-item-hover-bg: lighten($gray-blue, 2%); $list-item-link-color: $text-color; $list-item-shadow: $card-shadow; + $empty-list-cta-bg: $gray-blue; // Scrollbars @@ -152,8 +153,8 @@ $table-bg-hover: $dark-3; $btn-primary-bg: #ff6600; $btn-primary-bg-hl: #bc3e06; -$btn-secondary-bg: $blue-dark; $btn-secondary-bg-hl: lighten($blue-dark, 5%); +$btn-secondary-bg: $blue-dark; $btn-success-bg: $green; $btn-success-bg-hl: darken($green, 6%); @@ -266,6 +267,11 @@ $menu-dropdown-shadow: 5px 5px 20px -5px $black; // ------------------------- $tab-border-color: $dark-4; +// Toolbar +$toolbar-bg: $page-header-bg; +$toolbar-shadow: 0 0 20px black; +$toolbar-tab-bg: $gray-blue; + // Pagination // ------------------------- diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 45021a210fd9..ec34c963344c 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -31,6 +31,7 @@ $white: #fff; // Accent colors // ------------------------- $blue: #0083b3; +$blue-dark: #005f81; $blue-light: #00a8e6; $green: #3aa655; $red: #d44939; @@ -213,6 +214,11 @@ $menu-dropdown-shadow: 5px 5px 10px -5px $gray-1; // ------------------------- $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-shadow: 0 5px 30px 0 $gray-4; $search-filter-box-bg: $gray-7; diff --git a/public/sass/components/_buttons.scss b/public/sass/components/_buttons.scss index 916c141a5ecd..dcb4686701dc 100644 --- a/public/sass/components/_buttons.scss +++ b/public/sass/components/_buttons.scss @@ -120,8 +120,8 @@ // Info appears as a neutral blue .btn-secondary { @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl); + // Inverse appears as dark gray } -// Inverse appears as dark gray .btn-inverse { @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl, $btn-inverse-text-color, $btn-inverse-text-shadow); //background: $card-background; diff --git a/public/sass/components/_dashboard_grid.scss b/public/sass/components/_dashboard_grid.scss index da1f140d2527..5f142dfd02fb 100644 --- a/public/sass/components/_dashboard_grid.scss +++ b/public/sass/components/_dashboard_grid.scss @@ -3,7 +3,7 @@ .panel-in-fullscreen { .react-grid-layout { - height: 100% !important; + height: calc(100% - 20px) !important; } .react-grid-item { @@ -19,6 +19,10 @@ transform: translate(0px, 0px) !important; } + .panel { + margin: 0 !important; + } + // Disable grid interaction indicators in fullscreen panels .panel-header:hover { background-color: inherit; diff --git a/public/sass/components/_gf-form.scss b/public/sass/components/_gf-form.scss index 6d83fc6cf0bc..f2756e16203b 100644 --- a/public/sass/components/_gf-form.scss +++ b/public/sass/components/_gf-form.scss @@ -415,7 +415,27 @@ select.gf-form-input ~ .gf-form-help-icon { } .cta-form__close { + background: transparent; + padding: 4px 8px 4px 9px; + border: none; position: absolute; 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; } diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index fe4bd9e719eb..dcf3b1f0aa9b 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -41,7 +41,7 @@ .panel-in-fullscreen { .navbar { - @include navbar-alt-look(); + padding-left: 15px; } .navbar-button--add-panel, @@ -50,10 +50,6 @@ .navbar-page-btn .fa-caret-down { display: none; } - - .navbar-buttons--close { - display: flex; - } } .navbar-page-btn { @@ -98,7 +94,7 @@ } .navbar-buttons { - height: $navbarHeight; + // height: $navbarHeight; display: flex; align-items: center; justify-content: flex-end; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss new file mode 100644 index 000000000000..6d4022b6d3a1 --- /dev/null +++ b/public/sass/components/_panel_editor.scss @@ -0,0 +1,208 @@ +.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; +} diff --git a/public/sass/components/_scrollbar.scss b/public/sass/components/_scrollbar.scss index 00bd5f7c94c0..5dbb4518d524 100644 --- a/public/sass/components/_scrollbar.scss +++ b/public/sass/components/_scrollbar.scss @@ -106,78 +106,78 @@ 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 { diff --git a/public/sass/components/_submenu.scss b/public/sass/components/_submenu.scss index 1efd275bfadc..a6844b9e66b4 100644 --- a/public/sass/components/_submenu.scss +++ b/public/sass/components/_submenu.scss @@ -4,8 +4,7 @@ flex-wrap: wrap; align-content: flex-start; align-items: flex-start; - - margin: 0 0 $panel-margin 0; + padding: 0 0 $panel-margin 0; } .annotation-disabled, diff --git a/public/sass/components/_tabbed_view.scss b/public/sass/components/_tabbed_view.scss index 87b43a311429..c1cf78716bbe 100644 --- a/public/sass/components/_tabbed_view.scss +++ b/public/sass/components/_tabbed_view.scss @@ -2,9 +2,10 @@ display: flex; flex-direction: column; height: 100%; + flex-grow: 1; &.tabbed-view--new { - padding: 25px 0 0 0; + padding: 0 0 0 0; height: 100%; } } @@ -12,13 +13,14 @@ .tabbed-view-header { box-shadow: $page-header-shadow; border-bottom: 1px solid $page-header-border-color; + padding: 0 $dashboard-padding; @include clearfix(); } .tabbed-view-title { float: left; padding-top: 0.5rem; - margin: 0 $spacer*3 0 $spacer*1; + margin: 0 $spacer*3 0 0; } .tabbed-view-panel-title { diff --git a/public/sass/components/_tabs.scss b/public/sass/components/_tabs.scss index eb3c8ce13f5c..ea557b985a74 100644 --- a/public/sass/components/_tabs.scss +++ b/public/sass/components/_tabs.scss @@ -53,6 +53,7 @@ background-image: linear-gradient(to right, #ffd500 0%, #ff4400 99%, #ff4400 100%); } } + &.active--panel { background: $panel-bg !important; } diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index e12835d31c19..51a730926ba8 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -20,6 +20,7 @@ background-color: $page-bg; border-radius: 0 0 0 4px; box-shadow: $search-shadow; + z-index: $zindex-dropdown; } .gf-timepicker-absolute-section { diff --git a/public/sass/components/_toolbar.scss b/public/sass/components/_toolbar.scss new file mode 100644 index 000000000000..aa1c46e0fb45 --- /dev/null +++ b/public/sass/components/_toolbar.scss @@ -0,0 +1,59 @@ +.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; + } +} diff --git a/public/sass/components/_viz_editor.scss b/public/sass/components/_viz_editor.scss deleted file mode 100644 index 048e513cfbb6..000000000000 --- a/public/sass/components/_viz_editor.scss +++ /dev/null @@ -1,81 +0,0 @@ -.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%); -} diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 125edac500fc..79fe0b2569bf 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -37,20 +37,6 @@ div.flot-text { 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 { background-color: $panel-bg; border: $panel-border;