Merge branch 'release' of gitlab.com:theappsmith/internal-tools-client into fix/chart-component-title-align

This commit is contained in:
vicky_primathon.in 2020-03-20 09:33:49 +05:30
commit 9021e6edb1
40 changed files with 504 additions and 189 deletions

View File

@ -1,5 +1,4 @@
.git
.idea
node_modules
build
build.tgz

View File

@ -29,4 +29,5 @@ yarn-error.log*
.idea
.storybook-out/
cypress/videos
results/
cypress/screenshots
results/

View File

@ -4,6 +4,17 @@
- master
- merge_requests
.set_env_variables: &set_env_variables
- |
if [ "$CI_COMMIT_BRANCH" == "master" ]; then
REACT_APP_ENVIRONMENT="PRODUCTION"
elif [ "$CI_COMMIT_BRANCH" == "release" ]; then
REACT_APP_ENVIRONMENT="STAGING"
REACT_APP_BASE_URL="https://release-api.appsmith.com"
else
REACT_APP_ENVIRONMENT="DEVELOPMENT"
REACT_APP_BASE_URL="https://release-api.appsmith.com"
fi
image: cypress/base:10.16.3
variables:
@ -25,32 +36,38 @@ stages:
- package
- deploy
react-build:
react-build-release:
stage: build
script:
- *set_env_variables
- yarn install
# show where the Cypress test runner binaries are cached
- $(npm bin)/cypress cache path
# show all installed versions of Cypress binary
- $(npm bin)/cypress cache list
- $(npm bin)/cypress verify
- REACT_APP_ENVIRONMENT=$REACT_APP_ENVIRONMENT REACT_APP_BASE_URL=$REACT_APP_BASE_URL GIT_SHA=$CI_COMMIT_SHORT_SHA yarn build
artifacts:
expire_in: 1 week
paths:
- build/
only:
- release
- merge_requests
cypress-test:
cypress-test-release:
stage: test
script:
- REACT_APP_ENVIRONMENT=DEVELOPMENT REACT_APP_BASE_URL="https://release-api.appsmith.com" GIT_SHA=$CI_COMMIT_SHORT_SHA yarn build
- *set_env_variables
- yarn global add serve
- serve -s build -p 3000 &
# This is required in order to ensure that all the test cases pass
- echo "127.0.0.1 dev.appsmith.com" >> /etc/hosts
- yarn test
artifacts:
when: always
expire_in: 1 week
paths:
- build/
- cypress/screenshots
- cypress/videos
only:
@ -61,22 +78,38 @@ cypress-test:
docker-package-release:
image: docker:dind
services:
- docker:dind
- docker:dind
stage: package
script:
- docker build --build-arg REACT_APP_ENVIRONMENT=STAGING --build-arg GIT_SHA=$CI_COMMIT_SHORT_SHA -t appsmith/appsmith-editor:release .
- *set_env_variables
- docker build -t appsmith/appsmith-editor:release .
- docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_ACCESS_TOKEN
- docker push appsmith/appsmith-editor:release
only:
- release
react-build-prod:
stage: build
script:
- *set_env_variables
- yarn install
- REACT_APP_ENVIRONMENT=$REACT_APP_ENVIRONMENT GIT_SHA=$CI_COMMIT_SHORT_SHA yarn build
artifacts:
when: on_success
expire_in: 1 week
paths:
- build/
only:
- master
docker-package-prod:
image: docker:dind
services:
- docker:dind
stage: package
script:
- docker build --build-arg REACT_APP_ENVIRONMENT=PRODUCTION --build-arg GIT_SHA=$CI_COMMIT_SHORT_SHA -t appsmith/appsmith-editor:latest .
script:
- *set_env_variables
- docker build -t appsmith/appsmith-editor:latest .
- docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_ACCESS_TOKEN
- docker push appsmith/appsmith-editor:latest
only:

View File

@ -1,22 +1,7 @@
FROM node:10.19-alpine as build-deps
FROM nginx:1.17.9-alpine
WORKDIR /usr/src/app
ARG REACT_APP_ENVIRONMENT="DEVELOPMENT"
ARG GIT_SHA=""
ENV REACT_APP_ENVIRONMENT=${REACT_APP_ENVIRONMENT}
ENV REACT_APP_BASE_URL=${REACT_APP_BASE_URL}
ENV GIT_SHA=${GIT_SHA}
COPY package.json yarn.lock ./
COPY . ./
RUN yarn install && yarn build
# Use the output from the previous Docker build to create the nginx container
FROM nginx:1.17.9-alpine as final-image
COPY --from=build-deps /usr/src/app/docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build-deps /usr/src/app/build /var/www/appsmith
COPY ./build /var/www/appsmith
RUN ls -al /var/www/appsmith
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1,13 +1,11 @@
#!/bin/sh
# GIT_SHA=$(eval git rev-parse HEAD)
# GIT_BRANCH=$(git branch --no-color | grep -E '^\*' | sed 's/\*[^a-z]*//g')
# RELEASE="${GIT_BRANCH}_${GIT_SHA}"
# RELEASE=$(echo "$RELEASE" | sed -e 's/[\/\\\ .]/\-/g')
# echo $RELEASE
GIT_SHA=$(eval git rev-parse HEAD)
echo $GIT_SHA
REACT_APP_SENTRY_RELEASE=$GIT_SHA craco --max-old-space-size=4096 build --config craco.build.config.js
rm ./build/static/js/*.js.map

View File

@ -15,6 +15,7 @@
"@blueprintjs/timezone": "^3.6.0",
"@craco/craco": "^5.6.1",
"@sentry/browser": "^5.6.3",
"@sentry/webpack-plugin": "^1.10.0",
"@syncfusion/ej2-react-grids": "^17.4.40",
"@types/chance": "^1.0.7",
"@types/fontfaceobserver": "^0.0.6",
@ -117,7 +118,6 @@
],
"devDependencies": {
"@babel/core": "^7.7.4",
"@sentry/webpack-plugin": "^1.10.0",
"@storybook/addon-contexts": "^5.2.6",
"@storybook/addon-docs": "^5.2.8",
"@storybook/addon-knobs": "^5.2.6",
@ -158,8 +158,7 @@
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "yarn run test"
"pre-commit": "lint-staged"
}
}
}

View File

@ -71,14 +71,14 @@ class DatePickerComponent extends React.Component<DatePickerComponentProps> {
this.props.enableTimePicker
? {
useAmPm: true,
value: this.props.selectedDate || this.props.defaultDate,
value: this.props.selectedDate,
showArrowButtons: true,
}
: undefined
}
closeOnSelection={true}
onChange={this.onDateSelected}
value={this.props.selectedDate || this.props.defaultDate}
value={this.props.selectedDate}
/>
) : (
<DateRangeInput
@ -121,7 +121,6 @@ class DatePickerComponent extends React.Component<DatePickerComponentProps> {
export interface DatePickerComponentProps extends ComponentProps {
label: string;
defaultDate?: Date;
dateFormat: string;
enableTimePicker?: boolean;
selectedDate?: Date;

View File

@ -228,7 +228,8 @@ class DropDownComponent extends React.Component<DropDownComponentProps> {
rightIcon={IconNames.CHEVRON_DOWN}
text={
!_.isEmpty(this.props.options) &&
this.props.selectedIndex !== undefined
this.props.selectedIndex !== undefined &&
this.props.selectedIndex > -1
? this.props.options[this.props.selectedIndex].label
: "-- Empty --"
}

View File

@ -144,10 +144,19 @@ const DraggableComponent = (props: DraggableComponentProps) => {
props.widgetId === propertyPaneState.widgetId) ||
props.widgetId !== propertyPaneState.widgetId
) {
AnalyticsUtil.logEvent("PROPERTY_PANE_OPEN_CLICK", {
widgetType: props.type,
widgetId: props.widgetId,
});
showPropertyPane && showPropertyPane(props.widgetId, undefined, true);
} else {
AnalyticsUtil.logEvent("PROPERTY_PANE_CLOSE_CLICK", {
widgetType: props.type,
widgetId: props.widgetId,
});
showPropertyPane && showPropertyPane();
}
e.preventDefault();
e.stopPropagation();
};

View File

@ -318,7 +318,7 @@ class DynamicActionCreator extends React.Component<Props & ReduxStateProps> {
) => void,
) => {
return (
<div style={{ paddingLeft: 5 }}>
<div>
{selectedOption.arguments.map(arg => {
switch (arg.field) {
case "ACTION_SELECTOR_FIELD":
@ -357,14 +357,15 @@ class DynamicActionCreator extends React.Component<Props & ReduxStateProps> {
);
case "TEXT_FIELD":
return (
<React.Fragment key={arg.label}>
<ControlWrapper key={arg.label}>
<label>{arg.label}</label>
<InputText
label={arg.label}
value={arg.getSelectedValue(value, false)}
onChange={e => handleUpdate(e, arg.valueChangeHandler)}
isValid={true}
/>
</React.Fragment>
</ControlWrapper>
);
case "ALERT_TYPE_SELECTOR_FIELD":
return (

View File

@ -30,6 +30,8 @@ export interface ControlData {
id: string;
label: string;
propertyName: string;
helpText?: string;
isJSConvertible?: boolean;
controlType: ControlType;
propertyValue?: any;
isValid: boolean;

View File

@ -11,8 +11,9 @@ export function InputText(props: {
onChange: (event: React.ChangeEvent<HTMLTextAreaElement> | string) => void;
isValid: boolean;
validationMessage?: string;
placeholder?: string;
}) {
const { validationMessage, value, isValid, onChange } = props;
const { validationMessage, value, isValid, onChange, placeholder } = props;
return (
<StyledDynamicInput>
<DynamicAutocompleteInput
@ -26,6 +27,7 @@ export function InputText(props: {
}}
theme={"DARK"}
singleLine={false}
placeholder={placeholder}
/>
</StyledDynamicInput>
);
@ -33,7 +35,13 @@ export function InputText(props: {
class InputTextControl extends BaseControl<InputControlProps> {
render() {
const { validationMessage, propertyValue, isValid, label } = this.props;
const {
validationMessage,
propertyValue,
isValid,
label,
placeholderText,
} = this.props;
return (
<InputText
label={label}
@ -41,6 +49,7 @@ class InputTextControl extends BaseControl<InputControlProps> {
onChange={this.onTextChange}
isValid={isValid}
validationMessage={validationMessage}
placeholder={placeholderText}
/>
);
}

View File

@ -10,6 +10,7 @@ import { ControlType } from "constants/PropertyControlConstants";
import styled from "constants/DefaultTheme";
import { FormIcons } from "icons/FormIcons";
import { AnyStyledComponent } from "styled-components";
import { generateReactKey } from "utils/generators";
const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)`
padding: 5px 5px;
@ -28,16 +29,64 @@ const StyledOptionControlWrapper = styled(ControlWrapper)`
padding-right: 16px;
`;
class OptionControl extends BaseControl<ControlProps> {
function updateOptionLabel<T>(
options: Array<T>,
index: number,
updatedLabel: string,
) {
return options.map((option: T, optionIndex) => {
if (index !== optionIndex) {
return option;
}
return {
...option,
label: updatedLabel,
};
});
}
function updateOptionValue<T>(
options: Array<T>,
index: number,
updatedValue: string,
) {
return options.map((option, optionIndex) => {
if (index !== optionIndex) {
return option;
}
return {
...option,
value: updatedValue,
};
});
}
type DropDownOptionWithKey = DropdownOption & {
key: string;
};
class OptionControl extends BaseControl<
ControlProps,
{
renderOptions: DropDownOptionWithKey[];
}
> {
constructor(props: ControlProps) {
super(props);
this.state = {
renderOptions: [],
};
}
render() {
const options: DropdownOption[] = this.props.propertyValue || [{}];
const { renderOptions } = this.state;
debugger;
return (
<React.Fragment>
{options.map((option, index) => {
{renderOptions.map((option, index) => {
return (
<StyledOptionControlWrapper
orientation={"HORIZONTAL"}
key={option.value}
key={option.key}
>
<StyledOptionControlInputGroup
type={"text"}
@ -76,49 +125,100 @@ class OptionControl extends BaseControl<ControlProps> {
);
}
componentDidMount() {
const { propertyValue } = this.props;
const options: DropdownOption[] = Array.isArray(propertyValue)
? propertyValue
: [{}];
options.map(option => {
return {
...option,
key: generateReactKey(),
};
});
this.setState({
renderOptions: options.map(option => {
return {
...option,
key: generateReactKey(),
};
}),
});
}
deleteOption = (index: number) => {
const options: DropdownOption[] = this.props.propertyValue.slice();
options.splice(index, 1);
this.updateProperty("options", options);
const { propertyValue } = this.props;
const options: DropdownOption[] = Array.isArray(propertyValue)
? propertyValue
: [{}];
const { renderOptions } = this.state;
const newOptions = options.filter((o, i) => i !== index);
const newRenderOptions = renderOptions.filter((o, i) => i !== index);
this.updateProperty("options", newOptions);
this.setState({
renderOptions: newRenderOptions,
});
};
updateOptionLabel = (index: number, updatedLabel: string) => {
const options: DropdownOption[] = this.props.propertyValue;
const { propertyValue } = this.props;
const options: DropdownOption[] = Array.isArray(propertyValue)
? propertyValue
: [{}];
this.updateProperty(
"options",
options.map((option, optionIndex) => {
if (index !== optionIndex) {
return option;
}
return {
...option,
label: updatedLabel,
};
}),
updateOptionLabel(options, index, updatedLabel),
);
this.setState({
renderOptions: updateOptionLabel(
this.state.renderOptions,
index,
updatedLabel,
),
});
};
updateOptionValue = (index: number, updatedValue: string) => {
const options: DropdownOption[] = this.props.propertyValue;
const { propertyValue } = this.props;
const options: DropdownOption[] = Array.isArray(propertyValue)
? propertyValue
: [{}];
this.updateProperty(
"options",
options.map((option, optionIndex) => {
if (index !== optionIndex) {
return option;
}
return {
...option,
value: updatedValue,
};
}),
updateOptionValue(options, index, updatedValue),
);
this.setState({
renderOptions: updateOptionValue(
this.state.renderOptions,
index,
updatedValue,
),
});
};
addOption = () => {
const options: DropdownOption[] = this.props.propertyValue
? this.props.propertyValue.slice()
: [];
const { propertyValue } = this.props;
const options: DropdownOption[] = Array.isArray(propertyValue)
? propertyValue
: [{}];
const { renderOptions } = this.state;
options.push({ label: "", value: "" });
renderOptions.push({
label: "",
value: "",
key: generateReactKey(),
});
this.setState({
renderOptions: renderOptions,
});
this.updateProperty("options", options);
};

View File

@ -19,10 +19,3 @@ export type ControlType =
| "TIME_ZONE"
| "CODE_EDITOR"
| "COLUMN_ACTION_SELECTOR";
export const CONVERTIBLE_CONTROLS = [
"SWITCH",
"OPTION_INPUT",
"ACTION_SELECTOR",
"DATE_PICKER",
];

View File

@ -150,6 +150,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
FILE_PICKER_WIDGET: {
rows: 1,
files: [],
label: "Select Files",
columns: 4,
widgetName: "FilePicker",
isDefaultClickDisabled: true,
@ -201,14 +202,14 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
isDefaultClickDisabled: true,
},
FORM_WIDGET: {
rows: 10,
columns: 10,
rows: 13,
columns: 6,
widgetName: "Form",
blueprint: {
view: [
{
type: "TEXT_WIDGET",
size: { rows: 1, cols: 4 },
size: { rows: 1, cols: 12 },
position: { top: 0, left: 0 },
props: {
text: "Title",
@ -217,8 +218,8 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
},
{
type: "FORM_BUTTON_WIDGET",
size: { rows: 1, cols: 3 },
position: { top: 8, left: 13 },
size: { rows: 1, cols: 4 },
position: { top: 11, left: 12 },
props: {
text: "Submit",
buttonStyle: "PRIMARY_BUTTON",
@ -228,8 +229,8 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
},
{
type: "FORM_BUTTON_WIDGET",
size: { rows: 1, cols: 3 },
position: { top: 8, left: 10 },
size: { rows: 1, cols: 4 },
position: { top: 11, left: 8 },
props: {
text: "Reset",
buttonStyle: "SECONDARY_BUTTON",

View File

@ -1,6 +1,5 @@
import React from "react";
import _ from "lodash";
import { CONVERTIBLE_CONTROLS } from "constants/PropertyControlConstants";
import {
ControlPropertyLabelContainer,
ControlWrapper,
@ -10,6 +9,7 @@ import { ControlIcons } from "icons/ControlIcons";
import PropertyControlFactory from "utils/PropertyControlFactory";
import { WidgetProps } from "widgets/BaseWidget";
import { ControlConfig } from "reducers/entityReducers/propertyPaneConfigReducer";
import { Tooltip } from "@blueprintjs/core";
type Props = {
widgetProperties: WidgetProps;
@ -18,6 +18,47 @@ type Props = {
onPropertyChange: (propertyName: string, propertyValue: any) => void;
};
function UnderlinedLabel({
tooltip,
label,
}: {
tooltip?: string;
label: string;
}) {
const toolTipDefined = tooltip !== undefined;
return (
<Tooltip disabled={!toolTipDefined} content={tooltip} hoverOpenDelay={200}>
<div
style={
toolTipDefined
? {
height: "20px",
cursor: "help",
}
: {
height: "20px",
}
}
>
{label}
<span
style={
toolTipDefined
? {
borderBottom: "1px dashed",
width: "100%",
display: "inline-block",
position: "relative",
top: "-15px",
}
: {}
}
></span>
</div>
</Tooltip>
);
}
const PropertyControl = (props: Props) => {
const {
widgetProperties,
@ -54,7 +95,7 @@ const PropertyControl = (props: Props) => {
["dynamicProperties", propertyName],
false,
);
const isConvertible = CONVERTIBLE_CONTROLS.indexOf(config.controlType) > -1;
const isConvertible = !!propertyConfig.isJSConvertible;
const className = propertyConfig.label
.split(" ")
.join("")
@ -71,7 +112,11 @@ const PropertyControl = (props: Props) => {
}
>
<ControlPropertyLabelContainer>
<label>{label}</label>
<UnderlinedLabel
tooltip={propertyConfig.helpText}
label={label}
></UnderlinedLabel>
{isConvertible && (
<JSToggleButton
active={isDynamic}

View File

@ -82,6 +82,12 @@ class PropertyPane extends Component<
<CloseButton
onClick={(e: any) => {
AnalyticsUtil.logEvent("PROPERTY_PANE_CLOSE_CLICK", {
widgetType: this.props.widgetProperties
? this.props.widgetProperties.type
: "",
widgetId: this.props.widgetId,
});
this.props.hidePropertyPane();
e.preventDefault();
e.stopPropagation();
@ -167,6 +173,40 @@ class PropertyPane extends Component<
}
};
componentDidUpdate(prevProps: PropertyPaneProps & PropertyPaneFunctions) {
if (
this.props.widgetId !== prevProps.widgetId &&
this.props.widgetId !== undefined
) {
if (prevProps.widgetId && prevProps.widgetProperties) {
AnalyticsUtil.logEvent("PROPERTY_PANE_CLOSE", {
widgetType: prevProps.widgetProperties.type,
widgetId: prevProps.widgetId,
});
}
if (this.props.widgetProperties) {
AnalyticsUtil.logEvent("PROPERTY_PANE_OPEN", {
widgetType: this.props.widgetProperties.type,
widgetId: this.props.widgetId,
});
}
}
if (
this.props.widgetId === prevProps.widgetId &&
this.props.isVisible &&
!prevProps.isVisible &&
this.props.widgetProperties !== undefined
) {
AnalyticsUtil.logEvent("PROPERTY_PANE_OPEN", {
widgetType: this.props.widgetProperties.type,
widgetId: this.props.widgetId,
});
}
return true;
}
onPropertyChange(propertyName: string, propertyValue: any) {
this.props.updateWidgetProperty(
this.props.widgetId,

View File

@ -134,12 +134,10 @@ export function* evaluateDynamicBoundValueSaga(path: string): any {
export function* getActionParams(jsonPathKeys: string[] | undefined) {
if (_.isNil(jsonPathKeys)) return [];
const values: any = _.flatten(
yield all(
jsonPathKeys.map((jsonPath: string) => {
return call(evaluateDynamicBoundValueSaga, jsonPath);
}),
),
const values: any = yield all(
jsonPathKeys.map((jsonPath: string) => {
return call(evaluateDynamicBoundValueSaga, jsonPath);
}),
);
const dynamicBindings: Record<string, string> = {};
jsonPathKeys.forEach((key, i) => {
@ -302,7 +300,13 @@ export function* executeActionTriggers(
AnalyticsUtil.logEvent("NAVIGATE", {
navUrl: trigger.payload.url,
});
window.location.href = trigger.payload.url;
// Add a default protocol if it doesn't exist.
let url = trigger.payload.url;
if (url.indexOf("://") === -1) {
url = "https://" + url;
}
window.location.assign(url);
if (event.callback) event.callback({ success: true });
} else {
if (event.callback) event.callback({ success: false });
@ -329,10 +333,12 @@ export function* executeAppAction(action: ReduxAction<ExecuteActionPayload>) {
const { dynamicString, event, responseData } = action.payload;
const tree = yield select(evaluateDataTree);
const { triggers } = getDynamicValue(dynamicString, tree, responseData, true);
if (triggers) {
if (triggers && triggers.length) {
yield all(
triggers.map(trigger => call(executeActionTriggers, trigger, event)),
);
} else {
if (event.callback) event.callback({ success: true });
}
}
@ -585,8 +591,7 @@ function* executePageLoadActionsSaga(action: ReduxAction<PageAction[][]>) {
const pageActions = action.payload;
for (const actionSet of pageActions) {
const apiResponses = yield select(getActionResponses);
const filteredSet = actionSet.filter(action => !apiResponses[action.id]);
yield* yield all(filteredSet.map(a => call(executePageLoadAction, a)));
yield* yield all(actionSet.map(a => call(executePageLoadAction, a)));
}
}
@ -687,7 +692,7 @@ function* copyActionSaga(
export function* watchActionSagas() {
yield all([
takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga),
takeLatest(ReduxActionTypes.EXECUTE_ACTION, executeAppAction),
takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeAppAction),
takeLatest(ReduxActionTypes.RUN_API_REQUEST, runApiActionSaga),
takeLatest(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),

View File

@ -80,7 +80,8 @@ export function* errorSaga(
type,
payload: { error, show = true },
} = errorAction;
const message = error.message || ActionErrorDisplayMap[type](error);
const message =
error && error.message ? error.message : ActionErrorDisplayMap[type](error);
if (show) AppToaster.show({ message, type: ToastType.ERROR });
yield put({
type: ReduxActionTypes.REPORT_ERROR,

View File

@ -123,12 +123,12 @@ export function* fetchPageSaga(
if (isValidResponse) {
// Get Canvas payload
const canvasWidgetsPayload = getCanvasWidgetsPayload(fetchPageResponse);
// Execute page load actions
yield put(executePageLoadActions(canvasWidgetsPayload.pageActions));
// Update the canvas
yield put(updateCanvas(canvasWidgetsPayload));
// dispatch fetch page success
yield put(fetchPageSuccess());
// Execute page load actions
yield put(executePageLoadActions(canvasWidgetsPayload.pageActions));
}
} catch (error) {
yield put({
@ -155,8 +155,6 @@ export function* fetchPublishedPageSaga(
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
const canvasWidgetsPayload = getCanvasWidgetsPayload(response);
// Execute page load actions
yield put(executePageLoadActions(canvasWidgetsPayload.pageActions));
yield put(updateCanvas(canvasWidgetsPayload));
yield put({
type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
@ -167,6 +165,8 @@ export function* fetchPublishedPageSaga(
pageWidgetId: canvasWidgetsPayload.pageWidgetId,
},
});
// Execute page load actions
yield put(executePageLoadActions(canvasWidgetsPayload.pageActions));
}
} catch (error) {
yield put({

View File

@ -24,7 +24,7 @@ import {
takeLatest,
all,
} from "redux-saga/effects";
import { getNextEntityName } from "utils/AppsmithUtils";
import { convertToString, getNextEntityName } from "utils/AppsmithUtils";
import {
SetWidgetDynamicPropertyPayload,
updateWidgetProperty,
@ -262,7 +262,8 @@ function* setWidgetDynamicPropertySaga(
};
if (isDynamic) {
dynamicProperties[propertyName] = true;
yield put(updateWidgetProperty(widgetId, propertyName, "{{}}"));
const value = convertToString(widget[propertyName]);
yield put(updateWidgetProperty(widgetId, propertyName, value));
} else {
delete dynamicProperties[propertyName];
yield put(updateWidgetProperty(widgetId, propertyName, undefined));

View File

@ -22,6 +22,7 @@ import {
getResponseErrorMessage,
callAPI,
} from "./ErrorSagas";
import * as Sentry from "@sentry/browser";
import { fetchOrgsSaga } from "./OrgSagas";
@ -295,6 +296,9 @@ export function* setCurrentUserSaga(action: ReduxAction<FetchUserRequest>) {
const me = yield call(fetchUserSaga, action);
if (me) {
AnalyticsUtil.identifyUser(me.id, me);
Sentry.configureScope(function(scope) {
scope.setUser({ email: me.email, id: me.id });
});
resetAuthExpiration();
yield put({
type: ReduxActionTypes.SET_CURRENT_USER_SUCCESS,

View File

@ -42,7 +42,11 @@ export type EventName =
| "SAVE_DATA_SOURCE"
| "NAVIGATE"
| "PAGE_LOAD"
| "NAVIGATE_EDITOR";
| "NAVIGATE_EDITOR"
| "PROPERTY_PANE_OPEN"
| "PROPERTY_PANE_CLOSE"
| "PROPERTY_PANE_OPEN_CLICK"
| "PROPERTY_PANE_CLOSE_CLICK";
export type Gender = "MALE" | "FEMALE";
export interface User {

View File

@ -1,4 +1,4 @@
import { ReduxAction } from "../constants/ReduxActionConstants";
import { ReduxAction } from "constants/ReduxActionConstants";
import { getAppsmithConfigs } from "configs";
import * as Sentry from "@sentry/browser";
import AnalyticsUtil from "./AnalyticsUtil";
@ -77,3 +77,14 @@ export const getNextEntityName = (prefix: string, existingNames: string[]) => {
export const noop = () => {
console.log("noop");
};
export const convertToString = (value: any): string => {
if (_.isUndefined(value)) {
return "";
}
if (_.isObject(value)) {
return JSON.stringify(value, null, 2);
}
if (_.isString(value)) return value;
return value.toString();
};

View File

@ -1,12 +1,18 @@
import { WidgetType } from "constants/WidgetConstants";
import WidgetFactory from "./WidgetFactory";
import {
VALIDATION_TYPES,
ValidationResponse,
ValidationType,
Validator,
} from "constants/WidgetValidation";
// TODO: need to be strict about what the key can be
export const BASE_WIDGET_VALIDATION = {
isLoading: VALIDATION_TYPES.BOOLEAN,
isVisible: VALIDATION_TYPES.BOOLEAN,
isDisabled: VALIDATION_TYPES.BOOLEAN,
};
export type WidgetPropertyValidationType = Record<string, ValidationType>;
class ValidationFactory {

View File

@ -84,20 +84,16 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
};
}
let isValid = _.isBoolean(value);
const isBoolean = _.isBoolean(value);
const isStringTrueFalse = value === "true" || value === "false";
const isValid = isBoolean || isStringTrueFalse;
if (isStringTrueFalse) parsed = value !== "false";
if (!isValid) {
try {
parsed = !!value;
isValid = true;
} catch (e) {
console.error(`Error when parsing ${value} to boolean`);
console.error(e);
return {
isValid: false,
parsed: false,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
};
}
return {
isValid: isValid,
parsed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
};
}
return { isValid, parsed };
},
@ -217,6 +213,15 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
return { isValid, parsed };
},
[VALIDATION_TYPES.DATE]: (value: any): ValidationResponse => {
if (value === undefined) {
const today = new Date();
today.setHours(0, 0, 0);
return {
isValid: false,
parsed: today,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date`,
};
}
const isValid = moment(value).isValid();
const parsed = isValid ? moment(value).toDate() : new Date();
return {

View File

@ -4,7 +4,10 @@ import {
WidgetProps,
WidgetDataProps,
} from "widgets/BaseWidget";
import { WidgetPropertyValidationType } from "./ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "./ValidationFactory";
import React from "react";
type WidgetDerivedPropertyType = any;
@ -78,7 +81,7 @@ class WidgetFactory {
const map = this.widgetPropValidationMap.get(widgetType);
if (!map) {
console.error("Widget type validation is not defined");
return {};
return BASE_WIDGET_VALIDATION;
}
return map;
}

View File

@ -27,7 +27,10 @@ import { EditorContext } from "components/editorComponents/EditorContextProvider
import { PositionTypes } from "constants/WidgetConstants";
import ErrorBoundary from "components/editorComponents/ErrorBoundry";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
BASE_WIDGET_VALIDATION,
WidgetPropertyValidationType,
} from "utils/ValidationFactory";
import {
DerivedPropertiesMap,
TriggerPropertiesMap,
@ -66,7 +69,7 @@ abstract class BaseWidget<
// Needed to send a default no validation option. In case a widget needs
// validation implement this in the widget class again
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {};
return BASE_WIDGET_VALIDATION;
}
static getDerivedPropertiesMap(): DerivedPropertiesMap {

View File

@ -5,7 +5,10 @@ import ButtonComponent, {
ButtonType,
} from "components/designSystems/blueprint/ButtonComponent";
import { EventType } from "constants/ActionConstants";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
@ -30,9 +33,8 @@ class ButtonWidget extends BaseWidget<
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
text: VALIDATION_TYPES.TEXT,
isDisabled: VALIDATION_TYPES.BOOLEAN,
isVisible: VALIDATION_TYPES.BOOLEAN,
buttonStyle: VALIDATION_TYPES.TEXT,
};
}

View File

@ -4,13 +4,16 @@ import { WidgetType } from "constants/WidgetConstants";
import CheckboxComponent from "components/designSystems/blueprint/CheckboxComponent";
import { EventType } from "constants/ActionConstants";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
class CheckboxWidget extends BaseWidget<CheckboxWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
isDisabled: VALIDATION_TYPES.BOOLEAN,
...BASE_WIDGET_VALIDATION,
label: VALIDATION_TYPES.TEXT,
defaultCheckedState: VALIDATION_TYPES.BOOLEAN,
};
@ -34,18 +37,16 @@ class CheckboxWidget extends BaseWidget<CheckboxWidgetProps, WidgetState> {
componentDidUpdate(prevProps: CheckboxWidgetProps) {
super.componentDidUpdate(prevProps);
if (this.props.defaultCheckedState.toString()) {
if (
(this.props.isChecked !== prevProps.isChecked &&
this.props.isChecked === undefined) ||
this.props.defaultCheckedState.toString() !==
prevProps.defaultCheckedState.toString()
) {
this.updateWidgetMetaProperty(
"isChecked",
this.props.defaultCheckedState,
);
}
if (
(this.props.isChecked !== prevProps.isChecked &&
this.props.isChecked === undefined) ||
this.props.defaultCheckedState.toString() !==
prevProps.defaultCheckedState.toString()
) {
this.updateWidgetMetaProperty(
"isChecked",
this.props.defaultCheckedState,
);
}
}

View File

@ -3,7 +3,10 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/ActionConstants";
import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import {
DerivedPropertiesMap,
@ -13,8 +16,8 @@ import {
class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
defaultDate: VALIDATION_TYPES.DATE,
selectedDate: VALIDATION_TYPES.DATE,
timezone: VALIDATION_TYPES.TEXT,
enableTimePicker: VALIDATION_TYPES.BOOLEAN,
dateFormat: VALIDATION_TYPES.TEXT,
@ -37,6 +40,21 @@ class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
onDateSelected: true,
};
}
componentDidUpdate(prevProps: DatePickerWidgetProps) {
super.componentDidUpdate(prevProps);
if (this.props.defaultDate) {
if (
(this.props.selectedDate !== prevProps.selectedDate &&
this.props.selectedDate === undefined) ||
this.props.defaultDate.toDateString() !==
prevProps.defaultDate.toDateString()
) {
this.updateWidgetMetaProperty("selectedDate", this.props.defaultDate);
}
}
}
getPageView() {
return (
<DatePickerComponent
@ -45,7 +63,6 @@ class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
widgetId={this.props.widgetId}
timezone={this.props.timezone}
enableTimePicker={this.props.enableTimePicker}
defaultDate={this.props.defaultDate}
datePickerType={"DATE_PICKER"}
onDateSelected={this.onDateSelected}
selectedDate={this.props.selectedDate}
@ -74,7 +91,7 @@ class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
export type DatePickerType = "DATE_PICKER" | "DATE_RANGE_PICKER";
export interface DatePickerWidgetProps extends WidgetProps {
defaultDate?: Date;
defaultDate: Date;
selectedDate: Date;
timezone?: string;
enableTimePicker: boolean;

View File

@ -4,19 +4,24 @@ import { WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/ActionConstants";
import DropDownComponent from "components/designSystems/blueprint/DropdownComponent";
import _ from "lodash";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
placeholderText: VALIDATION_TYPES.TEXT,
label: VALIDATION_TYPES.TEXT,
options: VALIDATION_TYPES.OPTIONS_DATA,
selectionType: VALIDATION_TYPES.TEXT,
selectedIndexArr: VALIDATION_TYPES.ARRAY,
isRequired: VALIDATION_TYPES.BOOLEAN,
defaultOptionValue: VALIDATION_TYPES.TEXT,
};
}
static getDerivedPropertiesMap() {
@ -60,7 +65,8 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
) {
this.updateWidgetMetaProperty("selectedIndex", undefined);
this.updateWidgetMetaProperty("selectedIndexArr", []);
} else if (this.props.defaultOptionValue) {
}
if (this.props.defaultOptionValue) {
if (
(this.props.selectedIndex !== prevProps.selectedIndex &&
this.props.selectedIndex === undefined) ||
@ -69,7 +75,11 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
const selectedIndex = _.findIndex(this.props.options, option => {
return option.value === this.props.defaultOptionValue;
});
this.updateWidgetMetaProperty("selectedIndex", selectedIndex);
if (selectedIndex > -1) {
this.updateWidgetMetaProperty("selectedIndex", selectedIndex);
} else {
this.updateWidgetMetaProperty("selectedIndex", undefined);
}
}
}
}

View File

@ -7,7 +7,10 @@ import Webcam from "@uppy/webcam";
import Url from "@uppy/url";
import OneDrive from "@uppy/onedrive";
import FilePickerComponent from "components/designSystems/appsmith/FilePickerComponent";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { EventType, ExecutionResult } from "constants/ActionConstants";
import {
@ -28,9 +31,11 @@ class FilePickerWidget extends BaseWidget<FilePickerWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
label: VALIDATION_TYPES.TEXT,
maxNumFiles: VALIDATION_TYPES.NUMBER,
allowedFileTypes: VALIDATION_TYPES.ARRAY,
files: VALIDATION_TYPES.ARRAY,
isRequired: VALIDATION_TYPES.BOOLEAN,
};
}

View File

@ -5,7 +5,10 @@ import ButtonComponent, {
ButtonType,
} from "components/designSystems/blueprint/ButtonComponent";
import { EventType, ExecutionResult } from "constants/ActionConstants";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
BASE_WIDGET_VALIDATION,
WidgetPropertyValidationType,
} from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
@ -30,10 +33,11 @@ class FormButtonWidget extends BaseWidget<
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
text: VALIDATION_TYPES.TEXT,
disabledWhenInvalid: VALIDATION_TYPES.BOOLEAN,
isVisible: VALIDATION_TYPES.BOOLEAN,
buttonStyle: VALIDATION_TYPES.TEXT,
buttonType: VALIDATION_TYPES.TEXT,
};
}

View File

@ -2,12 +2,16 @@ import * as React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import ImageComponent from "components/designSystems/appsmith/ImageComponent";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
class ImageWidget extends BaseWidget<ImageWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
image: VALIDATION_TYPES.TEXT,
imageShape: VALIDATION_TYPES.TEXT,
defaultImage: VALIDATION_TYPES.TEXT,

View File

@ -5,7 +5,10 @@ import InputComponent, {
InputComponentProps,
} from "components/designSystems/blueprint/InputComponent";
import { EventType } from "constants/ActionConstants";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { FIELD_REQUIRED_ERROR } from "constants/messages";
import {
@ -16,6 +19,7 @@ import {
class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
inputType: VALIDATION_TYPES.TEXT,
defaultText: VALIDATION_TYPES.TEXT,
isDisabled: VALIDATION_TYPES.BOOLEAN,
@ -30,7 +34,9 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
inputValidators: VALIDATION_TYPES.ARRAY,
focusIndex: VALIDATION_TYPES.NUMBER,
isAutoFocusEnabled: VALIDATION_TYPES.BOOLEAN,
onTextChanged: VALIDATION_TYPES.TEXT,
isRequired: VALIDATION_TYPES.BOOLEAN,
isValid: VALIDATION_TYPES.BOOLEAN,
};
}
static getTriggerPropertyMap(): TriggerPropertiesMap {
@ -59,20 +65,17 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
componentDidMount() {
super.componentDidMount();
if (this.props.defaultText) {
this.updateWidgetMetaProperty("text", this.props.defaultText);
}
const text = this.props.defaultText || "";
this.updateWidgetMetaProperty("text", text);
}
componentDidUpdate(prevProps: InputWidgetProps) {
super.componentDidUpdate(prevProps);
if (this.props.defaultText) {
if (
(this.props.text !== prevProps.text && this.props.text === undefined) ||
this.props.defaultText !== prevProps.defaultText
) {
this.updateWidgetMetaProperty("text", this.props.defaultText);
}
if (
(this.props.text !== prevProps.text && this.props.text === undefined) ||
this.props.defaultText !== prevProps.defaultText
) {
this.updateWidgetMetaProperty("text", this.props.defaultText);
}
}

View File

@ -3,16 +3,22 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import RadioGroupComponent from "components/designSystems/blueprint/RadioGroupComponent";
import { EventType } from "constants/ActionConstants";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
class RadioGroupWidget extends BaseWidget<RadioGroupWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
label: VALIDATION_TYPES.TEXT,
options: VALIDATION_TYPES.OPTIONS_DATA,
selectedOptionValue: VALIDATION_TYPES.TEXT,
onSelectionChange: VALIDATION_TYPES.TEXT,
defaultOptionValue: VALIDATION_TYPES.TEXT,
isRequired: VALIDATION_TYPES.BOOLEAN,
};
}
@ -41,17 +47,15 @@ class RadioGroupWidget extends BaseWidget<RadioGroupWidgetProps, WidgetState> {
componentDidUpdate(prevProps: RadioGroupWidgetProps) {
super.componentDidUpdate(prevProps);
if (this.props.defaultOptionValue) {
if (
(this.props.selectedOptionValue !== prevProps.selectedOptionValue &&
this.props.selectedOptionValue === undefined) ||
this.props.defaultOptionValue !== prevProps.defaultOptionValue
) {
this.updateWidgetMetaProperty(
"selectedOptionValue",
this.props.defaultOptionValue,
);
}
if (
(this.props.selectedOptionValue !== prevProps.selectedOptionValue &&
this.props.selectedOptionValue === undefined) ||
this.props.defaultOptionValue !== prevProps.defaultOptionValue
) {
this.updateWidgetMetaProperty(
"selectedOptionValue",
this.props.defaultOptionValue,
);
}
}
getPageView() {

View File

@ -3,12 +3,16 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { Intent } from "@blueprintjs/core";
import SpinnerComponent from "components/designSystems/blueprint/SpinnerComponent";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
class SpinnerWidget extends BaseWidget<SpinnerWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
size: VALIDATION_TYPES.NUMBER,
value: VALIDATION_TYPES.NUMBER,
ellipsize: VALIDATION_TYPES.BOOLEAN,

View File

@ -6,7 +6,10 @@ import { forIn } from "lodash";
import TableComponent from "components/designSystems/syncfusion/TableComponent";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { ColumnModel } from "@syncfusion/ej2-grids";
import { ColumnDirTypecast } from "@syncfusion/ej2-react-grids";
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
@ -30,6 +33,7 @@ function constructColumns(data: object[]): ColumnModel[] | ColumnDirTypecast[] {
class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
tableData: VALIDATION_TYPES.TABLE_DATA,
nextPageKey: VALIDATION_TYPES.TEXT,
prevPageKey: VALIDATION_TYPES.TEXT,
@ -156,11 +160,6 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
}
}
type RowData = {
rowIndex: number;
};
type SelectedRow = object & RowData;
export interface TableWidgetProps extends WidgetProps {
nextPageKey?: string;
prevPageKey?: string;

View File

@ -3,7 +3,10 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import TextComponent from "components/designSystems/blueprint/TextComponent";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
const LINE_HEIGHTS: { [key in TextStyle]: number } = {
// The following values are arrived at by multiplying line-height with font-size
@ -16,9 +19,10 @@ const LINE_HEIGHTS: { [key in TextStyle]: number } = {
class TextWidget extends BaseWidget<TextWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
text: VALIDATION_TYPES.TEXT,
textStyle: VALIDATION_TYPES.TEXT,
isVisible: VALIDATION_TYPES.BOOLEAN,
shouldScroll: VALIDATION_TYPES.BOOLEAN,
};
}