From 678536abbacf77aa22c43973c19f315b5ee3a286 Mon Sep 17 00:00:00 2001 From: Nick Brown Date: Thu, 31 Mar 2022 15:33:25 +0100 Subject: [PATCH] Added a Tree View to react-page-hierarchy --- samples/react-pages-hierarchy/README.md | 4 ++- .../react-pages-hierarchy/assets/treeview.png | Bin 0 -> 3406 bytes .../config/package-solution.json | 8 +++++- .../src/apiHooks/usePageApi.ts | 25 +++++++++++++++-- .../src/utilities/Enums.ts | 3 +- .../IPageHierarchyWebPartProps.ts | 2 ++ .../PageHierarchyWebPart.manifest.json | 4 ++- .../pagehierarchy/PageHierarchyWebPart.ts | 26 +++++++++++++++++- .../components/Container/Container.tsx | 7 +++-- .../components/Container/IContainerProps.ts | 3 +- .../components/Layouts/ILayoutProps.ts | 3 ++ .../components/Layouts/TreeLayout.tsx | 18 ++++++++++++ .../src/webparts/pagehierarchy/loc/en-us.js | 8 +++++- .../webparts/pagehierarchy/loc/mystrings.d.ts | 6 ++++ 14 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 samples/react-pages-hierarchy/assets/treeview.png create mode 100644 samples/react-pages-hierarchy/src/webparts/pagehierarchy/components/Layouts/TreeLayout.tsx diff --git a/samples/react-pages-hierarchy/README.md b/samples/react-pages-hierarchy/README.md index ffdb61c96..df9c3368b 100644 --- a/samples/react-pages-hierarchy/README.md +++ b/samples/react-pages-hierarchy/README.md @@ -20,7 +20,7 @@ extensions: This web part allows users to create a faux page hierarchy in their pages library and use it for page-to-page navigation. It will ask you to create a page parent property on first use which is then used by the web part to either show a breadcrumb of the current pages ancestors or buttons for the pages children. ![Page Navigator](./assets/PagesHierarchy.gif) - +![Tree View](./assets/treeview.png) ## Compatibility @@ -48,6 +48,7 @@ This web part allows users to create a faux page hierarchy in their pages librar Solution|Author(s) --------|--------- react-pages-hierarchy|[Bo George](https://github.com/bogeorge) ([@bo_george](https://twitter.com/bo_george)) +react-pages-hierarchy|[Nick Brown](https://github.com/techienickb) ([@techienickb](https://twitter.com/techienickb)) ## Version history @@ -55,6 +56,7 @@ Version|Date|Comments -------|----|-------- 1.0|April 30, 2020|Initial release 1.2|March 24, 2022|Updated to SPFX v1.14 and PnP packages to v3 +1.3|March 31, 2322|Added a Tree View ## Minimal Path to Awesome diff --git a/samples/react-pages-hierarchy/assets/treeview.png b/samples/react-pages-hierarchy/assets/treeview.png new file mode 100644 index 0000000000000000000000000000000000000000..9a09e330cb84822e9cbc7a6f87e1c786bb050d2a GIT binary patch literal 3406 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 GbmO1C!?HC1 literal 0 HcmV?d00001 diff --git a/samples/react-pages-hierarchy/config/package-solution.json b/samples/react-pages-hierarchy/config/package-solution.json index af6f0c0af..1b7ab52bc 100644 --- a/samples/react-pages-hierarchy/config/package-solution.json +++ b/samples/react-pages-hierarchy/config/package-solution.json @@ -4,7 +4,7 @@ "name": "react-pages-hierarchy", "id": "89758fb6-85e2-4e2b-ac88-4f4e7e5f60cb", "title": "Pages Hierarchy", - "version": "1.0.2.0", + "version": "1.0.3.0", "includeClientSideAssets": true, "isDomainIsolated": false, "developer": { @@ -39,6 +39,12 @@ "description": "Displays a list of child pages", "id": "ce42762f-a823-4f12-80b2-b708cdebfaa5", "version": "1.0.2.0" + }, + { + "title": "Tree", + "description": "Displays a list of pages in a tree", + "id": "ce42762f-a823-4f12-80b2-b708cdebfaa6", + "version": "1.0.3.0" } ] }, diff --git a/samples/react-pages-hierarchy/src/apiHooks/usePageApi.ts b/samples/react-pages-hierarchy/src/apiHooks/usePageApi.ts index 698be59a0..6fe0edebf 100644 --- a/samples/react-pages-hierarchy/src/apiHooks/usePageApi.ts +++ b/samples/react-pages-hierarchy/src/apiHooks/usePageApi.ts @@ -5,6 +5,7 @@ import { Action } from "./action"; import { GetRequest } from './getRequest'; import { IPage } from '@src/models/IPage'; import { WebPartContext } from '@microsoft/sp-webpart-base'; +import { INavLink } from 'office-ui-fabric-react'; // state that we track interface PagesState { @@ -12,6 +13,7 @@ interface PagesState { userCanManagePages: boolean; ancestorPages: IPage[]; childrenPages: IPage[]; + tree: INavLink | null; getRequest: GetRequest; } @@ -27,7 +29,8 @@ interface PageApi { interface PageTreePayloadAction extends Action { payload: { childgrenPages: IPage[], - ancestorPages: IPage[] + ancestorPages: IPage[], + tree: INavLink }; } interface ParentPageColumnExistAction extends Action { @@ -56,6 +59,7 @@ function pagesReducer(state: PagesState, action: Action): PagesState { ...state, childrenPages: arrayAction.payload.childgrenPages, ancestorPages: arrayAction.payload.ancestorPages, + tree: arrayAction.payload.tree, getRequest: { isLoading: false, hasError: false, errorMessage: "" } }; case ActionTypes.GET_PAGES_ERRORED: @@ -84,13 +88,14 @@ function pagesReducer(state: PagesState, action: Action): PagesState { } } -export function usePageApi(currentPageId: number, pageEditFinished: boolean, context: WebPartContext): PageApi { +export function usePageApi(currentPageId: number, pageEditFinished: boolean, context: WebPartContext, treeTop: number, treeExpandTo: number): PageApi { const [pagesState, pagesDispatch] = useReducer(pagesReducer, { parentPageColumnExists: true, userCanManagePages: false, ancestorPages: [] = [], childrenPages: [] = [], getRequest: { isLoading: false, hasError: false, errorMessage: "" }, + tree: null }); const sp = spfi().using(SPFx(context)); @@ -143,11 +148,12 @@ export function usePageApi(currentPageId: number, pageEditFinished: boolean, con const ancestorPages: IPage[] = buildPageAncestors(pages, currentPageId).reverse(); const childrenPages: IPage[] = buildPageChildren(pages, currentPageId); + const treeLink: INavLink = buildHierarchy(pages); // dispatch the GET_ALL action pagesDispatch({ type: ActionTypes.GET_PAGES, - payload: { childgrenPages: childrenPages, ancestorPages: ancestorPages }, + payload: { childgrenPages: childrenPages, ancestorPages: ancestorPages, tree: treeLink }, } as PageTreePayloadAction); } @@ -247,6 +253,18 @@ export function usePageApi(currentPageId: number, pageEditFinished: boolean, con return childPages; } + function buildHierarchy(allPages: IPage[]): INavLink { + function recurse(id: number, l: number): INavLink { + var item: IPage = allPages.filter(i => i.id === id)[0]; + + var links: INavLink[] = []; + links = links.concat(allPages.filter(i => i.parentPageId === id).map(it => recurse(it.id, (l+1)))); + + return { name: item.title, url: item.url, key: item.id.toString(), links: links, isExpanded: treeExpandTo >= l }; + } + return recurse(treeTop, 1); + } + const addParentPageField = () => { addParentPageFieldToSitePages(); }; @@ -262,6 +280,7 @@ export function usePageApi(currentPageId: number, pageEditFinished: boolean, con hasError: pagesState.getRequest.hasError, errorMessage: pagesState.getRequest.errorMessage }, + tree: pagesState.tree }, addParentPageField }; diff --git a/samples/react-pages-hierarchy/src/utilities/Enums.ts b/samples/react-pages-hierarchy/src/utilities/Enums.ts index 7971e6b25..ebab91dce 100644 --- a/samples/react-pages-hierarchy/src/utilities/Enums.ts +++ b/samples/react-pages-hierarchy/src/utilities/Enums.ts @@ -1,7 +1,8 @@ export enum PagesToDisplay { None = 'none', Ancestors = 'ancestors', - Children = 'children' + Children = 'children', + Tree = 'tree' } export enum RenderDirection { diff --git a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/IPageHierarchyWebPartProps.ts b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/IPageHierarchyWebPartProps.ts index fc695aed4..b38c5d1c8 100644 --- a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/IPageHierarchyWebPartProps.ts +++ b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/IPageHierarchyWebPartProps.ts @@ -4,4 +4,6 @@ export default interface IPageHierarchyWebPartProps { title: string; debugPageId?: number; pagesToDisplay: PagesToDisplay; + treeFrom: number; + treeExpandTo: number } diff --git a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/PageHierarchyWebPart.manifest.json b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/PageHierarchyWebPart.manifest.json index c4df753af..c2d50b864 100644 --- a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/PageHierarchyWebPart.manifest.json +++ b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/PageHierarchyWebPart.manifest.json @@ -24,7 +24,9 @@ }, "officeFabricIconFontName": "CompassNW", "properties": { - "pagesToDisplay": "none" + "pagesToDisplay": "none", + "treeFrom": 1, + "treeExpandTo": 10 } } ] diff --git a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/PageHierarchyWebPart.ts b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/PageHierarchyWebPart.ts index 8f436694e..35625fc17 100644 --- a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/PageHierarchyWebPart.ts +++ b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/PageHierarchyWebPart.ts @@ -54,7 +54,9 @@ export default class PageHierarchyWebPart extends BaseWebPart { this.properties.title = t; this.render(); }, onConfigure: () => { this.onConfigure(); }, pageEditFinished: this.pageEditFinished, - context: this.context + context: this.context, + treeFrom: this.properties.treeFrom, + treeExpandTo: this.properties.treeExpandTo } ); ReactDom.render(element, this.domElement, () => { @@ -146,8 +148,30 @@ export default class PageHierarchyWebPart extends BaseWebPart = props => { - const pagesApi = usePageApi(props.currentPageId, props.pageEditFinished, props.context); - + const pagesApi = usePageApi(props.currentPageId, props.pageEditFinished, props.context, props.treeFrom, props.treeExpandTo); let controlToRender = undefined; switch (props.pagesToDisplay) { case PagesToDisplay.Ancestors: @@ -19,6 +19,9 @@ export const Container: React.FunctionComponent = props => { case PagesToDisplay.Children: controlToRender = ; break; + case PagesToDisplay.Tree: + controlToRender = + break; default: controlToRender =
void; onConfigure: () => void; - pageEditFinished: boolean; } diff --git a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/components/Layouts/ILayoutProps.ts b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/components/Layouts/ILayoutProps.ts index 446477446..bbe0d3981 100644 --- a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/components/Layouts/ILayoutProps.ts +++ b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/components/Layouts/ILayoutProps.ts @@ -1,8 +1,11 @@ import { IReadonlyTheme } from '@microsoft/sp-component-base'; import { IPage } from '@src/models/IPage'; +import { INavLink } from 'office-ui-fabric-react'; export interface ILayoutProps { domElement: HTMLElement; pages: IPage[]; + nav?: INavLink; + pageId?: number; themeVariant: IReadonlyTheme; } diff --git a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/components/Layouts/TreeLayout.tsx b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/components/Layouts/TreeLayout.tsx new file mode 100644 index 000000000..c7b0bdb14 --- /dev/null +++ b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/components/Layouts/TreeLayout.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import styles from './Layouts.module.scss'; +import { ILayoutProps } from './ILayoutProps'; +import * as strings from 'PageHierarchyWebPartStrings'; +import { Nav } from 'office-ui-fabric-react'; + +export const TreeLayout: React.FunctionComponent = props => { + return( +
+ {props.nav ? ( +
    + {
+ ) : ( + {strings.Message_NoChildrenFound} + )} +
); +}; diff --git a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/loc/en-us.js b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/loc/en-us.js index 5bf4b3237..bc3735978 100644 --- a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/loc/en-us.js +++ b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/loc/en-us.js @@ -5,6 +5,7 @@ define([], function() { "Configuration_Placeholder_ButtonLabel": "Configure", "Message_NoAncestorsFound": "No ancestors found for page. Update the parent page property to select a parent", "Message_NoChildrenFound": "No children found for page. Update the parent page property for other pages to set this as their parent.", + "Message_NoTreeFound": "No tree found for page. Update the tree settings to ensure tree is rendered.", "ParentPageMissing_Placeholder_IconText": "Parent Page Property is missing", "ParentPageMissing_Placeholder_Description": "Click button to add Parent Page Property to Site Pages", "ParentPageMissing_Placeholder_Description_NoPermissions": "You don't have permissions to add the property. Have a Site Owner edit this web part to enable the Parent Page Property.", @@ -15,9 +16,14 @@ define([], function() { "PropertyPane_Label_PagesToDisplay": "Choose pages to show based on their relationships to this page", "PropertyPane_PagesToDisplay_OptionText_Ancestors": "Ancestor Pages", "PropertyPane_PagesToDisplay_OptionText_Children": "Children Pages", + "PropertyPane_PagesToDisplay_OptionText_Tree": "Tree Hierarchy", + "PropertyPane_Label_TreeFrom": "Page Id for the Top Level of the Tree", "PropertyPane_GroupName_Debug": "Debug", "PropertyPane_Label_DebugPageId": "Debug Page Id", "PropertyPane_Label_VersionInfo": "Version: ", - "PropertyPane_Description_DebugPageId": "Provide a valid page list item id to see how the web part would render for it" + "PropertyPane_Description_DebugPageId": "Provide a valid page list item id to see how the web part would render for it", + "PropertyPane_Description_TreeFrom": "Please provide the page id for the root of the tree to render", + "PropertyPane_Label_TreeExpandTo": "Default Expand the Tree To Level", + "PropertyPane_Description_TreeExpandTo": "By default expand the tree to this level" } }); diff --git a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/loc/mystrings.d.ts b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/loc/mystrings.d.ts index 96e15f572..dfa200358 100644 --- a/samples/react-pages-hierarchy/src/webparts/pagehierarchy/loc/mystrings.d.ts +++ b/samples/react-pages-hierarchy/src/webparts/pagehierarchy/loc/mystrings.d.ts @@ -4,6 +4,7 @@ declare interface IPageHierarchyWebPartStrings { Configuration_Placeholder_ButtonLabel: string; Message_NoAncestorsFound: string; Message_NoChildrenFound: string; + Message_NoTreeFound: string; ParentPageMissing_Placeholder_IconText: string; ParentPageMissing_Placeholder_Description: string; ParentPageMissing_Placeholder_Description_NoPermissions: string; @@ -14,10 +15,15 @@ declare interface IPageHierarchyWebPartStrings { PropertyPane_Label_PagesToDisplay: string; PropertyPane_PagesToDisplay_OptionText_Ancestors: string; PropertyPane_PagesToDisplay_OptionText_Children: string; + PropertyPane_PagesToDisplay_OptionText_Tree: string; PropertyPane_GroupName_Debug: string; PropertyPane_Label_DebugPageId: string; PropertyPane_Label_VersionInfo: string; PropertyPane_Description_DebugPageId: string; + PropertyPane_Label_TreeFrom: string; + PropertyPane_Description_TreeFrom: string; + PropertyPane_Label_TreeExpandTo: string; + PropertyPane_Description_TreeExpandTo: string; } declare module 'PageHierarchyWebPartStrings' {