perf: reduce the bundle size, vol. 2 (#24969)
Co-authored-by: Tanvi Bhakta <tanvibhakta@gmail.com> Co-authored-by: Satish Gandham <hello@satishgandham.com>
This commit is contained in:
parent
25177f4837
commit
8a1870daa6
|
|
@ -35,6 +35,12 @@ const eslintConfig = {
|
|||
// Allow type imports as they don’t lead to bundling the dependency
|
||||
allowTypeImports: true,
|
||||
},
|
||||
{
|
||||
name: "sql-formatter",
|
||||
importNames: ["format"],
|
||||
message:
|
||||
"Reason: Instead of `import { format }` (which bundles all formatting dialects), please import only dialects you need (e.g. `import { formatDialect, postgresql }`. See https://github.com/sql-formatter-org/sql-formatter/issues/452",
|
||||
},
|
||||
],
|
||||
patterns: [
|
||||
...(baseNoRestrictedImports.patterns ?? []),
|
||||
|
|
@ -45,10 +51,10 @@ const eslintConfig = {
|
|||
],
|
||||
},
|
||||
],
|
||||
// Annoyingly, the `no-restricted-imports` rule doesn’t allow to restrict imports of
|
||||
// `editorComponents/CodeEditor` but not `editorComponents/CodeEditor/*`: https://stackoverflow.com/q/64995811/1192426
|
||||
// So we’re using `no-restricted-syntax` instead.
|
||||
"no-restricted-syntax": [
|
||||
// Annoyingly, the `no-restricted-imports` rule doesn’t allow to restrict imports of
|
||||
// `editorComponents/CodeEditor` but not `editorComponents/CodeEditor/*`: https://stackoverflow.com/q/64995811/1192426
|
||||
// So we’re using `no-restricted-syntax` instead.
|
||||
"error",
|
||||
{
|
||||
// Match all
|
||||
|
|
@ -61,6 +67,20 @@ const eslintConfig = {
|
|||
message:
|
||||
"Please don’t import CodeEditor directly – this will cause it to be bundled in the main chunk. Instead, use the LazyCodeEditor component.",
|
||||
},
|
||||
// Annoyingly, no-restricted-imports follows the gitignore exclude syntax,
|
||||
// so there’s no way to exclude all @uppy/* but not @uppy/*/*.css imports:
|
||||
// https://github.com/eslint/eslint/issues/16927
|
||||
{
|
||||
// Match all
|
||||
// - `import` statements
|
||||
// - that are not `import type` statements – we allow type imports as they don’t lead to bundling the dependency
|
||||
// - that import `@uppy/*` unless the `*` part ends with `.css`
|
||||
// Note: using `\\u002F` instead of `/` due to https://eslint.org/docs/latest/extend/selectors#known-issues
|
||||
selector:
|
||||
"ImportDeclaration[importKind!='type'][source.value=/^@uppy\\u002F(?!.*.css$)/]",
|
||||
message:
|
||||
"Please don’t import Uppy directly. End users rarely use Uppy (e.g. only when they need to upload a file) – but Uppy bundles ~200 kB of JS. Please import it lazily instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@
|
|||
"dayjs": "^1.10.6",
|
||||
"deep-diff": "^1.0.2",
|
||||
"design-system": "npm:@appsmithorg/design-system@2.1.15",
|
||||
"design-system-old": "npm:@appsmithorg/design-system-old@1.1.10",
|
||||
"design-system-old": "npm:@appsmithorg/design-system-old@1.1.11",
|
||||
"downloadjs": "^1.4.7",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-xml-parser": "^3.17.5",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseQueryGenerator } from "../BaseQueryGenerator";
|
||||
import { format } from "sql-formatter";
|
||||
import { formatDialect, sql } from "sql-formatter";
|
||||
import { QUERY_TYPE } from "../types";
|
||||
import type {
|
||||
WidgetQueryGenerationConfig,
|
||||
|
|
@ -74,9 +74,9 @@ export default abstract class MSSQL extends BaseQueryGenerator {
|
|||
);
|
||||
|
||||
//formats sql string
|
||||
const res = format(template, {
|
||||
const res = formatDialect(template, {
|
||||
params,
|
||||
language: "sql",
|
||||
dialect: sql,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseQueryGenerator } from "../BaseQueryGenerator";
|
||||
import { format } from "sql-formatter";
|
||||
import { formatDialect, mysql } from "sql-formatter";
|
||||
import { QUERY_TYPE } from "../types";
|
||||
import type {
|
||||
WidgetQueryGenerationConfig,
|
||||
|
|
@ -74,9 +74,9 @@ export default abstract class MySQL extends BaseQueryGenerator {
|
|||
);
|
||||
|
||||
//formats sql string
|
||||
const res = format(template, {
|
||||
const res = formatDialect(template, {
|
||||
params,
|
||||
language: "mysql",
|
||||
dialect: mysql,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseQueryGenerator } from "../BaseQueryGenerator";
|
||||
import { format } from "sql-formatter";
|
||||
import { formatDialect, postgresql } from "sql-formatter";
|
||||
import { QUERY_TYPE } from "../types";
|
||||
import type {
|
||||
WidgetQueryGenerationConfig,
|
||||
|
|
@ -82,9 +82,9 @@ export default abstract class PostgreSQL extends BaseQueryGenerator {
|
|||
{ template: "", params: {} },
|
||||
);
|
||||
//formats sql string
|
||||
const res = format(template, {
|
||||
const res = formatDialect(template, {
|
||||
params,
|
||||
language: "postgresql",
|
||||
dialect: postgresql,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseQueryGenerator } from "../BaseQueryGenerator";
|
||||
import { format } from "sql-formatter";
|
||||
import { formatDialect, snowflake } from "sql-formatter";
|
||||
import { QUERY_TYPE } from "../types";
|
||||
import type {
|
||||
WidgetQueryGenerationConfig,
|
||||
|
|
@ -75,9 +75,9 @@ export default abstract class Snowflake extends BaseQueryGenerator {
|
|||
);
|
||||
|
||||
//formats sql string
|
||||
const res = format(template, {
|
||||
const res = formatDialect(template, {
|
||||
params,
|
||||
language: "snowflake",
|
||||
dialect: snowflake,
|
||||
paramTypes: {
|
||||
positional: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ import type { Action } from "entities/Action";
|
|||
import { SegmentedControlContainer } from "../../pages/Editor/QueryEditor/EditorJSONtoForm";
|
||||
import ActionExecutionInProgressView from "./ActionExecutionInProgressView";
|
||||
import { CloseDebugger } from "./Debugger/DebuggerTabs";
|
||||
import { EMPTY_RESPONSE } from "./emptyResponse";
|
||||
|
||||
type TextStyleProps = {
|
||||
accent: "primary" | "secondary" | "error";
|
||||
|
|
@ -202,22 +203,6 @@ type Props = ReduxStateProps &
|
|||
responseDisplayFormat: { title: string; value: string };
|
||||
};
|
||||
|
||||
export const EMPTY_RESPONSE: ActionResponse = {
|
||||
statusCode: "",
|
||||
duration: "",
|
||||
body: "",
|
||||
headers: {},
|
||||
request: {
|
||||
headers: {},
|
||||
body: {},
|
||||
httpMethod: "",
|
||||
url: "",
|
||||
},
|
||||
size: "",
|
||||
responseDisplayFormat: "",
|
||||
dataTypes: [],
|
||||
};
|
||||
|
||||
const StatusCodeText = styled(BaseText)<PropsWithChildren<{ code: string }>>`
|
||||
color: ${(props) =>
|
||||
props.code.startsWith("2")
|
||||
|
|
|
|||
17
app/client/src/components/editorComponents/emptyResponse.tsx
Normal file
17
app/client/src/components/editorComponents/emptyResponse.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import type { ActionResponse } from "api/ActionAPI";
|
||||
|
||||
export const EMPTY_RESPONSE: ActionResponse = {
|
||||
statusCode: "",
|
||||
duration: "",
|
||||
body: "",
|
||||
headers: {},
|
||||
request: {
|
||||
headers: {},
|
||||
body: {},
|
||||
httpMethod: "",
|
||||
url: "",
|
||||
},
|
||||
size: "",
|
||||
responseDisplayFormat: "",
|
||||
dataTypes: [],
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import type { ControlData, ControlProps } from "./BaseControl";
|
||||
import BaseControl from "./BaseControl";
|
||||
import moment from "moment-timezone";
|
||||
import moment from "moment";
|
||||
import { TimePrecision } from "@blueprintjs/datetime";
|
||||
import type { WidgetProps } from "widgets/BaseWidget";
|
||||
import { ISO_DATE_FORMAT } from "constants/WidgetValidation";
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import classNames from "classnames";
|
|||
import styled from "styled-components";
|
||||
import { API_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
|
||||
import type { Action } from "entities/Action";
|
||||
import { EMPTY_RESPONSE } from "components/editorComponents/ApiResponseView";
|
||||
import { EMPTY_RESPONSE } from "components/editorComponents/emptyResponse";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import { getApiName } from "selectors/formSelectors";
|
||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import styled from "styled-components";
|
|||
import { API_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
|
||||
import type { Action } from "entities/Action";
|
||||
import PostBodyData from "./PostBodyData";
|
||||
import { EMPTY_RESPONSE } from "components/editorComponents/ApiResponseView";
|
||||
import { EMPTY_RESPONSE } from "components/editorComponents/emptyResponse";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import { getApiName } from "selectors/formSelectors";
|
||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { ILinter } from "./linters";
|
||||
import { BaseLinter, WorkerLinter } from "./linters";
|
||||
import { WorkerLinter } from "./linters";
|
||||
import type { LintTreeRequestPayload, updateJSLibraryProps } from "./types";
|
||||
|
||||
export class Linter {
|
||||
linter: ILinter;
|
||||
constructor(options: { useWorker: boolean }) {
|
||||
this.linter = options.useWorker ? new WorkerLinter() : new BaseLinter();
|
||||
constructor() {
|
||||
this.linter = new WorkerLinter();
|
||||
this.lintTree = this.lintTree.bind(this);
|
||||
this.updateJSLibraryGlobals = this.updateJSLibraryGlobals.bind(this);
|
||||
this.start = this.start.bind(this);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import type {
|
|||
updateJSLibraryProps,
|
||||
} from "plugins/Linting/types";
|
||||
import { LINT_WORKER_ACTIONS as LINT_ACTIONS } from "plugins/Linting/types";
|
||||
import { handlerMap } from "plugins/Linting/handlers";
|
||||
|
||||
export interface ILinter {
|
||||
lintTree(args: LintTreeRequestPayload): any;
|
||||
|
|
@ -13,21 +12,6 @@ export interface ILinter {
|
|||
shutdown(): void;
|
||||
}
|
||||
|
||||
export class BaseLinter implements ILinter {
|
||||
lintTree(args: LintTreeRequestPayload) {
|
||||
return handlerMap[LINT_ACTIONS.LINT_TREE](args);
|
||||
}
|
||||
updateJSLibraryGlobals(args: updateJSLibraryProps) {
|
||||
return handlerMap[LINT_ACTIONS.UPDATE_LINT_GLOBALS](args);
|
||||
}
|
||||
start() {
|
||||
return;
|
||||
}
|
||||
shutdown() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkerLinter implements ILinter {
|
||||
server: GracefulWorkerService;
|
||||
constructor() {
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ import PerformanceTracker, {
|
|||
PerformanceTransactionName,
|
||||
} from "utils/PerformanceTracker";
|
||||
import * as log from "loglevel";
|
||||
import { EMPTY_RESPONSE } from "components/editorComponents/ApiResponseView";
|
||||
import { EMPTY_RESPONSE } from "components/editorComponents/emptyResponse";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "@appsmith/constants/ApiConstants";
|
||||
import { evaluateActionBindings } from "sagas/EvaluationsSaga";
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { getFixedTimeDifference } from "workers/common/DataTreeEvaluator/utils";
|
|||
|
||||
const APPSMITH_CONFIGS = getAppsmithConfigs();
|
||||
|
||||
export const lintWorker = new Linter({ useWorker: true });
|
||||
export const lintWorker = new Linter();
|
||||
|
||||
function* updateLintGlobals(
|
||||
action: ReduxAction<{ add?: boolean; libs: TJSLibrary[] }>,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import {
|
|||
|
||||
import { getCurrentUser } from "selectors/usersSelectors";
|
||||
import history from "utils/history";
|
||||
import TourApp from "pages/Editor/GuidedTour/app.json";
|
||||
|
||||
import {
|
||||
getHadReachedStep,
|
||||
|
|
@ -119,6 +118,9 @@ function* createApplication() {
|
|||
}
|
||||
|
||||
if (workspace) {
|
||||
const TourAppPromise = import("pages/Editor/GuidedTour/app.json");
|
||||
const TourApp: Awaited<typeof TourAppPromise> = yield TourAppPromise;
|
||||
|
||||
const appFileObject = new File([JSON.stringify(TourApp)], "app.json", {
|
||||
type: "application/json",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,4 @@ import PropertyControlRegistry from "../PropertyControlRegistry";
|
|||
export const editorInitializer = async () => {
|
||||
registerWidgets();
|
||||
PropertyControlRegistry.registerPropertyControlBuilders();
|
||||
|
||||
const { default: moment } = await import("moment-timezone");
|
||||
moment.tz.setDefault(moment.tz.guess());
|
||||
};
|
||||
|
|
|
|||
36
app/client/src/utils/importUppy.ts
Normal file
36
app/client/src/utils/importUppy.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// We’re setting this flag to true when we know for sure the Uppy module was loaded and initialized.
|
||||
// When it’s `true`, the other modules will know that the importUppy function will resolve immediately
|
||||
// (in the next tick). They can use it to e.g. decide whether to show the loading spinner
|
||||
export let isUppyLoaded = false;
|
||||
|
||||
export async function importUppy() {
|
||||
const [Uppy, Dashboard, GoogleDrive, OneDrive, Url, Webcam] =
|
||||
await Promise.all([
|
||||
import(/* webpackChunkName: "uppy" */ "@uppy/core").then(
|
||||
(m) => m.default,
|
||||
),
|
||||
import(/* webpackChunkName: "uppy" */ "@uppy/dashboard").then(
|
||||
(m) => m.default,
|
||||
),
|
||||
import(/* webpackChunkName: "uppy" */ "@uppy/google-drive").then(
|
||||
(m) => m.default,
|
||||
),
|
||||
import(/* webpackChunkName: "uppy" */ "@uppy/onedrive").then(
|
||||
(m) => m.default,
|
||||
),
|
||||
import(/* webpackChunkName: "uppy" */ "@uppy/url").then((m) => m.default),
|
||||
import(/* webpackChunkName: "uppy" */ "@uppy/webcam").then(
|
||||
(m) => m.default,
|
||||
),
|
||||
]);
|
||||
isUppyLoaded = true;
|
||||
|
||||
return {
|
||||
Uppy,
|
||||
Dashboard,
|
||||
GoogleDrive,
|
||||
OneDrive,
|
||||
Url,
|
||||
Webcam,
|
||||
};
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ export default {
|
|||
.replace(new RegExp(`[${getLocaleDecimalSeperator()}]`), "."),
|
||||
);
|
||||
|
||||
if (_.isNaN(parsed)) {
|
||||
if (Number.isNaN(parsed)) {
|
||||
parsed = undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { ControlGroup, Classes, Label } from "@blueprintjs/core";
|
||||
import type { ComponentProps } from "widgets/BaseComponent";
|
||||
import { DateInput } from "@blueprintjs/datetime";
|
||||
import moment from "moment-timezone";
|
||||
import moment from "moment";
|
||||
import "@blueprintjs/datetime/lib/css/blueprint-datetime.css";
|
||||
import type { DatePickerType } from "../constants";
|
||||
import { WIDGET_PADDING } from "constants/WidgetConstants";
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type { IRef, Alignment } from "@blueprintjs/core";
|
|||
import { ControlGroup, Classes } from "@blueprintjs/core";
|
||||
import type { ComponentProps } from "widgets/BaseComponent";
|
||||
import { DateInput } from "@blueprintjs/datetime";
|
||||
import moment from "moment-timezone";
|
||||
import moment from "moment";
|
||||
import "@blueprintjs/datetime/lib/css/blueprint-datetime.css";
|
||||
import type { DatePickerType } from "../constants";
|
||||
import { TimePrecision } from "../constants";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import React from "react";
|
||||
import type { ComponentProps } from "widgets/BaseComponent";
|
||||
import "@uppy/core/dist/style.css";
|
||||
import "@uppy/dashboard/dist/style.css";
|
||||
import "@uppy/webcam/dist/style.css";
|
||||
import { BaseButton } from "widgets/ButtonWidget/component";
|
||||
import { Colors } from "constants/Colors";
|
||||
|
||||
|
|
@ -13,13 +10,6 @@ function FilePickerComponent(props: FilePickerComponentProps) {
|
|||
computedLabel = `${props.files.length} files selected`;
|
||||
}
|
||||
|
||||
/**
|
||||
* opens modal
|
||||
*/
|
||||
const openModal = () => {
|
||||
props.uppy.getPlugin("Dashboard").openModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseButton
|
||||
borderRadius={props.borderRadius}
|
||||
|
|
@ -30,7 +20,7 @@ function FilePickerComponent(props: FilePickerComponentProps) {
|
|||
maxWidth={props.maxWidth}
|
||||
minHeight={props.minHeight}
|
||||
minWidth={props.minWidth}
|
||||
onClick={openModal}
|
||||
onClick={props.openModal}
|
||||
shouldFitContent={props.shouldFitContent}
|
||||
text={computedLabel}
|
||||
/>
|
||||
|
|
@ -38,7 +28,7 @@ function FilePickerComponent(props: FilePickerComponentProps) {
|
|||
}
|
||||
export interface FilePickerComponentProps extends ComponentProps {
|
||||
label: string;
|
||||
uppy: any;
|
||||
openModal: () => void;
|
||||
isLoading: boolean;
|
||||
files?: any[];
|
||||
buttonColor: string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
import CloseIcon from "assets/icons/ads/cross.svg";
|
||||
import UpIcon from "assets/icons/ads/up-arrow.svg";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { createGlobalStyle } from "styled-components";
|
||||
import "@uppy/core/dist/style.css";
|
||||
import "@uppy/dashboard/dist/style.css";
|
||||
import "@uppy/webcam/dist/style.css";
|
||||
|
||||
// Using the `&&` trick (which duplicates the selector) to increase specificity of custom styles. Otherwise,
|
||||
// the custom styles may be overridden by @uppy/*.css imports – they have the same specificity,
|
||||
// and which styles exactly will be applied depends on the order of imports.
|
||||
const INCREASE_SPECIFICITY_SELECTOR = "&&";
|
||||
|
||||
export const FilePickerGlobalStyles = createGlobalStyle<{
|
||||
borderRadius?: string;
|
||||
}>`
|
||||
|
||||
/* Sets the font-family to theming font-family of the upload modal */
|
||||
.uppy-Root {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
font-family: var(--wds-font-family);
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************/
|
||||
/* Set the new dropHint upload icon */
|
||||
.uppy-Dashboard-dropFilesHereHint {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
background-image: none;
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
}
|
||||
}
|
||||
|
||||
.uppy-Dashboard-dropFilesHereHint {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
&::before {
|
||||
border: 2.5px solid var(--wds-accent-color);
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
display: inline-block;
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 43%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: inline-block;
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 46%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
|
||||
-webkit-mask-image: url(${UpIcon});
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-size: 30px;
|
||||
background: var(--wds-accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
/*********************************************************/
|
||||
/* Set the styles for the upload button */
|
||||
.uppy-StatusBar-actionBtn--upload {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
background-color: var(--wds-accent-color) !important;
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
}
|
||||
}
|
||||
|
||||
.uppy-Dashboard-Item-action--remove {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
/* Sets the border radius of the button when it is focused */
|
||||
&:focus {
|
||||
border-radius: ${({ borderRadius }) =>
|
||||
borderRadius === "0.375rem" ? "0.25rem" : borderRadius} !important;
|
||||
}
|
||||
|
||||
.uppy-c-icon {
|
||||
& path:first-child {
|
||||
/* Sets the black background of remove file button hidden */
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
& path:last-child {
|
||||
/* Sets the cross mark color of remove file button */
|
||||
fill: #858282;
|
||||
}
|
||||
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.06), 0px 1px 3px rgba(0, 0, 0, 0.1);
|
||||
|
||||
& {
|
||||
/* Sets the black background of remove file button hidden*/
|
||||
border-radius: ${({ borderRadius }) =>
|
||||
borderRadius === "0.375rem" ? "0.25rem" : borderRadius};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
/*********************************************************/
|
||||
/* Sets the back cancel button color to match theming primary color */
|
||||
.uppy-DashboardContent-back {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
color: var(--wds-accent-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--wds-accent-color);
|
||||
background-color: ${Colors.ATHENS_GRAY};
|
||||
}
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
/*********************************************************/
|
||||
/* Sets the style according to reskinning for x button at the top right corner of the modal */
|
||||
.uppy-Dashboard-close {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
background-color: white;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text-align: center;
|
||||
top: -33px;
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
|
||||
& span {
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
& span::after {
|
||||
content: ' ';
|
||||
-webkit-mask-image: url(${CloseIcon});
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-size: 20px;
|
||||
background: #858282;
|
||||
position: absolute;
|
||||
top: 32%;
|
||||
left: 32%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
|
||||
/*********************************************************/
|
||||
/* Sets the border radius of the upload modal */
|
||||
.uppy-Dashboard-inner {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
border-radius: ${({ borderRadius }) => borderRadius} !important;
|
||||
}
|
||||
}
|
||||
|
||||
.uppy-Dashboard-innerWrap {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
border-radius: ${({ borderRadius }) => borderRadius} !important;
|
||||
}
|
||||
}
|
||||
|
||||
.uppy-Dashboard-AddFiles {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
border-radius: ${({ borderRadius }) => borderRadius} !important;
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
/*********************************************************/
|
||||
/* Sets the error message style according to reskinning*/
|
||||
.uppy-Informer {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
bottom: 82px;
|
||||
& p[role="alert"] {
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
background-color: transparent;
|
||||
color: #D91921;
|
||||
border: 1px solid #D91921;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
/*********************************************************/
|
||||
/* Style the + add more files button on top right corner of the upload modal */
|
||||
.uppy-DashboardContent-addMore {
|
||||
${INCREASE_SPECIFICITY_SELECTOR} {
|
||||
color: var(--wds-accent-color);
|
||||
font-weight: 400;
|
||||
&:hover {
|
||||
background-color: ${Colors.ATHENS_GRAY};
|
||||
color: var(--wds-accent-color);
|
||||
}
|
||||
|
||||
& svg {
|
||||
fill: var(--wds-accent-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
}
|
||||
`;
|
||||
|
|
@ -1,14 +1,7 @@
|
|||
import Uppy from "@uppy/core";
|
||||
import Dashboard from "@uppy/dashboard";
|
||||
import GoogleDrive from "@uppy/google-drive";
|
||||
import OneDrive from "@uppy/onedrive";
|
||||
import Url from "@uppy/url";
|
||||
import type Dashboard from "@uppy/dashboard";
|
||||
import type { Uppy } from "@uppy/core";
|
||||
import type { UppyFile } from "@uppy/utils";
|
||||
import Webcam from "@uppy/webcam";
|
||||
import CloseIcon from "assets/icons/ads/cross.svg";
|
||||
import UpIcon from "assets/icons/ads/up-arrow.svg";
|
||||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import { Colors } from "constants/Colors";
|
||||
import type { WidgetType } from "constants/WidgetConstants";
|
||||
import { FILE_SIZE_LIMIT_FOR_BLOBS } from "constants/WidgetConstants";
|
||||
import { ValidationTypes } from "constants/WidgetValidation";
|
||||
|
|
@ -20,9 +13,9 @@ import log from "loglevel";
|
|||
|
||||
import React from "react";
|
||||
import shallowequal from "shallowequal";
|
||||
import { createGlobalStyle } from "styled-components";
|
||||
import { createBlobUrl, isBlobUrl } from "utils/AppsmithUtils";
|
||||
import type { DerivedPropertiesMap } from "utils/WidgetFactory";
|
||||
import { importUppy, isUppyLoaded } from "utils/importUppy";
|
||||
import type { WidgetProps, WidgetState } from "widgets/BaseWidget";
|
||||
import BaseWidget from "widgets/BaseWidget";
|
||||
import FilePickerComponent from "../component";
|
||||
|
|
@ -30,178 +23,12 @@ import FileDataTypes from "../constants";
|
|||
import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils";
|
||||
import type { AutocompletionDefinitions } from "widgets/constants";
|
||||
import parseFileData from "./FileParser";
|
||||
import { FilePickerGlobalStyles } from "./index.styled";
|
||||
|
||||
const CSV_ARRAY_LABEL = "Array of Objects (CSV, XLS(X), JSON, TSV)";
|
||||
|
||||
const ARRAY_CSV_HELPER_TEXT = `All non CSV, XLS(X), JSON or TSV filetypes will have an empty value. \n Large files used in widgets directly might slow down the app.`;
|
||||
|
||||
const FilePickerGlobalStyles = createGlobalStyle<{
|
||||
borderRadius?: string;
|
||||
}>`
|
||||
|
||||
/* Sets the font-family to theming font-family of the upload modal */
|
||||
.uppy-Root {
|
||||
font-family: var(--wds-font-family);
|
||||
}
|
||||
|
||||
/*********************************************************/
|
||||
/* Set the new dropHint upload icon */
|
||||
.uppy-Dashboard-dropFilesHereHint {
|
||||
background-image: none;
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
}
|
||||
|
||||
.uppy-Dashboard-dropFilesHereHint::before {
|
||||
border: 2.5px solid var(--wds-accent-color);
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
display: inline-block;
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 43%;
|
||||
}
|
||||
|
||||
.uppy-Dashboard-dropFilesHereHint::after {
|
||||
display: inline-block;
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 46%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
|
||||
-webkit-mask-image: url(${UpIcon});
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-size: 30px;
|
||||
background: var(--wds-accent-color);
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
/*********************************************************/
|
||||
/* Set the styles for the upload button */
|
||||
.uppy-StatusBar-actionBtn--upload {
|
||||
background-color: var(--wds-accent-color) !important;
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
}
|
||||
|
||||
.uppy-Dashboard-Item-action--remove {
|
||||
|
||||
/* Sets the border radius of the button when it is focused */
|
||||
&:focus {
|
||||
border-radius: ${({ borderRadius }) =>
|
||||
borderRadius === "0.375rem" ? "0.25rem" : borderRadius} !important;
|
||||
}
|
||||
|
||||
.uppy-c-icon {
|
||||
& path:first-child {
|
||||
/* Sets the black background of remove file button hidden */
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
& path:last-child {
|
||||
/* Sets the cross mark color of remove file button */
|
||||
fill: #858282;
|
||||
}
|
||||
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.06), 0px 1px 3px rgba(0, 0, 0, 0.1);
|
||||
|
||||
& {
|
||||
/* Sets the black background of remove file button hidden*/
|
||||
border-radius: ${({ borderRadius }) =>
|
||||
borderRadius === "0.375rem" ? "0.25rem" : borderRadius};
|
||||
}
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
/*********************************************************/
|
||||
/* Sets the back cancel button color to match theming primary color */
|
||||
.uppy-DashboardContent-back {
|
||||
color: var(--wds-accent-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--wds-accent-color);
|
||||
background-color: ${Colors.ATHENS_GRAY};
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
/*********************************************************/
|
||||
/* Sets the style according to reskinning for x button at the top right corner of the modal */
|
||||
.uppy-Dashboard-close {
|
||||
background-color: white;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text-align: center;
|
||||
top: -33px;
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
|
||||
& span {
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
& span::after {
|
||||
content: ' ';
|
||||
-webkit-mask-image: url(${CloseIcon});
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-size: 20px;
|
||||
background: #858282;
|
||||
position: absolute;
|
||||
top: 32%;
|
||||
left: 32%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
|
||||
/*********************************************************/
|
||||
/* Sets the border radius of the upload modal */
|
||||
.uppy-Dashboard-inner, .uppy-Dashboard-innerWrap {
|
||||
border-radius: ${({ borderRadius }) => borderRadius} !important;
|
||||
}
|
||||
|
||||
.uppy-Dashboard-AddFiles {
|
||||
border-radius: ${({ borderRadius }) => borderRadius} !important;
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
/*********************************************************/
|
||||
/* Sets the error message style according to reskinning*/
|
||||
.uppy-Informer {
|
||||
bottom: 82px;
|
||||
& p[role="alert"] {
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
background-color: transparent;
|
||||
color: #D91921;
|
||||
border: 1px solid #D91921;
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
/*********************************************************/
|
||||
/* Style the + add more files button on top right corner of the upload modal */
|
||||
.uppy-DashboardContent-addMore {
|
||||
color: var(--wds-accent-color);
|
||||
font-weight: 400;
|
||||
&:hover {
|
||||
background-color: ${Colors.ATHENS_GRAY};
|
||||
color: var(--wds-accent-color);
|
||||
}
|
||||
|
||||
& svg {
|
||||
fill: var(--wds-accent-color) !important;
|
||||
}
|
||||
}
|
||||
/*********************************************************/
|
||||
|
||||
}
|
||||
`;
|
||||
class FilePickerWidget extends BaseWidget<
|
||||
FilePickerWidgetProps,
|
||||
FilePickerWidgetState
|
||||
|
|
@ -212,8 +39,9 @@ class FilePickerWidget extends BaseWidget<
|
|||
super(props);
|
||||
this.isWidgetUnmounting = false;
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
uppy: this.initializeUppy(),
|
||||
areFilesLoading: false,
|
||||
isWaitingForUppyToLoad: false,
|
||||
isUppyModalOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -532,10 +360,12 @@ class FilePickerWidget extends BaseWidget<
|
|||
}
|
||||
|
||||
/**
|
||||
* if uppy is not initialized before, initialize it
|
||||
* else setState of uppy instance
|
||||
* Import and initialize the Uppy instance. We use memoize() to ensure that
|
||||
* once we initialize the instance, we keep returning the initialized one.
|
||||
*/
|
||||
initializeUppy = () => {
|
||||
loadAndInitUppyOnce = _.memoize(async () => {
|
||||
const { Uppy } = await importUppy();
|
||||
|
||||
const uppyState = {
|
||||
id: this.props.widgetId,
|
||||
autoProceed: false,
|
||||
|
|
@ -556,13 +386,18 @@ class FilePickerWidget extends BaseWidget<
|
|||
},
|
||||
};
|
||||
|
||||
return Uppy(uppyState);
|
||||
};
|
||||
const uppy = Uppy(uppyState);
|
||||
|
||||
await this.initializeUppyEventListeners(uppy);
|
||||
await this.initializeSelectedFiles(uppy);
|
||||
|
||||
return uppy;
|
||||
});
|
||||
|
||||
/**
|
||||
* set states on the uppy instance with new values
|
||||
*/
|
||||
reinitializeUppy = (props: FilePickerWidgetProps) => {
|
||||
reinitializeUppy = async (props: FilePickerWidgetProps) => {
|
||||
const uppyState = {
|
||||
id: props.widgetId,
|
||||
autoProceed: false,
|
||||
|
|
@ -581,14 +416,18 @@ class FilePickerWidget extends BaseWidget<
|
|||
},
|
||||
};
|
||||
|
||||
this.state.uppy.setOptions(uppyState);
|
||||
const uppy = await this.loadAndInitUppyOnce();
|
||||
uppy.setOptions(uppyState);
|
||||
};
|
||||
|
||||
/**
|
||||
* add all uppy events listeners needed
|
||||
*/
|
||||
initializeUppyEventListeners = () => {
|
||||
this.state.uppy
|
||||
initializeUppyEventListeners = async (uppy: Uppy) => {
|
||||
const { Dashboard, GoogleDrive, OneDrive, Url, Webcam } =
|
||||
await importUppy();
|
||||
|
||||
uppy
|
||||
.use(Dashboard, {
|
||||
target: "body",
|
||||
metaFields: [],
|
||||
|
|
@ -609,11 +448,15 @@ class FilePickerWidget extends BaseWidget<
|
|||
disablePageScrollWhenModalOpen: true,
|
||||
proudlyDisplayPoweredByUppy: false,
|
||||
onRequestCloseModal: () => {
|
||||
const plugin = this.state.uppy.getPlugin("Dashboard");
|
||||
const plugin = uppy.getPlugin("Dashboard") as Dashboard;
|
||||
|
||||
if (plugin) {
|
||||
plugin.closeModal();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isUppyModalOpen: false,
|
||||
});
|
||||
},
|
||||
locale: {
|
||||
strings: {
|
||||
|
|
@ -628,7 +471,7 @@ class FilePickerWidget extends BaseWidget<
|
|||
});
|
||||
|
||||
if (location.protocol === "https:") {
|
||||
this.state.uppy.use(Webcam, {
|
||||
uppy.use(Webcam, {
|
||||
onBeforeSnapshot: () => Promise.resolve(),
|
||||
countdown: false,
|
||||
mirror: true,
|
||||
|
|
@ -637,7 +480,7 @@ class FilePickerWidget extends BaseWidget<
|
|||
});
|
||||
}
|
||||
|
||||
this.state.uppy.on("file-removed", (file: UppyFile, reason: any) => {
|
||||
uppy.on("file-removed", (file: UppyFile, reason: any) => {
|
||||
/**
|
||||
* The below line will not update the selectedFiles meta prop when cancel-all event is triggered.
|
||||
* cancel-all event occurs when close or reset function of uppy is executed.
|
||||
|
|
@ -651,7 +494,7 @@ class FilePickerWidget extends BaseWidget<
|
|||
* Once the file is removed we update the selectedFiles
|
||||
* with the current files present in the uppy's internal state
|
||||
*/
|
||||
const updatedFiles = this.state.uppy
|
||||
const updatedFiles = uppy
|
||||
.getFiles()
|
||||
.map((currentFile: UppyFile, index: number) => ({
|
||||
type: currentFile.type,
|
||||
|
|
@ -674,7 +517,7 @@ class FilePickerWidget extends BaseWidget<
|
|||
}
|
||||
});
|
||||
|
||||
this.state.uppy.on("files-added", (files: UppyFile[]) => {
|
||||
uppy.on("files-added", (files: UppyFile[]) => {
|
||||
// Deep cloning the selectedFiles
|
||||
const selectedFiles = this.props.selectedFiles
|
||||
? klona(this.props.selectedFiles)
|
||||
|
|
@ -730,7 +573,7 @@ class FilePickerWidget extends BaseWidget<
|
|||
});
|
||||
});
|
||||
|
||||
this.state.uppy.on("upload", () => {
|
||||
uppy.on("upload", () => {
|
||||
this.onFilesSelected();
|
||||
});
|
||||
};
|
||||
|
|
@ -751,29 +594,31 @@ class FilePickerWidget extends BaseWidget<
|
|||
},
|
||||
});
|
||||
|
||||
this.setState({ isLoading: true });
|
||||
this.setState({ areFilesLoading: true });
|
||||
}
|
||||
};
|
||||
|
||||
handleActionComplete = () => {
|
||||
this.setState({ isLoading: false });
|
||||
this.setState({ areFilesLoading: false });
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps: FilePickerWidgetProps) {
|
||||
async componentDidUpdate(prevProps: FilePickerWidgetProps) {
|
||||
super.componentDidUpdate(prevProps);
|
||||
|
||||
const { selectedFiles: previousSelectedFiles = [] } = prevProps;
|
||||
const { selectedFiles = [] } = this.props;
|
||||
if (previousSelectedFiles.length && selectedFiles.length === 0) {
|
||||
this.state.uppy.reset();
|
||||
(await this.loadAndInitUppyOnce()).reset();
|
||||
} else if (
|
||||
!shallowequal(prevProps.allowedFileTypes, this.props.allowedFileTypes) ||
|
||||
prevProps.maxNumFiles !== this.props.maxNumFiles ||
|
||||
prevProps.maxFileSize !== this.props.maxFileSize
|
||||
) {
|
||||
this.reinitializeUppy(this.props);
|
||||
await this.reinitializeUppy(this.props);
|
||||
}
|
||||
this.clearFilesFromMemory(prevProps.selectedFiles);
|
||||
}
|
||||
|
||||
// Reclaim the memory used by blobs.
|
||||
clearFilesFromMemory(previousFiles: any[] = []) {
|
||||
const { selectedFiles: newFiles = [] } = this.props;
|
||||
|
|
@ -788,13 +633,13 @@ class FilePickerWidget extends BaseWidget<
|
|||
});
|
||||
}
|
||||
|
||||
initializeSelectedFiles() {
|
||||
async initializeSelectedFiles(uppy: Uppy) {
|
||||
/**
|
||||
* Since on unMount the uppy instance closes and it's internal state is lost along with the files present in it.
|
||||
* Below we add the files again to the uppy instance so that the files are retained.
|
||||
*/
|
||||
this.props.selectedFiles?.forEach((fileItem: any) => {
|
||||
this.state.uppy.addFile({
|
||||
uppy.addFile({
|
||||
name: fileItem.name,
|
||||
type: fileItem.type,
|
||||
data: new Blob([fileItem.data]),
|
||||
|
|
@ -806,12 +651,11 @@ class FilePickerWidget extends BaseWidget<
|
|||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
async componentDidMount() {
|
||||
super.componentDidMount();
|
||||
|
||||
try {
|
||||
this.initializeUppyEventListeners();
|
||||
this.initializeSelectedFiles();
|
||||
await this.loadAndInitUppyOnce();
|
||||
} catch (e) {
|
||||
log.debug("Error in initializing uppy");
|
||||
}
|
||||
|
|
@ -819,7 +663,9 @@ class FilePickerWidget extends BaseWidget<
|
|||
|
||||
componentWillUnmount() {
|
||||
this.isWidgetUnmounting = true;
|
||||
this.state.uppy.close();
|
||||
this.loadAndInitUppyOnce().then((uppy) => {
|
||||
uppy.close();
|
||||
});
|
||||
}
|
||||
|
||||
static getSetterConfig(): SetterConfig {
|
||||
|
|
@ -846,17 +692,39 @@ class FilePickerWidget extends BaseWidget<
|
|||
buttonColor={this.props.buttonColor}
|
||||
files={this.props.selectedFiles || []}
|
||||
isDisabled={this.props.isDisabled}
|
||||
isLoading={this.props.isLoading || this.state.isLoading}
|
||||
isLoading={
|
||||
this.props.isLoading ||
|
||||
this.state.areFilesLoading ||
|
||||
this.state.isWaitingForUppyToLoad
|
||||
}
|
||||
key={this.props.widgetId}
|
||||
label={this.props.label}
|
||||
maxWidth={this.props.maxWidth}
|
||||
minHeight={this.props.minHeight}
|
||||
minWidth={this.props.minWidth}
|
||||
openModal={async () => {
|
||||
// If Uppy is still loading, show a spinner to indicate that handling the click
|
||||
// will take some time.
|
||||
//
|
||||
// Copying the `isUppyLoaded` value because `isUppyLoaded` *will* always be true
|
||||
// by the time `await this.initUppyInstanceOnce()` resolves.
|
||||
const isUppyLoadedByThisPoint = isUppyLoaded;
|
||||
|
||||
if (!isUppyLoadedByThisPoint)
|
||||
this.setState({ isWaitingForUppyToLoad: true });
|
||||
const uppy = await this.loadAndInitUppyOnce();
|
||||
if (!isUppyLoadedByThisPoint)
|
||||
this.setState({ isWaitingForUppyToLoad: false });
|
||||
|
||||
const dashboardPlugin = uppy.getPlugin("Dashboard") as Dashboard;
|
||||
dashboardPlugin.openModal();
|
||||
this.setState({ isUppyModalOpen: true });
|
||||
}}
|
||||
shouldFitContent={this.isAutoLayoutMode}
|
||||
uppy={this.state.uppy}
|
||||
widgetId={this.props.widgetId}
|
||||
/>
|
||||
{this.state.uppy && this.state.uppy.getID() === this.props.widgetId && (
|
||||
|
||||
{this.state.isUppyModalOpen && (
|
||||
<FilePickerGlobalStyles borderRadius={this.props.borderRadius} />
|
||||
)}
|
||||
</>
|
||||
|
|
@ -869,8 +737,9 @@ class FilePickerWidget extends BaseWidget<
|
|||
}
|
||||
|
||||
interface FilePickerWidgetState extends WidgetState {
|
||||
isLoading: boolean;
|
||||
uppy: any;
|
||||
areFilesLoading: boolean;
|
||||
isWaitingForUppyToLoad: boolean;
|
||||
isUppyModalOpen: boolean;
|
||||
}
|
||||
|
||||
interface FilePickerWidgetProps extends WidgetProps {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class FilePickerComponent extends React.Component<
|
|||
|
||||
openModal = () => {
|
||||
if (!this.props.isDisabled) {
|
||||
this.props.uppy.getPlugin("Dashboard").openModal();
|
||||
this.props.openModal();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ class FilePickerComponent extends React.Component<
|
|||
}
|
||||
|
||||
public closeModal() {
|
||||
this.props.uppy.getPlugin("Dashboard").closeModal();
|
||||
this.props.closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +50,8 @@ export interface FilePickerComponentState {
|
|||
|
||||
export interface FilePickerComponentProps extends ComponentProps {
|
||||
label: string;
|
||||
uppy: any;
|
||||
openModal: () => void;
|
||||
closeModal: () => void;
|
||||
isLoading: boolean;
|
||||
files?: any[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
import React from "react";
|
||||
import type { Uppy } from "@uppy/core";
|
||||
import type { WidgetProps, WidgetState } from "../../BaseWidget";
|
||||
import BaseWidget from "../../BaseWidget";
|
||||
import type { WidgetType } from "constants/WidgetConstants";
|
||||
import FilePickerComponent from "../component";
|
||||
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";
|
||||
import { ValidationTypes } from "constants/WidgetValidation";
|
||||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import type { DerivedPropertiesMap } from "utils/WidgetFactory";
|
||||
import Dashboard from "@uppy/dashboard";
|
||||
import type Dashboard from "@uppy/dashboard";
|
||||
import shallowequal from "shallowequal";
|
||||
import _ from "lodash";
|
||||
import FileDataTypes from "./FileDataTypes";
|
||||
|
|
@ -19,6 +15,7 @@ import log from "loglevel";
|
|||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils";
|
||||
import type { AutocompletionDefinitions } from "widgets/constants";
|
||||
import { importUppy, isUppyLoaded } from "utils/importUppy";
|
||||
import type { SetterConfig } from "entities/AppTheming";
|
||||
|
||||
class FilePickerWidget extends BaseWidget<
|
||||
|
|
@ -28,8 +25,8 @@ class FilePickerWidget extends BaseWidget<
|
|||
constructor(props: FilePickerWidgetProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
uppy: this.initializeUppy(),
|
||||
areFilesLoading: false,
|
||||
isWaitingForUppyToLoad: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -253,10 +250,12 @@ class FilePickerWidget extends BaseWidget<
|
|||
}
|
||||
|
||||
/**
|
||||
* if uppy is not initialized before, initialize it
|
||||
* else setState of uppy instance
|
||||
* Import and initialize the Uppy instance. We use memoize() to ensure that
|
||||
* once we initialize the instance, we keep returning it.
|
||||
*/
|
||||
initializeUppy = () => {
|
||||
loadAndInitUppyOnce = _.memoize(async () => {
|
||||
const { Uppy } = await importUppy();
|
||||
|
||||
const uppyState = {
|
||||
id: this.props.widgetId,
|
||||
autoProceed: false,
|
||||
|
|
@ -277,13 +276,17 @@ class FilePickerWidget extends BaseWidget<
|
|||
},
|
||||
};
|
||||
|
||||
return Uppy(uppyState);
|
||||
};
|
||||
const uppy = Uppy(uppyState);
|
||||
|
||||
await this.initializeUppyEventListeners(uppy);
|
||||
|
||||
return uppy;
|
||||
});
|
||||
|
||||
/**
|
||||
* set states on the uppy instance with new values
|
||||
*/
|
||||
reinitializeUppy = (props: FilePickerWidgetProps) => {
|
||||
reinitializeUppy = async (props: FilePickerWidgetProps) => {
|
||||
const uppyState = {
|
||||
id: props.widgetId,
|
||||
autoProceed: false,
|
||||
|
|
@ -302,14 +305,18 @@ class FilePickerWidget extends BaseWidget<
|
|||
},
|
||||
};
|
||||
|
||||
this.state.uppy.setOptions(uppyState);
|
||||
const uppy = await this.loadAndInitUppyOnce();
|
||||
uppy.setOptions(uppyState);
|
||||
};
|
||||
|
||||
/**
|
||||
* add all uppy events listeners needed
|
||||
*/
|
||||
initializeUppyEventListeners = () => {
|
||||
this.state.uppy
|
||||
initializeUppyEventListeners = async (uppy: Uppy) => {
|
||||
const { Dashboard, GoogleDrive, OneDrive, Url, Webcam } =
|
||||
await importUppy();
|
||||
|
||||
uppy
|
||||
.use(Dashboard, {
|
||||
target: "body",
|
||||
metaFields: [],
|
||||
|
|
@ -330,7 +337,7 @@ class FilePickerWidget extends BaseWidget<
|
|||
disablePageScrollWhenModalOpen: true,
|
||||
proudlyDisplayPoweredByUppy: false,
|
||||
onRequestCloseModal: () => {
|
||||
const plugin = this.state.uppy.getPlugin("Dashboard");
|
||||
const plugin = uppy.getPlugin("Dashboard") as Dashboard;
|
||||
|
||||
if (plugin) {
|
||||
plugin.closeModal();
|
||||
|
|
@ -349,7 +356,7 @@ class FilePickerWidget extends BaseWidget<
|
|||
});
|
||||
|
||||
if (location.protocol === "https:") {
|
||||
this.state.uppy.use(Webcam, {
|
||||
uppy.use(Webcam, {
|
||||
onBeforeSnapshot: () => Promise.resolve(),
|
||||
countdown: false,
|
||||
mirror: true,
|
||||
|
|
@ -358,7 +365,7 @@ class FilePickerWidget extends BaseWidget<
|
|||
});
|
||||
}
|
||||
|
||||
this.state.uppy.on("file-removed", (file: any) => {
|
||||
uppy.on("file-removed", (file: any) => {
|
||||
const updatedFiles = this.props.selectedFiles
|
||||
? this.props.selectedFiles.filter((dslFile) => {
|
||||
return file.id !== dslFile.id;
|
||||
|
|
@ -367,7 +374,7 @@ class FilePickerWidget extends BaseWidget<
|
|||
this.props.updateWidgetMetaProperty("selectedFiles", updatedFiles);
|
||||
});
|
||||
|
||||
this.state.uppy.on("files-added", (files: any[]) => {
|
||||
uppy.on("files-added", (files: any[]) => {
|
||||
const dslFiles = this.props.selectedFiles
|
||||
? [...this.props.selectedFiles]
|
||||
: [];
|
||||
|
|
@ -415,7 +422,7 @@ class FilePickerWidget extends BaseWidget<
|
|||
});
|
||||
});
|
||||
|
||||
this.state.uppy.on("upload", () => {
|
||||
uppy.on("upload", () => {
|
||||
this.onFilesSelected();
|
||||
});
|
||||
};
|
||||
|
|
@ -436,40 +443,42 @@ class FilePickerWidget extends BaseWidget<
|
|||
},
|
||||
});
|
||||
|
||||
this.setState({ isLoading: true });
|
||||
this.setState({ areFilesLoading: true });
|
||||
}
|
||||
};
|
||||
|
||||
handleActionComplete = () => {
|
||||
this.setState({ isLoading: false });
|
||||
this.setState({ areFilesLoading: false });
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps: FilePickerWidgetProps) {
|
||||
async componentDidUpdate(prevProps: FilePickerWidgetProps) {
|
||||
if (
|
||||
prevProps.selectedFiles &&
|
||||
prevProps.selectedFiles.length > 0 &&
|
||||
this.props.selectedFiles === undefined
|
||||
) {
|
||||
this.state.uppy.reset();
|
||||
(await this.loadAndInitUppyOnce()).reset();
|
||||
} else if (
|
||||
!shallowequal(prevProps.allowedFileTypes, this.props.allowedFileTypes) ||
|
||||
prevProps.maxNumFiles !== this.props.maxNumFiles ||
|
||||
prevProps.maxFileSize !== this.props.maxFileSize
|
||||
) {
|
||||
this.reinitializeUppy(this.props);
|
||||
await this.reinitializeUppy(this.props);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
async componentDidMount() {
|
||||
try {
|
||||
this.initializeUppyEventListeners();
|
||||
await this.loadAndInitUppyOnce();
|
||||
} catch (e) {
|
||||
log.debug("Error in initializing uppy");
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.state.uppy.close();
|
||||
this.loadAndInitUppyOnce().then((uppy) => {
|
||||
uppy.close();
|
||||
});
|
||||
}
|
||||
|
||||
static getSetterConfig(): SetterConfig {
|
||||
|
|
@ -490,12 +499,37 @@ class FilePickerWidget extends BaseWidget<
|
|||
getPageView() {
|
||||
return (
|
||||
<FilePickerComponent
|
||||
closeModal={async () => {
|
||||
const uppy = await this.loadAndInitUppyOnce();
|
||||
|
||||
const dashboardPlugin = uppy.getPlugin("Dashboard") as Dashboard;
|
||||
dashboardPlugin.closeModal();
|
||||
}}
|
||||
files={this.props.selectedFiles || []}
|
||||
isDisabled={this.props.isDisabled}
|
||||
isLoading={this.props.isLoading || this.state.isLoading}
|
||||
isLoading={
|
||||
this.props.isLoading ||
|
||||
this.state.areFilesLoading ||
|
||||
this.state.isWaitingForUppyToLoad
|
||||
}
|
||||
key={this.props.widgetId}
|
||||
label={this.props.label}
|
||||
uppy={this.state.uppy}
|
||||
openModal={async () => {
|
||||
// If Uppy is still loading, show a spinner to indicate that handling the click
|
||||
// will take some time.
|
||||
//
|
||||
// Copying the `isUppyLoaded` value because `isUppyLoaded` *will* always be true
|
||||
// by the time `await this.initUppyInstanceOnce()` resolves.
|
||||
const isUppyLoadedByThisPoint = isUppyLoaded;
|
||||
if (!isUppyLoadedByThisPoint)
|
||||
this.setState({ isWaitingForUppyToLoad: true });
|
||||
const uppy = await this.loadAndInitUppyOnce();
|
||||
if (!isUppyLoadedByThisPoint)
|
||||
this.setState({ isWaitingForUppyToLoad: false });
|
||||
|
||||
const dashboardPlugin = uppy.getPlugin("Dashboard") as Dashboard;
|
||||
dashboardPlugin.openModal();
|
||||
}}
|
||||
widgetId={this.props.widgetId}
|
||||
/>
|
||||
);
|
||||
|
|
@ -507,8 +541,8 @@ class FilePickerWidget extends BaseWidget<
|
|||
}
|
||||
|
||||
export interface FilePickerWidgetState extends WidgetState {
|
||||
isLoading: boolean;
|
||||
uppy: any;
|
||||
areFilesLoading: boolean;
|
||||
isWaitingForUppyToLoad: boolean;
|
||||
}
|
||||
|
||||
export interface FilePickerWidgetProps extends WidgetProps {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import * as Sentry from "@sentry/react";
|
||||
import _ from "workers/common/JSLibrary/lodash-wrapper";
|
||||
import moment from "moment";
|
||||
import React, { useCallback, useContext, useMemo, useState } from "react";
|
||||
|
||||
import type { BaseInputComponentProps } from "./BaseInputField";
|
||||
|
|
@ -135,7 +133,7 @@ function CurrencyInputField({
|
|||
Sentry.captureException(e);
|
||||
}
|
||||
|
||||
const value = derived.value({ text }, moment, _);
|
||||
const value = derived.value({ text });
|
||||
|
||||
return {
|
||||
text,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,59 @@
|
|||
// 🚧 NOTE: this file exists only for the worker thread, as the worker thread needs to pass
|
||||
// the full Lodash library around. *Do not* import it in the main thread code, as that will
|
||||
// result in bundling the full Lodash. If you’re trying to pass a Lodash reference into some
|
||||
// function in the main thread, consider if you can instead:
|
||||
//
|
||||
// - import and call Lodash directly:
|
||||
//
|
||||
// Before:
|
||||
// // a.js
|
||||
// export function mapArray(_) { return _.map(someArray, someFunction); }
|
||||
// // b.js
|
||||
// import { mapArray } from './a';
|
||||
// import _ from 'lodash';
|
||||
// mapArray(_);
|
||||
//
|
||||
// After:
|
||||
// // a.js
|
||||
// import _ from 'lodash';
|
||||
// export function mapArray() { return _.map(someArray, someFunction); }
|
||||
// // b.js
|
||||
// import { mapArray } from './a';
|
||||
// mapArray();
|
||||
//
|
||||
// - pass only the function you need about:
|
||||
//
|
||||
// Before:
|
||||
// // a.js
|
||||
// export function mapArray(_) { return _.map(someArray, someFunction); }
|
||||
// // b.js
|
||||
// import { mapArray } from './a';
|
||||
// import _ from 'lodash';
|
||||
// mapArray(_);
|
||||
//
|
||||
// After:
|
||||
// // a.js
|
||||
// export function mapArray(_) { return _.map(someArray, someFunction); }
|
||||
// // b.js
|
||||
// import { mapArray } from './a';
|
||||
// import _ from 'lodash';
|
||||
// mapArray({ map: _.map });
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
// Jest mocks the `window` object when running worker tests
|
||||
process.env.NODE_ENV !== "test"
|
||||
) {
|
||||
throw new Error("lodash-wrapper.js must only be used in a worker thread");
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// We use babel-plugin-lodash to only import the lodash functions we use.
|
||||
// Unfortunately, the plugin doesn’t work with the following pattern:
|
||||
// import _ from 'lodash';
|
||||
// const something = _;
|
||||
// When it encounters code like above, it will replace _ with `undefined`,
|
||||
// which will break the app.
|
||||
// which will break the app (https://github.com/lodash/babel-plugin-lodash/issues/235).
|
||||
//
|
||||
// Given that we *need* to use the full lodash in ./resetJSLibraries.js,
|
||||
// we use this workaround where we’re importing Lodash using CommonJS require().
|
||||
|
|
|
|||
|
|
@ -9660,7 +9660,7 @@ __metadata:
|
|||
dayjs: ^1.10.6
|
||||
deep-diff: ^1.0.2
|
||||
design-system: "npm:@appsmithorg/design-system@2.1.15"
|
||||
design-system-old: "npm:@appsmithorg/design-system-old@1.1.10"
|
||||
design-system-old: "npm:@appsmithorg/design-system-old@1.1.11"
|
||||
diff: ^5.0.0
|
||||
dotenv: ^8.1.0
|
||||
downloadjs: ^1.4.7
|
||||
|
|
@ -13674,9 +13674,9 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"design-system-old@npm:@appsmithorg/design-system-old@1.1.10":
|
||||
version: 1.1.10
|
||||
resolution: "@appsmithorg/design-system-old@npm:1.1.10"
|
||||
"design-system-old@npm:@appsmithorg/design-system-old@1.1.11":
|
||||
version: 1.1.11
|
||||
resolution: "@appsmithorg/design-system-old@npm:1.1.11"
|
||||
dependencies:
|
||||
emoji-mart: 3.0.1
|
||||
peerDependencies:
|
||||
|
|
@ -13696,7 +13696,7 @@ __metadata:
|
|||
remixicon-react: ^1.0.0
|
||||
styled-components: 5.3.6
|
||||
tinycolor2: ^1.4.2
|
||||
checksum: 9eb74a7d59f3db7f6b19f31fecf0381247a094a8655cf4a5e64b7318e2659e28c643382f429d2aed601acf1209c062de7fb6071fa03ea0e5eb3830f840384fa7
|
||||
checksum: 968fc1be2ded862c2cac3bc8ae8eab6642d16ffda7b586aeb87ed69d1bad08ee5e3d1c05098adfb9e80f9c1a6fa6d4bc05d0cd464b700aa39005a5bb6c63b7ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user