mirror of
https://github.com/apache/superset.git
synced 2024-09-06 22:07:34 -04:00
feat(explore): Move chart header to top of the page (#19529)
* Move chart header to top of the page * Implement truncating and dynamic input * fix typing * Prevent cmd+z undoing changes when not in edit mode * Fix tests, add missing types * Show changed title in altered
This commit is contained in:
parent
1eef923b31
commit
602afbaa31
@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { t, styled } from '@superset-ui/core';
|
||||
import { css, t, styled } from '@superset-ui/core';
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
import { useComponentDidMount } from 'src/hooks/useComponentDidMount';
|
||||
import Icons from 'src/components/Icons';
|
||||
@ -32,9 +32,11 @@ interface FaveStarProps {
|
||||
}
|
||||
|
||||
const StyledLink = styled.a`
|
||||
font-size: ${({ theme }) => theme.typography.sizes.xl}px;
|
||||
${({ theme }) => css`
|
||||
font-size: ${theme.typography.sizes.xl}px;
|
||||
display: flex;
|
||||
padding: 0 0 0 0.5em;
|
||||
padding: 0 0 0 ${theme.gridUnit * 2}px;
|
||||
`};
|
||||
`;
|
||||
|
||||
const FaveStar = ({
|
||||
|
@ -86,7 +86,7 @@ const DatasourceContainer = styled.div`
|
||||
color: ${theme.colors.grayscale.light1};
|
||||
}
|
||||
.form-control.input-md {
|
||||
width: calc(100% - ${theme.gridUnit * 4}px);
|
||||
width: calc(100% - ${theme.gridUnit * 8}px);
|
||||
height: ${theme.gridUnit * 8}px;
|
||||
margin: ${theme.gridUnit * 2}px auto;
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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 userEvent from '@testing-library/user-event';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import { ChartEditableTitle } from './index';
|
||||
|
||||
const createProps = (overrides: Record<string, any> = {}) => ({
|
||||
title: 'Chart title',
|
||||
placeholder: 'Add the name of the chart',
|
||||
canEdit: true,
|
||||
onSave: jest.fn(),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('Chart editable title', () => {
|
||||
it('renders chart title', () => {
|
||||
const props = createProps();
|
||||
render(<ChartEditableTitle {...props} />);
|
||||
expect(screen.getByText('Chart title')).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders placeholder', () => {
|
||||
const props = createProps({
|
||||
title: '',
|
||||
});
|
||||
render(<ChartEditableTitle {...props} />);
|
||||
expect(screen.getByText('Add the name of the chart')).toBeVisible();
|
||||
});
|
||||
|
||||
it('click, edit and save title', () => {
|
||||
const props = createProps();
|
||||
render(<ChartEditableTitle {...props} />);
|
||||
const textboxElement = screen.getByRole('textbox');
|
||||
userEvent.click(textboxElement);
|
||||
userEvent.type(textboxElement, ' edited');
|
||||
expect(screen.getByText('Chart title edited')).toBeVisible();
|
||||
userEvent.type(textboxElement, '{enter}');
|
||||
expect(props.onSave).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders in non-editable mode', () => {
|
||||
const props = createProps({ canEdit: false });
|
||||
render(<ChartEditableTitle {...props} />);
|
||||
const titleElement = screen.getByLabelText('Chart title');
|
||||
expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
|
||||
expect(titleElement).toBeVisible();
|
||||
userEvent.click(titleElement);
|
||||
userEvent.type(titleElement, ' edited{enter}');
|
||||
expect(props.onSave).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 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, {
|
||||
ChangeEvent,
|
||||
KeyboardEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { css, styled, t } from '@superset-ui/core';
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
import { useResizeDetector } from 'react-resize-detector';
|
||||
|
||||
export type ChartEditableTitleProps = {
|
||||
title: string;
|
||||
placeholder: string;
|
||||
onSave: (title: string) => void;
|
||||
canEdit: boolean;
|
||||
};
|
||||
|
||||
const Styles = styled.div`
|
||||
${({ theme }) => css`
|
||||
display: flex;
|
||||
font-size: ${theme.typography.sizes.xl}px;
|
||||
font-weight: ${theme.typography.weights.bold};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
& .chart-title,
|
||||
& .chart-title-input {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
& .chart-title {
|
||||
cursor: default;
|
||||
}
|
||||
& .chart-title-input {
|
||||
border: none;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: ${theme.colors.grayscale.light1};
|
||||
}
|
||||
}
|
||||
|
||||
& .input-sizer {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
display: inline-block;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const ChartEditableTitle = ({
|
||||
title,
|
||||
placeholder,
|
||||
onSave,
|
||||
canEdit,
|
||||
}: ChartEditableTitleProps) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [currentTitle, setCurrentTitle] = useState(title || '');
|
||||
const contentRef = useRef<HTMLInputElement>(null);
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
|
||||
const { width: inputWidth, ref: sizerRef } = useResizeDetector();
|
||||
const { width: containerWidth, ref: containerRef } = useResizeDetector({
|
||||
refreshMode: 'debounce',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing && contentRef?.current) {
|
||||
contentRef.current.focus();
|
||||
// move cursor and scroll to the end
|
||||
if (contentRef.current.setSelectionRange) {
|
||||
const { length } = contentRef.current.value;
|
||||
contentRef.current.setSelectionRange(length, length);
|
||||
contentRef.current.scrollLeft = contentRef.current.scrollWidth;
|
||||
}
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
// a trick to make the input grow when user types text
|
||||
// we make additional span component, place it somewhere out of view and copy input
|
||||
// then we can measure the width of that span to resize the input element
|
||||
useLayoutEffect(() => {
|
||||
if (sizerRef?.current) {
|
||||
sizerRef.current.innerHTML = (currentTitle || placeholder).replace(
|
||||
/\s/g,
|
||||
' ',
|
||||
);
|
||||
}
|
||||
}, [currentTitle, placeholder, sizerRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
contentRef.current &&
|
||||
contentRef.current.scrollWidth > contentRef.current.clientWidth
|
||||
) {
|
||||
setShowTooltip(true);
|
||||
} else {
|
||||
setShowTooltip(false);
|
||||
}
|
||||
}, [inputWidth, containerWidth]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (!canEdit || isEditing) {
|
||||
return;
|
||||
}
|
||||
setIsEditing(true);
|
||||
}, [canEdit, isEditing]);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
const formattedTitle = currentTitle.trim();
|
||||
setCurrentTitle(formattedTitle);
|
||||
if (title !== formattedTitle) {
|
||||
onSave(formattedTitle);
|
||||
}
|
||||
setIsEditing(false);
|
||||
}, [canEdit, currentTitle, onSave, title]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(ev: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!canEdit || !isEditing) {
|
||||
return;
|
||||
}
|
||||
setCurrentTitle(ev.target.value);
|
||||
},
|
||||
[canEdit, isEditing],
|
||||
);
|
||||
|
||||
const handleKeyPress = useCallback(
|
||||
(ev: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
if (ev.key === 'Enter') {
|
||||
ev.preventDefault();
|
||||
contentRef.current?.blur();
|
||||
}
|
||||
},
|
||||
[canEdit],
|
||||
);
|
||||
|
||||
return (
|
||||
<Styles ref={containerRef}>
|
||||
<Tooltip
|
||||
id="title-tooltip"
|
||||
title={showTooltip && currentTitle && !isEditing ? currentTitle : null}
|
||||
>
|
||||
{canEdit ? (
|
||||
<input
|
||||
data-test="editable-title-input"
|
||||
className="chart-title-input"
|
||||
aria-label={t('Chart title')}
|
||||
ref={contentRef}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
onClick={handleClick}
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder={placeholder}
|
||||
value={currentTitle}
|
||||
css={css`
|
||||
cursor: ${isEditing ? 'text' : 'pointer'};
|
||||
|
||||
${inputWidth &&
|
||||
inputWidth > 0 &&
|
||||
css`
|
||||
width: ${inputWidth}px;
|
||||
`}
|
||||
`}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="chart-title"
|
||||
aria-label={t('Chart title')}
|
||||
ref={contentRef}
|
||||
>
|
||||
{currentTitle}
|
||||
</span>
|
||||
)}
|
||||
</Tooltip>
|
||||
<span ref={sizerRef} className="input-sizer" aria-hidden tabIndex={-1} />
|
||||
</Styles>
|
||||
);
|
||||
};
|
@ -22,6 +22,7 @@ import { bindActionCreators } from 'redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
CategoricalColorNamespace,
|
||||
css,
|
||||
SupersetClient,
|
||||
styled,
|
||||
t,
|
||||
@ -33,7 +34,6 @@ import {
|
||||
} from 'src/reports/actions/reports';
|
||||
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
||||
import { chartPropShape } from 'src/dashboard/util/propShapes';
|
||||
import EditableTitle from 'src/components/EditableTitle';
|
||||
import AlteredSliceTag from 'src/components/AlteredSliceTag';
|
||||
import FaveStar from 'src/components/FaveStar';
|
||||
import Timer from 'src/components/Timer';
|
||||
@ -44,6 +44,7 @@ import CertifiedBadge from 'src/components/CertifiedBadge';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import RowCountLabel from '../RowCountLabel';
|
||||
import ExploreAdditionalActionsMenu from '../ExploreAdditionalActionsMenu';
|
||||
import { ChartEditableTitle } from './ChartEditableTitle';
|
||||
|
||||
const CHART_STATUS_MAP = {
|
||||
failed: 'danger',
|
||||
@ -53,8 +54,8 @@ const CHART_STATUS_MAP = {
|
||||
|
||||
const propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
can_overwrite: PropTypes.bool.isRequired,
|
||||
can_download: PropTypes.bool.isRequired,
|
||||
canOverwrite: PropTypes.bool.isRequired,
|
||||
canDownload: PropTypes.bool.isRequired,
|
||||
dashboardId: PropTypes.number,
|
||||
isStarred: PropTypes.bool.isRequired,
|
||||
slice: PropTypes.object,
|
||||
@ -67,11 +68,13 @@ const propTypes = {
|
||||
};
|
||||
|
||||
const StyledHeader = styled.div`
|
||||
${({ theme }) => css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
|
||||
span[role='button'] {
|
||||
display: flex;
|
||||
@ -81,6 +84,8 @@ const StyledHeader = styled.div`
|
||||
.title-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
margin-right: ${theme.gridUnit * 6}px;
|
||||
}
|
||||
|
||||
.right-button-panel {
|
||||
@ -89,15 +94,15 @@ const StyledHeader = styled.div`
|
||||
|
||||
> .btn-group {
|
||||
flex: 0 0 auto;
|
||||
margin-left: ${({ theme }) => theme.gridUnit}px;
|
||||
margin-left: ${theme.gridUnit}px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
color: ${({ theme }) => theme.colors.grayscale.base};
|
||||
margin: 0 ${({ theme }) => theme.gridUnit * 1.5}px 0
|
||||
${({ theme }) => theme.gridUnit}px;
|
||||
color: ${theme.colors.grayscale.base};
|
||||
margin: 0 ${theme.gridUnit * 1.5}px 0 ${theme.gridUnit}px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledButtons = styled.span`
|
||||
@ -173,13 +178,6 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
getSliceName() {
|
||||
const { sliceName, table_name: tableName } = this.props;
|
||||
const title = sliceName || t('%s - untitled', tableName);
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
postChartFormData() {
|
||||
this.props.actions.postChartFormData(
|
||||
this.props.form_data,
|
||||
@ -221,22 +219,45 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user, form_data: formData, slice } = this.props;
|
||||
const {
|
||||
actions,
|
||||
chart,
|
||||
user,
|
||||
formData,
|
||||
slice,
|
||||
canOverwrite,
|
||||
canDownload,
|
||||
isStarred,
|
||||
sliceUpdated,
|
||||
sliceName,
|
||||
} = this.props;
|
||||
const {
|
||||
chartStatus,
|
||||
chartUpdateEndTime,
|
||||
chartUpdateStartTime,
|
||||
latestQueryFormData,
|
||||
queriesResponse,
|
||||
} = this.props.chart;
|
||||
sliceFormData,
|
||||
} = chart;
|
||||
// TODO: when will get appropriate design for multi queries use all results and not first only
|
||||
const queryResponse = queriesResponse?.[0];
|
||||
const oldSliceName = slice?.slice_name;
|
||||
const chartFinished = ['failed', 'rendered', 'success'].includes(
|
||||
this.props.chart.chartStatus,
|
||||
chartStatus,
|
||||
);
|
||||
return (
|
||||
<StyledHeader id="slice-header" className="panel-title-large">
|
||||
<StyledHeader id="slice-header">
|
||||
<div className="title-panel">
|
||||
<ChartEditableTitle
|
||||
title={sliceName}
|
||||
canEdit={
|
||||
!slice ||
|
||||
canOverwrite ||
|
||||
(slice?.owners || []).includes(user?.userId)
|
||||
}
|
||||
onSave={actions.updateChartTitle}
|
||||
placeholder={t('Add the name of the chart')}
|
||||
/>
|
||||
{slice?.certified_by && (
|
||||
<>
|
||||
<CertifiedBadge
|
||||
@ -245,26 +266,14 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
/>{' '}
|
||||
</>
|
||||
)}
|
||||
<EditableTitle
|
||||
title={this.getSliceName()}
|
||||
canEdit={
|
||||
!this.props.slice ||
|
||||
this.props.can_overwrite ||
|
||||
(this.props.slice?.owners || []).includes(
|
||||
this.props?.user?.userId,
|
||||
)
|
||||
}
|
||||
onSaveTitle={this.props.actions.updateChartTitle}
|
||||
/>
|
||||
|
||||
{this.props.slice && (
|
||||
{slice && (
|
||||
<StyledButtons>
|
||||
{user.userId && (
|
||||
<FaveStar
|
||||
itemId={this.props.slice.slice_id}
|
||||
fetchFaveStar={this.props.actions.fetchFaveStar}
|
||||
saveFaveStar={this.props.actions.saveFaveStar}
|
||||
isStarred={this.props.isStarred}
|
||||
itemId={slice.slice_id}
|
||||
fetchFaveStar={actions.fetchFaveStar}
|
||||
saveFaveStar={actions.saveFaveStar}
|
||||
isStarred={isStarred}
|
||||
showTooltip
|
||||
/>
|
||||
)}
|
||||
@ -272,15 +281,15 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
<PropertiesModal
|
||||
show={this.state.isPropertiesModalOpen}
|
||||
onHide={this.closePropertiesModal}
|
||||
onSave={this.props.sliceUpdated}
|
||||
slice={this.props.slice}
|
||||
onSave={sliceUpdated}
|
||||
slice={slice}
|
||||
/>
|
||||
)}
|
||||
{this.props.chart.sliceFormData && (
|
||||
{sliceFormData && (
|
||||
<AlteredSliceTag
|
||||
className="altered"
|
||||
origFormData={this.props.chart.sliceFormData}
|
||||
currentFormData={formData}
|
||||
origFormData={{ ...sliceFormData, chartTitle: oldSliceName }}
|
||||
currentFormData={{ ...formData, chartTitle: sliceName }}
|
||||
/>
|
||||
)}
|
||||
</StyledButtons>
|
||||
@ -306,10 +315,10 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
status={CHART_STATUS_MAP[chartStatus]}
|
||||
/>
|
||||
<ExploreAdditionalActionsMenu
|
||||
onOpenInEditor={this.props.actions.redirectSQLLab}
|
||||
onOpenInEditor={actions.redirectSQLLab}
|
||||
onOpenPropertiesModal={this.openPropertiesModal}
|
||||
slice={this.props.slice}
|
||||
canDownloadCSV={this.props.can_download}
|
||||
slice={slice}
|
||||
canDownloadCSV={canDownload}
|
||||
latestQueryFormData={latestQueryFormData}
|
||||
canAddReports={this.canAddReports()}
|
||||
/>
|
||||
|
@ -28,7 +28,6 @@ import {
|
||||
setItem,
|
||||
LocalStorageKeys,
|
||||
} from 'src/utils/localStorageHelpers';
|
||||
import ConnectedExploreChartHeader from './ExploreChartHeader';
|
||||
import { DataTablesPane } from './DataTablesPane';
|
||||
import { buildV1ChartDataPayload } from '../exploreUtils';
|
||||
|
||||
@ -63,7 +62,6 @@ const GUTTER_SIZE_FACTOR = 1.25;
|
||||
|
||||
const CHART_PANEL_PADDING_HORIZ = 30;
|
||||
const CHART_PANEL_PADDING_VERTICAL = 15;
|
||||
const HEADER_PADDING = 15;
|
||||
|
||||
const INITIAL_SIZES = [90, 10];
|
||||
const MIN_SIZES = [300, 50];
|
||||
@ -78,8 +76,8 @@ const Styles = styled.div`
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
|
||||
& > div:last-of-type {
|
||||
flex-basis: 100%;
|
||||
& > div {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
@ -114,10 +112,6 @@ const ExploreChartPanel = props => {
|
||||
const theme = useTheme();
|
||||
const gutterMargin = theme.gridUnit * GUTTER_SIZE_FACTOR;
|
||||
const gutterHeight = theme.gridUnit * GUTTER_SIZE_FACTOR;
|
||||
const { height: hHeight, ref: headerRef } = useResizeDetector({
|
||||
refreshMode: 'debounce',
|
||||
refreshRate: 300,
|
||||
});
|
||||
const { width: chartPanelWidth, ref: chartPanelRef } = useResizeDetector({
|
||||
refreshMode: 'debounce',
|
||||
refreshRate: 300,
|
||||
@ -156,21 +150,10 @@ const ExploreChartPanel = props => {
|
||||
}, [updateQueryContext]);
|
||||
|
||||
const calcSectionHeight = useCallback(
|
||||
percent => {
|
||||
let headerHeight;
|
||||
if (props.standalone) {
|
||||
headerHeight = 0;
|
||||
} else if (hHeight) {
|
||||
headerHeight = hHeight + HEADER_PADDING;
|
||||
} else {
|
||||
headerHeight = 50;
|
||||
}
|
||||
const containerHeight = parseInt(props.height, 10) - headerHeight;
|
||||
return (
|
||||
(containerHeight * percent) / 100 - (gutterHeight / 2 + gutterMargin)
|
||||
);
|
||||
},
|
||||
[gutterHeight, gutterMargin, props.height, props.standalone, hHeight],
|
||||
percent =>
|
||||
(parseInt(props.height, 10) * percent) / 100 -
|
||||
(gutterHeight / 2 + gutterMargin),
|
||||
[gutterHeight, gutterMargin, props.height, props.standalone],
|
||||
);
|
||||
|
||||
const [tableSectionHeight, setTableSectionHeight] = useState(
|
||||
@ -283,34 +266,12 @@ const ExploreChartPanel = props => {
|
||||
return standaloneChartBody;
|
||||
}
|
||||
|
||||
const header = (
|
||||
<ConnectedExploreChartHeader
|
||||
ownState={props.ownState}
|
||||
actions={props.actions}
|
||||
can_overwrite={props.can_overwrite}
|
||||
can_download={props.can_download}
|
||||
dashboardId={props.dashboardId}
|
||||
isStarred={props.isStarred}
|
||||
slice={props.slice}
|
||||
sliceName={props.sliceName}
|
||||
table_name={props.table_name}
|
||||
form_data={props.form_data}
|
||||
timeout={props.timeout}
|
||||
chart={props.chart}
|
||||
user={props.user}
|
||||
reports={props.reports}
|
||||
/>
|
||||
);
|
||||
|
||||
const elementStyle = (dimension, elementSize, gutterSize) => ({
|
||||
[dimension]: `calc(${elementSize}% - ${gutterSize + gutterMargin}px)`,
|
||||
});
|
||||
|
||||
return (
|
||||
<Styles className="panel panel-default chart-container" ref={chartPanelRef}>
|
||||
<div className="panel-heading" ref={headerRef}>
|
||||
{header}
|
||||
</div>
|
||||
{props.vizType === 'filter_box' ? (
|
||||
panelBody
|
||||
) : (
|
||||
|
@ -60,6 +60,7 @@ import {
|
||||
LOG_ACTIONS_MOUNT_EXPLORER,
|
||||
LOG_ACTIONS_CHANGE_EXPLORE_CONTROLS,
|
||||
} from '../../../logger/LogUtils';
|
||||
import ConnectedExploreChartHeader from '../ExploreChartHeader';
|
||||
|
||||
const propTypes = {
|
||||
...ExploreChartPanel.propTypes,
|
||||
@ -82,33 +83,59 @@ const propTypes = {
|
||||
vizType: PropTypes.string,
|
||||
};
|
||||
|
||||
const Styles = styled.div`
|
||||
background: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
const ExploreContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const ExploreHeaderContainer = styled.div`
|
||||
${({ theme }) => css`
|
||||
background-color: ${theme.colors.grayscale.light5};
|
||||
height: ${theme.gridUnit * 16}px;
|
||||
padding: 0 ${theme.gridUnit * 4}px;
|
||||
|
||||
.editable-title {
|
||||
overflow: hidden;
|
||||
|
||||
& > input[type='button'],
|
||||
& > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const ExplorePanelContainer = styled.div`
|
||||
${({ theme }) => css`
|
||||
background: ${theme.colors.grayscale.light5};
|
||||
text-align: left;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
flex-wrap: nowrap;
|
||||
flex-basis: 100vh;
|
||||
align-items: stretch;
|
||||
border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
|
||||
border-top: 1px solid ${theme.colors.grayscale.light2};
|
||||
.explore-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: ${({ theme }) => 2 * theme.gridUnit}px 0;
|
||||
padding: ${theme.gridUnit * 2}px 0;
|
||||
max-height: 100%;
|
||||
}
|
||||
.data-source-selection {
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
padding: ${({ theme }) => 2 * theme.gridUnit}px 0;
|
||||
border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
|
||||
background-color: ${theme.colors.grayscale.light5};
|
||||
padding: ${theme.gridUnit * 2}px 0;
|
||||
border-right: 1px solid ${theme.colors.grayscale.light2};
|
||||
}
|
||||
.main-explore-content {
|
||||
flex: 1;
|
||||
min-width: ${({ theme }) => theme.gridUnit * 128}px;
|
||||
border-left: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
|
||||
min-width: ${theme.gridUnit * 128}px;
|
||||
border-left: 1px solid ${theme.colors.grayscale.light2};
|
||||
.panel {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@ -121,12 +148,12 @@ const Styles = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 ${({ theme }) => 2 * theme.gridUnit}px;
|
||||
padding: 0 ${theme.gridUnit * 4}px;
|
||||
justify-content: space-between;
|
||||
.horizontal-text {
|
||||
text-transform: uppercase;
|
||||
color: ${({ theme }) => theme.colors.grayscale.light1};
|
||||
font-size: ${({ theme }) => 4 * theme.typography.sizes.s};
|
||||
color: ${theme.colors.grayscale.light1};
|
||||
font-size: ${theme.typography.sizes.s * 4};
|
||||
}
|
||||
}
|
||||
.no-show {
|
||||
@ -138,13 +165,14 @@ const Styles = styled.div`
|
||||
}
|
||||
.sidebar {
|
||||
height: 100%;
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light4};
|
||||
padding: ${({ theme }) => 2 * theme.gridUnit}px;
|
||||
width: ${({ theme }) => 8 * theme.gridUnit}px;
|
||||
background-color: ${theme.colors.grayscale.light4};
|
||||
padding: ${theme.gridUnit * 2}px;
|
||||
width: ${theme.gridUnit * 8}px;
|
||||
}
|
||||
.callpase-icon > svg {
|
||||
color: ${({ theme }) => theme.colors.primary.base};
|
||||
color: ${theme.colors.primary.base};
|
||||
}
|
||||
`};
|
||||
`;
|
||||
|
||||
const getWindowSize = () => ({
|
||||
@ -230,7 +258,7 @@ function ExploreViewContainer(props) {
|
||||
|
||||
const theme = useTheme();
|
||||
const width = `${windowSize.width}px`;
|
||||
const navHeight = props.standalone ? 0 : 90;
|
||||
const navHeight = props.standalone ? 0 : 120;
|
||||
const height = props.forcedHeight
|
||||
? `${props.forcedHeight}px`
|
||||
: `${windowSize.height - navHeight}px`;
|
||||
@ -515,7 +543,26 @@ function ExploreViewContainer(props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Styles id="explore-container" height={height}>
|
||||
<ExploreContainer>
|
||||
<ExploreHeaderContainer>
|
||||
<ConnectedExploreChartHeader
|
||||
ownState={props.ownState}
|
||||
actions={props.actions}
|
||||
canOverwrite={props.can_overwrite}
|
||||
canDownload={props.can_download}
|
||||
dashboardId={props.dashboardId}
|
||||
isStarred={props.isStarred}
|
||||
slice={props.slice}
|
||||
sliceName={props.sliceName}
|
||||
table_name={props.table_name}
|
||||
formData={props.form_data}
|
||||
timeout={props.timeout}
|
||||
chart={props.chart}
|
||||
user={props.user}
|
||||
reports={props.reports}
|
||||
/>
|
||||
</ExploreHeaderContainer>
|
||||
<ExplorePanelContainer id="explore-container">
|
||||
<Global
|
||||
styles={css`
|
||||
.navbar {
|
||||
@ -652,7 +699,8 @@ function ExploreViewContainer(props) {
|
||||
>
|
||||
{renderChartContainer()}
|
||||
</div>
|
||||
</Styles>
|
||||
</ExplorePanelContainer>
|
||||
</ExploreContainer>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,8 @@ const Styles = styled.div`
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
|
||||
padding: ${({ theme }) => 2 * theme.gridUnit}px;
|
||||
padding: ${({ theme }) => 4 * theme.gridUnit}px;
|
||||
padding-right: ${({ theme }) => 2 * theme.gridUnit}px;
|
||||
}
|
||||
.error-alert {
|
||||
margin: ${({ theme }) => 2 * theme.gridUnit}px;
|
||||
|
Loading…
Reference in New Issue
Block a user