page menu and import export changes

This commit is contained in:
Kartik Gupta 2024-10-22 15:49:55 +05:30
parent 8faf6fdc40
commit ee79e3a2cb
11 changed files with 139 additions and 161 deletions

View file

@ -326,7 +326,7 @@ const updateComponentLayout = (components, parentId, isCut = false) => {
};
const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => {
const parentId = componentParentId ?? component.component?.parent?.split('-').slice(0, -1).join('-');
const parentId = componentParentId ?? component.component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const parentComponent = allComponents.find((comp) => comp.componentId === parentId);
if (parentComponent) {

View file

@ -29,11 +29,9 @@ export const PageMenu = ({
setPinned,
}) => {
// const pages = useStore((state) => state.pages);
const allpages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow);
const showAddNewPageInput = useStore((state) => state.showAddNewPageInput);
const toggleShowAddNewPageInput = useStore((state) => state.toggleShowAddNewPageInput);
const showSearch = useStore((state) => state.showSearch);
const toggleSearch = useStore((state) => state.toggleSearch);
const handleSearch = useStore((state) => state.handleSearch);
const togglePageSettingMenu = useStore((state) => state.togglePageSettingMenu);
const shouldFreeze = useStore((state) => state.getShouldFreeze());
@ -44,58 +42,6 @@ export const PageMenu = ({
closePageEditPopover();
};
}, []);
// const [allpages, setAllPages] = useState(pages);
// const [haveUserPinned, setHaveUserPinned] = useState(false);
// const currentState = useCurrentState();
// const [newPageBeingCreated, setNewPageBeingCreated] = useState(false);
// const [showSearch, setShowSearch] = useState(false);
// const { enableReleasedVersionPopupState, isVersionReleased, isEditorFreezed } = useAppVersionStore(
// (state) => ({
// enableReleasedVersionPopupState: state.actions.enableReleasedVersionPopupState,
// isVersionReleased: state.isVersionReleased,
// isEditorFreezed: state.isEditorFreezed,
// }),
// shallow
// );
// useEffect(() => {
// setAllPages(pages);
// }, [pages]);
// const sortedAllPages = useMemo(
// () => [...allpages.filter((c) => !c.disabled), ...allpages.filter((c) => c.disabled)],
// [allpages]
// );
// const filterPages = (value) => {
// if (!value || value.length === 0) return clearSearch();
// const fuse = new Fuse(pages, { keys: ['name'], threshold: 0.3 });
// const result = fuse.search(value);
// setAllPages(result.map((item) => item.item));
// };
// const clearSearch = () => {
// setAllPages(pages);
// };
// React.useEffect(() => {
// if (!_.isEqual(pages, allpages)) {
// setAllPages(pages);
// }
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [JSON.stringify({ pages })]);
// const pinPagesPopover = (state) => {
// if (!haveUserPinned) {
// setPinned(state);
// }
// };
const license = useStore((state) => state.license);
const isLicensed =
!_.get(license, 'featureAccess.licenseStatus.isExpired', true) &&
_.get(license, 'featureAccess.licenseStatus.isLicenseValid', false);
const {
definition: { styles },
@ -110,42 +56,19 @@ export const PageMenu = ({
className="card-body p-0 pb-5"
>
<HeaderSection darkMode={darkMode}>
<HeaderSection.PanelHeader
title="Pages"
// settings={
// <GlobalSettings
darkMode={darkMode}
// showHideViewerNavigationControls={showHideViewerNavigationControls}
// isViewerNavigationDisabled={!appDefinition?.showViewerNavigation}
// />
// }
>
<HeaderSection.PanelHeader title="Pages" darkMode={darkMode}>
<div className="d-flex justify-content-end" style={{ gap: '2px' }}>
{isLicensed ? (
<ButtonSolid
title={'Add Page'}
onClick={() => (shouldFreeze ? enableReleasedVersionPopupState() : toggleShowAddNewPageInput(true))}
className="left-sidebar-header-btn"
fill={`var(--slate12)`}
darkMode={darkMode}
leftIcon="plus"
iconWidth="14"
variant="tertiary"
disabled={shouldFreeze}
></ButtonSolid>
) : (
<ButtonSolid
title={'Add Page'}
onClick={() => (shouldFreeze ? enableReleasedVersionPopupState() : toggleShowAddNewPageInput(true))}
className="left-sidebar-header-btn"
fill={`var(--slate12)`}
darkMode={darkMode}
leftIcon="plus"
iconWidth="14"
variant="tertiary"
disabled={shouldFreeze}
></ButtonSolid>
)}
<ButtonSolid
title={'Add Page'}
onClick={() => (shouldFreeze ? enableReleasedVersionPopupState() : toggleShowAddNewPageInput(true))}
className="left-sidebar-header-btn"
fill={`var(--slate12)`}
darkMode={darkMode}
leftIcon="plus"
iconWidth="14"
variant="tertiary"
disabled={shouldFreeze}
></ButtonSolid>
<ButtonSolid
title={'Settings'}
onClick={shouldFreeze ? enableReleasedVersionPopupState : togglePageSettingMenu}
@ -199,22 +122,12 @@ export const PageMenu = ({
<EditModal darkMode={darkMode} />
<SettingsModal darkMode={darkMode} />
<DeletePageConfirmationModal darkMode={darkMode} />
{isLicensed ? (
// <SortableTree collapsible indicator={true} />
<SortableList
Element={PageMenuItem}
darkMode={darkMode}
switchPage={switchPage}
classNames="page-handler"
/>
) : (
<SortableList
Element={PageMenuItem}
darkMode={darkMode}
switchPage={switchPage}
classNames="page-handler"
/>
)}
<SortableList
Element={PageMenuItem}
darkMode={darkMode}
switchPage={switchPage}
classNames="page-handler"
/>
{showAddNewPageInput && (
<div className="page-handler">
<AddingPageHandler darkMode={darkMode} />

View file

@ -39,7 +39,7 @@ export const SearchBox = forwardRef(
};
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
if (ref?.current && !ref.current.contains(event.target)) {
clearSearchText();
// Your function to be triggered
}

View file

@ -4,26 +4,26 @@ import { SortableContext, arrayMove, sortableKeyboardCoordinates } from '@dnd-ki
import { SortableItem } from './components';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { shallow } from 'zustand/shallow';
import useStore from '@/AppBuilder/_stores/store';
export function SortableList({ items, onChange, renderItem }) {
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(PointerSensor, {
activationConstraint: { delay: 150 },
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const { enableReleasedVersionPopupState, isVersionReleased } = useAppVersionStore(
(state) => ({
enableReleasedVersionPopupState: state.actions.enableReleasedVersionPopupState,
isVersionReleased: state.isVersionReleased,
}),
shallow
);
const shouldFreeze = useStore((state) => state.isVersionReleased || state.isEditorFreezed);
const enableReleasedVersionPopupState = useStore((state) => state.enableReleasedVersionPopupState);
return (
<DndContext
sensors={sensors}
onDragEnd={({ active, over }) => {
if (isVersionReleased) {
if (shouldFreeze) {
enableReleasedVersionPopupState();
return;
}

View file

@ -29,7 +29,7 @@ export function SortableItem({ children, id, classNames }) {
return (
<SortableItemContext.Provider value={context}>
<div className={classNames} ref={setNodeRef} style={style}>
<div {...attributes} {...listeners} ref={setNodeRef} className={classNames} style={style}>
{children}
</div>
</SortableItemContext.Provider>
@ -51,3 +51,16 @@ export function DragHandle({ show = true }) {
</button>
);
}
// hoc for wrapping components that need to be draggable
export function withDraggable(Component) {
return function DraggableComponent(props) {
const { attributes, listeners, ref } = useContext(SortableItemContext);
return (
<div {...attributes} {...listeners} ref={ref} className="draggable-container">
<Component {...props} />
</div>
);
};
}

View file

@ -1,36 +1,41 @@
import React from 'react';
import useStore from '@/AppBuilder/_stores/store';
import EmptyIllustration from '@assets/images/no-results.svg';
import { SortableList } from './SortableList';
import { DragHandle } from './components';
import { shallow } from 'zustand/shallow';
import _ from 'lodash';
const SortableComponent = ({ data, Element, ...restProps }) => {
const { onSort } = restProps;
const allpages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow);
const reorderPages = useStore((state) => state.reorderPages);
const [items, setItems] = React.useState([]);
const showSearch = useStore((state) => state.showSearch);
const pageSearchResults = useStore((state) => state.pageSearchResults);
React.useEffect(() => {
setItems(data);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(data)]);
const pagesTorender =
showSearch && pageSearchResults !== null
? allpages.filter((page) => pageSearchResults.includes(page.id))
: allpages;
//function to check if the item in items array has changed position with respect to the original data
const didItemChangePosition = (originalArr, sortedArry) => {
return originalArr.some((item, index) => {
return item.id !== sortedArry[index].id;
});
};
React.useEffect(() => {
if (items.length > 0 && didItemChangePosition(data, items)) {
onSort(items);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [items]);
if (pagesTorender.length === 0) {
return (
<div className="d-flex justify-content-center align-items-center" style={{ height: '100%' }}>
<div>
<EmptyIllustration />
<p data-cy={`label-no-pages-found`} className="mt-3 color-slate12">
No pages found
</p>
</div>
</div>
);
}
return (
<div style={{ maxWidth: 400, margin: '0' }}>
<SortableList
items={items}
onChange={setItems}
items={pagesTorender}
onChange={reorderPages}
renderItem={(page) => (
<SortableList.Item id={page.id} classNames={restProps.classNames}>
<Element page={page} {...restProps} />

View file

@ -1856,7 +1856,7 @@ const updateComponentLayout = (components, parentId, isCut = false) => {
};
//
const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => {
const parentId = componentParentId ?? component.component?.parent?.split('-').slice(0, -1).join('-');
const parentId = componentParentId ?? component.component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const parentComponent = allComponents.find((comp) => comp.componentId === parentId);

View file

@ -407,6 +407,23 @@ export class AppsControllerV2 {
await this.pageService.updatePage(updatePageDto, versionId);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/pages/reorder')
async reorderPages(@User() user, @Param('id') id, @Param('versionId') versionId, @Body() reorderPagesDto) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can(APP_RESOURCE_ACTIONS.VERSION_UPDATE, app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
console.log(reorderPagesDto, 'payload');
await this.pageService.reorderPages(reorderPagesDto, versionId);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Delete(':id/versions/:versionId/pages')

View file

@ -6,14 +6,32 @@ export function updateEntityReferences(node, resourceMapping: Record<string, str
const referenceExists = value;
if (referenceExists) {
const ref = value.replace('{{', '').replace('}}', '');
const matches = value.match(/{{(.*?)}}/g);
// gett all references {{entityName}}
if (matches) {
matches.forEach((match) => {
// remove curly braces and extract the entity "component.entityName.something"
const ref = match.slice(2, -2).trim();
const entityName = ref.split('.')[1];
const entityName = ref.split('.')[1];
if (resourceMapping[entityName]) {
const newValue = value.replace(entityName, resourceMapping[entityName]);
if (resourceMapping[entityName]) {
const newValue = value.replace(entityName, resourceMapping[entityName]);
node[key] = newValue;
value = newValue;
}
});
} else {
// kept this logic for fallback, although it should not be needed
const ref = value.replace('{{', '').replace('}}', '');
node[key] = newValue;
const entityName = ref.split('.')[1];
if (resourceMapping[entityName]) {
const newValue = value.replace(entityName, resourceMapping[entityName]);
node[key] = newValue;
}
}
}
} else if (typeof value === 'object') {
@ -45,11 +63,23 @@ export function findAllEntityReferences(node, allRefs): [] {
const referenceExists = value;
if (referenceExists) {
const ref = value.replace('{{', '').replace('}}', '');
const matches = value.match(/{{(.*?)}}/g);
if (matches) {
matches.forEach((match) => {
const ref = match.slice(2, -2).trim(); // Remove {{ and }}
const entityName = ref.split('.')[1];
if (entityName && !allRefs.includes(entityName)) {
allRefs.push(entityName);
}
});
} else {
// kept this logic for fallback, although it should not be needed
const ref = value.replace('{{', '').replace('}}', '');
const entityName = ref.split('.')[1];
const entityName = ref.split('.')[1];
allRefs.push(entityName);
allRefs.push(entityName);
}
}
} else if (typeof value === 'object') {
findAllEntityReferences(value, allRefs);

View file

@ -794,13 +794,13 @@ export class AppImportExportService {
const isParentTabOrCalendar = isChildOfTabsOrCalendar(component, pageComponents, parentId, true);
if (isParentTabOrCalendar) {
const childTabId = component.parent.split('-')[component.parent.split('-').length - 1];
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const childTabId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[2] : null;
const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const mappedParentId = newComponentIdsMap[_parentId];
parentId = `${mappedParentId}-${childTabId}`;
} else if (isChildOfKanbanModal(component, pageComponents, parentId, true)) {
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const mappedParentId = newComponentIdsMap[_parentId];
parentId = `${mappedParentId}-modal`;
@ -1885,13 +1885,13 @@ function transformComponentData(
);
if (isParentTabOrCalendar) {
const childTabId = component.parent.split('-')[component.parent.split('-').length - 1];
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const childTabId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[2] : null;
const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const mappedParentId = componentsMapping[_parentId];
parentId = `${mappedParentId}-${childTabId}`;
} else if (isChildOfKanbanModal(component, allComponents, parentId, true)) {
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const mappedParentId = componentsMapping[_parentId];
parentId = `${mappedParentId}-modal`;
@ -1940,7 +1940,7 @@ const isChildOfTabsOrCalendar = (
isNormalizedAppDefinitionSchema: boolean
) => {
if (componentParentId) {
const parentId = component?.parent?.split('-').slice(0, -1).join('-');
const parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const parentComponent = allComponents.find((comp) => comp.id === parentId);
@ -1964,7 +1964,7 @@ const isChildOfKanbanModal = (
) => {
if (!componentParentId || !componentParentId.includes('modal')) return false;
const parentId = component?.parent?.split('-').slice(0, -1).join('-');
const parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const parentComponent = allComponents.find((comp) => comp.id === parentId);

View file

@ -529,7 +529,7 @@ export class AppsService {
const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => {
if (componentParentId) {
const parentId = component?.parent?.split('-').slice(0, -1).join('-');
const parentId = component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const parentComponent = allComponents.find((comp) => comp.id === parentId);
@ -545,7 +545,7 @@ export class AppsService {
if (!componentParentId.includes('modal')) return false;
if (componentParentId) {
const parentId = componentParentId.split('-').slice(0, -1).join('-');
const parentId = componentParentId.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const isParentKandban = allComponents.find((comp) => comp.id === parentId)?.type === 'Kanban';
return isParentKandban;
@ -597,8 +597,8 @@ export class AppsService {
const isParentTabOrCalendar = isChildOfTabsOrCalendar(component, page.components, parentId);
if (isParentTabOrCalendar) {
const childTabId = component.parent.split('-')[component.parent.split('-').length - 1];
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const childTabId = component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[2];
const _parentId = component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const mappedParentId = oldComponentToNewComponentMapping[_parentId];
parentId = `${mappedParentId}-${childTabId}`;
@ -660,12 +660,12 @@ export class AppsService {
if (isParentTabOrCalendar) {
const childTabId = component?.parent?.split('-')[component?.parent?.split('-').length - 1];
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const _parentId = component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const mappedParentId = oldComponentToNewComponentMapping[_parentId];
parentId = `${mappedParentId}-${childTabId}`;
} else if (isChildOfKanbanModal(component.parent, page.components)) {
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const _parentId = component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const mappedParentId = oldComponentToNewComponentMapping[_parentId];
parentId = `${mappedParentId}-modal`;