PromucFlow_constructor/app/client/src/components/propertyControls/TabControl.tsx
albinAppsmith 629999f124
feat: [epic] appsmith design system version 2 deduplication (#22030)
## Description

### Fixes
- [x] https://github.com/appsmithorg/appsmith/issues/19383
- [x] https://github.com/appsmithorg/appsmith/issues/19384
- [x] https://github.com/appsmithorg/appsmith/issues/19385
- [x] https://github.com/appsmithorg/appsmith/issues/19386
- [x] https://github.com/appsmithorg/appsmith/issues/19387
- [x] https://github.com/appsmithorg/appsmith/issues/19388
- [x] https://github.com/appsmithorg/appsmith/issues/19389
- [x] https://github.com/appsmithorg/appsmith/issues/19390
- [x] https://github.com/appsmithorg/appsmith/issues/19391
- [x] https://github.com/appsmithorg/appsmith/issues/19392
- [x] https://github.com/appsmithorg/appsmith/issues/19393
- [x] https://github.com/appsmithorg/appsmith/issues/19394
- [x] https://github.com/appsmithorg/appsmith/issues/19395
- [x] https://github.com/appsmithorg/appsmith/issues/19396
- [x] https://github.com/appsmithorg/appsmith/issues/19397
- [x] https://github.com/appsmithorg/appsmith/issues/19398
- [x] https://github.com/appsmithorg/appsmith/issues/19399
- [x] https://github.com/appsmithorg/appsmith/issues/19400
- [x] https://github.com/appsmithorg/appsmith/issues/19401
- [x] https://github.com/appsmithorg/appsmith/issues/19402
- [x] https://github.com/appsmithorg/appsmith/issues/19403
- [x] https://github.com/appsmithorg/appsmith/issues/19404
- [x] https://github.com/appsmithorg/appsmith/issues/19405
- [x] https://github.com/appsmithorg/appsmith/issues/19406
- [x] https://github.com/appsmithorg/appsmith/issues/19407
- [x] https://github.com/appsmithorg/appsmith/issues/19408
- [x] https://github.com/appsmithorg/appsmith/issues/19409

Fixes # (issue)
> if no issue exists, please create an issue and ask the maintainers
about this first


Media
> A video or a GIF is preferred. when using Loom, don’t embed because it
looks like it’s a GIF. instead, just link to the video


## Type of change

> Please delete options that are not relevant.

- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- Chore (housekeeping or task changes that don't impact user perception)
- This change requires a documentation update


## How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Provide
instructions, so we can reproduce.
> Please also list any relevant details for your test configuration.
> Delete anything that is not important

- Manual
- Jest
- Cypress

### Test Plan
> Add Testsmith test cases links that relate to this PR

### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)


## Checklist:
### Dev activity
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


### QA activity:
- [ ] Test plan has been approved by relevant developers
- [ ] Test plan has been peer reviewed by QA
- [ ] Cypress test cases have been added and approved by either SDET or
manual QA
- [ ] Organized project review call with relevant stakeholders after
Round 1/2 of QA
- [ ] Added Test Plan Approved label after reveiwing all Cypress test

---------

Co-authored-by: Ankita Kinger <ankita@appsmith.com>
Co-authored-by: akash-codemonk <67054171+akash-codemonk@users.noreply.github.com>
Co-authored-by: Tanvi Bhakta <tanvi@appsmith.com>
Co-authored-by: Arsalan <arsalanyaldram0211@outlook.com>
Co-authored-by: Aman Agarwal <aman@appsmith.com>
Co-authored-by: Rohit Agarwal <rohit_agarwal@live.in>
Co-authored-by: Nilesh Sarupriya <nilesh@appsmith.com>
Co-authored-by: Nilesh Sarupriya <20905988+nsarupr@users.noreply.github.com>
Co-authored-by: Tanvi Bhakta <tanvibhakta@gmail.com>
Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
Co-authored-by: Parthvi Goswami <parthvigoswami@Parthvis-MacBook-Pro.local>
Co-authored-by: Vijetha-Kaja <vijetha@appsmith.com>
Co-authored-by: Parthvi <80334441+Parthvi12@users.noreply.github.com>
Co-authored-by: Apple <nandan@thinkify.io>
Co-authored-by: Saroj <43822041+sarojsarab@users.noreply.github.com>
Co-authored-by: Sangeeth Sivan <74818788+berzerkeer@users.noreply.github.com>
Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com>
Co-authored-by: Aishwarya-U-R <91450662+Aishwarya-U-R@users.noreply.github.com>
Co-authored-by: rahulramesha <rahul@appsmith.com>
Co-authored-by: Aswath K <aswath.sana@gmail.com>
Co-authored-by: Preet Sidhu <preetsidhu.bits@gmail.com>
Co-authored-by: Vijetha-Kaja <119562824+Vijetha-Kaja@users.noreply.github.com>
Co-authored-by: Shrikant Sharat Kandula <shrikant@appsmith.com>
2023-05-20 00:07:06 +05:30

258 lines
7.2 KiB
TypeScript

import React from "react";
import type { ControlProps } from "./BaseControl";
import BaseControl from "./BaseControl";
import type {
BaseItemProps as DroppableItem,
RenderComponentProps,
} from "./DraggableListComponent";
import orderBy from "lodash/orderBy";
import isString from "lodash/isString";
import isUndefined from "lodash/isUndefined";
import includes from "lodash/includes";
import map from "lodash/map";
import * as Sentry from "@sentry/react";
import { useDispatch } from "react-redux";
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import { DraggableListControl } from "pages/Editor/PropertyPane/DraggableListControl";
import { DraggableListCard } from "components/propertyControls/DraggableListCard";
import { Button, Tag } from "design-system";
function AddTabButtonComponent({ widgetId }: any) {
const dispatch = useDispatch();
const addOption = () => {
dispatch({
type: ReduxActionTypes.WIDGET_ADD_NEW_TAB_CHILD,
payload: {
widgetId,
},
});
};
return (
<Button
className="self-end t--add-tab-btn"
kind="tertiary"
onClick={addOption}
size="sm"
startIcon="plus"
>
Add tab
</Button>
);
}
function TabControlComponent(props: RenderComponentProps<DroppableItem>) {
const { index, item } = props;
const dispatch = useDispatch();
const deleteOption = () => {
dispatch({
type: ReduxActionTypes.WIDGET_DELETE_TAB_CHILD,
payload: { ...item, index },
});
if (props.deleteOption) props.deleteOption(index);
};
return (
<DraggableListCard
{...props}
deleteOption={deleteOption}
isDelete
placeholder="Tab title"
/>
);
}
type State = {
focusedIndex: number | null;
duplicateTabIds: string[];
};
class TabControl extends BaseControl<ControlProps, State> {
constructor(props: ControlProps) {
super(props);
this.state = {
focusedIndex: null,
duplicateTabIds: this.getDuplicateTabIds(props.propertyValue),
};
}
getDuplicateTabIds = (propertyValue: ControlProps["propertyValue"]) => {
const duplicateTabIds = [];
const tabIds = Object.keys(propertyValue);
const tabNames = map(propertyValue, "label");
for (let index = 0; index < tabNames.length; index++) {
const currLabel = tabNames[index] as string;
const duplicateValueIndex = tabNames.indexOf(currLabel);
if (duplicateValueIndex !== index) {
// get tab id from propertyValue index
duplicateTabIds.push(propertyValue[tabIds[index]].id);
}
}
return duplicateTabIds;
};
componentDidMount() {
this.migrateTabData(this.props.propertyValue);
}
componentDidUpdate(prevProps: ControlProps): void {
//on adding a new column last column should get focused
if (
Object.keys(prevProps.propertyValue).length + 1 ===
Object.keys(this.props.propertyValue).length
) {
this.updateFocus(Object.keys(this.props.propertyValue).length - 1, true);
}
}
migrateTabData(
tabData: Array<{
id: string;
label: string;
}>,
) {
// Added a migration script for older tab data that was strings
// deprecate after enough tabs have moved to the new format
if (isString(tabData)) {
try {
const parsedData: Array<{
sid: string;
label: string;
}> = JSON.parse(tabData);
this.updateProperty(this.props.propertyName, parsedData);
return parsedData;
} catch (error) {
Sentry.captureException({
message: "Tab Migration Failed",
oldData: this.props.propertyValue,
});
}
} else {
return this.props.propertyValue;
}
}
getTabItems = () => {
let menuItems: Array<{
id: string;
label: string;
isVisible?: boolean;
isDuplicateLabel?: boolean;
}> =
isString(this.props.propertyValue) ||
isUndefined(this.props.propertyValue)
? []
: Object.values(this.props.propertyValue);
menuItems = orderBy(menuItems, ["index"], ["asc"]);
menuItems = menuItems.map((tab: DroppableItem) => ({
...tab,
isDuplicateLabel: includes(this.state.duplicateTabIds, tab.id),
}));
return menuItems;
};
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 = this.getTabItems();
const tabToChange = tabs[index];
this.props.openNextPanel({
index,
...tabToChange,
propPaneId: this.props.widgetProperties.widgetId,
});
};
render() {
const tabs = this.getTabItems();
return (
<div className="flex flex-col">
<div className="t--number-of-tabs mb-1 ml-auto">
<Tag isClosable={false}>{tabs.length}</Tag>
</div>
<DraggableListControl
deleteOption={this.deleteOption}
fixedHeight={370}
focusedIndex={this.state.focusedIndex}
itemHeight={45}
items={tabs}
onEdit={this.onEdit}
propertyPath={this.props.dataTreePath}
renderComponent={TabControlComponent}
toggleVisibility={this.toggleVisibility}
updateFocus={this.updateFocus}
updateItems={this.updateItems}
updateOption={this.updateOption}
/>
<AddTabButtonComponent
widgetId={this.props.widgetProperties.widgetId}
/>
</div>
);
}
toggleVisibility = (index: number) => {
const tabs = this.getTabItems();
const isVisible = tabs[index].isVisible === true ? false : true;
const updatedTabs = tabs.map((tab, tabIndex) => {
if (index === tabIndex) {
return {
...tab,
isVisible: isVisible,
};
}
return tab;
});
this.updateProperty(this.props.propertyName, updatedTabs);
};
deleteOption = (index: number) => {
const tabIds = Object.keys(this.props.propertyValue);
const newPropertyValue = { ...this.props.propertyValue };
// detele current item from propertyValue
delete newPropertyValue[tabIds[index]];
const duplicateTabIds = this.getDuplicateTabIds(newPropertyValue);
this.setState({ duplicateTabIds });
};
updateOption = (index: number, updatedLabel: string) => {
const tabsArray = this.getTabItems();
const { id: itemId } = tabsArray[index];
this.updateProperty(
`${this.props.propertyName}.${itemId}.label`,
updatedLabel,
);
// check entered label is unique or duplicate
const tabNames = map(tabsArray, "label");
let duplicateTabIds = [...this.state.duplicateTabIds];
// if duplicate, add into array
if (includes(tabNames, updatedLabel)) {
duplicateTabIds.push(itemId);
this.setState({ duplicateTabIds });
} else {
duplicateTabIds = duplicateTabIds.filter((id) => id !== itemId);
this.setState({ duplicateTabIds });
}
};
updateFocus = (index: number, isFocused: boolean) => {
this.setState({ focusedIndex: isFocused ? index : null });
};
static getControlType() {
return "TABS_INPUT";
}
}
export default TabControl;