mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Migrating Vue components to React and stores refactoring (#585)
Signed-off-by: Roman <ixrock@gmail.com> Co-authored-by: Sebastian Malton <sebastian@malton.name> Co-authored-by: Sebastian Malton <smalton@mirantis.com> Co-authored-by: Lauri Nevala <lauri.nevala@gmail.com> Co-authored-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
297 files changed
+8186
-9103
No files matched your search
@@ -0,0 +1,11 @@
|
||||
.FilePicker {
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
color: var(--blue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import "./file-picker.scss"
|
||||
|
||||
import React from "react";
|
||||
import fse from "fs-extra";
|
||||
import path from "path";
|
||||
import { Icon } from "../icon";
|
||||
import { Spinner } from "../spinner";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import _ from "lodash";
|
||||
|
||||
export interface FileUploadProps {
|
||||
uploadDir: string;
|
||||
rename?: boolean;
|
||||
handler?(path: string[]): void;
|
||||
}
|
||||
|
||||
export interface MemoryUseProps {
|
||||
handler?(file: File[]): void;
|
||||
}
|
||||
|
||||
enum FileInputStatus {
|
||||
CLEAR = "clear",
|
||||
PROCESSING = "processing",
|
||||
ERROR = "error",
|
||||
}
|
||||
|
||||
export enum OverLimitStyle {
|
||||
REJECT = "reject",
|
||||
CAP = "cap",
|
||||
}
|
||||
|
||||
export enum OverSizeLimitStyle {
|
||||
REJECT = "reject",
|
||||
FILTER = "filter",
|
||||
}
|
||||
|
||||
export enum OverTotalSizeLimitStyle {
|
||||
REJECT = "reject",
|
||||
FILTER_LAST = "filter-last",
|
||||
FILTER_LARGEST = "filter-largest",
|
||||
}
|
||||
|
||||
export interface BaseProps {
|
||||
accept?: string;
|
||||
label: React.ReactNode;
|
||||
multiple?: boolean;
|
||||
|
||||
// limit is the optional maximum number of files to upload
|
||||
// the larger number is upper limit, the lower is lower limit
|
||||
// the lower limit is capped at 0 and the upper limit is capped at Infinity
|
||||
limit?: [number, number];
|
||||
|
||||
// default is "Reject"
|
||||
onOverLimit?: OverLimitStyle;
|
||||
|
||||
// individual files are checked before the total size.
|
||||
maxSize?: number;
|
||||
// default is "Reject"
|
||||
onOverSizeLimit?: OverSizeLimitStyle;
|
||||
|
||||
maxTotalSize?: number;
|
||||
// default is "Reject"
|
||||
onOverTotalSizeLimit?: OverTotalSizeLimitStyle;
|
||||
}
|
||||
|
||||
export type Props = BaseProps & (MemoryUseProps | FileUploadProps);
|
||||
|
||||
const defaultProps: Partial<Props> = {
|
||||
maxSize: Infinity,
|
||||
onOverSizeLimit: OverSizeLimitStyle.REJECT,
|
||||
maxTotalSize: Infinity,
|
||||
onOverLimit: OverLimitStyle.REJECT,
|
||||
onOverTotalSizeLimit: OverTotalSizeLimitStyle.REJECT,
|
||||
};
|
||||
|
||||
@observer
|
||||
export class FilePicker extends React.Component<Props> {
|
||||
static defaultProps = defaultProps as Object;
|
||||
|
||||
@observable status = FileInputStatus.CLEAR;
|
||||
@observable errorText?: string;
|
||||
|
||||
handleFileCount(files: File[]): File[] {
|
||||
const { limit: [minLimit, maxLimit] = [0, Infinity], onOverLimit } = this.props;
|
||||
if (files.length > maxLimit) {
|
||||
switch (onOverLimit) {
|
||||
case OverLimitStyle.CAP:
|
||||
files.length = maxLimit;
|
||||
break;
|
||||
case OverLimitStyle.REJECT:
|
||||
throw `Too many files. Expected at most ${maxLimit}. Got ${files.length}.`;
|
||||
}
|
||||
}
|
||||
if (files.length < minLimit) {
|
||||
throw `Too many files. Expected at most ${maxLimit}. Got ${files.length}.`;
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
handleIndiviualFileSizes(files: File[]): File[] {
|
||||
const { onOverSizeLimit, maxSize } = this.props;
|
||||
|
||||
switch (onOverSizeLimit) {
|
||||
case OverSizeLimitStyle.FILTER:
|
||||
return files.filter(file => file.size <= maxSize );
|
||||
case OverSizeLimitStyle.REJECT:
|
||||
const firstFileToLarge = files.find(file => file.size > maxSize);
|
||||
if (firstFileToLarge) {
|
||||
throw `${firstFileToLarge.name} is too large. Maximum size is ${maxSize}. Has size of ${firstFileToLarge.size}`;
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
handleTotalFileSizes(files: File[]): File[] {
|
||||
const { maxTotalSize, onOverTotalSizeLimit } = this.props;
|
||||
|
||||
const totalSize = _.sum(files.map(f => f.size));
|
||||
if (totalSize <= maxTotalSize) {
|
||||
return files;
|
||||
}
|
||||
|
||||
switch (onOverTotalSizeLimit) {
|
||||
case OverTotalSizeLimitStyle.FILTER_LARGEST:
|
||||
files = _.orderBy(files, ["size"])
|
||||
case OverTotalSizeLimitStyle.FILTER_LAST:
|
||||
let newTotalSize = totalSize;
|
||||
|
||||
for (;files.length > 0;) {
|
||||
newTotalSize -= files.pop().size;
|
||||
if (newTotalSize <= maxTotalSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return files;
|
||||
case OverTotalSizeLimitStyle.REJECT:
|
||||
throw `Total file size to upload is too large. Expected at most ${maxTotalSize}. Found ${totalSize}.`;
|
||||
}
|
||||
}
|
||||
|
||||
async handlePickFiles(selectedFiles: FileList) {
|
||||
const files: File[] = Array.from(selectedFiles);
|
||||
|
||||
try {
|
||||
const numberLimitedFiles = this.handleFileCount(files);
|
||||
const sizeLimitedFiles = this.handleIndiviualFileSizes(numberLimitedFiles);
|
||||
const totalSizeLimitedFiles = this.handleTotalFileSizes(sizeLimitedFiles);
|
||||
|
||||
if ("uploadDir" in this.props) {
|
||||
const { uploadDir } = this.props;
|
||||
this.status = FileInputStatus.PROCESSING;
|
||||
|
||||
const paths: string[] = [];
|
||||
const promises = totalSizeLimitedFiles.map(async file => {
|
||||
const destinationPath = path.join(uploadDir, file.name);
|
||||
paths.push(destinationPath);
|
||||
|
||||
return fse.copyFile(file.path, destinationPath);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
this.props.handler(paths);
|
||||
this.status = FileInputStatus.CLEAR;
|
||||
} else {
|
||||
this.props.handler(totalSizeLimitedFiles);
|
||||
}
|
||||
} catch (errorText) {
|
||||
this.status = FileInputStatus.ERROR;
|
||||
this.errorText = errorText;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { accept, label, multiple } = this.props;
|
||||
|
||||
return <div className="FilePicker">
|
||||
<label className="flex gaps align-center" htmlFor="file-upload">{label} {this.getIconRight()}</label>
|
||||
<input
|
||||
id="file-upload"
|
||||
name="FilePicker"
|
||||
type="file"
|
||||
accept={accept}
|
||||
multiple={multiple}
|
||||
onChange={(event) => this.handlePickFiles(event.target.files)}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
getIconRight(): React.ReactNode {
|
||||
switch (this.status) {
|
||||
case FileInputStatus.CLEAR:
|
||||
return <Icon className="clean" material="cloud_upload"></Icon>
|
||||
case FileInputStatus.PROCESSING:
|
||||
return <Spinner />;
|
||||
case FileInputStatus.ERROR:
|
||||
return <Icon material="error" title={this.errorText}></Icon>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./file-picker"
|
||||
Reference in new issue
Block a user