1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Export more UI components via extensions-api (#1285)

* export more UI components via extensions-api, touches #1277 #1284

Signed-off-by: Roman <ixrock@gmail.com>

* fix build, added table + notification-store exports

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-11-09 15:16:32 +02:00 committed by GitHub
parent 85f0ef8c2e
commit f69f8c793f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 138 additions and 120 deletions

View File

@ -1,23 +1,38 @@
// TODO: add more common re-usable UI components + refactor interfaces (Props -> ComponentProps) // Common UI components
export * from "../../renderer/components/icon" // layouts
export * from "../../renderer/components/checkbox"
export * from "../../renderer/components/tooltip"
export * from "../../renderer/components/button"
export * from "../../renderer/components/tabs"
export * from "../../renderer/components/badge"
export * from "../../renderer/components/layout/page-layout" export * from "../../renderer/components/layout/page-layout"
export * from "../../renderer/components/layout/wizard-layout"
export * from "../../renderer/components/layout/tab-layout"
// form-controls
export * from "../../renderer/components/button"
export * from "../../renderer/components/checkbox"
export * from "../../renderer/components/radio"
export * from "../../renderer/components/select"
export * from "../../renderer/components/slider"
export * from "../../renderer/components/input/input"
// other components
export * from "../../renderer/components/icon"
export * from "../../renderer/components/tooltip"
export * from "../../renderer/components/tabs"
export * from "../../renderer/components/table"
export * from "../../renderer/components/badge"
export * from "../../renderer/components/drawer" export * from "../../renderer/components/drawer"
export * from "../../renderer/components/dialog"
export * from "../../renderer/components/confirm-dialog";
export * from "../../renderer/components/line-progress"
export * from "../../renderer/components/menu"
export * from "../../renderer/components/notifications"
export * from "../../renderer/components/spinner"
export * from "../../renderer/components/stepper"
// kube helpers // kube helpers
export { KubeObjectDetailsProps, KubeObjectMenuProps } from "../../renderer/components/kube-object" export * from "../../renderer/components/kube-object"
export { KubeObjectMeta } from "../../renderer/components/kube-object/kube-object-meta" export * from "../../renderer/components/+events/kube-event-details"
export { KubeObjectListLayout, KubeObjectListLayoutProps } from "../../renderer/components/kube-object/kube-object-list-layout";
export { KubeEventDetails } from "../../renderer/components/+events/kube-event-details"
// specific exports // specific exports
export { ConfirmDialog } from "../../renderer/components/confirm-dialog"; export * from "../../renderer/components/status-brick";
export { MenuItem, SubMenu } from "../../renderer/components/menu";
export { StatusBrick } from "../../renderer/components/status-brick";
export { terminalStore, createTerminalTab } from "../../renderer/components/dock/terminal.store"; export { terminalStore, createTerminalTab } from "../../renderer/components/dock/terminal.store";
export { createPodLogsTab } from "../../renderer/components/dock/pod-logs.store"; export { createPodLogsTab } from "../../renderer/components/dock/pod-logs.store";

View File

@ -10,7 +10,7 @@ import { KubeObject } from "../../api/kube-object";
import { ICRDRouteParams } from "./crd.route"; import { ICRDRouteParams } from "./crd.route";
import { autorun, computed } from "mobx"; import { autorun, computed } from "mobx";
import { crdStore } from "./crd.store"; import { crdStore } from "./crd.store";
import { SortingCallback } from "../table"; import { TableSortCallback } from "../table";
import { apiManager } from "../../api/api-manager"; import { apiManager } from "../../api/api-manager";
interface Props extends RouteComponentProps<ICRDRouteParams> { interface Props extends RouteComponentProps<ICRDRouteParams> {
@ -50,7 +50,7 @@ export class CrdResources extends React.Component<Props> {
if (!crd) return null; if (!crd) return null;
const isNamespaced = crd.isNamespaced(); const isNamespaced = crd.isNamespaced();
const extraColumns = crd.getPrinterColumns(false); // Cols with priority bigger than 0 are shown in details const extraColumns = crd.getPrinterColumns(false); // Cols with priority bigger than 0 are shown in details
const sortingCallbacks: { [sortBy: string]: SortingCallback } = { const sortingCallbacks: { [sortBy: string]: TableSortCallback } = {
[sortBy.name]: (item: KubeObject) => item.getName(), [sortBy.name]: (item: KubeObject) => item.getName(),
[sortBy.namespace]: (item: KubeObject) => item.getNs(), [sortBy.namespace]: (item: KubeObject) => item.getNs(),
[sortBy.age]: (item: KubeObject) => item.metadata.creationTimestamp, [sortBy.age]: (item: KubeObject) => item.metadata.creationTimestamp,

View File

@ -6,15 +6,14 @@ import { Trans } from "@lingui/macro";
import { KubeObject } from "../../api/kube-object"; import { KubeObject } from "../../api/kube-object";
import { DrawerItem, DrawerTitle } from "../drawer"; import { DrawerItem, DrawerTitle } from "../drawer";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { Icon } from "../icon";
import { eventStore } from "./event.store"; import { eventStore } from "./event.store";
interface Props { export interface KubeEventDetailsProps {
object: KubeObject; object: KubeObject;
} }
@observer @observer
export class KubeEventDetails extends React.Component<Props> { export class KubeEventDetails extends React.Component<KubeEventDetailsProps> {
async componentDidMount() { async componentDidMount() {
eventStore.loadAll(); eventStore.loadAll();
} }

View File

@ -12,7 +12,7 @@ import { Icon } from "../icon";
import { Input } from "../input"; import { Input } from "../input";
import { cssNames, prevDefault } from "../../utils"; import { cssNames, prevDefault } from "../../utils";
import { Button } from "../button"; import { Button } from "../button";
import { isRequired, Validator } from "../input/input_validators"; import { isRequired, InputValidator } from "../input/input_validators";
@observer @observer
export class Workspaces extends React.Component { export class Workspaces extends React.Component {
@ -122,7 +122,7 @@ export class Workspaces extends React.Component {
editing: isEditing, editing: isEditing,
default: isDefault, default: isDefault,
}); });
const existenceValidator: Validator = { const existenceValidator: InputValidator = {
message: () => `Workspace '${name}' already exists`, message: () => `Workspace '${name}' already exists`,
validate: value => !workspaceStore.getByName(value.trim()) validate: value => !workspaceStore.getByName(value.trim())
} }

View File

@ -4,13 +4,13 @@ import React from "react";
import { cssNames } from "../../utils/cssNames"; import { cssNames } from "../../utils/cssNames";
import { TooltipDecoratorProps, withTooltip } from "../tooltip"; import { TooltipDecoratorProps, withTooltip } from "../tooltip";
interface Props extends React.HTMLAttributes<any>, TooltipDecoratorProps { export interface BadgeProps extends React.HTMLAttributes<any>, TooltipDecoratorProps {
small?: boolean; small?: boolean;
label?: React.ReactNode; label?: React.ReactNode;
} }
@withTooltip @withTooltip
export class Badge extends React.Component<Props> { export class Badge extends React.Component<BadgeProps> {
render() { render() {
const { className, label, small, children, ...elemProps } = this.props; const { className, label, small, children, ...elemProps } = this.props;
return <> return <>

View File

@ -2,7 +2,7 @@ import './checkbox.scss'
import React from 'react' import React from 'react'
import { autobind, cssNames } from "../../utils"; import { autobind, cssNames } from "../../utils";
interface Props<T = boolean> { export interface CheckboxProps<T = boolean> {
theme?: "dark" | "light"; theme?: "dark" | "light";
className?: string; className?: string;
label?: React.ReactNode; label?: React.ReactNode;
@ -12,7 +12,7 @@ interface Props<T = boolean> {
onChange?(value: T, evt: React.ChangeEvent<HTMLInputElement>): void; onChange?(value: T, evt: React.ChangeEvent<HTMLInputElement>): void;
} }
export class Checkbox extends React.PureComponent<Props> { export class Checkbox extends React.PureComponent<CheckboxProps> {
private input: HTMLInputElement; private input: HTMLInputElement;
@autobind() @autobind()

View File

@ -9,7 +9,10 @@ import { Button, ButtonProps } from "../button";
import { Dialog, DialogProps } from "../dialog"; import { Dialog, DialogProps } from "../dialog";
import { Icon } from "../icon"; import { Icon } from "../icon";
export interface IConfirmDialogParams { export interface ConfirmDialogProps extends Partial<DialogProps> {
}
export interface ConfirmDialogParams {
ok?: () => void; ok?: () => void;
labelOk?: ReactNode; labelOk?: ReactNode;
labelCancel?: ReactNode; labelCancel?: ReactNode;
@ -19,17 +22,14 @@ export interface IConfirmDialogParams {
cancelButtonProps?: Partial<ButtonProps> cancelButtonProps?: Partial<ButtonProps>
} }
interface Props extends Partial<DialogProps> {
}
@observer @observer
export class ConfirmDialog extends React.Component<Props> { export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
@observable static isOpen = false; @observable static isOpen = false;
@observable.ref static params: IConfirmDialogParams; @observable.ref static params: ConfirmDialogParams;
@observable isSaving = false; @observable isSaving = false;
static open(params: IConfirmDialogParams) { static open(params: ConfirmDialogParams) {
ConfirmDialog.isOpen = true; ConfirmDialog.isOpen = true;
ConfirmDialog.params = params; ConfirmDialog.params = params;
} }
@ -38,14 +38,14 @@ export class ConfirmDialog extends React.Component<Props> {
ConfirmDialog.isOpen = false; ConfirmDialog.isOpen = false;
} }
public defaultParams: IConfirmDialogParams = { public defaultParams: ConfirmDialogParams = {
ok: noop, ok: noop,
labelOk: <Trans>Ok</Trans>, labelOk: <Trans>Ok</Trans>,
labelCancel: <Trans>Cancel</Trans>, labelCancel: <Trans>Cancel</Trans>,
icon: <Icon big material="warning"/>, icon: <Icon big material="warning"/>,
}; };
get params(): IConfirmDialogParams { get params(): ConfirmDialogParams {
return Object.assign({}, this.defaultParams, ConfirmDialog.params); return Object.assign({}, this.defaultParams, ConfirmDialog.params);
} }

View File

@ -2,11 +2,11 @@ import React from "react";
import { DrawerItem, DrawerItemProps } from "./drawer-item"; import { DrawerItem, DrawerItemProps } from "./drawer-item";
import { Badge } from "../badge"; import { Badge } from "../badge";
interface Props extends DrawerItemProps { export interface DrawerItemLabelsProps extends DrawerItemProps {
labels: string[]; labels: string[];
} }
export function DrawerItemLabels(props: Props) { export function DrawerItemLabels(props: DrawerItemLabelsProps) {
const { labels, ...itemProps } = props; const { labels, ...itemProps } = props;
if (!labels || !labels.length) { if (!labels || !labels.length) {
return null; return null;

View File

@ -5,14 +5,14 @@ import { Icon } from "../icon";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { _i18n } from "../../i18n"; import { _i18n } from "../../i18n";
interface Props { export interface DrawerParamTogglerProps {
label: string | number; label: string | number;
} }
interface State { interface State {
open?: boolean; open?: boolean;
} }
export class DrawerParamToggler extends React.Component<Props, State> { export class DrawerParamToggler extends React.Component<DrawerParamTogglerProps, State> {
public state: State = {} public state: State = {}
toggle = () => { toggle = () => {

View File

@ -2,12 +2,12 @@ import "./drawer-title.scss";
import React from "react"; import React from "react";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
interface Props { export interface DrawerTitleProps {
className?: string; className?: string;
title?: React.ReactNode; title?: React.ReactNode;
} }
export class DrawerTitle extends React.Component<Props> { export class DrawerTitle extends React.Component<DrawerTitleProps> {
render() { render() {
const { title, children, className } = this.props const { title, children, className } = this.props
return ( return (

View File

@ -3,13 +3,16 @@ import "./input.scss";
import React, { DOMAttributes, InputHTMLAttributes, TextareaHTMLAttributes } from "react"; import React, { DOMAttributes, InputHTMLAttributes, TextareaHTMLAttributes } from "react";
import { autobind, cssNames, debouncePromise } from "../../utils"; import { autobind, cssNames, debouncePromise } from "../../utils";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { conditionalValidators, Validator } from "./input_validators"; import * as Validators from "./input_validators";
import { InputValidator } from "./input_validators";
import isString from "lodash/isString" import isString from "lodash/isString"
import isFunction from "lodash/isFunction" import isFunction from "lodash/isFunction"
import isBoolean from "lodash/isBoolean" import isBoolean from "lodash/isBoolean"
import uniqueId from "lodash/uniqueId" import uniqueId from "lodash/uniqueId"
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> const { conditionalValidators, ...InputValidators } = Validators;
export { InputValidators, InputValidator }
type InputElement = HTMLInputElement | HTMLTextAreaElement; type InputElement = HTMLInputElement | HTMLTextAreaElement;
type InputElementProps = InputHTMLAttributes<InputElement> & TextareaHTMLAttributes<InputElement> & DOMAttributes<InputElement>; type InputElementProps = InputHTMLAttributes<InputElement> & TextareaHTMLAttributes<InputElement> & DOMAttributes<InputElement>;
@ -24,7 +27,7 @@ export type InputProps<T = string> = Omit<InputElementProps, "onChange" | "onSub
showValidationLine?: boolean; // show animated validation line for async validators showValidationLine?: boolean; // show animated validation line for async validators
iconLeft?: string | React.ReactNode; // material-icon name in case of string-type iconLeft?: string | React.ReactNode; // material-icon name in case of string-type
iconRight?: string | React.ReactNode; iconRight?: string | React.ReactNode;
validators?: Validator | Validator[]; validators?: InputValidator | InputValidator[];
onChange?(value: T, evt: React.ChangeEvent<InputElement>): void; onChange?(value: T, evt: React.ChangeEvent<InputElement>): void;
onSubmit?(value: T): void; onSubmit?(value: T): void;
} }
@ -49,7 +52,7 @@ export class Input extends React.Component<InputProps, State> {
static defaultProps = defaultProps as object; static defaultProps = defaultProps as object;
public input: InputElement; public input: InputElement;
public validators: Validator[] = []; public validators: InputValidator[] = [];
public state: State = { public state: State = {
dirty: !!this.props.dirty, dirty: !!this.props.dirty,
@ -149,7 +152,7 @@ export class Input extends React.Component<InputProps, State> {
}); });
} }
private getValidatorError(value: string, { message }: Validator) { private getValidatorError(value: string, { message }: InputValidator) {
if (isFunction(message)) return message(value, this.props) if (isFunction(message)) return message(value, this.props)
return message || ""; return message || "";
} }
@ -288,9 +291,9 @@ export class Input extends React.Component<InputProps, State> {
return ( return (
<div className={className}> <div className={className}>
<label className="input-area flex gaps align-center"> <label className="input-area flex gaps align-center">
{isString(iconLeft) ? <Icon material={iconLeft} /> : iconLeft} {isString(iconLeft) ? <Icon material={iconLeft}/> : iconLeft}
{multiLine ? <textarea {...inputProps as any} /> : <input {...inputProps as any} />} {multiLine ? <textarea {...inputProps as any} /> : <input {...inputProps as any} />}
{isString(iconRight) ? <Icon material={iconRight} /> : iconRight} {isString(iconRight) ? <Icon material={iconRight}/> : iconRight}
</label> </label>
<div className="input-info flex gaps"> <div className="input-info flex gaps">
{!valid && dirty && ( {!valid && dirty && (

View File

@ -4,26 +4,26 @@ import { t } from "@lingui/macro";
import { _i18n } from '../../i18n'; import { _i18n } from '../../i18n';
import fse from "fs-extra"; import fse from "fs-extra";
export interface Validator { export interface InputValidator {
debounce?: number; // debounce for async validators in ms debounce?: number; // debounce for async validators in ms
condition?(props: InputProps): boolean; // auto-bind condition depending on input props condition?(props: InputProps): boolean; // auto-bind condition depending on input props
message?: ReactNode | ((value: string, props?: InputProps) => ReactNode | string); message?: ReactNode | ((value: string, props?: InputProps) => ReactNode | string);
validate(value: string, props?: InputProps): boolean | Promise<any>; // promise can throw error message validate(value: string, props?: InputProps): boolean | Promise<any>; // promise can throw error message
} }
export const isRequired: Validator = { export const isRequired: InputValidator = {
condition: ({ required }) => required, condition: ({ required }) => required,
message: () => _i18n._(t`This field is required`), message: () => _i18n._(t`This field is required`),
validate: value => !!value.trim(), validate: value => !!value.trim(),
}; };
export const isEmail: Validator = { export const isEmail: InputValidator = {
condition: ({ type }) => type === "email", condition: ({ type }) => type === "email",
message: () => _i18n._(t`Wrong email format`), message: () => _i18n._(t`Wrong email format`),
validate: value => !!value.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/), validate: value => !!value.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/),
}; };
export const isNumber: Validator = { export const isNumber: InputValidator = {
condition: ({ type }) => type === "number", condition: ({ type }) => type === "number",
message: () => _i18n._(t`Invalid number`), message: () => _i18n._(t`Invalid number`),
validate: (value, { min, max }) => { validate: (value, { min, max }) => {
@ -36,37 +36,37 @@ export const isNumber: Validator = {
}, },
}; };
export const isUrl: Validator = { export const isUrl: InputValidator = {
condition: ({ type }) => type === "url", condition: ({ type }) => type === "url",
message: () => _i18n._(t`Wrong url format`), message: () => _i18n._(t`Wrong url format`),
validate: value => !!value.match(/^$|^http(s)?:\/\/\w+(\.\w+)*(:[0-9]+)?\/?(\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)*$/), validate: value => !!value.match(/^$|^http(s)?:\/\/\w+(\.\w+)*(:[0-9]+)?\/?(\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)*$/),
}; };
export const isPath: Validator = { export const isPath: InputValidator = {
condition: ({ type }) => type === "text", condition: ({ type }) => type === "text",
message: () => _i18n._(t`This field must be a valid path`), message: () => _i18n._(t`This field must be a valid path`),
validate: value => !value || fse.pathExistsSync(value), validate: value => !value || fse.pathExistsSync(value),
} }
export const minLength: Validator = { export const minLength: InputValidator = {
condition: ({ minLength }) => !!minLength, condition: ({ minLength }) => !!minLength,
message: (value, { minLength }) => _i18n._(t`Minimum length is ${minLength}`), message: (value, { minLength }) => _i18n._(t`Minimum length is ${minLength}`),
validate: (value, { minLength }) => value.length >= minLength, validate: (value, { minLength }) => value.length >= minLength,
}; };
export const maxLength: Validator = { export const maxLength: InputValidator = {
condition: ({ maxLength }) => !!maxLength, condition: ({ maxLength }) => !!maxLength,
message: (value, { maxLength }) => _i18n._(t`Maximum length is ${maxLength}`), message: (value, { maxLength }) => _i18n._(t`Maximum length is ${maxLength}`),
validate: (value, { maxLength }) => value.length <= maxLength, validate: (value, { maxLength }) => value.length <= maxLength,
}; };
const systemNameMatcher = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/; const systemNameMatcher = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/;
export const systemName: Validator = { export const systemName: InputValidator = {
message: () => _i18n._(t`A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics.`), message: () => _i18n._(t`A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics.`),
validate: value => !!value.match(systemNameMatcher), validate: value => !!value.match(systemNameMatcher),
}; };
export const accountId: Validator = { export const accountId: InputValidator = {
message: () => _i18n._(t`Invalid account ID`), message: () => _i18n._(t`Invalid account ID`),
validate: value => (isEmail.validate(value) || systemName.validate(value)) validate: value => (isEmail.validate(value) || systemName.validate(value))
}; };

View File

@ -5,8 +5,8 @@ import React, { ReactNode } from "react";
import { computed, observable, reaction, toJS, when } from "mobx"; import { computed, observable, reaction, toJS, when } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import { Plural, Trans } from "@lingui/macro"; 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 { TableSortCallback, Table, TableCell, TableCellProps, TableHead, TableProps, TableRow, TableRowProps } from "../table";
import { autobind, createStorage, cssNames, IClassName, isReactNode, noop, prevDefault, stopPropagation } from "../../utils"; import { autobind, createStorage, cssNames, IClassName, isReactNode, noop, prevDefault, stopPropagation } from "../../utils";
import { AddRemoveButtons, AddRemoveButtonsProps } from "../add-remove-buttons"; import { AddRemoveButtons, AddRemoveButtonsProps } from "../add-remove-buttons";
import { NoItems } from "../no-items"; import { NoItems } from "../no-items";
@ -52,7 +52,7 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
isSelectable?: boolean; // show checkbox in rows for selecting items isSelectable?: boolean; // show checkbox in rows for selecting items
isSearchable?: boolean; // apply search-filter & add search-input isSearchable?: boolean; // apply search-filter & add search-input
copyClassNameFromHeadCells?: boolean; copyClassNameFromHeadCells?: boolean;
sortingCallbacks?: { [sortBy: string]: SortingCallback }; sortingCallbacks?: { [sortBy: string]: TableSortCallback };
tableProps?: Partial<TableProps>; // low-level table configuration tableProps?: Partial<TableProps>; // low-level table configuration
renderTableHeader: TableCellProps[] | null; renderTableHeader: TableCellProps[] | null;
renderTableContents: (item: T) => (ReactNode | TableCellProps)[]; renderTableContents: (item: T) => (ReactNode | TableCellProps)[];
@ -67,7 +67,7 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
onDetails?: (item: T) => void; onDetails?: (item: T) => void;
// other // other
customizeRemoveDialog?: (selectedItems: T[]) => Partial<IConfirmDialogParams>; customizeRemoveDialog?: (selectedItems: T[]) => Partial<ConfirmDialogParams>;
renderFooter?: (parent: ItemListLayout) => React.ReactNode; renderFooter?: (parent: ItemListLayout) => React.ReactNode;
} }

View File

@ -1,3 +1,4 @@
export * from "./kube-object-details" export * from "./kube-object-details"
export * from "./kube-object-list-layout" export * from "./kube-object-list-layout"
export * from "./kube-object-menu" export * from "./kube-object-menu"
export * from "./kube-object-meta"

View File

@ -11,7 +11,7 @@ import { Spinner } from "../spinner";
import { apiManager } from "../../api/api-manager"; import { apiManager } from "../../api/api-manager";
import { crdStore } from "../+custom-resources/crd.store"; import { crdStore } from "../+custom-resources/crd.store";
import { CrdResourceDetails } from "../+custom-resources"; import { CrdResourceDetails } from "../+custom-resources";
import { KubeObjectMenu } from "./kube-object-menu" import { KubeObjectMenu } from "./kube-object-menu"
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
export interface KubeObjectDetailsProps<T = KubeObject> { export interface KubeObjectDetailsProps<T = KubeObject> {

View File

@ -2,17 +2,16 @@ import React from "react";
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { IKubeMetaField, KubeObject } from "../../api/kube-object"; import { IKubeMetaField, KubeObject } from "../../api/kube-object";
import { DrawerItem, DrawerItemLabels } from "../drawer"; import { DrawerItem, DrawerItemLabels } from "../drawer";
import { WorkloadKubeObject } from "../../api/workload-kube-object";
import { getDetailsUrl } from "../../navigation"; import { getDetailsUrl } from "../../navigation";
import { lookupApiLink } from "../../api/kube-api"; import { lookupApiLink } from "../../api/kube-api";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
export interface Props { export interface KubeObjectMetaProps {
object: KubeObject; object: KubeObject;
hideFields?: IKubeMetaField[]; hideFields?: IKubeMetaField[];
} }
export class KubeObjectMeta extends React.Component<Props> { export class KubeObjectMeta extends React.Component<KubeObjectMetaProps> {
static defaultHiddenFields: IKubeMetaField[] = [ static defaultHiddenFields: IKubeMetaField[] = [
"uid", "resourceVersion", "selfLink" "uid", "resourceVersion", "selfLink"
]; ];

View File

@ -12,14 +12,14 @@ export interface TabRoute extends RouteProps {
url: string; url: string;
} }
interface Props { export interface TabLayoutProps {
children: ReactNode; children: ReactNode;
className?: any; className?: any;
tabs?: TabRoute[]; tabs?: TabRoute[];
contentClass?: string; contentClass?: string;
} }
export const TabLayout = observer(({ className, contentClass, tabs, children }: Props) => { export const TabLayout = observer(({ className, contentClass, tabs, children }: TabLayoutProps) => {
const routePath = navigation.location.pathname; const routePath = navigation.location.pathname;
return ( return (
<div className={cssNames("TabLayout", className)}> <div className={cssNames("TabLayout", className)}>

View File

@ -3,7 +3,7 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { cssNames, IClassName } from "../../utils"; import { cssNames, IClassName } from "../../utils";
interface Props extends React.DOMAttributes<any> { export interface WizardLayoutProps extends React.DOMAttributes<any> {
className?: IClassName; className?: IClassName;
header?: React.ReactNode; header?: React.ReactNode;
headerClass?: IClassName; headerClass?: IClassName;
@ -15,7 +15,7 @@ interface Props extends React.DOMAttributes<any> {
} }
@observer @observer
export class WizardLayout extends React.Component<Props> { export class WizardLayout extends React.Component<WizardLayoutProps> {
render() { render() {
const { const {
className, contentClass, infoPanelClass, infoPanel, header, headerClass, centered, className, contentClass, infoPanelClass, infoPanel, header, headerClass, centered,

View File

@ -3,7 +3,7 @@ import React from "react";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { TooltipDecoratorProps, withTooltip } from "../tooltip"; import { TooltipDecoratorProps, withTooltip } from "../tooltip";
interface Props extends React.HTMLProps<any>, TooltipDecoratorProps { export interface LineProgressProps extends React.HTMLProps<any>, TooltipDecoratorProps {
value: number; value: number;
min?: number; min?: number;
max?: number; max?: number;
@ -12,8 +12,8 @@ interface Props extends React.HTMLProps<any>, TooltipDecoratorProps {
} }
@withTooltip @withTooltip
export class LineProgress extends React.PureComponent<Props> { export class LineProgress extends React.PureComponent<LineProgressProps> {
static defaultProps: Props = { static defaultProps: LineProgressProps = {
value: 0, value: 0,
min: 0, min: 0,
max: 100, max: 100,

View File

@ -10,7 +10,7 @@ import debounce from "lodash/debounce"
export const MenuContext = React.createContext<MenuContextValue>(null); export const MenuContext = React.createContext<MenuContextValue>(null);
export type MenuContextValue = Menu; export type MenuContextValue = Menu;
interface MenuPosition { export interface MenuPosition {
left?: boolean; left?: boolean;
top?: boolean; top?: boolean;
right?: boolean; right?: boolean;

View File

@ -1 +1,2 @@
export * from './notifications' export * from './notifications'
export * from './notifications.store'

View File

@ -5,8 +5,8 @@ import isObject from "lodash/isObject"
import uniqueId from "lodash/uniqueId"; import uniqueId from "lodash/uniqueId";
import { JsonApiErrorParsed } from "../../api/json-api"; import { JsonApiErrorParsed } from "../../api/json-api";
export type IMessageId = string | number; export type NotificationId = string | number;
export type IMessage = React.ReactNode | React.ReactNode[] | JsonApiErrorParsed; export type NotificationMessage = React.ReactNode | React.ReactNode[] | JsonApiErrorParsed;
export enum NotificationStatus { export enum NotificationStatus {
OK = "ok", OK = "ok",
@ -14,20 +14,20 @@ export enum NotificationStatus {
INFO = "info", INFO = "info",
} }
export interface INotification { export interface Notification {
id?: IMessageId; id?: NotificationId;
message: IMessage; message: NotificationMessage;
status?: NotificationStatus; status?: NotificationStatus;
timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide
} }
@autobind() @autobind()
export class NotificationsStore { export class NotificationsStore {
public notifications = observable<INotification>([], { deep: false }); public notifications = observable<Notification>([], { deep: false });
protected autoHideTimers = new Map<IMessageId, number>(); protected autoHideTimers = new Map<NotificationId, number>();
addAutoHideTimer(notification: INotification) { addAutoHideTimer(notification: Notification) {
this.removeAutoHideTimer(notification); this.removeAutoHideTimer(notification);
const { id, timeout } = notification; const { id, timeout } = notification;
if (timeout) { if (timeout) {
@ -36,7 +36,7 @@ export class NotificationsStore {
} }
} }
removeAutoHideTimer(notification: INotification) { removeAutoHideTimer(notification: Notification) {
const { id } = notification; const { id } = notification;
if (this.autoHideTimers.has(id)) { if (this.autoHideTimers.has(id)) {
clearTimeout(this.autoHideTimers.get(id)); clearTimeout(this.autoHideTimers.get(id));
@ -45,7 +45,7 @@ export class NotificationsStore {
} }
@action @action
add(notification: INotification) { add(notification: Notification) {
if (!notification.id) { if (!notification.id) {
notification.id = uniqueId("notification_"); notification.id = uniqueId("notification_");
} }
@ -56,11 +56,11 @@ export class NotificationsStore {
} }
@action @action
remove(itemOrId: IMessageId | INotification) { remove(itemOrId: NotificationId | Notification) {
if (!isObject(itemOrId)) { if (!isObject(itemOrId)) {
itemOrId = this.notifications.find(item => item.id === itemOrId); itemOrId = this.notifications.find(item => item.id === itemOrId);
} }
return this.notifications.remove(itemOrId as INotification); return this.notifications.remove(itemOrId as Notification);
} }
} }

View File

@ -5,7 +5,7 @@ import { reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react" import { disposeOnUnmount, observer } from "mobx-react"
import { JsonApiErrorParsed } from "../../api/json-api"; import { JsonApiErrorParsed } from "../../api/json-api";
import { cssNames, prevDefault } from "../../utils"; import { cssNames, prevDefault } from "../../utils";
import { IMessage, INotification, notificationsStore, NotificationStatus } from "./notifications.store"; import { NotificationMessage, Notification, notificationsStore, NotificationStatus } from "./notifications.store";
import { Animate } from "../animate"; import { Animate } from "../animate";
import { Icon } from "../icon" import { Icon } from "../icon"
@ -13,7 +13,7 @@ import { Icon } from "../icon"
export class Notifications extends React.Component { export class Notifications extends React.Component {
public elem: HTMLElement; public elem: HTMLElement;
static ok(message: IMessage) { static ok(message: NotificationMessage) {
notificationsStore.add({ notificationsStore.add({
message: message, message: message,
timeout: 2500, timeout: 2500,
@ -21,7 +21,7 @@ export class Notifications extends React.Component {
}) })
} }
static error(message: IMessage) { static error(message: NotificationMessage) {
notificationsStore.add({ notificationsStore.add({
message: message, message: message,
timeout: 10000, timeout: 10000,
@ -29,7 +29,7 @@ export class Notifications extends React.Component {
}); });
} }
static info(message: IMessage, customOpts: Partial<INotification> = {}) { static info(message: NotificationMessage, customOpts: Partial<Notification> = {}) {
return notificationsStore.add({ return notificationsStore.add({
status: NotificationStatus.INFO, status: NotificationStatus.INFO,
timeout: 0, timeout: 0,
@ -56,7 +56,7 @@ export class Notifications extends React.Component {
}) })
} }
getMessage(notification: INotification) { getMessage(notification: Notification) {
let { message } = notification; let { message } = notification;
if (message instanceof JsonApiErrorParsed) { if (message instanceof JsonApiErrorParsed) {
message = message.toString(); message = message.toString();

View File

@ -5,7 +5,7 @@ import uniqueId from "lodash/uniqueId";
// todo: refactor with contexts // todo: refactor with contexts
interface RadioGroupProps { export interface RadioGroupProps {
className?: any; className?: any;
value?: any; value?: any;
asButtons?: boolean; asButtons?: boolean;
@ -35,7 +35,7 @@ export class RadioGroup extends React.Component<RadioGroupProps, {}> {
} }
} }
type RadioProps = React.HTMLProps<any> & { export type RadioProps = React.HTMLProps<any> & {
name?: string; name?: string;
label?: React.ReactNode | any; label?: React.ReactNode | any;
value?: any; value?: any;

View File

@ -4,20 +4,20 @@ import "./slider.scss";
import React, { Component } from "react"; import React, { Component } from "react";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import MaterialSlider, { SliderClassKey, SliderProps } from "@material-ui/core/Slider"; import MaterialSlider, { SliderClassKey, SliderProps as MaterialSliderProps } from "@material-ui/core/Slider";
interface Props extends Omit<SliderProps, "onChange"> { export interface SliderProps extends Omit<MaterialSliderProps, "onChange"> {
className?: string; className?: string;
onChange?(evt: React.FormEvent<any>, value: number): void; onChange?(evt: React.FormEvent<any>, value: number): void;
} }
const defaultProps: Partial<Props> = { const defaultProps: Partial<SliderProps> = {
step: 1, step: 1,
min: 0, min: 0,
max: 100, max: 100,
}; };
export class Slider extends Component<Props> { export class Slider extends Component<SliderProps> {
static defaultProps = defaultProps as object; static defaultProps = defaultProps as object;
private classNames: Partial<{ [P in SliderClassKey]: string }> = { private classNames: Partial<{ [P in SliderClassKey]: string }> = {

View File

@ -2,12 +2,12 @@ import './cube-spinner.scss'
import React from 'react' import React from 'react'
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
interface Props { export interface CubeSpinnerProps {
className?: string; className?: string;
center?: boolean; center?: boolean;
} }
export class CubeSpinner extends React.Component<Props> { export class CubeSpinner extends React.Component<CubeSpinnerProps> {
render() { render() {
const { className, center } = this.props; const { className, center } = this.props;
return ( return (

View File

@ -3,12 +3,12 @@ import './spinner.scss'
import React from 'react' import React from 'react'
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
interface Props extends React.HTMLProps<any> { export interface SpinnerProps extends React.HTMLProps<any> {
singleColor?: boolean; singleColor?: boolean;
center?: boolean; center?: boolean;
} }
export class Spinner extends React.Component<Props, {}> { export class Spinner extends React.Component<SpinnerProps, {}> {
private elem: HTMLElement; private elem: HTMLElement;
static defaultProps = { static defaultProps = {

View File

@ -4,11 +4,11 @@ import React from "react";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { TooltipDecoratorProps, withTooltip } from "../tooltip"; import { TooltipDecoratorProps, withTooltip } from "../tooltip";
interface Props extends React.HTMLAttributes<any>, TooltipDecoratorProps { export interface StatusBrickProps extends React.HTMLAttributes<any>, TooltipDecoratorProps {
} }
@withTooltip @withTooltip
export class StatusBrick extends React.Component<Props> { export class StatusBrick extends React.Component<StatusBrickProps> {
render() { render() {
const { className, ...elemProps } = this.props const { className, ...elemProps } = this.props
return ( return (

View File

@ -2,7 +2,7 @@ import "./stepper.scss";
import React from "react"; import React from "react";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
interface Props extends React.HTMLProps<any> { export interface StepperProps extends React.HTMLProps<any> {
step: number; step: number;
steps: Step[]; steps: Step[];
} }
@ -11,7 +11,7 @@ interface Step {
title?: string; title?: string;
} }
export class Stepper extends React.Component<Props, {}> { export class Stepper extends React.Component<StepperProps, {}> {
render() { render() {
const { className, steps, ...props } = this.props; const { className, steps, ...props } = this.props;
const stepsCount = steps.length; const stepsCount = steps.length;

View File

@ -1,5 +1,5 @@
import "./table-cell.scss"; import "./table-cell.scss";
import type { SortBy, SortParams } from "./table"; import type { TableSortBy, TableSortParams } from "./table";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { autobind, cssNames } from "../../utils"; import { autobind, cssNames } from "../../utils";
@ -13,9 +13,9 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
title?: ReactNode; title?: ReactNode;
checkbox?: boolean; // render cell with a checkbox checkbox?: boolean; // render cell with a checkbox
isChecked?: boolean; // mark checkbox as checked or not isChecked?: boolean; // mark checkbox as checked or not
sortBy?: SortBy; // column name, must be same as key in sortable object <Table sortable={}/> sortBy?: TableSortBy; // column name, must be same as key in sortable object <Table sortable={}/>
_sorting?: Partial<SortParams>; // <Table> sorting state, don't use this prop outside (!) _sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!)
_sort?(sortBy: SortBy): void; // <Table> sort function, don't use this prop outside (!) _sort?(sortBy: TableSortBy): void; // <Table> sort function, don't use this prop outside (!)
_nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!) _nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!)
} }

View File

@ -14,10 +14,10 @@ import { ItemObject } from "../../item.store";
// todo: refactor + decouple search from location // todo: refactor + decouple search from location
export type SortBy = string; export type TableSortBy = string;
export type OrderBy = "asc" | "desc" | string; export type TableOrderBy = "asc" | "desc" | string;
export type SortParams = { sortBy: SortBy; orderBy: OrderBy } export type TableSortParams = { sortBy: TableSortBy; orderBy: TableOrderBy }
export type SortingCallback<D = any> = (data: D) => string | number | (string | number)[]; export type TableSortCallback<D = any> = (data: D) => string | number | (string | number)[];
export interface TableProps extends React.DOMAttributes<HTMLDivElement> { export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
items?: ItemObject[]; // Raw items data items?: ItemObject[]; // Raw items data
@ -29,11 +29,11 @@ export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
sortable?: { sortable?: {
// Define sortable callbacks for every column in <TableHead><TableCell sortBy="someCol"><TableHead> // Define sortable callbacks for every column in <TableHead><TableCell sortBy="someCol"><TableHead>
// @sortItem argument in the callback is an object, provided in <TableRow sortItem={someColDataItem}/> // @sortItem argument in the callback is an object, provided in <TableRow sortItem={someColDataItem}/>
[sortBy: string]: SortingCallback; [sortBy: string]: TableSortCallback;
}; };
sortSyncWithUrl?: boolean; // sorting state is managed globally from url params sortSyncWithUrl?: boolean; // sorting state is managed globally from url params
sortByDefault?: Partial<SortParams>; // default sorting params sortByDefault?: Partial<TableSortParams>; // default sorting params
onSort?: (params: SortParams) => void; // callback on sort change, default: global sync with url onSort?: (params: TableSortParams) => void; // callback on sort change, default: global sync with url
noItems?: React.ReactNode; // Show no items state table list is empty noItems?: React.ReactNode; // Show no items state table list is empty
selectedItemId?: string; // Allows to scroll list to selected item selectedItemId?: string; // Allows to scroll list to selected item
virtual?: boolean; // Use virtual list component to render only visible rows virtual?: boolean; // Use virtual list component to render only visible rows
@ -55,7 +55,7 @@ export class Table extends React.Component<TableProps> {
@observable sortParamsLocal = this.props.sortByDefault; @observable sortParamsLocal = this.props.sortByDefault;
@computed get sortParams(): Partial<SortParams> { @computed get sortParams(): Partial<TableSortParams> {
if (this.props.sortSyncWithUrl) { if (this.props.sortSyncWithUrl) {
const sortBy = navigation.searchParams.get("sortBy") const sortBy = navigation.searchParams.get("sortBy")
const orderBy = navigation.searchParams.get("orderBy") const orderBy = navigation.searchParams.get("orderBy")
@ -105,7 +105,7 @@ export class Table extends React.Component<TableProps> {
} }
@autobind() @autobind()
protected onSort(params: SortParams) { protected onSort(params: TableSortParams) {
const { sortSyncWithUrl, onSort } = this.props; const { sortSyncWithUrl, onSort } = this.props;
if (sortSyncWithUrl) { if (sortSyncWithUrl) {
setQueryParams(params) setQueryParams(params)
@ -119,11 +119,11 @@ export class Table extends React.Component<TableProps> {
} }
@autobind() @autobind()
sort(colName: SortBy) { sort(colName: TableSortBy) {
const { sortBy, orderBy } = this.sortParams; const { sortBy, orderBy } = this.sortParams;
const sameColumn = sortBy == colName; const sameColumn = sortBy == colName;
const newSortBy: SortBy = colName; const newSortBy: TableSortBy = colName;
const newOrderBy: OrderBy = (!orderBy || !sameColumn || orderBy === "desc") ? "asc" : "desc"; const newOrderBy: TableOrderBy = (!orderBy || !sameColumn || orderBy === "desc") ? "asc" : "desc";
this.onSort({ this.onSort({
sortBy: String(newSortBy), sortBy: String(newSortBy),
orderBy: newOrderBy, orderBy: newOrderBy,