chore: Migrate .less styles to Emotion (#22474)

This commit is contained in:
Kamil Gabryjelski 2023-01-19 09:17:10 +01:00 committed by GitHub
parent 5026da50e1
commit 39c96d0568
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 1665 additions and 2414 deletions

View File

@ -127,10 +127,11 @@ export const databasesPage = {
export const sqlLabView = {
sqlEditorLeftBar: {
sqlEditorLeftBar: '[class="SqlEditorLeftBar"]',
databaseSchemaTableSection: '[class="SqlEditorLeftBar"] > :nth-child(1)',
sqlEditorLeftBar: '[data-test="sql-editor-left-bar"]',
databaseSchemaTableSection:
'[data-test="sql-editor-left-bar"] > :nth-child(1)',
tableSchemaSection:
'[class="SqlEditorLeftBar"] > :nth-child(1) > :nth-child(3) > :nth-child(1)',
'[data-test="sql-editor-left-bar"] > :nth-child(1) > :nth-child(3) > :nth-child(1)',
tableSchemaInputEmpty: '[aria-label="Select table or type table name"]',
},
databaseInput: '[data-test=DatabaseSelector] > :nth-child(1)',

View File

@ -42,9 +42,9 @@ import {
import { BYTES_PER_CHAR, KB_STORAGE } from './constants';
import setupApp from '../setup/setupApp';
import './main.less';
import '../assets/stylesheets/reactable-pagination.less';
import { theme } from '../preamble';
import { SqlLabGlobalStyles } from './SqlLabGlobalStyles';
setupApp();
setupExtensions();
@ -141,6 +141,7 @@ const Application = () => (
<Provider store={store}>
<ThemeProvider theme={theme}>
<GlobalStyles />
<SqlLabGlobalStyles />
<App />
</ThemeProvider>
</Provider>

View File

@ -16,13 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
@import '../../assets/stylesheets/less/variables.less';
@import './builder.less';
@import './dashboard.less';
@import './dnd.less';
@import './filter-scope-selector.less';
@import './grid.less';
@import './popover-menu.less';
@import './resizable.less';
@import './components/index.less';
import React from 'react';
import { Global } from '@emotion/react';
import { css } from '@superset-ui/core';
export const SqlLabGlobalStyles = () => (
<Global
styles={theme => css`
body {
min-height: max(
100vh,
${theme.gridUnit * 125}px
); // Set a min height so the gutter is always visible when resizing
overflow: hidden;
}
`}
/>
);

View File

@ -18,6 +18,8 @@
*/
import React, { useState, useEffect, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { css, styled } from '@superset-ui/core';
import { usePrevious } from 'src/hooks/usePrevious';
import { areArraysShallowEqual } from 'src/reduxUtils';
import sqlKeywords from 'src/SqlLab/utils/sqlKeywords';
@ -57,6 +59,28 @@ type AceEditorWrapperProps = {
hotkeys: HotKey[];
};
const StyledAceEditor = styled(AceEditor)`
${({ theme }) => css`
&& {
// double class is better than !important
border: 1px solid ${theme.colors.grayscale.light2};
font-feature-settings: 'liga' off, 'calt' off;
// Fira Code causes problem with Ace under Firefox
font-family: 'Menlo', 'Consolas', 'Courier New', 'Ubuntu Mono',
'source-code-pro', 'Lucida Console', monospace;
&.ace_autocomplete {
// Use !important because Ace Editor applies extra CSS at the last second
// when opening the autocomplete.
width: ${theme.gridUnit * 130}px !important;
}
.ace_scroller {
background-color: ${theme.colors.grayscale.light4};
}
}
`}
`;
const AceEditorWrapper = ({
autocomplete,
onBlur = () => {},
@ -258,7 +282,7 @@ const AceEditorWrapper = ({
};
return (
<AceEditor
<StyledAceEditor
keywords={words}
onLoad={onEditorLoad}
onBlur={onBlurSql}

View File

@ -20,7 +20,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { t } from '@superset-ui/core';
import { css, styled, t } from '@superset-ui/core';
import throttle from 'lodash/throttle';
import ToastContainer from 'src/components/MessageToasts/ToastContainer';
import {
@ -32,6 +32,69 @@ import * as Actions from 'src/SqlLab/actions/sqlLab';
import TabbedSqlEditors from '../TabbedSqlEditors';
import QueryAutoRefresh from '../QueryAutoRefresh';
const SqlLabStyles = styled.div`
${({ theme }) => css`
&.SqlLab {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 0 ${theme.gridUnit * 2}px;
pre {
padding: 0 !important;
margin: 0;
border: none;
font-size: ${theme.typography.sizes.s}px;
background: transparent !important;
}
.north-pane {
display: flex;
flex-direction: column;
}
.ace_editor {
flex-grow: 1;
}
.ace_content {
height: 100%;
}
.ant-tabs-content-holder {
/* This is needed for Safari */
height: 100%;
}
.ant-tabs-content {
height: 100%;
position: relative;
background-color: ${theme.colors.grayscale.light5};
overflow-x: auto;
overflow-y: auto;
> .ant-tabs-tabpane {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
}
.ResultsModal .ant-modal-body {
min-height: ${theme.gridUnit * 140}px;
}
.ant-modal-body {
overflow: auto;
}
}
`};
`;
class App extends React.PureComponent {
constructor(props) {
super(props);
@ -99,7 +162,7 @@ class App extends React.PureComponent {
return window.location.replace('/superset/sqllab/history/');
}
return (
<div className="App SqlLab">
<SqlLabStyles className="App SqlLab">
<QueryAutoRefresh
queries={queries}
refreshQueries={actions?.refreshQueries}
@ -107,7 +170,7 @@ class App extends React.PureComponent {
/>
<TabbedSqlEditors />
<ToastContainer />
</div>
</SqlLabStyles>
);
}
}

View File

@ -18,7 +18,7 @@
*/
import React, { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { t } from '@superset-ui/core';
import { css, styled, t } from '@superset-ui/core';
import Alert from 'src/components/Alert';
import TableView from 'src/components/TableView';
@ -36,6 +36,12 @@ export interface EstimateQueryCostButtonProps {
disabled?: boolean;
}
const CostEstimateModalStyles = styled.div`
${({ theme }) => css`
font-size: ${theme.typography.sizes.s};
`}
`;
const EstimateQueryCostButton = ({
getEstimate,
queryEditorId,
@ -76,13 +82,14 @@ const EstimateQueryCostButton = ({
}
if (queryCostEstimate?.completed) {
return (
<TableView
columns={columns}
data={tableData}
withPagination={false}
emptyWrapperType={EmptyWrapperType.Small}
className="cost-estimate"
/>
<CostEstimateModalStyles>
<TableView
columns={columns}
data={tableData}
withPagination={false}
emptyWrapperType={EmptyWrapperType.Small}
/>
</CostEstimateModalStyles>
);
}
return <Loading position="normal" />;

View File

@ -17,8 +17,7 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { styledMount as mount } from 'spec/helpers/theming';
import Label from 'src/components/Label';
import QueryStateLabel from 'src/SqlLab/components/QueryStateLabel';
@ -34,7 +33,7 @@ describe('SavedQuery', () => {
);
});
it('has an Overlay and a Popover', () => {
const wrapper = shallow(<QueryStateLabel {...mockedProps} />);
const wrapper = mount(<QueryStateLabel {...mockedProps} />);
expect(wrapper.find(Label)).toExist();
});
});

View File

@ -19,16 +19,18 @@
import React from 'react';
import Label from 'src/components/Label';
import { STATE_TYPE_MAP } from 'src/SqlLab/constants';
import { Query } from '@superset-ui/core';
import { styled, Query } from '@superset-ui/core';
interface QueryStateLabelProps {
query: Query;
}
const StyledLabel = styled(Label)`
margin-right: ${({ theme }) => theme.gridUnit}px;
`;
export default function QueryStateLabel({ query }: QueryStateLabelProps) {
return (
<Label className="m-r-3" type={STATE_TYPE_MAP[query.state]}>
{query.state}
</Label>
<StyledLabel type={STATE_TYPE_MAP[query.state]}>{query.state}</StyledLabel>
);
}

View File

@ -22,7 +22,13 @@ import ButtonGroup from 'src/components/ButtonGroup';
import Alert from 'src/components/Alert';
import Button from 'src/components/Button';
import shortid from 'shortid';
import { QueryResponse, QueryState, styled, t } from '@superset-ui/core';
import {
QueryResponse,
QueryState,
styled,
t,
useTheme,
} from '@superset-ui/core';
import { usePrevious } from 'src/hooks/usePrevious';
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
import {
@ -133,6 +139,7 @@ const ResultSet = ({
user,
defaultQueryLimit,
}: ResultSetProps) => {
const theme = useTheme();
const [searchText, setSearchText] = useState('');
const [cachedData, setCachedData] = useState<Record<string, unknown>[]>([]);
const [showSaveDatasetModal, setShowSaveDatasetModal] = useState(false);
@ -449,7 +456,7 @@ const ResultSet = ({
<ButtonGroup>
<Button
buttonSize="small"
className="m-r-5"
css={{ marginRight: theme.gridUnit }}
onClick={() => popSelectStar(tempSchema, tempTable)}
>
{t('Query in a new tab')}

View File

@ -23,7 +23,7 @@ import { CSSTransition } from 'react-transition-group';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import Split from 'react-split';
import { t, styled, useTheme } from '@superset-ui/core';
import { css, t, styled, useTheme } from '@superset-ui/core';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import Modal from 'src/components/Modal';
@ -132,6 +132,62 @@ const StyledSidebar = styled.div`
hide ? 'transparent' : theme.colors.grayscale.light2};
`;
const StyledSqlEditor = styled.div`
${({ theme }) => css`
display: flex;
flex-direction: row;
height: 100%;
.schemaPane {
transition: transform ${theme.transitionTiming}s ease-in-out;
}
.queryPane {
flex: 1 1 auto;
padding: ${theme.gridUnit * 2}px;
overflow-y: auto;
overflow-x: scroll;
}
.schemaPane-enter-done,
.schemaPane-exit {
transform: translateX(0);
z-index: 7;
}
.schemaPane-exit-active {
transform: translateX(-120%);
}
.schemaPane-enter-active {
transform: translateX(0);
max-width: ${theme.gridUnit * 75}px;
}
.schemaPane-enter,
.schemaPane-exit-done {
max-width: 0;
transform: translateX(-120%);
overflow: hidden;
}
.schemaPane-exit-done + .queryPane {
margin-left: 0;
}
.gutter {
border-top: 1px solid ${theme.colors.grayscale.light2};
border-bottom: 1px solid ${theme.colors.grayscale.light2};
width: 3%;
margin: ${theme.gridUnit}px 47%;
}
.gutter.gutter-vertical {
cursor: row-resize;
}
`}
`;
const propTypes = {
tables: PropTypes.array.isRequired,
queryEditor: PropTypes.object.isRequired,
@ -636,7 +692,7 @@ const SqlEditor = ({
? 'schemaPane-exit-done'
: 'schemaPane-enter-done';
return (
<div ref={sqlEditorRef} className="SqlEditor">
<StyledSqlEditor ref={sqlEditorRef} className="SqlEditor">
<CSSTransition classNames="schemaPane" in={!hideLeftBar} timeout={300}>
<ResizableSidebar
id={`sqllab:${queryEditor.id}`}
@ -704,7 +760,7 @@ const SqlEditor = ({
<span>{t('Name')}</span>
<Input placeholder={createModalPlaceHolder} onChange={ctasChanged} />
</Modal>
</div>
</StyledSqlEditor>
);
};

View File

@ -89,12 +89,25 @@ const collapseStyles = (theme: SupersetTheme) => css`
.ant-collapse-arrow {
top: ${theme.gridUnit * 2}px !important;
color: ${theme.colors.primary.dark1} !important;
&: hover {
&:hover {
color: ${theme.colors.primary.dark2} !important;
}
}
`;
const LeftBarStyles = styled.div`
${({ theme }) => css`
height: 100%;
display: flex;
flex-direction: column;
.divider {
border-bottom: 1px solid ${theme.colors.grayscale.light4};
margin: ${theme.gridUnit * 4}px 0;
}
`}
`;
const SqlEditorLeftBar = ({
database,
queryEditorId,
@ -228,7 +241,7 @@ const SqlEditorLeftBar = ({
}, []);
return (
<div data-test="sql-editor-left-bar" className="SqlEditorLeftBar">
<LeftBarStyles data-test="sql-editor-left-bar">
<TableSelectorMultiple
onEmptyResults={onEmptyResults}
emptyState={emptyStateComponent(emptyResultsWithSearch)}
@ -276,7 +289,7 @@ const SqlEditorLeftBar = ({
<i className="fa fa-bomb" /> {t('Reset state')}
</Button>
)}
</div>
</LeftBarStyles>
);
};

View File

@ -41,6 +41,12 @@ const TabTitle = styled.span`
text-transform: none;
`;
const IconContainer = styled.div`
display: inline-block;
width: ${({ theme }) => theme.gridUnit * 8}px;
text-align: center;
`;
interface Props {
queryEditor: QueryEditor;
}
@ -91,9 +97,9 @@ const SqlEditorTabHeader: React.FC<Props> = ({ queryEditor }) => {
onClick={() => actions.removeQueryEditor(qe)}
data-test="close-tab-menu-option"
>
<div className="icon-container">
<IconContainer>
<i className="fa fa-close" />
</div>
</IconContainer>
{t('Close tab')}
</Menu.Item>
<Menu.Item
@ -101,9 +107,9 @@ const SqlEditorTabHeader: React.FC<Props> = ({ queryEditor }) => {
onClick={renameTab}
data-test="rename-tab-menu-option"
>
<div className="icon-container">
<IconContainer>
<i className="fa fa-i-cursor" />
</div>
</IconContainer>
{t('Rename tab')}
</Menu.Item>
<Menu.Item
@ -111,9 +117,9 @@ const SqlEditorTabHeader: React.FC<Props> = ({ queryEditor }) => {
onClick={() => actions.toggleLeftBar(qe)}
data-test="toggle-menu-option"
>
<div className="icon-container">
<IconContainer>
<i className="fa fa-cogs" />
</div>
</IconContainer>
{qe.hideLeftBar ? t('Expand tool bar') : t('Hide tool bar')}
</Menu.Item>
<Menu.Item
@ -121,9 +127,9 @@ const SqlEditorTabHeader: React.FC<Props> = ({ queryEditor }) => {
onClick={() => actions.removeAllOtherQueryEditors(qe)}
data-test="close-all-other-menu-option"
>
<div className="icon-container">
<IconContainer>
<i className="fa fa-times-circle-o" />
</div>
</IconContainer>
{t('Close all other tabs')}
</Menu.Item>
<Menu.Item
@ -131,9 +137,9 @@ const SqlEditorTabHeader: React.FC<Props> = ({ queryEditor }) => {
onClick={() => actions.cloneQueryToNewTab(qe, false)}
data-test="clone-tab-menu-option"
>
<div className="icon-container">
<IconContainer>
<i className="fa fa-files-o" />
</div>
</IconContainer>
{t('Duplicate tab')}
</Menu.Item>
</Menu>

View File

@ -17,13 +17,42 @@
* under the License.
*/
import React from 'react';
import { QueryState, styled } from '@superset-ui/core';
import { css, QueryState, styled } from '@superset-ui/core';
import Icons, { IconType } from 'src/components/Icons';
const IconContainer = styled.span`
position: absolute;
top: -7px;
left: 0px;
top: -6px;
left: 1px;
`;
const Circle = styled.div`
${({ theme }) => css`
border-radius: 50%;
width: ${theme.gridUnit * 3}px;
height: ${theme.gridUnit * 3}px;
display: inline-block;
background-color: ${theme.colors.grayscale.light2};
text-align: center;
vertical-align: middle;
font-size: ${theme.typography.sizes.m}px;
font-weight: ${theme.typography.weights.bold};
color: ${theme.colors.grayscale.light5};
position: relative;
&.running {
background-color: ${theme.colors.info.base};
}
&.success {
background-color: ${theme.colors.success.base};
}
&.failed {
background-color: ${theme.colors.error.base};
}
`}
`;
interface TabStatusIconProps {
@ -38,12 +67,12 @@ const STATE_ICONS: Record<string, React.FC<IconType>> = {
export default function TabStatusIcon({ tabState }: TabStatusIconProps) {
const StatusIcon = STATE_ICONS[tabState];
return (
<div className={`circle ${tabState}`}>
<Circle className={`circle ${tabState}`}>
{StatusIcon && (
<IconContainer>
<StatusIcon iconSize="xs" />
</IconContainer>
)}
</div>
</Circle>
);
}

View File

@ -212,9 +212,9 @@ describe('TabbedSqlEditors', () => {
});
it('should disable new tab when offline', () => {
wrapper = getWrapper();
expect(wrapper.find(EditableTabs).props().hideAdd).toBe(false);
expect(wrapper.find('#a11y-query-editor-tabs').props().hideAdd).toBe(false);
wrapper.setProps({ offline: true });
expect(wrapper.find(EditableTabs).props().hideAdd).toBe(true);
expect(wrapper.find('#a11y-query-editor-tabs').props().hideAdd).toBe(true);
});
it('should have an empty state when query editors is empty', () => {
wrapper = getWrapper();

View File

@ -54,6 +54,12 @@ const defaultProps = {
scheduleQueryWarning: null,
};
const StyledEditableTabs = styled(EditableTabs)`
height: 100%;
display: flex;
flex-direction: column;
`;
const StyledTab = styled.span`
line-height: 24px;
`;
@ -303,7 +309,7 @@ class TabbedSqlEditors extends React.PureComponent {
);
return (
<EditableTabs
<StyledEditableTabs
destroyInactiveTabPane
activeKey={this.props.tabHistory[this.props.tabHistory.length - 1]}
id="a11y-query-editor-tabs"
@ -331,7 +337,7 @@ class TabbedSqlEditors extends React.PureComponent {
>
{editors}
{noQueryEditors && emptyTabState}
</EditableTabs>
</StyledEditableTabs>
);
}
}

View File

@ -21,7 +21,7 @@ import { useDispatch } from 'react-redux';
import Collapse from 'src/components/Collapse';
import Card from 'src/components/Card';
import ButtonGroup from 'src/components/ButtonGroup';
import { t, styled } from '@superset-ui/core';
import { css, t, styled } from '@superset-ui/core';
import { debounce } from 'lodash';
import { removeDataPreview, removeTables } from 'src/SqlLab/actions/sqlLab';
@ -61,7 +61,7 @@ export interface TableElementProps {
const StyledSpan = styled.span`
color: ${({ theme }) => theme.colors.primary.dark1};
&: hover {
&:hover {
color: ${({ theme }) => theme.colors.primary.dark2};
}
cursor: pointer;
@ -72,6 +72,39 @@ const Fade = styled.div`
opacity: ${(props: { hovered: boolean }) => (props.hovered ? 1 : 0)};
`;
const StyledCollapsePanel = styled(Collapse.Panel)`
${({ theme }) => css`
& {
.ws-el-controls {
margin-right: ${-theme.gridUnit}px;
display: flex;
}
.header-container {
display: flex;
flex: 1;
align-items: center;
width: 100%;
.table-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: ${theme.typography.sizes.l}px;
flex: 1;
}
.header-right-side {
margin-left: auto;
display: flex;
align-items: center;
margin-right: ${theme.gridUnit * 8}px;
}
}
}
`}
`;
const TableElement = ({ table, ...props }: TableElementProps) => {
const dispatch = useDispatch();
@ -287,7 +320,7 @@ const TableElement = ({ table, ...props }: TableElementProps) => {
};
return (
<Collapse.Panel
<StyledCollapsePanel
{...props}
key={table.id}
header={renderHeader()}
@ -295,7 +328,7 @@ const TableElement = ({ table, ...props }: TableElementProps) => {
forceRender
>
{renderBody()}
</Collapse.Panel>
</StyledCollapsePanel>
);
};

View File

@ -1,491 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
@import '../assets/stylesheets/less/variables.less';
body {
min-height: ~'max(100vh, 500px)'; // Set a min height so the gutter is always visible when resizing
overflow: hidden;
}
.inlineBlock {
display: inline-block;
}
.valignTop {
vertical-align: top;
}
.inline {
display: inline;
}
.nopadding {
padding: 0px;
}
.pane-cell {
padding: 10px;
overflow: auto;
width: 100%;
height: 100%;
}
.ant-tabs-content-holder {
/* This is needed for Safari */
height: 100%;
}
.ant-tabs-content {
height: 100%;
position: relative;
background-color: @lightest;
overflow-x: auto;
overflow-y: auto;
> .ant-tabs-tabpane {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
}
.Workspace .btn-sm {
box-shadow: 1px 1px 2px fade(@darkest, @opacity-light);
margin-top: 2px;
padding: 4px;
}
.Workspace hr {
margin-top: 10px;
margin-bottom: 10px;
}
div.Workspace {
height: 100%;
margin: 0px;
}
.padded {
padding: 10px;
}
.p-t-10 {
padding-top: 10px;
}
.p-t-5 {
padding-top: 5px;
}
.m-r-5 {
margin-right: 5px;
}
.m-r-3 {
margin-right: 3px;
}
.m-l-1 {
margin-left: 1px;
}
.m-l-2 {
margin-left: 2px;
}
.m-r-10 {
margin-right: 10px;
}
.m-l-10 {
margin-left: 10px;
}
.m-l-5 {
margin-left: 5px;
}
.m-b-10 {
margin-bottom: 10px;
}
.m-t-5 {
margin-top: 5px;
}
.m-t-10 {
margin-top: 10px;
}
.p-t-10 {
padding-top: 10px;
}
.no-shadow {
box-shadow: none;
background-color: transparent;
}
.pane-west {
height: 100%;
overflow: auto;
}
.circle {
@circle-diameter: 10px;
border-radius: (@circle-diameter / 2);
width: @circle-diameter;
height: @circle-diameter;
display: inline-block;
background-color: @gray-light;
text-align: center;
vertical-align: middle;
font-size: @font-size-m;
font-weight: @font-weight-bold;
color: @lightest;
position: relative;
}
.running {
background-color: @info;
}
.success {
background-color: @success;
}
.failed {
background-color: @danger;
}
.handle {
cursor: move;
}
#a11y-query-editor-tabs {
height: 100%;
display: flex;
flex-direction: column;
}
.SqlLab {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
padding: 0 10px;
pre {
padding: 0px !important;
margin: 0px;
border: none;
font-size: @font-size-s;
background-color: transparent !important;
}
.north-pane {
display: flex;
flex-direction: column;
}
#ace-editor {
height: calc(100% - 51px);
flex-grow: 1;
}
.ace_content {
height: 100%;
}
}
.SqlEditorTabs li {
a:focus {
outline: 0;
}
.ddbtn-tab {
font-size: inherit;
color: black;
&:active {
background: none;
}
svg {
vertical-align: middle;
}
}
.dropdown.btn-group.btn-group-sm {
width: 3px;
height: 3px;
border-radius: 1.5px;
background: #bababa;
margin-right: 8px;
font-weight: @font-weight-normal;
display: inline-flex;
&:hover {
background-color: @primary-color;
&:before,
&:after {
background-color: @primary-color;
}
}
&:before,
&:after {
position: absolute;
content: ' ';
width: 3px;
height: 3px;
border-radius: 1.5px;
background-color: #bababa;
}
&:before {
transform: translateY(-5px);
}
&:after {
transform: translateY(5px);
}
}
ul.dropdown-menu {
margin-top: 10px;
}
.dropdown-toggle {
padding-top: 2px;
}
}
.SqlEditor {
display: flex;
flex-direction: row;
height: 100%;
.schemaPane {
transition: transform @timing-normal ease-in-out;
}
.queryPane {
flex: 1 1 auto;
padding: 10px;
overflow-y: none;
overflow-x: scroll;
}
.schemaPane-enter-done,
.schemaPane-exit {
transform: translateX(0);
z-index: 7;
}
.schemaPane-exit-active {
transform: translateX(-120%);
}
.schemaPane-enter-active {
transform: translateX(0);
max-width: 300px;
}
.schemaPane-enter,
.schemaPane-exit-done {
max-width: 0;
transform: translateX(-120%);
overflow: hidden;
}
.schemaPane-exit-done + .queryPane {
margin-left: 0;
}
.gutter {
border-top: 1px solid @gray-light;
border-bottom: 1px solid @gray-light;
width: 3%;
margin: 3px 47%;
}
.gutter.gutter-vertical {
cursor: row-resize;
}
}
.SqlEditorLeftBar {
height: 100%;
display: flex;
flex-direction: column;
.divider {
border-bottom: 1px solid @gray-bg;
margin: 15px 0;
}
}
.popover {
max-width: 400px;
}
.table-label {
margin-top: 5px;
margin-right: 10px;
float: left;
}
div.tablePopover {
opacity: 0.7 !important;
&:hover {
opacity: 1 !important;
}
}
.ace_editor.ace_editor {
//double class is better than !important
border: 1px solid @gray-light;
font-feature-settings: @font-feature-settings;
// Fira Code causes problem with Ace under Firefox
font-family: 'Menlo', 'Consolas', 'Courier New', 'Ubuntu Mono',
'source-code-pro', 'Lucida Console', monospace;
&.ace_autocomplete {
// Use !important because Ace Editor applies extra CSS at the last second
// when opening the autocomplete.
width: 520px !important;
}
}
.Select__menu-outer {
min-width: 100%;
width: inherit;
z-index: @z-index-dropdown;
}
.Select__clear-indicator {
margin-top: -2px;
}
.Select__arrow {
margin-top: 5px;
}
.ace_scroller {
background-color: @gray-bg;
}
.TableElement {
.well {
margin-top: 5px;
margin-bottom: 5px;
padding: 5px 10px;
}
.ws-el-controls {
margin-right: -0.3em;
display: flex;
}
.header-container {
display: flex;
flex: 1;
align-items: center;
width: 100%;
.table-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
flex: 1;
}
.header-right-side {
margin-left: auto;
display: flex;
align-items: center;
margin-right: 33px;
}
}
}
.QueryTable .label {
display: inline-block;
}
.QueryTable .ant-btn {
position: static;
}
.ResultsModal .ant-modal-body {
min-height: 560px;
}
.ant-modal-body {
overflow: auto;
}
a.Link {
cursor: pointer;
}
.QueryTable .well {
padding: 3px 5px;
margin: 3px 5px;
}
.nav-tabs .ddbtn-tab {
padding: 0;
border: none;
background: none;
position: relative;
top: 2px;
&:focus {
outline: 0;
}
&:active {
box-shadow: none;
}
}
.icon-container {
display: inline-block;
width: 30px;
text-align: center;
}
.search-date-filter-container {
display: flex;
.Select {
margin-right: 3px;
}
}
.cost-estimate {
font-size: @font-size-s;
}

View File

@ -42,10 +42,6 @@ input.form-control {
background-color: @lightest;
}
.chart-header a.danger {
color: @danger;
}
.disabledButton {
pointer-events: none;
}
@ -165,16 +161,6 @@ img.viz-thumb-option {
max-height: 700px;
}
.chart-header .header-text {
font-size: @font-size-xl;
line-height: 22px;
padding-bottom: 8px;
border-bottom: 1px solid @gray;
margin-top: 10px;
margin-left: 10px;
margin-right: 10px;
}
#is_cached {
display: none;
}
@ -327,6 +313,10 @@ table.table-no-hover tr:hover {
margin-bottom: 10px;
}
.m-l-2 {
margin-left: 2px;
}
.m-l-4 {
margin-left: 4px;
}

View File

@ -113,6 +113,10 @@ const Styles = styled.div`
.pivot_table tbody tr {
font-feature-settings: 'tnum' 1;
}
.alert {
margin: ${({ theme }) => theme.gridUnit * 2}px;
}
}
`;

View File

@ -39,7 +39,6 @@ import {
} from '../../logger/LogUtils';
import { areObjectsEqual } from '../../reduxUtils';
import '../stylesheets/index.less';
import getLocationHash from '../util/getLocationHash';
import isDashboardEmpty from '../util/isDashboardEmpty';
import { getAffectedOwnDataCharts } from '../util/charts/getOwnDataCharts';

View File

@ -125,7 +125,7 @@ describe('DashboardBuilder', () => {
it('should render a StickyContainer with class "dashboard"', () => {
const { getByTestId } = setup();
const stickyContainer = getByTestId('dashboard-content');
const stickyContainer = getByTestId('dashboard-content-wrapper');
expect(stickyContainer).toHaveClass('dashboard');
});
@ -133,7 +133,7 @@ describe('DashboardBuilder', () => {
const { getByTestId } = setup({
dashboardState: { ...mockState.dashboardState, editMode: true },
});
const stickyContainer = getByTestId('dashboard-content');
const stickyContainer = getByTestId('dashboard-content-wrapper');
expect(stickyContainer).toHaveClass('dashboard dashboard--editing');
});

View File

@ -26,7 +26,14 @@ import React, {
useRef,
useState,
} from 'react';
import { css, JsonObject, styled, t } from '@superset-ui/core';
import {
addAlpha,
css,
JsonObject,
styled,
t,
useTheme,
} from '@superset-ui/core';
import { Global } from '@emotion/react';
import { useDispatch, useSelector } from 'react-redux';
import ErrorBoundary from 'src/components/ErrorBoundary';
@ -82,52 +89,66 @@ import { useNativeFilters } from './state';
type DashboardBuilderProps = {};
const StyledDiv = styled.div`
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
flex: 1;
/* Special cases */
${({ theme }) => css`
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
flex: 1;
/* Special cases */
/* A row within a column has inset hover menu */
.dragdroppable-column .dragdroppable-row .hover-menu--left {
left: -12px;
background: ${({ theme }) => theme.colors.grayscale.light5};
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
}
/* A row within a column has inset hover menu */
.dragdroppable-column .dragdroppable-row .hover-menu--left {
left: ${theme.gridUnit * -3}px;
background: ${theme.colors.grayscale.light5};
border: 1px solid ${theme.colors.grayscale.light2};
}
.dashboard-component-tabs {
position: relative;
}
.dashboard-component-tabs {
position: relative;
}
/* A column within a column or tabs has inset hover menu */
.dragdroppable-column .dragdroppable-column .hover-menu--top,
.dashboard-component-tabs .dragdroppable-column .hover-menu--top {
top: -12px;
background: ${({ theme }) => theme.colors.grayscale.light5};
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
}
/* A column within a column or tabs has inset hover menu */
.dragdroppable-column .dragdroppable-column .hover-menu--top,
.dashboard-component-tabs .dragdroppable-column .hover-menu--top {
top: ${theme.gridUnit * -3}px;
background: ${theme.colors.grayscale.light5};
border: 1px solid ${theme.colors.grayscale.light2};
}
/* move Tabs hover menu to top near actual Tabs */
.dashboard-component-tabs > .hover-menu-container > .hover-menu--left {
top: 0;
transform: unset;
background: transparent;
}
/* move Tabs hover menu to top near actual Tabs */
.dashboard-component-tabs > .hover-menu-container > .hover-menu--left {
top: 0;
transform: unset;
background: transparent;
}
/* push Chart actions to upper right */
.dragdroppable-column .dashboard-component-chart-holder .hover-menu--top,
.dragdroppable .dashboard-component-header .hover-menu--top {
right: 8px;
top: 8px;
background: transparent;
border: none;
transform: unset;
left: unset;
}
div:hover > .hover-menu-container .hover-menu,
.hover-menu-container .hover-menu:hover {
opacity: 1;
}
/* push Chart actions to upper right */
.dragdroppable-column .dashboard-component-chart-holder .hover-menu--top,
.dragdroppable .dashboard-component-header .hover-menu--top {
right: ${theme.gridUnit * 2}px;
top: ${theme.gridUnit * 2}px;
background: transparent;
border: none;
transform: unset;
left: unset;
}
div:hover > .hover-menu-container .hover-menu,
.hover-menu-container .hover-menu:hover {
opacity: 1;
}
p {
margin: 0 0 ${theme.gridUnit * 2}px 0;
}
i.danger {
color: ${theme.colors.error.base};
}
i.warning {
color: ${theme.colors.alert.base};
}
`}
`;
// @z-index-above-dashboard-charts + 1 = 11
@ -164,75 +185,247 @@ const StyledContent = styled.div<{
${({ fullSizeChartId }) => fullSizeChartId && `z-index: 101;`}
`;
const StyledDashboardContent = styled.div<{
dashboardFiltersOpen: boolean;
editMode: boolean;
nativeFiltersEnabled: boolean;
filterBarOrientation: FilterBarOrientation;
}>`
display: flex;
flex-direction: row;
flex-wrap: nowrap;
height: auto;
flex: 1;
const DashboardContentWrapper = styled.div`
${({ theme }) => css`
&.dashboard {
position: relative;
flex-grow: 1;
display: flex;
flex-direction: column;
height: 100%;
.grid-container .dashboard-component-tabs {
box-shadow: none;
padding-left: 0;
}
.grid-container {
/* without this, the grid will not get smaller upon toggling the builder panel on */
width: 0;
flex: 1;
position: relative;
margin-top: ${({ theme }) => theme.gridUnit * 6}px;
margin-right: ${({ theme }) => theme.gridUnit * 8}px;
margin-bottom: ${({ theme }) => theme.gridUnit * 6}px;
margin-left: ${({
theme,
dashboardFiltersOpen,
editMode,
nativeFiltersEnabled,
filterBarOrientation,
}) => {
if (
!dashboardFiltersOpen &&
!editMode &&
nativeFiltersEnabled &&
filterBarOrientation !== FilterBarOrientation.HORIZONTAL
) {
return 0;
/* drop shadow for top-level tabs only */
& .dashboard-component-tabs {
box-shadow: 0 ${theme.gridUnit}px ${theme.gridUnit}px 0
${addAlpha(
theme.colors.grayscale.dark2,
parseFloat(theme.opacity.light) / 100,
)};
padding-left: ${theme.gridUnit *
2}px; /* note this is added to tab-level padding, to match header */
}
return theme.gridUnit * 8;
}}px;
${({ editMode, theme }) =>
editMode &&
.dropdown-toggle.btn.btn-primary .caret {
color: ${theme.colors.grayscale.light5};
}
.background--transparent {
background-color: transparent;
}
.background--white {
background-color: ${theme.colors.grayscale.light5};
}
}
&.dashboard--editing {
.grid-row:after,
.dashboard-component-tabs > .hover-menu:hover + div:after {
border: 1px dashed transparent;
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1;
pointer-events: none;
}
.resizable-container {
& .dashboard-component-chart-holder {
.dashboard-chart {
.chart-container {
cursor: move;
opacity: 0.2;
}
.slice_container {
/* disable chart interactions in edit mode */
pointer-events: none;
}
}
&:hover .dashboard-chart .chart-container {
opacity: 0.7;
}
}
&:hover,
&.resizable-container--resizing:hover {
& > .dashboard-component-chart-holder:after {
border: 1px dashed ${theme.colors.primary.base};
}
}
}
.resizable-container--resizing:hover > .grid-row:after,
.hover-menu:hover + .grid-row:after,
.dashboard-component-tabs > .hover-menu:hover + div:after {
border: 1px dashed ${theme.colors.primary.base};
z-index: 2;
}
.grid-row:after,
.dashboard-component-tabs > .hover-menu + div:after {
border: 1px dashed ${theme.colors.grayscale.light2};
}
/* provide hit area in case row contents is edge to edge */
.dashboard-component-tabs-content {
.dragdroppable-row {
padding-top: ${theme.gridUnit * 4}px;
}
& > div:not(:last-child):not(.empty-droptarget) {
margin-bottom: ${theme.gridUnit * 4}px;
}
}
.dashboard-component-chart-holder {
&:after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1;
pointer-events: none;
border: 1px solid transparent;
}
&:hover:after {
border: 1px dashed ${theme.colors.primary.base};
z-index: 2;
}
}
.contract-trigger:before {
display: none;
}
}
& .dashboard-component-tabs-content {
& > div:not(:last-child):not(.empty-droptarget) {
margin-bottom: ${theme.gridUnit * 4}px;
}
& > .empty-droptarget {
position: absolute;
width: 100%;
}
& > .empty-droptarget:first-child {
height: ${theme.gridUnit * 4}px;
top: -2px;
z-index: 10;
}
& > .empty-droptarget:last-child {
height: ${theme.gridUnit * 3}px;
bottom: 0;
}
}
.empty-droptarget:first-child .drop-indicator--bottom {
top: ${theme.gridUnit * 6}px;
}
`}
`;
const StyledDashboardContent = styled.div<{
editMode: boolean;
marginLeft: number;
}>`
${({ theme, editMode, marginLeft }) => css`
display: flex;
flex-direction: row;
flex-wrap: nowrap;
height: auto;
flex: 1;
.grid-container .dashboard-component-tabs {
box-shadow: none;
padding-left: 0;
}
.grid-container {
/* without this, the grid will not get smaller upon toggling the builder panel on */
width: 0;
flex: 1;
position: relative;
margin-top: ${theme.gridUnit * 6}px;
margin-right: ${theme.gridUnit * 8}px;
margin-bottom: ${theme.gridUnit * 6}px;
margin-left: ${marginLeft}px;
${editMode &&
`
max-width: calc(100% - ${
BUILDER_SIDEPANEL_WIDTH + theme.gridUnit * 16
}px);
`}
}
.dashboard-builder-sidepane {
width: ${BUILDER_SIDEPANEL_WIDTH}px;
z-index: 1;
}
/* this is the ParentSize wrapper */
& > div:first-child {
height: inherit !important;
}
}
.dashboard-component-chart-holder {
// transitionable traits to show filter relevance
transition: opacity ${({ theme }) => theme.transitionTiming}s,
border-color ${({ theme }) => theme.transitionTiming}s,
box-shadow ${({ theme }) => theme.transitionTiming}s;
border: 0 solid transparent;
}
.dashboard-builder-sidepane {
width: ${BUILDER_SIDEPANEL_WIDTH}px;
z-index: 1;
}
.dashboard-component-chart-holder {
width: 100%;
height: 100%;
background-color: ${theme.colors.grayscale.light5};
position: relative;
padding: ${theme.gridUnit * 4}px;
overflow-y: visible;
// transitionable traits to show filter relevance
transition: opacity ${theme.transitionTiming}s ease-in-out,
border-color ${theme.transitionTiming}s ease-in-out,
box-shadow ${theme.transitionTiming}s ease-in-out;
&.fade-in {
border-radius: ${theme.borderRadius}px;
box-shadow: inset 0 0 0 2px ${theme.colors.primary.base},
0 0 0 3px
${addAlpha(
theme.colors.primary.base,
parseFloat(theme.opacity.light) / 100,
)};
}
&.fade-out {
border-radius: ${theme.borderRadius}px;
box-shadow: none;
}
& .missing-chart-container {
display: flex;
flex-direction: column;
align-items: center;
overflow-y: auto;
justify-content: center;
.missing-chart-body {
font-size: ${theme.typography.sizes.s}px;
position: relative;
display: flex;
}
}
}
`}
`;
const DashboardBuilder: FC<DashboardBuilderProps> = () => {
const dispatch = useDispatch();
const uiConfig = useUiConfig();
const theme = useTheme();
const dashboardId = useSelector<RootState, string>(
({ dashboardInfo }) => `${dashboardInfo.id}`,
@ -433,6 +626,14 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
],
);
const dashboardContentMarginLeft =
!dashboardFiltersOpen &&
!editMode &&
nativeFiltersEnabled &&
filterBarOrientation !== FilterBarOrientation.HORIZONTAL
? 0
: theme.gridUnit * 8;
return (
<StyledDiv>
{showFilterBar && filterBarOrientation === FilterBarOrientation.VERTICAL && (
@ -518,16 +719,14 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
image="dashboard.svg"
/>
)}
<div
data-test="dashboard-content"
<DashboardContentWrapper
data-test="dashboard-content-wrapper"
className={cx('dashboard', editMode && 'dashboard--editing')}
>
<StyledDashboardContent
className="dashboard-content"
dashboardFiltersOpen={dashboardFiltersOpen}
editMode={editMode}
nativeFiltersEnabled={nativeFiltersEnabled}
filterBarOrientation={filterBarOrientation}
marginLeft={dashboardContentMarginLeft}
>
{showDashboard ? (
<DashboardContainer topLevelTabs={topLevelTabs} />
@ -536,7 +735,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
)}
{editMode && <BuilderComponentPane topOffset={barTopOffset} />}
</StyledDashboardContent>
</div>
</DashboardContentWrapper>
</StyledContent>
{dashboardIsSaving && (
<Loading

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { styled, t } from '@superset-ui/core';
import { addAlpha, css, styled, t } from '@superset-ui/core';
import { EmptyStateBig } from 'src/components/EmptyState';
import { componentShape } from '../util/propShapes';
import DashboardComponent from '../containers/DashboardComponent';
@ -58,16 +58,62 @@ const DashboardEmptyStateContainer = styled.div`
right: 0;
`;
const GridContent = styled.div`
${({ theme }) => css`
display: flex;
flex-direction: column;
/* gutters between rows */
& > div:not(:last-child):not(.empty-droptarget) {
margin-bottom: ${theme.gridUnit * 4}px;
}
& > .empty-droptarget {
width: 100%;
height: 100%;
}
& > .empty-droptarget:first-child {
height: ${theme.gridUnit * 12}px;
margin-top: ${theme.gridUnit * -6}px;
margin-bottom: ${theme.gridUnit * -6}px;
}
& > .empty-droptarget:only-child {
height: 80vh;
}
`}
`;
const GridColumnGuide = styled.div`
${({ theme }) => css`
// /* Editing guides */
&.grid-column-guide {
position: absolute;
top: 0;
min-height: 100%;
background-color: ${addAlpha(
theme.colors.primary.base,
parseFloat(theme.opacity.light) / 100,
)};
pointer-events: none;
box-shadow: inset 0 0 0 1px
${addAlpha(
theme.colors.primary.base,
parseFloat(theme.opacity.mediumHeavy) / 100,
)};
}
`};
`;
class DashboardGrid extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isResizing: false,
rowGuideTop: null,
};
this.handleResizeStart = this.handleResizeStart.bind(this);
this.handleResize = this.handleResize.bind(this);
this.handleResizeStop = this.handleResizeStop.bind(this);
this.handleTopDropTargetDrop = this.handleTopDropTargetDrop.bind(this);
this.getRowGuidePosition = this.getRowGuidePosition.bind(this);
@ -90,30 +136,17 @@ class DashboardGrid extends React.PureComponent {
this.grid = ref;
}
handleResizeStart({ ref, direction }) {
let rowGuideTop = null;
if (direction === 'bottom' || direction === 'bottomRight') {
rowGuideTop = this.getRowGuidePosition(ref);
}
handleResizeStart() {
this.setState(() => ({
isResizing: true,
rowGuideTop,
}));
}
handleResize({ ref, direction }) {
if (direction === 'bottom' || direction === 'bottomRight') {
this.setState(() => ({ rowGuideTop: this.getRowGuidePosition(ref) }));
}
}
handleResizeStop({ id, widthMultiple: width, heightMultiple: height }) {
this.props.resizeComponent({ id, width, height });
this.setState(() => ({
isResizing: false,
rowGuideTop: null,
}));
}
@ -150,7 +183,7 @@ class DashboardGrid extends React.PureComponent {
(width + GRID_GUTTER_SIZE) / GRID_COLUMN_COUNT;
const columnWidth = columnPlusGutterWidth - GRID_GUTTER_SIZE;
const { isResizing, rowGuideTop } = this.state;
const { isResizing } = this.state;
const shouldDisplayEmptyState = gridComponent?.children?.length === 0;
const shouldDisplayTopLevelTabEmptyState =
@ -227,7 +260,7 @@ class DashboardGrid extends React.PureComponent {
</DashboardEmptyStateContainer>
)}
<div className="dashboard-grid" ref={this.setGridRef}>
<div className="grid-content" data-test="grid-content">
<GridContent className="grid-content" data-test="grid-content">
{/* make the area above components droppable */}
{editMode && (
<DragDroppable
@ -278,7 +311,7 @@ class DashboardGrid extends React.PureComponent {
Array(GRID_COLUMN_COUNT)
.fill(null)
.map((_, i) => (
<div
<GridColumnGuide
key={`grid-column-${i}`}
className="grid-column-guide"
style={{
@ -287,16 +320,7 @@ class DashboardGrid extends React.PureComponent {
}}
/>
))}
{isResizing && rowGuideTop && (
<div
className="grid-row-guide"
style={{
top: rowGuideTop,
width,
}}
/>
)}
</div>
</GridContent>
</div>
</>
);

View File

@ -76,13 +76,6 @@ describe('DashboardGrid', () => {
expect(wrapper.find('.grid-column-guide')).toHaveLength(GRID_COLUMN_COUNT);
});
it('should render a grid row guide when resizing', () => {
const wrapper = setup();
expect(wrapper.find('.grid-row-guide')).not.toExist();
wrapper.setState({ isResizing: true, rowGuideTop: 10 });
expect(wrapper.find('.grid-row-guide')).toExist();
});
it('should call resizeComponent when a child DashboardComponent calls resizeStop', () => {
const resizeComponent = sinon.spy();
const args = { id: 'id', widthMultiple: 1, heightMultiple: 3 };

View File

@ -25,7 +25,7 @@ import React, {
useRef,
useState,
} from 'react';
import { styled, t } from '@superset-ui/core';
import { css, styled, t } from '@superset-ui/core';
import { useUiConfig } from 'src/components/UiConfigContext';
import { Tooltip } from 'src/components/Tooltip';
import { useDispatch, useSelector } from 'react-redux';
@ -64,6 +64,69 @@ const CrossFilterIcon = styled(Icons.CursorTarget)`
width: 22px;
`;
const ChartHeaderStyles = styled.div`
${({ theme }) => css`
font-size: ${theme.typography.sizes.l}px;
font-weight: ${theme.typography.weights.bold};
margin-bottom: ${theme.gridUnit}px;
display: flex;
max-width: 100%;
align-items: flex-start;
min-height: 0;
& > .header-title {
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
flex-grow: 1;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
& > span.ant-tooltip-open {
display: inline;
}
}
& > .header-controls {
display: flex;
& > * {
margin-left: ${theme.gridUnit * 2}px;
}
}
.dropdown.btn-group {
pointer-events: none;
vertical-align: top;
& > * {
pointer-events: auto;
}
}
.dropdown-toggle.btn.btn-default {
background: none;
border: none;
box-shadow: none;
}
.dropdown-menu.dropdown-menu-right {
top: ${theme.gridUnit * 5}px;
}
.divider {
margin: ${theme.gridUnit}px 0;
}
.refresh-tooltip {
display: block;
height: ${theme.gridUnit * 4}px;
margin: ${theme.gridUnit}px 0;
color: ${theme.colors.text.label};
}
`}
`;
const SliceHeader: FC<SliceHeaderProps> = ({
innerRef = null,
forceRefresh = () => ({}),
@ -134,7 +197,7 @@ const SliceHeader: FC<SliceHeaderProps> = ({
const exploreUrl = `/explore/?dashboard_page_id=${dashboardPageId}&slice_id=${slice.slice_id}`;
return (
<div className="chart-header" data-test="slice-header" ref={innerRef}>
<ChartHeaderStyles data-test="slice-header" ref={innerRef}>
<div className="header-title" ref={headerRef}>
<Tooltip title={headerTooltip}>
<EditableTitle
@ -229,7 +292,7 @@ const SliceHeader: FC<SliceHeaderProps> = ({
</>
)}
</div>
</div>
</ChartHeaderStyles>
);
};

View File

@ -69,12 +69,20 @@ const MENU_KEYS = {
DRILL_TO_DETAIL: 'drill_to_detail',
};
// TODO: replace 3 dots with an icon
const VerticalDotsContainer = styled.div`
padding: ${({ theme }) => theme.gridUnit / 4}px
${({ theme }) => theme.gridUnit * 1.5}px;
.dot {
display: block;
height: ${({ theme }) => theme.gridUnit}px;
width: ${({ theme }) => theme.gridUnit}px;
border-radius: 50%;
margin: ${({ theme }) => theme.gridUnit / 2}px 0;
background-color: ${({ theme }) => theme.colors.text.label};
}
&:hover {

View File

@ -21,6 +21,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { DragSource, DropTarget } from 'react-dnd';
import cx from 'classnames';
import { css, styled } from '@superset-ui/core';
import { componentShape } from '../../util/propShapes';
import { dragConfig, dropConfig } from './dragDroppableConfig';
@ -73,6 +74,64 @@ const defaultProps = {
dragPreviewRef() {},
};
const DragDroppableStyles = styled.div`
${({ theme }) => css`
position: relative;
&.dragdroppable--dragging {
opacity: 0.2;
}
&.dragdroppable-row {
width: 100%;
}
&.dragdroppable-column .resizable-container span div {
z-index: 10;
}
& {
.drop-indicator {
display: block;
background-color: ${theme.colors.primary.base};
position: absolute;
z-index: 10;
}
.drop-indicator--top {
top: 0;
left: 0;
height: ${theme.gridUnit}px;
width: 100%;
min-width: ${theme.gridUnit * 4}px;
}
.drop-indicator--bottom {
top: 100%;
left: 0;
height: ${theme.gridUnit}px;
width: 100%;
min-width: ${theme.gridUnit * 4}px;
}
.drop-indicator--right {
top: 0;
left: 100%;
height: 100%;
width: ${theme.gridUnit}px;
min-height: ${theme.gridUnit * 4}px;
}
.drop-indicator--left {
top: 0;
left: 0;
height: 100%;
width: ${theme.gridUnit}px;
min-height: ${theme.gridUnit * 4}px;
}
}
`};
`;
// export unwrapped component for testing
export class UnwrappedDragDroppable extends React.PureComponent {
constructor(props) {
@ -141,7 +200,7 @@ export class UnwrappedDragDroppable extends React.PureComponent {
: {};
return (
<div
<DragDroppableStyles
style={style}
ref={this.setRef}
data-test="dragdroppable-object"
@ -154,7 +213,7 @@ export class UnwrappedDragDroppable extends React.PureComponent {
)}
>
{children(childProps)}
</div>
</DragDroppableStyles>
);
}
}

View File

@ -17,7 +17,10 @@
* under the License.
*/
import React from 'react';
import { shallow, mount } from 'enzyme';
import {
styledMount as mount,
styledShallow as shallow,
} from 'spec/helpers/theming';
import sinon from 'sinon';
import newComponentFactory from 'src/dashboard/util/newComponentFactory';

View File

@ -20,7 +20,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Button from 'src/components/Button';
import { t, styled } from '@superset-ui/core';
import { css, t, styled } from '@superset-ui/core';
import buildFilterScopeTreeEntry from 'src/dashboard/util/buildFilterScopeTreeEntry';
import getFilterScopeNodesTree from 'src/dashboard/util/getFilterScopeNodesTree';
@ -49,6 +49,268 @@ const propTypes = {
onCloseModal: PropTypes.func.isRequired,
};
const ScopeContainer = styled.div`
${({ theme }) => css`
display: flex;
flex-direction: column;
height: 80%;
margin-right: ${theme.gridUnit * -6}px;
font-size: ${theme.typography.sizes.m}px;
& .nav.nav-tabs {
border: none;
}
& .filter-scope-body {
flex: 1;
max-height: calc(100% - ${theme.gridUnit * 32}px);
.filter-field-pane,
.filter-scope-pane {
overflow-y: auto;
}
}
& .warning-message {
padding: ${theme.gridUnit * 6}px;
}
`}
`;
const ScopeBody = styled.div`
${({ theme }) => css`
&.filter-scope-body {
flex: 1;
max-height: calc(100% - ${theme.gridUnit * 32}px);
.filter-field-pane,
.filter-scope-pane {
overflow-y: auto;
}
}
`}
`;
const ScopeHeader = styled.div`
${({ theme }) => css`
height: ${theme.gridUnit * 16}px;
border-bottom: 1px solid ${theme.colors.grayscale.light2};
padding-left: ${theme.gridUnit * 6}px;
margin-left: ${theme.gridUnit * -6}px;
h4 {
margin-top: 0;
}
.selected-fields {
margin: ${theme.gridUnit * 3}px 0 ${theme.gridUnit * 4}px;
visibility: hidden;
&.multi-edit-mode {
visibility: visible;
}
.selected-scopes {
padding-left: ${theme.gridUnit}px;
}
}
`}
`;
const ScopeSelector = styled.div`
${({ theme }) => css`
&.filters-scope-selector {
display: flex;
flex-direction: row;
position: relative;
height: 100%;
a,
a:active,
a:hover {
color: inherit;
text-decoration: none;
}
.react-checkbox-tree .rct-icon.rct-icon-expand-all,
.react-checkbox-tree .rct-icon.rct-icon-collapse-all {
font-family: ${theme.typography.families.sansSerif};
font-size: ${theme.typography.sizes.m}px;
color: ${theme.colors.primary.base};
&::before {
content: '';
}
&:hover {
text-decoration: underline;
}
&:focus {
outline: none;
}
}
.filter-field-pane {
position: relative;
width: 40%;
padding: ${theme.gridUnit * 4}px;
padding-left: 0;
border-right: 1px solid ${theme.colors.grayscale.light2};
.filter-container label {
font-weight: ${theme.typography.weights.normal};
margin: 0 0 0 ${theme.gridUnit * 4}px;
word-break: break-all;
}
.filter-field-item {
height: ${theme.gridUnit * 9}px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 ${theme.gridUnit * 6}px;
margin-left: ${theme.gridUnit * -6}px;
&.is-selected {
border: 1px solid ${theme.colors.text.label};
border-radius: ${theme.borderRadius}px;
background-color: ${theme.colors.grayscale.light4};
margin-left: ${theme.gridUnit * -6}px;
}
}
.react-checkbox-tree {
.rct-title .root {
font-weight: ${theme.typography.weights.bold};
}
.rct-text {
height: ${theme.gridUnit * 10}px;
}
}
}
.filter-scope-pane {
position: relative;
flex: 1;
padding: ${theme.gridUnit * 4}px;
padding-right: ${theme.gridUnit * 6}px;
}
.react-checkbox-tree {
flex-direction: column;
color: ${theme.colors.grayscale.dark1};
font-size: ${theme.typography.sizes.m}px;
.filter-scope-type {
padding: ${theme.gridUnit * 2}px 0;
display: flex;
align-items: center;
&.chart {
font-weight: ${theme.typography.weights.normal};
}
&.selected-filter {
padding-left: ${theme.gridUnit * 7}px;
position: relative;
color: ${theme.colors.text.label};
&::before {
content: ' ';
position: absolute;
left: 0;
top: 50%;
width: ${theme.gridUnit * 4}px;
height: ${theme.gridUnit * 4}px;
border-radius: ${theme.borderRadius}px;
margin-top: ${theme.gridUnit * -2}px;
box-shadow: inset 0 0 0 2px ${theme.colors.grayscale.light2};
background: ${theme.colors.grayscale.light3};
}
}
&.root {
font-weight: ${theme.typography.weights.bold};
}
}
.rct-checkbox {
svg {
position: relative;
top: 3px;
width: ${theme.gridUnit * 4.5}px;
}
}
.rct-node-leaf {
.rct-bare-label {
&::before {
padding-left: ${theme.gridUnit}px;
}
}
}
.rct-options {
text-align: left;
margin-left: 0;
margin-bottom: ${theme.gridUnit * 2}px;
}
.rct-text {
margin: 0;
display: flex;
}
.rct-title {
display: block;
}
// disable style from react-checkbox-trees.css
.rct-node-clickable:hover,
.rct-node-clickable:focus,
label:hover,
label:active {
background: none !important;
}
}
.multi-edit-mode {
&.filter-scope-pane {
.rct-node.rct-node-leaf .filter-scope-type.filter_box {
display: none;
}
}
.filter-field-item {
padding: 0 ${theme.gridUnit * 4}px 0 ${theme.gridUnit * 12}px;
margin-left: ${theme.gridUnit * -12}px;
&.is-selected {
margin-left: ${theme.gridUnit * -13}px;
}
}
}
.scope-search {
position: absolute;
right: ${theme.gridUnit * 4}px;
top: ${theme.gridUnit * 4}px;
border-radius: ${theme.borderRadius}px;
border: 1px solid ${theme.colors.grayscale.light2};
padding: ${theme.gridUnit}px ${theme.gridUnit * 2}px;
font-size: ${theme.typography.sizes.m}px;
outline: none;
&:focus {
border: 1px solid ${theme.colors.primary.base};
}
}
}
`}
`;
const ActionsContainer = styled.div`
${({ theme }) => `
height: ${theme.gridUnit * 16}px;
@ -496,28 +758,28 @@ export default class FilterScopeSelector extends React.PureComponent {
const { showSelector } = this.state;
return (
<div className="filter-scope-container">
<div className="filter-scope-header">
<ScopeContainer>
<ScopeHeader>
<h4>{t('Configure filter scopes')}</h4>
{showSelector && this.renderEditingFiltersName()}
</div>
</ScopeHeader>
<div className="filter-scope-body">
<ScopeBody className="filter-scope-body">
{!showSelector ? (
<div className="warning-message">
{t('There are no filters in this dashboard.')}
</div>
) : (
<div className="filters-scope-selector">
<ScopeSelector className="filters-scope-selector">
<div className={cx('filter-field-pane multi-edit-mode')}>
{this.renderFilterFieldList()}
</div>
<div className="filter-scope-pane multi-edit-mode">
{this.renderFilterScopeTree()}
</div>
</div>
</ScopeSelector>
)}
</div>
</ScopeBody>
<ActionsContainer>
<Button buttonSize="small" onClick={this.onClose}>
@ -533,7 +795,7 @@ export default class FilterScopeSelector extends React.PureComponent {
</Button>
)}
</ActionsContainer>
</div>
</ScopeContainer>
);
}
}

View File

@ -114,6 +114,15 @@ const SHOULD_UPDATE_ON_PROP_CHANGES = Object.keys(propTypes).filter(
const OVERFLOWABLE_VIZ_TYPES = new Set(['filter_box']);
const DEFAULT_HEADER_HEIGHT = 22;
const ChartWrapper = styled.div`
overflow: hidden;
position: relative;
&.dashboard-chart--overflowable {
overflow: visible;
}
`;
const ChartOverlay = styled.div`
position: absolute;
top: 0;
@ -486,7 +495,7 @@ class Chart extends React.Component {
/>
)}
<div
<ChartWrapper
className={cx(
'dashboard-chart',
isOverflowable && 'dashboard-chart--overflowable',
@ -530,7 +539,7 @@ class Chart extends React.Component {
datasetsStatus={datasetsStatus}
isInView={isInView}
/>
</div>
</ChartWrapper>
</SliceContainer>
);
}

View File

@ -20,7 +20,7 @@ import React, { useState, useMemo, useCallback, useEffect } from 'react';
import { ResizeCallback, ResizeStartCallback } from 're-resizable';
import cx from 'classnames';
import { useSelector } from 'react-redux';
import { css } from '@superset-ui/core';
import { LayoutItem, RootState } from 'src/dashboard/types';
import AnchorLink from 'src/dashboard/components/AnchorLink';
import Chart from 'src/dashboard/containers/Chart';
@ -69,6 +69,15 @@ interface ChartHolderProps {
isInView: boolean;
}
const fullSizeStyle = css`
&& {
position: fixed;
z-index: 3000;
left: 0;
top: 0;
}
`;
const ChartHolder: React.FC<ChartHolderProps> = ({
id,
parentId,
@ -265,13 +274,13 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
ref={dragSourceRef}
data-test="dashboard-component-chart-holder"
style={focusHighlightStyles}
css={isFullSize ? fullSizeStyle : undefined}
className={cx(
'dashboard-component',
'dashboard-component-chart-holder',
// The following class is added to support custom dashboard styling via the CSS editor
`dashboard-chart-id-${chartId}`,
outlinedComponentId ? 'fade-in' : 'fade-out',
isFullSize && 'full-size',
)}
>
{!editMode && (

View File

@ -19,6 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { css, styled, t } from '@superset-ui/core';
import Icons from 'src/components/Icons';
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton';
@ -58,6 +59,46 @@ const propTypes = {
const defaultProps = {};
const ColumnStyles = styled.div`
${({ theme }) => css`
&.grid-column {
width: 100%;
position: relative;
& > :not(.hover-menu):not(:last-child) {
margin-bottom: ${theme.gridUnit * 4}px;
}
}
.dashboard--editing &:after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1;
pointer-events: none;
border: 1px dashed ${theme.colors.grayscale.light2};
}
.dashboard--editing .resizable-container--resizing:hover > &:after,
.dashboard--editing .hover-menu:hover + &:after {
border: 1px dashed ${theme.colors.primary.base};
z-index: 2;
}
`}
`;
const emptyColumnContentStyles = theme => css`
min-height: ${theme.gridUnit * 25}px;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: ${theme.colors.text.label};
`;
class Column extends React.PureComponent {
constructor(props) {
super(props);
@ -172,32 +213,32 @@ class Column extends React.PureComponent {
/>
</HoverMenu>
)}
<div
className={cx(
'grid-column',
columnItems.length === 0 && 'grid-column--empty',
backgroundStyle.className,
)}
<ColumnStyles
className={cx('grid-column', backgroundStyle.className)}
>
{columnItems.map((componentId, itemIndex) => (
<DashboardComponent
key={componentId}
id={componentId}
parentId={columnComponent.id}
depth={depth + 1}
index={itemIndex}
availableColumnCount={columnComponent.meta.width}
columnWidth={columnWidth}
onResizeStart={onResizeStart}
onResize={onResize}
onResizeStop={onResizeStop}
isComponentVisible={isComponentVisible}
onChangeTab={onChangeTab}
/>
))}
{columnItems.length === 0 ? (
<div css={emptyColumnContentStyles}>{t('Empty column')}</div>
) : (
columnItems.map((componentId, itemIndex) => (
<DashboardComponent
key={componentId}
id={componentId}
parentId={columnComponent.id}
depth={depth + 1}
index={itemIndex}
availableColumnCount={columnComponent.meta.width}
columnWidth={columnWidth}
onResizeStart={onResizeStart}
onResize={onResize}
onResizeStop={onResizeStop}
isComponentVisible={isComponentVisible}
onChangeTab={onChangeTab}
/>
))
)}
{dropIndicatorProps && <div {...dropIndicatorProps} />}
</div>
</ColumnStyles>
</WithPopoverMenu>
</ResizableContainer>
)}

View File

@ -18,6 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { css, styled } from '@superset-ui/core';
import DragDroppable from '../dnd/DragDroppable';
import HoverMenu from '../menu/HoverMenu';
@ -36,6 +37,31 @@ const propTypes = {
deleteComponent: PropTypes.func.isRequired,
};
const DividerLine = styled.div`
${({ theme }) => css`
width: 100%;
padding: ${theme.gridUnit * 2}px 0; /* this is padding not margin to enable a larger mouse target */
background-color: transparent;
&:after {
content: '';
height: 1px;
width: 100%;
background-color: ${theme.colors.grayscale.light2};
display: block;
}
div[draggable='true'] & {
cursor: move;
}
.dashboard-component-tabs & {
padding-left: ${theme.gridUnit * 4}px;
padding-right: ${theme.gridUnit * 4}px;
}
`}
`;
class Divider extends React.PureComponent {
constructor(props) {
super(props);
@ -75,7 +101,7 @@ class Divider extends React.PureComponent {
</HoverMenu>
)}
<div className="dashboard-component dashboard-component-divider" />
<DividerLine className="dashboard-component dashboard-component-divider" />
{dropIndicatorProps && <div {...dropIndicatorProps} />}
</div>

View File

@ -19,6 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { css, styled } from '@superset-ui/core';
import PopoverDropdown from 'src/components/PopoverDropdown';
import EditableTitle from 'src/components/EditableTitle';
@ -55,6 +56,64 @@ const propTypes = {
const defaultProps = {};
const HeaderStyles = styled.div`
${({ theme }) => css`
font-weight: ${theme.typography.weights.bold};
width: 100%;
padding: ${theme.gridUnit * 4}px 0;
&.header-small {
font-size: ${theme.typography.sizes.l}px;
}
&.header-medium {
font-size: ${theme.typography.sizes.xl}px;
}
&.header-large {
font-size: ${theme.typography.sizes.xxl}px;
}
.dashboard--editing .dashboard-grid & {
&:after {
border: 1px dashed transparent;
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1;
pointer-events: none;
}
&:hover:after {
border: 1px dashed ${theme.colors.primary.base};
z-index: 2;
}
}
.dashboard--editing .dragdroppable-row & {
cursor: move;
}
/**
* grids add margin between items, so don't double pad within columns
* we'll not worry about double padding on top as it can serve as a visual separator
*/
.grid-column > :not(:last-child) & {
margin-bottom: ${theme.gridUnit * -4}px;
}
.background--white &,
&.background--white,
.dashboard-component-tabs & {
padding-left: ${theme.gridUnit * 4}px;
padding-right: ${theme.gridUnit * 4}px;
}
`}
`;
class Header extends React.PureComponent {
constructor(props) {
super(props);
@ -154,7 +213,7 @@ class Header extends React.PureComponent {
]}
editMode={editMode}
>
<div
<HeaderStyles
className={cx(
'dashboard-component',
'dashboard-component-header',
@ -178,7 +237,7 @@ class Header extends React.PureComponent {
{!editMode && (
<AnchorLink id={component.id} dashboardId={dashboardId} />
)}
</div>
</HeaderStyles>
</WithPopoverMenu>
{dropIndicatorProps && <div {...dropIndicatorProps} />}

View File

@ -21,7 +21,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import cx from 'classnames';
import { t, SafeMarkdown } from '@superset-ui/core';
import { css, styled, t, SafeMarkdown } from '@superset-ui/core';
import { Logger, LOG_ACTIONS_RENDER_CHART } from 'src/logger/LogUtils';
import { MarkdownEditor } from 'src/components/AsyncAceEditor';
@ -83,6 +83,37 @@ Click here to learn more about [markdown formatting](https://bit.ly/1dQOfRK)`;
const MARKDOWN_ERROR_MESSAGE = t('This markdown component has an error.');
const MarkdownStyles = styled.div`
${({ theme }) => css`
&.dashboard-markdown {
overflow: hidden;
h4,
h5,
h6 {
font-weight: ${theme.typography.weights.normal};
}
h5 {
color: ${theme.colors.grayscale.base};
}
h6 {
font-size: ${theme.typography.sizes.s}px;
}
.dashboard-component-chart-holder {
overflow-y: auto;
overflow-x: hidden;
}
.dashboard--editing & {
cursor: move;
}
}
`}
`;
class Markdown extends React.PureComponent {
constructor(props) {
super(props);
@ -322,7 +353,7 @@ class Markdown extends React.PureComponent {
]}
editMode={editMode}
>
<div
<MarkdownStyles
data-test="dashboard-markdown-editor"
className={cx(
'dashboard-markdown',
@ -363,7 +394,7 @@ class Markdown extends React.PureComponent {
: this.renderPreviewMode()}
</div>
</ResizableContainer>
</div>
</MarkdownStyles>
{dropIndicatorProps && <div {...dropIndicatorProps} />}
</WithPopoverMenu>
)}

View File

@ -19,7 +19,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core';
import {
css,
FeatureFlag,
isFeatureEnabled,
styled,
t,
} from '@superset-ui/core';
import DragDroppable from 'src/dashboard/components/dnd/DragDroppable';
import DragHandle from 'src/dashboard/components/dnd/DragHandle';
@ -58,6 +64,36 @@ const propTypes = {
updateComponents: PropTypes.func.isRequired,
};
const GridRow = styled.div`
${({ theme }) => css`
position: relative;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: flex-start;
width: 100%;
height: fit-content;
& > :not(:last-child):not(.hover-menu) {
margin-right: ${theme.gridUnit * 4}px;
}
&.grid-row--empty {
min-height: ${theme.gridUnit * 25}px;
}
`}
`;
const emptyRowContentStyles = theme => css`
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: ${theme.colors.text.label};
`;
class Row extends React.PureComponent {
constructor(props) {
super(props);
@ -201,7 +237,7 @@ class Row extends React.PureComponent {
/>
</HoverMenu>
)}
<div
<GridRow
className={cx(
'grid-row',
rowItems.length === 0 && 'grid-row--empty',
@ -210,28 +246,32 @@ class Row extends React.PureComponent {
data-test={`grid-row-${backgroundStyle.className}`}
ref={this.containerRef}
>
{rowItems.map((componentId, itemIndex) => (
<DashboardComponent
key={componentId}
id={componentId}
parentId={rowComponent.id}
depth={depth + 1}
index={itemIndex}
availableColumnCount={
availableColumnCount - occupiedColumnCount
}
columnWidth={columnWidth}
onResizeStart={onResizeStart}
onResize={onResize}
onResizeStop={onResizeStop}
isComponentVisible={isComponentVisible}
onChangeTab={onChangeTab}
isInView={this.state.isInView}
/>
))}
{rowItems.length === 0 ? (
<div css={emptyRowContentStyles}>{t('Empty row')}</div>
) : (
rowItems.map((componentId, itemIndex) => (
<DashboardComponent
key={componentId}
id={componentId}
parentId={rowComponent.id}
depth={depth + 1}
index={itemIndex}
availableColumnCount={
availableColumnCount - occupiedColumnCount
}
columnWidth={columnWidth}
onResizeStart={onResizeStart}
onResize={onResize}
onResizeStop={onResizeStop}
isComponentVisible={isComponentVisible}
onChangeTab={onChangeTab}
isInView={this.state.isInView}
/>
))
)}
{dropIndicatorProps && <div {...dropIndicatorProps} />}
</div>
</GridRow>
</WithPopoverMenu>
)}
</DragDroppable>

View File

@ -19,10 +19,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { css, styled } from '@superset-ui/core';
import DragDroppable from '../../dnd/DragDroppable';
import { NEW_COMPONENTS_SOURCE_ID } from '../../../util/constants';
import { NEW_COMPONENT_SOURCE_TYPE } from '../../../util/componentTypes';
import DragDroppable from 'src/dashboard/components/dnd/DragDroppable';
import { NEW_COMPONENTS_SOURCE_ID } from 'src/dashboard/util/constants';
import { NEW_COMPONENT_SOURCE_TYPE } from 'src/dashboard/util/componentTypes';
const propTypes = {
id: PropTypes.string.isRequired,
@ -35,6 +36,53 @@ const defaultProps = {
className: null,
};
const NewComponent = styled.div`
${({ theme }) => css`
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
padding: ${theme.gridUnit * 4}px;
background: ${theme.colors.grayscale.light5};
cursor: move;
&:not(.static):hover {
background: ${theme.colors.grayscale.light4};
}
`}
`;
const NewComponentPlaceholder = styled.div`
${({ theme }) => css`
position: relative;
background: ${theme.colors.grayscale.light4};
width: ${theme.gridUnit * 10}px;
height: ${theme.gridUnit * 10}px;
margin-right: ${theme.gridUnit * 4}px;
border: 1px solid ${theme.colors.grayscale.light5};
display: flex;
align-items: center;
justify-content: center;
color: ${theme.colors.text.label};
font-size: ${theme.typography.sizes.xxl}px;
&.fa-window-restore {
font-size: ${theme.typography.sizes.l}px;
}
&.fa-area-chart {
font-size: ${theme.typography.sizes.xl}px;
}
&.divider-placeholder:after {
content: '';
height: 2px;
width: 100%;
background-color: ${theme.colors.grayscale.light2};
}
`}
`;
export default class DraggableNewComponent extends React.PureComponent {
render() {
const { label, id, type, className, meta } = this.props;
@ -50,14 +98,12 @@ export default class DraggableNewComponent extends React.PureComponent {
editMode
>
{({ dragSourceRef }) => (
<div
ref={dragSourceRef}
className="new-component"
data-test="new-component"
>
<div className={cx('new-component-placeholder', className)} />
<NewComponent ref={dragSourceRef} data-test="new-component">
<NewComponentPlaceholder
className={cx('new-component-placeholder', className)}
/>
{label}
</div>
</NewComponent>
)}
</DragDroppable>
);

View File

@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
import { mount } from 'enzyme';
import { styledMount as mount } from 'spec/helpers/theming';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
@ -73,7 +73,9 @@ describe('DraggableNewComponent', () => {
it('should render the passed label', () => {
const wrapper = setup();
expect(wrapper.find('.new-component').text()).toBe(props.label);
expect(
wrapper.find('[data-test="new-component"]').at(0).childAt(0).text(),
).toBe(props.label);
});
it('should add the passed className', () => {

View File

@ -18,6 +18,7 @@
*/
import React from 'react';
import cx from 'classnames';
import { css, styled } from '@superset-ui/core';
import backgroundStyleOptions from 'src/dashboard/util/backgroundStyleOptions';
import PopoverDropdown, {
@ -31,19 +32,63 @@ interface BackgroundStyleDropdownProps {
onChange: OnChangeHandler;
}
const BackgroundStyleOption = styled.div`
${({ theme }) => css`
display: inline-block;
&:before {
content: '';
width: 1em;
height: 1em;
margin-right: ${theme.gridUnit * 2}px;
display: inline-block;
vertical-align: middle;
}
&.background--white {
padding-left: 0;
background: transparent;
&:before {
background: ${theme.colors.grayscale.light5};
border: 1px solid ${theme.colors.grayscale.light2};
}
}
/* Create the transparent rect icon */
&.background--transparent:before {
background-image: linear-gradient(
45deg,
${theme.colors.text.label} 25%,
transparent 25%
),
linear-gradient(-45deg, ${theme.colors.text.label} 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, ${theme.colors.text.label} 75%),
linear-gradient(-45deg, transparent 75%, ${theme.colors.text.label} 75%);
background-size: ${theme.gridUnit * 2}px ${theme.gridUnit * 2}px;
background-position: 0 0, 0 ${theme.gridUnit}px,
${theme.gridUnit}px ${-theme.gridUnit}px, ${-theme.gridUnit}px 0px;
}
`}
`;
function renderButton(option: OptionProps) {
return (
<div className={cx('background-style-option', option.className)}>
<BackgroundStyleOption
className={cx('background-style-option', option.className)}
>
{`${option.label} background`}
</div>
</BackgroundStyleOption>
);
}
function renderOption(option: OptionProps) {
return (
<div className={cx('background-style-option', option.className)}>
<BackgroundStyleOption
className={cx('background-style-option', option.className)}
>
{option.label}
</div>
</BackgroundStyleOption>
);
}

View File

@ -18,6 +18,7 @@
*/
import React from 'react';
import cx from 'classnames';
import { addAlpha, css, styled } from '@superset-ui/core';
type ShouldFocusContainer = HTMLDivElement & {
contains: (event_target: EventTarget & HTMLElement) => Boolean;
@ -41,6 +42,67 @@ interface WithPopoverMenuState {
isFocused: Boolean;
}
const WithPopoverMenuStyles = styled.div`
${({ theme }) => css`
position: relative;
outline: none;
&.with-popover-menu--focused:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 2px solid ${theme.colors.primary.base};
pointer-events: none;
}
.dashboard-component-tabs li &.with-popover-menu--focused:after {
top: ${theme.gridUnit * -3}px;
left: ${theme.gridUnit * -2}px;
width: calc(100% + ${theme.gridUnit * 4}px);
height: calc(100% + ${theme.gridUnit * 7}px);
}
`}
`;
const PopoverMenuStyles = styled.div`
${({ theme }) => css`
position: absolute;
flex-wrap: nowrap;
left: 1px;
top: -42px;
height: ${theme.gridUnit * 10}px;
padding: 0 ${theme.gridUnit * 4}px;
background: ${theme.colors.grayscale.light5};
box-shadow: 0 1px 2px 1px
${addAlpha(
theme.colors.grayscale.dark2,
parseFloat(theme.opacity.mediumLight) / 100,
)};
font-size: ${theme.typography.sizes.m}px;
cursor: default;
z-index: 3000;
&,
.menu-item {
display: flex;
flex-direction: row;
align-items: center;
}
/* vertical spacer after each menu item */
.menu-item:not(:last-child):after {
content: '';
width: 1px;
height: 100%;
background: ${theme.colors.grayscale.light2};
margin: 0 ${theme.gridUnit * 4}px;
}
`}
`;
export default class WithPopoverMenu extends React.PureComponent<
WithPopoverMenuProps,
WithPopoverMenuState
@ -126,7 +188,7 @@ export default class WithPopoverMenu extends React.PureComponent<
const { isFocused } = this.state;
return (
<div
<WithPopoverMenuStyles
ref={this.setRef}
onClick={this.handleClick}
role="none"
@ -138,15 +200,15 @@ export default class WithPopoverMenu extends React.PureComponent<
>
{children}
{editMode && isFocused && (menuItems?.length ?? 0) > 0 && (
<div className="popover-menu">
<PopoverMenuStyles>
{menuItems.map((node: React.ReactNode, i: Number) => (
<div className="menu-item" key={`menu-item-${i}`}>
{node}
</div>
))}
</div>
</PopoverMenuStyles>
)}
</div>
</WithPopoverMenuStyles>
);
}
}

View File

@ -21,7 +21,6 @@ import React from 'react';
import { css } from '@emotion/react';
import { FilterBarOrientation } from 'src/dashboard/types';
import FilterDivider from './FilterDivider';
import 'src/dashboard/stylesheets/index.less';
import { FilterDividerProps } from './types';
export default {

View File

@ -20,6 +20,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Resizable } from 're-resizable';
import cx from 'classnames';
import { css, styled } from '@superset-ui/core';
import ResizableHandle from './ResizableHandle';
import resizableConfig from '../../util/resizableConfig';
@ -80,6 +81,93 @@ const HANDLE_CLASSES = {
right: 'resizable-container-handle--right',
bottom: 'resizable-container-handle--bottom',
};
const StyledResizable = styled(Resizable)`
${({ theme }) => css`
&.resizable-container {
background-color: transparent;
position: relative;
/* re-resizable sets an empty div to 100% width and height, which doesn't
play well with many 100% height containers we need */
& ~ div {
width: auto !important;
height: auto !important;
}
}
&.resizable-container--resizing {
/* after ensures border visibility on top of any children */
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: inset 0 0 0 2px ${theme.colors.primary.base};
}
& > span .resize-handle {
border-color: ${theme.colors.primary.base};
}
}
.resize-handle {
opacity: 0;
z-index: 10;
&--bottom-right {
position: absolute;
border-right: 1px solid ${theme.colors.text.label};
border-bottom: 1px solid ${theme.colors.text.label};
right: ${theme.gridUnit * 4}px;
bottom: ${theme.gridUnit * 4}px;
width: ${theme.gridUnit * 2}px;
height: ${theme.gridUnit * 2}px;
}
&--right {
width: ${theme.gridUnit / 2}px;
height: ${theme.gridUnit * 5}px;
right: ${theme.gridUnit}px;
top: 50%;
transform: translate(0, -50%);
position: absolute;
border-left: 1px solid ${theme.colors.text.label};
border-right: 1px solid ${theme.colors.text.label};
}
&--bottom {
height: ${theme.gridUnit / 2}px;
width: ${theme.gridUnit * 5}px;
bottom: ${theme.gridUnit}px;
left: 50%;
transform: translate(-50%);
position: absolute;
border-top: 1px solid ${theme.colors.text.label};
border-bottom: 1px solid ${theme.colors.text.label};
}
}
`}
&.resizable-container:hover .resize-handle,
&.resizable-container--resizing .resize-handle {
opacity: 1;
}
.dragdroppable-column & .resizable-container-handle--right {
/* override the default because the inner column's handle's mouse target is very small */
right: 0 !important;
}
& .resizable-container-handle--bottom {
bottom: 0 !important;
}
`;
class ResizableContainer extends React.PureComponent {
constructor(props) {
super(props);
@ -186,7 +274,7 @@ class ResizableContainer extends React.PureComponent {
const { isResizing } = this.state;
return (
<Resizable
<StyledResizable
enable={enableConfig}
grid={SNAP_TO_GRID}
minWidth={
@ -228,7 +316,7 @@ class ResizableContainer extends React.PureComponent {
handleClasses={HANDLE_CLASSES}
>
{children}
</Resizable>
</StyledResizable>
);
}
}

View File

@ -63,7 +63,7 @@ import {
getFilterValue,
getPermalinkValue,
} from 'src/dashboard/components/nativeFilters/FilterBar/keyValue';
import { filterCardPopoverStyle } from 'src/dashboard/styles';
import { filterCardPopoverStyle, headerStyles } from 'src/dashboard/styles';
import { DashboardContextForExplore } from 'src/types/DashboardContextForExplore';
import shortid from 'shortid';
import { RootState } from '../types';
@ -365,7 +365,7 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: PageProps) => {
return (
<>
<Global styles={filterCardPopoverStyle(theme)} />
<Global styles={[filterCardPopoverStyle(theme), headerStyles(theme)]} />
<FilterBoxMigrationModal
show={filterboxMigrationState === FILTER_BOX_MIGRATION_STATES.UNDECIDED}
hideFooter={!isMigrationEnabled}

View File

@ -18,6 +18,39 @@
*/
import { css, SupersetTheme } from '@superset-ui/core';
export const headerStyles = (theme: SupersetTheme) => css`
body {
h1 {
font-weight: ${theme.typography.weights.bold};
line-height: 1.4;
font-size: ${theme.typography.sizes.xxl}px;
letter-spacing: -0.2px;
margin-top: ${theme.gridUnit * 3}px;
margin-bottom: ${theme.gridUnit * 3}px;
}
h2 {
font-weight: ${theme.typography.weights.bold};
line-height: 1.4;
font-size: ${theme.typography.sizes.xl}px;
margin-top: ${theme.gridUnit * 3}px;
margin-bottom: ${theme.gridUnit * 2}px;
}
h3,
h4,
h5,
h6 {
font-weight: ${theme.typography.weights.bold};
line-height: 1.4;
font-size: ${theme.typography.sizes.l}px;
letter-spacing: 0.2px;
margin-top: ${theme.gridUnit * 2}px;
margin-bottom: ${theme.gridUnit}px;
}
}
`;
export const filterCardPopoverStyle = (theme: SupersetTheme) => css`
.filter-card-popover {
width: 240px;

View File

@ -1,49 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.dashboard {
position: relative;
color: @almost-black;
flex-grow: 1;
display: flex;
flex-direction: column;
height: 100%;
}
/* only top-level tabs have popover, give it more padding to match header + tabs */
.dashboard > .with-popover-menu > .popover-menu {
left: 24px;
}
/* drop shadow for top-level tabs only */
.dashboard .dashboard-component-tabs {
box-shadow: 0 4px 4px 0 fade(@darkest, @opacity-light);
padding-left: 8px; /* note this is added to tab-level padding, to match header */
}
.dropdown-toggle.btn.btn-primary .caret {
color: @lightest;
}
.background--transparent {
background-color: transparent;
}
.background--white {
background-color: @lightest;
}

View File

@ -1,150 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.dashboard-component-chart-holder {
width: 100%;
height: 100%;
color: @gray-dark;
background-color: @lightest;
position: relative;
padding: 16px;
overflow-y: visible;
// transitionable traits for when a filter is being actively focused
transition: opacity 0.2s, border-color 0.2s, box-shadow 0.2s;
border: 2px solid transparent;
.missing-chart-container {
display: flex;
flex-direction: column;
align-items: center;
overflow-y: auto;
justify-content: center;
.missing-chart-body {
font-size: @font-size-s;
position: relative;
display: flex;
}
}
&.fade-in {
border-radius: @border-radius-large;
box-shadow: inset 0 0 0 2px @shadow-highlight,
0 0 0 3px fade(@shadow-highlight, @opacity-light);
transition: box-shadow 0.2s ease-in-out, opacity 0.2s ease-in-out,
border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
&.fade-out {
border-radius: @border-radius-large;
box-shadow: none;
transition: box-shadow 0.2s ease-in-out, opacity 0.2s ease-in-out,
border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
}
.dashboard-chart {
overflow: hidden;
position: relative;
}
.dashboard-chart.dashboard-chart--overflowable {
overflow: visible;
}
.dashboard--editing {
.dashboard-component-chart-holder {
&:after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
z-index: @z-index-chart;
pointer-events: none;
border: 1px solid transparent;
}
&:hover:after {
border: 1px dashed @indicator-color;
z-index: @z-index-chart--dragging;
}
}
.resizable-container {
&:hover,
&.resizable-container--resizing:hover {
& > .dashboard-component-chart-holder:after {
border: 1px dashed @indicator-color;
}
}
}
.resizable-container .dashboard-component-chart-holder {
.dashboard-chart {
.chart-container {
cursor: move;
opacity: 0.2;
}
.slice_container {
/* disable chart interactions in edit mode */
pointer-events: none;
}
}
&:hover .dashboard-chart .chart-container {
opacity: 0.7;
}
}
}
.dot {
@dot-diameter: 4px;
height: @dot-diameter;
width: @dot-diameter;
border-radius: @dot-diameter / 2;
margin: @dot-diameter / 2 0;
background-color: @gray;
display: inline-block;
a[role='menuitem'] & {
width: 8px;
height: 8px;
margin-right: 8px;
}
}
.time-filter-tabs > .nav-tabs {
margin-bottom: 8px;
}
.time-filter-tabs > .nav-tabs > li > a {
padding: 4px;
}
.full-size {
position: fixed;
z-index: @z-index-max;
left: 0;
top: 0;
}

View File

@ -1,64 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.grid-column {
width: 100%;
position: relative;
}
/* gutters between elements in a column */
.grid-column > :not(:only-child):not(.hover-menu):not(:last-child) {
margin-bottom: 16px;
}
.dashboard--editing {
.grid-column:after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: @z-index-chart;
pointer-events: none;
border: 1px dashed @gray-light;
}
.resizable-container.resizable-container--resizing:hover > .grid-column:after,
.hover-menu:hover + .grid-column:after {
border: 1px dashed @indicator-color;
z-index: @z-index-chart--dragging;
}
}
.grid-column--empty {
min-height: 100px;
&:before {
content: 'Empty column';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: @gray-light;
}
}

View File

@ -1,42 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.dashboard-component-divider {
width: 100%;
padding: 8px 0; /* this is padding not margin to enable a larger mouse target */
background-color: transparent;
}
.dashboard-component-divider:after {
content: '';
height: 1px;
width: 100%;
background-color: @gray-light;
display: block;
}
.new-component-placeholder.divider-placeholder:after {
content: '';
height: 2px;
width: 100%;
background-color: @gray-light;
}
.dragdroppable .dashboard-component-divider {
cursor: move;
}

View File

@ -1,87 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.dashboard-component-header {
width: 100%;
font-weight: @font-weight-bold;
padding: 16px 0;
color: @almost-black;
}
.dashboard--editing {
.dashboard-grid {
.dashboard-component-header {
&:after {
border: 1px dashed transparent;
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: @z-index-chart;
pointer-events: none;
}
&:hover:after {
border: 1px dashed @indicator-color;
z-index: @z-index-chart--dragging;
}
}
}
.dragdroppable-row .dashboard-component-header {
cursor: move;
}
}
.header-style-option {
font-weight: @font-weight-bold;
color: @almost-black;
}
.dashboard--editing
/* note: sizes should be a multiple of the 8px grid unit so that rows in the grid align */
.header-small {
font-size: @font-size-l;
}
.header-medium {
font-size: @font-size-xl;
}
.header-large {
font-size: @font-size-xxl;
}
.background--white .dashboard-component-header,
.dashboard-component-header.background--white,
.dashboard-component-tabs .dashboard-component-header,
.dashboard-component-tabs .dashboard-component-divider {
padding-left: 16px;
padding-right: 16px;
}
/*
* grids add margin between items, so don't double pad within columns
* we'll not worry about double padding on top as it can serve as a visual separator
*/
.grid-column > :not(:only-child):not(:last-child) .dashboard-component-header {
margin-bottom: -16px;
}

View File

@ -1,25 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
@import './chart.less';
@import './column.less';
@import './divider.less';
@import './header.less';
@import './new-component.less';
@import './row.less';
@import './markdown.less';

View File

@ -1,57 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.dashboard-markdown {
overflow: hidden;
h4,
h5,
h6 {
font-weight: @font-weight-normal;
}
h5 {
color: @gray-heading;
}
h6 {
font-size: @font-size-s;
}
.dashboard-component-chart-holder {
overflow-y: auto;
overflow-x: hidden;
}
.dashboard--editing & {
cursor: move;
}
#ace-editor {
border: none;
}
}
/* maximize editing space */
.dashboard-markdown--editing {
.dashboard-component-chart-holder {
.with-popover-menu--focused & {
padding: 1px;
}
}
}

View File

@ -1,55 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
@import '../../../assets/stylesheets/less/variables.less';
.new-component {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
padding: 16px;
background: @lightest;
cursor: move;
&:not(.static):hover {
background: @gray-bg;
}
}
.new-component-placeholder {
position: relative;
background: @gray-bg;
width: 40px;
height: 40px;
margin-right: 16px;
border: 1px solid @lightest;
display: flex;
align-items: center;
justify-content: center;
color: @gray;
font-size: @font-size-xxl;
&.fa-window-restore {
font-size: @font-size-l;
}
&.fa-area-chart {
font-size: @font-size-xl;
}
}

View File

@ -1,92 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.grid-row {
position: relative;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: flex-start;
width: 100%;
height: fit-content;
}
/* gutters between elements in a row */
.grid-row > :not(:only-child):not(:last-child):not(.hover-menu) {
margin-right: 16px;
}
/* hover indicator */
.dashboard--editing {
.grid-row:after,
.dashboard-component-tabs > .hover-menu:hover + div:after {
border: 1px dashed transparent;
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: @z-index-chart;
pointer-events: none;
}
.resizable-container.resizable-container--resizing:hover > .grid-row:after,
.hover-menu:hover + .grid-row:after,
.dashboard-component-tabs > .hover-menu:hover + div:after {
border: 1px dashed @indicator-color;
z-index: @z-index-chart--dragging;
}
.grid-row:after,
.dashboard-component-tabs > .hover-menu + div:after {
border: 1px dashed @gray-light;
}
/* provide hit area in case row contents is edge to edge */
.dashboard-component-tabs-content {
.dragdroppable-row {
padding-top: 16px;
}
}
}
/* gutters between rows within tab */
.dashboard-component-tabs-content
> div:not(:only-child):not(:last-child):not(.empty-droptarget) {
margin-bottom: 16px;
}
.grid-row.grid-row--empty {
/* this centers the empty note content */
align-items: center;
height: 100px;
&:before {
position: absolute;
top: 0;
left: 0;
content: 'Empty row';
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
color: @gray;
}
}

View File

@ -1,164 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* header has mysterious extra margin */
header.top {
margin-bottom: 2px;
z-index: 10;
}
body {
h1 {
font-weight: @font-weight-bold;
line-height: @line-height-base;
font-size: @font-size-xxl;
letter-spacing: -0.2px;
margin-top: 12px;
margin-bottom: 12px;
}
h2 {
font-weight: @font-weight-bold;
line-height: @line-height-base;
font-size: @font-size-xl;
margin-top: 12px;
margin-bottom: 8px;
}
h3,
h4,
h5,
h6 {
font-weight: @font-weight-bold;
line-height: @line-height-base;
font-size: @font-size-l;
letter-spacing: 0.2px;
margin-top: 8px;
margin-bottom: 4px;
}
p {
margin: 0 0 8px 0;
}
}
.dashboard .chart-header {
font-size: @font-size-l;
font-weight: @font-weight-bold;
margin-bottom: 4px;
display: flex;
max-width: 100%;
align-items: flex-start;
min-height: 0;
& > .header-title {
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
flex-grow: 1;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
& > span.ant-tooltip-open {
display: inline;
}
}
& > .header-controls {
display: flex;
& > * {
margin-left: 8px;
}
}
.dropdown.btn-group {
pointer-events: none;
vertical-align: top;
& > * {
pointer-events: auto;
}
}
.dropdown-toggle.btn.btn-default {
background: none;
border: none;
box-shadow: none;
}
.dropdown-menu.dropdown-menu-right {
top: 20px;
}
.divider {
margin: 5px 0;
}
.refresh-tooltip {
display: block;
height: 16px;
margin: 3px 0;
color: @gray;
}
}
.dashboard .chart-header,
.dashboard .dashboard-header {
.dropdown-menu {
padding: 9px 0;
}
.dropdown-menu li {
font-weight: @font-weight-normal;
}
}
.react-bs-container-body {
max-height: 400px;
overflow-y: auto;
}
.hidden,
#pageDropDown {
display: none;
}
.separator .chart-container {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.dashboard .title {
margin: 0 20px;
}
.slice_container .alert {
margin: 10px;
}
i.danger {
color: @danger;
}
i.warning {
color: @warning;
}

View File

@ -1,130 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.dragdroppable {
position: relative;
}
// Fixes ISSUE-12181 - before in chart's contract-trigger breaks drag and drop mode
.dashboard--editing {
.contract-trigger:before {
display: none;
}
}
.dragdroppable--dragging {
opacity: 0.2;
}
.dragdroppable-row {
width: 100%;
}
.dragdroppable-column {
.resizable-container {
span {
div {
z-index: @z-index-above-dashboard-charts;
}
}
}
}
/* drop indicators */
.drop-indicator {
display: block;
background-color: @indicator-color;
position: absolute;
z-index: @z-index-above-dashboard-charts;
}
.drop-indicator--top {
top: 0;
left: 0;
height: 4px;
width: 100%;
min-width: 16px;
}
.drop-indicator--bottom {
top: 100%;
left: 0;
height: 4px;
width: 100%;
min-width: 16px;
}
.empty-droptarget:first-child {
.drop-indicator--bottom {
top: 24px;
}
}
.drop-indicator--right {
top: 0;
left: 100%;
height: 100%;
width: 4px;
min-height: 16px;
}
.drop-indicator--left {
top: 0;
left: 0;
height: 100%;
width: 4px;
min-height: 16px;
}
/* empty drop targets */
.dashboard-component-tabs-content {
& > .empty-droptarget {
position: absolute;
width: 100%;
}
& > .empty-droptarget:first-child {
height: 14px;
top: -2px;
z-index: @z-index-above-dashboard-charts;
}
& > .empty-droptarget:last-child {
height: 12px;
bottom: 0px;
}
}
.grid-content {
/* note we don't do a :last-child selection because
assuming bottom empty-droptarget is last child is fragile */
& > .empty-droptarget {
width: 100%;
height: 100%;
}
& > .empty-droptarget:first-child {
height: 48px;
margin-top: -24px;
margin-bottom: -24px;
}
& > .empty-droptarget:only-child {
height: 80vh;
}
}

View File

@ -1,259 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
@import '../../assets/stylesheets/less/variables.less';
.filter-scope-container {
display: flex;
flex-direction: column;
height: 80%;
margin-right: -24px;
font-size: @font-size-m;
.nav.nav-tabs {
border: none;
}
.filter-scope-body {
flex: 1;
max-height: calc(100% - 128px);
.filter-field-pane,
.filter-scope-pane {
overflow-y: scroll;
}
}
.warning-message {
padding: 24px;
}
}
.filter-scope-header {
height: 64px;
border-bottom: 1px solid @gray-light;
padding-left: 24px;
margin-left: -24px;
h4 {
margin-top: 0;
}
.selected-fields {
margin: 12px 0 16px;
visibility: hidden;
&.multi-edit-mode {
visibility: visible;
}
.selected-scopes {
padding-left: 5px;
}
}
}
.filters-scope-selector {
display: flex;
flex-direction: row;
position: relative;
height: 100%;
a,
a:active,
a:hover {
color: @almost-black;
text-decoration: none;
}
.react-checkbox-tree .rct-icon.rct-icon-expand-all,
.react-checkbox-tree .rct-icon.rct-icon-collapse-all {
font-size: @font-size-m;
font-family: @font-family-sans-serif;
color: @brand-primary;
&::before {
content: '';
}
&:hover {
text-decoration: underline;
}
&:focus {
outline: none;
}
}
.filter-field-pane {
position: relative;
width: 40%;
padding: 16px 16px 16px 0;
border-right: 1px solid @gray-light;
.filter-container {
label {
font-weight: @font-weight-normal;
margin: 0 0 0 16px;
word-break: break-all;
}
}
.filter-field-item {
height: 35px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 24px;
margin-left: -24px;
&.is-selected {
border: 1px solid @gray-heading;
border-radius: @border-radius-large;
background-color: @gray-bg;
margin-left: -25px;
}
}
.react-checkbox-tree {
.rct-title .root {
font-weight: @font-weight-bold;
}
.rct-text {
height: 40px;
}
}
}
.filter-scope-pane {
position: relative;
flex: 1;
padding: 16px 24px 16px 16px;
}
.react-checkbox-tree {
flex-direction: column;
color: @almost-black;
font-size: @font-size-m;
.filter-scope-type {
padding: 8px 0;
display: flex;
align-items: center;
&.chart {
font-weight: @font-weight-normal;
}
&.selected-filter {
padding-left: 28px;
position: relative;
color: @gray-heading;
&::before {
content: ' ';
position: absolute;
left: 0;
top: 50%;
width: 18px;
height: 18px;
border-radius: @border-radius-normal;
margin-top: -9px;
box-shadow: inset 0 0 0 2px @gray-light;
background: #f2f2f2;
}
}
&.root {
font-weight: @font-weight-bold;
}
}
.rct-checkbox {
svg {
position: relative;
top: 3px;
width: 18px;
}
}
.rct-node-leaf {
.rct-bare-label {
&::before {
padding-left: 5px;
}
}
}
.rct-options {
text-align: left;
margin-left: 0;
margin-bottom: 8px;
}
.rct-text {
margin: 0;
display: flex;
}
.rct-title {
display: block;
}
// disable style from react-checkbox-trees.css
.rct-node-clickable:hover,
.rct-node-clickable:focus,
label:hover,
label:active {
background: none !important;
}
}
.multi-edit-mode {
&.filter-scope-pane {
.rct-node.rct-node-leaf .filter-scope-type.filter_box {
display: none;
}
}
.filter-field-item {
padding: 0 16px 0 50px;
margin-left: -50px;
&.is-selected {
margin-left: -51px;
}
}
}
.scope-search {
position: absolute;
right: 16px;
top: 16px;
border-radius: @border-radius-large;
border: 1px solid @gray-light;
padding: 4px 8px 4px 8px;
font-size: @font-size-m;
outline: none;
&:focus {
border: 1px solid @brand-primary;
}
}
}

View File

@ -1,53 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* this is the ParentSize wrapper */
.grid-container > div:first-child {
height: inherit !important;
}
.grid-content {
display: flex;
flex-direction: column;
}
/* gutters between rows */
.grid-content > div:not(:only-child):not(:last-child):not(.empty-droptarget) {
margin-bottom: 16px;
}
/* Editing guides */
.grid-column-guide {
position: absolute;
top: 0;
min-height: 100%;
background-color: fade(@indicator-color, @opacity-light);
pointer-events: none;
box-shadow: inset 0 0 0 1px fade(@indicator-color, @opacity-medium-heavy);
}
.grid-row-guide {
position: absolute;
left: 0;
bottom: 2;
height: 2;
background-color: @indicator-color;
pointer-events: none;
z-index: @z-index-above-dashboard-charts;
}

View File

@ -1,140 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.with-popover-menu {
position: relative;
outline: none;
}
.grid-row.grid-row--empty .with-popover-menu {
/* drop indicator doesn't show up without this */
width: 100%;
height: 100%;
}
.with-popover-menu--focused:after {
content: '';
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
border: 2px solid @indicator-color;
pointer-events: none;
}
.popover-menu {
position: absolute;
flex-wrap: nowrap;
left: 1px;
top: -42px;
height: 40px;
padding: 0 16px;
background: @lightest;
box-shadow: 0 1px 2px 1px fade(@darkest, @opacity-medium-light);
font-size: @font-size-m;
cursor: default;
z-index: @z-index-max;
&,
.menu-item {
display: flex;
flex-direction: row;
align-items: center;
}
/* vertical spacer after each menu item */
.menu-item:not(:only-child):not(:last-child):after {
content: '';
width: 1;
height: 100%;
background: @gray-light;
margin: 0 16px;
}
}
/* the focus menu doesn't account for parent padding */
.dashboard-component-tabs li .with-popover-menu--focused:after {
top: -12px;
left: -8px;
width: ~'calc(100% + 16px)'; /* escape for .less */
height: ~'calc(100% + 28px)';
}
.dashboard-component-tabs li .popover-menu {
top: -56px;
left: -7px;
}
.hover-dropdown .btn {
&:hover,
&:active,
&:focus {
background: initial;
box-shadow: none;
}
}
.hover-dropdown,
.popover-menu {
li.dropdown-item {
&:hover a {
background: @menu-hover;
}
&.active a {
background: @gray-light;
font-weight: @font-weight-bold;
color: @almost-black;
}
}
}
/* background style menu */
.background-style-option {
display: inline-block;
&:before {
content: '';
width: 1em;
height: 1em;
margin-right: 8px;
display: inline-block;
vertical-align: middle;
}
&.background--white {
padding-left: 0;
background: transparent;
&:before {
background: @lightest;
border: 1px solid @gray-light;
}
}
/* Create the transparent rect icon */
&.background--transparent:before {
background-image: linear-gradient(45deg, @gray 25%, transparent 25%),
linear-gradient(-45deg, @gray 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, @gray 75%),
linear-gradient(-45deg, transparent 75%, @gray 75%);
background-size: 8px 8px;
background-position: 0 0, 0 4px, 4px -4px, -4px 0px;
}
}

View File

@ -1,105 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.resizable-container {
background-color: transparent;
position: relative;
/* re-resizable sets an empty div to 100% width and height, which doesn't
play well with many 100% height containers we need
*/
& ~ div {
width: auto !important;
height: auto !important;
}
}
.resizable-container--resizing {
/* after ensures border visibility on top of any children */
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: inset 0 0 0 2px @indicator-color;
}
& > span .resize-handle {
border-color: @indicator-color;
}
}
.resize-handle {
opacity: 0;
z-index: @z-index-above-dashboard-charts;
&--bottom-right {
position: absolute;
border: solid;
border-width: 0 1.5px 1.5px 0;
border-right-color: @gray;
border-bottom-color: @gray;
right: 16px;
bottom: 16px;
width: 8px;
height: 8px;
}
&--right {
width: 2px;
height: 20px;
right: 4px;
top: 50%;
transform: translate(0, -50%);
position: absolute;
border-left: 1px solid @gray;
border-right: 1px solid @gray;
}
&--bottom {
height: 2px;
width: 20px;
bottom: 4px;
left: 50%;
transform: translate(-50%);
position: absolute;
border-top: 1px solid @gray;
border-bottom: 1px solid @gray;
}
}
.resizable-container:hover .resize-handle,
.resizable-container--resizing .resize-handle {
opacity: 1;
}
.dragdroppable-column .resizable-container-handle--right {
/* override the default because the inner column's handle's mouse target is very small */
right: 0 !important;
}
.dragdroppable-column .dragdroppable-column .resizable-container-handle--right {
/* override the default because the inner column's handle's mouse target is very small */
right: 0 !important;
}
.resizable-container-handle--bottom {
bottom: 0 !important;
}

View File

@ -1,137 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
@import '../assets/stylesheets/less/variables.less';
.scrollbar-container {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
}
.scrollbar-content {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
overflow-y: auto;
margin-right: 0px;
margin-bottom: 40px;
}
.edit-desc-icon {
padding: 0 0 0 0.5em;
font-size: @font-size-m;
}
.checkbox {
float: left;
margin-top: 0px;
margin-right: 3px;
}
.background-transparent {
background-color: transparent !important;
}
.fa.expander {
width: 15px;
}
.list-group {
margin-bottom: 10px;
}
.color-popover.popover {
border: none;
background-color: transparent;
}
.color-popover .popover-content {
padding: 0;
background-color: transparent;
}
.column-option {
margin-left: 3px;
}
.datasource-container {
overflow: auto;
}
.adhoc-metric-edit-tabs > .nav-tabs {
margin-bottom: 6px;
& > li > a {
padding: 4px 4px 4px 4px;
}
}
#metrics-edit-popover {
max-width: none;
.inline-editable {
line-height: 30px; // hand-tweaked to match the height of the input
}
}
.adhoc-option {
cursor: pointer;
}
.label-dropdown ul.dropdown-menu {
position: fixed;
top: auto;
left: auto;
margin: 20px 0 0;
}
.label-btn:hover,
.label-btn-label:hover {
background-color: @gray-dark;
}
.label-btn-label {
cursor: pointer;
}
.adhoc-label-arrow {
font-size: @font-size-s;
margin-left: 3px;
position: static;
}
.time-filter-tabs > .nav-tabs {
margin-bottom: 8px;
& > li > a {
padding: 4px;
}
}
div.section-header {
font-size: @font-size-s;
font-weight: @font-weight-bold;
color: @gray-light5;
margin-bottom: 0;
margin-top: 0;
padding-bottom: 16px;
}

View File

@ -45,6 +45,8 @@ const StyledHeader = styled.header`
${({ theme }) => `
background-color: ${theme.colors.grayscale.light5};
margin-bottom: 2px;
z-index: 10;
&:nth-last-of-type(2) nav {
margin-bottom: 2px;
}