mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Allow to install packed extensions from URL or local file (#1456)
* Option to install an extension from filesystem/url #1227 -- part 1 (UI) Signed-off-by: Roman <ixrock@gmail.com> * DropFileInput: common component to handle droped files (replaced also in add-cluster-page) Signed-off-by: Roman <ixrock@gmail.com> * fix: install via url-string on input.submit Signed-off-by: Roman <ixrock@gmail.com> * ui tweaks & minor fixes Signed-off-by: Roman <ixrock@gmail.com> * more ui/ux tweaks & fixes Signed-off-by: Roman <ixrock@gmail.com> * layout fixes Signed-off-by: Roman <ixrock@gmail.com> * component renaming: `copy-to-click` => `copy-to-clipboard` => `clipboard` Signed-off-by: Roman <ixrock@gmail.com> * reworks -- part 1 Signed-off-by: Roman <ixrock@gmail.com> * fix downloading file, added common/utils/downloadFile Signed-off-by: Roman <ixrock@gmail.com> * confirm before install, unpack tar first steps Signed-off-by: Roman <ixrock@gmail.com> * installation flow, extracting .tgz Signed-off-by: Roman <ixrock@gmail.com> * clean up, fix lint issues Signed-off-by: Roman <ixrock@gmail.com> * update .azure-pipelines.yml Signed-off-by: Roman <ixrock@gmail.com> * fixes & refactoring Signed-off-by: Roman <ixrock@gmail.com> * fix lint harder :/ Signed-off-by: Roman <ixrock@gmail.com> * fix validation Signed-off-by: Roman <ixrock@gmail.com> * fix validation harder Signed-off-by: Roman <ixrock@gmail.com> * responding to comments, fixed package validation Signed-off-by: Roman <ixrock@gmail.com> * common/utils/tar.ts: reject with Error-type Signed-off-by: Roman <ixrock@gmail.com> * fix: unit-tests Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
1 parent
2da598b66e
commit
77ae31550a
36 files changed
+799
-193
No files matched your search
@@ -0,0 +1,9 @@
|
||||
.DropFileInput {
|
||||
&.droppable {
|
||||
box-shadow: 0 0 0 5px $primary; // fixme: might not work sometimes
|
||||
|
||||
> * {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import "./drop-file-input.scss";
|
||||
import React from "react";
|
||||
import { autobind, cssNames, IClassName } from "../../utils";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import logger from "../../../main/logger";
|
||||
|
||||
export interface DropFileInputProps extends React.DOMAttributes<any> {
|
||||
className?: IClassName;
|
||||
disabled?: boolean;
|
||||
onDropFiles(files: File[], meta: DropFileMeta): void;
|
||||
}
|
||||
|
||||
export interface DropFileMeta<T extends HTMLElement = any> {
|
||||
evt: React.DragEvent<T>;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DropFileInput<T extends HTMLElement = any> extends React.Component<DropFileInputProps> {
|
||||
@observable dropAreaActive = false;
|
||||
|
||||
@autobind()
|
||||
onDragEnter() {
|
||||
this.dropAreaActive = true;
|
||||
}
|
||||
|
||||
@autobind()
|
||||
onDragLeave() {
|
||||
this.dropAreaActive = false;
|
||||
}
|
||||
|
||||
@autobind()
|
||||
onDragOver(evt: React.DragEvent<T>) {
|
||||
if (this.props.onDragOver) {
|
||||
this.props.onDragOver(evt);
|
||||
}
|
||||
evt.preventDefault(); // enable onDrop()-callback
|
||||
evt.dataTransfer.dropEffect = "move";
|
||||
}
|
||||
|
||||
@autobind()
|
||||
onDrop(evt: React.DragEvent<T>) {
|
||||
if (this.props.onDrop) {
|
||||
this.props.onDrop(evt);
|
||||
}
|
||||
this.dropAreaActive = false;
|
||||
const files = Array.from(evt.dataTransfer.files);
|
||||
if (files.length > 0) {
|
||||
this.props.onDropFiles(files, { evt });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { disabled, className } = this.props;
|
||||
const { onDragEnter, onDragLeave, onDragOver, onDrop } = this;
|
||||
try {
|
||||
const contentElem = React.Children.only(this.props.children) as React.ReactElement<React.HTMLProps<HTMLElement>>;
|
||||
const isValidContentElem = React.isValidElement(contentElem);
|
||||
if (!disabled && isValidContentElem) {
|
||||
const contentElemProps: React.HTMLProps<HTMLElement> = {
|
||||
className: cssNames("DropFileInput", className, {
|
||||
droppable: this.dropAreaActive,
|
||||
}),
|
||||
onDragEnter,
|
||||
onDragLeave,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
};
|
||||
return React.cloneElement(contentElem, contentElemProps);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("Invalid root content-element for DropFileInput", { err: String(err) });
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,4 @@ export * from './input';
|
||||
export * from './search-input';
|
||||
export * from './search-input-url';
|
||||
export * from './file-input';
|
||||
export * from './drop-file-input';
|
||||
@@ -89,6 +89,10 @@
|
||||
|
||||
&.theme {
|
||||
&.round-black {
|
||||
&.invalid label {
|
||||
border-color: $colorSoftError !important;
|
||||
}
|
||||
|
||||
label {
|
||||
background: $mainBackground;
|
||||
border: 1px solid $borderFaintColor;
|
||||
@@ -107,3 +111,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Tooltip.InputTooltipError {
|
||||
--bgc: #{$colorError};
|
||||
--border: none;
|
||||
--color: white;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import "./input.scss";
|
||||
|
||||
import React, { DOMAttributes, InputHTMLAttributes, TextareaHTMLAttributes } from "react";
|
||||
import { autobind, cssNames, debouncePromise } from "../../utils";
|
||||
import { autobind, cssNames, debouncePromise, getRandId } from "../../utils";
|
||||
import { Icon } from "../icon";
|
||||
import * as Validators from "./input_validators";
|
||||
import { InputValidator } from "./input_validators";
|
||||
@@ -9,6 +9,7 @@ import isString from "lodash/isString";
|
||||
import isFunction from "lodash/isFunction";
|
||||
import isBoolean from "lodash/isBoolean";
|
||||
import uniqueId from "lodash/uniqueId";
|
||||
import { Tooltip } from "../tooltip";
|
||||
|
||||
const { conditionalValidators, ...InputValidators } = Validators;
|
||||
export { InputValidators, InputValidator };
|
||||
@@ -25,6 +26,7 @@ export type InputProps<T = string> = Omit<InputElementProps, "onChange" | "onSub
|
||||
maxRows?: number; // when multiLine={true} define max rows size
|
||||
dirty?: boolean; // show validation errors even if the field wasn't touched yet
|
||||
showValidationLine?: boolean; // show animated validation line for async validators
|
||||
showErrorsAsTooltip?: boolean; // show validation errors as a tooltip :hover (instead of block below)
|
||||
iconLeft?: string | React.ReactNode; // material-icon name in case of string-type
|
||||
iconRight?: string | React.ReactNode;
|
||||
contentRight?: string | React.ReactNode; // Any component of string goes after iconRight
|
||||
@@ -265,7 +267,7 @@ export class Input extends React.Component<InputProps, State> {
|
||||
|
||||
render() {
|
||||
const {
|
||||
multiLine, showValidationLine, validators, theme, maxRows, children,
|
||||
multiLine, showValidationLine, validators, theme, maxRows, children, showErrorsAsTooltip,
|
||||
maxLength, rows, disabled, autoSelectOnFocus, iconLeft, iconRight, contentRight,
|
||||
...inputProps
|
||||
} = this.props;
|
||||
@@ -292,21 +294,31 @@ export class Input extends React.Component<InputProps, State> {
|
||||
ref: this.bindRef,
|
||||
spellCheck: "false",
|
||||
});
|
||||
|
||||
const tooltipId = showErrorsAsTooltip ? getRandId({ prefix: "input_tooltip_id" }) : undefined;
|
||||
const showErrors = errors.length > 0 && !valid && dirty;
|
||||
const errorsInfo = (
|
||||
<div className="errors box grow">
|
||||
{errors.map((error, i) => <p key={i}>{error}</p>)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={className}>
|
||||
<label className="input-area flex gaps align-center">
|
||||
<div id={tooltipId} className={className}>
|
||||
<label className="input-area flex gaps align-center" id="">
|
||||
{isString(iconLeft) ? <Icon material={iconLeft}/> : iconLeft}
|
||||
{multiLine ? <textarea {...inputProps as any} /> : <input {...inputProps as any} />}
|
||||
{isString(iconRight) ? <Icon material={iconRight} /> : iconRight}
|
||||
{isString(iconRight) ? <Icon material={iconRight}/> : iconRight}
|
||||
{contentRight}
|
||||
</label>
|
||||
<div className="input-info flex gaps">
|
||||
{!valid && dirty && (
|
||||
<div className="errors box grow">
|
||||
{errors.map((error, i) => <p key={i}>{error}</p>)}
|
||||
{showErrorsAsTooltip && showErrors && (
|
||||
<Tooltip targetId={tooltipId} className="InputTooltipError">
|
||||
<div className="flex gaps align-center">
|
||||
<Icon material="error_outline"/>
|
||||
{errorsInfo}
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
)}
|
||||
<div className="input-info flex gaps">
|
||||
{!showErrorsAsTooltip && showErrors && errorsInfo}
|
||||
{this.showMaxLenIndicator && (
|
||||
<div className="maxLengthIndicator box right">
|
||||
{this.getValue().length} / {maxLength}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.Input.SearchInput {
|
||||
--compact-focus-width: 190px;
|
||||
--spacing: 6px 6px 6px 10px;
|
||||
|
||||
max-width: 900px;
|
||||
min-width: 220px;
|
||||
@@ -10,7 +11,7 @@
|
||||
border: none;
|
||||
border-radius: $radius;
|
||||
box-shadow: 0 0 0 1px $halfGray;
|
||||
padding: 6px 6px 6px 10px;
|
||||
padding: var(--spacing);
|
||||
|
||||
.Icon {
|
||||
height: $margin * 2;
|
||||
|
||||
Reference in new issue
Block a user