Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/client/src/constants/HelpConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export const HelpMap = {
path: "/widget-reference/filepicker",
searchKey: "File picker",
},
FILE_PICKER_WIDGET_V2: {
path: "/widget-reference/filepicker",
searchKey: "File picker",
},
FORM_BUTTON_WIDGET: {
path: "",
searchKey: "",
Expand Down
1 change: 1 addition & 0 deletions app/client/src/constants/WidgetConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export enum WidgetTypes {
CANVAS_WIDGET = "CANVAS_WIDGET",
ICON_WIDGET = "ICON_WIDGET",
FILE_PICKER_WIDGET = "FILE_PICKER_WIDGET",
FILE_PICKER_WIDGET_V2 = "FILE_PICKER_WIDGET_V2",
VIDEO_WIDGET = "VIDEO_WIDGET",
SKELETON_WIDGET = "SKELETON_WIDGET",
LIST_WIDGET = "LIST_WIDGET",
Expand Down
2 changes: 1 addition & 1 deletion app/client/src/icons/WidgetIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const WidgetIcons: {
<ImageIcon />
</IconWrapper>
),
FILE_PICKER_WIDGET: (props: IconProps) => (
FILE_PICKER_WIDGET_V2: (props: IconProps) => (
<IconWrapper {...props}>
<FilePickerIcon />
</IconWrapper>
Expand Down
17 changes: 17 additions & 0 deletions app/client/src/mockResponses/WidgetConfigResponse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,23 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
isRequired: false,
isDisabled: false,
},
FILE_PICKER_WIDGET_V2: {
rows: 1 * GRID_DENSITY_MIGRATION_V1,
files: [],
selectedFiles: [],
defaultSelectedFiles: [],
allowedFileTypes: [],
label: "Select Files",
columns: 4 * GRID_DENSITY_MIGRATION_V1,
maxNumFiles: 1,
maxFileSize: 5,
fileDataType: FileDataTypes.Base64,
widgetName: "FilePicker",
isDefaultClickDisabled: true,
version: 1,
isRequired: false,
isDisabled: false,
},
TABS_WIDGET: {
rows: 7 * GRID_DENSITY_MIGRATION_V1,
columns: 8 * GRID_DENSITY_MIGRATION_V1,
Expand Down
2 changes: 1 addition & 1 deletion app/client/src/mockResponses/WidgetSidebarResponse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const WidgetSidebarResponse: WidgetCardProps[] = [
key: generateReactKey(),
},
{
type: "FILE_PICKER_WIDGET",
type: "FILE_PICKER_WIDGET_V2",
widgetCardName: "FilePicker",
key: generateReactKey(),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { DropdownWidgetProps } from "widgets/DropdownWidget";
import { CheckboxWidgetProps } from "widgets/CheckboxWidget";
import { RadioGroupWidgetProps } from "widgets/RadioGroupWidget";
import { FilePickerWidgetProps } from "widgets/FilepickerWidget";
import { FilePickerWidgetV2Props } from "widgets/FilepickerWidgetV2";

import {
TabsWidgetProps,
TabContainerWidgetProps,
Expand Down Expand Up @@ -76,6 +78,7 @@ export interface WidgetConfigReducerState {
SWITCH_WIDGET: Partial<SwitchWidgetProps> & WidgetConfigProps;
RADIO_GROUP_WIDGET: Partial<RadioGroupWidgetProps> & WidgetConfigProps;
FILE_PICKER_WIDGET: Partial<FilePickerWidgetProps> & WidgetConfigProps;
FILE_PICKER_WIDGET_V2: Partial<FilePickerWidgetV2Props> & WidgetConfigProps;
TABS_WIDGET: Partial<TabsWidgetProps<TabContainerWidgetProps>> &
WidgetConfigProps;
TABS_MIGRATOR_WIDGET: Partial<TabsWidgetProps<TabContainerWidgetProps>> &
Expand Down
37 changes: 34 additions & 3 deletions app/client/src/sagas/ActionExecutionSagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ import {
isActionSaving,
} from "selectors/entitiesSelector";
import { AppState } from "reducers";
import { mapToPropList } from "utils/AppsmithUtils";
import { isBlobUrl, mapToPropList, parseBlobUrl } from "utils/AppsmithUtils";
import { validateResponse } from "sagas/ErrorSagas";
import { TypeOptions } from "react-toastify";
import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "constants/ApiConstants";
Expand Down Expand Up @@ -119,6 +119,7 @@ import { ENTITY_TYPE, PLATFORM_ERROR } from "entities/AppsmithConsole";
import LOG_TYPE from "entities/AppsmithConsole/logtype";
import { matchPath } from "react-router";
import { setDataUrl } from "./PageSagas";
import FileDataTypes from "widgets/FileDataTypes";

enum ActionResponseDataTypes {
BINARY = "BINARY",
Expand Down Expand Up @@ -481,11 +482,16 @@ export function* evaluateActionParams(

// Convert to object and transform non string values
const actionParams: Record<string, string> = {};
bindings.forEach((key, i) => {
for (let i = 0; i < bindings.length; i++) {
const key = bindings[i];
let value = values[i];
if (typeof value === "object") value = JSON.stringify(value);
if (isBlobUrl(value)) {
value = yield call(readBlob, value);
}

actionParams[key] = value;
});
}
return mapToPropList(actionParams);
}

Expand Down Expand Up @@ -1139,3 +1145,28 @@ export function* watchActionExecutionSagas() {
),
]);
}

/**
*
* @param blobUrl string A blob url with type added a query param
* @returns promise that resolves to file content
*/
function* readBlob(blobUrl: string): any {
const [url, fileType] = parseBlobUrl(blobUrl);
const file = yield fetch(url).then((r) => r.blob());

const data = yield new Promise((resolve) => {
const reader = new FileReader();
if (fileType === FileDataTypes.Base64) {
reader.readAsDataURL(file);
} else if (fileType === FileDataTypes.Binary) {
reader.readAsBinaryString(file);
} else {
reader.readAsText(file);
}
reader.onloadend = () => {
resolve(reader.result);
};
});
return data;
}
32 changes: 32 additions & 0 deletions app/client/src/utils/AppsmithUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,35 @@ export const retryPromise = (
export const getRandomPaletteColor = (colorPalette: string[]) => {
return colorPalette[Math.floor(Math.random() * colorPalette.length)];
};

export const isBlobUrl = (url: string) => {
return typeof url === "string" && url.startsWith("blob:");
};

/**
*
* @param data string file data
* @param type string file type
* @returns string containing blob id and type
*/
export const createBlobUrl = (data: string, type: string) => {
let url = URL.createObjectURL(data);
url = url.replace(
`${window.location.protocol}//${window.location.hostname}/`,
"",
);

return `${url}?type=${type}`;
};

/**
*
* @param blobId string blob id along with type.
* @returns [string,string] [blobUrl, type]
*/
export const parseBlobUrl = (blobId: string) => {
const url = `blob:${window.location.protocol}//${
window.location.hostname
}/${blobId.substring(5)}`;
return url.split("?type=");
};
2 changes: 1 addition & 1 deletion app/client/src/utils/TypeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const getType = (value: unknown) => {

export function isURL(str: string) {
const pattern = new RegExp(
"^(https?:\\/\\/)?" + // protocol
"^((blob:)?https?:\\/\\/)?" + // protocol
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
Expand Down
19 changes: 19 additions & 0 deletions app/client/src/utils/WidgetRegistry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ import FilePickerWidget, {
FilePickerWidgetProps,
ProfiledFilePickerWidget,
} from "widgets/FilepickerWidget";

import FilePickerWidgetV2, {
FilePickerWidgetV2Props,
ProfiledFilePickerWidgetV2,
} from "widgets/FilepickerWidgetV2";
import DatePickerWidget, {
DatePickerWidgetProps,
ProfiledDatePickerWidget,
Expand Down Expand Up @@ -290,6 +295,20 @@ export default class WidgetBuilderRegistry {
FilePickerWidget.getMetaPropertiesMap(),
FilePickerWidget.getPropertyPaneConfig(),
);

WidgetFactory.registerWidgetBuilder(
"FILE_PICKER_WIDGET_V2",
{
buildWidget(widgetData: FilePickerWidgetV2Props): JSX.Element {
return <ProfiledFilePickerWidgetV2 {...widgetData} />;
},
},
FilePickerWidgetV2.getDerivedPropertiesMap(),
FilePickerWidgetV2.getDefaultPropertiesMap(),
FilePickerWidgetV2.getMetaPropertiesMap(),
FilePickerWidgetV2.getPropertyPaneConfig(),
);

WidgetFactory.registerWidgetBuilder(
"DATE_PICKER_WIDGET",
{
Expand Down
8 changes: 8 additions & 0 deletions app/client/src/utils/autocomplete/EntityDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,14 @@ export const entityDefinitions = {
files: "[file]",
isDisabled: "bool",
},
FILE_PICKER_WIDGET_V2: {
"!doc":
"Filepicker widget is used to allow users to upload files from their local machines to any cloud storage via API. Cloudinary and Amazon S3 have simple APIs for cloud storage uploads",
"!url": "https://docs.appsmith.com/widget-reference/filepicker",
isVisible: isVisible,
files: "[file]",
isDisabled: "bool",
},
LIST_WIDGET: (widget: any) => ({
"!doc":
"Containers are used to group widgets together to form logical higher order widgets. Containers let you organize your page better and move all the widgets inside them together.",
Expand Down
Loading