superset/superset-frontend/src/components/EditableTitle.tsx
David Aaron Suddjian 18658f45be
feat(dashboards): Filter status indicators (#10936)
* Initial commit of new filters badge.

* refactor applied/rejected filters code

* finished filter indicators

* filter badge tested

* unnecessary imports

* formatting and types

* fixes

* license

* code quality tweaks

* state management for showing focused filter scope

* clean up filter key extraction code

* remove unnecessary styles

* temp css to demonstrate highlighting

* fix focused filter logic

* no more color badges

* new toys for highlighting dash components (#11144)

* tweak style for the filter chart when filter is focused

* style: Filters p0 css2 (#11151)

* nixing background tweak

* src paths

* another quick theme color

* src paths, adjusting pill icon color, changing icons, showing applied/busted counts

* linting stuff

* fixing and tweaking tests

* show filter indicator when filters are not active

* chart title bar cleanup

* open the right panel when popover opens

* unused import

* fix EditableTitle tests

* margin on dashboard header

* show the chart dropdown menu

* fix blur filter breaking dropdowns

* style tweak - no pointer events when irrelevant charts are blurred

* fix box shadow on filter highlight

* it's an array

* attempt fixing e2e

* style: filters p0 icon churn (#11215)

* new filters icon

* icon styling

* bigger icons in list views

* better sizing of table actions and favStars

* more icon sizing...

* fixing more button size jankiness

* linting

* Filters performance (#11255)

* fixing time filter "ok" button

* making unset filter menu collapsible

* sort alphabetically

* fix highlighting when removing items

* try a flex layout (for browser render perf)

* more specific transitioning

* temp: comment out some code as a test

* temp: comment out more code

* temp: remove possibly expensive computations from ChartHolder

* Revert "temp: comment out some code as a test"

This reverts commit 309b880e90.

* Revert "temp: comment out more code"

This reverts commit 64c88b2cba.

* Revert "temp: remove possibly expensive computations from ChartHolder"

This reverts commit 37ce0214f0.

* experiment: upgrade react-select to v3

* Revert "experiment: upgrade react-select to v3"

This reverts commit c3972ba486.

* fix the damn problem

* remove code used for testing purposes

* awful hack to avoid adding a class to a container

* approaching infinity... and not beyond!

* fix ref forwarding

* add theme to tests as necessary

* fix(extra-filters): add logic for identifying applied extra filters (#11325)

* fix: use dashboard id for stable cache key (#11293)

* fix: button translations missing (#11187)

* button translations missing

* blank space before text

* feat: update time_compare description and choices (#11294)

* feat: update time_compare description and choices

* Update sections.jsx

* fix(extra-filters): add logic for identifying applied extra filters

* lint

Co-authored-by: Jesse Yang <jesse.yang@airbnb.com>
Co-authored-by: rubenSastre <ruben.sastre@decathlon.com>
Co-authored-by: Erik Ritter <erik.ritter@airbnb.com>

* address design feedback

* slight tweak to panel logic, keep panels open that user has opened

* rearrange code to be more graceful

* fix: bump superset-ui/core (#11385)

* use is_dttm instead of is_temporal

* types, names

* only show unset filter panel if there are unset filters

* fix highlighting the filter control

* fix filterbox layout

* translations

* fix cypress

* actually add the test attribute

* Update superset-frontend/src/dashboard/components/DashboardBuilder.jsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/dashboard/components/DashboardBuilder.jsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* formatting

* add link comment to hack

* Update superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* stop importing lodash

* Update superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* skip broken test

* Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* adjust colors of titles

* linting

* no indicators when chart is loading

* support all time fields

* fix lock file

Co-authored-by: Natalie Ruhe <natalie@preset.io>
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
Co-authored-by: Jesse Yang <jesse.yang@airbnb.com>
Co-authored-by: rubenSastre <ruben.sastre@decathlon.com>
Co-authored-by: Erik Ritter <erik.ritter@airbnb.com>
Co-authored-by: Ville Brofeldt <ville.v.brofeldt@gmail.com>
2020-10-28 15:46:24 -07:00

213 lines
5.8 KiB
TypeScript

/**
* 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, { useEffect, useState, useRef } from 'react';
import cx from 'classnames';
import { t } from '@superset-ui/core';
import TooltipWrapper from './TooltipWrapper';
interface EditableTitleProps {
canEdit?: boolean;
emptyText?: string;
extraClasses?: Array<string> | string;
multiLine?: boolean;
noPermitTooltip?: string;
onSaveTitle: (arg0: string) => {};
showTooltip?: boolean;
style?: object;
title: string;
}
export default function EditableTitle({
canEdit = false,
emptyText,
extraClasses,
multiLine = false,
noPermitTooltip,
onSaveTitle,
showTooltip = true,
style,
title,
}: EditableTitleProps) {
const [isEditing, setIsEditing] = useState(false);
const [currentTitle, setCurrentTitle] = useState(title);
const [lastTitle, setLastTitle] = useState(title);
const [
contentBoundingRect,
setContentBoundingRect,
] = useState<DOMRect | null>(null);
// Used so we can access the DOM element if a user clicks on this component.
const contentRef = useRef<any | HTMLInputElement | HTMLTextAreaElement>();
useEffect(() => {
if (title !== currentTitle) {
setLastTitle(currentTitle);
setCurrentTitle(title);
}
}, [title]);
function handleClick() {
if (!canEdit || isEditing) {
return;
}
// For multi-line values, save the actual rendered size of the displayed text.
// Later, if a textarea is constructed for editing the value, we'll need this.
const contentBounding = contentRef.current
? contentRef.current.getBoundingClientRect()
: null;
setIsEditing(true);
setContentBoundingRect(contentBounding);
}
function handleBlur() {
const formattedTitle = currentTitle.trim();
if (!canEdit) {
return;
}
setIsEditing(false);
if (!formattedTitle.length) {
setCurrentTitle(lastTitle);
return;
}
if (lastTitle !== formattedTitle) {
setLastTitle(formattedTitle);
}
if (title !== formattedTitle) {
onSaveTitle(formattedTitle);
}
}
// this entire method exists to support using EditableTitle as the title of a
// react-bootstrap Tab, as a workaround for this line in react-bootstrap https://goo.gl/ZVLmv4
//
// tl;dr when a Tab EditableTitle is being edited, typically the Tab it's within has been
// clicked and is focused/active. for accessibility, when focused the Tab <a /> intercepts
// the ' ' key (among others, including all arrows) and onChange() doesn't fire. somehow
// keydown is still called so we can detect this and manually add a ' ' to the current title
function handleKeyDown(event: any) {
if (event.key === ' ') {
event.stopPropagation();
}
}
function handleChange(ev: any) {
if (!canEdit) {
return;
}
setCurrentTitle(ev.target.value);
}
function handleKeyPress(ev: any) {
if (ev.key === 'Enter') {
ev.preventDefault();
handleBlur();
}
}
let value: string | undefined;
if (currentTitle) {
value = currentTitle;
} else if (!isEditing) {
value = emptyText;
}
// Construct an inline style based on previously-saved height of the rendered label. Only
// used in multi-line contexts.
const editStyle =
isEditing && contentBoundingRect
? { height: `${contentBoundingRect.height}px` }
: undefined;
// Create a textarea when we're editing a multi-line value, otherwise create an input (which may
// be text or a button).
let titleComponent =
multiLine && isEditing ? (
<textarea
data-test="editable-title-input"
ref={contentRef}
required
value={value}
className={!title ? 'text-muted' : undefined}
onKeyDown={handleKeyDown}
onChange={handleChange}
onBlur={handleBlur}
onClick={handleClick}
onKeyPress={handleKeyPress}
style={editStyle}
/>
) : (
<input
data-test="editable-title-input"
ref={contentRef}
required
type={isEditing ? 'text' : 'button'}
value={value}
className={!title ? 'text-muted' : undefined}
onKeyDown={handleKeyDown}
onChange={handleChange}
onBlur={handleBlur}
onClick={handleClick}
onKeyPress={handleKeyPress}
/>
);
if (showTooltip && !isEditing) {
titleComponent = (
<TooltipWrapper
label="title"
tooltip={
canEdit
? t('click to edit')
: noPermitTooltip ||
t("You don't have the rights to alter this title.")
}
>
{titleComponent}
</TooltipWrapper>
);
}
if (!canEdit) {
// don't actually want an input in this case
titleComponent = (
<span data-test="editable-title-input" title={value}>
{value}
</span>
);
}
return (
<span
data-test="editable-title"
className={cx(
'editable-title',
extraClasses,
canEdit && 'editable-title--editable',
isEditing && 'editable-title--editing',
)}
style={style}
>
{titleComponent}
</span>
);
}