Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perspectives and tree widgets #1067

Merged
merged 29 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7429337
Load perspectives in app-loader
markus-moser Feb 24, 2025
b0bec01
Apply latest automatic api client updates
markus-moser Feb 24, 2025
aec69b7
Implement tree filters and permissions
markus-moser Feb 25, 2025
46ec976
Merge branch 'perspectives' of https://github.com/pimcore/studio-ui-b…
markus-moser Feb 25, 2025
f959fca
Update API client
markus-moser Feb 25, 2025
7f4f4ea
Apply eslint-fixer changes
markus-moser Feb 25, 2025
ffc0477
Respect rootFolderId from widget data instead of separate request
markus-moser Feb 25, 2025
66a9445
Implement showRoot logic for assets
markus-moser Feb 25, 2025
435de2c
Implement the showRoot logic for data objects
markus-moser Feb 25, 2025
901ed81
Fix permissions of root nodes
markus-moser Feb 25, 2025
abce062
Merge remote-tracking branch 'origin/1.x' into perspectives
markus-moser Feb 25, 2025
77bf05a
Automatic frontend build
markus-moser Feb 25, 2025
b515f41
Remove unused prop
markus-moser Feb 25, 2025
3fb081f
Remove console.log
markus-moser Feb 25, 2025
8c34dec
Respect expanded left and right widget setting
markus-moser Feb 25, 2025
dde9880
Merge branch 'perspectives' of https://github.com/pimcore/studio-ui-b…
markus-moser Feb 25, 2025
291e78a
Automatic frontend build
markus-moser Feb 25, 2025
aacdfe5
Add missing tree permission checks
markus-moser Feb 26, 2025
58daaae
Rename TreePermission enum to TreeAction
markus-moser Feb 26, 2025
7c93467
Automatic frontend build
markus-moser Feb 26, 2025
6166b37
Refactor main nav to registry approach
markus-moser Feb 26, 2025
fcdc05a
Add option for custom click handler in menu
markus-moser Feb 26, 2025
2104533
Check perspective permissions in main nav
markus-moser Feb 26, 2025
29018d6
Automatic frontend build
markus-moser Feb 26, 2025
a07045a
Fix hidden permission
markus-moser Feb 26, 2025
9501070
Automatic frontend build
markus-moser Feb 26, 2025
9270edc
Unify perspective permission enums
markus-moser Feb 27, 2025
48b003f
Merge branch 'perspectives' of https://github.com/pimcore/studio-ui-b…
markus-moser Feb 27, 2025
818731b
Automatic frontend build
markus-moser Feb 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/build/api/docs.jsonopenapi.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions assets/build/api/openapi-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ const config: ConfigFile = {
},
'../../js/src/core/modules/document/sites-slice.gen.ts': {
filterEndpoints: pathMatcher(/\/documents\/sites\//i)
},
'../../js/src/core/modules/perspectives/perspectives-slice.gen.ts': {
filterEndpoints: pathMatcher(/\/perspectives\//i)
}
},
exportName: 'api',
Expand Down
5 changes: 5 additions & 0 deletions assets/js/src/core/app/config/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

/* eslint-disable max-lines */
import { container } from '@Pimcore/app/depency-injection'
import { FolderTabManager } from '@Pimcore/modules/asset/editor/types/folder/tab-manager/folder-tab-manager'
import { IconLibrary } from '@Pimcore/modules/icon-library/services/icon-library'
Expand Down Expand Up @@ -143,6 +144,10 @@ import {
} from '@Pimcore/modules/element/dynamic-types/defintinitions/batch-edits/types/text/dynamic-type-batch-edit-text-area'
import { DynamicTypeObjectDataFieldCollection } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-field-collection'
import { DynamicTypeObjectDataObjectBrick } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-object-brick'
import { MainNavRegistry } from '@Pimcore/modules/app/nav/services/main-nav-registry'

// Main nav
container.bind(serviceIds.mainNavRegistry).to(MainNavRegistry).inSingletonScope()

// Widget manager
container.bind(serviceIds.widgetManager).to(WidgetRegistry).inSingletonScope()
Expand Down
3 changes: 3 additions & 0 deletions assets/js/src/core/app/config/services/service-ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export const dynamicTypeRegistriesServiceIds = {
}

export const serviceIds = {
// Main nav
mainNavRegistry: 'MainNavRegistry',

// Widget manager
widgetManager: 'WidgetManagerService',

Expand Down
1 change: 1 addition & 0 deletions assets/js/src/core/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import '@Pimcore/app/config/app-config'
import '@Pimcore/app/module-system/module-system'
import '@Pimcore/app/config/services'
import '@Pimcore/app/i18n'
import '@Pimcore/modules/app/nav'
import '@Pimcore/modules/icon-library'
import '@Pimcore/modules/asset'
import '@Pimcore/modules/data-object'
Expand Down
23 changes: 23 additions & 0 deletions assets/js/src/core/modules/app/app-loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import React, { useEffect, useState } from 'react'
import { api } from '@Pimcore/modules/auth/user/user-api-slice.gen'
import { api as settingsApi } from '@Pimcore/modules/app/settings/settings-slice.gen'
import { api as perspectivesApi } from '@Pimcore/modules/perspectives/perspectives-slice.gen'
import { useAppDispatch } from '@Pimcore/app/store'
import { useTranslationGetCollectionMutation } from '@Pimcore/modules/app/translations/translations-api-slice.gen'
import { useTranslation } from 'react-i18next'
Expand All @@ -25,6 +26,10 @@ import { useAlertModal } from '@Pimcore/components/modal/alert-modal/hooks/use-a
import { ErrorModalService } from '@Pimcore/modules/app/error-handler/services/error-modal-service'
import trackError, { ApiError } from '@Pimcore/modules/app/error-handler'
import { useMercureCreateCookieMutation } from './mercure-api-slice.gen'
import { setActivePerspective } from '../perspectives/active-perspective-slice'
import { updateOuterModel } from '../widget-manager/widget-manager-slice'
import { getInitialModelJson } from '../widget-manager/utils/widget-manager-outer-model'
import { isPlainObject } from 'lodash'

export interface IAppLoaderProps {
children: React.ReactNode
Expand Down Expand Up @@ -81,6 +86,23 @@ export const AppLoader = (props: IAppLoaderProps): React.JSX.Element => {
return await settingsFetcher
}

async function initActivePerspective (): Promise<any> {
const perspectiveFetcher = dispatch(perspectivesApi.endpoints.perspectiveGetConfigById.initiate({ perspectiveId: 'my_custom' }))

perspectiveFetcher
.then(({ data, isSuccess, isError, error }) => {
isError && trackError(new ApiError(error))

if (isSuccess && isPlainObject(data)) {
dispatch(setActivePerspective(data))
dispatch(updateOuterModel(getInitialModelJson()))
}
})
.catch(() => {})

return await perspectiveFetcher
}

async function loadTranslations (): Promise<any> {
await translations({ translation: { locale: 'en', keys: [] } })
.unwrap()
Expand All @@ -96,6 +118,7 @@ export const AppLoader = (props: IAppLoaderProps): React.JSX.Element => {
Promise.all([
initLoadUser(),
initSettings(),
initActivePerspective(),
loadTranslations()
]).then(() => {
setIsLoading(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import { createStyles } from 'antd-style'

export const useStlyes = createStyles(({
export const useStyles = createStyles(({
token,
css
}) => {
Expand Down
116 changes: 4 additions & 112 deletions assets/js/src/core/modules/app/base-layout/left-sidebar-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,122 +11,14 @@
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { Avatar } from 'antd'
import React from 'react'
import { useStlyes } from './left-sidebar-view.styles'
import { Icon } from '@Pimcore/components/icon/icon'
import { MainNav } from '@Pimcore/modules/app/nav/main-nav'
import { useMainNav } from '@Pimcore/modules/app/nav/hooks/use-main-nav'
import { Avatar } from 'antd'
import React from 'react'
import { useStyles } from './left-sidebar-view.styles'

export const LeftSidebarView = (): React.JSX.Element => {
const { styles } = useStlyes()
const { addNavItem } = useMainNav()

addNavItem({
path: 'Settings/Document Types',
permission: 'documents'
})

addNavItem({
path: 'Tools/Notes & Events',
className: 'item-style-modifier',
widgetConfig: {
name: 'Notes & Events',
id: 'notes-and-events',
component: 'notes-and-events',
config: {
icon: {
type: 'name',
value: 'notes-events'
}
}
}
})

addNavItem({
path: 'Settings/Tag Configuration',
className: 'item-style-modifier',
widgetConfig: {
name: 'Tag Configuration',
id: 'tag-configuration',
component: 'tag-configuration',
config: {
icon: {
type: 'name',
value: 'tag-configuration'
}
}
}
})

addNavItem({
path: 'Tools/Glossary'
})

addNavItem({
path: 'Settings/User & Roles/Users',
className: 'item-style-modifier',
widgetConfig: {
name: 'Users',
id: 'user-management',
component: 'user-management',
config: {
icon: {
type: 'name',
value: 'user'
}
}
}
})

addNavItem({
path: 'Settings/User & Roles/Roles',
widgetConfig: {
name: 'Roles',
id: 'role-management',
component: 'role-management',
config: {
icon: {
type: 'name',
value: 'user'
}
}
}
})

addNavItem({
path: 'Settings/User & Roles/Open ID Connect Config/Configuration'
})

addNavItem({
path: 'Settings',
icon: 'menu'
})

addNavItem({
path: 'Tools',
icon: 'accessory'
})

addNavItem({
path: 'Marketing',
icon: 'marketing'
})

addNavItem({
path: 'Customers',
icon: 'customers'
})

addNavItem({
path: 'Cache',
icon: 'cache'
})

addNavItem({
path: 'System Related',
icon: 'shield'
})
const { styles } = useStyles()

return (
<div className={ styles.leftSidebar }>
Expand Down
88 changes: 68 additions & 20 deletions assets/js/src/core/modules/app/nav/hooks/use-main-nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,86 @@
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { useAppDispatch, useAppSelector } from '@Pimcore/app/store'
import {
addNavItem as addNavItemAction,
type IMainNavItem
} from '@Pimcore/modules/app/nav/main-nav-slice'
import { container } from '@Pimcore/app/depency-injection'
import { useMemo } from 'react'
import { type IMainNavItem, type MainNavRegistry } from '../services/main-nav-registry'
import { serviceIds } from '@Pimcore/app/config/services/service-ids'
import { useUser } from '@Pimcore/modules/auth/hooks/use-user'
import { isAllowed } from '@Pimcore/modules/auth/permission-helper'
import { isAllowedInPerspective } from '@Pimcore/modules/perspectives/permission-checker'

interface IUseMainNavReturn {
addNavItem: (item: IMainNavItem) => void
getNavItems: IMainNavItem[]
navItems: IMainNavItem[]
}

export const useMainNav = (): IUseMainNavReturn => {
const dispatch = useAppDispatch()
const addNavItemToItemList = (items: IMainNavItem[], item: IMainNavItem): void => {
const levels = item.path.split('/')
if (levels.length > 4) {
console.warn('MainNav: Maximum depth of 4 levels is allowed, Item will be ignored', item)
return
}

function addNavItem (item: IMainNavItem): void {
let userIsAllowed = true
if (item.permission !== undefined) {
userIsAllowed = isAllowed(item.permission)
}
let currentLevel = items
levels.forEach((level: string, index) => {
let existingItem = currentLevel.find(i => i.id === level)
const isCurrentItem = index === levels.length - 1

if (!userIsAllowed) {
return
if (existingItem === undefined) {
existingItem = {
order: isCurrentItem ? item.order : 100,
id: level,
label: item.label ?? level,
path: levels.slice(0, index + 1).join('/'),
children: [],
icon: isCurrentItem ? item.icon : undefined,
widgetConfig: isCurrentItem ? item.widgetConfig : undefined,
className: isCurrentItem ? item.className : undefined,
perspectivePermission: isCurrentItem ? item.perspectivePermission : undefined,
perspectivePermissionHide: isCurrentItem ? item.perspectivePermissionHide : undefined
}
currentLevel.push(existingItem)
} else if (index === levels.length - 1) {
Object.assign(existingItem, {
icon: item.icon,
order: item.order ?? 100,
className: item.className
})
}

dispatch(addNavItemAction(item))
currentLevel = existingItem.children ?? []
currentLevel.sort((a, b) => (a.order ?? 100) - (b.order ?? 100))
})

items.sort((a, b) => (a.order ?? 100) - (b.order ?? 100))
}

export const useMainNav = (): IUseMainNavReturn => {
const mainNavRegistryService = container.get<MainNavRegistry>(serviceIds.mainNavRegistry)
const user = useUser()

const createNavItems = (): IMainNavItem[] => {
const items: IMainNavItem[] = []

mainNavRegistryService.getMainNavItems().forEach(item => {
if (item.permission !== undefined && !isAllowed(item.permission)) {
return
}

if (item.perspectivePermission !== undefined && !isAllowedInPerspective(item.perspectivePermission)) {
return
}

addNavItemToItemList(items, item)
})

return items
}

const getNavItems = useAppSelector(state => state['main-nav'].items)
const navItems = useMemo(() => {
return createNavItems()
}, [mainNavRegistryService.getMainNavItems(), user])

return {
addNavItem,
getNavItems
navItems
}
}
36 changes: 36 additions & 0 deletions assets/js/src/core/modules/app/nav/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* 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 { container } from '@Pimcore/app/depency-injection'
import { serviceIds } from '@Pimcore/app/config/services/service-ids'
import { moduleSystem } from '@Pimcore/app/module-system/module-system'
import { type MainNavRegistry } from './services/main-nav-registry'
import { NavPermission } from '@Pimcore/modules/perspectives/enums/nav-permission'

moduleSystem.registerModule({
onInit: () => {
const mainNavRegistryService = container.get<MainNavRegistry>(serviceIds.mainNavRegistry)

mainNavRegistryService.registerMainNavItem({
path: 'Settings',
icon: 'menu',
perspectivePermissionHide: NavPermission.SettingsHidden
})

mainNavRegistryService.registerMainNavItem({
path: 'Tools',
icon: 'accessory',
perspectivePermissionHide: NavPermission.ToolsHidden
})
}
})
Loading