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 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); const parentComponent = allComponents.find((comp) => comp.componentId === parentId);
if (parentComponent) { if (parentComponent) {

View file

@ -29,11 +29,9 @@ export const PageMenu = ({
setPinned, setPinned,
}) => { }) => {
// const pages = useStore((state) => state.pages); // const pages = useStore((state) => state.pages);
const allpages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow);
const showAddNewPageInput = useStore((state) => state.showAddNewPageInput); const showAddNewPageInput = useStore((state) => state.showAddNewPageInput);
const toggleShowAddNewPageInput = useStore((state) => state.toggleShowAddNewPageInput); const toggleShowAddNewPageInput = useStore((state) => state.toggleShowAddNewPageInput);
const showSearch = useStore((state) => state.showSearch); const showSearch = useStore((state) => state.showSearch);
const toggleSearch = useStore((state) => state.toggleSearch);
const handleSearch = useStore((state) => state.handleSearch); const handleSearch = useStore((state) => state.handleSearch);
const togglePageSettingMenu = useStore((state) => state.togglePageSettingMenu); const togglePageSettingMenu = useStore((state) => state.togglePageSettingMenu);
const shouldFreeze = useStore((state) => state.getShouldFreeze()); const shouldFreeze = useStore((state) => state.getShouldFreeze());
@ -44,58 +42,6 @@ export const PageMenu = ({
closePageEditPopover(); 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 { const {
definition: { styles }, definition: { styles },
@ -110,42 +56,19 @@ export const PageMenu = ({
className="card-body p-0 pb-5" className="card-body p-0 pb-5"
> >
<HeaderSection darkMode={darkMode}> <HeaderSection darkMode={darkMode}>
<HeaderSection.PanelHeader <HeaderSection.PanelHeader title="Pages" darkMode={darkMode}>
title="Pages"
// settings={
// <GlobalSettings
darkMode={darkMode}
// showHideViewerNavigationControls={showHideViewerNavigationControls}
// isViewerNavigationDisabled={!appDefinition?.showViewerNavigation}
// />
// }
>
<div className="d-flex justify-content-end" style={{ gap: '2px' }}> <div className="d-flex justify-content-end" style={{ gap: '2px' }}>
{isLicensed ? ( <ButtonSolid
<ButtonSolid title={'Add Page'}
title={'Add Page'} onClick={() => (shouldFreeze ? enableReleasedVersionPopupState() : toggleShowAddNewPageInput(true))}
onClick={() => (shouldFreeze ? enableReleasedVersionPopupState() : toggleShowAddNewPageInput(true))} className="left-sidebar-header-btn"
className="left-sidebar-header-btn" fill={`var(--slate12)`}
fill={`var(--slate12)`} darkMode={darkMode}
darkMode={darkMode} leftIcon="plus"
leftIcon="plus" iconWidth="14"
iconWidth="14" variant="tertiary"
variant="tertiary" disabled={shouldFreeze}
disabled={shouldFreeze} ></ButtonSolid>
></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 <ButtonSolid
title={'Settings'} title={'Settings'}
onClick={shouldFreeze ? enableReleasedVersionPopupState : togglePageSettingMenu} onClick={shouldFreeze ? enableReleasedVersionPopupState : togglePageSettingMenu}
@ -199,22 +122,12 @@ export const PageMenu = ({
<EditModal darkMode={darkMode} /> <EditModal darkMode={darkMode} />
<SettingsModal darkMode={darkMode} /> <SettingsModal darkMode={darkMode} />
<DeletePageConfirmationModal darkMode={darkMode} /> <DeletePageConfirmationModal darkMode={darkMode} />
{isLicensed ? ( <SortableList
// <SortableTree collapsible indicator={true} /> Element={PageMenuItem}
<SortableList darkMode={darkMode}
Element={PageMenuItem} switchPage={switchPage}
darkMode={darkMode} classNames="page-handler"
switchPage={switchPage} />
classNames="page-handler"
/>
) : (
<SortableList
Element={PageMenuItem}
darkMode={darkMode}
switchPage={switchPage}
classNames="page-handler"
/>
)}
{showAddNewPageInput && ( {showAddNewPageInput && (
<div className="page-handler"> <div className="page-handler">
<AddingPageHandler darkMode={darkMode} /> <AddingPageHandler darkMode={darkMode} />

View file

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

View file

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

View file

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

View file

@ -1856,7 +1856,7 @@ const updateComponentLayout = (components, parentId, isCut = false) => {
}; };
// //
const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => { 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); const parentComponent = allComponents.find((comp) => comp.componentId === parentId);

View file

@ -407,6 +407,23 @@ export class AppsControllerV2 {
await this.pageService.updatePage(updatePageDto, versionId); 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) @UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor) @UseInterceptors(ValidAppInterceptor)
@Delete(':id/versions/:versionId/pages') @Delete(':id/versions/:versionId/pages')

View file

@ -6,14 +6,32 @@ export function updateEntityReferences(node, resourceMapping: Record<string, str
const referenceExists = value; const referenceExists = value;
if (referenceExists) { 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]) { node[key] = newValue;
const newValue = value.replace(entityName, resourceMapping[entityName]); 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') { } else if (typeof value === 'object') {
@ -45,11 +63,23 @@ export function findAllEntityReferences(node, allRefs): [] {
const referenceExists = value; const referenceExists = value;
if (referenceExists) { 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') { } else if (typeof value === 'object') {
findAllEntityReferences(value, allRefs); findAllEntityReferences(value, allRefs);

View file

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

View file

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