Fix: Tabs widget refactor with new nested property validation (#4014)

* dip

* test cases first commit

* Adding Tabs migrator.

* fixing tests.

* bug fix.

* selected tab fix

* missed commit

* fixing bugs

* Fixing tab name bugs.

* close property pane when dragging or resizing

* migration changes.

* release rebase changes.

* adding List factory

* remove dynamic bindings on deleting tabs.

* Adding validation messages for nested properties as well

* fixing validation issue.

* tabs visibility validation.

* missed commit

* Fixing broken cypress tests.

* Fixing broken tests.
This commit is contained in:
Ashok Kumar M 2021-04-27 12:46:54 +05:30 committed by GitHub
parent 0b2bd452df
commit 8f7cc87801
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 2110 additions and 177 deletions

View File

@ -22,10 +22,13 @@ describe("Container Widget Functionality", function() {
/**
* @param{Text} Random Colour
*/
cy.testCodeMirror(this.data.colour);
cy.get(widgetsPage.backgroundcolorPicker)
.first()
.click({ force: true });
cy.xpath(widgetsPage.greenColor).click();
cy.get(widgetsPage.containerD)
.should("have.css", "background-color")
.and("eq", this.data.rgbValue);
.and("eq", "rgb(3, 179, 101)");
/**
* @param{toggleButton Css} Assert to be checked
*/
@ -41,7 +44,7 @@ describe("Container Widget Functionality", function() {
cy.get(widgetsPage.containerD)
.eq(0)
.should("have.css", "background-color")
.and("eq", this.data.rgbValue);
.and("eq", "rgb(3, 179, 101)");
});
afterEach(() => {
// put your clean up code if any

View File

@ -65,6 +65,7 @@
"verticalCenter": ".t--icon-tab-CENTER",
"verticalBottom": ".t--icon-tab-BOTTOM",
"textColor": ".t--property-control-textcolor input",
"backgroundcolorPicker": ".t--property-control-backgroundcolor input",
"greenColor": "//div[@color='rgb(3, 179, 101)']",
"toggleJsColor": ".t--property-control-textcolor .t--js-toggle",
"backgroundColor": ".t--property-control-cellbackground input",

View File

@ -15,7 +15,7 @@ module.exports = {
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node", "css"],
moduleDirectories: ["node_modules", "src", "test"],
transformIgnorePatterns: [
"<rootDir>/node_modules/(?!codemirror|react-dnd|dnd-core|@babel|(@blueprintjs/core/lib/esnext)|(@blueprintjs/core/lib/esm)|@github)",
"<rootDir>/node_modules/(?!codemirror|react-dnd|dnd-core|@babel|(@blueprintjs/core/lib/esnext)|(@blueprintjs/core/lib/esm)|@github|lodash-es)",
],
moduleNameMapper: {
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js",
@ -23,6 +23,7 @@ module.exports = {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
"<rootDir>/test/__mocks__/fileMock.js",
"^worker-loader!": "<rootDir>/test/__mocks__/workerMock.js",
"^!!raw-loader!": "<rootDir>/test/__mocks__/derivedMock.js",
"test/(.*)": "<rootDir>/test/$1",
},
globals: {

View File

@ -149,6 +149,7 @@
"start-prod": "REACT_APP_ENVIRONMENT=PRODUCTION craco start",
"cytest": "REACT_APP_TESTING=TESTING REACT_APP_ENVIRONMENT=DEVELOPMENT craco start & ./node_modules/.bin/cypress open",
"test:unit": "$(npm bin)/jest -b --colors --no-cache --coverage --collectCoverage=true --coverageDirectory='../../' --coverageReporters='json-summary'",
"test:jest": "$(npm bin)/jest --watch",
"storybook": "start-storybook -p 9009 -s public",
"build-storybook": "build-storybook -s public"
},
@ -212,6 +213,7 @@
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.21.3",
"eslint-plugin-react-hooks": "^2.3.0",
"factory.ts": "^0.5.1",
"jest-canvas-mock": "^2.3.1",
"mocha": "^7.1.0",
"mocha-junit-reporter": "^1.23.3",

View File

@ -227,12 +227,23 @@ export type WidgetAddChildren = {
}>;
};
export type WidgetUpdateProperty = {
widgetId: string;
propertyPath: string;
propertyValue: any;
};
export const updateWidget = (
operation: WidgetOperation,
widgetId: string,
payload: any,
): ReduxAction<
WidgetAddChild | WidgetMove | WidgetResize | WidgetDelete | WidgetAddChildren
| WidgetAddChild
| WidgetMove
| WidgetResize
| WidgetDelete
| WidgetAddChildren
| WidgetUpdateProperty
> => {
return {
type: ReduxActionTypes["WIDGET_" + operation],

View File

@ -1,7 +1,10 @@
import React, { RefObject, ReactNode, useEffect, useRef } from "react";
import styled, { css } from "styled-components";
import { ComponentProps } from "./BaseComponent";
import { TabsWidgetProps, TabContainerWidgetProps } from "widgets/TabsWidget";
import {
TabsWidgetProps,
TabContainerWidgetProps,
} from "widgets/Tabs/TabsWidget";
import { generateClassName, getCanvasClassName } from "utils/generators";
import { getBorderCSSShorthand } from "constants/DefaultTheme";
import ScrollIndicator from "components/ads/ScrollIndicator";

View File

@ -257,7 +257,7 @@ class PrimaryColumnsControl extends BaseControl<ControlProps> {
this.props.openNextPanel({
...originalColumn,
widgetId: this.props.widgetProperties.widgetId,
propPaneId: this.props.widgetProperties.widgetId,
});
};
//Used to reorder columns

View File

@ -1,12 +1,11 @@
import React, { useState } from "react";
import React, { useCallback } from "react";
import BaseControl, { ControlProps } from "./BaseControl";
import {
StyledHiddenIcon,
StyledInputGroup,
StyledPropertyPaneButton,
StyledVisibleIcon,
StyledDragIcon,
StyledDeleteIcon,
StyledEditIcon,
} from "./StyledControls";
import styled from "constants/DefaultTheme";
import { generateReactKey } from "utils/generators";
@ -63,12 +62,15 @@ type RenderComponentProps = {
deleteOption: (index: number) => void;
updateOption: (index: number, value: string) => void;
toggleVisibility?: (index: number) => void;
onEdit?: (props: any) => void;
};
function TabControlComponent(props: RenderComponentProps) {
const { deleteOption, updateOption, item, index, toggleVisibility } = props;
const { deleteOption, updateOption, item, index } = props;
const debouncedUpdate = debounce(updateOption, 1000);
const [visibility, setVisibility] = useState(item.isVisible);
const handleChange = useCallback(() => props.onEdit && props.onEdit(index), [
index,
]);
return (
<ItemWrapper>
<StyledDragIcon height={20} width={20} />
@ -89,29 +91,12 @@ function TabControlComponent(props: RenderComponentProps) {
deleteOption(index);
}}
/>
{visibility || visibility === undefined ? (
<StyledVisibleIcon
className="t--show-tab-btn"
height={20}
width={20}
marginRight={36}
onClick={() => {
setVisibility(!visibility);
toggleVisibility && toggleVisibility(index);
}}
/>
) : (
<StyledHiddenIcon
className="t--show-tab-btn"
height={20}
width={20}
marginRight={36}
onClick={() => {
setVisibility(!visibility);
toggleVisibility && toggleVisibility(index);
}}
/>
)}
<StyledEditIcon
className="t--edit-column-btn"
height={20}
width={20}
onClick={handleChange}
/>
</ItemWrapper>
);
}
@ -148,15 +133,37 @@ class TabControl extends BaseControl<ControlProps> {
}
}
updateItems = (items: Array<Record<string, unknown>>) => {
this.updateProperty(this.props.propertyName, items);
updateItems = (items: Array<Record<string, any>>) => {
const tabsObj = items.reduce((obj: any, each: any, index: number) => {
obj[each.id] = {
...each,
index,
};
return obj;
}, {});
this.updateProperty(this.props.propertyName, tabsObj);
};
onEdit = (index: number) => {
const tabs: Array<{
id: string;
label: string;
}> = Object.values(this.props.propertyValue);
const tabToChange = tabs[index];
this.props.openNextPanel({
index,
...tabToChange,
propPaneId: this.props.widgetProperties.widgetId,
});
};
render() {
const tabs: Array<{
id: string;
label: string;
}> = _.isString(this.props.propertyValue) ? [] : this.props.propertyValue;
}> = _.isString(this.props.propertyValue)
? []
: Object.values(this.props.propertyValue);
return (
<TabsWrapper>
<DroppableComponent
@ -167,6 +174,7 @@ class TabControl extends BaseControl<ControlProps> {
updateOption={this.updateOption}
updateItems={this.updateItems}
toggleVisibility={this.toggleVisibility}
onEdit={this.onEdit}
/>
<StyledPropertyPaneButtonWrapper>
<StyledPropertyPaneButton
@ -204,51 +212,52 @@ class TabControl extends BaseControl<ControlProps> {
};
deleteOption = (index: number) => {
let tabs: Array<Record<string, unknown>> = this.props.propertyValue.slice();
if (tabs.length === 1) return;
delete tabs[index];
tabs = tabs.filter(Boolean);
this.updateProperty(this.props.propertyName, tabs);
const tabsArray: any = Object.values(this.props.propertyValue);
const itemId = tabsArray[index].id;
if (tabsArray && tabsArray.length === 1) return;
const updatedArray = tabsArray.filter((eachItem: any, i: number) => {
return i !== index;
});
const updatedObj = updatedArray.reduce(
(obj: any, each: any, index: number) => {
obj[each.id] = {
...each,
index,
};
return obj;
},
{},
);
this.deleteProperties([`${this.props.propertyName}.${itemId}.isVisible`]);
this.updateProperty(this.props.propertyName, updatedObj);
};
updateOption = (index: number, updatedLabel: string) => {
const tabs: Array<{
id: string;
label: string;
}> = this.props.propertyValue;
const updatedTabs = tabs.map((tab, tabIndex) => {
if (index === tabIndex) {
return {
...tab,
label: updatedLabel,
};
}
return tab;
});
this.updateProperty(this.props.propertyName, updatedTabs);
const tabsArray: any = Object.values(this.props.propertyValue);
const itemId = tabsArray[index].id;
this.updateProperty(
`${this.props.propertyName}.${itemId}.label`,
updatedLabel,
);
};
addOption = () => {
let tabs: Array<{
id: string;
label: string;
widgetId: string;
isVisible: boolean;
}> = this.props.propertyValue;
let tabs = this.props.propertyValue;
const tabsArray = Object.values(tabs);
const newTabId = generateReactKey({ prefix: "tab" });
const newTabLabel = getNextEntityName(
"Tab ",
tabs.map((tab) => tab.label),
tabsArray.map((tab: any) => tab.label),
);
tabs = [
tabs = {
...tabs,
{
[newTabId]: {
id: newTabId,
label: newTabLabel,
widgetId: generateReactKey(),
isVisible: true,
},
];
};
this.updateProperty(this.props.propertyName, tabs);
};

View File

@ -13,6 +13,7 @@ const FIELD_VALUES: Record<
CANVAS_WIDGET: {},
ICON_WIDGET: {},
SKELETON_WIDGET: {},
TABS_MIGRATOR_WIDGET: {},
CONTAINER_WIDGET: {
backgroundColor: "string",
isVisible: "boolean",
@ -61,8 +62,6 @@ const FIELD_VALUES: Record<
// onSelectionChange: "Function Call",
},
TABS_WIDGET: {
tabs:
"Array<{ label: string, id: string(unique), widgetId: string(unique) }>",
selectedTab: "string",
isVisible: "boolean",
},

View File

@ -83,6 +83,10 @@ export const HelpMap = {
path: "",
searchKey: "Tabs",
},
TABS_MIGRATOR_WIDGET: {
path: "",
searchKey: "",
},
MODAL_WIDGET: {
path: "",
searchKey: "",

View File

@ -340,6 +340,7 @@ export const ReduxActionTypes: { [key: string]: string } = {
UNDO_DELETE_WIDGET: "UNDO_DELETE_WIDGET",
CUT_SELECTED_WIDGET: "CUT_SELECTED_WIDGET",
WIDGET_ADD_CHILDREN: "WIDGET_ADD_CHILDREN",
WIDGET_UPDATE_PROPERTY: "WIDGET_UPDATE_PROPERTY",
SET_EVALUATED_TREE: "SET_EVALUATED_TREE",
SET_EVALUATION_INVERSE_DEPENDENCY_MAP:
"SET_EVALUATION_INVERSE_DEPENDENCY_MAP",

View File

@ -26,6 +26,7 @@ export enum WidgetTypes {
SKELETON_WIDGET = "SKELETON_WIDGET",
LIST_WIDGET = "LIST_WIDGET",
SWITCH_WIDGET = "SWITCH_WIDGET",
TABS_MIGRATOR_WIDGET = "TABS_MIGRATOR_WIDGET",
}
export type WidgetType = keyof typeof WidgetTypes;

View File

@ -16,7 +16,6 @@ export enum VALIDATION_TYPES {
DEFAULT_DATE = "DEFAULT_DATE",
MIN_DATE = "MIN_DATE",
MAX_DATE = "MAX_DATE",
TABS_DATA = "TABS_DATA",
LIST_DATA = "LIST_DATA",
CHART_SERIES_DATA = "CHART_SERIES_DATA",
CUSTOM_FUSION_CHARTS_DATA = "CUSTOM_FUSION_CHARTS_DATA",
@ -30,6 +29,7 @@ export enum VALIDATION_TYPES {
LAT_LONG = "LAT_LONG",
TABLE_PAGE_NO = "TABLE_PAGE_NO",
ROW_INDICES = "ROW_INDICES",
TABS_DATA = "TABS_DATA",
}
export type ValidationResponse = {
@ -43,6 +43,7 @@ export type Validator = (
value: any,
props: WidgetProps,
dataTree?: DataTree,
property?: string,
) => ValidationResponse;
export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.sssZ";

View File

@ -236,10 +236,22 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
columns: 8,
shouldScrollContents: false,
widgetName: "Tabs",
tabs: [
{ label: "Tab 1", id: "tab1", widgetId: "", isVisible: true },
{ label: "Tab 2", id: "tab2", widgetId: "", isVisible: true },
],
tabsObj: {
tab1: {
label: "Tab 1",
id: "tab1",
widgetId: "",
isVisible: true,
index: 0,
},
tab2: {
label: "Tab 2",
id: "tab2",
widgetId: "",
isVisible: true,
index: 1,
},
},
shouldShowTabs: true,
defaultTab: "Tab 1",
blueprint: {
@ -247,18 +259,18 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
{
type: BlueprintOperationTypes.MODIFY_PROPS,
fn: (widget: WidgetProps & { children?: WidgetProps[] }) => {
const tabs = [...widget.tabs];
const newTabs = tabs.map((tab: any) => {
const tabs = Object.values({ ...widget.tabsObj });
const tabsObj = tabs.reduce((obj: any, tab: any) => {
const newTab = { ...tab };
newTab.widgetId = generateReactKey();
return newTab;
});
obj[newTab.id] = newTab;
return obj;
}, {});
const updatePropertyMap = [
{
widgetId: widget.widgetId,
propertyName: "tabs",
propertyValue: newTabs,
propertyName: "tabsObj",
propertyValue: tabsObj,
},
];
return updatePropertyMap;
@ -266,7 +278,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
},
],
},
version: 1,
version: 2,
},
MODAL_WIDGET: {
rows: 6,
@ -511,6 +523,13 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
widgetName: "Skeleton",
version: 1,
},
TABS_MIGRATOR_WIDGET: {
isLoading: true,
rows: 1,
columns: 1,
widgetName: "Skeleton",
version: 1,
},
[WidgetTypes.LIST_WIDGET]: {
backgroundColor: "",
itemBackgroundColor: "white",

View File

@ -68,7 +68,10 @@ export const CurrentPageEntityProperties = memo(
case ENTITY_TYPE.WIDGET:
const type: Exclude<
Partial<WidgetType>,
"CANVAS_WIDGET" | "ICON_WIDGET" | "SKELETON_WIDGET"
| "CANVAS_WIDGET"
| "ICON_WIDGET"
| "SKELETON_WIDGET"
| "TABS_MIGRATOR_WIDGET"
> = entity.type;
config = entityDefinitions[type];
if (!config) {

View File

@ -76,7 +76,10 @@ export const EntityProperties = memo(
case ENTITY_TYPE.WIDGET:
const type: Exclude<
Partial<WidgetType>,
"CANVAS_WIDGET" | "ICON_WIDGET" | "SKELETON_WIDGET"
| "CANVAS_WIDGET"
| "ICON_WIDGET"
| "SKELETON_WIDGET"
| "TABS_MIGRATOR_WIDGET"
> = entity.type;
config = entityDefinitions[type];
if (!config) {

View File

@ -83,7 +83,7 @@ export const EntityName = forwardRef(
) {
const parent = state.entities.canvasWidgets[widget.parentId];
if (parent.type === WidgetTypes.TABS_WIDGET) {
return parent.tabs;
return Object.values(parent.tabsObj);
}
}
}

View File

@ -33,16 +33,15 @@ export const WidgetContextMenu = (props: {
// If the widget is a tab we are updating the `tabs` of the property of the widget
// This is similar to deleting a tab from the property pane
if (widget.tabName && parentWidget.type === WidgetTypes.TABS_WIDGET) {
const filteredTabs = parentWidget.tabs.filter(
(tab: any) => tab.widgetId !== widgetId,
);
const tabsObj = { ...parentWidget.tabsObj };
delete tabsObj[widget.tabId];
const filteredTabs = Object.values(tabsObj);
if (widget.parentId && !!filteredTabs.length) {
dispatch(
updateWidgetPropertyRequest(
widget.parentId,
"tabs",
filteredTabs,
"tabsObj",
tabsObj,
RenderModes.CANVAS,
),
);

View File

@ -127,7 +127,7 @@ export const PanelPropertiesEditor = (
}, [widgetProperties, panelParentPropertyPath, panelProps, panelConfig]);
const panelConfigs = useMemo(() => {
if (currentIndex) {
if (currentIndex !== undefined) {
let path: string | undefined = undefined;
if (isString(currentIndex)) {
path = `${panelParentPropertyPath}.${currentIndex}`;
@ -147,7 +147,7 @@ export const PanelPropertiesEditor = (
);
useEffect(() => {
if (panelProps.widgetId !== widgetProperties.widgetId) {
if (panelProps.propPaneId !== widgetProperties.widgetId) {
props.closePanel();
}
}, [widgetProperties.widgetId]);

View File

@ -249,12 +249,13 @@ const PropertyControl = memo((props: Props) => {
let validationMessage = "";
if (widgetProperties) {
isValid = widgetProperties.invalidProps
? !(propertyName in widgetProperties.invalidProps)
? !_.has(widgetProperties.invalidProps, propertyName)
: true;
validationMessage = widgetProperties.validationMessages
? propertyName in widgetProperties.validationMessages
? widgetProperties.validationMessages[propertyName]
: ""
const validationMsgPresent =
widgetProperties.validationMessages &&
_.has(widgetProperties.validationMessages, propertyName);
validationMessage = validationMsgPresent
? _.get(widgetProperties.validationMessages, propertyName)
: "";
}
return { isValid, validationMessage };

View File

@ -18,7 +18,7 @@ import { FilePickerWidgetProps } from "../../widgets/FilepickerWidget";
import {
TabsWidgetProps,
TabContainerWidgetProps,
} from "../../widgets/TabsWidget";
} from "../../widgets/Tabs/TabsWidget";
import { ChartWidgetProps } from "widgets/ChartWidget";
import { FormWidgetProps } from "widgets/FormWidget";
import { FormButtonWidgetProps } from "widgets/FormButtonWidget";
@ -70,6 +70,8 @@ export interface WidgetConfigReducerState {
FILE_PICKER_WIDGET: Partial<FilePickerWidgetProps> & WidgetConfigProps;
TABS_WIDGET: Partial<TabsWidgetProps<TabContainerWidgetProps>> &
WidgetConfigProps;
TABS_MIGRATOR_WIDGET: Partial<TabsWidgetProps<TabContainerWidgetProps>> &
WidgetConfigProps;
MODAL_WIDGET: Partial<ModalWidgetProps> & WidgetConfigProps;
CHART_WIDGET: Partial<ChartWidgetProps> & WidgetConfigProps;
FORM_WIDGET: Partial<FormWidgetProps> & WidgetConfigProps;

View File

@ -152,7 +152,7 @@ export function* fetchPageListSaga(
}
}
const getCanvasWidgetsPayload = (
export const getCanvasWidgetsPayload = (
pageResponse: FetchPageResponse,
): UpdateCanvasPayload => {
const normalizedResponse = CanvasWidgetsNormalizer.normalize(
@ -596,13 +596,14 @@ export function* updateWidgetNameSaga(
// TODO(abhinav): Why do we need to jump through these hoops just to
// change the tab name? Figure out a better design to make this moot.
const tabs:
| Array<{
id: string;
widgetId: string;
label: string;
}>
| undefined = yield select((state: AppState) => {
const tabsObj: Record<
string,
{
id: string;
widgetId: string;
label: string;
}
> = yield select((state: AppState) => {
// Check if this widget exists in the canvas widgets
if (state.entities.canvasWidgets.hasOwnProperty(action.payload.id)) {
// If it does assign it to a variable
@ -617,7 +618,7 @@ export function* updateWidgetNameSaga(
// Check if this parent is a TABS_WIDGET
if (parent.type === WidgetTypes.TABS_WIDGET) {
// If it is return the tabs property
return parent.tabs;
return parent.tabsObj;
}
}
}
@ -626,7 +627,8 @@ export function* updateWidgetNameSaga(
});
// If we're trying to update the name of a tab in the TABS_WIDGET
if (tabs !== undefined) {
if (tabsObj !== undefined) {
const tabs: any = Object.values(tabsObj);
// Get all canvas widgets
const stateWidgets = yield select(getWidgets);
// Shallow copy canvas widgets as they're immutable
@ -641,14 +643,19 @@ export function* updateWidgetNameSaga(
// Shallow copy the parent widget so that we can update the properties
const parent = { ...widgets[parentId] };
// Update the tabs property of the parent tabs widget
parent.tabs = tabs.map(
(tab: { widgetId: string; label: string; id: string }) => {
if (tab.widgetId === action.payload.id) {
return { ...tab, label: action.payload.newName };
}
return tab;
},
const tabToChange = tabs.find(
(each: any) => each.widgetId === action.payload.id,
);
const updatedTab = {
...tabToChange,
label: action.payload.newName,
};
parent.tabsObj = {
...parent.tabsObj,
[updatedTab.id]: {
...updatedTab,
},
};
// replace the parent widget in the canvas widgets
widgets[parentId] = parent;
// Update and save the new widgets

View File

@ -593,20 +593,21 @@ export function* undoDeleteSaga(action: ReduxAction<{ widgetId: string }>) {
widget.type === WidgetTypes.CANVAS_WIDGET &&
widget.parentId
) {
const parent = { ...widgets[widget.parentId] };
if (parent.tabs) {
parent.tabs = parent.tabs.slice();
const parent = cloneDeep(widgets[widget.parentId]);
if (parent.tabsObj) {
try {
parent.tabs.push({
const tabs = Object.values(parent.tabsObj);
parent.tabsObj[widget.tabId] = {
id: widget.tabId,
widgetId: widget.widgetId,
label: widget.tabName || widget.widgetName,
});
isVisible: true,
};
widgets = {
...widgets,
[widget.parentId]: {
...widgets[widget.parentId],
tabs: parent.tabs,
tabsObj: parent.tabsObj,
},
};
} catch (error) {
@ -866,7 +867,7 @@ function* setWidgetDynamicPropertySaga(
) {
const { isDynamic, propertyPath, widgetId } = action.payload;
const stateWidget: WidgetProps = yield select(getWidget, widgetId);
let widget = { ...stateWidget };
let widget = cloneDeep({ ...stateWidget });
const propertyValue = _.get(widget, propertyPath);
let dynamicPropertyPathList = getWidgetDynamicPropertyPathList(widget);
if (isDynamic) {
@ -1041,6 +1042,9 @@ function* removeWidgetProperties(widget: WidgetProps, paths: string[]) {
let dynamicBindingPathList: DynamicPath[] = getEntityDynamicBindingPathList(
widget,
);
let dynamicPropertyPathList: DynamicPath[] = getWidgetDynamicPropertyPathList(
widget,
);
paths.forEach((propertyPath) => {
dynamicTriggerPathList = dynamicTriggerPathList.filter((dynamicPath) => {
@ -1050,10 +1054,18 @@ function* removeWidgetProperties(widget: WidgetProps, paths: string[]) {
dynamicBindingPathList = dynamicBindingPathList.filter((dynamicPath) => {
return !isChildPropertyPath(propertyPath, dynamicPath.key);
});
dynamicPropertyPathList = dynamicPropertyPathList.filter(
(dynamicPath) => {
return !isChildPropertyPath(propertyPath, dynamicPath.key);
},
);
});
widget.dynamicBindingPathList = dynamicBindingPathList;
widget.dynamicTriggerPathList = dynamicTriggerPathList;
widget.dynamicPropertyPathList = dynamicPropertyPathList;
paths.forEach((propertyPath) => {
widget = unsetPropertyPath(widget, propertyPath) as WidgetProps;
});
@ -1389,14 +1401,15 @@ function* pasteWidgetSaga() {
}
// Update the tabs for the tabs widget.
if (widget.tabs && widget.type === WidgetTypes.TABS_WIDGET) {
if (widget.tabsObj && widget.type === WidgetTypes.TABS_WIDGET) {
try {
const tabs = widget.tabs;
const tabs = Object.values(widget.tabsObj);
if (Array.isArray(tabs)) {
widget.tabs = tabs.map((tab) => {
widget.tabsObj = tabs.reduce((obj: any, tab) => {
tab.widgetId = widgetIdMap[tab.widgetId];
return tab;
});
obj[tab.id] = tab;
return obj;
}, {});
}
} catch (error) {
log.debug("Error updating tabs", error);
@ -1692,6 +1705,10 @@ export default function* widgetOperationSagas() {
ReduxActionTypes.UPDATE_WIDGET_PROPERTY_REQUEST,
updateWidgetPropertySaga,
),
takeEvery(
ReduxActionTypes.WIDGET_UPDATE_PROPERTY,
updateWidgetPropertySaga,
),
takeEvery(
ReduxActionTypes.SET_WIDGET_DYNAMIC_PROPERTY,
setWidgetDynamicPropertySaga,

View File

@ -58,7 +58,12 @@ export const getWidgetPropsForPropertyPane = createSelector(
},
);
const isResizingorDragging = (state: AppState) =>
state.ui.widgetDragResize.isResizing || state.ui.widgetDragResize.isDragging;
export const getIsPropertyPaneVisible = createSelector(
getPropertyPaneState,
(pane: PropertyPaneReduxState) => !!(pane.isVisible && pane.widgetId),
isResizingorDragging,
(pane: PropertyPaneReduxState, isResizingorDragging: boolean) =>
!!(!isResizingorDragging && pane.isVisible && pane.widgetId),
);

View File

@ -31,6 +31,7 @@ import { migrateIncorrectDynamicBindingPathLists } from "utils/migrations/Incorr
import * as Sentry from "@sentry/react";
import { migrateTextStyleFromTextWidget } from "./migrations/TextWidgetReplaceTextStyle";
import { nextAvailableRowInContainer } from "entities/Widget/utils";
import { DATA_BIND_REGEX_GLOBAL } from "constants/BindingsConstants";
export type WidgetOperationParams = {
operation: WidgetOperation;
@ -333,6 +334,111 @@ const rteDefaultValueMigration = (
return currentDSL;
};
function migrateTabsDataUsingMigrator(
currentDSL: ContainerWidgetProps<WidgetProps>,
) {
if (currentDSL.type === WidgetTypes.TABS_WIDGET && currentDSL.version === 1) {
try {
currentDSL.type = WidgetTypes.TABS_MIGRATOR_WIDGET;
currentDSL.version = 1;
} catch (error) {
Sentry.captureException({
message: "Tabs Migration Failed",
oldData: currentDSL.tabs,
});
currentDSL.tabsObj = {};
delete currentDSL.tabs;
}
}
if (currentDSL.children && currentDSL.children.length) {
currentDSL.children = currentDSL.children.map(migrateTabsDataUsingMigrator);
}
return currentDSL;
}
export function migrateTabsData(currentDSL: ContainerWidgetProps<WidgetProps>) {
if (
[WidgetTypes.TABS_WIDGET, WidgetTypes.TABS_MIGRATOR_WIDGET].includes(
currentDSL.type as any,
) &&
currentDSL.version === 1
) {
try {
currentDSL.type = WidgetTypes.TABS_WIDGET;
const isTabsDataBinded = isString(currentDSL.tabs);
currentDSL.dynamicPropertyPathList =
currentDSL.dynamicPropertyPathList || [];
currentDSL.dynamicBindingPathList =
currentDSL.dynamicBindingPathList || [];
if (isTabsDataBinded) {
const tabsString = currentDSL.tabs.replace(
DATA_BIND_REGEX_GLOBAL,
(word: any) => `"${word}"`,
);
try {
currentDSL.tabs = JSON.parse(tabsString);
} catch (error) {
return migrateTabsDataUsingMigrator(currentDSL);
}
const dynamicPropsList = currentDSL.tabs
.filter((each: any) => DATA_BIND_REGEX_GLOBAL.test(each.isVisible))
.map((each: any) => {
return { key: `tabsObj.${each.id}.isVisible` };
});
const dynamicBindablePropsList = currentDSL.tabs.map((each: any) => {
return { key: `tabsObj.${each.id}.isVisible` };
});
currentDSL.dynamicPropertyPathList = [
...currentDSL.dynamicPropertyPathList,
...dynamicPropsList,
];
currentDSL.dynamicBindingPathList = [
...currentDSL.dynamicBindingPathList,
...dynamicBindablePropsList,
];
}
currentDSL.dynamicPropertyPathList = currentDSL.dynamicPropertyPathList.filter(
(each) => {
return each.key !== "tabs";
},
);
currentDSL.dynamicBindingPathList = currentDSL.dynamicBindingPathList.filter(
(each) => {
return each.key !== "tabs";
},
);
currentDSL.tabsObj = currentDSL.tabs.reduce(
(obj: any, tab: any, index: number) => {
obj = {
...obj,
[tab.id]: {
...tab,
isVisible: tab.isVisible === undefined ? true : tab.isVisible,
index,
},
};
return obj;
},
{},
);
currentDSL.version = 2;
delete currentDSL.tabs;
} catch (error) {
Sentry.captureException({
message: "Tabs Migration Failed",
oldData: currentDSL.tabs,
});
currentDSL.tabsObj = {};
delete currentDSL.tabs;
}
}
if (currentDSL.children && currentDSL.children.length) {
currentDSL.children = currentDSL.children.map(migrateTabsData);
}
return currentDSL;
}
// A rudimentary transform function which updates the DSL based on its version.
function migrateOldChartData(currentDSL: ContainerWidgetProps<WidgetProps>) {
if (currentDSL.type === WidgetTypes.CHART_WIDGET) {
@ -543,6 +649,11 @@ const transformDSL = (currentDSL: ContainerWidgetProps<WidgetProps>) => {
currentDSL.version = 17;
}
if (currentDSL.version === 17) {
currentDSL = migrateTabsData(currentDSL);
currentDSL.version = 18;
}
return currentDSL;
};

View File

@ -44,7 +44,7 @@ import TabsWidget, {
TabsWidgetProps,
TabContainerWidgetProps,
ProfiledTabsWidget,
} from "widgets/TabsWidget";
} from "widgets/Tabs/TabsWidget";
import {
ModalWidgetProps,
ProfiledModalWidget,
@ -100,6 +100,9 @@ import SwitchWidget, {
ProfiledSwitchWidget,
SwitchWidgetProps,
} from "widgets/SwitchWidget";
import TabsMigratorWidget, {
ProfiledTabsMigratorWidget,
} from "widgets/Tabs/TabsMigrator";
export default class WidgetBuilderRegistry {
static registerWidgetBuilders() {
WidgetFactory.registerWidgetBuilder(
@ -296,6 +299,20 @@ export default class WidgetBuilderRegistry {
TabsWidget.getMetaPropertiesMap(),
TabsWidget.getPropertyPaneConfig(),
);
WidgetFactory.registerWidgetBuilder(
"TABS_MIGRATOR_WIDGET",
{
buildWidget(
widgetProps: TabsWidgetProps<TabContainerWidgetProps>,
): JSX.Element {
return <ProfiledTabsMigratorWidget {...widgetProps} />;
},
},
TabsMigratorWidget.getDerivedPropertiesMap(),
TabsMigratorWidget.getDefaultPropertiesMap(),
TabsMigratorWidget.getMetaPropertiesMap(),
TabsMigratorWidget.getPropertyPaneConfig(),
);
WidgetFactory.registerWidgetBuilder(
WidgetTypes.MODAL_WIDGET,
{

View File

@ -170,7 +170,6 @@ export const entityDefinitions = {
},
TABS_WIDGET: {
isVisible: isVisible,
tabs: "[tabs]",
selectedTab: "string",
},
MODAL_WIDGET: {

View File

@ -0,0 +1,110 @@
import BaseWidget, { WidgetState } from "widgets/BaseWidget";
import { TabContainerWidgetProps, TabsWidgetProps } from "./TabsWidget";
import React from "react";
import { WidgetType, WidgetTypes } from "constants/WidgetConstants";
import withMeta from "widgets/MetaHOC";
import * as Sentry from "@sentry/react";
import { migrateTabsData } from "utils/WidgetPropsUtils";
import { cloneDeep } from "lodash";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
class TabsMigratorWidget extends BaseWidget<
TabsWidgetProps<TabContainerWidgetProps>,
WidgetState
> {
getPageView() {
return <></>;
}
static getPropertyPaneConfig() {
return [
{
sectionName: "General",
children: [
{
helpText: "Takes an array of tab names to render tabs",
propertyName: "tabs",
isJSConvertible: true,
label: "Tabs",
controlType: "TABS_INPUT",
isBindProperty: true,
isTriggerProperty: false,
validation: VALIDATION_TYPES.TABS_DATA,
},
{
propertyName: "shouldShowTabs",
helpText:
"Hides the tabs so that different widgets can be displayed based on the default tab",
label: "Show Tabs",
controlType: "SWITCH",
isBindProperty: false,
isTriggerProperty: false,
},
{
propertyName: "defaultTab",
helpText: "Selects a tab name specified by default",
placeholderText: "Enter tab name",
label: "Default Tab",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: VALIDATION_TYPES.SELECTED_TAB,
},
{
propertyName: "shouldScrollContents",
label: "Scroll Contents",
controlType: "SWITCH",
isBindProperty: false,
isTriggerProperty: false,
},
{
propertyName: "isVisible",
label: "Visible",
helpText: "Controls the visibility of the widget",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: VALIDATION_TYPES.BOOLEAN,
},
],
},
{
sectionName: "Actions",
children: [
{
helpText: "Triggers an action when the button is clicked",
propertyName: "onTabSelected",
label: "onTabSelected",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
];
}
componentDidMount() {
if (this.props.evaluatedValues) {
const tabsDsl = cloneDeep(this.props);
const migratedTabsDsl = migrateTabsData(tabsDsl);
this.batchUpdateWidgetProperty({
modify: {
tabsObj: migratedTabsDsl.tabsObj,
type: WidgetTypes.TABS_WIDGET,
version: 2,
dynamicPropertyPathList: migratedTabsDsl.dynamicPropertyPathList,
dynamicBindingPathList: migratedTabsDsl.dynamicBindingPathList,
},
remove: ["tabs"],
});
}
}
getWidgetType(): WidgetType {
return "TABS_MIGRATOR_WIDGET";
}
}
export default TabsMigratorWidget;
export const ProfiledTabsMigratorWidget = Sentry.withProfiler(
withMeta(TabsMigratorWidget),
);

View File

@ -0,0 +1,98 @@
import {
buildChildren,
widgetCanvasFactory,
} from "test/factories/WidgetFactoryUtils";
import { render, fireEvent } from "test/testUtils";
import Canvas from "pages/Editor/Canvas";
import React from "react";
import { useDispatch } from "react-redux";
import { editorInitializer } from "utils/EditorUtils";
import { initCanvasLayout } from "actions/pageActions";
import { getCanvasWidgetsPayload } from "sagas/PageSagas";
import { noop } from "utils/AppsmithUtils";
Element.prototype.scrollTo = noop;
const SetCanvas = ({ dsl, children }: any) => {
const dispatch = useDispatch();
const mockResp: any = {
data: {
id: "asa",
name: "App",
applicationId: "asa",
layouts: [
{
id: "w323",
dsl,
layoutOnLoadActions: [],
layoutActions: [],
},
],
},
};
const canvasWidgetsPayload = getCanvasWidgetsPayload(mockResp);
dispatch(initCanvasLayout(canvasWidgetsPayload));
return <>{children}</>;
};
describe("Tabs widget functional cases", () => {
it("Should render 2 tabs by default", () => {
editorInitializer();
const children: any = buildChildren([{ type: "TABS_WIDGET" }]);
const dsl: any = widgetCanvasFactory.build({
children,
});
const component = render(
<SetCanvas dsl={dsl}>
<Canvas dsl={dsl}></Canvas>
</SetCanvas>,
);
const tab1 = component.queryByText("Tab 1");
const tab2 = component.queryByText("Tab 2");
expect(tab1).toBeDefined();
expect(tab2).toBeDefined();
});
it("Should render components inside tabs by default", () => {
editorInitializer();
const tab1Children = buildChildren([
{ type: "SWITCH_WIDGET", label: "Tab1 Switch" },
{ type: "CHECKBOX_WIDGET", label: "Tab1 Checkbox" },
]);
const tab2Children = buildChildren([
{ type: "INPUT_WIDGET", text: "Tab2 Text" },
{ type: "BUTTON_WIDGET", label: "Tab2 Button" },
]);
const children: any = buildChildren([{ type: "TABS_WIDGET" }]);
children[0].children[0].children = tab1Children;
children[0].children[1].children = tab2Children;
const dsl: any = widgetCanvasFactory.build({
children,
});
const component = render(
<SetCanvas dsl={dsl}>
<Canvas dsl={dsl}></Canvas>
</SetCanvas>,
);
const tab1 = component.queryByText("Tab 1");
const tab2: any = component.queryByText("Tab 2");
expect(tab1).toBeDefined();
expect(tab2).toBeDefined();
let tab1Switch = component.queryByText("Tab1 Switch");
let tab1Checkbox = component.queryByText("Tab1 Checkbox");
let tab2Input = component.queryByText("Tab2 Text");
let tab2Button = component.queryByText("Tab2 Button");
expect(tab1Switch).toBeDefined();
expect(tab1Checkbox).toBeDefined();
expect(tab2Input).toBeNull();
expect(tab2Button).toBeNull();
fireEvent.click(tab2);
tab1Switch = component.queryByText("Tab1 Switch");
tab1Checkbox = component.queryByText("Tab1 Checkbox");
tab2Input = component.queryByText("Tab2 Text");
tab2Button = component.queryByText("Tab2 Button");
expect(tab1Switch).toBeNull();
expect(tab1Checkbox).toBeNull();
expect(tab2Input).toBeDefined();
expect(tab2Button).toBeDefined();
});
});

View File

@ -1,7 +1,7 @@
import React from "react";
import TabsComponent from "components/designSystems/appsmith/TabsComponent";
import { WidgetType, WidgetTypes } from "constants/WidgetConstants";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import BaseWidget, { WidgetProps, WidgetState } from "../BaseWidget";
import WidgetFactory from "utils/WidgetFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import _ from "lodash";
@ -9,7 +9,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import { WidgetOperations } from "widgets/BaseWidget";
import * as Sentry from "@sentry/react";
import { generateReactKey } from "utils/generators";
import withMeta, { WithMeta } from "./MetaHOC";
import withMeta, { WithMeta } from "../MetaHOC";
class TabsWidget extends BaseWidget<
TabsWidgetProps<TabContainerWidgetProps>,
@ -22,13 +22,47 @@ class TabsWidget extends BaseWidget<
children: [
{
helpText: "Takes an array of tab names to render tabs",
propertyName: "tabs",
isJSConvertible: true,
propertyName: "tabsObj",
isJSConvertible: false,
label: "Tabs",
controlType: "TABS_INPUT",
isBindProperty: true,
isBindProperty: false,
isTriggerProperty: false,
validation: VALIDATION_TYPES.TABS_DATA,
panelConfig: {
editableTitle: true,
titlePropertyName: "label",
panelIdPropertyName: "id",
updateHook: (
props: any,
propertyPath: string,
propertyValue: string,
) => {
return [
{
propertyPath,
propertyValue,
},
];
},
children: [
{
sectionName: "Tab Control",
children: [
{
propertyName: "isVisible",
label: "Visible",
helpText: "Controls the visibility of the widget",
controlType: "SWITCH",
useValidationMessage: true,
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: VALIDATION_TYPES.BOOLEAN,
},
],
},
],
},
},
{
propertyName: "defaultTab",
@ -97,7 +131,9 @@ class TabsWidget extends BaseWidget<
static getDerivedPropertiesMap() {
return {
selectedTab: `{{_.find(this.tabs, { widgetId: this.selectedTabWidgetId }).label}}`,
selectedTab: `{{_.find(Object.values(this.tabsObj), {
widgetId: this.selectedTabWidgetId,
}).label}}`,
};
}
@ -152,8 +188,11 @@ class TabsWidget extends BaseWidget<
}
addTabContainer = (widgetIds: string[]) => {
const tabs = Object.values(this.props.tabsObj || {});
widgetIds.forEach((newWidgetId: string) => {
const tab = this.props.tabs.find((tab) => tab.widgetId === newWidgetId);
const tab = _.find(tabs, {
widgetId: newWidgetId,
});
if (tab) {
const columns =
(this.props.rightColumn - this.props.leftColumn) *
@ -186,6 +225,18 @@ class TabsWidget extends BaseWidget<
});
};
updateTabContainerNames = () => {
this.props.children.forEach((each) => {
const tab = this.props.tabsObj[each.tabId];
if (tab && each.tabName !== tab.label) {
this.updateWidget(WidgetOperations.UPDATE_PROPERTY, each.widgetId, {
propertyPath: "tabName",
propertyValue: tab.label,
});
}
});
};
removeTabContainer = (widgetIds: string[]) => {
widgetIds.forEach((widgetIdToRemove: string) => {
this.updateWidget(WidgetOperations.DELETE, widgetIdToRemove, {
@ -196,38 +247,47 @@ class TabsWidget extends BaseWidget<
componentDidUpdate(prevProps: TabsWidgetProps<TabContainerWidgetProps>) {
if (
Array.isArray(this.props.tabs) &&
JSON.stringify(this.props.tabs) !== JSON.stringify(prevProps.tabs)
JSON.stringify(this.props.tabsObj) !== JSON.stringify(prevProps.tabsObj)
) {
const tabWidgetIds = this.props.tabs.map((tab) => tab.widgetId);
const tabWidgetIds = Object.values(this.props.tabsObj).map(
(tab) => tab.widgetId,
);
const childWidgetIds = this.props.children
.filter(Boolean)
.map((child) => child.widgetId);
// If the tabs and children are different,
// add and/or remove tab container widgets
if (!this.props.invalidProps?.tabs) {
if (_.xor(childWidgetIds, tabWidgetIds).length > 0) {
const widgetIdsToRemove: string[] = _.without(
childWidgetIds,
...tabWidgetIds,
);
const widgetIdsToCreate: string[] = _.without(
tabWidgetIds,
...childWidgetIds,
);
if (_.xor(childWidgetIds, tabWidgetIds).length > 0) {
const widgetIdsToRemove: string[] = _.without(
childWidgetIds,
...tabWidgetIds,
);
const widgetIdsToCreate: string[] = _.without(
tabWidgetIds,
...childWidgetIds,
);
if (widgetIdsToCreate && widgetIdsToCreate.length) {
this.addTabContainer(widgetIdsToCreate);
}
if (widgetIdsToRemove && widgetIdsToRemove.length) {
this.removeTabContainer(widgetIdsToRemove);
}
}
this.updateTabContainerNames();
// If all tabs were removed.
if (tabWidgetIds.length === 0) {
const newTabContainerWidgetId = generateReactKey();
const tabs = [
{ id: "tab1", widgetId: newTabContainerWidgetId, label: "Tab 1" },
];
this.updateWidgetProperty("tabs", tabs);
}
// If all tabs were removed.
if (tabWidgetIds.length === 0) {
const newTabContainerWidgetId = generateReactKey();
const tabs = {
tab1: {
id: "tab1",
widgetId: newTabContainerWidgetId,
label: "Tab 1",
index: 0,
},
};
this.updateWidgetProperty("tabsObj", tabs);
}
}
const visibleTabs = this.getVisibleTabs();
@ -276,7 +336,8 @@ class TabsWidget extends BaseWidget<
}
generateTabContainers = () => {
const { tabs, widgetId } = this.props;
const { tabsObj, widgetId } = this.props;
const tabs = Object.values(tabsObj || {});
const childWidgetIds = this.props.children
?.filter(Boolean)
.map((child) => child.widgetId);
@ -312,10 +373,13 @@ class TabsWidget extends BaseWidget<
};
getVisibleTabs = () => {
if (Array.isArray(this.props.tabs)) {
return this.props.tabs.filter(
(tab) => tab.isVisible === undefined || tab.isVisible === true,
);
const tabs = Object.values(this.props.tabsObj || {});
if (tabs.length) {
return tabs
.filter(
(tab) => tab.isVisible === undefined || !!tab.isVisible === true,
)
.sort((tab1, tab2) => tab1.index - tab2.index);
}
return [];
};
@ -323,7 +387,7 @@ class TabsWidget extends BaseWidget<
componentDidMount() {
const visibleTabs = this.getVisibleTabs();
// If we have a defaultTab
if (this.props.defaultTab && this.props.tabs?.length) {
if (this.props.defaultTab && Object.keys(this.props.tabsObj || {}).length) {
// Find the default Tab object
const selectedTab = _.find(visibleTabs, {
label: this.props.defaultTab,
@ -345,7 +409,10 @@ class TabsWidget extends BaseWidget<
selectedTabWidgetId,
);
}
} else if (!this.props.selectedTabWidgetId && this.props.tabs?.length) {
} else if (
!this.props.selectedTabWidgetId &&
Object.keys(this.props.tabsObj || {}).length
) {
// If no tab is selected
// Select the first tab in the tabs list.
this.props.updateWidgetMetaProperty(
@ -372,6 +439,16 @@ export interface TabsWidgetProps<T extends TabContainerWidgetProps>
widgetId: string;
isVisible?: boolean;
}>;
tabsObj: Record<
string,
{
id: string;
label: string;
widgetId: string;
isVisible?: boolean;
index: number;
}
>;
shouldShowTabs: boolean;
children: T[];
snapColumns?: number;

View File

@ -741,18 +741,15 @@ export const VALIDATORS: Record<VALIDATION_TYPES, Validator> = {
value: any,
props: WidgetProps,
): ValidationResponse => {
const tabs =
props.tabs && isString(props.tabs)
? JSON.parse(props.tabs)
: props.tabs && Array.isArray(props.tabs)
? props.tabs
: [];
const tabs: any = props.tabsObj
? Object.values(props.tabsObj)
: props.tabs || [];
const tabNames = tabs.map((i: { label: string; id: string }) => i.label);
const isValidTabName = tabNames.includes(value);
return {
isValid: isValidTabName,
parsed: value,
message: isValidTabName ? "" : `Invalid tab name.`,
parsed: isValidTabName ? value : "",
message: isValidTabName ? "" : `Tab name provided does not exist.`,
};
},
[VALIDATION_TYPES.DEFAULT_OPTION_VALUE]: (

View File

@ -0,0 +1,3 @@
import widgetPropertyFns from "!!raw-loader!./derived.js";
export default widgetPropertyFns;

View File

@ -0,0 +1,28 @@
import { makeFactory } from "factory.ts";
import { WidgetProps } from "widgets/BaseWidget";
import { ContainerWidgetProps } from "widgets/ContainerWidget";
import defaultTemplate from "../../src/templates/default";
import { WidgetTypeFactories } from "./Widgets/WidgetTypeFactories";
const defaultMainContainer: ContainerWidgetProps<WidgetProps> = {
...(defaultTemplate as any),
renderMode: "PAGE",
version: 1,
isLoading: false,
};
export const mainContainerFactory = makeFactory({ ...defaultMainContainer });
export const widgetCanvasFactory = makeFactory(mainContainerFactory.build());
const buildChild = (child: Partial<WidgetProps>): WidgetProps => {
return WidgetTypeFactories[child.type || "CANVAS_WIDGET"].build({
...child,
});
};
export const buildChildren = (children: Partial<WidgetProps>[]) => {
try {
return children.map((child) => {
return buildChild(child);
});
} catch (error) {
console.error("Check if child widget data provided");
}
};

View File

@ -0,0 +1,31 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const ButtonFactory = Factory.Sync.makeFactory<WidgetProps>({
widgetName: Factory.each((i) => `Button${i + 1}`),
rightColumn: 12,
onClick: "",
isDefaultClickDisabled: true,
widgetId: generateReactKey(),
buttonStyle: "PRIMARY_BUTTON",
topRow: 1,
bottomRow: 2,
parentRowSpace: 38,
isVisible: true,
type: "BUTTON_WIDGET",
dynamicBindingPathList: [],
parentId: "0",
isLoading: false,
parentColumnSpace: 34.6875,
leftColumn: 10,
dynamicTriggerPathList: [
{
key: "onClick",
},
],
text: "Test Button Text",
isDisabled: false,
version: 1,
renderMode: "CANVAS",
});

View File

@ -0,0 +1,25 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const CanvasFactory = Factory.Sync.makeFactory<WidgetProps>({
backgroundColor: "none",
rightColumn: 1224,
snapColumns: 16,
detachFromLayout: true,
topRow: 0,
bottomRow: 1280,
containerStyle: "none",
snapRows: 33,
parentRowSpace: 1,
type: "CANVAS_WIDGET",
canExtend: true,
minHeight: 1292,
parentColumnSpace: 1,
dynamicBindingPathList: [],
leftColumn: 0,
widgetName: Factory.each((i) => `Canvas${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,61 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const ChartFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
chartType: "LINE_CHART",
chartName: "Sales on working days",
allowHorizontalScroll: false,
chartData: [
{
seriesName: "Sales",
data: [
{
x: "Mon",
y: 10000,
},
{
x: "Tue",
y: 12000,
},
{
x: "Wed",
y: 32000,
},
{
x: "Thu",
y: 28000,
},
{
x: "Fri",
y: 14000,
},
{
x: "Sat",
y: 19000,
},
{
x: "Sun",
y: 36000,
},
],
},
],
xAxisName: "Last Week",
yAxisName: "Total Order Revenue $",
type: "CHART_WIDGET",
isLoading: false,
parentColumnSpace: 71.75,
parentRowSpace: 38,
leftColumn: 2,
rightColumn: 8,
topRow: 1,
bottomRow: 9,
parentId: "rglduihhzk",
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Chart${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,23 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const CheckboxFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
label: "Label",
defaultCheckedState: true,
type: "CHECKBOX_WIDGET",
isLoading: false,
parentColumnSpace: 71.75,
parentRowSpace: 38,
leftColumn: 10,
rightColumn: 13,
topRow: 4,
bottomRow: 5,
parentId: "e3tq9qwta6",
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Checkbox${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,25 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const ContainerFactory = Factory.Sync.makeFactory<WidgetProps>({
backgroundColor: "#FFFFFF",
widgetName: Factory.each((i) => `Container${(i+1)}` ),
type: "CONTAINER_WIDGET",
containerStyle: "card",
isVisible: true,
isLoading: false,
parentColumnSpace: 75.25,
parentRowSpace: 38,
dynamicBindingPathList: [],
leftColumn: 0,
rightColumn: 8,
topRow: 0,
bottomRow: 9,
snapColumns: 16,
orientation: "VERTICAL",
children: [],
version: 1,
widgetId: generateReactKey(),
renderMode: "CANVAS",
});

View File

@ -0,0 +1,47 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const OldDatepickerFactory = Factory.Sync.makeFactory<WidgetProps>({
widgetName: Factory.each((i) => `OldDatePicker${i + 1}`),
rightColumn: 11,
dateFormat: "DD/MM/YYYY",
topRow: 7,
bottomRow: 8,
isValid: "{{ DatePicker1.isRequired ? !!DatePicker1.selectedDate : true }}",
parentRowSpace: 38,
isVisible: true,
datePickerType: "DATE_PICKER",
label: "From Date",
type: "DATE_PICKER_WIDGET",
dynamicBindingPathList: [],
isLoading: false,
enableTimePicker: true,
parentColumnSpace: 34.6875,
leftColumn: 3,
isDisabled: false,
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});
export const DatepickerFactory = Factory.Sync.makeFactory<WidgetProps>({
widgetName: Factory.each((i) => `DatePicker${i + 1}`),
isVisible: true,
isDisabled: false,
datePickerType: "DATE_PICKER",
label: "",
dateFormat: "DD/MM/YYYY HH:mm",
defaultDate: "2021-02-05T10:53:12.791Z",
version: 2,
type: "DATE_PICKER_WIDGET2",
isLoading: false,
parentColumnSpace: 74,
parentRowSpace: 40,
leftColumn: 5,
rightColumn: 10,
topRow: 0,
bottomRow: 1,
widgetId: generateReactKey(),
parentId: "0",
});

View File

@ -0,0 +1,29 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const DropdownFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
label: "",
selectionType: "SINGLE_SELECT",
options: [
{ label: "Vegetarian", value: "VEG" },
{ label: "Non-Vegetarian", value: "NON_VEG" },
{ label: "Vegan", value: "VEGAN" },
],
defaultOptionValue: "VEG",
type: "DROP_DOWN_WIDGET",
isLoading: false,
parentColumnSpace: 74,
parentRowSpace: 40,
leftColumn: 10,
rightColumn: 15,
topRow: 1,
bottomRow: 2,
parentId: "0",
widgetId: generateReactKey(),
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Dropdown${i + 1}`),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,25 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const FilepickerFactory = Factory.Sync.makeFactory<WidgetProps>({
rightColumn: 8,
isDefaultClickDisabled: true,
topRow: 1,
bottomRow: 2,
isValid: "{{ FilePicker1.isRequired ? FilePicker1.files.length > 0 : true }}",
parentRowSpace: 38,
isVisible: true,
label: "Upload Files",
maxFileSize: "",
type: "FILE_PICKER_WIDGET",
dynamicBindingPathList: [],
isLoading: false,
parentColumnSpace: 34.6875,
leftColumn: 4,
files: [],
widgetName: Factory.each((i) => `FilePicker${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,24 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const FormButtonFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
text: "Reset",
isDefaultClickDisabled: true,
buttonStyle: "SECONDARY_BUTTON",
disabledWhenInvalid: false,
resetFormOnClick: true,
type: "FORM_BUTTON_WIDGET",
isLoading: false,
leftColumn: 8,
rightColumn: 12,
topRow: 12,
bottomRow: 13,
parentId: "qrqizehc5b",
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `FormButton${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,217 @@
{
}
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const FormFactory = Factory.Sync.makeFactory<WidgetProps>({
backgroundColor: "Gray",
rightColumn: 11,
topRow: 0,
bottomRow: 13,
parentRowSpace: 38,
isVisible: true,
type: "FORM_WIDGET",
dynamicBindingPathList: [],
blueprint: {
view: [
{
position: {
top: 0,
left: 0,
},
type: "CANVAS_WIDGET",
props: {
blueprint: {
view: [
{
size: {
rows: 1,
cols: 12,
},
position: {
top: 0,
left: 0,
},
type: "TEXT_WIDGET",
props: {
text: "Title",
textStyle: "HEADING",
},
},
{
size: {
rows: 1,
cols: 4,
},
position: {
top: 11,
left: 12,
},
type: "FORM_BUTTON_WIDGET",
props: {
resetFormOnClick: false,
disabledWhenInvalid: true,
buttonStyle: "PRIMARY_BUTTON",
text: "Submit",
},
},
{
size: {
rows: 1,
cols: 4,
},
position: {
top: 11,
left: 8,
},
type: "FORM_BUTTON_WIDGET",
props: {
resetFormOnClick: true,
disabledWhenInvalid: false,
buttonStyle: "SECONDARY_BUTTON",
text: "Reset",
},
},
],
},
detachFromLayout: true,
children: [],
containerStyle: "none",
canExtend: false,
},
},
],
},
isLoading: false,
parentColumnSpace: 71.75,
leftColumn: 5,
children: [
{
widgetName: "Canvas1",
rightColumn: 430.5,
detachFromLayout: true,
widgetId: "nxlutw2g3v",
containerStyle: "none",
topRow: 0,
bottomRow: 494,
parentRowSpace: 1,
isVisible: true,
canExtend: false,
type: "CANVAS_WIDGET",
dynamicBindingPathList: [],
blueprint: {
view: [
{
size: {
rows: 1,
cols: 12,
},
position: {
top: 0,
left: 0,
},
type: "TEXT_WIDGET",
props: {
text: "Title",
textStyle: "HEADING",
},
},
{
size: {
rows: 1,
cols: 4,
},
position: {
top: 11,
left: 12,
},
type: "FORM_BUTTON_WIDGET",
props: {
resetFormOnClick: false,
disabledWhenInvalid: true,
buttonStyle: "PRIMARY_BUTTON",
text: "Submit",
},
},
{
size: {
rows: 1,
cols: 4,
},
position: {
top: 11,
left: 8,
},
type: "FORM_BUTTON_WIDGET",
props: {
resetFormOnClick: true,
disabledWhenInvalid: false,
buttonStyle: "SECONDARY_BUTTON",
text: "Reset",
},
},
],
},
minHeight: 494,
isLoading: false,
parentColumnSpace: 1,
leftColumn: 0,
children: [
{
isLoading: false,
widgetName: "Text1",
rightColumn: 12,
leftColumn: 0,
widgetId: "uvz6hzdz7c",
topRow: 0,
bottomRow: 1,
isVisible: true,
text: "Title",
textStyle: "HEADING",
type: "TEXT_WIDGET",
dynamicBindingPathList: [],
},
{
resetFormOnClick: false,
widgetName: "FormButton1",
rightColumn: 16,
isDefaultClickDisabled: true,
widgetId: "tf20n9k4z2",
buttonStyle: "PRIMARY_BUTTON",
topRow: 11,
bottomRow: 12,
isVisible: true,
type: "FORM_BUTTON_WIDGET",
dynamicBindingPathList: [],
isLoading: false,
disabledWhenInvalid: true,
leftColumn: 12,
text: "Submit",
},
{
resetFormOnClick: true,
widgetName: "FormButton2",
rightColumn: 12,
isDefaultClickDisabled: true,
widgetId: "6xnpe13jie",
buttonStyle: "SECONDARY_BUTTON",
topRow: 11,
bottomRow: 12,
isVisible: true,
type: "FORM_BUTTON_WIDGET",
dynamicBindingPathList: [],
isLoading: false,
disabledWhenInvalid: false,
leftColumn: 8,
text: "Reset",
},
],
},
],
widgetName: Factory.each((i) => `Form${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,23 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const IconFactory = Factory.Sync.makeFactory<WidgetProps>({
rightColumn: 16,
onClick: "",
color: "#040627",
iconName: "cross",
topRow: 0,
bottomRow: 1,
isVisible: true,
type: "ICON_WIDGET",
parentId: "dma7flgdrm",
isLoading: false,
leftColumn: 15,
iconSize: 24,
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Icon${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,24 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const ImageFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
defaultImage:
"https://res.cloudinary.com/drako999/image/upload/v1589196259/default.png",
imageShape: "RECTANGLE",
image: "",
widgetName: Factory.each((i) => `Image${i + 1}`),
type: "IMAGE_WIDGET",
isLoading: false,
parentColumnSpace: 34.6875,
parentRowSpace: 38,
leftColumn: 6,
rightColumn: 10,
topRow: 2,
bottomRow: 5,
parentId: "bxekwxgc1i",
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,30 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const InputFactory = Factory.Sync.makeFactory<WidgetProps>({
widgetName: Factory.each((i) => `Input${i + 1}`),
onTextChanged: "",
rightColumn: 11,
widgetId: generateReactKey(),
topRow: 6,
bottomRow: 7,
isValid: "",
parentRowSpace: 38,
isVisible: true,
label: "Test Input Label",
type: "INPUT_WIDGET",
dynamicBindingPathList: [],
parentId: "iw4o07jvik",
isLoading: false,
parentColumnSpace: 34.6875,
leftColumn: 6,
dynamicTriggerPathList: [
{
key: "onTextChange",
},
],
inputType: "",
placeholderText: "",
defaultText: "",
});

View File

@ -0,0 +1,23 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const ListFactory = Factory.Sync.makeFactory<WidgetProps>({
image: "",
defaultImage: "",
type: "LIST_WIDGET",
parentId: "Container1",
parentColumnSpace: 2,
parentRowSpace: 3,
leftColumn: 2,
rightColumn: 3,
topRow: 1,
bottomRow: 3,
isLoading: false,
items: [],
disablePropertyPane: false,
widgetName: Factory.each((i) => `List${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,32 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const MapFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
isDisabled: false,
enableSearch: true,
zoomLevel: 50,
enablePickLocation: true,
allowZoom: true,
mapCenter: {
lat: 20.593684,
long: 78.96288,
},
defaultMarkers:
'[\n {\n "lat: -34.397,\n "long: 150.644,\n "title: "Test A"\n }\n]',
type: "MAP_WIDGET",
isLoading: false,
parentColumnSpace: 71.75,
parentRowSpace: 38,
leftColumn: 3,
rightColumn: 11,
topRow: 0,
bottomRow: 12,
parentId: "yt4ouwn0sk",
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Map${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,270 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const ModalFactory = Factory.Sync.makeFactory<WidgetProps>({
rightColumn: 0,
detachFromLayout: true,
topRow: 0,
bottomRow: 0,
parentRowSpace: 1,
isVisible: false,
canOutsideClickClose: true,
type: "MODAL_WIDGET",
canEscapeKeyClose: true,
parentId: "0",
shouldScrollContents: true,
blueprint: {
view: [
{
position: {
top: 0,
left: 0,
},
type: "CANVAS_WIDGET",
props: {
shouldScrollContents: false,
blueprint: {
view: [
{
size: {
rows: 1,
cols: 1,
},
position: {
top: 0,
left: 15,
},
type: "ICON_WIDGET",
props: {
color: "#040627",
iconName: "cross",
iconSize: 24,
},
},
{
size: {
rows: 1,
cols: 15,
},
position: {
top: 0,
left: 0,
},
type: "TEXT_WIDGET",
props: {
text: "Modal Title",
textStyle: "HEADING",
},
},
{
size: {
rows: 1,
cols: 3,
},
position: {
top: 4,
left: 10,
},
type: "BUTTON_WIDGET",
props: {
buttonStyle: "SECONDARY_BUTTON",
text: "Cancel",
},
},
{
size: {
rows: 1,
cols: 3,
},
position: {
top: 4,
left: 13,
},
type: "BUTTON_WIDGET",
props: {
buttonStyle: "PRIMARY_BUTTON",
text: "Confirm",
},
},
],
operations: [
{
type: "MODIFY_PROPS",
},
],
},
detachFromLayout: true,
children: [],
isVisible: true,
isDisabled: false,
canExtend: true,
},
},
],
},
isLoading: false,
parentColumnSpace: 1,
size: "MODAL_SMALL",
leftColumn: 0,
children: [
{
widgetName: "Canvas1",
rightColumn: 0,
detachFromLayout: true,
widgetId: "dma7flgdrm",
topRow: 0,
bottomRow: 0,
parentRowSpace: 1,
isVisible: true,
canExtend: true,
type: "CANVAS_WIDGET",
parentId: "s8mtp5krfz",
shouldScrollContents: false,
blueprint: {
view: [
{
size: {
rows: 1,
cols: 1,
},
position: {
top: 0,
left: 15,
},
type: "ICON_WIDGET",
props: {
color: "#040627",
iconName: "cross",
iconSize: 24,
},
},
{
size: {
rows: 1,
cols: 15,
},
position: {
top: 0,
left: 0,
},
type: "TEXT_WIDGET",
props: {
text: "Modal Title",
textStyle: "HEADING",
},
},
{
size: {
rows: 1,
cols: 3,
},
position: {
top: 4,
left: 10,
},
type: "BUTTON_WIDGET",
props: {
buttonStyle: "SECONDARY_BUTTON",
text: "Cancel",
},
},
{
size: {
rows: 1,
cols: 3,
},
position: {
top: 4,
left: 13,
},
type: "BUTTON_WIDGET",
props: {
buttonStyle: "PRIMARY_BUTTON",
text: "Confirm",
},
},
],
operations: [
{
type: "MODIFY_PROPS",
},
],
},
minHeight: 0,
isLoading: false,
parentColumnSpace: 1,
leftColumn: 0,
children: [
{
widgetName: "Icon1",
rightColumn: 16,
onClick: "{{closeModal('TestModal')}}",
color: "#040627",
iconName: "cross",
widgetId: "n5fc0ven2a",
topRow: 0,
bottomRow: 1,
isVisible: true,
type: "ICON_WIDGET",
parentId: "dma7flgdrm",
isLoading: false,
leftColumn: 15,
iconSize: 24,
},
{
isLoading: false,
widgetName: "Text1",
rightColumn: 15,
leftColumn: 0,
widgetId: "s818l5hhvq",
topRow: 0,
bottomRow: 1,
isVisible: true,
text: "Modal Title",
textStyle: "HEADING",
type: "TEXT_WIDGET",
parentId: "dma7flgdrm",
},
{
widgetName: "Button1",
rightColumn: 13,
isDefaultClickDisabled: true,
widgetId: "io777lh7bc",
buttonStyle: "SECONDARY_BUTTON",
topRow: 4,
bottomRow: 5,
isVisible: true,
type: "BUTTON_WIDGET",
parentId: "dma7flgdrm",
isLoading: false,
leftColumn: 10,
text: "Cancel",
isDisabled: false,
},
{
widgetName: "Button2",
rightColumn: 16,
isDefaultClickDisabled: true,
widgetId: "gexp49bfyb",
buttonStyle: "PRIMARY_BUTTON",
topRow: 4,
bottomRow: 5,
isVisible: true,
type: "BUTTON_WIDGET",
parentId: "dma7flgdrm",
isLoading: false,
leftColumn: 13,
text: "Confirm",
isDisabled: false,
},
],
isDisabled: false,
},
],
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Modal${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,42 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const RadiogroupFactory = Factory.Sync.makeFactory<WidgetProps>({
rightColumn: 16,
topRow: 3,
bottomRow: 5,
parentRowSpace: 38,
isVisible: true,
label: "Test Radio",
type: "RADIO_GROUP_WIDGET",
isLoading: false,
defaultOptionValue: "1",
parentColumnSpace: 34.6875,
leftColumn: 12,
dynamicTriggerPathList: [{
key: "onSelectionChange"
}],
onSelectionChange: "{{navigateTo()}}",
options: [
{
id: "1",
label: "jarvis",
value: "1"
},
{
id: "2",
label: "marvel",
value: "2"
},
{
label: "iron",
value: "4"
}
],
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `RadioGroup${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,29 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const RichTextFactory = Factory.Sync.makeFactory<WidgetProps>({
rightColumn: 11,
topRow: 3,
bottomRow: 8,
parentRowSpace: 38,
isVisible: true,
type: "RICH_TEXT_EDITOR_WIDGET",
isLoading: false,
parentColumnSpace: 34.6875,
leftColumn: 3,
dynamicTriggerPathList: [
{
key: "onTextChange",
},
],
defaultText: "",
text: "This is the initial <b>content</b> of the editor",
isDisabled: false,
onTextChange: "{{navigateTo()}}",
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `RichText${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,19 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const SkeletonFactory = Factory.Sync.makeFactory<WidgetProps>({
bottomRow: 0,
isLoading: false,
leftColumn: 0,
parentColumnSpace: 0,
parentRowSpace: 0,
rightColumn: 0,
topRow: 0,
type: "SKELETON_WIDGET",
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Skeleton${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,22 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const SwitchFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
label: "Switch",
defaultSwitchState: true,
widgetName: Factory.each((i) => `Switch${(i+1)}` ),
type: "SWITCH_WIDGET",
isLoading: false,
parentColumnSpace: 71.75,
parentRowSpace: 38,
leftColumn: 10,
rightColumn: 13,
topRow: 18,
bottomRow: 19,
parentId: "e3tq9qwta6",
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,111 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const TableFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
label: "Data",
widgetName: Factory.each((i) => `Table${i + 1}`),
searchKey: "",
tableData:
'[\n {\n "id: 2381224,\n "email: "michael.lawson@reqres.in",\n "userName: "Michael Lawson",\n "productName: "Chicken Sandwich",\n "orderAmount: 4.99\n },\n {\n "id: 2736212,\n "email: "lindsay.ferguson@reqres.in",\n "userName: "Lindsay Ferguson",\n "productName: "Tuna Salad",\n "orderAmount: 9.99\n },\n {\n "id: 6788734,\n "email: "tobias.funke@reqres.in",\n "userName: "Tobias Funke",\n "productName: "Beef steak",\n "orderAmount: 19.99\n }\n]',
type: "TABLE_WIDGET",
isLoading: false,
parentColumnSpace: 74,
parentRowSpace: 40,
leftColumn: 2,
rightColumn: 10,
topRow: 12,
bottomRow: 19,
parentId: "0",
widgetId: generateReactKey(),
dynamicBindingPathList: [],
primaryColumns: {
id: {
index: 0,
width: 150,
id: "id",
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
columnType: "text",
textColor: "#4E5D78",
textSize: "PARAGRAPH",
fontStyle: "NORMAL",
enableFilter: true,
enableSort: true,
isVisible: true,
isDerived: false,
label: "id",
computedValue: "",
},
email: {
index: 1,
width: 150,
id: "email",
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
columnType: "text",
textColor: "#4E5D78",
textSize: "PARAGRAPH",
fontStyle: "NORMAL",
enableFilter: true,
enableSort: true,
isVisible: true,
isDerived: false,
label: "email",
computedValue: "",
},
userName: {
index: 2,
width: 150,
id: "userName",
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
columnType: "text",
textColor: "#4E5D78",
textSize: "PARAGRAPH",
fontStyle: "NORMAL",
enableFilter: true,
enableSort: true,
isVisible: true,
isDerived: false,
label: "userName",
computedValue: "",
},
productName: {
index: 3,
width: 150,
id: "productName",
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
columnType: "text",
textColor: "#4E5D78",
textSize: "PARAGRAPH",
fontStyle: "NORMAL",
enableFilter: true,
enableSort: true,
isVisible: true,
isDerived: false,
label: "productName",
computedValue: "",
},
orderAmount: {
index: 4,
width: 150,
id: "orderAmount",
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
columnType: "text",
textColor: "#4E5D78",
textSize: "PARAGRAPH",
fontStyle: "NORMAL",
enableFilter: true,
enableSort: true,
isVisible: true,
isDerived: false,
label: "orderAmount",
computedValue: "",
},
},
});

View File

@ -0,0 +1,145 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const OldTabsFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
shouldScrollContents: false,
tabs: [
{
label: "Tab 1",
id: "tab1",
widgetId: "o9ody00ep7",
},
{
label: "Tab 2",
id: "tab2",
widgetId: "plhuaxd4lo",
},
],
shouldShowTabs: true,
defaultTab: "Tab 1",
type: "TABS_WIDGET",
isLoading: false,
parentColumnSpace: 74,
parentRowSpace: 40,
leftColumn: 1,
rightColumn: 9,
topRow: 12,
bottomRow: 19,
parentId: "0",
children: [
{
type: "CANVAS_WIDGET",
tabId: "tab1",
tabName: "Tab 1",
widgetId: "o9ody00ep7",
parentId: "jd83uvbkmp",
detachFromLayout: true,
children: [],
parentRowSpace: 1,
parentColumnSpace: 1,
leftColumn: 0,
rightColumn: 592,
topRow: 0,
bottomRow: 280,
isLoading: false,
widgetName: "Canvas1",
renderMode: "CANVAS",
},
{
type: "CANVAS_WIDGET",
tabId: "tab2",
tabName: "Tab 2",
widgetId: "plhuaxd4lo",
parentId: "jd83uvbkmp",
detachFromLayout: true,
children: [],
parentRowSpace: 1,
parentColumnSpace: 1,
leftColumn: 0,
rightColumn: 592,
topRow: 0,
bottomRow: 280,
isLoading: false,
widgetName: "Canvas1",
renderMode: "CANVAS",
},
],
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Tabs${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});
export const TabsFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
shouldScrollContents: false,
tabsObj: {
tab1: {
label: "Tab 1",
id: "tab1",
widgetId: "o9ody00ep7",
},
tab2: {
label: "Tab 2",
id: "tab2",
widgetId: "plhuaxd4lo",
},
},
shouldShowTabs: true,
defaultTab: "Tab 1",
type: "TABS_WIDGET",
isLoading: false,
parentColumnSpace: 74,
parentRowSpace: 40,
leftColumn: 1,
rightColumn: 9,
topRow: 12,
bottomRow: 19,
parentId: "0",
children: [
{
type: "CANVAS_WIDGET",
tabId: "tab1",
tabName: "Tab 1",
widgetId: "o9ody00ep7",
parentId: "jd83uvbkmp",
detachFromLayout: true,
children: [],
parentRowSpace: 1,
parentColumnSpace: 1,
leftColumn: 0,
rightColumn: 592,
topRow: 0,
bottomRow: 280,
isLoading: false,
widgetName: "Canvas1",
renderMode: "CANVAS",
},
{
type: "CANVAS_WIDGET",
tabId: "tab2",
tabName: "Tab 2",
widgetId: "plhuaxd4lo",
parentId: "jd83uvbkmp",
detachFromLayout: true,
children: [],
parentRowSpace: 1,
parentColumnSpace: 1,
leftColumn: 0,
rightColumn: 592,
topRow: 0,
bottomRow: 280,
isLoading: false,
widgetName: "Canvas1",
renderMode: "CANVAS",
},
],
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Tabs${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,31 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const TextFactory = Factory.Sync.makeFactory<WidgetProps>({
widgetName: Factory.each((i) => `Text${(i+1)}`),
rightColumn: 12,
onClick: "",
isDefaultClickDisabled: true,
widgetId: generateReactKey(),
buttonStyle: "PRIMARY_BUTTON",
topRow: 1,
bottomRow: 2,
parentRowSpace: 38,
isVisible: true,
type: "BUTTON_WIDGET",
dynamicBindingPathList: [],
parentId: "0",
isLoading: false,
parentColumnSpace: 34.6875,
leftColumn: 10,
dynamicTriggerPathList: [
{
key: "onClick",
},
],
text: "Test Button Text",
isDisabled: false,
version: 1,
renderMode: "CANVAS",
});

View File

@ -0,0 +1,23 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const VideoFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
url: "https://www.youtube.com/watch?v=mzqK0QIZRLs",
autoPlay: false,
type: "VIDEO_WIDGET",
isLoading: false,
parentColumnSpace: 74,
parentRowSpace: 40,
leftColumn: 1,
rightColumn: 8,
topRow: 17,
bottomRow: 24,
parentId: "0",
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Video${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -0,0 +1,53 @@
import { SwitchFactory } from "./SwitchFactory";
import { ButtonFactory } from "./ButtonFactory";
import { TextFactory } from "./TextFactory";
import { ImageFactory } from "./ImageFactory";
import { InputFactory } from "./InputFactory";
import { TableFactory } from "./TableFactory";
import { OldDatepickerFactory, DatepickerFactory } from "./DatepickerFactory";
import { ContainerFactory } from "./ContainerFactory";
import { DropdownFactory } from "./DropdownFactory";
import { CheckboxFactory } from "./CheckboxFactory";
import { RadiogroupFactory } from "./RadiogroupFactory";
import { OldTabsFactory, TabsFactory } from "./TabsFactory";
import { ModalFactory } from "./ModalFactory";
import { RichTextFactory } from "./RichTextFactory";
import { ChartFactory } from "./ChartFactory";
import { FormFactory } from "./FormFactory";
import { FormButtonFactory } from "./FormButtonFactory";
import { MapFactory } from "./MapFactory";
import { CanvasFactory } from "./CanvasFactory";
import { IconFactory } from "./IconFactory";
import { FilepickerFactory } from "./FilepickerFactory";
import { VideoFactory } from "./VideoFactory";
import { SkeletonFactory } from "./SkeletonFactory";
import { ListFactory } from "./ListFactory";
export const WidgetTypeFactories = {
SWITCH_WIDGET: SwitchFactory,
BUTTON_WIDGET: ButtonFactory,
TEXT_WIDGET: TextFactory,
IMAGE_WIDGET: ImageFactory,
INPUT_WIDGET: InputFactory,
CONTAINER_WIDGET: ContainerFactory,
DATE_PICKER_WIDGET: OldDatepickerFactory,
DATE_PICKER_WIDGET2: DatepickerFactory,
TABLE_WIDGET: TableFactory,
DROP_DOWN_WIDGET: DropdownFactory,
CHECKBOX_WIDGET: CheckboxFactory,
RADIO_GROUP_WIDGET: RadiogroupFactory,
TABS_WIDGET: TabsFactory,
TABS_MIGRATOR_WIDGET: OldTabsFactory,
MODAL_WIDGET: ModalFactory,
RICH_TEXT_EDITOR_WIDGET: RichTextFactory,
CHART_WIDGET: ChartFactory,
FORM_WIDGET: FormFactory,
FORM_BUTTON_WIDGET: FormButtonFactory,
MAP_WIDGET: MapFactory,
CANVAS_WIDGET: CanvasFactory,
ICON_WIDGET: IconFactory,
FILE_PICKER_WIDGET: FilepickerFactory,
VIDEO_WIDGET: VideoFactory,
SKELETON_WIDGET: SkeletonFactory,
LIST_WIDGET: ListFactory,
};

View File

@ -7,6 +7,8 @@ import { getCurrentThemeDetails } from "../src/selectors/themeSelectors";
import * as customQueries from "./customQueries";
import { BrowserRouter } from "react-router-dom";
import { AppState } from "reducers";
import { DndProvider } from "react-dnd";
import TouchBackend from "react-dnd-touch-backend";
const customRender = (
ui: ReactElement,
@ -25,7 +27,14 @@ const customRender = (
return render(
<BrowserRouter>
<Provider store={reduxStore}>
<ThemeProvider theme={defaultTheme}>{ui}</ThemeProvider>
<DndProvider
backend={TouchBackend}
options={{
enableMouseEvents: true,
}}
>
<ThemeProvider theme={defaultTheme}>{ui}</ThemeProvider>
</DndProvider>
</Provider>
</BrowserRouter>,
{

View File

@ -8162,6 +8162,14 @@ extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
factory.ts@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/factory.ts/-/factory.ts-0.5.1.tgz#4bab72d8457078906aa6ab396c0d341e8a3ab382"
integrity sha512-jwAq8w7MmxUojIFzKezMwTzDc5QoxcqzAA8+n9A0EAWBje2CRHUeBrW9x/ioV2DRjHgkHX7i0G0ipfDhlatIQw==
dependencies:
clone-deep "^4.0.1"
source-map-support "^0.5.9"
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
@ -15637,7 +15645,7 @@ source-map-resolve@^0.6.0:
atob "^2.1.2"
decode-uri-component "^0.2.0"
source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19:
source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@^0.5.9, source-map-support@~0.5.12, source-map-support@~0.5.19:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
dependencies: