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

Quick action open #1087

Merged
merged 25 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f150637
open asset and document basic wiring
xIrusux Feb 24, 2025
771893d
more
xIrusux Feb 24, 2025
5501da9
open asset api call and open
xIrusux Feb 25, 2025
6512135
change of approach + tranlsations
xIrusux Feb 25, 2025
12a206d
open open element straight from menu button
xIrusux Feb 26, 2025
10903c1
final
xIrusux Feb 26, 2025
8ef4f78
Automatic frontend build
xIrusux Feb 27, 2025
e149e19
use one element to open object, document and asset
xIrusux Feb 27, 2025
ca93ce5
Merge branch '1.x' of github.com:pimcore/studio-ui-bundle into quick-…
xIrusux Feb 27, 2025
e48fe72
adjust to new set up nav
xIrusux Feb 27, 2025
9f1fa61
adjustmends to make onclick work
xIrusux Feb 27, 2025
5d994e0
pass button to main nav
xIrusux Feb 27, 2025
8bac7ac
merge
xIrusux Feb 27, 2025
5da507c
Automatic frontend build
xIrusux Feb 27, 2025
510d1f7
adjust passed in button styling, error handling and tranlsations
xIrusux Feb 28, 2025
a4dc652
Merge branch 'quick-action-open' of github.com:pimcore/studio-ui-bund…
xIrusux Feb 28, 2025
24d1710
Automatic frontend build
xIrusux Feb 28, 2025
ea060fb
error message simplification
xIrusux Feb 28, 2025
9f40467
Merge branch 'quick-action-open' of github.com:pimcore/studio-ui-bund…
xIrusux Feb 28, 2025
ddd3896
Automatic frontend build
xIrusux Feb 28, 2025
c8a90fe
Move file menu to top
markus-moser Mar 3, 2025
a89097c
Automatic frontend build
markus-moser Mar 3, 2025
1f43dcc
use correct endpoint and errorKey
xIrusux Mar 3, 2025
2ce58b9
Merge branch 'quick-action-open' of github.com:pimcore/studio-ui-bund…
xIrusux Mar 3, 2025
e34b001
Automatic frontend build
xIrusux Mar 3, 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
1 change: 1 addition & 0 deletions assets/js/src/core/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ import '@Pimcore/modules/element/dynamic-types'
import '@Pimcore/modules/user'
import '@Pimcore/modules/tags'
import '@Pimcore/modules/notes-and-events'
import '@Pimcore/modules/open-element'
import 'flexlayout-react/style/light.css'
import '../../../css/globals.css'
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { type IErrorGetContent } from '@Pimcore/modules/app/error-handler/types'

type ApiErrorData = FetchBaseQueryError | SerializedError

interface IApiErrorDetails {
export interface IApiErrorDetails {
detail?: string
errorKey?: string
message?: string
Expand Down
2 changes: 2 additions & 0 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 @@ -44,6 +44,8 @@ const addNavItemToItemList = (items: IMainNavItem[], item: IMainNavItem): void =
children: [],
icon: isCurrentItem ? item.icon : undefined,
widgetConfig: isCurrentItem ? item.widgetConfig : undefined,
onClick: isCurrentItem ? item.onClick : undefined,
button: isCurrentItem ? item.button : undefined,
className: isCurrentItem ? item.className : undefined,
perspectivePermission: isCurrentItem ? item.perspectivePermission : undefined,
perspectivePermissionHide: isCurrentItem ? item.perspectivePermissionHide : undefined
Expand Down
6 changes: 6 additions & 0 deletions assets/js/src/core/modules/app/nav/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ moduleSystem.registerModule({
onInit: () => {
const mainNavRegistryService = container.get<MainNavRegistry>(serviceIds.mainNavRegistry)

mainNavRegistryService.registerMainNavItem({
path: 'File',
icon: 'document',
perspectivePermissionHide: NavPermission.FileHidden
})

mainNavRegistryService.registerMainNavItem({
path: 'Settings',
icon: 'menu',
Expand Down
67 changes: 39 additions & 28 deletions assets/js/src/core/modules/app/nav/main-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { IconButton } from '@Pimcore/components/icon-button/icon-button'
import { useTranslation } from 'react-i18next'
import { type IMainNavItem } from './services/main-nav-registry'
import { isAllowedInPerspective } from '@Pimcore/modules/perspectives/permission-checker'
import { isUndefined } from 'lodash'

export const MainNav = (): React.JSX.Element => {
const { t } = useTranslation()
Expand All @@ -47,7 +48,7 @@ export const MainNav = (): React.JSX.Element => {

const renderNavItem = (item: IMainNavItem, index: string, level = 0): React.JSX.Element => {
const isVisible = (item.children !== undefined && item.children.length > 0) ||
(item.widgetConfig !== undefined)
(item.widgetConfig !== undefined) || (item.onClick !== undefined) || (item.button !== undefined)

const isHiddenInPerspective = item.perspectivePermissionHide !== undefined && isAllowedInPerspective(item.perspectivePermissionHide)

Expand All @@ -60,32 +61,40 @@ export const MainNav = (): React.JSX.Element => {
className={ `main-nav__list-item ${openKeys.includes(index) ? 'is-active' : ''} ${item.className ?? ''}` }
key={ item.id }
>
<button
className={ 'main-nav__list-btn' }
onClick={ () => {
if (item.children !== undefined && item.children.length > 0) {
handleOpenState(index)
} else if (item.onClick !== undefined) {
item.onClick()
setIsOpen(false)
} else if (item.widgetConfig !== undefined) {
openMainWidget(item.widgetConfig)
setIsOpen(false)
}
} }
>
{item.icon !== undefined ? (<Icon value={ item.icon } />) : null}
{item.label}

{item.children !== undefined && item.children.length > 0
? (
<Icon
className={ 'main-nav__list-btn-icon' }
value={ 'chevron-right' }
/>
)
: null}
</button>
{!isUndefined(item.button)
? (
<div>
{item.button()}
</div>
)
: (
<button
className={ 'main-nav__list-btn' }
onClick={ () => {
if (item.children !== undefined && item.children.length > 0) {
handleOpenState(index)
} else if (item.onClick !== undefined) {
item.onClick()
setIsOpen(false)
} else if (item.widgetConfig !== undefined) {
openMainWidget(item.widgetConfig)
setIsOpen(false)
}
} }
>
{item.icon !== undefined ? (<Icon value={ item.icon } />) : null}
{item.label}

{item.children !== undefined && item.children.length > 0
? (
<Icon
className={ 'main-nav__list-btn-icon' }
value={ 'chevron-right' }
/>
)
: null}
</button>
)}

{item.children !== undefined && item.children.length > 0
? (
Expand Down Expand Up @@ -119,7 +128,9 @@ export const MainNav = (): React.JSX.Element => {
<div ref={ elRef }>
<IconButton
icon={ { value: 'menu' } }
onClick={ () => { setIsOpen(!isOpen) } }
onClick={ () => {
setIsOpen(!isOpen)
} }
type={ 'text' }
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import { type WidgetManagerTabConfig } from '@Pimcore/modules/widget-manager/widget-manager-slice'
import { injectable } from 'inversify'
import type React from 'react'

export interface IMainNavItem {
path: string
Expand All @@ -25,6 +26,7 @@ export interface IMainNavItem {
perspectivePermission?: string
perspectivePermissionHide?: string
onClick?: () => void
button?: () => React.JSX.Element
widgetConfig?: WidgetManagerTabConfig
className?: string
}
Expand Down
14 changes: 14 additions & 0 deletions assets/js/src/core/modules/element/element-api-slice-enhanced.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,19 @@

import { type AssetPermissions } from '@Pimcore/modules/asset/asset-api-slice.gen'
import { type DataObjectPermissions } from '@Pimcore/modules/data-object/data-object-api-slice.gen'
import {
api
} from '@Pimcore/modules/element/element-api-slice.gen'

export type ElementPermissions = AssetPermissions & DataObjectPermissions

export const {
useElementDeleteMutation,
useElementGetDeleteInfoQuery,
useElementFolderCreateMutation,
useElementGetContextPermissionsQuery,
useElementGetIdByPathQuery,
useElementGetSubtypeQuery,
useElementResolveBySearchTermQuery,
useLazyElementResolveBySearchTermQuery
} = api
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { uuid } from '@Pimcore/utils/uuid'
import { Flex } from '@Pimcore/components/flex/flex'
import { useElementHelper } from '@Pimcore/modules/element/hooks/use-element-helper'
import { isUndefined } from 'lodash'
import { NoteModal } from '@Pimcore/modules/notes-and-events/table/note-modal'
import { NoteModal } from '@Pimcore/modules/notes-and-events/note-modal'
import { Tag } from '@Pimcore/components/tag/tag'

type DataNoteWithActions = DataNote & {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* 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 {
useLazyElementResolveBySearchTermQuery
} from '@Pimcore/modules/element/element-api-slice-enhanced'
import { useElementHelper } from '@Pimcore/modules/element/hooks/use-element-helper'
import { type ElementGetIdByPathApiResponse } from '@Pimcore/modules/element/element-api-slice.gen'
import { type ElementType } from '@Pimcore/types/enums/element/element-type'
import trackError, { ApiError } from '@Pimcore/modules/app/error-handler'
import { type IApiErrorDetails } from '@Pimcore/modules/app/error-handler/classes/api-error'

interface OpenAssetHelperReturn {
openElementByPathOrId: (value: string | number | undefined, elementType: ElementType) => Promise<void>
isLoading: boolean
}

export const openElementHelper = (): OpenAssetHelperReturn => {
const { openElement } = useElementHelper()

const [fetchElementId, { isLoading }] = useLazyElementResolveBySearchTermQuery()
const handleFetchAndOpen = async (searchTerm: string, elementType: ElementType): Promise<void> => {
try {
const result: ElementGetIdByPathApiResponse = await fetchElementId({
elementType,
searchTerm
}).unwrap()
await openElement({ id: result.id, type: elementType })
} catch (unknownError) {
const error = unknownError as IApiErrorDetails
trackError(new ApiError(error))
}
}

const openElementByPathOrId = async (value: string | number, elementType: ElementType): Promise<void> => {
if (!isNaN(Number(value))) {
await handleFetchAndOpen(value.toString(), elementType)
} else if (typeof value === 'string') {
await handleFetchAndOpen(value, elementType)
}
}

return { openElementByPathOrId, isLoading }
}
47 changes: 47 additions & 0 deletions assets/js/src/core/modules/open-element/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* 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 '../app/nav/services/main-nav-registry'
import { NavPermission } from '../perspectives/enums/nav-permission'
import { OpenElement } from '@Pimcore/modules/open-element/open-element'
import React from 'react'

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

mainNavRegistryService.registerMainNavItem({
path: 'File/Open Asset',
label: 'Open Asset',
perspectivePermission: NavPermission.OpenAsset,
button: () => React.createElement(OpenElement, { elementType: 'asset' })
})

mainNavRegistryService.registerMainNavItem({
path: 'File/Open Document',
label: 'Open Document',
perspectivePermission: NavPermission.OpenDocument,
button: () => React.createElement(OpenElement, { elementType: 'document' })
})

mainNavRegistryService.registerMainNavItem({
path: 'File/Open Data Object',
label: 'Open Data Object',
perspectivePermission: NavPermission.OpenObject,
button: () => React.createElement(OpenElement, { elementType: 'data-object' })
})
}
})
84 changes: 84 additions & 0 deletions assets/js/src/core/modules/open-element/open-element.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* 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 { useTranslation } from 'react-i18next'
import { useFormModal } from '@Pimcore/components/modal/form-modal/hooks/use-form-modal'
import { openElementHelper } from '@Pimcore/modules/open-element/hooks/open-element-helper'
import { type ElementType } from '@Pimcore/types/enums/element/element-type'
import React from 'react'

interface OpenDocumentProp {
elementType: ElementType
}

export const OpenElement = ({ elementType }: OpenDocumentProp): React.JSX.Element => {
const { openElementByPathOrId } = openElementHelper()
const { t } = useTranslation()
const { input } = useFormModal()

const buttonTexts = {
'data-object': t('open-data-object.button'),
asset: t('open-asset.button'),
document: t('open-document.button')
}

const modalTexts = {
'data-object': {
title: t('open-data-object-modal.title'),
label: t('open-data-object-modal.label'),
requiredMessage: t('open-data-object-modal.required-message'),
okText: t('open-data-object-modal.ok-button'),
cancelText: t('open-data-object-modal.cancel-button')
},
asset: {
title: t('open-asset-modal.title'),
label: t('open-asset-modal.label'),
requiredMessage: t('open-asset-modal.required-message'),
okText: t('open-asset-modal.ok-button'),
cancelText: t('open-asset-modal.cancel-button')
},
document: {
title: t('open-document-modal.title'),
label: t('open-document-modal.label'),
requiredMessage: t('open-document-modal.required-message'),
okText: t('open-document-modal.ok-button'),
cancelText: t('open-document-modal.cancel-button')
}
}

const texts = modalTexts[elementType]
const handleClick = (): void => {
input({
title: texts.title,
label: texts.label,
rule: {
required: true,
message: texts.requiredMessage
},
okText: texts.okText,
cancelText: texts.cancelText,
onOk: async (value: string) => {
await openElementByPathOrId(value, elementType)
}
})
}

return (
<button
className={ 'main-nav__list-btn' }
onClick={ handleClick }
>
{buttonTexts[elementType]}
</button>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export enum NavPermission {
ToolsHidden = 'extras.hidden',
NotesAndEvents = 'extras.notesEvents',

FileHidden = 'file.hidden',
OpenDocument = 'file.open_document',
OpenObject = 'file.open_object',
OpenAsset = 'file.open_asset',

SettingsHidden = 'settings.hidden',
TagConfiguration = 'settings.tagConfiguration',
UsersHidden = 'settings.users_hidden',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"entrypoints": {
"vendor": {
"js": [
"/bundles/pimcorestudioui/build/227cac5c-7325-4a9e-8efb-483ca25b1a9f/vendor.js"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"bundles/pimcorestudioui/build/227cac5c-7325-4a9e-8efb-483ca25b1a9f/vendor.js": "/bundles/pimcorestudioui/build/227cac5c-7325-4a9e-8efb-483ca25b1a9f/vendor.js"
}
Loading