mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Allow user to select Kubeconfig from filesystem (#740)
* Allow user to select Kubeconfig from filesystem, fix #738 Signed-off-by: Roman <ixrock@gmail.com> Co-authored-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> Co-authored-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
24 files changed
+1113
-263
No files matched your search
@@ -0,0 +1,66 @@
|
||||
import React, { InputHTMLAttributes } from "react";
|
||||
|
||||
export interface FileInputSelection<T = string> {
|
||||
file: File;
|
||||
data?: T | any; // not available when readAsTexts={false}
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface Props extends InputHTMLAttributes<any> {
|
||||
id?: string; // could be used with <label htmlFor={id}/> to open filesystem dialog
|
||||
accept?: string; // allowed file types to select, e.g. "application/json"
|
||||
readAsText?: boolean; // provide files content as text in selection-callback
|
||||
multiple?: boolean;
|
||||
onSelectFiles(...selectedFiles: FileInputSelection[]): void;
|
||||
}
|
||||
|
||||
export class FileInput extends React.Component<Props> {
|
||||
protected input: HTMLInputElement;
|
||||
|
||||
protected style: React.CSSProperties = {
|
||||
position: "absolute",
|
||||
display: "none",
|
||||
};
|
||||
|
||||
selectFiles = () => {
|
||||
this.input.click(); // opens system dialog for selecting files
|
||||
}
|
||||
|
||||
protected onChange = async (evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const fileList = Array.from(evt.target.files);
|
||||
if (!fileList.length) {
|
||||
return;
|
||||
}
|
||||
let selectedFiles: FileInputSelection[] = fileList.map(file => ({ file }));
|
||||
if (this.props.readAsText) {
|
||||
const readingFiles: Promise<FileInputSelection>[] = fileList.map(file => {
|
||||
return new Promise((resolve) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
resolve({
|
||||
file: file,
|
||||
data: reader.result,
|
||||
error: reader.error ? String(reader.error) : null,
|
||||
})
|
||||
};
|
||||
reader.readAsText(file);
|
||||
})
|
||||
});
|
||||
selectedFiles = await Promise.all(readingFiles);
|
||||
}
|
||||
this.props.onSelectFiles(...selectedFiles);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onSelectFiles, readAsText, ...props } = this.props;
|
||||
return (
|
||||
<input
|
||||
type="file"
|
||||
style={this.style}
|
||||
onChange={this.onChange}
|
||||
ref={e => this.input = e}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './input'
|
||||
export * from './search-input'
|
||||
export * from './file-input'
|
||||
@@ -17,6 +17,7 @@ export type InputProps<T = string> = Omit<InputElementProps, "onChange" | "onSub
|
||||
theme?: "round-black";
|
||||
className?: string;
|
||||
value?: T;
|
||||
autoSelectOnFocus?: boolean
|
||||
multiLine?: boolean; // use text-area as input field
|
||||
maxRows?: number; // when multiLine={true} define max rows size
|
||||
dirty?: boolean; // show validation errors even if the field wasn't touched yet
|
||||
@@ -112,8 +113,7 @@ export class Input extends React.Component<InputProps, State> {
|
||||
const result = validator.validate(value, this.props);
|
||||
if (isBoolean(result) && !result) {
|
||||
errors.push(this.getValidatorError(value, validator));
|
||||
}
|
||||
else if (result instanceof Promise) {
|
||||
} else if (result instanceof Promise) {
|
||||
if (!validationId) {
|
||||
this.validationId = validationId = uniqueId("validation_id_");
|
||||
}
|
||||
@@ -176,8 +176,9 @@ export class Input extends React.Component<InputProps, State> {
|
||||
|
||||
@autobind()
|
||||
onFocus(evt: React.FocusEvent<InputElement>) {
|
||||
const { onFocus } = this.props;
|
||||
const { onFocus, autoSelectOnFocus } = this.props;
|
||||
if (onFocus) onFocus(evt);
|
||||
if (autoSelectOnFocus) this.select();
|
||||
this.setState({ focused: true });
|
||||
}
|
||||
|
||||
@@ -255,13 +256,14 @@ export class Input extends React.Component<InputProps, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
/* eslint-disable */
|
||||
let { multiLine, showValidationLine, validators, theme, maxRows, children, iconLeft, iconRight, ...inputProps } = this.props;
|
||||
let { className, maxLength, rows, disabled, } = this.props;
|
||||
/* eslint-enable */
|
||||
const {
|
||||
multiLine, showValidationLine, validators, theme, maxRows, children,
|
||||
maxLength, rows, disabled, autoSelectOnFocus, iconLeft, iconRight,
|
||||
...inputProps
|
||||
} = this.props;
|
||||
const { focused, dirty, valid, validating, errors } = this.state;
|
||||
|
||||
className = cssNames("Input", className, {
|
||||
const className = cssNames("Input", this.props.className, {
|
||||
[`theme ${theme}`]: theme,
|
||||
focused: focused,
|
||||
disabled: disabled,
|
||||
@@ -271,10 +273,6 @@ export class Input extends React.Component<InputProps, State> {
|
||||
validatingLine: validating && showValidationLine,
|
||||
});
|
||||
|
||||
// normalize icons
|
||||
if (isString(iconLeft)) iconLeft = <Icon material={iconLeft}/>
|
||||
if (isString(iconRight)) iconRight = <Icon material={iconRight}/>
|
||||
|
||||
// prepare input props
|
||||
Object.assign(inputProps, {
|
||||
className: "input box grow",
|
||||
@@ -291,9 +289,9 @@ export class Input extends React.Component<InputProps, State> {
|
||||
return (
|
||||
<div className={className}>
|
||||
<label className="input-area flex gaps align-center">
|
||||
{iconLeft}
|
||||
{isString(iconLeft) ? <Icon material={iconLeft}/> : iconLeft}
|
||||
{multiLine ? <textarea {...inputProps as any}/> : <input {...inputProps as any}/>}
|
||||
{iconRight}
|
||||
{isString(iconRight) ? <Icon material={iconRight}/> : iconRight}
|
||||
</label>
|
||||
<div className="input-info flex gaps">
|
||||
{!valid && dirty && (
|
||||
|
||||
Reference in new issue
Block a user