mirror of
https://github.com/apache/superset.git
synced 2026-06-03 14:49:23 +00:00
207 lines
6.1 KiB
TypeScript
207 lines
6.1 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 { ensureIsArray, t } from '@superset-ui/core';
|
|
import AntdSelect, { LabeledValue as AntdLabeledValue } from 'antd/lib/select';
|
|
import React, { ReactElement, RefObject } from 'react';
|
|
import { DownOutlined, SearchOutlined } from '@ant-design/icons';
|
|
import { StyledHelperText, StyledLoadingText, StyledSpin } from './styles';
|
|
import { LabeledValue, RawValue, SelectOptionsType, V } from './types';
|
|
|
|
const { Option } = AntdSelect;
|
|
|
|
export const SELECT_ALL_VALUE: RawValue = 'Select All';
|
|
export const selectAllOption = {
|
|
value: SELECT_ALL_VALUE,
|
|
label: String(SELECT_ALL_VALUE),
|
|
};
|
|
|
|
export function isObject(value: unknown): value is Record<string, unknown> {
|
|
return (
|
|
value !== null &&
|
|
typeof value === 'object' &&
|
|
Array.isArray(value) === false
|
|
);
|
|
}
|
|
|
|
export function isLabeledValue(value: unknown): value is AntdLabeledValue {
|
|
return isObject(value) && 'value' in value && 'label' in value;
|
|
}
|
|
|
|
export function getValue(
|
|
option: string | number | AntdLabeledValue | null | undefined,
|
|
) {
|
|
return isLabeledValue(option) ? option.value : option;
|
|
}
|
|
|
|
export function hasOption(
|
|
value: V,
|
|
options?: V | LabeledValue | (V | LabeledValue)[],
|
|
checkLabel = false,
|
|
): boolean {
|
|
const optionsArray = ensureIsArray(options);
|
|
// When comparing the values we use the equality
|
|
// operator to automatically convert different types
|
|
return (
|
|
optionsArray.find(
|
|
x =>
|
|
// eslint-disable-next-line eqeqeq
|
|
x == value ||
|
|
(isObject(x) &&
|
|
// eslint-disable-next-line eqeqeq
|
|
(('value' in x && x.value == value) ||
|
|
(checkLabel && 'label' in x && x.label === value))),
|
|
) !== undefined
|
|
);
|
|
}
|
|
|
|
/**
|
|
* It creates a comparator to check for a specific property.
|
|
* Can be used with string and number property values.
|
|
* */
|
|
export const propertyComparator =
|
|
(property: string) => (a: AntdLabeledValue, b: AntdLabeledValue) => {
|
|
if (typeof a[property] === 'string' && typeof b[property] === 'string') {
|
|
return a[property].localeCompare(b[property]);
|
|
}
|
|
return (a[property] as number) - (b[property] as number);
|
|
};
|
|
|
|
export const sortSelectedFirstHelper = (
|
|
a: AntdLabeledValue,
|
|
b: AntdLabeledValue,
|
|
selectValue:
|
|
| string
|
|
| number
|
|
| RawValue[]
|
|
| AntdLabeledValue
|
|
| AntdLabeledValue[]
|
|
| undefined,
|
|
) =>
|
|
selectValue && a.value !== undefined && b.value !== undefined
|
|
? Number(hasOption(b.value, selectValue)) -
|
|
Number(hasOption(a.value, selectValue))
|
|
: 0;
|
|
|
|
export const sortComparatorWithSearchHelper = (
|
|
a: AntdLabeledValue,
|
|
b: AntdLabeledValue,
|
|
inputValue: string,
|
|
sortCallback: (a: AntdLabeledValue, b: AntdLabeledValue) => number,
|
|
sortComparator: (
|
|
a: AntdLabeledValue,
|
|
b: AntdLabeledValue,
|
|
search?: string | undefined,
|
|
) => number,
|
|
) => sortCallback(a, b) || sortComparator(a, b, inputValue);
|
|
|
|
export const sortComparatorForNoSearchHelper = (
|
|
a: AntdLabeledValue,
|
|
b: AntdLabeledValue,
|
|
sortCallback: (a: AntdLabeledValue, b: AntdLabeledValue) => number,
|
|
sortComparator: (
|
|
a: AntdLabeledValue,
|
|
b: AntdLabeledValue,
|
|
search?: string | undefined,
|
|
) => number,
|
|
) => sortCallback(a, b) || sortComparator(a, b, '');
|
|
|
|
// use a function instead of component since every rerender of the
|
|
// Select component will create a new component
|
|
export const getSuffixIcon = (
|
|
isLoading: boolean | undefined,
|
|
showSearch: boolean,
|
|
isDropdownVisible: boolean,
|
|
) => {
|
|
if (isLoading) {
|
|
return <StyledSpin size="small" />;
|
|
}
|
|
if (showSearch && isDropdownVisible) {
|
|
return <SearchOutlined />;
|
|
}
|
|
return <DownOutlined />;
|
|
};
|
|
|
|
export const dropDownRenderHelper = (
|
|
originNode: ReactElement & { ref?: RefObject<HTMLElement> },
|
|
isDropdownVisible: boolean,
|
|
isLoading: boolean | undefined,
|
|
optionsLength: number,
|
|
helperText: string | undefined,
|
|
errorComponent?: JSX.Element,
|
|
) => {
|
|
if (!isDropdownVisible) {
|
|
originNode.ref?.current?.scrollTo({ top: 0 });
|
|
}
|
|
if (isLoading && optionsLength === 0) {
|
|
return <StyledLoadingText>{t('Loading...')}</StyledLoadingText>;
|
|
}
|
|
if (errorComponent) {
|
|
return errorComponent;
|
|
}
|
|
return (
|
|
<>
|
|
{helperText && (
|
|
<StyledHelperText role="note">{helperText}</StyledHelperText>
|
|
)}
|
|
{originNode}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export const handleFilterOptionHelper = (
|
|
search: string,
|
|
option: AntdLabeledValue,
|
|
optionFilterProps: string[],
|
|
filterOption: boolean | Function,
|
|
) => {
|
|
if (typeof filterOption === 'function') {
|
|
return filterOption(search, option);
|
|
}
|
|
|
|
if (filterOption) {
|
|
const searchValue = search.trim().toLowerCase();
|
|
if (optionFilterProps?.length) {
|
|
return optionFilterProps.some(prop => {
|
|
const optionProp = option?.[prop]
|
|
? String(option[prop]).trim().toLowerCase()
|
|
: '';
|
|
return optionProp.includes(searchValue);
|
|
});
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
export const hasCustomLabels = (options: SelectOptionsType) =>
|
|
options?.some(opt => !!opt?.customLabel);
|
|
|
|
export const renderSelectOptions = (options: SelectOptionsType) =>
|
|
options.map(opt => {
|
|
const isOptObject = typeof opt === 'object';
|
|
const label = isOptObject ? opt?.label || opt.value : opt;
|
|
const value = isOptObject ? opt.value : opt;
|
|
const { customLabel, ...optProps } = opt;
|
|
return (
|
|
<Option {...optProps} key={value} label={label} value={value}>
|
|
{isOptObject && customLabel ? customLabel : label}
|
|
</Option>
|
|
);
|
|
});
|