Action refactor

This commit is contained in:
Hetu Nandu 2020-02-18 10:41:52 +00:00
parent b22478e96e
commit fb80c4b576
63 changed files with 1721 additions and 1200 deletions

View File

@ -46,6 +46,7 @@
"husky": "^3.0.5", "husky": "^3.0.5",
"interweave": "^12.1.1", "interweave": "^12.1.1",
"interweave-autolink": "^4.0.1", "interweave-autolink": "^4.0.1",
"json-fn": "^1.1.1",
"lint-staged": "^9.2.5", "lint-staged": "^9.2.5",
"localforage": "^1.7.3", "localforage": "^1.7.3",
"lodash": "^4.17.11", "lodash": "^4.17.11",
@ -74,6 +75,7 @@
"react-select": "^3.0.8", "react-select": "^3.0.8",
"react-spring": "^8.0.27", "react-spring": "^8.0.27",
"react-tabs": "^3.0.0", "react-tabs": "^3.0.0",
"react-toastify": "^5.5.0",
"react-transition-group": "^4.3.0", "react-transition-group": "^4.3.0",
"redux": "^4.0.1", "redux": "^4.0.1",
"redux-form": "^8.2.6", "redux-form": "^8.2.6",

View File

@ -1,4 +1,4 @@
import { RestAction, PaginationField } from "api/ActionAPI"; import { RestAction, PaginationField, ActionResponse } from "api/ActionAPI";
import { import {
ReduxActionTypes, ReduxActionTypes,
ReduxAction, ReduxAction,
@ -130,6 +130,19 @@ export const copyActionError = (payload: {
}; };
}; };
export const executeApiActionRequest = (payload: { id: string }) => ({
type: ReduxActionTypes.EXECUTE_API_ACTION_REQUEST,
payload: payload,
});
export const executeApiActionSuccess = (payload: {
id: string;
response: ActionResponse;
}) => ({
type: ReduxActionTypes.EXECUTE_API_ACTION_SUCCESS,
payload: payload,
});
export default { export default {
createAction: createActionRequest, createAction: createActionRequest,
fetchActions, fetchActions,

View File

@ -24,4 +24,5 @@ export interface UpdateWidgetPropertyPayload {
propertyValue: any; propertyValue: any;
renderMode: RenderMode; renderMode: RenderMode;
dynamicBindings?: Record<string, boolean>; dynamicBindings?: Record<string, boolean>;
dynamicTriggers?: Record<string, true>;
} }

View File

@ -3,27 +3,18 @@ import {
ReduxAction, ReduxAction,
ReduxActionErrorTypes, ReduxActionErrorTypes,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { PaginationField } from "api/ActionAPI";
import { import {
ActionPayload, ExecuteActionPayload,
ExecuteErrorPayload, ExecuteErrorPayload,
PageAction, PageAction,
} from "constants/ActionConstants"; } from "constants/ActionConstants";
export const executeAction = ( export const executeAction = (
actionPayloads: ActionPayload[], payload: ExecuteActionPayload,
paginationField?: PaginationField, ): ReduxAction<ExecuteActionPayload> => {
): ReduxAction<{
actions: ActionPayload[];
paginationField: PaginationField;
}> => {
return { return {
type: ReduxActionTypes.EXECUTE_ACTION, type: ReduxActionTypes.EXECUTE_ACTION,
payload: { payload,
actions: actionPayloads,
paginationField: paginationField,
},
}; };
}; };

View File

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.00033 0.666656C7.35215 0.666656 5.74099 1.1554 4.37058 2.07108C3.00017 2.98675 1.93206 4.28824 1.30133 5.81096C0.670603 7.33368 0.505575 9.00923 0.827119 10.6257C1.14866 12.2423 1.94234 13.7271 3.10777 14.8925C4.27321 16.058 5.75807 16.8517 7.37458 17.1732C8.99109 17.4947 10.6666 17.3297 12.1894 16.699C13.7121 16.0683 15.0136 15.0002 15.9292 13.6297C16.8449 12.2593 17.3337 10.6482 17.3337 8.99999C17.3337 7.90564 17.1181 6.82201 16.6993 5.81096C16.2805 4.79991 15.6667 3.88125 14.8929 3.10743C14.1191 2.33361 13.2004 1.71978 12.1894 1.30099C11.1783 0.882205 10.0947 0.666656 9.00033 0.666656ZM11.2587 10.075C11.3368 10.1525 11.3988 10.2446 11.4411 10.3462C11.4834 10.4477 11.5052 10.5566 11.5052 10.6667C11.5052 10.7767 11.4834 10.8856 11.4411 10.9871C11.3988 11.0887 11.3368 11.1809 11.2587 11.2583C11.1812 11.3364 11.089 11.3984 10.9875 11.4407C10.8859 11.483 10.777 11.5048 10.667 11.5048C10.557 11.5048 10.4481 11.483 10.3465 11.4407C10.245 11.3984 10.1528 11.3364 10.0753 11.2583L9.00033 10.175L7.92533 11.2583C7.84786 11.3364 7.75569 11.3984 7.65414 11.4407C7.55259 11.483 7.44367 11.5048 7.33366 11.5048C7.22365 11.5048 7.11473 11.483 7.01318 11.4407C6.91163 11.3984 6.81946 11.3364 6.742 11.2583C6.66389 11.1809 6.60189 11.0887 6.55959 10.9871C6.51728 10.8856 6.4955 10.7767 6.4955 10.6667C6.4955 10.5566 6.51728 10.4477 6.55959 10.3462C6.60189 10.2446 6.66389 10.1525 6.742 10.075L7.82533 8.99999L6.742 7.92499C6.58508 7.76807 6.49692 7.55524 6.49692 7.33332C6.49692 7.11141 6.58508 6.89858 6.742 6.74166C6.89892 6.58474 7.11174 6.49658 7.33366 6.49658C7.55558 6.49658 7.76841 6.58474 7.92533 6.74166L9.00033 7.82499L10.0753 6.74166C10.2322 6.58474 10.4451 6.49658 10.667 6.49658C10.8889 6.49658 11.1017 6.58474 11.2587 6.74166C11.4156 6.89858 11.5037 7.11141 11.5037 7.33332C11.5037 7.55524 11.4156 7.76807 11.2587 7.92499L10.1753 8.99999L11.2587 10.075Z" fill="#CE4257"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.00131 0.666687C7.35313 0.666687 5.74196 1.15543 4.37155 2.07111C3.00114 2.98679 1.93304 4.28827 1.30231 5.81099C0.671579 7.33371 0.506552 9.00926 0.828095 10.6258C1.14964 12.2423 1.94331 13.7271 3.10875 14.8926C4.27419 16.058 5.75905 16.8517 7.37555 17.1732C8.99206 17.4948 10.6676 17.3297 12.1903 16.699C13.7131 16.0683 15.0145 15.0002 15.9302 13.6298C16.8459 12.2594 17.3346 10.6482 17.3346 9.00002C17.3346 7.90567 17.1191 6.82204 16.7003 5.81099C16.2815 4.79994 15.6677 3.88129 14.8939 3.10746C14.12 2.33364 13.2014 1.71981 12.1903 1.30102C11.1793 0.882235 10.0957 0.666687 9.00131 0.666687ZM9.83464 12.3334C9.83464 12.5544 9.74684 12.7663 9.59056 12.9226C9.43428 13.0789 9.22232 13.1667 9.00131 13.1667C8.78029 13.1667 8.56833 13.0789 8.41205 12.9226C8.25577 12.7663 8.16797 12.5544 8.16797 12.3334V8.16669C8.16797 7.94567 8.25577 7.73371 8.41205 7.57743C8.56833 7.42115 8.78029 7.33335 9.00131 7.33335C9.22232 7.33335 9.43428 7.42115 9.59056 7.57743C9.74684 7.73371 9.83464 7.94567 9.83464 8.16669V12.3334ZM9.00131 6.50002C8.83649 6.50002 8.67537 6.45115 8.53833 6.35958C8.40129 6.26801 8.29448 6.13786 8.23141 5.98559C8.16833 5.83332 8.15183 5.66576 8.18398 5.50411C8.21614 5.34246 8.29551 5.19398 8.41205 5.07743C8.52859 4.96089 8.67708 4.88152 8.83873 4.84937C9.00038 4.81721 9.16794 4.83371 9.32021 4.89679C9.47248 4.95986 9.60263 5.06667 9.6942 5.20371C9.78576 5.34075 9.83464 5.50187 9.83464 5.66669C9.83464 5.8877 9.74684 6.09966 9.59056 6.25594C9.43428 6.41222 9.22232 6.50002 9.00131 6.50002Z" fill="#0384FE"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.00033 0.666687C7.35215 0.666687 5.74099 1.15543 4.37058 2.07111C3.00017 2.98679 1.93206 4.28827 1.30133 5.81099C0.670603 7.33371 0.505575 9.00926 0.827119 10.6258C1.14866 12.2423 1.94234 13.7271 3.10777 14.8926C4.27321 16.058 5.75807 16.8517 7.37458 17.1732C8.99109 17.4948 10.6666 17.3297 12.1894 16.699C13.7121 16.0683 15.0136 15.0002 15.9292 13.6298C16.8449 12.2594 17.3337 10.6482 17.3337 9.00002C17.3337 7.90567 17.1181 6.82204 16.6993 5.81099C16.2805 4.79994 15.6667 3.88129 14.8929 3.10746C14.1191 2.33364 13.2004 1.71981 12.1894 1.30102C11.1783 0.882235 10.0947 0.666687 9.00033 0.666687ZM12.5837 7.00835L8.77533 12.0084C8.6977 12.1092 8.598 12.1909 8.48388 12.2473C8.36977 12.3036 8.24426 12.333 8.117 12.3334C7.99042 12.334 7.86535 12.3059 7.75128 12.251C7.63721 12.1961 7.53714 12.116 7.45866 12.0167L5.42533 9.42502C5.35803 9.33857 5.30841 9.2397 5.27932 9.13408C5.25022 9.02845 5.24222 8.91812 5.25576 8.8094C5.2693 8.70068 5.30412 8.59569 5.35824 8.50042C5.41236 8.40516 5.48471 8.32149 5.57116 8.25419C5.74576 8.11826 5.96721 8.05727 6.18678 8.08462C6.2955 8.09816 6.40049 8.13298 6.49576 8.1871C6.59102 8.24122 6.67469 8.31357 6.742 8.40002L8.10033 10.1334L11.2503 5.96669C11.3171 5.87914 11.4004 5.8056 11.4956 5.75026C11.5908 5.69492 11.6959 5.65887 11.805 5.64417C11.9141 5.62947 12.0251 5.6364 12.1315 5.66457C12.2379 5.69274 12.3378 5.7416 12.4253 5.80835C12.5129 5.87511 12.5864 5.95846 12.6418 6.05363C12.6971 6.14881 12.7331 6.25395 12.7478 6.36306C12.7625 6.47217 12.7556 6.58311 12.7274 6.68954C12.6993 6.79597 12.6504 6.89581 12.5837 6.98335V7.00835Z" fill="#36AB80"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,3 @@
<svg width="20" height="17" viewBox="0 0 20 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.7999 12.5833L12.4083 1.98331C12.1498 1.57899 11.7937 1.24625 11.3728 1.01576C10.9519 0.785273 10.4798 0.664459 9.99992 0.664459C9.52005 0.664459 9.04791 0.785273 8.62702 1.01576C8.20613 1.24625 7.85004 1.57899 7.59159 1.98331L1.19992 12.5833C0.974132 12.9597 0.851411 13.3889 0.844097 13.8277C0.836783 14.2666 0.945133 14.6996 1.15825 15.0833C1.40465 15.5152 1.7613 15.8739 2.19176 16.1228C2.62221 16.3717 3.11102 16.5019 3.60825 16.5H16.3916C16.8855 16.5052 17.3722 16.3801 17.8023 16.1373C18.2325 15.8944 18.591 15.5423 18.8416 15.1166C19.061 14.729 19.1728 14.2897 19.1655 13.8443C19.1581 13.3989 19.0319 12.9636 18.7999 12.5833ZM9.99992 13.1666C9.8351 13.1666 9.67399 13.1178 9.53694 13.0262C9.3999 12.9346 9.29309 12.8045 9.23002 12.6522C9.16695 12.4999 9.15044 12.3324 9.1826 12.1707C9.21475 12.0091 9.29412 11.8606 9.41066 11.7441C9.52721 11.6275 9.67569 11.5481 9.83734 11.516C9.999 11.4838 10.1666 11.5003 10.3188 11.5634C10.4711 11.6265 10.6012 11.7333 10.6928 11.8703C10.7844 12.0074 10.8333 12.1685 10.8333 12.3333C10.8333 12.5543 10.7455 12.7663 10.5892 12.9226C10.4329 13.0788 10.2209 13.1666 9.99992 13.1666ZM10.8333 9.83331C10.8333 10.0543 10.7455 10.2663 10.5892 10.4226C10.4329 10.5788 10.2209 10.6666 9.99992 10.6666C9.77891 10.6666 9.56695 10.5788 9.41066 10.4226C9.25438 10.2663 9.16659 10.0543 9.16659 9.83331V6.49998C9.16659 6.27896 9.25438 6.067 9.41066 5.91072C9.56695 5.75444 9.77891 5.66665 9.99992 5.66665C10.2209 5.66665 10.4329 5.75444 10.5892 5.91072C10.7455 6.067 10.8333 6.27896 10.8333 6.49998V9.83331Z" fill="#F69D2C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -139,7 +139,7 @@ const mapButtonStyleToStyleName = (buttonStyle?: ButtonStyle) => {
const ButtonContainer = (props: ButtonContainerProps & ButtonStyleProps) => { const ButtonContainer = (props: ButtonContainerProps & ButtonStyleProps) => {
return ( return (
<BaseButton <BaseButton
className={props.isLoading ? "bp3-skeleton" : ""} loading={props.isLoading}
icon={props.icon} icon={props.icon}
rightIcon={props.rightIcon} rightIcon={props.rightIcon}
text={props.text} text={props.text}

View File

@ -107,7 +107,7 @@ const DropdownContainer = styled.div`
border-radius: ${props => props.theme.radii[1]}px; border-radius: ${props => props.theme.radii[1]}px;
box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14); box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14);
padding: ${props => props.theme.spaces[3]}px; padding: ${props => props.theme.spaces[3]}px;
background:white; background: white;
} }
&& .${Classes.POPOVER_WRAPPER} { && .${Classes.POPOVER_WRAPPER} {
@ -123,7 +123,6 @@ const DropdownContainer = styled.div`
max-width: 100%; max-width: 100%;
max-height: auto; max-height: auto;
} }
width: 100%; width: 100%;
`; `;

View File

@ -20,7 +20,6 @@ import {
import React, { useRef, MutableRefObject, useEffect, memo } from "react"; import React, { useRef, MutableRefObject, useEffect, memo } from "react";
import styled from "constants/DefaultTheme"; import styled from "constants/DefaultTheme";
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl"; import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
import { ActionPayload } from "constants/ActionConstants";
import { Classes } from "@blueprintjs/core"; import { Classes } from "@blueprintjs/core";
import { TablePagination } from "../appsmith/TablePagination"; import { TablePagination } from "../appsmith/TablePagination";
@ -32,7 +31,7 @@ export interface TableComponentProps {
height: number; height: number;
width: number; width: number;
columnActions?: ColumnAction[]; columnActions?: ColumnAction[];
onCommandClick: (actions: ActionPayload[]) => void; onCommandClick: (dynamicTrigger: string) => void;
disableDrag: (disable: boolean) => void; disableDrag: (disable: boolean) => void;
nextPageClick: Function; nextPageClick: Function;
prevPageClick: Function; prevPageClick: Function;
@ -158,7 +157,7 @@ const TableComponent = memo(
const commands: CommandModel[] = (props.columnActions || []).map(action => { const commands: CommandModel[] = (props.columnActions || []).map(action => {
return { return {
buttonOption: { content: action.label }, buttonOption: { content: action.label },
data: action.actionPayloads, data: action.dynamicTrigger,
}; };
}); });
@ -172,7 +171,7 @@ const TableComponent = memo(
action.label.toLowerCase() === _target.title.toLowerCase(), action.label.toLowerCase() === _target.title.toLowerCase(),
) )
.forEach(action => { .forEach(action => {
props.onCommandClick(action.actionPayloads); props.onCommandClick(action.dynamicTrigger);
}); });
} }
} }

View File

@ -0,0 +1,445 @@
import React, { ChangeEvent } from "react";
import { connect } from "react-redux";
import { AppState } from "reducers";
import { DropdownOption } from "widgets/DropdownWidget";
import _ from "lodash";
import { ControlWrapper } from "components/propertyControls/StyledControls";
import { InputText } from "components/propertyControls/InputTextControl";
import StyledDropdown from "components/editorComponents/StyledDropdown";
const ACTION_TRIGGER_REGEX = /^{{([\s\S]*?)\(([\s\S]*?)\)}}$/g;
const ACTION_ANONYMOUS_FUNC_REGEX = /\(\) => ([\s\S]*?)(\([\s\S]*?\))/g;
const ALERT_STYLE_OPTIONS = [
{ label: "Info", value: "'info'", id: "info" },
{ label: "Success", value: "'success'", id: "success" },
{ label: "Error", value: "'error'", id: "error" },
{ label: "Warning", value: "'warning'", id: "warning" },
];
type ValueChangeHandler = (changeValue: string, currentValue: string) => string;
type ActionCreatorArgumentConfig = {
label: string;
field: string;
valueChangeHandler: ValueChangeHandler;
getSelectedValue: (value: string, returnArguments: boolean) => string;
};
interface ActionCreatorDropdownOption extends DropdownOption {
arguments: ActionCreatorArgumentConfig[];
}
const handleTopLevelFuncUpdate: ValueChangeHandler = (
value: string,
): string => {
return value === "none" ? "" : `{{${value}()}}`;
};
const handleApiArgSelect = (
changeValue: string,
currentValue: string,
label: "onSuccess" | "onError",
): string => {
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
const args = [...matches[0][2].matchAll(ACTION_ANONYMOUS_FUNC_REGEX)];
let successArg = args[0] ? args[0][0] : "() => {}";
let errorArg = args[1] ? args[1][0] : "() => {}";
if (label === "onSuccess") {
successArg = changeValue.endsWith(")")
? `() => ${changeValue}`
: `() => ${changeValue}()`;
}
if (label === "onError") {
errorArg = changeValue.endsWith(")")
? `() => ${changeValue}`
: `() => ${changeValue}()`;
}
return currentValue.replace(
ACTION_TRIGGER_REGEX,
`{{$1(${successArg}, ${errorArg})}}`,
);
};
const handlePageNameArgSelect = (changeValue: string, currentValue: string) => {
return currentValue.replace(ACTION_TRIGGER_REGEX, `{{$1(${changeValue})}}`);
};
const handleTextArgChange = (
changeValue: string,
currentValue: string,
): string => {
return currentValue.replace(ACTION_TRIGGER_REGEX, `{{$1('${changeValue}')}}`);
};
const handleAlertTextChange = (
changeValue: string,
currentValue: string,
): string => {
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
const args = matches[0][2].split(",");
args[0] = `'${changeValue}'`;
return currentValue.replace(
ACTION_TRIGGER_REGEX,
`{{$1(${args.join(",")})}}`,
);
};
const handleAlertTypeChange = (
changeValue: string,
currentValue: string,
): string => {
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
const args = matches[0][2].split(",");
args[1] = changeValue;
return currentValue.replace(
ACTION_TRIGGER_REGEX,
`{{$1(${args.join(",")})}}`,
);
};
const getApiArgumentValue = (
value: string,
label: "onSuccess" | "onError",
returnSubArguments = false,
): string => {
let selectedValue = "none";
let selectedValueArgs = "";
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
if (matches.length) {
const funcArgs = matches[0][2];
const argIndex = label === "onSuccess" ? 0 : 1;
const args = [...funcArgs.matchAll(ACTION_ANONYMOUS_FUNC_REGEX)];
const selectedArg = args[argIndex];
if (selectedArg && selectedArg.length) {
selectedValue = selectedArg[1];
selectedValueArgs = selectedArg[2];
}
}
if (returnSubArguments) return selectedValueArgs;
return selectedValue;
};
const getPageNameSelectedValue = (value: string) => {
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
return matches.length ? matches[0][2] : "none";
};
const getTextArgValue = (value: string) => {
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
if (matches.length) {
const stringValue = matches[0][2];
return stringValue.substring(1, stringValue.length - 1);
}
return "";
};
const getAlertTextValue = (value: string) => {
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
if (matches.length) {
const funcArgs = matches[0][2];
const arg = funcArgs.split(",")[0];
return arg.substring(1, arg.length - 1);
}
return "";
};
const getAlertTypeValue = (value: string) => {
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
if (matches.length) {
const funcArgs = matches[0][2];
const arg = funcArgs.split(",")[1];
return arg ? arg.trim() : "'primary'";
}
return "";
};
export const PropertyPaneActionDropdownOptions: ActionCreatorDropdownOption[] = [
{
label: "No action",
value: "none",
id: "none",
arguments: [],
},
{
label: "Call API",
value: "api",
id: "api",
arguments: [
{
label: "onSuccess",
field: "ACTION_SELECTOR_FIELD",
valueChangeHandler: (changeValue, currentValue) =>
handleApiArgSelect(changeValue, currentValue, "onSuccess"),
getSelectedValue: (value: string, returnArgs = false) =>
getApiArgumentValue(value, "onSuccess", returnArgs),
},
{
label: "onError",
field: "ACTION_SELECTOR_FIELD",
valueChangeHandler: (changeValue, currentValue) =>
handleApiArgSelect(changeValue, currentValue, "onError"),
getSelectedValue: (value: string, returnArgs = false) =>
getApiArgumentValue(value, "onError", returnArgs),
},
],
},
{
label: "Navigate to Page",
value: "navigateTo",
id: "navigateTo",
arguments: [
{
label: "pageName",
field: "PAGE_SELECTOR_FIELD",
valueChangeHandler: handlePageNameArgSelect,
getSelectedValue: getPageNameSelectedValue,
},
],
},
{
label: "Navigate to URL",
value: "navigateToUrl",
id: "navigateToUrl",
arguments: [
{
label: "URL",
field: "TEXT_FIELD",
valueChangeHandler: handleTextArgChange,
getSelectedValue: getTextArgValue,
},
],
},
{
label: "Show Alert",
value: "showAlert",
id: "showAlert",
arguments: [
{
label: "text",
field: "TEXT_FIELD",
valueChangeHandler: handleAlertTextChange,
getSelectedValue: getAlertTextValue,
},
{
label: "type",
field: "ALERT_TYPE_SELECTOR_FIELD",
valueChangeHandler: handleAlertTypeChange,
getSelectedValue: getAlertTypeValue,
},
],
},
];
type ReduxStateProps = {
actions: DropdownOption[];
pageNameDropdown: DropdownOption[];
};
interface Props {
value: string;
onValueChange: (newValue: string) => void;
}
class DynamicActionCreator extends React.Component<Props & ReduxStateProps> {
getTopLevelFuncValue = (value: string) => {
let matches: any[] = [];
if (value) {
matches = value ? [...value.matchAll(ACTION_TRIGGER_REGEX)] : [];
}
let mainFuncSelectedValue = "none";
if (matches.length) {
mainFuncSelectedValue = matches[0][1] || "none";
}
return mainFuncSelectedValue;
};
handleValueUpdate = (
updateValueOrEvent: string | ChangeEvent<HTMLTextAreaElement>,
valueUpdateHandler: ValueChangeHandler,
) => {
const { value, onValueChange } = this.props;
let updateValue = updateValueOrEvent;
if (typeof updateValueOrEvent !== "string") {
updateValue = updateValueOrEvent.target.value;
}
const newValue = valueUpdateHandler(updateValue as string, value);
onValueChange(newValue);
};
renderSubArgumentFields = (
argValue: string,
allOptions: ActionCreatorDropdownOption[],
parentChangeHandler: (
updateValueOrEvent: string | ChangeEvent<HTMLTextAreaElement>,
valueUpdateHandler: ValueChangeHandler,
) => void,
argumentConfig: ActionCreatorArgumentConfig,
) => {
const subArgValue = argumentConfig.getSelectedValue(argValue, false);
const subArguments = argumentConfig.getSelectedValue(argValue, true);
let selectedOption = allOptions[0];
allOptions
.filter(o => o.value !== "api")
.forEach(o => {
if (o.value === subArgValue) {
selectedOption = o;
}
});
const handleValueUpdate = (
updateValueOrEvent: string | ChangeEvent<HTMLTextAreaElement>,
valueUpdateHandler: ValueChangeHandler,
) => {
let updateValue = updateValueOrEvent;
if (typeof updateValueOrEvent !== "string") {
updateValue = updateValueOrEvent.target.value;
}
const tempArg = `{{${subArgValue}${subArguments}}}`;
const newValue = valueUpdateHandler(updateValue as string, tempArg);
const newArgValue = newValue.substring(2, newValue.length - 2);
parentChangeHandler(newArgValue, argumentConfig.valueChangeHandler);
};
const subFunctionCall = `{{${subArgValue}${subArguments}}}`;
return this.renderActionArgumentFields(
subFunctionCall,
selectedOption,
allOptions,
handleValueUpdate,
);
};
renderActionArgumentFields = (
value: string,
selectedOption: ActionCreatorDropdownOption,
allOptions: ActionCreatorDropdownOption[],
handleUpdate: (
updateValueOrEvent: string | ChangeEvent<HTMLTextAreaElement>,
valueUpdateHandler: ValueChangeHandler,
) => void,
) => {
return (
<div style={{ paddingLeft: 5 }}>
{selectedOption.arguments.map(arg => {
switch (arg.field) {
case "ACTION_SELECTOR_FIELD":
return (
<ControlWrapper key={arg.label}>
<label>{arg.label}</label>
<StyledDropdown
options={allOptions}
selectedValue={arg.getSelectedValue(value, false)}
onSelect={value =>
handleUpdate(value, arg.valueChangeHandler)
}
/>
{this.renderSubArgumentFields(
value,
allOptions,
handleUpdate,
arg,
)}
</ControlWrapper>
);
case "PAGE_SELECTOR_FIELD":
return (
<ControlWrapper key={arg.label}>
<label>{arg.label}</label>
<StyledDropdown
options={this.props.pageNameDropdown}
selectedValue={arg.getSelectedValue(value, false)}
onSelect={value =>
handleUpdate(value, arg.valueChangeHandler)
}
/>
</ControlWrapper>
);
case "TEXT_FIELD":
return (
<React.Fragment key={arg.label}>
<InputText
label={arg.label}
value={arg.getSelectedValue(value, false)}
onChange={e => handleUpdate(e, arg.valueChangeHandler)}
isValid={true}
/>
</React.Fragment>
);
case "ALERT_TYPE_SELECTOR_FIELD":
return (
<ControlWrapper key={arg.label}>
<label>{arg.label}</label>
<StyledDropdown
options={ALERT_STYLE_OPTIONS}
selectedValue={arg.getSelectedValue(value, false)}
onSelect={value =>
handleUpdate(value, arg.valueChangeHandler)
}
/>
</ControlWrapper>
);
default:
return null;
}
})}
</div>
);
};
render() {
const { actions, value } = this.props;
const topLevelFuncValue = this.getTopLevelFuncValue(value);
const actionOptions = PropertyPaneActionDropdownOptions.map(o => {
if (o.id === "api") {
return {
...o,
children: actions.map(a => ({ ...o, ...a })),
};
} else {
return o;
}
});
let selectedOption = actionOptions[0];
actionOptions.forEach(o => {
if (
o.value === topLevelFuncValue ||
_.some(o.children, {
value: topLevelFuncValue,
})
) {
selectedOption = o;
}
});
return (
<React.Fragment>
<StyledDropdown
options={actionOptions}
selectedValue={topLevelFuncValue}
onSelect={value =>
this.handleValueUpdate(value, handleTopLevelFuncUpdate)
}
/>
{this.renderActionArgumentFields(
value,
selectedOption,
actionOptions,
this.handleValueUpdate,
)}
</React.Fragment>
);
}
}
const mapStateToProps = (state: AppState): ReduxStateProps => ({
actions: state.entities.actions.map(a => ({
label: a.config.name,
id: a.config.id,
value: `${a.config.name}.run`,
})),
pageNameDropdown: state.entities.pageList.pages.map(p => ({
label: p.pageName,
id: p.pageId,
value: `'${p.pageName}'`,
})),
});
export default connect(mapStateToProps)(DynamicActionCreator);

View File

@ -10,15 +10,13 @@ import "codemirror/addon/hint/javascript-hint";
import "codemirror/addon/display/placeholder"; import "codemirror/addon/display/placeholder";
import "codemirror/addon/edit/closebrackets"; import "codemirror/addon/edit/closebrackets";
import "codemirror/addon/display/autorefresh"; import "codemirror/addon/display/autorefresh";
import { import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors";
getNameBindingsForAutocomplete,
NameBindingsWithData,
} from "selectors/nameBindingsWithDataSelector";
import { AUTOCOMPLETE_MATCH_REGEX } from "constants/BindingsConstants"; import { AUTOCOMPLETE_MATCH_REGEX } from "constants/BindingsConstants";
import ErrorTooltip from "components/editorComponents/ErrorTooltip"; import ErrorTooltip from "components/editorComponents/ErrorTooltip";
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form"; import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
import _ from "lodash"; import _ from "lodash";
import { parseDynamicString } from "utils/DynamicBindingUtils"; import { parseDynamicString } from "utils/DynamicBindingUtils";
import { DataTree } from "entities/DataTree/dataTreeFactory";
require("codemirror/mode/javascript/javascript"); require("codemirror/mode/javascript/javascript");
const HintStyles = createGlobalStyle` const HintStyles = createGlobalStyle`
@ -125,7 +123,7 @@ const THEMES = {
type THEME = "LIGHT" | "DARK"; type THEME = "LIGHT" | "DARK";
interface ReduxStateProps { interface ReduxStateProps {
dynamicData: NameBindingsWithData; dynamicData: DataTree;
} }
export type DynamicAutocompleteInputProps = { export type DynamicAutocompleteInputProps = {
@ -306,7 +304,7 @@ class DynamicAutocompleteInput extends Component<Props, State> {
} }
const mapStateToProps = (state: AppState): ReduxStateProps => ({ const mapStateToProps = (state: AppState): ReduxStateProps => ({
dynamicData: getNameBindingsForAutocomplete(state), dynamicData: getDataTreeForAutocomplete(state),
}); });
export default connect(mapStateToProps)(DynamicAutocompleteInput); export default connect(mapStateToProps)(DynamicAutocompleteInput);

View File

@ -9,19 +9,15 @@ import { updateWidget } from "actions/pageActions";
import { executeAction, disableDragAction } from "actions/widgetActions"; import { executeAction, disableDragAction } from "actions/widgetActions";
import { updateWidgetProperty } from "actions/controlActions"; import { updateWidgetProperty } from "actions/controlActions";
import { ActionPayload } from "constants/ActionConstants"; import { ExecuteActionPayload } from "constants/ActionConstants";
import { RenderModes } from "constants/WidgetConstants"; import { RenderModes } from "constants/WidgetConstants";
import { OccupiedSpace } from "constants/editorConstants"; import { OccupiedSpace } from "constants/editorConstants";
import { getOccupiedSpaces } from "selectors/editorSelectors"; import { getOccupiedSpaces } from "selectors/editorSelectors";
import { PaginationField } from "api/ActionAPI";
import { updateWidgetMetaProperty } from "actions/metaActions"; import { updateWidgetMetaProperty } from "actions/metaActions";
export type EditorContextType = { export type EditorContextType = {
executeAction?: ( executeAction?: (actionPayloads: ExecuteActionPayload) => void;
actionPayloads: ActionPayload[],
paginationField?: PaginationField,
) => void;
updateWidget?: ( updateWidget?: (
operation: WidgetOperation, operation: WidgetOperation,
widgetId: string, widgetId: string,
@ -99,21 +95,19 @@ const mapDispatchToProps = (dispatch: any) => {
RenderModes.CANVAS, RenderModes.CANVAS,
), ),
), ),
executeAction: (actionPayload: ExecuteActionPayload) =>
dispatch(executeAction(actionPayload)),
updateWidget: (
operation: WidgetOperation,
widgetId: string,
payload: any,
) => dispatch(updateWidget(operation, widgetId, payload)),
updateWidgetMetaProperty: ( updateWidgetMetaProperty: (
widgetId: string, widgetId: string,
propertyName: string, propertyName: string,
propertyValue: any, propertyValue: any,
) => ) =>
dispatch(updateWidgetMetaProperty(widgetId, propertyName, propertyValue)), dispatch(updateWidgetMetaProperty(widgetId, propertyName, propertyValue)),
executeAction: (
actionPayloads: ActionPayload[],
paginationField?: PaginationField,
) => dispatch(executeAction(actionPayloads, paginationField)),
updateWidget: (
operation: WidgetOperation,
widgetId: string,
payload: any,
) => dispatch(updateWidget(operation, widgetId, payload)),
disableDrag: (disable: boolean) => { disableDrag: (disable: boolean) => {
dispatch(disableDragAction(disable)); dispatch(disableDragAction(disable));
}, },

View File

@ -0,0 +1,101 @@
import React from "react";
import _ from "lodash";
import { DropdownOption } from "widgets/DropdownWidget";
import {
StyledDropDown,
StyledDropDownContainer,
} from "components/propertyControls/StyledControls";
import {
Button,
MenuItem,
PopoverInteractionKind,
PopoverPosition,
} from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons";
type ActionTypeDropdownProps = {
options: DropdownOption[];
selectedValue: string;
onSelect: (value: string) => void;
};
class StyledDropdown extends React.Component<ActionTypeDropdownProps> {
handleSelect = (option: DropdownOption) => {
this.props.onSelect(option.value);
};
renderItem = (option: DropdownOption) => {
const isSelected = this.isOptionSelected(option);
return (
<MenuItem
className="single-select"
active={isSelected}
key={option.value}
onClick={() => this.handleSelect(option)}
text={option.label}
popoverProps={{
minimal: true,
hoverCloseDelay: 0,
interactionKind: PopoverInteractionKind.HOVER,
position: PopoverPosition.BOTTOM,
modifiers: {
arrow: {
enabled: false,
},
offset: {
enabled: true,
offset: "-16px, 0",
},
},
}}
>
{option.children && option.children.map(this.renderItem)}
</MenuItem>
);
};
isOptionSelected = (currentOption: DropdownOption) => {
if (currentOption.children) {
return _.some(currentOption.children, {
value: this.props.selectedValue,
});
} else {
return currentOption.value === this.props.selectedValue;
}
};
render() {
const { selectedValue } = this.props;
let selectedOption = this.props.options[0];
this.props.options.forEach(o => {
if (o.value === selectedValue) {
selectedOption = o;
} else {
const childOption = _.find(o.children, {
value: this.props.selectedValue,
});
if (childOption) selectedOption = childOption;
}
});
return (
<StyledDropDownContainer>
<StyledDropDown
filterable={false}
items={this.props.options}
itemRenderer={this.renderItem}
onItemSelect={_.noop}
popoverProps={{
minimal: true,
usePortal: false,
position: PopoverPosition.BOTTOM,
}}
>
<Button
rightIcon={IconNames.CHEVRON_DOWN}
text={selectedOption.label}
/>
</StyledDropDown>
</StyledDropDownContainer>
);
}
}
export default StyledDropdown;

View File

@ -1,7 +1,52 @@
import { Position, Toaster } from "@blueprintjs/core"; import React from "react";
import { toast, ToastOptions, TypeOptions, ToastType } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import styled from "styled-components";
import { theme } from "constants/DefaultTheme";
import { AlertIcons } from "icons/AlertIcons";
// To add a toast import this instance and call .show() const ToastBody = styled.div<{ type: TypeOptions }>`
// https://blueprintjs.com/docs/#core/components/toast.example height: 100%;
export const AppToaster = Toaster.create({ border-left: 4px solid ${({ type }) => theme.alert[type].color};
position: Position.BOTTOM_RIGHT, border-radius: 4px;
}); background-color: white;
color: black;
display: flex;
align-items: center;
padding-left: 5px;
`;
const ToastMessage = styled.span`
font-size: 16px;
margin: 0 5px;
`;
const ToastIcon = {
info: AlertIcons.INFO,
success: AlertIcons.SUCCESS,
error: AlertIcons.ERROR,
warning: AlertIcons.WARNING,
default: AlertIcons.INFO,
};
type Props = ToastOptions & { message: string; closeToast?: () => void };
const ToastComponent = (props: Props) => {
const alertType = props.type || ToastType.INFO;
const Icon = ToastIcon[alertType];
return (
<ToastBody type={alertType}>
<Icon color={theme.alert[alertType].color} width={20} height={20} />
<ToastMessage>{props.message}</ToastMessage>
</ToastBody>
);
};
const Toaster = {
show: (config: ToastOptions & { message: string }) => {
toast(<ToastComponent {...config} />);
},
clear: () => toast.dismiss(),
};
export const AppToaster = Toaster;

View File

@ -1,336 +0,0 @@
import React from "react";
import { ActionPayload, ActionType } from "constants/ActionConstants";
import { DropdownOption } from "widgets/DropdownWidget";
import { MenuItem, Button } from "@blueprintjs/core";
import styled, { theme } from "constants/DefaultTheme";
import { CloseButton } from "components/designSystems/blueprint/CloseButton";
import { StyledDropDown } from "./StyledControls";
import { IItemRendererProps } from "@blueprintjs/select";
import { InputText } from "./InputTextControl";
const DEFAULT_ACTION_TYPE = "Select Action Type" as ActionType;
const DEFAULT_ACTION_LABEL = "Select Action";
enum ACTION_RESOLUTION_TYPE {
SUCCESS,
ERROR,
}
const renderItem = (option: DropdownOption, itemProps: IItemRendererProps) => {
if (!itemProps.modifiers.matchesPredicate) {
return null;
}
return (
<MenuItem
active={itemProps.modifiers.active}
key={option.value}
onClick={itemProps.handleClick}
text={option.label}
/>
);
};
const ActionSelectorDropDown = styled(StyledDropDown)`
&&&&& {
display: block;
margin-bottom: 2px;
width: 100%;
.bp3-popover-target {
width: 100%;
.bp3-button {
justify-content: flex-start;
width: 100%;
.bp3-icon {
order: 2;
margin-left: auto;
}
}
}
}
`;
const ActionSelectorDropDownContainer = styled.div`
position: relative;
`;
interface ActionSelectorProps {
allActions: DropdownOption[];
actionTypeOptions: DropdownOption[];
selectedActionType: ActionType;
selectedActionLabel: string;
label: string;
identifier: string;
labelEditable?: boolean;
actionResolutionType: ACTION_RESOLUTION_TYPE | string;
updateActions: (actions: ActionPayload[], key?: string) => void;
updateLabel?: (label: string, key: string) => void;
actions: ActionPayload[];
}
export default function ActionSelector(props: ActionSelectorProps) {
function clearActionSelectorType(
actionResolutionType: ACTION_RESOLUTION_TYPE | string,
) {
let actionPayloads: ActionPayload[] = props.actions
? props.actions.slice()
: [];
const actionPayload = actionPayloads[0];
switch (actionResolutionType) {
case props.identifier:
actionPayloads = [];
break;
case ACTION_RESOLUTION_TYPE.SUCCESS:
actionPayload.onSuccess = undefined;
break;
case ACTION_RESOLUTION_TYPE.ERROR:
actionPayload.onError = undefined;
break;
}
props.updateActions(actionPayloads, props.identifier);
}
function clearActionSelectorLabel(
actionResolutionType: ACTION_RESOLUTION_TYPE | string,
) {
let actionPayloads = props.actions.slice();
const actionPayload = (props.actions[0] as any) as ActionPayload;
switch (actionResolutionType) {
case props.identifier:
actionPayloads = [];
actionPayloads.push(({
...actionPayload,
actionId: undefined,
onSuccess: undefined,
onError: undefined,
} as any) as ActionPayload);
break;
case ACTION_RESOLUTION_TYPE.SUCCESS:
const successActionPayload =
actionPayload.onSuccess !== undefined
? actionPayload.onSuccess[0]
: undefined;
const successActionPayloads = [];
successActionPayloads.push({
...successActionPayload,
actionId: "",
});
actionPayload.onSuccess = successActionPayloads as ActionPayload[];
break;
case ACTION_RESOLUTION_TYPE.ERROR:
const errorActionPayload =
actionPayload.onError !== undefined
? actionPayload.onError[0]
: undefined;
const errorActionPayloads = [];
errorActionPayloads.push({
...errorActionPayload,
actionId: "",
});
actionPayload.onError = errorActionPayloads as ActionPayload[];
break;
}
props.updateActions(actionPayloads, props.identifier);
}
const onActionTypeSelect = (item: DropdownOption) => {
const actionPayloads: ActionPayload[] = props.actions
? props.actions.slice()
: [];
const actionPayload = actionPayloads[0];
if (actionPayload && actionPayload.actionType !== item.value) {
actionPayload.actionId = "";
actionPayload.onError = undefined;
actionPayload.onSuccess = undefined;
actionPayload.actionType = item.value as ActionType;
} else {
const actionPayload = { actionType: item.value } as ActionPayload;
actionPayloads.push(actionPayload);
}
props.updateActions(actionPayloads, props.identifier);
};
const onSuccessActionTypeSelect = (item: DropdownOption) => {
const actionPayloads: ActionPayload[] = props.actions
? props.actions.slice()
: [];
const actionPayload = actionPayloads[0];
if (actionPayload) {
const successActionPayloads: ActionPayload[] =
actionPayload.onSuccess || [];
const successActionPayload = successActionPayloads[0];
if (successActionPayload) {
successActionPayload.actionId = "";
successActionPayload.actionType = item.value as ActionType;
} else {
const successActionPayload = {
actionType: item.value,
} as ActionPayload;
successActionPayloads.push(successActionPayload);
}
actionPayload.onSuccess = successActionPayloads;
}
props.updateActions(actionPayloads, props.identifier);
};
const onErrorActionTypeSelect = (item: DropdownOption) => {
const actionPayloads: ActionPayload[] = props.actions
? props.actions.slice()
: [];
const actionPayload = actionPayloads[0];
if (actionPayload) {
const errorActionPayloads: ActionPayload[] = actionPayload.onError || [];
const errorActionPayload = errorActionPayloads[0];
if (errorActionPayload) {
errorActionPayload.actionId = "";
errorActionPayload.actionType = item.value as ActionType;
} else {
const errorActionPayload = {
actionType: item.value,
} as ActionPayload;
errorActionPayloads.push(errorActionPayload);
}
actionPayload.onError = errorActionPayloads;
}
props.updateActions(actionPayloads, props.identifier);
};
const onActionSelect = (item: DropdownOption): void => {
const actionPayloads: ActionPayload[] = props.actions
? props.actions.slice()
: [];
const actionPayload = actionPayloads[0];
actionPayload.actionId = item.value;
props.updateActions(actionPayloads, props.identifier);
};
const onSuccessActionSelect = (item: DropdownOption): void => {
const actionPayloads: ActionPayload[] = props.actions
? props.actions.slice()
: [];
const actionPayload = actionPayloads[0];
const successActionPayloads: ActionPayload[] = actionPayload.onSuccess as ActionPayload[];
const successActionPayload = successActionPayloads[0];
successActionPayload.actionId = item.value;
actionPayload.onSuccess = successActionPayloads;
props.updateActions(actionPayloads, props.identifier);
};
const onErrorActionSelect = (item: DropdownOption): void => {
const actionPayloads: ActionPayload[] = props.actions
? props.actions.slice()
: [];
const actionPayload = actionPayloads[0];
const errorActionPayloads: ActionPayload[] = actionPayload.onError as ActionPayload[];
const errorActionPayload = errorActionPayloads[0];
errorActionPayload.actionId = item.value;
actionPayload.onError = errorActionPayloads;
props.updateActions(actionPayloads, props.identifier);
};
let onTypeSelect = onActionTypeSelect;
switch (props.actionResolutionType) {
case ACTION_RESOLUTION_TYPE.SUCCESS:
onTypeSelect = onSuccessActionTypeSelect;
break;
case ACTION_RESOLUTION_TYPE.ERROR:
onTypeSelect = onErrorActionTypeSelect;
break;
}
let onActionSelectHandler = onActionSelect;
switch (props.actionResolutionType) {
case ACTION_RESOLUTION_TYPE.SUCCESS:
onActionSelectHandler = onSuccessActionSelect;
break;
case ACTION_RESOLUTION_TYPE.ERROR:
onActionSelectHandler = onErrorActionSelect;
break;
}
const showActionTypeRemoveButton =
props.selectedActionType &&
props.selectedActionType !== DEFAULT_ACTION_TYPE;
const showActionLabelRemoveButton =
props.selectedActionLabel &&
props.selectedActionLabel !== DEFAULT_ACTION_LABEL;
const onTextChange = (
event: React.ChangeEvent<HTMLTextAreaElement> | string,
) => {
let value = event;
if (typeof event !== "string") {
value = event.target.value;
}
!!props.updateLabel &&
props.updateLabel((value as any) as string, props.identifier);
// props.updateProperty(this.props.propertyName, value);
};
return (
<div>
<div hidden={props.labelEditable}>
<label>{props.identifier}</label>
</div>
<div hidden={!props.labelEditable}>
<InputText
label={"Action Name"}
value={props.label}
onChange={onTextChange}
isValid={true}
></InputText>
<label>{"On Action Click"}</label>
</div>
<ActionSelectorDropDownContainer>
<ActionSelectorDropDown
items={props.actionTypeOptions}
filterable={false}
itemRenderer={renderItem}
onItemSelect={onTypeSelect}
noResults={<MenuItem disabled={true} text="No results." />}
>
{
<Button
text={props.selectedActionType}
rightIcon={showActionTypeRemoveButton ? false : "chevron-down"}
/>
}
</ActionSelectorDropDown>
{showActionTypeRemoveButton && (
<CloseButton
size={theme.spaces[5]}
color={theme.colors.paneSectionLabel}
onClick={() => {
clearActionSelectorType(props.actionResolutionType);
}}
></CloseButton>
)}
</ActionSelectorDropDownContainer>
<ActionSelectorDropDownContainer>
{props.selectedActionType !== DEFAULT_ACTION_TYPE && (
<ActionSelectorDropDown
items={props.allActions}
filterable={false}
itemRenderer={renderItem}
onItemSelect={onActionSelectHandler}
noResults={<MenuItem disabled={true} text="No results." />}
>
<Button
text={props.selectedActionLabel}
rightIcon={showActionLabelRemoveButton ? false : "chevron-down"}
/>
</ActionSelectorDropDown>
)}
{showActionLabelRemoveButton && (
<CloseButton
size={theme.spaces[5]}
color={theme.colors.paneSectionLabel}
onClick={() => {
clearActionSelectorLabel(props.actionResolutionType);
}}
></CloseButton>
)}
</ActionSelectorDropDownContainer>
</div>
);
}

View File

@ -2,221 +2,30 @@ import React from "react";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { ControlWrapper } from "./StyledControls"; import { ControlWrapper } from "./StyledControls";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import { import DynamicActionCreator from "components/editorComponents/DynamicActionCreator";
ActionPayload,
ActionType,
PropertyPaneActionDropdownOptions,
} from "constants/ActionConstants";
import { DropdownOption } from "widgets/DropdownWidget";
import { connect } from "react-redux";
import { AppState } from "reducers";
import styled from "styled-components";
import ActionSelector from "./ActionSelector";
import { RestAction } from "api/ActionAPI";
const DEFAULT_ACTION_TYPE = "Select Action Type" as ActionType; class ActionSelectorControl extends BaseControl<ControlProps> {
const DEFAULT_ACTION_LABEL = "Select Action"; handleValueUpdate = (newValue: string) => {
enum ACTION_RESOLUTION_TYPE { const { propertyName } = this.props;
SUCCESS, this.updateProperty(propertyName, newValue);
ERROR,
}
const ResolutionActionContainer = styled.div`
padding: 0px 0px;
`;
function getActions(
actionPayloads: ActionPayload[] | undefined,
): {
action: ActionPayload | undefined;
onSuccessAction: ActionPayload | undefined;
onErrorAction: ActionPayload | undefined;
} {
const action: ActionPayload | undefined = actionPayloads && actionPayloads[0];
const onSuccessAction: ActionPayload | undefined =
action && action.onSuccess && action.onSuccess[0];
const onErrorAction: ActionPayload | undefined =
action && action.onError && action.onError[0];
return {
action,
onSuccessAction,
onErrorAction,
}; };
}
interface FinalActionSelectorProps {
actions: ActionPayload[];
identifier: string;
actionsData: RestAction[];
label: string;
labelEditable?: boolean;
updateLabel?: (label: string, key: string) => void;
updateActions: (actions: ActionPayload[], key?: string) => void;
}
export function FinalActionSelector(props: FinalActionSelectorProps) {
function getSelectionActionType(
type: ACTION_RESOLUTION_TYPE | string,
): ActionType {
let selectedActionTypeValue: ActionType | undefined;
const { action, onSuccessAction, onErrorAction } = getActions(
props.actions,
);
switch (type) {
case props.identifier:
selectedActionTypeValue = action && action.actionType;
break;
case ACTION_RESOLUTION_TYPE.SUCCESS:
selectedActionTypeValue = onSuccessAction && onSuccessAction.actionType;
break;
case ACTION_RESOLUTION_TYPE.ERROR:
selectedActionTypeValue = onErrorAction && onErrorAction.actionType;
break;
default:
break;
}
const foundActionType = PropertyPaneActionDropdownOptions.find(
actionType => actionType.value === selectedActionTypeValue,
);
return foundActionType
? (foundActionType.label as ActionType)
: DEFAULT_ACTION_TYPE;
}
function getSelectionActionLabel(
type: ACTION_RESOLUTION_TYPE | string,
allActions: DropdownOption[],
): string {
let selectedActionId: string | undefined = "";
const { action, onSuccessAction, onErrorAction } = getActions(
props.actions,
);
switch (type) {
case props.identifier:
selectedActionId = action && action.actionId;
break;
case ACTION_RESOLUTION_TYPE.SUCCESS:
selectedActionId = onSuccessAction && onSuccessAction.actionId;
break;
case ACTION_RESOLUTION_TYPE.ERROR:
selectedActionId = onErrorAction && onErrorAction.actionId;
break;
default:
break;
}
const foundAction = allActions.find(
action => action.value === selectedActionId,
);
return foundAction ? foundAction.label : DEFAULT_ACTION_LABEL;
}
const actionTypeOptions: DropdownOption[] = PropertyPaneActionDropdownOptions;
const allActions = props.actionsData.map(action => {
return {
label: action.name,
value: action.id,
id: action.id,
};
});
const selectedActionType = getSelectionActionType(props.identifier);
const selectedActionLabel = getSelectionActionLabel(
props.identifier,
allActions,
);
const selectedSuccessActionType = getSelectionActionType(
ACTION_RESOLUTION_TYPE.SUCCESS,
);
const selectedSuccessActionLabel = getSelectionActionLabel(
ACTION_RESOLUTION_TYPE.SUCCESS,
allActions,
);
const selectedErrorActionType = getSelectionActionType(
ACTION_RESOLUTION_TYPE.ERROR,
);
const selectedErrorActionLabel = getSelectionActionLabel(
ACTION_RESOLUTION_TYPE.ERROR,
allActions,
);
return (
<ControlWrapper>
<ActionSelector
allActions={allActions}
actionTypeOptions={actionTypeOptions}
selectedActionType={selectedActionType}
selectedActionLabel={selectedActionLabel}
label={props.label}
identifier={props.identifier}
actionResolutionType={props.identifier}
updateActions={props.updateActions}
updateLabel={props.updateLabel}
actions={props.actions}
labelEditable={props.labelEditable}
/>
{selectedActionLabel !== DEFAULT_ACTION_LABEL && (
<ResolutionActionContainer>
<ActionSelector
allActions={allActions}
actionTypeOptions={actionTypeOptions}
selectedActionType={selectedSuccessActionType}
selectedActionLabel={selectedSuccessActionLabel}
identifier={"On Success"}
label={"On Success"}
actionResolutionType={ACTION_RESOLUTION_TYPE.SUCCESS}
updateActions={props.updateActions}
updateLabel={props.updateLabel}
actions={props.actions}
/>
<ActionSelector
allActions={allActions}
actionTypeOptions={actionTypeOptions}
selectedActionType={selectedErrorActionType}
selectedActionLabel={selectedErrorActionLabel}
identifier={"On Error"}
label={"On Error"}
actionResolutionType={ACTION_RESOLUTION_TYPE.ERROR}
updateActions={props.updateActions}
updateLabel={props.updateLabel}
actions={props.actions}
/>
</ResolutionActionContainer>
)}
</ControlWrapper>
);
}
class ActionSelectorControl extends BaseControl<
ControlProps & { data: RestAction[] }
> {
render() { render() {
const { propertyValue } = this.props;
return ( return (
<FinalActionSelector <ControlWrapper>
actionsData={this.props.data} <label>{this.props.label}</label>
label={this.props.propertyName} <DynamicActionCreator
actions={this.props.propertyValue} value={propertyValue}
identifier={this.props.propertyName} onValueChange={this.handleValueUpdate}
updateActions={this.updateActions} />
/> </ControlWrapper>
); );
} }
updateActions = (actions: ActionPayload[]) => {
this.updateProperty(this.props.propertyName, actions);
};
getControlType(): ControlType { getControlType(): ControlType {
return "ACTION_SELECTOR"; return "ACTION_SELECTOR";
} }
} }
export interface ActionSelectorControlProps extends ControlProps { export default ActionSelectorControl;
propertyValue: ActionPayload[];
}
const mapStateToProps = (state: AppState): { data: RestAction[] } => ({
data: state.entities.actions.map(a => a.config),
});
export default connect(mapStateToProps)(ActionSelectorControl);

View File

@ -6,7 +6,10 @@ import { Component } from "react";
import _ from "lodash"; import _ from "lodash";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
abstract class BaseControl<T extends ControlProps> extends Component<T> { abstract class BaseControl<P extends ControlProps, S = {}> extends Component<
P,
S
> {
updateProperty(propertyName: string, propertyValue: any) { updateProperty(propertyName: string, propertyValue: any) {
if (!_.isNil(this.props.onPropertyChange)) if (!_.isNil(this.props.onPropertyChange))
this.props.onPropertyChange(propertyName, propertyValue); this.props.onPropertyChange(propertyName, propertyValue);

View File

@ -3,21 +3,18 @@ import React from "react";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls"; import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import { AppState } from "reducers";
import { connect } from "react-redux";
import { ActionPayload } from "constants/ActionConstants";
import { FinalActionSelector } from "./ActionSelectorControl";
import { generateReactKey } from "utils/generators"; import { generateReactKey } from "utils/generators";
import styled from "constants/DefaultTheme"; import styled from "constants/DefaultTheme";
import { AnyStyledComponent } from "styled-components"; import { AnyStyledComponent } from "styled-components";
import { FormIcons } from "icons/FormIcons"; import { FormIcons } from "icons/FormIcons";
import { RestAction } from "api/ActionAPI"; import { InputText } from "components/propertyControls/InputTextControl";
import DynamicActionCreator from "components/editorComponents/DynamicActionCreator";
export interface ColumnAction { export interface ColumnAction {
label: string; label: string;
id: string; id: string;
actionPayloads: ActionPayload[]; dynamicTrigger: string;
} }
const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)` const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)`
padding: 5px 5px; padding: 5px 5px;
position: absolute; position: absolute;
@ -27,40 +24,44 @@ const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)`
`; `;
class ColumnActionSelectorControl extends BaseControl< class ColumnActionSelectorControl extends BaseControl<
ColumnActionSelectorControlProps & { data: RestAction[] } ColumnActionSelectorControlProps
> { > {
render() { render() {
return ( return (
<ControlWrapper orientation={"VERTICAL"}> <ControlWrapper orientation={"VERTICAL"}>
{this.props.propertyValue && {this.props.propertyValue &&
this.props.propertyValue.map( this.props.propertyValue.map((columnAction: ColumnAction) => {
(columnAction: ColumnAction, index: number) => { return (
return ( <div
<div key={columnAction.id}
key={columnAction.id} style={{
style={{ position: "relative",
position: "relative", }}
// position: "absolute", >
}} <InputText
> label={columnAction.label}
<FinalActionSelector value={columnAction.label}
identifier={columnAction.id} onChange={this.updateColumnActionLabel.bind(
actionsData={this.props.data} this,
actions={columnAction.actionPayloads} columnAction,
updateActions={this.updateActions} )}
label={columnAction.label} isValid={true}
labelEditable={true} />
updateLabel={this.updateLabel} <DynamicActionCreator
/> value={columnAction.dynamicTrigger}
<StyledDeleteIcon onValueChange={this.updateColumnActionFunction.bind(
height={20} this,
width={20} columnAction,
onClick={this.removeColumnAction.bind(this, index)} )}
/> />
</div> <StyledDeleteIcon
); height={20}
}, width={20}
)} onClick={this.removeColumnAction.bind(this, columnAction)}
/>
</div>
);
})}
<StyledPropertyPaneButton <StyledPropertyPaneButton
text={"Column Action"} text={"Column Action"}
icon={"plus"} icon={"plus"}
@ -71,67 +72,50 @@ class ColumnActionSelectorControl extends BaseControl<
</ControlWrapper> </ControlWrapper>
); );
} }
onTextChange = (event: React.ChangeEvent<HTMLTextAreaElement> | string) => {
let value = event; updateColumnActionLabel = (
if (typeof event !== "string") { columnAction: ColumnAction,
value = event.target.value; newValue: React.ChangeEvent<HTMLTextAreaElement> | string,
) => {
let value = newValue;
if (typeof newValue !== "string") {
value = newValue.target.value;
} }
this.updateProperty(this.props.propertyName, value); const update = this.props.propertyValue.map((a: ColumnAction) => {
if (a.id === columnAction.id) return { ...a, label: value };
return a;
});
this.updateProperty(this.props.propertyName, update);
}; };
updateActions = (actions: ActionPayload[], key?: string) => {
const columnActions = this.props.propertyValue || []; updateColumnActionFunction = (
const columnActionsClone = columnActions.slice(); columnAction: ColumnAction,
const foundColumnActionIndex = columnActionsClone.findIndex( newValue: string,
(columnAction: ColumnAction) => columnAction.id === key, ) => {
const update = this.props.propertyValue.map((a: ColumnAction) => {
if (a.id === columnAction.id) return { ...a, dynamicTrigger: newValue };
return a;
});
this.updateProperty(this.props.propertyName, update);
};
removeColumnAction = (columnAction: ColumnAction) => {
const update = this.props.propertyValue.filter(
(a: ColumnAction) => a.id !== columnAction.id,
); );
this.updateProperty(this.props.propertyName, update);
if (foundColumnActionIndex !== -1) {
let foundColumnAction = columnActionsClone[foundColumnActionIndex];
foundColumnAction = Object.assign({}, foundColumnAction);
foundColumnAction.actionPayloads = actions;
columnActionsClone.splice(foundColumnActionIndex, 1, foundColumnAction);
}
this.updateProperty(this.props.propertyName, columnActionsClone);
};
updateLabel = (label: string, key: string) => {
const columnActions = this.props.propertyValue || [];
const columnActionsClone = columnActions.slice();
const foundColumnActionIndex = columnActionsClone.findIndex(
(columnAction: ColumnAction) => columnAction.id === key,
);
if (foundColumnActionIndex !== -1) {
let foundColumnAction = columnActionsClone[foundColumnActionIndex];
foundColumnAction = Object.assign({}, foundColumnAction);
foundColumnAction.label = label;
columnActionsClone.splice(foundColumnActionIndex, 1, foundColumnAction);
}
this.updateProperty(this.props.propertyName, columnActionsClone);
};
removeColumnAction = (index: number) => {
const columnActions = this.props.propertyValue || [];
const columnActionsClone = columnActions.slice();
columnActionsClone.splice(index, 1);
this.updateProperty(this.props.propertyName, columnActionsClone);
}; };
addColumnAction = () => { addColumnAction = () => {
const columnActions = this.props.propertyValue || []; const columnActions = this.props.propertyValue || [];
const columnActionsClone = columnActions.slice(); const update = columnActions.concat([
columnActionsClone.push({ {
label: "Action", label: "Action",
id: generateReactKey(), id: generateReactKey(),
actionPayloads: [], actionPayloads: [],
}); },
]);
this.updateProperty(this.props.propertyName, columnActionsClone); this.updateProperty(this.props.propertyName, update);
};
onToggle = () => {
this.updateProperty(this.props.propertyName, !this.props.propertyValue);
}; };
getControlType(): ControlType { getControlType(): ControlType {
@ -141,8 +125,4 @@ class ColumnActionSelectorControl extends BaseControl<
export type ColumnActionSelectorControlProps = ControlProps; export type ColumnActionSelectorControlProps = ControlProps;
const mapStateToProps = (state: AppState): { data: RestAction[] } => ({ export default ColumnActionSelectorControl;
data: state.entities.actions.map(a => a.config),
});
export default connect(mapStateToProps)(ColumnActionSelectorControl);

View File

@ -2,7 +2,11 @@ import React from "react";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { Button, MenuItem } from "@blueprintjs/core"; import { Button, MenuItem } from "@blueprintjs/core";
import { IItemRendererProps } from "@blueprintjs/select"; import { IItemRendererProps } from "@blueprintjs/select";
import { ControlWrapper, StyledDropDown } from "./StyledControls"; import {
ControlWrapper,
StyledDropDown,
StyledDropDownContainer,
} from "./StyledControls";
import { DropdownOption } from "widgets/DropdownWidget"; import { DropdownOption } from "widgets/DropdownWidget";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
@ -14,18 +18,24 @@ class DropDownControl extends BaseControl<DropDownControlProps> {
return ( return (
<ControlWrapper> <ControlWrapper>
<label>{this.props.label}</label> <label>{this.props.label}</label>
<StyledDropDown <StyledDropDownContainer>
items={this.props.options} <StyledDropDown
filterable={false} items={this.props.options}
itemRenderer={this.renderItem} filterable={false}
onItemSelect={this.onItemSelect} itemRenderer={this.renderItem}
noResults={<MenuItem disabled={true} text="No results." />} onItemSelect={this.onItemSelect}
> noResults={<MenuItem disabled={true} text="No results." />}
<Button popoverProps={{
text={selected ? selected.label : ""} minimal: true,
rightIcon="chevron-down" usePortal: false,
/> }}
</StyledDropDown> >
<Button
text={selected ? selected.label : ""}
rightIcon="chevron-down"
/>
</StyledDropDown>
</StyledDropDownContainer>
</ControlWrapper> </ControlWrapper>
); );
} }
@ -41,8 +51,8 @@ class DropDownControl extends BaseControl<DropDownControlProps> {
const isSelected: boolean = this.isOptionSelected(option); const isSelected: boolean = this.isOptionSelected(option);
return ( return (
<MenuItem <MenuItem
icon={isSelected ? "tick" : "blank"} className="single-select"
active={itemProps.modifiers.active} active={isSelected}
key={option.value} key={option.value}
onClick={itemProps.handleClick} onClick={itemProps.handleClick}
text={option.label} text={option.label}

View File

@ -1,10 +1,11 @@
import styled from "styled-components"; import styled from "styled-components";
import { Select, MultiSelect } from "@blueprintjs/select"; import { Select, MultiSelect } from "@blueprintjs/select";
import { Switch, InputGroup, Button } from "@blueprintjs/core"; import { Switch, InputGroup, Button, Classes } from "@blueprintjs/core";
import { DropdownOption } from "widgets/DropdownWidget"; import { DropdownOption } from "widgets/DropdownWidget";
import { ContainerOrientation } from "constants/WidgetConstants"; import { ContainerOrientation } from "constants/WidgetConstants";
import { DateInput } from "@blueprintjs/datetime"; import { DateInput } from "@blueprintjs/datetime";
import { TimezonePicker } from "@blueprintjs/timezone"; import { TimezonePicker } from "@blueprintjs/timezone";
import { Colors } from "constants/Colors";
type ControlWrapperProps = { type ControlWrapperProps = {
orientation?: ContainerOrientation; orientation?: ContainerOrientation;
@ -29,12 +30,90 @@ export const ControlWrapper = styled.div<ControlWrapperProps>`
} }
`; `;
export const StyledDropDownContainer = styled.div`
&&&& .${Classes.BUTTON} {
box-shadow: none;
border-radius: 4px;
background-color: ${Colors.SHARK};
color: ${Colors.CADET_BLUE};
background-image: none;
}
&&&& .${Classes.MENU_ITEM} {
border-radius: ${props => props.theme.radii[1]}px;
&:hover {
background: ${Colors.POLAR};
}
&.${Classes.ACTIVE} {
background: ${Colors.POLAR};
color: ${props => props.theme.colors.textDefault};
position: relative;
&.single-select {
&:before {
left: 0;
top: -2px;
position: absolute;
content: "";
background: ${props => props.theme.colors.primary};
border-radius: 4px 0 0 4px;
width: 4px;
height: 100%;
}
}
}
}
&& .${Classes.POPOVER} {
width: 100%;
border-radius: ${props => props.theme.radii[1]}px;
box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14);
padding: ${props => props.theme.spaces[3]}px;
background: white;
}
&&&& .${Classes.POPOVER_CONTENT} {
box-shadow: none;
}
&& .${Classes.POPOVER_WRAPPER} {
.${Classes.OVERLAY} {
.${Classes.TRANSITION_CONTAINER} {
width: 100%;
}
}
}
&& .${Classes.MENU} {
max-width: 100%;
max-height: auto;
}
width: 100%;
`;
const DropDown = Select.ofType<DropdownOption>(); const DropDown = Select.ofType<DropdownOption>();
export const StyledDropDown = styled(DropDown)` export const StyledDropDown = styled(DropDown)`
&&& button { div {
background: ${props => props.theme.colors.paneInputBG}; flex: 1 1 auto;
color: ${props => props.theme.colors.textOnDarkBG}; }
box-shadow: none; span {
width: 100%;
position: relative;
}
.${Classes.BUTTON} {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
}
.${Classes.BUTTON_TEXT} {
text-overflow: ellipsis;
text-align: left;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
&& {
.${Classes.ICON} {
width: fit-content;
color: ${Colors.SLATE_GRAY};
}
} }
`; `;

View File

@ -1,17 +1,30 @@
import { AlertType, MessageIntent } from "widgets/AlertWidget"; export type ExecuteActionPayload = {
import { DropdownOption } from "widgets/DropdownWidget"; dynamicString: string;
event: {
type: EventType;
};
responseData?: any;
};
export type EventType = export enum EventType {
| "ON_CLICK" ON_PAGE_LOAD = "ON_PAGE_LOAD",
| "ON_HOVER" ON_PREV_PAGE = "ON_PREV_PAGE",
| "ON_TOGGLE" ON_NEXT_PAGE = "ON_NEXT_PAGE",
| "ON_LOAD" ON_ERROR = "ON_ERROR",
| "ON_TEXT_CHANGE" ON_SUCCESS = "ON_SUCCESS",
| "ON_SUBMIT" ON_ROW_SELECTED = "ON_ROW_SELECTED",
| "ON_CHECK_CHANGE" ON_CLICK = "ON_CLICK",
| "ON_SELECT" ON_HOVER = "ON_HOVER",
| "ON_DATE_SELECTED" ON_TOGGLE = "ON_TOGGLE",
| "ON_DATE_RANGE_SELECTED"; ON_LOAD = "ON_LOAD",
ON_TEXT_CHANGE = "ON_TEXT_CHANGE",
ON_SUBMIT = "ON_SUBMIT",
ON_CHECK_CHANGE = "ON_CHECK_CHANGE",
ON_SELECT = "ON_SELECT",
ON_DATE_SELECTED = "ON_DATE_SELECTED",
ON_DATE_RANGE_SELECTED = "ON_DATE_RANGE_SELECTED",
ON_OPTION_CHANGE = "ON_OPTION_CHANGE",
}
export type ActionType = export type ActionType =
| "API" | "API"
@ -22,60 +35,8 @@ export type ActionType =
| "SET_VALUE" | "SET_VALUE"
| "DOWNLOAD"; | "DOWNLOAD";
export const PropertyPaneActionDropdownOptions: DropdownOption[] = [
{ label: "Call API", value: "API" },
// { label: "Run Query", value: "QUERY" },
];
export interface BaseActionPayload {
actionId: string;
actionType: ActionType;
contextParams: Record<string, string>;
onSuccess?: ActionPayload[];
onError?: ActionPayload[];
}
export type ActionPayload =
| NavigateActionPayload
| SetValueActionPayload
| ExecuteJSActionPayload
| DownloadDataActionPayload
| TableAction;
export type NavigationType = "NEW_TAB" | "INLINE";
export interface NavigateActionPayload extends BaseActionPayload {
pageUrl: string;
navigationType: NavigationType;
}
export interface ShowAlertActionPayload extends BaseActionPayload {
header: string;
message: string;
alertType: AlertType;
intent: MessageIntent;
}
export interface SetValueActionPayload extends BaseActionPayload {
header: string;
message: string;
alertType: AlertType;
intent: MessageIntent;
}
export interface ExecuteJSActionPayload extends BaseActionPayload {
jsFunctionId: string;
jsFunction: string;
}
export type DownloadFiletype = "CSV" | "XLS" | "JSON" | "TXT"; export type DownloadFiletype = "CSV" | "XLS" | "JSON" | "TXT";
export interface DownloadDataActionPayload extends BaseActionPayload {
data: JSON;
fileName: string;
fileType: DownloadFiletype;
}
export interface PageAction { export interface PageAction {
id: string; id: string;
pluginType: ActionType; pluginType: ActionType;
@ -83,10 +44,6 @@ export interface PageAction {
jsonPathKeys: string[]; jsonPathKeys: string[];
} }
export interface TableAction extends BaseActionPayload {
actionName: string;
}
export interface ExecuteErrorPayload { export interface ExecuteErrorPayload {
actionId: string; actionId: string;
error: any; error: any;

View File

@ -35,6 +35,9 @@ export const Colors: Record<string, string> = {
JAFFA: "#F2994A", JAFFA: "#F2994A",
BLUE_BAYOUX: "#4E5D78", BLUE_BAYOUX: "#4E5D78",
MINT_TULIP: "#B5F1F1", MINT_TULIP: "#B5F1F1",
AZURE_RADIANCE: "#0384FE",
OCEAN_GREEN: "#36AB80",
BUTTER_CUP: "#F7AF22",
}; };
export type Color = typeof Colors[keyof typeof Colors]; export type Color = typeof Colors[keyof typeof Colors];

View File

@ -260,6 +260,7 @@ export type Theme = {
}; };
}; };
pageContentWidth: number; pageContentWidth: number;
alert: Record<string, { color: Color }>;
}; };
export const getColorWithOpacity = (color: Color, opacity: number) => { export const getColorWithOpacity = (color: Color, opacity: number) => {
@ -398,6 +399,20 @@ export const theme: Theme = {
}, },
}, },
pageContentWidth: 1224, pageContentWidth: 1224,
alert: {
info: {
color: Colors.AZURE_RADIANCE,
},
success: {
color: Colors.OCEAN_GREEN,
},
error: {
color: Colors.RED,
},
warning: {
color: Colors.BUTTER_CUP,
},
},
}; };
export { css, createGlobalStyle, keyframes, ThemeProvider }; export { css, createGlobalStyle, keyframes, ThemeProvider };

View File

@ -140,6 +140,8 @@ export const ReduxActionTypes: { [key: string]: string } = {
ADD_USER_TO_ORG_INIT: "ADD_USER_TO_ORG_INIT", ADD_USER_TO_ORG_INIT: "ADD_USER_TO_ORG_INIT",
ADD_USER_TO_ORG_SUCCESS: "ADD_USER_TO_ORG_ERROR", ADD_USER_TO_ORG_SUCCESS: "ADD_USER_TO_ORG_ERROR",
SET_META_PROP: "SET_META_PROP", SET_META_PROP: "SET_META_PROP",
EXECUTE_API_ACTION_REQUEST: "EXECUTE_API_ACTION_REQUEST",
EXECUTE_API_ACTION_SUCCESS: "EXECUTE_API_ACTION_SUCCESS",
}; };
export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes]; export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];
@ -200,6 +202,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
SET_DEFAULT_APPLICATION_PAGE_ERROR: "SET_DEFAULT_APPLICATION_PAGE_ERROR", SET_DEFAULT_APPLICATION_PAGE_ERROR: "SET_DEFAULT_APPLICATION_PAGE_ERROR",
CREATE_ORGANIZATION_ERROR: "CREATE_ORGANIZATION_ERROR", CREATE_ORGANIZATION_ERROR: "CREATE_ORGANIZATION_ERROR",
ADD_USER_TO_ORG_ERROR: "ADD_USER_TO_ORG_ERROR", ADD_USER_TO_ORG_ERROR: "ADD_USER_TO_ORG_ERROR",
EXECUTE_API_ACTION_ERROR: "EXECUTE_API_ACTION_ERROR",
}; };
export const ReduxFormActionTypes: { [key: string]: string } = { export const ReduxFormActionTypes: { [key: string]: string } = {

View File

@ -1,2 +0,0 @@
export const ENTITY_TYPE_ACTION = "ACTION";
export const ENTITY_TYPE_WIDGET = "WIDGET";

View File

@ -0,0 +1,94 @@
import { ActionData } from "reducers/entityReducers/actionsReducer";
import { WidgetProps } from "widgets/BaseWidget";
import { AppState } from "reducers";
import { ActionResponse } from "api/ActionAPI";
export type ActionDescription<T> = {
type: string;
payload: T;
};
type ActionDispatcher<T, A extends string[]> = (
...args: A
) => ActionDescription<T>;
export enum ENTITY_TYPE {
ACTION = "ACTION",
WIDGET = "WIDGET",
}
export type RunActionPayload = {
actionId: string;
onSuccess: string;
onError: string;
};
export interface DataTreeAction extends Omit<ActionData, "data"> {
data: ActionResponse["body"];
run: ActionDispatcher<RunActionPayload, [string, string]>;
ENTITY_TYPE: ENTITY_TYPE.ACTION;
}
export interface DataTreeWidget extends WidgetProps {
ENTITY_TYPE: ENTITY_TYPE.WIDGET;
}
export type DataTree = {
[key: string]: DataTreeAction | DataTreeWidget | ActionDispatcher<any, any>;
} & { actionPaths?: string[] };
export class DataTreeFactory {
static create(state: AppState): DataTree {
const dataTree: DataTree = {};
dataTree.actionPaths = ["navigateTo", "navigateToUrl", "showAlert"];
state.entities.actions.forEach(a => {
dataTree[a.config.name] = {
...a,
data: a.data ? a.data.body : {},
run: function(onSuccess: string, onError: string) {
return {
type: "RUN_ACTION",
payload: {
actionId: this.config.id,
onSuccess: onSuccess ? `{{${onSuccess.toString()}}}` : "",
onError: onError ? `{{${onError.toString()}}}` : "",
},
};
},
ENTITY_TYPE: ENTITY_TYPE.ACTION,
};
dataTree.actionPaths && dataTree.actionPaths.push(`${a.config.name}.run`);
});
Object.keys(state.entities.canvasWidgets).forEach(w => {
const widget = state.entities.canvasWidgets[w];
const widgetMetaProps = state.entities.meta[w];
dataTree[widget.widgetName] = {
...widget,
...widgetMetaProps,
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
};
});
dataTree.navigateTo = function(pageName: string) {
return {
type: "NAVIGATE_TO",
payload: { pageName },
};
};
dataTree.navigateToUrl = function(url: string) {
return {
type: "NAVIGATE_TO_URL",
payload: { url },
};
};
dataTree.showAlert = function(message: string, style: string) {
return {
type: "SHOW_ALERT",
payload: { message, style },
};
};
return dataTree;
}
}

View File

@ -0,0 +1,35 @@
import React from "react";
import { IconProps, IconWrapper } from "constants/IconConstants";
import { ReactComponent as InfoIcon } from "assets/icons/alert/info.svg";
import { ReactComponent as SuccessIcon } from "assets/icons/alert/success.svg";
import { ReactComponent as ErrorIcon } from "assets/icons/alert/error.svg";
import { ReactComponent as WarningIcon } from "assets/icons/alert/warning.svg";
/* eslint-disable react/display-name */
export const AlertIcons: {
[id: string]: Function;
} = {
INFO: (props: IconProps) => (
<IconWrapper {...props}>
<InfoIcon />
</IconWrapper>
),
SUCCESS: (props: IconProps) => (
<IconWrapper {...props}>
<SuccessIcon />
</IconWrapper>
),
ERROR: (props: IconProps) => (
<IconWrapper {...props}>
<ErrorIcon />
</IconWrapper>
),
WARNING: (props: IconProps) => (
<IconWrapper {...props}>
<WarningIcon />
</IconWrapper>
),
};
export type AlertIconName = keyof typeof AlertIcons;

View File

@ -121,4 +121,12 @@ div.bp3-popover-arrow {
.display-none { .display-none {
display: none; display: none;
} }
.Toastify__toast {
padding: 0 !important;
border-radius: 4px !important;
}
.Toastify__toast-body {
margin: 0 !important;
}

View File

@ -13,6 +13,7 @@ import TouchBackend from "react-dnd-touch-backend";
import { appInitializer } from "utils/AppsmithUtils"; import { appInitializer } from "utils/AppsmithUtils";
import ProtectedRoute from "./pages/common/ProtectedRoute"; import ProtectedRoute from "./pages/common/ProtectedRoute";
import { Slide, ToastContainer } from "react-toastify";
import store from "./store"; import store from "./store";
import { import {
BASE_URL, BASE_URL,
@ -49,6 +50,13 @@ ReactDOM.render(
> >
<Provider store={store}> <Provider store={store}>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<ToastContainer
hideProgressBar
draggable={false}
transition={Slide}
autoClose={5000}
closeButton={false}
/>
<Helmet> <Helmet>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
<link rel="shortcut icon" href="/favicon-orange.ico" /> <link rel="shortcut icon" href="/favicon-orange.ico" />

View File

@ -1,9 +1,18 @@
import RealmExecutor from "./RealmExecutor"; import RealmExecutor from "./RealmExecutor";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { ActionDescription } from "entities/DataTree/dataTreeFactory";
export type JSExecutorGlobal = Record<string, object>; export type JSExecutorGlobal = Record<string, object>;
export type JSExecutorResult = {
result: any;
triggers?: ActionDescription<any>[];
};
export interface JSExecutor { export interface JSExecutor {
execute: (src: string, data: JSExecutorGlobal) => any; execute: (
src: string,
data: JSExecutorGlobal,
callbackData?: any,
) => JSExecutorResult;
registerLibrary: (accessor: string, lib: any) => void; registerLibrary: (accessor: string, lib: any) => void;
unRegisterLibrary: (accessor: string) => void; unRegisterLibrary: (accessor: string) => void;
} }
@ -56,8 +65,12 @@ class JSExecutionManager {
this.registerLibrary(config.accessor, config.lib); this.registerLibrary(config.accessor, config.lib);
}); });
} }
evaluateSync(jsSrc: string, data: JSExecutorGlobal) { evaluateSync(
return this.currentExecutor.execute(jsSrc, data); jsSrc: string,
data: JSExecutorGlobal,
callbackData?: any,
): JSExecutorResult {
return this.currentExecutor.execute(jsSrc, data, callbackData);
} }
} }
const JSExecutionManagerSingleton = new JSExecutionManager(); const JSExecutionManagerSingleton = new JSExecutionManager();

View File

@ -1,4 +1,9 @@
import { JSExecutorGlobal, JSExecutor } from "./JSExecutionManagerSingleton"; import {
JSExecutorGlobal,
JSExecutor,
JSExecutorResult,
} from "./JSExecutionManagerSingleton";
import JSONFn from "json-fn";
declare let Realm: any; declare let Realm: any;
export default class RealmExecutor implements JSExecutor { export default class RealmExecutor implements JSExecutor {
@ -10,19 +15,40 @@ export default class RealmExecutor implements JSExecutor {
libraries: Record<string, any> = {}; libraries: Record<string, any> = {};
constructor() { constructor() {
this.rootRealm = Realm.makeRootRealm(); this.rootRealm = Realm.makeRootRealm();
this.registerLibrary("JSONFn", JSONFn);
this.createSafeFunction = this.rootRealm.evaluate(` this.createSafeFunction = this.rootRealm.evaluate(`
(function createSafeFunction(unsafeFn) { (function createSafeFunction(unsafeFn) {
return function safeFn(...args) { return function safeFn(...args) {
unsafeFn(...args); return unsafeFn(...args);
} }
}) })
`); `);
this.createSafeObject = this.rootRealm.evaluate(` // After parsing the data we add a triggers list on the global scope to
(function creaetSafeObject(unsafeObject) { // push to it during any script execution
return JSON.parse(JSON.stringify(unsafeObject)); // We replace all action descriptor functions with our pusher function
// which has reference to the triggers via binding
this.createSafeObject = this.rootRealm.evaluate(
`
(function createSafeObject(unsafeObject) {
const safeObject = JSONFn.parse(JSONFn.stringify(unsafeObject));
if(safeObject.actionPaths) {
safeObject.triggers = [];
const pusher = function (action, ...payload) {
const actionPayload = action(...payload);
this.triggers.push(actionPayload);
}
safeObject.actionPaths.forEach(path => {
const action = _.get(safeObject, path);
const entity = _.get(safeObject, path.split(".")[0])
_.set(safeObject, path, pusher.bind(safeObject, action.bind(entity)))
})
}
return safeObject
}) })
`); `,
);
} }
registerLibrary(accessor: string, lib: any) { registerLibrary(accessor: string, lib: any) {
this.rootRealm.global[accessor] = lib; this.rootRealm.global[accessor] = lib;
} }
@ -38,14 +64,46 @@ export default class RealmExecutor implements JSExecutor {
} }
return result; return result;
} }
execute(sourceText: string, data: JSExecutorGlobal) { execute(
sourceText: string,
data: JSExecutorGlobal,
callbackData?: any,
): JSExecutorResult {
const safeCallbackData = this.createSafeObject(callbackData || {});
const safeData = this.createSafeObject(data); const safeData = this.createSafeObject(data);
let result;
try { try {
result = this.rootRealm.evaluate(sourceText, safeData); // We create a closed function and evaluate that
// This is to send any triggers received during evaluations
// triggers should already be defined in the safeData
const scriptToEvaluate = `
function closedFunction () {
const result = ${sourceText};
return { result, triggers }
}
closedFunction()
`;
const scriptWithCallback = `
function callback (script) {
const userFunction = script;
const result = userFunction(CALLBACK_DATA);
return { result, triggers };
}
callback(${sourceText});
`;
const script = callbackData ? scriptWithCallback : scriptToEvaluate;
const data = callbackData
? { ...safeData, CALLBACK_DATA: safeCallbackData }
: safeData;
const { result, triggers } = this.rootRealm.evaluate(script, data);
return {
result: this.convertToMainScope(result),
triggers,
};
} catch (e) { } catch (e) {
console.error(`Error: "${e.message}" when evaluating {{${sourceText}}}`); console.error(`Error: "${e.message}" when evaluating {{${sourceText}}}`);
return { result: undefined, triggers: [] };
} }
return this.convertToMainScope(result);
} }
} }

View File

@ -6,6 +6,7 @@ import { Switch, Route } from "react-router-dom";
import { AppState } from "reducers"; import { AppState } from "reducers";
import { import {
AppViewerRouteParams, AppViewerRouteParams,
BuilderRouteParams,
getApplicationViewerPageURL, getApplicationViewerPageURL,
} from "constants/routes"; } from "constants/routes";
import { import {
@ -18,7 +19,7 @@ import {
getIsInitialized, getIsInitialized,
} from "selectors/appViewSelectors"; } from "selectors/appViewSelectors";
import { executeAction } from "actions/widgetActions"; import { executeAction } from "actions/widgetActions";
import { ActionPayload } from "constants/ActionConstants"; import { ExecuteActionPayload } from "constants/ActionConstants";
import SideNav from "./viewer/SideNav"; import SideNav from "./viewer/SideNav";
import { SideNavItemProps } from "./viewer/SideNavItem"; import { SideNavItemProps } from "./viewer/SideNavItem";
import AppViewerHeader from "./viewer/AppViewerHeader"; import AppViewerHeader from "./viewer/AppViewerHeader";
@ -27,7 +28,6 @@ import { RenderModes } from "constants/WidgetConstants";
import { EditorContext } from "components/editorComponents/EditorContextProvider"; import { EditorContext } from "components/editorComponents/EditorContextProvider";
import AppViewerPageContainer from "./AppViewerPageContainer"; import AppViewerPageContainer from "./AppViewerPageContainer";
import AppViewerSideNavWrapper from "./viewer/AppViewerSideNavWrapper"; import AppViewerSideNavWrapper from "./viewer/AppViewerSideNavWrapper";
import { PaginationField } from "api/ActionAPI";
import { updateWidgetMetaProperty } from "actions/metaActions"; import { updateWidgetMetaProperty } from "actions/metaActions";
const AppViewWrapper = styled.div` const AppViewWrapper = styled.div`
@ -48,7 +48,7 @@ export type AppViewerProps = {
pages?: PageListPayload; pages?: PageListPayload;
initializeAppViewer: Function; initializeAppViewer: Function;
isInitialized: boolean; isInitialized: boolean;
executeAction: (actionPayloads: ActionPayload[]) => void; executeAction: (actionPayload: ExecuteActionPayload) => void;
updateWidgetProperty: ( updateWidgetProperty: (
widgetId: string, widgetId: string,
propertyName: string, propertyName: string,
@ -59,7 +59,7 @@ export type AppViewerProps = {
propertyName: string, propertyName: string,
propertyValue: any, propertyValue: any,
) => void; ) => void;
}; } & RouteComponentProps<BuilderRouteParams>;
class AppViewer extends Component< class AppViewer extends Component<
AppViewerProps & RouteComponentProps<AppViewerRouteParams> AppViewerProps & RouteComponentProps<AppViewerRouteParams>
@ -70,6 +70,7 @@ class AppViewer extends Component<
this.props.initializeAppViewer(applicationId); this.props.initializeAppViewer(applicationId);
} }
} }
public render() { public render() {
const { isInitialized } = this.props; const { isInitialized } = this.props;
if (!isInitialized) return null; if (!isInitialized) return null;
@ -121,10 +122,8 @@ const mapStateToProps = (state: AppState) => ({
}); });
const mapDispatchToProps = (dispatch: any) => ({ const mapDispatchToProps = (dispatch: any) => ({
executeAction: ( executeAction: (actionPayload: ExecuteActionPayload) =>
actionPayloads: ActionPayload[], dispatch(executeAction(actionPayload)),
paginationField?: PaginationField,
) => dispatch(executeAction(actionPayloads, paginationField)),
updateWidgetProperty: ( updateWidgetProperty: (
widgetId: string, widgetId: string,
propertyName: string, propertyName: string,

View File

@ -4,11 +4,10 @@ import {
ReduxAction, ReduxAction,
ReduxActionErrorTypes, ReduxActionErrorTypes,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { ActionResponse, RestAction, PaginationField } from "api/ActionAPI"; import { ActionResponse, RestAction } from "api/ActionAPI";
import { ActionPayload, ExecuteErrorPayload } from "constants/ActionConstants"; import { ExecuteErrorPayload } from "constants/ActionConstants";
import _ from "lodash";
interface ActionData { export interface ActionData {
isLoading: boolean; isLoading: boolean;
config: RestAction; config: RestAction;
data?: ActionResponse; data?: ActionResponse;
@ -67,15 +66,12 @@ const actionsReducer = createReducer(initialState, {
state: ActionDataState, state: ActionDataState,
action: ReduxAction<{ id: string }>, action: ReduxAction<{ id: string }>,
): ActionDataState => state.filter(a => a.config.id !== action.payload.id), ): ActionDataState => state.filter(a => a.config.id !== action.payload.id),
[ReduxActionTypes.EXECUTE_ACTION]: ( [ReduxActionTypes.EXECUTE_API_ACTION_REQUEST]: (
state: ActionDataState, state: ActionDataState,
action: ReduxAction<{ action: ReduxAction<{ id: string }>,
actions: ActionPayload[];
paginationField: PaginationField;
}>,
): ActionDataState => ): ActionDataState =>
state.map(a => { state.map(a => {
if (_.find(action.payload.actions, { actionId: a.config.id })) { if (a.config.id === action.payload.id) {
return { return {
...a, ...a,
isLoading: true, isLoading: true,
@ -83,14 +79,13 @@ const actionsReducer = createReducer(initialState, {
} }
return a; return a;
}), }),
[ReduxActionTypes.EXECUTE_ACTION_SUCCESS]: ( [ReduxActionTypes.EXECUTE_API_ACTION_SUCCESS]: (
state: ActionDataState, state: ActionDataState,
action: ReduxAction<{ [id: string]: ActionResponse }>, action: ReduxAction<{ id: string; response: ActionResponse }>,
): ActionDataState => { ): ActionDataState => {
const actionId = Object.keys(action.payload)[0];
return state.map(a => { return state.map(a => {
if (a.config.id === actionId) { if (a.config.id === action.payload.id) {
return { ...a, isLoading: false, data: action.payload[actionId] }; return { ...a, isLoading: false, data: action.payload.response };
} }
return a; return a;
}); });

View File

@ -38,6 +38,7 @@ const canvasWidgetsReducer = createReducer(initialState, {
...widget, ...widget,
[action.payload.propertyName]: action.payload.propertyValue, [action.payload.propertyName]: action.payload.propertyValue,
dynamicBindings: action.payload.dynamicBindings, dynamicBindings: action.payload.dynamicBindings,
dynamicTriggers: action.payload.dynamicTriggers,
}, },
}; };
}, },

View File

@ -57,5 +57,3 @@ export interface AppState {
meta: MetaState; meta: MetaState;
}; };
} }
export type DataTree = AppState["entities"];

View File

@ -3,29 +3,27 @@ import {
ReduxActionErrorTypes, ReduxActionErrorTypes,
ReduxActionTypes, ReduxActionTypes,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { Intent } from "@blueprintjs/core";
import { import {
all, all,
call, call,
select,
put, put,
select,
takeEvery, takeEvery,
takeLatest, takeLatest,
take,
} from "redux-saga/effects"; } from "redux-saga/effects";
import { import {
ActionPayload, EventType,
ExecuteActionPayload,
PageAction, PageAction,
ExecuteJSActionPayload,
} from "constants/ActionConstants"; } from "constants/ActionConstants";
import ActionAPI, { import ActionAPI, {
ActionApiResponse, ActionApiResponse,
ActionCreateUpdateResponse, ActionCreateUpdateResponse,
ActionResponse, ActionResponse,
ExecuteActionRequest, ExecuteActionRequest,
PaginationField,
Property, Property,
RestAction, RestAction,
PaginationField,
} from "api/ActionAPI"; } from "api/ActionAPI";
import { AppState } from "reducers"; import { AppState } from "reducers";
import _ from "lodash"; import _ from "lodash";
@ -37,6 +35,8 @@ import {
copyActionSuccess, copyActionSuccess,
createActionSuccess, createActionSuccess,
deleteActionSuccess, deleteActionSuccess,
executeApiActionRequest,
executeApiActionSuccess,
FetchActionsPayload, FetchActionsPayload,
moveActionError, moveActionError,
moveActionSuccess, moveActionSuccess,
@ -50,17 +50,27 @@ import {
removeBindingsFromObject, removeBindingsFromObject,
} from "utils/DynamicBindingUtils"; } from "utils/DynamicBindingUtils";
import { validateResponse } from "./ErrorSagas"; import { validateResponse } from "./ErrorSagas";
import {
ERROR_MESSAGE_SELECT_ACTION,
ERROR_MESSAGE_SELECT_ACTION_TYPE,
} from "constants/messages";
import { getFormData } from "selectors/formSelectors"; import { getFormData } from "selectors/formSelectors";
import { API_EDITOR_FORM_NAME } from "constants/forms"; import { API_EDITOR_FORM_NAME } from "constants/forms";
import { executeAction, executeActionError } from "actions/widgetActions"; import { executeAction, executeActionError } from "actions/widgetActions";
import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton"; import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton";
import { getParsedDataTree } from "selectors/nameBindingsWithDataSelector"; import { getParsedDataTree } from "selectors/dataTreeSelectors";
import { transformRestAction } from "transformers/RestActionTransformer"; import { transformRestAction } from "transformers/RestActionTransformer";
import { getActionResponses } from "selectors/entitiesSelector"; import { getActionResponses } from "selectors/entitiesSelector";
import {
ActionDescription,
RunActionPayload,
} from "entities/DataTree/dataTreeFactory";
import {
getCurrentApplicationId,
getPageList,
} from "selectors/editorSelectors";
import history from "utils/history";
import {
BUILDER_PAGE_URL,
getApplicationViewerPageURL,
} from "constants/routes";
import { ToastType } from "react-toastify";
export const getAction = ( export const getAction = (
state: AppState, state: AppState,
@ -89,7 +99,8 @@ const createActionErrorResponse = (
export function* evaluateDynamicBoundValueSaga(path: string): any { export function* evaluateDynamicBoundValueSaga(path: string): any {
const tree = yield select(getParsedDataTree); const tree = yield select(getParsedDataTree);
return getDynamicValue(`{{${path}}}`, tree); const dynamicResult = getDynamicValue(`{{${path}}}`, tree);
return dynamicResult.result;
} }
export function* getActionParams(jsonPathKeys: string[] | undefined) { export function* getActionParams(jsonPathKeys: string[] | undefined) {
@ -110,41 +121,40 @@ export function* getActionParams(jsonPathKeys: string[] | undefined) {
return mapToPropList(dynamicBindings); return mapToPropList(dynamicBindings);
} }
function* executeJSActionSaga(jsAction: ExecuteJSActionPayload) { // function* executeJSActionSaga(jsAction: ExecuteJSActionPayload) {
const tree = yield select(getParsedDataTree); // const tree = yield select(getParsedDataTree);
const result = JSExecutionManagerSingleton.evaluateSync( // const result = JSExecutionManagerSingleton.evaluateSync(
jsAction.jsFunction, // jsAction.jsFunction,
tree, // tree,
); // );
//
yield put({ // yield put({
type: ReduxActionTypes.SAVE_JS_EXECUTION_RECORD, // type: ReduxActionTypes.SAVE_JS_EXECUTION_RECORD,
payload: { // payload: {
[jsAction.jsFunctionId]: result, // [jsAction.jsFunctionId]: result,
}, // },
}); // });
} // }
export function* executeAPIQueryActionSaga( export function* executeAPIQueryActionSaga(
apiAction: ActionPayload, apiAction: RunActionPayload,
paginationField: PaginationField, event: EventType,
) { ) {
const { actionId, onSuccess, onError } = apiAction;
try { try {
const api: PageAction = yield select(getAction, apiAction.actionId); yield put(executeApiActionRequest({ id: apiAction.actionId }));
if (!api) { const api: RestAction = yield select(getAction, actionId);
yield put(
executeActionError({
actionId: apiAction.actionId,
error: "No action selected",
}),
);
return;
}
const params: Property[] = yield call(getActionParams, api.jsonPathKeys); const params: Property[] = yield call(getActionParams, api.jsonPathKeys);
const pagination =
event === EventType.ON_NEXT_PAGE
? "NEXT"
: event === EventType.ON_PREV_PAGE
? "PREV"
: undefined;
const executeActionRequest: ExecuteActionRequest = { const executeActionRequest: ExecuteActionRequest = {
action: { id: apiAction.actionId }, action: { id: actionId },
params, params,
paginationField: paginationField, paginationField: pagination,
}; };
const response: ActionApiResponse = yield ActionAPI.executeAction( const response: ActionApiResponse = yield ActionAPI.executeAction(
executeActionRequest, executeActionRequest,
@ -152,119 +162,113 @@ export function* executeAPIQueryActionSaga(
let payload = createActionResponse(response); let payload = createActionResponse(response);
if (response.responseMeta && response.responseMeta.error) { if (response.responseMeta && response.responseMeta.error) {
payload = createActionErrorResponse(response); payload = createActionErrorResponse(response);
if (apiAction.onError) { if (onError) {
yield put({ yield put(
type: ReduxActionTypes.EXECUTE_ACTION, executeAction({
payload: { dynamicString: onError,
actions: apiAction.onError, event: {
}, type: EventType.ON_ERROR,
}); },
responseData: payload,
}),
);
} }
yield put( yield put(
executeActionError({ executeActionError({
actionId: apiAction.actionId, actionId,
error: response.responseMeta.error, error: response.responseMeta.error,
}), }),
); );
} else { } else {
if (apiAction.onSuccess) { yield put(
yield put({ executeApiActionSuccess({ id: apiAction.actionId, response: payload }),
type: ReduxActionTypes.EXECUTE_ACTION, );
payload: { if (onSuccess) {
actions: apiAction.onSuccess, yield put(
}, executeAction({
}); dynamicString: onSuccess,
event: {
type: EventType.ON_SUCCESS,
},
responseData: payload,
}),
);
} }
yield put({
type: ReduxActionTypes.EXECUTE_ACTION_SUCCESS,
payload: { [apiAction.actionId]: payload },
});
} }
return response; return response;
} catch (error) { } catch (error) {
yield put( yield put(
executeActionError({ executeActionError({
actionId: apiAction.actionId, actionId: actionId,
error, error,
}), }),
); );
if (onError) {
yield put(
executeAction({
dynamicString: `{{${onError}}}`,
event: {
type: EventType.ON_ERROR,
},
responseData: {},
}),
);
}
} }
} }
function validateActionPayload(actionPayload: ActionPayload) { function* navigateActionSaga(action: { pageName: string }, event: EventType) {
const validation = { const pageList = yield select(getPageList);
isValid: true, const applicationId = yield select(getCurrentApplicationId);
messages: [] as string[], const page = _.find(pageList, { pageName: action.pageName });
}; if (page) {
// TODO need to make this check via RENDER_MODE;
const noActionId = actionPayload.actionId === undefined; const path = history.location.pathname.endsWith("/edit")
validation.isValid = validation.isValid && !noActionId; ? BUILDER_PAGE_URL(applicationId, page.pageId)
if (noActionId) { : getApplicationViewerPageURL(applicationId, page.pageId);
validation.messages.push(ERROR_MESSAGE_SELECT_ACTION); history.push(path);
} }
const noActionType = actionPayload.actionType === undefined;
validation.isValid = validation.isValid && !noActionType;
if (noActionType) {
validation.messages.push(ERROR_MESSAGE_SELECT_ACTION_TYPE);
}
return validation;
} }
export function* executeActionSaga( export function* executeActionTriggers(
actionPayloads: ActionPayload[], trigger: ActionDescription<any>,
paginationField: PaginationField, event: EventType,
): any {
yield all(
_.map(actionPayloads, (actionPayload: ActionPayload) => {
const actionValidation = validateActionPayload(actionPayload);
if (!actionValidation.isValid) {
console.error(actionValidation.messages.join(", "));
return undefined;
}
switch (actionPayload.actionType) {
case "API":
return call(
executeAPIQueryActionSaga,
actionPayload,
paginationField,
);
case "QUERY":
return call(
executeAPIQueryActionSaga,
actionPayload,
paginationField,
);
case "JS_FUNCTION":
return call(
executeJSActionSaga,
actionPayload as ExecuteJSActionPayload,
);
default:
return undefined;
}
}),
);
}
export function* executeReduxActionSaga(
action: ReduxAction<{
actions: ActionPayload[];
paginationField: PaginationField;
}>,
) { ) {
if (!_.isNil(action.payload)) { switch (trigger.type) {
yield* executeActionSaga( case "RUN_ACTION":
action.payload.actions, yield call(executeAPIQueryActionSaga, trigger.payload, event);
action.payload.paginationField, break;
); case "NAVIGATE_TO":
} else { yield call(navigateActionSaga, trigger.payload, event);
yield put( break;
executeActionError({ case "NAVIGATE_TO_URL":
actionId: "", if (trigger.payload.url) {
error: "No action payload", window.location.href = trigger.payload.url;
}), }
break;
case "SHOW_ALERT":
AppToaster.show({
message: trigger.payload.message,
type: trigger.payload.style,
});
break;
default:
yield put(
executeActionError({
error: "Trigger type unknown",
actionId: "",
}),
);
}
}
export function* executeAppAction(action: ReduxAction<ExecuteActionPayload>) {
const { dynamicString, event, responseData } = action.payload;
const tree = yield select(getParsedDataTree);
const { triggers } = getDynamicValue(dynamicString, tree, responseData, true);
if (triggers) {
yield all(
triggers.map(trigger => call(executeActionTriggers, trigger, event.type)),
); );
} }
} }
@ -278,7 +282,7 @@ export function* createActionSaga(actionPayload: ReduxAction<RestAction>) {
if (isValidResponse) { if (isValidResponse) {
AppToaster.show({ AppToaster.show({
message: `${actionPayload.payload.name} Action created`, message: `${actionPayload.payload.name} Action created`,
intent: Intent.SUCCESS, type: ToastType.SUCCESS,
}); });
yield put(createActionSuccess(response.data)); yield put(createActionSuccess(response.data));
} }
@ -324,7 +328,7 @@ export function* updateActionSaga(
if (isValidResponse) { if (isValidResponse) {
AppToaster.show({ AppToaster.show({
message: `${actionPayload.payload.data.name} Action updated`, message: `${actionPayload.payload.data.name} Action updated`,
intent: Intent.SUCCESS, type: ToastType.SUCCESS,
}); });
yield put(updateActionSuccess({ data: response.data })); yield put(updateActionSuccess({ data: response.data }));
yield put(runApiAction(data.id)); yield put(runApiAction(data.id));
@ -347,7 +351,7 @@ export function* deleteActionSaga(actionPayload: ReduxAction<{ id: string }>) {
if (isValidResponse) { if (isValidResponse) {
AppToaster.show({ AppToaster.show({
message: `${response.data.name} Action deleted`, message: `${response.data.name} Action deleted`,
intent: Intent.SUCCESS, type: ToastType.SUCCESS,
}); });
yield put(deleteActionSuccess({ id })); yield put(deleteActionSuccess({ id }));
} }
@ -421,12 +425,11 @@ export function* runApiActionSaga(
function* executePageLoadActionsSaga(action: ReduxAction<PageAction[][]>) { function* executePageLoadActionsSaga(action: ReduxAction<PageAction[][]>) {
const pageActions = action.payload; const pageActions = action.payload;
const actionPayloads: ActionPayload[][] = pageActions.map(actionSet => const actionPayloads: RunActionPayload[][] = pageActions.map(actionSet =>
actionSet.map(action => ({ actionSet.map(action => ({
actionId: action.id, actionId: action.id,
actionType: action.pluginType, onSuccess: "",
contextParams: {}, onError: "",
actionName: action.name,
})), })),
); );
for (const actionSet of actionPayloads) { for (const actionSet of actionPayloads) {
@ -434,11 +437,11 @@ function* executePageLoadActionsSaga(action: ReduxAction<PageAction[][]>) {
const filteredSet = actionSet.filter( const filteredSet = actionSet.filter(
action => !apiResponses[action.actionId], action => !apiResponses[action.actionId],
); );
yield put(executeAction(filteredSet)); yield* yield all(
/* eslint-disable no-empty-pattern */ filteredSet.map(a =>
for (const {} of filteredSet) { call(executeAPIQueryActionSaga, a, EventType.ON_PAGE_LOAD),
yield take(ReduxActionTypes.EXECUTE_ACTION_SUCCESS); ),
} );
} }
} }
@ -469,14 +472,14 @@ function* moveActionSaga(
if (isValidResponse) { if (isValidResponse) {
AppToaster.show({ AppToaster.show({
message: `${response.data.name} Action moved`, message: `${response.data.name} Action moved`,
intent: Intent.SUCCESS, type: ToastType.SUCCESS,
}); });
} }
yield put(moveActionSuccess(response.data)); yield put(moveActionSuccess(response.data));
} catch (e) { } catch (e) {
AppToaster.show({ AppToaster.show({
message: `Error while moving action ${actionObject.name}`, message: `Error while moving action ${actionObject.name}`,
intent: Intent.DANGER, type: ToastType.ERROR,
}); });
yield put( yield put(
moveActionError({ moveActionError({
@ -510,14 +513,14 @@ function* copyActionSaga(
if (isValidResponse) { if (isValidResponse) {
AppToaster.show({ AppToaster.show({
message: `${actionObject.name} Action copied`, message: `${actionObject.name} Action copied`,
intent: Intent.SUCCESS, type: ToastType.SUCCESS,
}); });
} }
yield put(copyActionSuccess(response.data)); yield put(copyActionSuccess(response.data));
} catch (e) { } catch (e) {
AppToaster.show({ AppToaster.show({
message: `Error while copying action ${actionObject.name}`, message: `Error while copying action ${actionObject.name}`,
intent: Intent.DANGER, type: ToastType.ERROR,
}); });
yield put(copyActionError(action.payload)); yield put(copyActionError(action.payload));
} }
@ -526,7 +529,7 @@ function* copyActionSaga(
export function* watchActionSagas() { export function* watchActionSagas() {
yield all([ yield all([
takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga), takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga),
takeLatest(ReduxActionTypes.EXECUTE_ACTION, executeReduxActionSaga), takeLatest(ReduxActionTypes.EXECUTE_ACTION, executeAppAction),
takeLatest(ReduxActionTypes.RUN_API_REQUEST, runApiActionSaga), takeLatest(ReduxActionTypes.RUN_API_REQUEST, runApiActionSaga),
takeLatest(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga), takeLatest(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga), takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),

View File

@ -1,5 +1,4 @@
import _ from "lodash"; import _ from "lodash";
import { Intent } from "@blueprintjs/core";
import { import {
ReduxActionTypes, ReduxActionTypes,
ReduxActionErrorTypes, ReduxActionErrorTypes,
@ -10,6 +9,7 @@ import { DEFAULT_ERROR_MESSAGE, DEFAULT_ACTION_ERROR } from "constants/errors";
import { ApiResponse } from "api/ApiResponses"; import { ApiResponse } from "api/ApiResponses";
import { put, takeLatest, call } from "redux-saga/effects"; import { put, takeLatest, call } from "redux-saga/effects";
import { ERROR_500 } from "constants/messages"; import { ERROR_500 } from "constants/messages";
import { ToastType } from "react-toastify";
export function* callAPI(apiCall: any, requestPayload: any) { export function* callAPI(apiCall: any, requestPayload: any) {
try { try {
@ -80,7 +80,7 @@ export function* errorSaga(
payload: { error, show = true }, payload: { error, show = true },
} = errorAction; } = errorAction;
const message = error.message || ActionErrorDisplayMap[type](error); const message = error.message || ActionErrorDisplayMap[type](error);
if (show) AppToaster.show({ message, intent: Intent.DANGER }); if (show) AppToaster.show({ message, type: ToastType.ERROR });
yield put({ yield put({
type: ReduxActionTypes.REPORT_ERROR, type: ReduxActionTypes.REPORT_ERROR,
payload: { payload: {

View File

@ -22,6 +22,7 @@ import { isDynamicValue } from "utils/DynamicBindingUtils";
import { WidgetProps } from "widgets/BaseWidget"; import { WidgetProps } from "widgets/BaseWidget";
import _ from "lodash"; import _ from "lodash";
import { WidgetTypes } from "constants/WidgetConstants"; import { WidgetTypes } from "constants/WidgetConstants";
import WidgetFactory from "utils/WidgetFactory";
export function* addChildSaga(addChildAction: ReduxAction<WidgetAddChild>) { export function* addChildSaga(addChildAction: ReduxAction<WidgetAddChild>) {
try { try {
@ -184,15 +185,29 @@ function* updateWidgetPropertySaga(
const isDynamic = isDynamicValue(propertyValue); const isDynamic = isDynamicValue(propertyValue);
const widget: WidgetProps = yield select(getWidget, widgetId); const widget: WidgetProps = yield select(getWidget, widgetId);
let dynamicBindings: Record<string, boolean> = widget.dynamicBindings || {}; let dynamicBindings: Record<string, boolean> = widget.dynamicBindings || {};
if (!isDynamic && propertyName in dynamicBindings) { let dynamicTriggers: Record<string, true> = widget.dynamicTriggers || {};
dynamicBindings = _.omit(dynamicBindings, propertyName); const triggerProperties = WidgetFactory.getWidgetTriggerPropertiesMap(
} widget.type,
if (isDynamic && !(propertyName in dynamicBindings)) { );
dynamicBindings[propertyName] = true; if (propertyName in triggerProperties) {
if (propertyValue && !(propertyName in dynamicTriggers)) {
dynamicTriggers[propertyName] = true;
}
if (!propertyValue && propertyName in dynamicTriggers) {
dynamicTriggers = _.omit(dynamicTriggers, propertyName);
}
} else {
if (!isDynamic && propertyName in dynamicBindings) {
dynamicBindings = _.omit(dynamicBindings, propertyName);
}
if (isDynamic && !(propertyName in dynamicBindings)) {
dynamicBindings[propertyName] = true;
}
} }
yield put({ yield put({
type: ReduxActionTypes.UPDATE_WIDGET_PROPERTY, type: ReduxActionTypes.UPDATE_WIDGET_PROPERTY,
payload: { ...updateAction.payload, dynamicBindings }, payload: { ...updateAction.payload, dynamicBindings, dynamicTriggers },
}); });
} }

View File

@ -1,12 +1,11 @@
import { createSelector } from "reselect"; import { createSelector } from "reselect";
import { AppState, DataTree } from "reducers"; import { AppState } from "reducers";
import { AppViewReduxState } from "reducers/uiReducers/appViewReducer"; import { AppViewReduxState } from "reducers/uiReducers/appViewReducer";
import { PageListReduxState } from "reducers/entityReducers/pageListReducer"; import { PageListReduxState } from "reducers/entityReducers/pageListReducer";
import { getDataTree } from "./entitiesSelector"; import { getEntities } from "./entitiesSelector";
import createCachedSelector from "re-reselect"; import createCachedSelector from "re-reselect";
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer"; import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
import { getValidatedDynamicProps } from "./editorSelectors"; import { getValidatedWidgetsAndActionTriggers } from "./editorSelectors";
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
const getAppViewState = (state: AppState) => state.ui.appView; const getAppViewState = (state: AppState) => state.ui.appView;
const getPageListState = (state: AppState): PageListReduxState => const getPageListState = (state: AppState): PageListReduxState =>
@ -49,16 +48,11 @@ export const getPageWidgetId = createSelector(
); );
export const getCurrentPageLayoutDSL = createCachedSelector( export const getCurrentPageLayoutDSL = createCachedSelector(
getPageWidgetId, getPageWidgetId,
getDataTree, getEntities,
getValidatedDynamicProps, getValidatedWidgetsAndActionTriggers,
( (pageWidgetId: string, entities: AppState["entities"], widgets) => {
pageWidgetId: string,
entities: DataTree,
validatedDynamicWidgets: CanvasWidgetsReduxState,
) => {
return CanvasWidgetsNormalizer.denormalize(pageWidgetId, { return CanvasWidgetsNormalizer.denormalize(pageWidgetId, {
...entities, canvasWidgets: widgets,
canvasWidgets: validatedDynamicWidgets,
}); });
}, },
)((pageWidgetId, entities) => entities || 0); )((pageWidgetId, entities) => entities || 0);

View File

@ -0,0 +1,46 @@
import { AppState } from "reducers";
import { createSelector } from "reselect";
import { getActions } from "./entitiesSelector";
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
import createCachedSelector from "re-reselect";
import { getEvaluatedDataTree } from "utils/DynamicBindingUtils";
import { extraLibraries } from "jsExecution/JSExecutionManagerSingleton";
import { DataTree, DataTreeFactory } from "entities/DataTree/dataTreeFactory";
export const getUnevaluatedDataTree = (state: AppState): DataTree =>
DataTreeFactory.create(state);
export const getParsedDataTree = createSelector(
getUnevaluatedDataTree,
(dataTree: DataTree) => {
return getEvaluatedDataTree(dataTree, true);
},
);
// For autocomplete. Use actions cached responses if
// there isn't a response already
export const getDataTreeForAutocomplete = createCachedSelector(
getParsedDataTree,
getActions,
(dataTree: DataTree, actions: ActionDataState) => {
const cachedResponses: Record<string, any> = {};
if (actions && actions.length) {
actions.forEach(action => {
if (!(action.config.name in dataTree) && action.config.cacheResponse) {
try {
cachedResponses[action.config.name] = JSON.parse(
action.config.cacheResponse,
);
} catch (e) {
cachedResponses[action.config.name] = action.config.cacheResponse;
}
}
});
}
const libs: Record<string, any> = {};
extraLibraries.forEach(
config => (libs[config.accessor] = libs[config.accessor]),
);
return { ...dataTree, ...cachedResponses, ...libs };
},
)((state: AppState) => state.entities.actions.length);

View File

@ -1,13 +1,13 @@
import { createSelector } from "reselect"; import { createSelector } from "reselect";
import createCachedSelector from "re-reselect"; import createCachedSelector from "re-reselect";
import { AppState, DataTree } from "reducers"; import { AppState } from "reducers";
import { EditorReduxState } from "reducers/uiReducers/editorReducer"; import { EditorReduxState } from "reducers/uiReducers/editorReducer";
import { WidgetConfigReducerState } from "reducers/entityReducers/widgetConfigReducer"; import { WidgetConfigReducerState } from "reducers/entityReducers/widgetConfigReducer";
import { WidgetCardProps } from "widgets/BaseWidget"; import { WidgetCardProps } from "widgets/BaseWidget";
import { WidgetSidebarReduxState } from "reducers/uiReducers/widgetSidebarReducer"; import { WidgetSidebarReduxState } from "reducers/uiReducers/widgetSidebarReducer";
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer"; import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
import { getDataTree } from "./entitiesSelector"; import { getEntities } from "./entitiesSelector";
import { import {
FlattenedWidgetProps, FlattenedWidgetProps,
CanvasWidgetsReduxState, CanvasWidgetsReduxState,
@ -16,7 +16,7 @@ import { PageListReduxState } from "reducers/entityReducers/pageListReducer";
import { OccupiedSpace } from "constants/editorConstants"; import { OccupiedSpace } from "constants/editorConstants";
import { WidgetTypes } from "constants/WidgetConstants"; import { WidgetTypes } from "constants/WidgetConstants";
import { getParsedDataTree } from "./nameBindingsWithDataSelector"; import { getParsedDataTree } from "selectors/dataTreeSelectors";
import _ from "lodash"; import _ from "lodash";
const getEditorState = (state: AppState) => state.ui.editor; const getEditorState = (state: AppState) => state.ui.editor;
@ -116,10 +116,10 @@ export const getWidgetCards = createSelector(
}, },
); );
export const getValidatedDynamicProps = createSelector( export const getValidatedWidgetsAndActionTriggers = createSelector(
getDataTree, getEntities,
getParsedDataTree, getParsedDataTree,
(entities: DataTree, tree) => { (entities: AppState["entities"], tree) => {
const widgets = { ...entities.canvasWidgets }; const widgets = { ...entities.canvasWidgets };
Object.keys(widgets).forEach(widgetKey => { Object.keys(widgets).forEach(widgetKey => {
const evaluatedWidget = _.find(tree, { widgetId: widgetKey }); const evaluatedWidget = _.find(tree, { widgetId: widgetKey });
@ -140,7 +140,7 @@ export const getValidatedDynamicProps = createSelector(
export const getDenormalizedDSL = createCachedSelector( export const getDenormalizedDSL = createCachedSelector(
getPageWidgetId, getPageWidgetId,
getValidatedDynamicProps, getValidatedWidgetsAndActionTriggers,
(pageWidgetId: string, validatedDynamicWidgets: CanvasWidgetsReduxState) => { (pageWidgetId: string, validatedDynamicWidgets: CanvasWidgetsReduxState) => {
return CanvasWidgetsNormalizer.denormalize(pageWidgetId, { return CanvasWidgetsNormalizer.denormalize(pageWidgetId, {
canvasWidgets: validatedDynamicWidgets, canvasWidgets: validatedDynamicWidgets,

View File

@ -1,8 +1,9 @@
import { AppState, DataTree } from "reducers"; import { AppState } from "reducers";
import { ActionDataState } from "reducers/entityReducers/actionsReducer"; import { ActionDataState } from "reducers/entityReducers/actionsReducer";
import { ActionResponse } from "api/ActionAPI"; import { ActionResponse } from "api/ActionAPI";
export const getDataTree = (state: AppState): DataTree => state.entities; export const getEntities = (state: AppState): AppState["entities"] =>
state.entities;
export const getPluginIdOfName = ( export const getPluginIdOfName = (
state: AppState, state: AppState,

View File

@ -1,72 +0,0 @@
import { AppState, DataTree } from "reducers";
import { createSelector } from "reselect";
import { getActions, getDataTree } from "./entitiesSelector";
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
import createCachedSelector from "re-reselect";
import { getEvaluatedDataTree } from "utils/DynamicBindingUtils";
import {
ENTITY_TYPE_ACTION,
ENTITY_TYPE_WIDGET,
} from "constants/entityConstants";
import { extraLibraries } from "jsExecution/JSExecutionManagerSingleton";
export type NameBindingsWithData = { [key: string]: any };
export const getNameBindingsWithData = createSelector(
getDataTree,
(dataTree: DataTree): NameBindingsWithData => {
const nameBindingsWithData: Record<string, object> = {};
dataTree.actions.forEach(a => {
nameBindingsWithData[a.config.name] = {
...a,
data: a.data ? a.data.body : {},
__type: ENTITY_TYPE_ACTION,
};
});
Object.keys(dataTree.canvasWidgets).forEach(w => {
const widget = dataTree.canvasWidgets[w];
const widgetMetaProps = dataTree.meta[w];
nameBindingsWithData[widget.widgetName] = {
...widget,
...widgetMetaProps,
__type: ENTITY_TYPE_WIDGET,
};
});
return nameBindingsWithData;
},
);
export const getParsedDataTree = createSelector(
getNameBindingsWithData,
(namedBindings: NameBindingsWithData) => {
return getEvaluatedDataTree(namedBindings, true);
},
);
// For autocomplete. Use actions cached responses if
// there isn't a response already
export const getNameBindingsForAutocomplete = createCachedSelector(
getParsedDataTree,
getActions,
(dataTree: NameBindingsWithData, actions: ActionDataState) => {
const cachedResponses: Record<string, any> = {};
if (actions && actions.length) {
actions.forEach(action => {
if (!(action.config.name in dataTree) && action.config.cacheResponse) {
try {
cachedResponses[action.config.name] = JSON.parse(
action.config.cacheResponse,
);
} catch (e) {
cachedResponses[action.config.name] = action.config.cacheResponse;
}
}
});
}
const libs: Record<string, any> = {};
extraLibraries.forEach(
config => (libs[config.accessor] = libs[config.accessor]),
);
return { ...dataTree, ...cachedResponses, ...libs };
},
)((state: AppState) => state.entities.actions.length);

View File

@ -9,11 +9,9 @@ import {
getEvaluatedDataTree, getEvaluatedDataTree,
} from "utils/DynamicBindingUtils"; } from "utils/DynamicBindingUtils";
import { WidgetProps } from "widgets/BaseWidget"; import { WidgetProps } from "widgets/BaseWidget";
import { import { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";
NameBindingsWithData,
getNameBindingsWithData,
} from "./nameBindingsWithDataSelector";
import _ from "lodash"; import _ from "lodash";
import { DataTree } from "entities/DataTree/dataTreeFactory";
const getPropertyPaneState = (state: AppState): PropertyPaneReduxState => const getPropertyPaneState = (state: AppState): PropertyPaneReduxState =>
state.ui.propertyPane; state.ui.propertyPane;
@ -42,11 +40,8 @@ export const getCurrentWidgetProperties = createSelector(
export const getWidgetPropsWithValidations = createSelector( export const getWidgetPropsWithValidations = createSelector(
getCurrentWidgetProperties, getCurrentWidgetProperties,
getNameBindingsWithData, getUnevaluatedDataTree,
( (widget: WidgetProps | undefined, nameBindingsWithData: DataTree) => {
widget: WidgetProps | undefined,
nameBindingsWithData: NameBindingsWithData,
) => {
if (!widget) return undefined; if (!widget) return undefined;
const tree = getEvaluatedDataTree(nameBindingsWithData, false); const tree = getEvaluatedDataTree(nameBindingsWithData, false);
const evaluatedWidget = _.find(tree, { widgetId: widget.widgetId }); const evaluatedWidget = _.find(tree, { widgetId: widget.widgetId });

View File

@ -2,11 +2,17 @@ import _ from "lodash";
import { WidgetProps } from "widgets/BaseWidget"; import { WidgetProps } from "widgets/BaseWidget";
import { DATA_BIND_REGEX } from "constants/BindingsConstants"; import { DATA_BIND_REGEX } from "constants/BindingsConstants";
import ValidationFactory from "./ValidationFactory"; import ValidationFactory from "./ValidationFactory";
import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton"; import JSExecutionManagerSingleton, {
JSExecutorResult,
} from "jsExecution/JSExecutionManagerSingleton";
import unescapeJS from "unescape-js"; import unescapeJS from "unescape-js";
import { NameBindingsWithData } from "selectors/nameBindingsWithDataSelector";
import toposort from "toposort"; import toposort from "toposort";
import { ENTITY_TYPE_ACTION } from "constants/entityConstants"; import {
DataTree,
DataTreeAction,
DataTreeWidget,
ENTITY_TYPE,
} from "entities/DataTree/dataTreeFactory";
export const removeBindingsFromObject = (obj: object) => { export const removeBindingsFromObject = (obj: object) => {
const string = JSON.stringify(obj); const string = JSON.stringify(obj);
@ -100,12 +106,18 @@ export const getDynamicBindings = (
}; };
// Paths are expected to have "{name}.{path}" signature // Paths are expected to have "{name}.{path}" signature
// Also returns any action triggers found after evaluating value
export const evaluateDynamicBoundValue = ( export const evaluateDynamicBoundValue = (
data: NameBindingsWithData, data: DataTree,
path: string, path: string,
): any => { callbackData?: any,
): JSExecutorResult => {
const unescapedInput = unescapeJS(path); const unescapedInput = unescapeJS(path);
return JSExecutionManagerSingleton.evaluateSync(unescapedInput, data); return JSExecutionManagerSingleton.evaluateSync(
unescapedInput,
data,
callbackData,
);
}; };
// For creating a final value where bindings could be in a template format // For creating a final value where bindings could be in a template format
@ -128,26 +140,40 @@ export const createDynamicValueString = (
export const getDynamicValue = ( export const getDynamicValue = (
dynamicBinding: string, dynamicBinding: string,
data: NameBindingsWithData, data: DataTree,
): any => { callBackData?: any,
includeTriggers = false,
): JSExecutorResult => {
// Get the {{binding}} bound values // Get the {{binding}} bound values
const { bindings, paths } = getDynamicBindings(dynamicBinding); const { bindings, paths } = getDynamicBindings(dynamicBinding);
if (bindings.length) { if (bindings.length) {
// Get the Data Tree value of those "binding "paths // Get the Data Tree value of those "binding "paths
const values = paths.map((p, i) => { const values = paths.map((p, i) => {
if (p) { if (p) {
return evaluateDynamicBoundValue(data, p); const result = evaluateDynamicBoundValue(data, p, callBackData);
if (includeTriggers) {
return result;
} else {
return { result: result.result };
}
} else { } else {
return bindings[i]; return { result: bindings[i], triggers: [] };
} }
}); });
// if it is just one binding, no need to create template string // if it is just one binding, no need to create template string
if (bindings.length === 1) return values[0]; if (bindings.length === 1) return values[0];
// else return a string template with bindings // else return a string template with bindings
return createDynamicValueString(dynamicBinding, bindings, values); const templateString = createDynamicValueString(
dynamicBinding,
bindings,
values.map(v => v.result),
);
return {
result: templateString,
};
} }
return undefined; return { result: undefined, triggers: [] };
}; };
export const enhanceWidgetWithValidations = ( export const enhanceWidgetWithValidations = (
@ -199,7 +225,7 @@ export const getParsedTree = (tree: any) => {
}; };
export const getEvaluatedDataTree = ( export const getEvaluatedDataTree = (
dataTree: NameBindingsWithData, dataTree: DataTree,
parseValues: boolean, parseValues: boolean,
) => { ) => {
const dynamicDependencyMap = createDependencyTree(dataTree); const dynamicDependencyMap = createDependencyTree(dataTree);
@ -218,7 +244,7 @@ export const getEvaluatedDataTree = (
type DynamicDependencyMap = Record<string, Array<string>>; type DynamicDependencyMap = Record<string, Array<string>>;
export const createDependencyTree = ( export const createDependencyTree = (
dataTree: NameBindingsWithData, dataTree: DataTree,
): Array<[string, string]> => { ): Array<[string, string]> => {
const dependencyMap: DynamicDependencyMap = {}; const dependencyMap: DynamicDependencyMap = {};
const allKeys = getAllPaths(dataTree); const allKeys = getAllPaths(dataTree);
@ -276,20 +302,24 @@ const calculateSubDependencies = (
}; };
export const setTreeLoading = ( export const setTreeLoading = (
dataTree: NameBindingsWithData, dataTree: DataTree,
dependencyMap: Array<[string, string]>, dependencyMap: Array<[string, string]>,
) => { ) => {
const result = _.cloneDeep(dataTree); const result = _.cloneDeep(dataTree);
Object.keys(dataTree) Object.keys(dataTree)
.filter( .filter(e => {
e => dataTree[e].__type === ENTITY_TYPE_ACTION && dataTree[e].isLoading, const entity = dataTree[e] as DataTreeAction;
) return entity.ENTITY_TYPE === ENTITY_TYPE.ACTION && entity.isLoading;
})
.reduce( .reduce(
(allEntities: string[], curr) => (allEntities: string[], curr) =>
allEntities.concat(getEntityDependencies(dependencyMap, curr)), allEntities.concat(getEntityDependencies(dependencyMap, curr)),
[], [],
) )
.forEach(w => (result[w].isLoading = true)); .forEach(w => {
const entity = result[w] as DataTreeWidget;
entity.isLoading = true;
});
return result; return result;
}; };
@ -329,10 +359,10 @@ export const getEntityDependencies = (
}; };
export function dependencySortedEvaluateDataTree( export function dependencySortedEvaluateDataTree(
dataTree: NameBindingsWithData, dataTree: DataTree,
dependencyTree: Array<[string, string]>, dependencyTree: Array<[string, string]>,
parseValues: boolean, parseValues: boolean,
) { ): DataTree {
const tree = _.cloneDeep(dataTree); const tree = _.cloneDeep(dataTree);
try { try {
// sort dependencies and remove empty dependencies // sort dependencies and remove empty dependencies
@ -340,30 +370,28 @@ export function dependencySortedEvaluateDataTree(
.reverse() .reverse()
.filter(d => !!d); .filter(d => !!d);
// evaluate and replace values // evaluate and replace values
return sortedDependencies.reduce( return sortedDependencies.reduce((currentTree: DataTree, path: string) => {
(currentTree: NameBindingsWithData, path: string) => { const binding = _.get(currentTree as any, path);
const binding = _.get(currentTree as any, path); const widgetType = _.get(
const widgetType = _.get( currentTree as any,
currentTree as any, `${path.split(".")[0]}.type`,
`${path.split(".")[0]}.type`, null,
null, );
let result = binding;
if (isDynamicValue(binding)) {
const dynamicResult = getDynamicValue(binding, currentTree);
result = dynamicResult.result;
}
if (widgetType && parseValues) {
const { parsed } = ValidationFactory.validateWidgetProperty(
widgetType,
`${path.split(".")[1]}`,
result,
); );
let result = binding; result = parsed;
if (isDynamicValue(binding)) { }
result = getDynamicValue(binding, currentTree); return _.set(currentTree, path, result);
} }, tree);
if (widgetType && parseValues) {
const { parsed } = ValidationFactory.validateWidgetProperty(
widgetType,
`${path.split(".")[1]}`,
result,
);
result = parsed;
}
return _.set(currentTree, path, result);
},
tree,
);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return tree; return tree;

View File

@ -3,19 +3,19 @@ import {
mockExecute, mockExecute,
mockRegisterLibrary, mockRegisterLibrary,
} from "../../test/__mocks__/RealmExecutorMock"; } from "../../test/__mocks__/RealmExecutorMock";
jest.mock("jsExecution/RealmExecutor", () => {
return jest.fn().mockImplementation(() => {
return { execute: mockExecute, registerLibrary: mockRegisterLibrary };
});
});
import { import {
dependencySortedEvaluateDataTree, dependencySortedEvaluateDataTree,
getDynamicValue, getDynamicValue,
getEntityDependencies, getEntityDependencies,
parseDynamicString, parseDynamicString,
} from "./DynamicBindingUtils"; } from "./DynamicBindingUtils";
import { getNameBindingsWithData } from "selectors/nameBindingsWithDataSelector"; import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
import { AppState, DataTree } from "reducers";
jest.mock("jsExecution/RealmExecutor", () => {
return jest.fn().mockImplementation(() => {
return { execute: mockExecute, registerLibrary: mockRegisterLibrary };
});
});
beforeAll(() => { beforeAll(() => {
mockRegisterLibrary.mockClear(); mockRegisterLibrary.mockClear();
@ -24,12 +24,24 @@ beforeAll(() => {
it("Gets the value from the data tree", () => { it("Gets the value from the data tree", () => {
const dynamicBinding = "{{GetUsers.data}}"; const dynamicBinding = "{{GetUsers.data}}";
const nameBindingsWithData = { const nameBindingsWithData: DataTree = {
GetUsers: { GetUsers: {
data: "correct data", data: { text: "correct data" },
config: {
id: "id",
name: "text",
actionConfiguration: {},
pageId: "",
jsonPathKeys: [],
datasource: { id: "" },
pluginType: "1",
},
isLoading: false,
ENTITY_TYPE: ENTITY_TYPE.ACTION,
run: jest.fn(),
}, },
}; };
const actualValue = "correct data"; const actualValue = { result: { text: "correct data" } };
const value = getDynamicValue(dynamicBinding, nameBindingsWithData); const value = getDynamicValue(dynamicBinding, nameBindingsWithData);
@ -101,7 +113,7 @@ it("evaluates the data tree", () => {
}, },
}; };
const result = dependencySortedEvaluateDataTree(input, dynamicBindings); const result = dependencySortedEvaluateDataTree(input, dynamicBindings, true);
expect(result).toEqual(output); expect(result).toEqual(output);
}); });

View File

@ -73,11 +73,7 @@ class PropertyControlRegistry {
}); });
PropertyControlFactory.registerControlBuilder("COLUMN_ACTION_SELECTOR", { PropertyControlFactory.registerControlBuilder("COLUMN_ACTION_SELECTOR", {
buildPropertyControl(controlProps: ControlProps): JSX.Element { buildPropertyControl(controlProps: ControlProps): JSX.Element {
return ( return <ColumnActionSelectorControl {...controlProps} />;
<ColumnActionSelectorControl
{...controlProps}
></ColumnActionSelectorControl>
);
}, },
}); });
} }

View File

@ -8,6 +8,7 @@ import { WidgetPropertyValidationType } from "./ValidationFactory";
type WidgetDerivedPropertyType = any; type WidgetDerivedPropertyType = any;
export type DerivedPropertiesMap = Record<string, string>; export type DerivedPropertiesMap = Record<string, string>;
export type TriggerPropertiesMap = Record<string, true>;
class WidgetFactory { class WidgetFactory {
static widgetMap: Map<WidgetType, WidgetBuilder<WidgetProps>> = new Map(); static widgetMap: Map<WidgetType, WidgetBuilder<WidgetProps>> = new Map();
@ -23,16 +24,22 @@ class WidgetFactory {
WidgetType, WidgetType,
DerivedPropertiesMap DerivedPropertiesMap
> = new Map(); > = new Map();
static triggerPropertiesMap: Map<
WidgetType,
TriggerPropertiesMap
> = new Map();
static registerWidgetBuilder( static registerWidgetBuilder(
widgetType: WidgetType, widgetType: WidgetType,
widgetBuilder: WidgetBuilder<WidgetProps>, widgetBuilder: WidgetBuilder<WidgetProps>,
widgetPropertyValidation: WidgetPropertyValidationType, widgetPropertyValidation: WidgetPropertyValidationType,
derivedPropertiesMap: DerivedPropertiesMap, derivedPropertiesMap: DerivedPropertiesMap,
triggerPropertiesMap: TriggerPropertiesMap,
) { ) {
this.widgetMap.set(widgetType, widgetBuilder); this.widgetMap.set(widgetType, widgetBuilder);
this.widgetPropValidationMap.set(widgetType, widgetPropertyValidation); this.widgetPropValidationMap.set(widgetType, widgetPropertyValidation);
this.derivedPropertiesMap.set(widgetType, derivedPropertiesMap); this.derivedPropertiesMap.set(widgetType, derivedPropertiesMap);
this.triggerPropertiesMap.set(widgetType, triggerPropertiesMap);
} }
static createWidget( static createWidget(
@ -84,6 +91,17 @@ class WidgetFactory {
} }
return map; return map;
} }
static getWidgetTriggerPropertiesMap(
widgetType: WidgetType,
): TriggerPropertiesMap {
const map = this.triggerPropertiesMap.get(widgetType);
if (!map) {
console.error("Widget trigger map is not defined");
return {};
}
return map;
}
} }
export interface WidgetCreationException { export interface WidgetCreationException {

View File

@ -33,6 +33,7 @@ class WidgetBuilderRegistry {
}, },
ContainerWidget.getPropertyValidationMap(), ContainerWidget.getPropertyValidationMap(),
ContainerWidget.getDerivedPropertiesMap(), ContainerWidget.getDerivedPropertiesMap(),
ContainerWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
@ -44,6 +45,7 @@ class WidgetBuilderRegistry {
}, },
TextWidget.getPropertyValidationMap(), TextWidget.getPropertyValidationMap(),
TextWidget.getDerivedPropertiesMap(), TextWidget.getDerivedPropertiesMap(),
TextWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
@ -55,6 +57,7 @@ class WidgetBuilderRegistry {
}, },
ButtonWidget.getPropertyValidationMap(), ButtonWidget.getPropertyValidationMap(),
ButtonWidget.getDerivedPropertiesMap(), ButtonWidget.getDerivedPropertiesMap(),
ButtonWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
@ -66,6 +69,7 @@ class WidgetBuilderRegistry {
}, },
SpinnerWidget.getPropertyValidationMap(), SpinnerWidget.getPropertyValidationMap(),
SpinnerWidget.getDerivedPropertiesMap(), SpinnerWidget.getDerivedPropertiesMap(),
SpinnerWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
@ -77,6 +81,7 @@ class WidgetBuilderRegistry {
}, },
InputWidget.getPropertyValidationMap(), InputWidget.getPropertyValidationMap(),
InputWidget.getDerivedPropertiesMap(), InputWidget.getDerivedPropertiesMap(),
InputWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
@ -88,6 +93,7 @@ class WidgetBuilderRegistry {
}, },
CheckboxWidget.getPropertyValidationMap(), CheckboxWidget.getPropertyValidationMap(),
CheckboxWidget.getDerivedPropertiesMap(), CheckboxWidget.getDerivedPropertiesMap(),
ContainerWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
@ -99,6 +105,7 @@ class WidgetBuilderRegistry {
}, },
DropdownWidget.getPropertyValidationMap(), DropdownWidget.getPropertyValidationMap(),
DropdownWidget.getDerivedPropertiesMap(), DropdownWidget.getDerivedPropertiesMap(),
DropdownWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
@ -110,6 +117,7 @@ class WidgetBuilderRegistry {
}, },
RadioGroupWidget.getPropertyValidationMap(), RadioGroupWidget.getPropertyValidationMap(),
RadioGroupWidget.getDerivedPropertiesMap(), RadioGroupWidget.getDerivedPropertiesMap(),
RadioGroupWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
@ -121,6 +129,7 @@ class WidgetBuilderRegistry {
}, },
ImageWidget.getPropertyValidationMap(), ImageWidget.getPropertyValidationMap(),
ImageWidget.getDerivedPropertiesMap(), ImageWidget.getDerivedPropertiesMap(),
ImageWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
"TABLE_WIDGET", "TABLE_WIDGET",
@ -131,6 +140,7 @@ class WidgetBuilderRegistry {
}, },
TableWidget.getPropertyValidationMap(), TableWidget.getPropertyValidationMap(),
TableWidget.getDerivedPropertiesMap(), TableWidget.getDerivedPropertiesMap(),
TableWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
"FILE_PICKER_WIDGET", "FILE_PICKER_WIDGET",
@ -141,6 +151,7 @@ class WidgetBuilderRegistry {
}, },
FilePickerWidget.getPropertyValidationMap(), FilePickerWidget.getPropertyValidationMap(),
FilePickerWidget.getDerivedPropertiesMap(), FilePickerWidget.getDerivedPropertiesMap(),
FilePickerWidget.getTriggerPropertyMap(),
); );
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
"DATE_PICKER_WIDGET", "DATE_PICKER_WIDGET",
@ -151,6 +162,7 @@ class WidgetBuilderRegistry {
}, },
DatePickerWidget.getPropertyValidationMap(), DatePickerWidget.getPropertyValidationMap(),
DatePickerWidget.getDerivedPropertiesMap(), DatePickerWidget.getDerivedPropertiesMap(),
DatePickerWidget.getTriggerPropertyMap(),
); );
} }
} }

View File

@ -1,7 +1,5 @@
import React, { Component } from "react"; import React, { Component } from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetProps } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { ActionPayload } from "constants/ActionConstants";
class AlertWidget extends Component { class AlertWidget extends Component {
getPageView() { getPageView() {
@ -17,7 +15,6 @@ export interface AlertWidgetProps extends WidgetProps {
intent: MessageIntent; intent: MessageIntent;
header: string; header: string;
message: string; message: string;
onPrimaryClick: ActionPayload[];
} }
export default AlertWidget; export default AlertWidget;

View File

@ -19,7 +19,7 @@ import {
import _ from "lodash"; import _ from "lodash";
import DraggableComponent from "components/editorComponents/DraggableComponent"; import DraggableComponent from "components/editorComponents/DraggableComponent";
import ResizableComponent from "components/editorComponents/ResizableComponent"; import ResizableComponent from "components/editorComponents/ResizableComponent";
import { ActionPayload } from "constants/ActionConstants"; import { ExecuteActionPayload } from "constants/ActionConstants";
import PositionedContainer from "components/designSystems/appsmith/PositionedContainer"; import PositionedContainer from "components/designSystems/appsmith/PositionedContainer";
import WidgetNameComponent from "components/designSystems/appsmith/WidgetNameComponent"; import WidgetNameComponent from "components/designSystems/appsmith/WidgetNameComponent";
import shallowequal from "shallowequal"; import shallowequal from "shallowequal";
@ -28,8 +28,10 @@ import { PositionTypes } from "constants/WidgetConstants";
import ErrorBoundary from "components/editorComponents/ErrorBoundry"; import ErrorBoundary from "components/editorComponents/ErrorBoundry";
import { WidgetPropertyValidationType } from "utils/ValidationFactory"; import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { DerivedPropertiesMap } from "utils/WidgetFactory"; import {
import { PaginationField } from "api/ActionAPI"; DerivedPropertiesMap,
TriggerPropertiesMap,
} from "utils/WidgetFactory";
/*** /***
* BaseWidget * BaseWidget
* *
@ -70,6 +72,10 @@ abstract class BaseWidget<
return {}; return {};
} }
static getTriggerPropertyMap(): TriggerPropertiesMap {
return {};
}
/** /**
* Widget abstraction to register the widget type * Widget abstraction to register the widget type
* ```javascript * ```javascript
@ -84,14 +90,9 @@ abstract class BaseWidget<
* Widgets can execute actions using this `executeAction` method. * Widgets can execute actions using this `executeAction` method.
* Triggers may be specific to the widget * Triggers may be specific to the widget
*/ */
executeAction( executeAction(actionPayload: ExecuteActionPayload): void {
actionPayloads?: ActionPayload[],
paginationField?: PaginationField,
): void {
const { executeAction } = this.context; const { executeAction } = this.context;
executeAction && executeAction && executeAction(actionPayload);
!_.isNil(actionPayloads) &&
executeAction(actionPayloads, paginationField);
} }
disableDrag(disable: boolean) { disableDrag(disable: boolean) {
@ -269,6 +270,7 @@ export interface WidgetProps extends WidgetDataProps {
key?: string; key?: string;
renderMode: RenderMode; renderMode: RenderMode;
dynamicBindings?: Record<string, boolean>; dynamicBindings?: Record<string, boolean>;
dynamicTriggers?: Record<string, true>;
invalidProps?: Record<string, boolean>; invalidProps?: Record<string, boolean>;
validationMessages?: Record<string, string>; validationMessages?: Record<string, string>;
isDefaultClickDisabled?: boolean; isDefaultClickDisabled?: boolean;

View File

@ -2,9 +2,10 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants"; import { WidgetType } from "constants/WidgetConstants";
import ButtonComponent from "components/designSystems/blueprint/ButtonComponent"; import ButtonComponent from "components/designSystems/blueprint/ButtonComponent";
import { ActionPayload } from "constants/ActionConstants"; import { EventType } from "constants/ActionConstants";
import { WidgetPropertyValidationType } from "utils/ValidationFactory"; import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
class ButtonWidget extends BaseWidget<ButtonWidgetProps, WidgetState> { class ButtonWidget extends BaseWidget<ButtonWidgetProps, WidgetState> {
onButtonClickBound: (event: React.MouseEvent<HTMLElement>) => void; onButtonClickBound: (event: React.MouseEvent<HTMLElement>) => void;
@ -23,8 +24,21 @@ class ButtonWidget extends BaseWidget<ButtonWidgetProps, WidgetState> {
}; };
} }
static getTriggerPropertyMap(): TriggerPropertiesMap {
return {
onClick: true,
};
}
onButtonClick() { onButtonClick() {
super.executeAction(this.props.onClick); if (this.props.onClick) {
super.executeAction({
dynamicString: this.props.onClick,
event: {
type: EventType.ON_CLICK,
},
});
}
} }
getPageView() { getPageView() {
@ -56,7 +70,7 @@ export type ButtonStyle =
export interface ButtonWidgetProps extends WidgetProps { export interface ButtonWidgetProps extends WidgetProps {
text?: string; text?: string;
buttonStyle?: ButtonStyle; buttonStyle?: ButtonStyle;
onClick?: ActionPayload[]; onClick?: string;
isDisabled?: boolean; isDisabled?: boolean;
isVisible?: boolean; isVisible?: boolean;
} }

View File

@ -2,9 +2,10 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants"; import { WidgetType } from "constants/WidgetConstants";
import CheckboxComponent from "components/designSystems/blueprint/CheckboxComponent"; import CheckboxComponent from "components/designSystems/blueprint/CheckboxComponent";
import { ActionPayload } from "constants/ActionConstants"; import { EventType } from "constants/ActionConstants";
import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { WidgetPropertyValidationType } from "utils/ValidationFactory"; import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
class CheckboxWidget extends BaseWidget<CheckboxWidgetProps, WidgetState> { class CheckboxWidget extends BaseWidget<CheckboxWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType { static getPropertyValidationMap(): WidgetPropertyValidationType {
@ -16,6 +17,12 @@ class CheckboxWidget extends BaseWidget<CheckboxWidgetProps, WidgetState> {
}; };
} }
static getTriggerPropertyMap(): TriggerPropertiesMap {
return {
onCheckChange: true,
};
}
getPageView() { getPageView() {
return ( return (
<CheckboxComponent <CheckboxComponent
@ -32,7 +39,14 @@ class CheckboxWidget extends BaseWidget<CheckboxWidgetProps, WidgetState> {
onCheckChange = (isChecked: boolean) => { onCheckChange = (isChecked: boolean) => {
this.updateWidgetProperty("isChecked", isChecked); this.updateWidgetProperty("isChecked", isChecked);
super.executeAction(this.props.onCheckChange); if (this.props.onCheckChange) {
super.executeAction({
dynamicString: this.props.onCheckChange,
event: {
type: EventType.ON_CHECK_CHANGE,
},
});
}
}; };
getWidgetType(): WidgetType { getWidgetType(): WidgetType {
@ -45,7 +59,7 @@ export interface CheckboxWidgetProps extends WidgetProps {
defaultCheckedState: boolean; defaultCheckedState: boolean;
isChecked?: boolean; isChecked?: boolean;
isDisabled?: boolean; isDisabled?: boolean;
onCheckChange?: ActionPayload[]; onCheckChange?: string;
} }
export default CheckboxWidget; export default CheckboxWidget;

View File

@ -1,10 +1,11 @@
import React from "react"; import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants"; import { WidgetType } from "constants/WidgetConstants";
import { ActionPayload } from "constants/ActionConstants"; import { EventType } from "constants/ActionConstants";
import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent"; import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent";
import { WidgetPropertyValidationType } from "utils/ValidationFactory"; import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> { class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType { static getPropertyValidationMap(): WidgetPropertyValidationType {
@ -20,6 +21,11 @@ class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
minDate: VALIDATION_TYPES.DATE, minDate: VALIDATION_TYPES.DATE,
}; };
} }
static getTriggerPropertyMap(): TriggerPropertiesMap {
return {
onDateSelected: true,
};
}
getPageView() { getPageView() {
return ( return (
<DatePickerComponent <DatePickerComponent
@ -39,7 +45,14 @@ class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
onDateSelected = (selectedDate: Date) => { onDateSelected = (selectedDate: Date) => {
this.updateWidgetProperty("selectedDate", selectedDate); this.updateWidgetProperty("selectedDate", selectedDate);
super.executeAction(this.props.onDateSelected); if (this.props.onDateSelected) {
super.executeAction({
dynamicString: this.props.onDateSelected,
event: {
type: EventType.ON_DATE_SELECTED,
},
});
}
}; };
getWidgetType(): WidgetType { getWidgetType(): WidgetType {
@ -57,8 +70,8 @@ export interface DatePickerWidgetProps extends WidgetProps {
dateFormat: string; dateFormat: string;
label: string; label: string;
datePickerType: DatePickerType; datePickerType: DatePickerType;
onDateSelected: ActionPayload[]; onDateSelected?: string;
onDateRangeSelected: ActionPayload[]; onDateRangeSelected?: string;
maxDate: Date; maxDate: Date;
minDate: Date; minDate: Date;
} }

View File

@ -1,11 +1,12 @@
import React from "react"; import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants"; import { WidgetType } from "constants/WidgetConstants";
import { ActionPayload } from "constants/ActionConstants"; import { EventType } from "constants/ActionConstants";
import DropDownComponent from "components/designSystems/blueprint/DropdownComponent"; import DropDownComponent from "components/designSystems/blueprint/DropdownComponent";
import _ from "lodash"; import _ from "lodash";
import { WidgetPropertyValidationType } from "utils/ValidationFactory"; import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
export interface DropDownDerivedProps { export interface DropDownDerivedProps {
selectedOption?: DropdownOption; selectedOption?: DropdownOption;
@ -30,9 +31,8 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
: undefined : undefined
}}`, }}`,
selectedOptionArr: `{{ selectedOptionArr: `{{
const options = this.options || [];
this.selectionType === "MULTI_SELECT" this.selectionType === "MULTI_SELECT"
? options.filter((opt, index) => ? this.options.filter((opt, index) =>
_.includes(this.selectedIndexArr, index), _.includes(this.selectedIndexArr, index),
) )
: undefined : undefined
@ -40,6 +40,12 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
}; };
} }
static getTriggerPropertyMap(): TriggerPropertiesMap {
return {
onOptionChange: true,
};
}
componentDidUpdate(prevProps: DropdownWidgetProps) { componentDidUpdate(prevProps: DropdownWidgetProps) {
super.componentDidUpdate(prevProps); super.componentDidUpdate(prevProps);
if ( if (
@ -103,7 +109,14 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
this.updateWidgetMetaProperty("selectedIndexArr", selectedIndexArr); this.updateWidgetMetaProperty("selectedIndexArr", selectedIndexArr);
} }
} }
super.executeAction(this.props.onOptionChange); if (this.props.onOptionChange) {
super.executeAction({
dynamicString: this.props.onOptionChange,
event: {
type: EventType.ON_OPTION_CHANGE,
},
});
}
}; };
onOptionRemoved = (removedIndex: number) => { onOptionRemoved = (removedIndex: number) => {
@ -113,7 +126,14 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
}) })
: []; : [];
this.updateWidgetMetaProperty("selectedIndexArr", updateIndexArr); this.updateWidgetMetaProperty("selectedIndexArr", updateIndexArr);
super.executeAction(this.props.onOptionChange); if (this.props.onOptionChange) {
super.executeAction({
dynamicString: this.props.onOptionChange,
event: {
type: EventType.ON_OPTION_CHANGE,
},
});
}
}; };
getWidgetType(): WidgetType { getWidgetType(): WidgetType {
@ -125,6 +145,9 @@ export type SelectionType = "SINGLE_SELECT" | "MULTI_SELECT";
export interface DropdownOption { export interface DropdownOption {
label: string; label: string;
value: string; value: string;
id?: string;
onSelect?: (option: DropdownOption) => void;
children?: DropdownOption[];
} }
export interface DropdownWidgetProps extends WidgetProps { export interface DropdownWidgetProps extends WidgetProps {
@ -134,7 +157,7 @@ export interface DropdownWidgetProps extends WidgetProps {
selectedIndexArr?: number[]; selectedIndexArr?: number[];
selectionType: SelectionType; selectionType: SelectionType;
options?: DropdownOption[]; options?: DropdownOption[];
onOptionChange?: ActionPayload[]; onOptionChange?: string;
} }
export default DropdownWidget; export default DropdownWidget;

View File

@ -2,11 +2,13 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants"; import { WidgetType } from "constants/WidgetConstants";
import InputComponent from "components/designSystems/blueprint/InputComponent"; import InputComponent from "components/designSystems/blueprint/InputComponent";
import { ActionPayload } from "constants/ActionConstants"; import { EventType } from "constants/ActionConstants";
import { WidgetPropertyValidationType } from "utils/ValidationFactory"; import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> { class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
regex = new RegExp("");
static getPropertyValidationMap(): WidgetPropertyValidationType { static getPropertyValidationMap(): WidgetPropertyValidationType {
return { return {
inputType: VALIDATION_TYPES.TEXT, inputType: VALIDATION_TYPES.TEXT,
@ -25,7 +27,11 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
isAutoFocusEnabled: VALIDATION_TYPES.BOOLEAN, isAutoFocusEnabled: VALIDATION_TYPES.BOOLEAN,
}; };
} }
regex = new RegExp(""); static getTriggerPropertyMap(): TriggerPropertiesMap {
return {
onTextChanged: true,
};
}
componentDidMount() { componentDidMount() {
super.componentDidMount(); super.componentDidMount();
@ -51,7 +57,14 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
onValueChange = (value: string) => { onValueChange = (value: string) => {
this.updateWidgetProperty("text", value); this.updateWidgetProperty("text", value);
super.executeAction(this.props.onTextChanged); if (this.props.onTextChanged) {
super.executeAction({
dynamicString: this.props.onTextChanged,
event: {
type: EventType.ON_TEXT_CHANGE,
},
});
}
}; };
getPageView() { getPageView() {
@ -115,7 +128,7 @@ export interface InputWidgetProps extends WidgetProps {
maxChars?: number; maxChars?: number;
minNum?: number; minNum?: number;
maxNum?: number; maxNum?: number;
onTextChanged?: ActionPayload[]; onTextChanged?: string;
label: string; label: string;
inputValidators: InputValidator[]; inputValidators: InputValidator[];
focusIndex?: number; focusIndex?: number;

View File

@ -2,9 +2,10 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants"; import { WidgetType } from "constants/WidgetConstants";
import RadioGroupComponent from "components/designSystems/blueprint/RadioGroupComponent"; import RadioGroupComponent from "components/designSystems/blueprint/RadioGroupComponent";
import { ActionPayload } from "constants/ActionConstants"; import { EventType } from "constants/ActionConstants";
import { WidgetPropertyValidationType } from "utils/ValidationFactory"; import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
class RadioGroupWidget extends BaseWidget<RadioGroupWidgetProps, WidgetState> { class RadioGroupWidget extends BaseWidget<RadioGroupWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType { static getPropertyValidationMap(): WidgetPropertyValidationType {
@ -20,6 +21,11 @@ class RadioGroupWidget extends BaseWidget<RadioGroupWidgetProps, WidgetState> {
"{{_.find(this.options, { value: this.selectedOptionValue })}}", "{{_.find(this.options, { value: this.selectedOptionValue })}}",
}; };
} }
static getTriggerPropertyMap(): TriggerPropertiesMap {
return {
onSelectionChange: true,
};
}
getPageView() { getPageView() {
return ( return (
<RadioGroupComponent <RadioGroupComponent
@ -36,7 +42,14 @@ class RadioGroupWidget extends BaseWidget<RadioGroupWidgetProps, WidgetState> {
onRadioSelectionChange = (updatedValue: string) => { onRadioSelectionChange = (updatedValue: string) => {
this.updateWidgetProperty("selectedOptionValue", updatedValue); this.updateWidgetProperty("selectedOptionValue", updatedValue);
super.executeAction(this.props.onSelectionChange); if (this.props.onSelectionChange) {
super.executeAction({
dynamicString: this.props.onSelectionChange,
event: {
type: EventType.ON_OPTION_CHANGE,
},
});
}
}; };
getWidgetType(): WidgetType { getWidgetType(): WidgetType {
@ -54,7 +67,7 @@ export interface RadioGroupWidgetProps extends WidgetProps {
label: string; label: string;
options: RadioOption[]; options: RadioOption[];
selectedOptionValue: string; selectedOptionValue: string;
onSelectionChange?: ActionPayload[]; onSelectionChange: string;
} }
export default RadioGroupWidget; export default RadioGroupWidget;

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants"; import { WidgetType } from "constants/WidgetConstants";
import { ActionPayload, TableAction } from "constants/ActionConstants"; import { EventType } from "constants/ActionConstants";
import { forIn } from "lodash"; import { forIn } from "lodash";
import TableComponent from "components/designSystems/syncfusion/TableComponent"; import TableComponent from "components/designSystems/syncfusion/TableComponent";
@ -10,6 +10,7 @@ import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { ColumnModel } from "@syncfusion/ej2-grids"; import { ColumnModel } from "@syncfusion/ej2-grids";
import { ColumnDirTypecast } from "@syncfusion/ej2-react-grids"; import { ColumnDirTypecast } from "@syncfusion/ej2-react-grids";
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl"; import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
function constructColumns(data: object[]): ColumnModel[] | ColumnDirTypecast[] { function constructColumns(data: object[]): ColumnModel[] | ColumnDirTypecast[] {
const cols: ColumnModel[] | ColumnDirTypecast[] = []; const cols: ColumnModel[] | ColumnDirTypecast[] = [];
@ -42,6 +43,14 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
}; };
} }
static getTriggerPropertyMap(): TriggerPropertiesMap {
return {
onRowSelected: true,
onPageChange: true,
columnActions: true,
};
}
getPageView() { getPageView() {
const { tableData } = this.props; const { tableData } = this.props;
const columns = constructColumns(tableData); const columns = constructColumns(tableData);
@ -67,29 +76,11 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
}} }}
columnActions={this.props.columnActions} columnActions={this.props.columnActions}
onCommandClick={this.onCommandClick} onCommandClick={this.onCommandClick}
onRowClick={(rowData: object, index: number) => { onRowClick={this.handleRowClick}
const { onRowSelected } = this.props;
this.updateWidgetProperty("selectedRowIndex", index);
super.executeAction(onRowSelected);
}}
serverSidePaginationEnabled={serverSidePaginationEnabled} serverSidePaginationEnabled={serverSidePaginationEnabled}
pageNo={pageNo} pageNo={pageNo}
nextPageClick={() => { nextPageClick={this.handleNextPageClick}
let pageNo = this.props.pageNo || 1; prevPageClick={this.handlePrevPageClick}
pageNo = pageNo + 1;
super.updateWidgetMetaProperty("pageNo", pageNo);
super.executeAction(this.props.onPageChange, "NEXT");
}}
prevPageClick={() => {
let pageNo = this.props.pageNo || 1;
pageNo = pageNo - 1;
if (pageNo >= 1) {
super.updateWidgetMetaProperty("pageNo", pageNo);
super.executeAction(this.props.onPageChange, "PREV");
}
}}
updatePageNo={(pageNo: number) => { updatePageNo={(pageNo: number) => {
super.updateWidgetMetaProperty("pageNo", pageNo); super.updateWidgetMetaProperty("pageNo", pageNo);
}} }}
@ -100,8 +91,56 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
); );
} }
onCommandClick = (actions: ActionPayload[]) => { onCommandClick = (action: string) => {
super.executeAction(actions); super.executeAction({
dynamicString: action,
event: {
type: EventType.ON_CLICK,
},
});
};
handleRowClick = (rowData: object, index: number) => {
const { onRowSelected } = this.props;
super.updateWidgetProperty("selectedRow", index);
if (onRowSelected) {
super.executeAction({
dynamicString: onRowSelected,
event: {
type: EventType.ON_ROW_SELECTED,
},
});
}
};
handleNextPageClick = () => {
let pageNo = this.props.pageNo || 1;
pageNo = pageNo + 1;
super.updateWidgetMetaProperty("pageNo", pageNo);
if (this.props.onPageChange) {
super.executeAction({
dynamicString: this.props.onPageChange,
event: {
type: EventType.ON_NEXT_PAGE,
},
});
}
};
handlePrevPageClick = () => {
let pageNo = this.props.pageNo || 1;
pageNo = pageNo - 1;
if (pageNo >= 1) {
super.updateWidgetMetaProperty("pageNo", pageNo);
if (this.props.onPageChange) {
super.executeAction({
dynamicString: this.props.onPageChange,
event: {
type: EventType.ON_PREV_PAGE,
},
});
}
}
}; };
getWidgetType(): WidgetType { getWidgetType(): WidgetType {
@ -119,9 +158,8 @@ export interface TableWidgetProps extends WidgetProps {
prevPageKey?: string; prevPageKey?: string;
label: string; label: string;
tableData: object[]; tableData: object[];
recordActions?: TableAction[]; onPageChange?: string;
onPageChange?: ActionPayload[]; onRowSelected?: string;
onRowSelected?: ActionPayload[];
selectedRowIndex?: number; selectedRowIndex?: number;
columnActions?: ColumnAction[]; columnActions?: ColumnAction[];
serverSidePaginationEnabled?: boolean; serverSidePaginationEnabled?: boolean;

View File

@ -7,7 +7,7 @@ export const mockExecute = jest.fn().mockImplementation((src, data) => {
}); });
finalSource = finalSource.substring(0, finalSource.length - 2) + ";"; finalSource = finalSource.substring(0, finalSource.length - 2) + ";";
finalSource += src; finalSource += src;
return eval(finalSource); return { result: eval(finalSource), triggers: [] };
}); });
export const mockRegisterLibrary = jest.fn(); export const mockRegisterLibrary = jest.fn();

1
app/client/typings/json-fn/index.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module "json-fn";

View File

@ -931,7 +931,7 @@
core-js-pure "^3.0.0" core-js-pure "^3.0.0"
regenerator-runtime "^0.13.2" regenerator-runtime "^0.13.2"
"@babel/runtime@7.8.4", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6": "@babel/runtime@7.8.4", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6":
version "7.8.4" version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308"
integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==
@ -9303,6 +9303,11 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
json-fn@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/json-fn/-/json-fn-1.1.1.tgz#4293c9198a482d6697d334a6e32cd0d221121e80"
integrity sha1-QpPJGYpILWaX0zSm4yzQ0iESHoA=
json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@ -13006,6 +13011,16 @@ react-textarea-autosize@^7.1.0:
"@babel/runtime" "^7.1.2" "@babel/runtime" "^7.1.2"
prop-types "^15.6.0" prop-types "^15.6.0"
react-toastify@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-5.5.0.tgz#f55de44f6b5e3ce3b13b69e5bb4427f2c9404822"
integrity sha512-jsVme7jALIFGRyQsri/g4YTsRuaaGI70T6/ikjwZMB4mwTZaCWqj5NqxhGrRStKlJc5npXKKvKeqTiRGQl78LQ==
dependencies:
"@babel/runtime" "^7.4.2"
classnames "^2.2.6"
prop-types "^15.7.2"
react-transition-group "^4"
react-transition-group@^2.2.1, react-transition-group@^2.9.0: react-transition-group@^2.2.1, react-transition-group@^2.9.0:
version "2.9.0" version "2.9.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
@ -13016,7 +13031,7 @@ react-transition-group@^2.2.1, react-transition-group@^2.9.0:
prop-types "^15.6.2" prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4" react-lifecycles-compat "^3.0.4"
react-transition-group@^4.3.0: react-transition-group@^4, react-transition-group@^4.3.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683"
integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw== integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==