From ee79e3a2cb9fa530cb5ea4a0ba0a8a37e11162b5 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Tue, 22 Oct 2024 15:49:55 +0530 Subject: [PATCH] page menu and import export changes --- .../AppBuilder/AppCanvas/appCanvasUtils.js | 2 +- .../LeftSidebar/PageMenu/PageMenu.jsx | 123 +++--------------- frontend/src/_components/SearchBox.jsx | 2 +- .../_components/SortableList/SortableList.jsx | 18 +-- .../SortableList/components/SortableItem.jsx | 15 ++- .../src/_components/SortableList/index.js | 47 ++++--- frontend/src/_helpers/appUtils.js | 2 +- server/src/controllers/apps.controller.v2.ts | 17 +++ server/src/helpers/import_export.helpers.ts | 46 +++++-- .../src/services/app_import_export.service.ts | 16 +-- server/src/services/apps.service.ts | 12 +- 11 files changed, 139 insertions(+), 161 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index 519f43f931..4fb0a82eeb 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -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) { diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx index e18f8c5b40..3396e793ba 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx @@ -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" > - - // } - > +
- {isLicensed ? ( - (shouldFreeze ? enableReleasedVersionPopupState() : toggleShowAddNewPageInput(true))} - className="left-sidebar-header-btn" - fill={`var(--slate12)`} - darkMode={darkMode} - leftIcon="plus" - iconWidth="14" - variant="tertiary" - disabled={shouldFreeze} - > - ) : ( - (shouldFreeze ? enableReleasedVersionPopupState() : toggleShowAddNewPageInput(true))} - className="left-sidebar-header-btn" - fill={`var(--slate12)`} - darkMode={darkMode} - leftIcon="plus" - iconWidth="14" - variant="tertiary" - disabled={shouldFreeze} - > - )} + (shouldFreeze ? enableReleasedVersionPopupState() : toggleShowAddNewPageInput(true))} + className="left-sidebar-header-btn" + fill={`var(--slate12)`} + darkMode={darkMode} + leftIcon="plus" + iconWidth="14" + variant="tertiary" + disabled={shouldFreeze} + > - {isLicensed ? ( - // - - ) : ( - - )} + {showAddNewPageInput && (
diff --git a/frontend/src/_components/SearchBox.jsx b/frontend/src/_components/SearchBox.jsx index ec52e37fa1..1e910dd519 100644 --- a/frontend/src/_components/SearchBox.jsx +++ b/frontend/src/_components/SearchBox.jsx @@ -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 } diff --git a/frontend/src/_components/SortableList/SortableList.jsx b/frontend/src/_components/SortableList/SortableList.jsx index 42273bcbd9..7a59e7fda9 100644 --- a/frontend/src/_components/SortableList/SortableList.jsx +++ b/frontend/src/_components/SortableList/SortableList.jsx @@ -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 ( { - if (isVersionReleased) { + if (shouldFreeze) { enableReleasedVersionPopupState(); return; } diff --git a/frontend/src/_components/SortableList/components/SortableItem.jsx b/frontend/src/_components/SortableList/components/SortableItem.jsx index 383ac4ced1..345aa9aee5 100644 --- a/frontend/src/_components/SortableList/components/SortableItem.jsx +++ b/frontend/src/_components/SortableList/components/SortableItem.jsx @@ -29,7 +29,7 @@ export function SortableItem({ children, id, classNames }) { return ( -
+
{children}
@@ -51,3 +51,16 @@ export function DragHandle({ show = true }) { ); } + +// hoc for wrapping components that need to be draggable +export function withDraggable(Component) { + return function DraggableComponent(props) { + const { attributes, listeners, ref } = useContext(SortableItemContext); + + return ( +
+ +
+ ); + }; +} diff --git a/frontend/src/_components/SortableList/index.js b/frontend/src/_components/SortableList/index.js index 8146d4c14e..375b7add58 100644 --- a/frontend/src/_components/SortableList/index.js +++ b/frontend/src/_components/SortableList/index.js @@ -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 ( +
+
+ +

+ No pages found +

+
+
+ ); + } return (
( diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 943d363979..5875a021c9 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -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); diff --git a/server/src/controllers/apps.controller.v2.ts b/server/src/controllers/apps.controller.v2.ts index 6ce6650776..c4e5d7f54c 100644 --- a/server/src/controllers/apps.controller.v2.ts +++ b/server/src/controllers/apps.controller.v2.ts @@ -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') diff --git a/server/src/helpers/import_export.helpers.ts b/server/src/helpers/import_export.helpers.ts index 9269743e23..2c98071159 100644 --- a/server/src/helpers/import_export.helpers.ts +++ b/server/src/helpers/import_export.helpers.ts @@ -6,14 +6,32 @@ export function updateEntityReferences(node, resourceMapping: Record { + // 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); diff --git a/server/src/services/app_import_export.service.ts b/server/src/services/app_import_export.service.ts index 699cd12a99..a693db254b 100644 --- a/server/src/services/app_import_export.service.ts +++ b/server/src/services/app_import_export.service.ts @@ -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); diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index b40f964523..6270ff9378 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -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`;