PromucFlow_constructor/app/client/src/widgets/FilepickerWidget.tsx

490 lines
14 KiB
TypeScript
Raw Normal View History

import React from "react";
2019-11-05 05:09:50 +00:00
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
2019-11-25 05:07:27 +00:00
import { WidgetType } from "constants/WidgetConstants";
import FilePickerComponent from "components/designSystems/appsmith/FilePickerComponent";
2019-11-05 05:09:50 +00:00
import Uppy from "@uppy/core";
import GoogleDrive from "@uppy/google-drive";
import Webcam from "@uppy/webcam";
import Url from "@uppy/url";
import OneDrive from "@uppy/onedrive";
2020-03-16 07:59:07 +00:00
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/WidgetValidation";
2019-11-22 13:12:39 +00:00
import { VALIDATION_TYPES } from "constants/WidgetValidation";
2020-03-13 07:24:03 +00:00
import { EventType, ExecutionResult } from "constants/ActionConstants";
import { DerivedPropertiesMap } from "utils/WidgetFactory";
import Dashboard from "@uppy/dashboard";
2020-02-24 14:28:20 +00:00
import shallowequal from "shallowequal";
2020-03-06 09:20:18 +00:00
import _ from "lodash";
import * as Sentry from "@sentry/react";
2020-10-06 09:01:51 +00:00
import withMeta, { WithMeta } from "./MetaHOC";
2019-11-05 05:09:50 +00:00
2020-06-08 11:29:20 +00:00
class FilePickerWidget extends BaseWidget<
FilePickerWidgetProps,
FilePickerWidgetState
> {
2019-11-05 05:09:50 +00:00
constructor(props: FilePickerWidgetProps) {
super(props);
2020-06-08 11:29:20 +00:00
this.state = {
isLoading: false,
uppy: this.initializeUppy(),
2020-06-08 11:29:20 +00:00
};
2019-11-05 05:09:50 +00:00
}
static getPropertyPaneConfig() {
return [
{
sectionName: "General",
children: [
{
propertyName: "label",
label: "Label",
controlType: "INPUT_TEXT",
helpText: "Sets the label of the button",
placeholderText: "Enter label text",
inputType: "TEXT",
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "maxNumFiles",
label: "Max No. files",
helpText:
"Sets the maximum number of files that can be uploaded at once",
controlType: "INPUT_TEXT",
placeholderText: "Enter no. of files",
inputType: "INTEGER",
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "maxFileSize",
helpText: "Sets the maximum size of each file that can be uploaded",
label: "Max file size",
controlType: "INPUT_TEXT",
placeholderText: "File size in mb",
inputType: "INTEGER",
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "allowedFileTypes",
helpText: "Restricts the type of files which can be uploaded",
label: "Allowed File Types",
controlType: "MULTI_SELECT",
placeholderText: "Select file types",
options: [
{
label: "Any File",
value: "*",
},
{
label: "Images",
value: "image/*",
},
{
label: "Videos",
value: "video/*",
},
{
label: "Audio",
value: "audio/*",
},
{
label: "Text",
value: "text/*",
},
{
label: "MS Word",
value: ".doc",
},
{
label: "JPEG",
value: "image/jpeg",
},
{
label: "PNG",
value: ".png",
},
],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
{
helpText: "Set the format of the data read from the files",
propertyName: "fileDataType",
label: "Data Format",
controlType: "DROP_DOWN",
options: [
{
2021-03-12 09:13:47 +00:00
label: FileDataTypes.Base64,
value: FileDataTypes.Base64,
},
{
2021-03-12 09:13:47 +00:00
label: FileDataTypes.Binary,
value: FileDataTypes.Binary,
},
{
2021-03-12 09:13:47 +00:00
label: FileDataTypes.Text,
value: FileDataTypes.Text,
},
],
isBindProperty: false,
isTriggerProperty: false,
},
{
propertyName: "isRequired",
label: "Required",
helpText: "Makes input to the widget mandatory",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "isVisible",
label: "Visible",
helpText: "Controls the visibility of the widget",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "uploadedFileUrlPaths",
helpText:
"Stores the url of the uploaded file so that it can be referenced in an action later",
label: "Uploaded File URLs",
controlType: "INPUT_TEXT",
placeholderText: 'Enter [ "url1", "url2" ]',
inputType: "TEXT",
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "isDisabled",
label: "Disable",
helpText: "Disables input to this widget",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
],
},
{
sectionName: "Actions",
children: [
{
helpText:
"Triggers an action when the user selects a file. Upload files to a CDN here and store their urls in uploadedFileUrls",
propertyName: "onFilesSelected",
label: "onFilesSelected",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
];
}
2019-11-22 13:12:39 +00:00
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
2020-03-16 07:59:07 +00:00
...BASE_WIDGET_VALIDATION,
2019-11-22 13:12:39 +00:00
label: VALIDATION_TYPES.TEXT,
maxNumFiles: VALIDATION_TYPES.NUMBER,
allowedFileTypes: VALIDATION_TYPES.ARRAY,
selectedFiles: VALIDATION_TYPES.ARRAY,
2020-03-06 09:45:21 +00:00
isRequired: VALIDATION_TYPES.BOOLEAN,
// onFilesSelected: VALIDATION_TYPES.ACTION_SELECTOR,
2020-03-06 09:45:21 +00:00
};
}
static getDerivedPropertiesMap(): DerivedPropertiesMap {
return {
isValid: `{{ this.isRequired ? this.files.length > 0 : true }}`,
files: `{{this.selectedFiles.map((file) => { return { ...file, data: this.fileDataType === "Base64" ? file.base64 : this.fileDataType === "Binary" ? file.raw : file.text } })}}`,
2019-11-22 13:12:39 +00:00
};
}
2020-04-17 16:15:09 +00:00
static getMetaPropertiesMap(): Record<string, any> {
return {
selectedFiles: [],
2020-04-17 16:15:09 +00:00
uploadedFileData: {},
};
}
/**
* if uppy is not initialized before, initialize it
* else setState of uppy instance
*/
initializeUppy = () => {
const uppyState = {
2019-11-05 05:09:50 +00:00
id: this.props.widgetId,
autoProceed: false,
2019-11-05 05:09:50 +00:00
allowMultipleUploads: true,
debug: false,
restrictions: {
maxFileSize: this.props.maxFileSize
? this.props.maxFileSize * 1024 * 1024
: null,
maxNumberOfFiles: this.props.maxNumFiles,
minNumberOfFiles: null,
allowedFileTypes:
this.props.allowedFileTypes &&
(this.props.allowedFileTypes.includes("*") ||
_.isEmpty(this.props.allowedFileTypes))
? null
: this.props.allowedFileTypes,
},
};
return Uppy(uppyState);
};
/**
* set states on the uppy instance with new values
*/
reinitializeUppy = (props: FilePickerWidgetProps) => {
const uppyState = {
id: props.widgetId,
autoProceed: false,
allowMultipleUploads: true,
debug: false,
2019-11-05 05:09:50 +00:00
restrictions: {
2020-03-11 14:37:12 +00:00
maxFileSize: props.maxFileSize ? props.maxFileSize * 1024 * 1024 : null,
2019-11-05 05:09:50 +00:00
maxNumberOfFiles: props.maxNumFiles,
minNumberOfFiles: null,
2020-03-06 09:20:18 +00:00
allowedFileTypes:
props.allowedFileTypes &&
(this.props.allowedFileTypes.includes("*") ||
2020-03-06 09:20:18 +00:00
_.isEmpty(props.allowedFileTypes))
? null
: props.allowedFileTypes,
2019-11-05 05:09:50 +00:00
},
};
this.state.uppy.setOptions(uppyState);
};
/**
* add all uppy events listeners needed
*/
initializeUppyEventListeners = () => {
this.state.uppy
.use(Dashboard, {
target: "body",
metaFields: [],
inline: false,
width: 750,
height: 550,
thumbnailWidth: 280,
showLinkToFileUploadResult: true,
showProgressDetails: false,
hideUploadButton: false,
hideProgressAfterFinish: false,
note: null,
closeAfterFinish: true,
closeModalOnClickOutside: true,
disableStatusBar: false,
disableInformer: false,
disableThumbnailGenerator: false,
disablePageScrollWhenModalOpen: true,
proudlyDisplayPoweredByUppy: false,
onRequestCloseModal: () => {
const plugin = this.state.uppy.getPlugin("Dashboard");
if (plugin) {
plugin.closeModal();
}
},
locale: {},
})
2019-11-05 05:09:50 +00:00
.use(GoogleDrive, { companionUrl: "https://companion.uppy.io" })
.use(Url, { companionUrl: "https://companion.uppy.io" })
.use(OneDrive, {
companionUrl: "https://companion.uppy.io/",
})
.use(Webcam, {
onBeforeSnapshot: () => Promise.resolve(),
countdown: false,
mirror: true,
facingMode: "user",
locale: {},
});
this.state.uppy.on("file-removed", (file: any) => {
const updatedFiles = this.props.selectedFiles
? this.props.selectedFiles.filter((dslFile) => {
2020-03-13 07:24:03 +00:00
return file.id !== dslFile.id;
})
: [];
2020-10-06 09:01:51 +00:00
this.props.updateWidgetMetaProperty("files", updatedFiles);
});
this.state.uppy.on("files-added", (files: any[]) => {
const dslFiles = this.props.selectedFiles
? [...this.props.selectedFiles]
: [];
const fileReaderPromises = files.map((file) => {
const reader = new FileReader();
return new Promise((resolve) => {
reader.readAsDataURL(file.data);
reader.onloadend = () => {
const base64data = reader.result;
const binaryReader = new FileReader();
binaryReader.readAsBinaryString(file.data);
binaryReader.onloadend = () => {
const rawData = binaryReader.result;
const textReader = new FileReader();
textReader.readAsText(file.data);
textReader.onloadend = () => {
const text = textReader.result;
const newFile = {
type: file.type,
id: file.id,
base64: base64data,
raw: rawData,
text: text,
data:
2021-03-12 09:13:47 +00:00
this.props.fileDataType === FileDataTypes.Base64
? base64data
2021-03-12 09:13:47 +00:00
: this.props.fileDataType === FileDataTypes.Binary
? rawData
: text,
name: file.meta ? file.meta.name : undefined,
};
resolve(newFile);
};
};
};
});
});
Promise.all(fileReaderPromises).then((files) => {
this.props.updateWidgetMetaProperty(
"selectedFiles",
dslFiles.concat(files),
);
});
});
this.state.uppy.on("upload", () => {
this.onFilesSelected();
});
2019-11-05 05:09:50 +00:00
};
/**
* this function is called when user selects the files and it do two things:
* 1. calls the action if any
* 2. set isLoading prop to true when calling the action
*/
onFilesSelected = () => {
if (this.props.onFilesSelected) {
this.executeAction({
dynamicString: this.props.onFilesSelected,
event: {
type: EventType.ON_FILES_SELECTED,
2020-03-13 07:24:03 +00:00
callback: this.handleFileUploaded,
},
});
this.setState({ isLoading: true });
}
};
/**
* sets uploadFilesUrl in meta propety and sets isLoading to false
*
* @param result
*/
2020-03-13 07:24:03 +00:00
handleFileUploaded = (result: ExecutionResult) => {
if (result.success) {
2020-10-06 09:01:51 +00:00
this.props.updateWidgetMetaProperty(
"uploadedFileUrls",
this.props.uploadedFileUrlPaths,
2020-03-13 07:24:03 +00:00
);
this.setState({ isLoading: false });
2020-03-13 07:24:03 +00:00
}
};
2019-11-05 05:09:50 +00:00
componentDidUpdate(prevProps: FilePickerWidgetProps) {
super.componentDidUpdate(prevProps);
if (
prevProps.selectedFiles &&
prevProps.selectedFiles.length > 0 &&
this.props.selectedFiles === undefined
2020-03-13 07:24:03 +00:00
) {
this.state.uppy.reset();
2020-03-13 07:24:03 +00:00
} else if (
2020-02-24 14:28:20 +00:00
!shallowequal(prevProps.allowedFileTypes, this.props.allowedFileTypes) ||
2020-03-06 09:20:18 +00:00
prevProps.maxNumFiles !== this.props.maxNumFiles ||
2020-03-10 10:42:04 +00:00
prevProps.maxFileSize !== this.props.maxFileSize
2019-11-05 05:09:50 +00:00
) {
this.reinitializeUppy(this.props);
2019-11-05 05:09:50 +00:00
}
}
componentDidMount() {
super.componentDidMount();
this.initializeUppyEventListeners();
2019-11-05 05:09:50 +00:00
}
componentWillUnmount() {
this.state.uppy.close();
2019-11-05 05:09:50 +00:00
}
getPageView() {
return (
<FilePickerComponent
uppy={this.state.uppy}
widgetId={this.props.widgetId}
key={this.props.widgetId}
label={this.props.label}
files={this.props.selectedFiles || []}
isLoading={this.props.isLoading || this.state.isLoading}
isDisabled={this.props.isDisabled}
/>
2019-11-05 05:09:50 +00:00
);
}
getWidgetType(): WidgetType {
return "FILE_PICKER_WIDGET";
}
}
2020-06-08 11:29:20 +00:00
export interface FilePickerWidgetState extends WidgetState {
isLoading: boolean;
uppy: any;
2020-06-08 11:29:20 +00:00
}
2020-10-06 09:01:51 +00:00
export interface FilePickerWidgetProps extends WidgetProps, WithMeta {
2019-11-05 05:09:50 +00:00
label: string;
maxNumFiles?: number;
2020-03-06 09:20:18 +00:00
maxFileSize?: number;
selectedFiles?: any[];
2019-11-05 05:09:50 +00:00
allowedFileTypes: string[];
onFilesSelected?: string;
2021-03-12 09:17:42 +00:00
fileDataType: FileDataTypes;
2020-03-06 09:45:21 +00:00
isRequired?: boolean;
uploadedFileUrlPaths?: string;
2019-11-05 05:09:50 +00:00
}
2021-03-12 09:17:42 +00:00
export enum FileDataTypes {
2021-03-12 09:13:47 +00:00
Base64 = "Base64",
Text = "Text",
Binary = "Binary",
}
2019-11-05 05:09:50 +00:00
export default FilePickerWidget;
2020-10-06 09:01:51 +00:00
export const ProfiledFilePickerWidget = Sentry.withProfiler(
withMeta(FilePickerWidget),
);