Skip to content

Commit

Permalink
Add root node for relevant element trees (#875)
Browse files Browse the repository at this point in the history
* Add root node for relevant element trees

* Automatic frontend build

---------

Co-authored-by: vin0401 <[email protected]>
  • Loading branch information
vin0401 and vin0401 authored Jan 15, 2025
1 parent 15fcbe4 commit f736f48
Show file tree
Hide file tree
Showing 37 changed files with 1,922 additions and 83 deletions.
35 changes: 27 additions & 8 deletions assets/js/src/core/components/element-tree/element-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface TreeProps {
nodeId: number
nodeApiHook: any
maxItemsPerNode?: number
rootNode?: TreeNodeProps

renderNode: ElementType<TreeNodeProps>
renderNodeContent: ElementType<TreeNodeContentProps>
Expand Down Expand Up @@ -92,12 +93,15 @@ const ElementTree = (
renderNode = defaultProps.renderNode,
renderNodeContent = defaultProps.renderNodeContent,
contextMenu: ContextMenu,
rootNode,
...props
}: TreeProps
): React.JSX.Element => {
const selectedIdsState = useState<string[]>([])
const { styles } = useStyles()
const { nodeId } = props
const hasRootNode = rootNode !== undefined && parseInt(rootNode.id) === nodeId
const preparedRootNode = rootNode
const { apiHookResult, dataTransformer } = nodeApiHook({
id: nodeId,
level: -1
Expand Down Expand Up @@ -135,32 +139,47 @@ const ElementTree = (
return (<div>{'Error'}</div>)
}

let items: any[] = []
let items: TreeNodeProps[] = []

if (isLoading === false && data !== undefined) {
const { nodes } = dataTransformer(data)
items = nodes
}

const TreeNode = renderNode
if (hasRootNode) {
preparedRootNode!.children = items
preparedRootNode!.hasChildren = false
}

const TreeNode = renderNode
const treeContent = (
<div className={ ['tree', styles.tree].join(' ') }>
<TreeContext.Provider value={ treeContextValue }>
{items.map((item, index) => (
{hasRootNode && (
<TreeNode
internalKey={ `${index}` }
key={ item.id }
{ ...item }
key={ preparedRootNode!.id }
{ ...preparedRootNode! }
/>
))}
)}

{!hasRootNode && (
<>
{items.map((item, index) => (
<TreeNode
key={ item.id }
{ ...item }
internalKey={ `${index}` }
/>
))}
</>
)}
</TreeContext.Provider>
</div>
)

return (
<UploadProvider>
{isLoading === true && (
{isLoading === true && !hasRootNode && (
<Box padding={ { left: 'extra-small' } }>
<Skeleton />
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export const useStyles = createStyles(({ token, css }) => {
treeNode: css`
user-select: none;
&.tree-node--is-root {
.tree-node__content {
padding-left: ${token.paddingSM}px;
}
}
.tree-node__content {
cursor: pointer;
width: 100%;
Expand Down
19 changes: 14 additions & 5 deletions assets/js/src/core/components/element-tree/node/tree-node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface TreeNodeProps {
metaData?: any
type?: string
parentId?: string
isRoot?: boolean
}

const defaultProps: TreeNodeProps = {
Expand All @@ -57,7 +58,8 @@ const defaultProps: TreeNodeProps = {
properties: false
},
level: 0,
isLocked: false
isLocked: false,
isRoot: false
}

const { useToken } = theme
Expand All @@ -68,6 +70,7 @@ const TreeNode = ({
icon = defaultProps.icon,
label = defaultProps.label,
level = defaultProps.level,
isRoot = defaultProps.isRoot,
...props
}: TreeNodeProps): React.JSX.Element => {
const { token } = useToken()
Expand Down Expand Up @@ -102,6 +105,10 @@ const TreeNode = ({
classes.push('tree-node--selected')
}

if (isRoot === true) {
classes.push('tree-node--is-root')
}

return classes.join(' ')
}

Expand Down Expand Up @@ -220,10 +227,12 @@ const TreeNode = ({
}
tabIndex={ -1 }
>
<TreeExpander
node={ treeNodeProps }
state={ [isExpanded, setIsExpanded] }
/>
{isRoot !== true && (
<TreeExpander
node={ treeNodeProps }
state={ [isExpanded, setIsExpanded] }
/>
)}

<Upload { ...uploadProps }>
<div className="tree-node__content-wrapper">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@
import React from 'react'
import { TabTitleContainer, type TabTitleContainerProps } from '@Pimcore/modules/widget-manager/title/tab-title-container'
import { useAssetDraft } from '../../hooks/use-asset-draft'
import { useTranslation } from 'react-i18next'

export const TitleContainer = (props: TabTitleContainerProps): React.JSX.Element => {
const { node } = props
const { asset } = useAssetDraft(node.getConfig().id as number)
const { t } = useTranslation()

if (asset?.parentId === 0) {
node.getName = () => t('home')
}

return (
<TabTitleContainer
Expand Down
35 changes: 2 additions & 33 deletions assets/js/src/core/modules/asset/tree/hooks/use-node-api-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ import { type TreeNodeProps } from '@Pimcore/components/element-tree/node/tree-n
import { TreeContext } from '@Pimcore/components/element-tree/element-tree'
import {
type AssetGetTreeApiResponse,
type AssetPermissions,
useAssetGetTreeQuery
} from '@Pimcore/modules/asset/asset-api-slice-enhanced'
import { type TypedUseQueryHookResult } from '@reduxjs/toolkit/query/react'
import { type Dispatch, type SetStateAction, useContext, useState } from 'react'
import { getElementIcon } from '@Pimcore/modules/element/element-helper'
import { transformApiDataToNodes } from '../utils/transform-api-data-to-node'

interface AssetTreeAdditionalTreeProps {
pager?: number
Expand All @@ -43,37 +42,7 @@ export const useNodeApiHook = (node: TreeNodeProps): NodeApiHookReturnType => {
const apiHookResult = useAssetGetTreeQuery({ parentId: parseInt(node.id), pageSize: maxItemsPerNode!, page: 1, ...additionalQueryParams })

function dataTransformer (data: AssetGetTreeApiResponse): DataTransformerReturnType {
const nodes: TreeNodeProps[] = []

const assetData = data.items
assetData.forEach((assetNode) => {
nodes.push({
id: assetNode.id.toString(),
icon: getElementIcon(assetNode, { type: 'name', value: 'unknown' }),
label: assetNode.filename!,
type: assetNode.type,
parentId: assetNode.parentId.toString(),
children: [],
hasChildren: assetNode.hasChildren,
isLocked: assetNode.isLocked,
metaData: {
asset: assetNode
},
permissions: assetNode.permissions ?? [] as AssetPermissions,
level: node.level + 1,
...(() => {
if (node.level === -1) {
return { internalKey: `${assetNode.id}` }
}

return { internalKey: `${node.internalKey}-${assetNode.id}` }
})()
})
})

const total = data.totalItems ?? maxItemsPerNode

return { nodes, total }
return transformApiDataToNodes(node, data, maxItemsPerNode)
}

function mergeAdditionalQueryParams (newParams: AssetTreeAdditionalTreeProps): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { type TreeNodeProps } from '@Pimcore/components/element-tree/node/tree-n

export const withDraggable = (Component: ElementType<TreeNodeProps>): ElementType<TreeNodeProps> => {
const DraggableNodeContent = (props: TreeNodeProps): ReactElement => {
const metaData: Asset | undefined = props.metaData.asset
const metaData: Asset | undefined = props.metaData?.asset

if (props.metaData?.asset === undefined) {
return (
Expand Down
54 changes: 52 additions & 2 deletions assets/js/src/core/modules/asset/tree/tree-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,63 @@ import { SearchContainer } from './search/search-container'
import { withDraggable } from './node/with-draggable'
import { AssetTreeContextMenu } from '@Pimcore/modules/asset/tree/context-menu/context-menu'
import { useSettings } from '@Pimcore/modules/app/settings/hooks/use-settings'
import { type ElementIcon, useAssetGetTreeQuery } from '../asset-api-slice-enhanced'
import { transformApiDataToNodes } from './utils/transform-api-data-to-node'
import { Skeleton } from '@Pimcore/components/element-tree/skeleton/skeleton'
import { useTranslation } from 'react-i18next'
import { Box } from '@Pimcore/components/box/box'

export interface TreeContainerProps {
id: number
}

const TreeContainer = ({ id = 1, ...props }: TreeContainerProps): React.JSX.Element => {
export interface IDefaultRootNodeProps {
icon: ElementIcon
level: number
isRoot: true
}

const defaultRootNodeProps: IDefaultRootNodeProps = {
icon: { type: 'name', value: 'home-root-folder' },
level: -1,
isRoot: true
}

const TreeContainer = ({ id = 1 }: TreeContainerProps): React.JSX.Element => {
const { openAsset } = useAssetHelper()
const { isLoading, data: rootNodeData } = useAssetGetTreeQuery({ pageSize: 1, page: 1, excludeFolders: false, pathIncludeParent: true, pathIncludeDescendants: true })
const { asset_tree_paging_limit: assetTreePagingLimit } = useSettings()
const pagingLimit: number | undefined = assetTreePagingLimit
const { t } = useTranslation()

if (isLoading || rootNodeData === undefined) {
return (
<Box padding={ 'small' }>
<Skeleton />
</Box>
)
}

const transformedNodes = transformApiDataToNodes(
{
children: [],
icon: { type: 'name', value: 'home-root-folder' },
id: '0',
internalKey: '0',
label: '',
level: -1,
isLocked: false,
permissions: {}
},
rootNodeData,
pagingLimit
)

const rootNode: TreeNodeProps = {
...transformedNodes.nodes[0],
...defaultRootNodeProps,
label: t('home')
}

async function onSelect (node: TreeNodeProps): Promise<void> {
openAsset({
Expand All @@ -41,14 +90,15 @@ const TreeContainer = ({ id = 1, ...props }: TreeContainerProps): React.JSX.Elem
return (
<ElementTree
contextMenu={ AssetTreeContextMenu }
maxItemsPerNode={ assetTreePagingLimit }
maxItemsPerNode={ pagingLimit }
nodeApiHook={ useNodeApiHook }
nodeId={ id }
onSelect={ onSelect }
renderFilter={ SearchContainer }
renderNode={ withDraggable(TreeNode) }
renderNodeContent={ defaultProps.renderNodeContent }
renderPager={ PagerContainer }
rootNode={ rootNode }
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Pimcore
*
* This source file is available under two different licenses:
* - Pimcore Open Core License (POCL)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { type TreeNodeProps } from '@Pimcore/components/element-tree/node/tree-node'
import { type AssetGetTreeApiResponse, type AssetPermissions } from '../../asset-api-slice.gen'
import { getElementIcon } from '@Pimcore/modules/element/element-helper'

export interface DataTransformerReturnType {
nodes: TreeNodeProps[]
total: number
}

export const transformApiDataToNodes = (node: TreeNodeProps, data: AssetGetTreeApiResponse, maxItemsPerNode: number | undefined): DataTransformerReturnType => {
const nodes: TreeNodeProps[] = []

const assetData = data.items
assetData.forEach((assetNode) => {
nodes.push({
id: assetNode.id.toString(),
icon: getElementIcon(assetNode, { type: 'name', value: 'unknown' }),
label: assetNode.filename!,
type: assetNode.type,
parentId: assetNode.parentId.toString(),
children: [],
hasChildren: assetNode.hasChildren,
isLocked: assetNode.isLocked,
metaData: {
asset: assetNode
},
permissions: assetNode.permissions ?? [] as AssetPermissions,
level: node.level + 1,
...(() => {
if (node.level === -1) {
return { internalKey: `${assetNode.id}` }
}

return { internalKey: `${node.internalKey}-${assetNode.id}` }
})()
})
})

const total = data.totalItems ?? maxItemsPerNode

return { nodes, total }
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@
import React from 'react'
import { TabTitleContainer, type TabTitleContainerProps } from '@Pimcore/modules/widget-manager/title/tab-title-container'
import { useDataObjectDraft } from '@Pimcore/modules/data-object/hooks/use-data-object-draft'
import { useTranslation } from 'react-i18next'

export const TitleContainer = (props: TabTitleContainerProps): React.JSX.Element => {
const { node } = props
const { dataObject } = useDataObjectDraft(node.getConfig().id as number)
const { t } = useTranslation()

if (dataObject?.parentId === 0) {
node.getName = () => t('home')
}

return (
<TabTitleContainer//
Expand Down
Loading

0 comments on commit f736f48

Please sign in to comment.