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:
Ivan Akulov 2023-07-16 20:49:41 +02:00 committed by GitHub
parent 25177f4837
commit 8a1870daa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 523 additions and 331 deletions

View File

@ -35,6 +35,12 @@ const eslintConfig = {
// Allow type imports as they dont 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 doesnt allow to restrict imports of
// `editorComponents/CodeEditor` but not `editorComponents/CodeEditor/*`: https://stackoverflow.com/q/64995811/1192426
// So were using `no-restricted-syntax` instead.
"no-restricted-syntax": [
// Annoyingly, the `no-restricted-imports` rule doesnt allow to restrict imports of
// `editorComponents/CodeEditor` but not `editorComponents/CodeEditor/*`: https://stackoverflow.com/q/64995811/1192426
// So were using `no-restricted-syntax` instead.
"error",
{
// Match all
@ -61,6 +67,20 @@ const eslintConfig = {
message:
"Please dont 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 theres 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 dont 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 dont 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.",
},
],
},
};

View File

@ -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",

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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,
},

View File

@ -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")

View 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: [],
};

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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);

View File

@ -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() {

View File

@ -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";

View File

@ -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[] }>,

View File

@ -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",
});

View File

@ -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());
};

View File

@ -0,0 +1,36 @@
// Were setting this flag to true when we know for sure the Uppy module was loaded and initialized.
// When its `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,
};
}

View File

@ -77,7 +77,7 @@ export default {
.replace(new RegExp(`[${getLocaleDecimalSeperator()}]`), "."),
);
if (_.isNaN(parsed)) {
if (Number.isNaN(parsed)) {
parsed = undefined;
}

View File

@ -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";

View File

@ -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";

View File

@ -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;

View File

@ -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;
}
}
}
/*********************************************************/
}
`;

View File

@ -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 {

View File

@ -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[];
}

View File

@ -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 {

View File

@ -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,

View File

@ -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 youre 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 doesnt 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 were importing Lodash using CommonJS require().

View File

@ -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