cleanup Lens repo with tighter linting

Signed-off-by: Sebastian Malton <smalton@mirantis.com>
This commit is contained in:
Sebastian Malton committed 2020-07-09 17:00:23 -04:00
1 parent e468105143
commit b1ff34879a
470 files changed
+8447 -7443

No files matched your search

@@ -1,4 +1,4 @@
import React from "react"
import React from "react";
import { Icon, IconProps } from "../icon";
import { FilterType } from "./page-filters.store";
@@ -6,7 +6,7 @@ interface Props extends Partial<IconProps> {
type: FilterType;
}
export function FilterIcon(props: Props) {
export function FilterIcon(props: Props): JSX.Element {
const { type, ...iconProps } = props;
switch (type) {
case FilterType.NAMESPACE:
@@ -16,6 +16,6 @@ export function FilterIcon(props: Props) {
return <Icon small material="search" {...iconProps}/>;
default:
return <Icon small material="filter_list" {...iconProps}/>
return <Icon small material="filter_list" {...iconProps}/>;
}
}
@@ -1 +1 @@
export * from "./item-list-layout"
export * from "./item-list-layout";
@@ -1,13 +1,13 @@
import "./item-list-layout.scss"
import groupBy from "lodash/groupBy"
import "./item-list-layout.scss";
import groupBy from "lodash/groupBy";
import React, { ReactNode } from "react";
import { computed, observable, reaction, toJS, when } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { Plural, Trans } from "@lingui/macro";
import { ConfirmDialog, IConfirmDialogParams } from "../confirm-dialog";
import { ConfirmDialog, ConfirmDialogParams } from "../confirm-dialog";
import { SortingCallback, Table, TableCell, TableCellProps, TableHead, TableProps, TableRow, TableRowProps } from "../table";
import { autobind, createStorage, cssNames, IClassName, isReactNode, noop, prevDefault, stopPropagation } from "../../utils";
import { autobind, StorageHelper, cssNames, ClassName, isReactNode, noop, prevDefault, stopPropagation } from "../../utils";
import { AddRemoveButtons, AddRemoveButtonsProps } from "../add-remove-buttons";
import { NoItems } from "../no-items";
import { Spinner } from "../spinner";
@@ -25,7 +25,7 @@ import { themeStore } from "../../theme.store";
export type SearchFilter<T extends ItemObject = any> = (item: T) => string | number | (string | number)[];
export type ItemsFilter<T extends ItemObject = any> = (items: T[]) => T[];
interface IHeaderPlaceholders {
interface HeaderPlaceholders {
title: ReactNode;
search: ReactNode;
filters: ReactNode;
@@ -33,7 +33,7 @@ interface IHeaderPlaceholders {
}
export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
className: IClassName;
className: ClassName;
store: ItemStore<T>;
dependentStores?: ItemStore[];
isClusterScoped?: boolean;
@@ -43,9 +43,9 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
// header (title, filtering, searching, etc.)
showHeader?: boolean;
headerClassName?: IClassName;
headerClassName?: ClassName;
renderHeaderTitle?: ReactNode | ((parent: ItemListLayout) => ReactNode);
customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode;
customizeHeader?: (placeholders: HeaderPlaceholders, content: ReactNode) => Partial<HeaderPlaceholders> | ReactNode;
// items list configuration
isReady?: boolean; // show loading indicator while not ready
@@ -67,7 +67,7 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
onDetails?: (item: T) => void;
// other
customizeRemoveDialog?: (selectedItems: T[]) => Partial<IConfirmDialogParams>;
customizeRemoveDialog?: (selectedItems: T[]) => Partial<ConfirmDialogParams>;
renderFooter?: (parent: ItemListLayout) => React.ReactNode;
}
@@ -103,31 +103,35 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
// keep ui user settings in local storage
const defaultUserSettings = toJS(this.userSettings);
const storage = createStorage<ItemListLayoutUserSettings>("items_list_layout", defaultUserSettings);
const storage = new StorageHelper<ItemListLayoutUserSettings>("items_list_layout", defaultUserSettings);
Object.assign(this.userSettings, storage.get()); // restore
disposeOnUnmount(this, [
reaction(() => toJS(this.userSettings), settings => storage.set(settings)),
]);
}
async componentDidMount() {
async componentDidMount(): Promise<void> {
const { store, dependentStores, isClusterScoped } = this.props;
const stores = [store, ...dependentStores];
if (!isClusterScoped) stores.push(namespaceStore);
if (!isClusterScoped) {
stores.push(namespaceStore);
}
try {
await Promise.all(stores.map(store => store.loadAll()));
const subscriptions = stores.map(store => store.subscribe());
await when(() => this.isUnmounting);
subscriptions.forEach(dispose => dispose()); // unsubscribe all
} catch(error) {
console.log("catched", error)
console.log("catched", error);
}
}
componentWillUnmount() {
componentWillUnmount(): void {
this.isUnmounting = true;
const { store, isSelectable } = this.props;
if (isSelectable) store.resetSelection();
if (isSelectable) {
store.resetSelection();
}
}
private filterCallbacks: { [type: string]: ItemsFilter } = {
@@ -135,13 +139,13 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
const { searchFilters, isSearchable } = this.props;
const search = pageFilters.getValues(FilterType.SEARCH)[0] || "";
if (search && isSearchable && searchFilters) {
const normalizeText = (text: string) => String(text).toLowerCase();
const normalizeText = (text: any): string => String(text).toLowerCase();
const searchTexts = [search].map(normalizeText);
return items.filter(item => {
return searchFilters.some(getTexts => {
const sourceTexts: string[] = [getTexts(item)].flat().map(normalizeText);
return sourceTexts.some(source => searchTexts.some(search => source.includes(search)));
})
});
});
}
return items;
@@ -150,18 +154,18 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
[FilterType.NAMESPACE]: items => {
const filterValues = pageFilters.getValues(FilterType.NAMESPACE);
if (filterValues.length > 0) {
return items.filter(item => filterValues.includes(item.getNs()))
return items.filter(item => filterValues.includes(item.getNs()));
}
return items;
},
}
@computed get isReady() {
@computed get isReady(): boolean {
const { isReady, store } = this.props;
return typeof isReady == "boolean" ? isReady : store.isLoaded;
}
@computed get filters() {
@computed get filters(): Filter[] {
let { activeFilters } = pageFilters;
const { isClusterScoped, isSearchable, searchFilters } = this.props;
if (isClusterScoped) {
@@ -174,16 +178,18 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
}
applyFilters<T>(filters: ItemsFilter[], items: T[]): T[] {
if (!filters || !filters.length) return items;
if (!filters || !filters.length) {
return items;
}
return filters.reduce((items, filter) => filter(items), items);
}
@computed get allItems() {
@computed get allItems(): ItemObject[] {
const { filterItems, store } = this.props;
return this.applyFilters(filterItems, store.items);
}
@computed get items() {
@computed get items(): ItemObject[] {
const { allItems, filters, filterCallbacks } = this;
const filterItems: ItemsFilter[] = [];
const filterGroups = groupBy<Filter>(filters, ({ type }) => type);
@@ -198,7 +204,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
}
@autobind()
getRow(uid: string) {
getRow(uid: string): JSX.Element {
const {
isSelectable, renderTableHeader, renderTableContents, renderItemMenu,
store, hasDetailsView, onDetails,
@@ -206,7 +212,9 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
} = this.props;
const { isSelected } = store;
const item = this.items.find(item => item.getId() == uid);
if (!item) return;
if (!item) {
return;
}
const itemId = item.getId();
return (
<TableRow
@@ -235,7 +243,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
cellProps.className = cssNames(cellProps.className, headCell.className);
}
}
return <TableCell key={index} {...cellProps}/>
return <TableCell key={index} {...cellProps}/>;
})
}
{renderItemMenu && (
@@ -248,7 +256,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
}
@autobind()
removeItemsDialog() {
removeItemsDialog(): void {
const { customizeRemoveDialog, store } = this.props;
const { selectedItems, removeSelectedItems } = store;
const visibleMaxNamesCount = 5;
@@ -268,19 +276,19 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
/>
),
...dialogCustomProps,
})
});
}
renderFilters() {
renderFilters(): JSX.Element {
const { hideFilters } = this.props;
const { isReady, userSettings, filters } = this;
if (!isReady || !filters.length || hideFilters || !userSettings.showAppliedFilters) {
return;
}
return <PageFiltersList filters={filters}/>
return <PageFiltersList filters={filters}/>;
}
renderNoItems() {
renderNoItems(): JSX.Element {
const { allItems, items, filters } = this;
const allItemsCount = allItems.length;
const itemsCount = items.length;
@@ -290,17 +298,17 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
<NoItems>
<Trans>No items found.</Trans>
<p>
<a onClick={() => pageFilters.reset()} className="contrast">
<a onClick={(): void => pageFilters.reset()} className="contrast">
<Trans>Reset filters?</Trans>
</a>
</p>
</NoItems>
)
);
}
return <NoItems/>
return <NoItems/>;
}
renderHeaderContent(placeholders: IHeaderPlaceholders): ReactNode {
renderHeaderContent(placeholders: HeaderPlaceholders): ReactNode {
const { title, filters, search, info } = placeholders;
return (
<>
@@ -311,21 +319,23 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
{filters}
{search}
</>
)
);
}
renderInfo() {
renderInfo(): JSX.Element {
const { allItems, items, isReady, userSettings, filters } = this;
const allItemsCount = allItems.length;
const itemsCount = items.length;
const isFiltered = isReady && filters.length > 0;
if (isFiltered) {
const toggleFilters = () => userSettings.showAppliedFilters = !userSettings.showAppliedFilters;
const toggleFilters = (): void => {
userSettings.showAppliedFilters = !userSettings.showAppliedFilters;
};
return (
<Trans>
<a onClick={toggleFilters}>Filtered</a>: {itemsCount} / {allItemsCount}
</Trans>
)
);
}
return (
<Plural
@@ -336,11 +346,13 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
);
}
renderHeader() {
renderHeader(): JSX.Element {
const { showHeader, customizeHeader, renderHeaderTitle, headerClassName, isClusterScoped } = this.props;
if (!showHeader) return;
if (!showHeader) {
return;
}
const title = typeof renderHeaderTitle === "function" ? renderHeaderTitle(this) : renderHeaderTitle;
const placeholders: IHeaderPlaceholders = {
const placeholders: HeaderPlaceholders = {
title: <h5 className="title">{title}</h5>,
info: this.renderInfo(),
filters: <>
@@ -350,17 +362,16 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
}}/>
</>,
search: <SearchInput/>,
}
};
let header = this.renderHeaderContent(placeholders);
if (customizeHeader) {
const modifiedHeader = customizeHeader(placeholders, header);
if (isReactNode(modifiedHeader)) {
header = modifiedHeader;
}
else {
} else {
header = this.renderHeaderContent({
...placeholders,
...modifiedHeader as IHeaderPlaceholders,
...modifiedHeader as HeaderPlaceholders,
});
}
}
@@ -368,10 +379,10 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
<div className={cssNames("header flex gaps align-center", headerClassName)}>
{header}
</div>
)
);
}
renderList() {
renderList(): JSX.Element {
const {
isSelectable, tableProps = {}, renderTableHeader, renderItemMenu,
store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem
@@ -422,16 +433,16 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
{...addRemoveButtons}
/>
</div>
)
);
}
renderFooter() {
renderFooter(): React.ReactNode {
if (this.props.renderFooter) {
return this.props.renderFooter(this);
}
}
render() {
render(): JSX.Element {
const { className } = this.props;
return (
<div className={cssNames("ItemListLayout flex column", className)}>
@@ -1,5 +1,5 @@
import "./page-filters-list.scss"
import React from "react"
import "./page-filters-list.scss";
import React from "react";
import { observer } from "mobx-react";
import { Trans } from "@lingui/macro";
import { Badge } from "../badge";
@@ -20,10 +20,10 @@ export class PageFiltersList extends React.Component<Props> {
}
}
reset = () => pageFilters.reset()
remove = (filter: Filter) => pageFilters.removeFilter(filter)
reset = (): void => pageFilters.reset()
remove = (filter: Filter): void => pageFilters.removeFilter(filter)
renderContent() {
renderContent(): JSX.Element {
const { filters } = this.props;
if (!filters.length) {
return null;
@@ -51,23 +51,23 @@ export class PageFiltersList extends React.Component<Props> {
<Icon
small
material="close"
onClick={() => this.remove(filter)}
onClick={(): void => this.remove(filter)}
/>
</>
)}
/>
)
);
})}
</div>
</>
)
);
}
render() {
render(): JSX.Element {
return (
<div className="PageFiltersList">
{this.renderContent()}
</div>
)
);
}
}
@@ -1,4 +1,4 @@
import React from "react"
import React from "react";
import { observer } from "mobx-react";
import { computed } from "mobx";
import { t, Trans } from "@lingui/macro";
@@ -28,7 +28,7 @@ export class PageFiltersSelect extends React.Component<Props> {
disableFilters: {},
}
@computed get groupedOptions() {
@computed get groupedOptions(): GroupSelectOption<SelectOptionFilter>[] {
const options: GroupSelectOption<SelectOptionFilter>[] = [];
const { disableFilters } = this.props;
if (!disableFilters[FilterType.NAMESPACE]) {
@@ -42,9 +42,9 @@ export class PageFiltersSelect extends React.Component<Props> {
value: name,
icon: <Icon small material="layers"/>,
selected: selectedValues.includes(name),
}
};
})
})
});
}
return options;
}
@@ -53,10 +53,10 @@ export class PageFiltersSelect extends React.Component<Props> {
return this.groupedOptions.reduce((options, optGroup) => {
options.push(...optGroup.options);
return options;
}, [])
}, []);
}
private formatLabel = (option: SelectOptionFilter) => {
private formatLabel = (option: SelectOptionFilter): JSX.Element => {
const { label, value, type, selected } = option;
return (
<div className="flex gaps">
@@ -67,30 +67,29 @@ export class PageFiltersSelect extends React.Component<Props> {
);
}
private onSelect = (option: SelectOptionFilter) => {
private onSelect = (option: SelectOptionFilter): void => {
const { type, value, selected } = option;
const { addFilter, removeFilter } = pageFilters;
const filter = { type, value };
if (!selected) {
addFilter(filter);
}
else {
} else {
removeFilter(filter);
}
}
render() {
render(): JSX.Element {
const { groupedOptions, formatLabel, onSelect, options } = this;
if (!options.length && this.props.allowEmpty) {
return null;
}
const { allowEmpty, disableFilters, ...selectProps } = this.props;
const { allowEmpty: _allowEmpty, disableFilters: _disableFilters, ...selectProps } = this.props;
const selectedOptions = options.filter(opt => opt.selected);
return (
<Select
{...selectProps}
placeholder={_i18n._(t`Filters (${selectedOptions.length}/${options.length})`)}
noOptionsMessage={() => _i18n._(t`No filters available.`)}
noOptionsMessage={(): string=> _i18n._(t`No filters available.`)}
autoConvertOptions={false}
tabSelectsValue={false}
controlShouldRenderValue={false}
@@ -98,6 +97,6 @@ export class PageFiltersSelect extends React.Component<Props> {
formatOptionLabel={formatLabel}
onChange={onSelect}
/>
)
);
}
}
@@ -18,7 +18,7 @@ export class PageFiltersStore {
protected filters = observable.array<Filter>([], { deep: false });
protected isDisabled = observable.map<FilterType, boolean>();
@computed get activeFilters() {
@computed get activeFilters(): Filter[] {
return this.filters.filter(filter => !this.isDisabled.get(filter.type));
}
@@ -27,7 +27,7 @@ export class PageFiltersStore {
this.syncWithContextNamespace();
}
protected syncWithContextNamespace() {
protected syncWithContextNamespace(): () => void {
const disposers = [
reaction(() => this.getValues(FilterType.NAMESPACE), filteredNs => {
if (filteredNs.length !== namespaceStore.contextNs.length) {
@@ -47,10 +47,10 @@ export class PageFiltersStore {
fireImmediately: true
})
];
return () => disposers.forEach(dispose => dispose());
return (): void => disposers.forEach(dispose => dispose());
}
protected syncWithGlobalSearch() {
protected syncWithGlobalSearch(): () => void {
const disposers = [
reaction(() => this.getValues(FilterType.SEARCH)[0], setSearch),
reaction(() => getSearch(), search => {
@@ -65,50 +65,53 @@ export class PageFiltersStore {
fireImmediately: true
})
];
return () => disposers.forEach(dispose => dispose());
return (): void => disposers.forEach(dispose => dispose());
}
addFilter(filter: Filter, begin = false) {
if (begin) this.filters.unshift(filter);
else {
addFilter(filter: Filter, begin = false): void {
if (begin) {
this.filters.unshift(filter);
} else {
this.filters.push(filter);
}
}
removeFilter(filter: Filter) {
removeFilter(filter: Filter): void {
if (!this.filters.remove(filter)) {
const filterCopy = this.filters.find(f => f.type === filter.type && f.value === filter.value);
if (filterCopy) this.filters.remove(filterCopy);
if (filterCopy) {
this.filters.remove(filterCopy);
}
}
}
getByType(type: FilterType, value?: any): Filter {
return this.filters.find(filter => filter.type === type && (
arguments.length > 1 ? filter.value === value : true
))
));
}
getValues(type: FilterType) {
getValues(type: FilterType): string[] {
return this.filters
.filter(filter => filter.type === type)
.map(filter => filter.value);
}
isEnabled(type: FilterType) {
isEnabled(type: FilterType): boolean {
return !this.isDisabled.get(type);
}
disable(type: FilterType | FilterType[]) {
disable(type: FilterType | FilterType[]): () => () => void {
[type].flat().forEach(type => this.isDisabled.set(type, true));
return () => this.enable(type);
return (): () => void => this.enable(type);
}
enable(type: FilterType | FilterType[]) {
enable(type: FilterType | FilterType[]): () => void{
[type].flat().forEach(type => this.isDisabled.delete(type));
return () => this.disable(type);
return (): () => void => this.disable(type);
}
reset() {
reset(): void {
this.filters.length = 0;
this.isDisabled.clear();
}