diff --git a/superset-frontend/cypress-base/cypress/support/directories.ts b/superset-frontend/cypress-base/cypress/support/directories.ts index fde9ee0cdeac..d74aef607d76 100644 --- a/superset-frontend/cypress-base/cypress/support/directories.ts +++ b/superset-frontend/cypress-base/cypress/support/directories.ts @@ -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)', diff --git a/superset-frontend/src/SqlLab/App.jsx b/superset-frontend/src/SqlLab/App.jsx index 812202eec20f..0bb08cade039 100644 --- a/superset-frontend/src/SqlLab/App.jsx +++ b/superset-frontend/src/SqlLab/App.jsx @@ -41,9 +41,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 = () => ( + diff --git a/superset-frontend/src/dashboard/stylesheets/index.less b/superset-frontend/src/SqlLab/SqlLabGlobalStyles.tsx similarity index 66% rename from superset-frontend/src/dashboard/stylesheets/index.less rename to superset-frontend/src/SqlLab/SqlLabGlobalStyles.tsx index 0b11c2bec806..f1398e3be9ac 100644 --- a/superset-frontend/src/dashboard/stylesheets/index.less +++ b/superset-frontend/src/SqlLab/SqlLabGlobalStyles.tsx @@ -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 = () => ( + css` + body { + min-height: max( + 100vh, + ${theme.gridUnit * 125}px + ); // Set a min height so the gutter is always visible when resizing + overflow: hidden; + } + `} + /> +); diff --git a/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx b/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx index e241a121dbee..0dd3385ea57e 100644 --- a/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx +++ b/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx @@ -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 ( - 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 ( -
+ -
+ ); } } diff --git a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx index c0c92e3a5549..4dd5c489582a 100644 --- a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx @@ -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 ( - + + + ); } return ; diff --git a/superset-frontend/src/SqlLab/components/QuerySearch/QuerySearch.test.jsx b/superset-frontend/src/SqlLab/components/QuerySearch/QuerySearch.test.jsx deleted file mode 100644 index 2a891d34af84..000000000000 --- a/superset-frontend/src/SqlLab/components/QuerySearch/QuerySearch.test.jsx +++ /dev/null @@ -1,139 +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 React from 'react'; -import thunk from 'redux-thunk'; -import configureStore from 'redux-mock-store'; -import fetchMock from 'fetch-mock'; -import QuerySearch from 'src/SqlLab/components/QuerySearch'; -import { Provider } from 'react-redux'; -import { supersetTheme, ThemeProvider } from '@superset-ui/core'; -import { fireEvent, render, screen, act } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; -import userEvent from '@testing-library/user-event'; -import { user } from 'src/SqlLab/fixtures'; - -const mockStore = configureStore([thunk]); -const store = mockStore({ - sqlLab: user, -}); - -const SEARCH_ENDPOINT = 'glob:*/superset/search_queries?*'; -const USER_ENDPOINT = 'glob:*/api/v1/query/related/user'; -const DATABASE_ENDPOINT = 'glob:*/api/v1/database/?*'; - -fetchMock.get(SEARCH_ENDPOINT, []); -fetchMock.get(USER_ENDPOINT, []); -fetchMock.get(DATABASE_ENDPOINT, []); - -describe('QuerySearch', () => { - const mockedProps = { - displayLimit: 50, - }; - - it('is valid', () => { - expect( - React.isValidElement( - - - - - , - ), - ).toBe(true); - }); - - beforeEach(async () => { - // You need this await function in order to change state in the app. In fact you need it everytime you re-render. - await act(async () => { - render( - - - - - , - ); - }); - }); - - it('should have three Selects', () => { - expect(screen.getByText(/28 days ago/i)).toBeInTheDocument(); - expect(screen.getByText(/now/i)).toBeInTheDocument(); - expect(screen.getByText(/success/i)).toBeInTheDocument(); - }); - - it('updates fromTime on user selects from time', () => { - const role = screen.getByText(/28 days ago/i); - fireEvent.keyDown(role, { key: 'ArrowDown', keyCode: 40 }); - userEvent.click(screen.getByText(/1 hour ago/i)); - expect(screen.getByText(/1 hour ago/i)).toBeInTheDocument(); - }); - - it('updates toTime on user selects on time', () => { - const role = screen.getByText(/now/i); - fireEvent.keyDown(role, { key: 'ArrowDown', keyCode: 40 }); - userEvent.click(screen.getByText(/1 hour ago/i)); - expect(screen.getByText(/1 hour ago/i)).toBeInTheDocument(); - }); - - it('updates status on user selects status', () => { - const role = screen.getByText(/success/i); - fireEvent.keyDown(role, { key: 'ArrowDown', keyCode: 40 }); - userEvent.click(screen.getByText(/failed/i)); - expect(screen.getByText(/failed/i)).toBeInTheDocument(); - }); - - it('should have one input for searchText', () => { - expect( - screen.getByPlaceholderText(/Query search string/i), - ).toBeInTheDocument(); - }); - - it('updates search text on user inputs search text', () => { - const search = screen.getByPlaceholderText(/Query search string/i); - userEvent.type(search, 'text'); - expect(search.value).toBe('text'); - }); - - it('should have one Button', () => { - const button = screen.getAllByRole('button'); - expect(button.length).toEqual(1); - }); - - it('should call API when search button is pressed', async () => { - fetchMock.resetHistory(); - const button = screen.getByRole('button'); - await act(async () => { - userEvent.click(button); - }); - expect(fetchMock.calls(SEARCH_ENDPOINT)).toHaveLength(1); - }); - - it('should call API when (only)enter key is pressed', async () => { - fetchMock.resetHistory(); - const search = screen.getByPlaceholderText(/Query search string/i); - await act(async () => { - userEvent.type(search, 'a'); - }); - expect(fetchMock.calls(SEARCH_ENDPOINT)).toHaveLength(0); - await act(async () => { - userEvent.type(search, '{enter}'); - }); - expect(fetchMock.calls(SEARCH_ENDPOINT)).toHaveLength(1); - }); -}); diff --git a/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx b/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx deleted file mode 100644 index 3018ff192458..000000000000 --- a/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx +++ /dev/null @@ -1,289 +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 React, { useState, useEffect } from 'react'; -import { useDispatch } from 'react-redux'; - -import { setDatabases, addDangerToast } from 'src/SqlLab/actions/sqlLab'; -import Button from 'src/components/Button'; -import Select from 'src/components/DeprecatedSelect'; -import { styled, t, SupersetClient, QueryResponse } from '@superset-ui/core'; -import { debounce } from 'lodash'; -import Loading from 'src/components/Loading'; -import { - now, - epochTimeXHoursAgo, - epochTimeXDaysAgo, - epochTimeXYearsAgo, -} from 'src/utils/dates'; -import AsyncSelect from 'src/components/AsyncSelect'; -import { STATUS_OPTIONS, TIME_OPTIONS } from 'src/SqlLab/constants'; -import QueryTable from '../QueryTable'; - -interface QuerySearchProps { - displayLimit: number; -} - -interface UserMutatorProps { - value: number; - text: string; -} - -interface DbMutatorProps { - id: number; - database_name: string; -} - -const TableWrapper = styled.div` - display: flex; - flex-direction: column; - flex: 1; - height: 100%; -`; - -const TableStyles = styled.div` - table { - background-color: ${({ theme }) => theme.colors.grayscale.light4}; - } - - .table > thead > tr > th { - border-bottom: ${({ theme }) => theme.gridUnit / 2}px solid - ${({ theme }) => theme.colors.grayscale.light2}; - background: ${({ theme }) => theme.colors.grayscale.light4}; - } -`; - -const StyledTableStylesContainer = styled.div` - overflow: auto; -`; - -const QuerySearch = ({ displayLimit }: QuerySearchProps) => { - const dispatch = useDispatch(); - - const [databaseId, setDatabaseId] = useState(''); - const [userId, setUserId] = useState(''); - const [searchText, setSearchText] = useState(''); - const [from, setFrom] = useState('28 days ago'); - const [to, setTo] = useState('now'); - const [status, setStatus] = useState('success'); - const [queriesArray, setQueriesArray] = useState([]); - const [queriesLoading, setQueriesLoading] = useState(true); - - const getTimeFromSelection = (selection: string) => { - switch (selection) { - case 'now': - return now(); - case '1 hour ago': - return epochTimeXHoursAgo(1); - case '1 day ago': - return epochTimeXDaysAgo(1); - case '7 days ago': - return epochTimeXDaysAgo(7); - case '28 days ago': - return epochTimeXDaysAgo(28); - case '90 days ago': - return epochTimeXDaysAgo(90); - case '1 year ago': - return epochTimeXYearsAgo(1); - default: - return null; - } - }; - - const insertParams = (baseUrl: string, params: string[]) => { - const validParams = params.filter(function (p) { - return p !== ''; - }); - return `${baseUrl}?${validParams.join('&')}`; - }; - - const refreshQueries = async () => { - setQueriesLoading(true); - const params = [ - userId && `user_id=${userId}`, - databaseId && `database_id=${databaseId}`, - searchText && `search_text=${searchText}`, - status && `status=${status}`, - from && `from=${getTimeFromSelection(from)}`, - to && `to=${getTimeFromSelection(to)}`, - ]; - - try { - const response = await SupersetClient.get({ - endpoint: insertParams('/superset/search_queries', params), - }); - const queries = Object.values(response.json); - setQueriesArray(queries); - } catch (err) { - dispatch(addDangerToast(t('An error occurred when refreshing queries'))); - } finally { - setQueriesLoading(false); - } - }; - useEffect(() => { - refreshQueries(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const onUserClicked = (userId: string) => { - setUserId(userId); - refreshQueries(); - }; - - const onDbClicked = (dbId: string) => { - setDatabaseId(dbId); - refreshQueries(); - }; - - const onKeyDown = (event: React.KeyboardEvent) => { - if (event.keyCode === 13) { - refreshQueries(); - } - }; - - const onChange = (e: React.ChangeEvent) => { - e.persist(); - const handleChange = debounce(e => { - setSearchText(e.target.value); - }, 200); - handleChange(e); - }; - - const userMutator = ({ result }: { result: UserMutatorProps[] }) => - result.map(({ value, text }: UserMutatorProps) => ({ - label: text, - value, - })); - - const dbMutator = ({ result }: { result: DbMutatorProps[] }) => { - const options = result.map(({ id, database_name }: DbMutatorProps) => ({ - value: id, - label: database_name, - })); - dispatch(setDatabases(result)); - if (result.length === 0) { - dispatch( - addDangerToast(t("It seems you don't have access to any database")), - ); - } - return options; - }; - - return ( - -
-
- setUserId(selected?.value)} - placeholder={t('Filter by user')} - /> -
-
- setDatabaseId(db?.value)} - dataEndpoint="/api/v1/database/?q=(filters:!((col:expose_in_sqllab,opr:eq,value:!t)))" - value={databaseId} - mutator={dbMutator} - placeholder={t('Filter by database')} - /> -
-
- -
-
- ({ value: xt, label: xt }))} - value={{ value: to, label: to }} - autosize={false} - onChange={(selected: any) => setTo(selected?.value)} - /> - - -
+ ); }; diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx index cc62022a123d..a96cb841167a 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx @@ -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 ( -
+ {t('Reset state')} )} -
+ ); }; diff --git a/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx index 8e4372d1091b..1e1b22a81d24 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx @@ -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 = ({ queryEditor }) => { onClick={() => actions.removeQueryEditor(qe)} data-test="close-tab-menu-option" > -
+ -
+ {t('Close tab')} = ({ queryEditor }) => { onClick={renameTab} data-test="rename-tab-menu-option" > -
+ -
+ {t('Rename tab')}
= ({ queryEditor }) => { onClick={() => actions.toggleLeftBar(qe)} data-test="toggle-menu-option" > -
+ -
+ {qe.hideLeftBar ? t('Expand tool bar') : t('Hide tool bar')}
= ({ queryEditor }) => { onClick={() => actions.removeAllOtherQueryEditors(qe)} data-test="close-all-other-menu-option" > -
+ -
+ {t('Close all other tabs')}
= ({ queryEditor }) => { onClick={() => actions.cloneQueryToNewTab(qe, false)} data-test="clone-tab-menu-option" > -
+ -
+ {t('Duplicate tab')}
diff --git a/superset-frontend/src/SqlLab/components/TabStatusIcon/index.tsx b/superset-frontend/src/SqlLab/components/TabStatusIcon/index.tsx index ab6348e83502..f40e94686658 100644 --- a/superset-frontend/src/SqlLab/components/TabStatusIcon/index.tsx +++ b/superset-frontend/src/SqlLab/components/TabStatusIcon/index.tsx @@ -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> = { export default function TabStatusIcon({ tabState }: TabStatusIconProps) { const StatusIcon = STATE_ICONS[tabState]; return ( -
+ {StatusIcon && ( )} -
+ ); } diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx index 73ff450b3827..105751a4d0eb 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx @@ -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(); diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx index 63c7cc862caf..a06f3b7b6019 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx @@ -53,6 +53,12 @@ const defaultProps = { scheduleQueryWarning: null, }; +const StyledEditableTabs = styled(EditableTabs)` + height: 100%; + display: flex; + flex-direction: column; +`; + const StyledTab = styled.span` line-height: 24px; `; @@ -305,7 +311,7 @@ class TabbedSqlEditors extends React.PureComponent { ); return ( - {editors} {noQueryEditors && emptyTabState} - + ); } } diff --git a/superset-frontend/src/SqlLab/components/TableElement/index.tsx b/superset-frontend/src/SqlLab/components/TableElement/index.tsx index 44fbe6e1cc0c..7d6e1ec6cd28 100644 --- a/superset-frontend/src/SqlLab/components/TableElement/index.tsx +++ b/superset-frontend/src/SqlLab/components/TableElement/index.tsx @@ -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 ( - { forceRender > {renderBody()} - + ); }; diff --git a/superset-frontend/src/SqlLab/main.less b/superset-frontend/src/SqlLab/main.less deleted file mode 100644 index aa75c0ec00b6..000000000000 --- a/superset-frontend/src/SqlLab/main.less +++ /dev/null @@ -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; -} diff --git a/superset-frontend/src/assets/stylesheets/superset.less b/superset-frontend/src/assets/stylesheets/superset.less index 5808d0144bc7..39bd719d272d 100644 --- a/superset-frontend/src/assets/stylesheets/superset.less +++ b/superset-frontend/src/assets/stylesheets/superset.less @@ -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; } diff --git a/superset-frontend/src/components/Chart/Chart.jsx b/superset-frontend/src/components/Chart/Chart.jsx index 8be36994505a..aac44a5186c5 100644 --- a/superset-frontend/src/components/Chart/Chart.jsx +++ b/superset-frontend/src/components/Chart/Chart.jsx @@ -113,6 +113,10 @@ const Styles = styled.div` .pivot_table tbody tr { font-feature-settings: 'tnum' 1; } + + .alert { + margin: ${({ theme }) => theme.gridUnit * 2}px; + } } `; diff --git a/superset-frontend/src/dashboard/components/Dashboard.jsx b/superset-frontend/src/dashboard/components/Dashboard.jsx index 5da8f501f8eb..be70fa557974 100644 --- a/superset-frontend/src/dashboard/components/Dashboard.jsx +++ b/superset-frontend/src/dashboard/components/Dashboard.jsx @@ -38,7 +38,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'; diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx index 65969e2ca6d7..1edd09ceeda1 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx @@ -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'); }); diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx index d2ce6a2f1a3e..31187740744c 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx @@ -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 */ - - /* 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}; - } - - .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}; - } - - /* 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; - } + ${({ 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: ${theme.gridUnit * -3}px; + background: ${theme.colors.grayscale.light5}; + border: 1px solid ${theme.colors.grayscale.light2}; + } + + .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: ${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; + } + + /* 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 DashboardContentWrapper = styled.div` + ${({ theme }) => css` + &.dashboard { + position: relative; + flex-grow: 1; + display: flex; + flex-direction: column; + height: 100%; + + /* 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 */ + } + + .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<{ - dashboardFiltersOpen: boolean; editMode: boolean; - nativeFiltersEnabled: boolean; - filterBarOrientation: FilterBarOrientation; + marginLeft: number; }>` - 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; + ${({ theme, editMode, marginLeft }) => css` + display: flex; + flex-direction: row; + flex-wrap: nowrap; + height: auto; 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; - } - return theme.gridUnit * 8; - }}px; - ${({ editMode, theme }) => - editMode && + .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; - } - - .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; - } + + /* this is the ParentSize wrapper */ + & > div:first-child { + height: inherit !important; + } + } + + .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 = () => { const dispatch = useDispatch(); const uiConfig = useUiConfig(); + const theme = useTheme(); const dashboardId = useSelector( ({ dashboardInfo }) => `${dashboardInfo.id}`, @@ -430,6 +623,14 @@ const DashboardBuilder: FC = () => { ], ); + const dashboardContentMarginLeft = + !dashboardFiltersOpen && + !editMode && + nativeFiltersEnabled && + filterBarOrientation !== FilterBarOrientation.HORIZONTAL + ? 0 + : theme.gridUnit * 8; + return ( {showFilterBar && filterBarOrientation === FilterBarOrientation.VERTICAL && ( @@ -513,16 +714,14 @@ const DashboardBuilder: FC = () => { image="dashboard.svg" /> )} -
{showDashboard ? ( @@ -531,7 +730,7 @@ const DashboardBuilder: FC = () => { )} {editMode && } -
+
); diff --git a/superset-frontend/src/dashboard/components/DashboardGrid.jsx b/superset-frontend/src/dashboard/components/DashboardGrid.jsx index fa45486b2090..601dbac4a4cd 100644 --- a/superset-frontend/src/dashboard/components/DashboardGrid.jsx +++ b/superset-frontend/src/dashboard/components/DashboardGrid.jsx @@ -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 { )}
-
+ {/* make the area above components droppable */} {editMode && ( ( -
))} - {isResizing && rowGuideTop && ( -
- )} -
+
); diff --git a/superset-frontend/src/dashboard/components/DashboardGrid.test.jsx b/superset-frontend/src/dashboard/components/DashboardGrid.test.jsx index 4fd1704b230e..c954ecff7be6 100644 --- a/superset-frontend/src/dashboard/components/DashboardGrid.test.jsx +++ b/superset-frontend/src/dashboard/components/DashboardGrid.test.jsx @@ -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 }; diff --git a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx index eaf045c114ec..8c4bef5c29e1 100644 --- a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx @@ -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 = ({ innerRef = null, forceRefresh = () => ({}), @@ -134,7 +197,7 @@ const SliceHeader: FC = ({ const exploreUrl = `/explore/?dashboard_page_id=${dashboardPageId}&slice_id=${slice.slice_id}`; return ( -
+
= ({ )}
-
+ ); }; diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx index 5bdc442ba19a..00e83799c76e 100644 --- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx @@ -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 { diff --git a/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx b/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx index 8675c0bc63a3..3bc9f4d299a0 100644 --- a/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx +++ b/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx @@ -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 ( -
{children(childProps)} -
+ ); } } diff --git a/superset-frontend/src/dashboard/components/dnd/DragDroppable.test.jsx b/superset-frontend/src/dashboard/components/dnd/DragDroppable.test.jsx index 65d77e773c56..e8366815a6dd 100644 --- a/superset-frontend/src/dashboard/components/dnd/DragDroppable.test.jsx +++ b/superset-frontend/src/dashboard/components/dnd/DragDroppable.test.jsx @@ -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'; diff --git a/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx b/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx index 995cac01ca1f..1b146148682b 100644 --- a/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx +++ b/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx @@ -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 ( -
-
+ +

{t('Configure filter scopes')}

{showSelector && this.renderEditingFiltersName()} -
+ -
+ {!showSelector ? (
{t('There are no filters in this dashboard.')}
) : ( -
+
{this.renderFilterFieldList()}
{this.renderFilterScopeTree()}
-
+ )} -
+ )} -
+ ); } } diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx index f5fa7fa4b34e..f16805fba7ca 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx @@ -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 { /> )} -
-
+ ); } diff --git a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.tsx b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.tsx index 30626a6ba580..a93f3b0b8db6 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.tsx +++ b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.tsx @@ -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 = ({ id, parentId, @@ -265,13 +274,13 @@ const ChartHolder: React.FC = ({ 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 && ( diff --git a/superset-frontend/src/dashboard/components/gridComponents/Column.jsx b/superset-frontend/src/dashboard/components/gridComponents/Column.jsx index 90666d6d9883..1883531404f7 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Column.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Column.jsx @@ -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 { /> )} -
- {columnItems.map((componentId, itemIndex) => ( - - ))} + {columnItems.length === 0 ? ( +
{t('Empty column')}
+ ) : ( + columnItems.map((componentId, itemIndex) => ( + + )) + )} {dropIndicatorProps &&
} -
+ )} diff --git a/superset-frontend/src/dashboard/components/gridComponents/Divider.jsx b/superset-frontend/src/dashboard/components/gridComponents/Divider.jsx index 8be4fe892485..078405be3e4a 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Divider.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Divider.jsx @@ -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 { )} -
+ {dropIndicatorProps &&
}
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Header.jsx b/superset-frontend/src/dashboard/components/gridComponents/Header.jsx index 938a48bf099d..253f377fc2f7 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Header.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Header.jsx @@ -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} > -
)} -
+ {dropIndicatorProps &&
} diff --git a/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx b/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx index d4a4f7790b09..1d358529b577 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx @@ -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} > -
-
+ {dropIndicatorProps &&
} )} diff --git a/superset-frontend/src/dashboard/components/gridComponents/Row.jsx b/superset-frontend/src/dashboard/components/gridComponents/Row.jsx index 26980701c7a4..ae3db36d1dbf 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Row.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Row.jsx @@ -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 { /> )} -
- {rowItems.map((componentId, itemIndex) => ( - - ))} + {rowItems.length === 0 ? ( +
{t('Empty row')}
+ ) : ( + rowItems.map((componentId, itemIndex) => ( + + )) + )} {dropIndicatorProps &&
} -
+ )} diff --git a/superset-frontend/src/dashboard/components/gridComponents/new/DraggableNewComponent.jsx b/superset-frontend/src/dashboard/components/gridComponents/new/DraggableNewComponent.jsx index f4f33c9333df..c261fd2da25e 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/new/DraggableNewComponent.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/new/DraggableNewComponent.jsx @@ -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 }) => ( -
-
+ + {label} -
+ )} ); diff --git a/superset-frontend/src/dashboard/components/gridComponents/new/DraggableNewComponent.test.jsx b/superset-frontend/src/dashboard/components/gridComponents/new/DraggableNewComponent.test.jsx index 2b56f02f5d91..a105fa43964f 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/new/DraggableNewComponent.test.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/new/DraggableNewComponent.test.jsx @@ -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', () => { diff --git a/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx b/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx index 82c899410253..cf9bdc1a58ab 100644 --- a/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx +++ b/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx @@ -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 ( -
+ {`${option.label} background`} -
+ ); } function renderOption(option: OptionProps) { return ( -
+ {option.label} -
+ ); } diff --git a/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx b/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx index df44b4e1c1f2..3aa0aa81d3d2 100644 --- a/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx +++ b/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx @@ -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 ( -
{children} {editMode && isFocused && (menuItems?.length ?? 0) > 0 && ( -
+ {menuItems.map((node: React.ReactNode, i: Number) => (
{node}
))} -
+ )} -
+ ); } } diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.stories.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.stories.tsx index 212e9033588f..46e7c03fadb5 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.stories.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.stories.tsx @@ -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 { diff --git a/superset-frontend/src/dashboard/components/resizable/ResizableContainer.jsx b/superset-frontend/src/dashboard/components/resizable/ResizableContainer.jsx index ff576101f491..a14731574e58 100644 --- a/superset-frontend/src/dashboard/components/resizable/ResizableContainer.jsx +++ b/superset-frontend/src/dashboard/components/resizable/ResizableContainer.jsx @@ -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 ( - {children} - + ); } } diff --git a/superset-frontend/src/dashboard/containers/DashboardPage.tsx b/superset-frontend/src/dashboard/containers/DashboardPage.tsx index a835523ae768..0066354f589b 100644 --- a/superset-frontend/src/dashboard/containers/DashboardPage.tsx +++ b/superset-frontend/src/dashboard/containers/DashboardPage.tsx @@ -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 = ({ idOrSlug }: PageProps) => { return ( <> - + 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; diff --git a/superset-frontend/src/dashboard/stylesheets/builder.less b/superset-frontend/src/dashboard/stylesheets/builder.less deleted file mode 100644 index 422d455622a4..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/builder.less +++ /dev/null @@ -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; -} diff --git a/superset-frontend/src/dashboard/stylesheets/components/chart.less b/superset-frontend/src/dashboard/stylesheets/components/chart.less deleted file mode 100644 index 9f557ff95c6f..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/components/chart.less +++ /dev/null @@ -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; -} diff --git a/superset-frontend/src/dashboard/stylesheets/components/column.less b/superset-frontend/src/dashboard/stylesheets/components/column.less deleted file mode 100644 index 7d7b7baba364..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/components/column.less +++ /dev/null @@ -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; - } -} diff --git a/superset-frontend/src/dashboard/stylesheets/components/divider.less b/superset-frontend/src/dashboard/stylesheets/components/divider.less deleted file mode 100644 index 7ee4956ddfc0..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/components/divider.less +++ /dev/null @@ -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; -} diff --git a/superset-frontend/src/dashboard/stylesheets/components/header.less b/superset-frontend/src/dashboard/stylesheets/components/header.less deleted file mode 100644 index 355385d373fd..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/components/header.less +++ /dev/null @@ -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; -} diff --git a/superset-frontend/src/dashboard/stylesheets/components/index.less b/superset-frontend/src/dashboard/stylesheets/components/index.less deleted file mode 100644 index d99e11df2a45..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/components/index.less +++ /dev/null @@ -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'; diff --git a/superset-frontend/src/dashboard/stylesheets/components/markdown.less b/superset-frontend/src/dashboard/stylesheets/components/markdown.less deleted file mode 100644 index d8b3676eb8e9..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/components/markdown.less +++ /dev/null @@ -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; - } - } -} diff --git a/superset-frontend/src/dashboard/stylesheets/components/new-component.less b/superset-frontend/src/dashboard/stylesheets/components/new-component.less deleted file mode 100644 index 2202a59ff70b..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/components/new-component.less +++ /dev/null @@ -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; - } -} diff --git a/superset-frontend/src/dashboard/stylesheets/components/row.less b/superset-frontend/src/dashboard/stylesheets/components/row.less deleted file mode 100644 index ec096d0bfa94..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/components/row.less +++ /dev/null @@ -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; - } -} diff --git a/superset-frontend/src/dashboard/stylesheets/dashboard.less b/superset-frontend/src/dashboard/stylesheets/dashboard.less deleted file mode 100644 index cdbdeb648157..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/dashboard.less +++ /dev/null @@ -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; -} diff --git a/superset-frontend/src/dashboard/stylesheets/dnd.less b/superset-frontend/src/dashboard/stylesheets/dnd.less deleted file mode 100644 index 2465c6fac066..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/dnd.less +++ /dev/null @@ -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; - } -} diff --git a/superset-frontend/src/dashboard/stylesheets/filter-scope-selector.less b/superset-frontend/src/dashboard/stylesheets/filter-scope-selector.less deleted file mode 100644 index e12054e09da7..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/filter-scope-selector.less +++ /dev/null @@ -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; - } - } -} diff --git a/superset-frontend/src/dashboard/stylesheets/grid.less b/superset-frontend/src/dashboard/stylesheets/grid.less deleted file mode 100644 index 5b793b96bdb6..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/grid.less +++ /dev/null @@ -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; -} diff --git a/superset-frontend/src/dashboard/stylesheets/popover-menu.less b/superset-frontend/src/dashboard/stylesheets/popover-menu.less deleted file mode 100644 index 4abc49499360..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/popover-menu.less +++ /dev/null @@ -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; - } -} diff --git a/superset-frontend/src/dashboard/stylesheets/resizable.less b/superset-frontend/src/dashboard/stylesheets/resizable.less deleted file mode 100644 index be7182716d04..000000000000 --- a/superset-frontend/src/dashboard/stylesheets/resizable.less +++ /dev/null @@ -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; -} diff --git a/superset-frontend/src/explore/main.less b/superset-frontend/src/explore/main.less deleted file mode 100644 index 015a8a1a3bed..000000000000 --- a/superset-frontend/src/explore/main.less +++ /dev/null @@ -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; -} diff --git a/superset-frontend/src/views/components/Menu.tsx b/superset-frontend/src/views/components/Menu.tsx index 31d8af656dfa..9b5071099991 100644 --- a/superset-frontend/src/views/components/Menu.tsx +++ b/superset-frontend/src/views/components/Menu.tsx @@ -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; }