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

Added paste options to context-menu #1117

Open
wants to merge 14 commits into
base: 1.x
Choose a base branch
from
Open
Changes from all commits
Commits
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
124 changes: 124 additions & 0 deletions assets/js/src/core/modules/data-object/actions/paste/use-paste.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* 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 { ItemType } from '@Pimcore/components/dropdown/dropdown'
import type { Element } from '@Pimcore/modules/element/element-helper'
import { useCopyPaste, type UseCopyPasteHookReturn } from '@Pimcore/modules/element/actions/copy-paste/use-copy-paste'
import { Icon } from '@Pimcore/components/icon/icon'
import { TreePermission } from '@Pimcore/modules/perspectives/enums/tree-permission'
import { checkElementPermission } from '@Pimcore/modules/element/permissions/permission-helper'
import { setNodeFetching } from '@Pimcore/components/element-tree/element-tree-slice'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useTreePermission } from '@Pimcore/modules/element/tree/provider/tree-permission-provider/use-tree-permission'
import { useAppDispatch } from '@Pimcore/app/store'
import { useTreeId } from '@Pimcore/modules/element/tree/provider/tree-id-provider/use-tree-id'

interface UsePasteHookParams {
storedNode: UseCopyPasteHookReturn['storedNode']
nodeTask: UseCopyPasteHookReturn['nodeTask']
}

export interface UsePasteHookReturn {
pasteAsChildTreeContextMenuItem: (node: TreeNodeProps) => ItemType
pasteAsChildRecursiveTreeContextMenuItem: (node: TreeNodeProps) => ItemType
pasteRecursiveUpdatingReferencesTreeContextMenuItem: (node: TreeNodeProps) => ItemType
pasteOnlyContentsTreeContextMenuItem: (node: TreeNodeProps) => ItemType
isPasteMenuHidden: (node: Element | TreeNodeProps) => boolean
}

export const usePaste = ({ storedNode, nodeTask }: UsePasteHookParams): UsePasteHookReturn => {
const { t } = useTranslation()
const { isTreeActionAllowed } = useTreePermission()
const dispatch = useAppDispatch()
const { paste } = useCopyPaste('data-object')
const { treeId } = useTreeId(true)

const pasteAsChildRecursiveTreeContextMenuItem = (node: TreeNodeProps): ItemType => {
return {
label: t('element.tree.paste-as-child-recursive'),
key: 'pasteAsChildRecursive',
icon: <Icon value={ 'paste' } />,
hidden: isPasteOpenHidden(node),
onClick: async () => {
dispatch(setNodeFetching({ treeId, nodeId: String(node.id), isFetching: true }))
await paste(parseInt(node.id), { recursive: true, updateReferences: false }, storedNode)
}
}
}

const pasteRecursiveUpdatingReferencesTreeContextMenuItem = (node: TreeNodeProps): ItemType => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option still does not work

return {
label: t('element.tree.paste-recursive-updating-references'),
key: 'pasteRecursiveUpdatingReferences',
icon: <Icon value={ 'paste' } />,
hidden: isPasteOpenHidden(node),
onClick: async () => {
dispatch(setNodeFetching({ treeId, nodeId: String(node.id), isFetching: true }))
await paste(parseInt(node.id))
}
}
}

const pasteAsChildTreeContextMenuItem = (node: TreeNodeProps): ItemType => {
return {
label: t('element.tree.paste-as-child'),
key: 'pasteAsChild',
icon: <Icon value={ 'paste' } />,
hidden: isPasteOpenHidden(node),
onClick: async () => {
dispatch(setNodeFetching({ treeId, nodeId: String(node.id), isFetching: true }))
await paste(parseInt(node.id), { recursive: false, updateReferences: false })
}
}
}

const pasteOnlyContentsTreeContextMenuItem = (node: TreeNodeProps): ItemType => {
return {
label: t('element.tree.paste-only-contents'),
key: 'pasteOnlyContents',
icon: <Icon value={ 'paste' } />,
hidden: isPasteOnlyContentsHidden(node),
onClick: async () => {
console.log('not implemented!')
}
}
}

const isPasteOpenHidden = (node: Element | TreeNodeProps): boolean => {
return !isTreeActionAllowed(TreePermission.Paste) ||
(storedNode === undefined || nodeTask !== 'copy') ||
!checkElementPermission(node.permissions, 'create')
}

const isPasteOnlyContentsHidden = (node: Element | TreeNodeProps): boolean => {
return isPasteOpenHidden(node) ||
node.type === 'folder' ||
node.isLocked ||
storedNode?.type !== node.type
}

const isPasteMenuHidden = (node: Element | TreeNodeProps): boolean => {
return isPasteOpenHidden(node) &&
isPasteOnlyContentsHidden(node)
}

return {
pasteAsChildTreeContextMenuItem,
pasteAsChildRecursiveTreeContextMenuItem,
pasteRecursiveUpdatingReferencesTreeContextMenuItem,
pasteOnlyContentsTreeContextMenuItem,
isPasteMenuHidden
}
}
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ import { useCopyPaste } from '@Pimcore/modules/element/actions/copy-paste/use-co
import { useLock } from '@Pimcore/modules/element/actions/lock/use-lock'
import { getElementActionCacheKey } from '@Pimcore/modules/element/element-helper'
import { useAddObject } from '../../actions/add-object/use-add-object'
import { usePaste } from '@Pimcore/modules/data-object/actions/paste/use-paste'

export interface DataObjectTreeContextMenuProps {
node: TreeNodeProps
@@ -37,20 +38,38 @@ export const DataObjectTreeContextMenu = (props: DataObjectTreeContextMenuProps)
const { renameTreeContextMenuItem } = useRename('data-object', getElementActionCacheKey('data-object', 'rename', parseInt(node.id)))
const { deleteTreeContextMenuItem } = useDelete('data-object', getElementActionCacheKey('data-object', 'delete', parseInt(node.id)))
const { refreshTreeContextMenuItem } = useRefreshTree('data-object')
const { copyTreeContextMenuItem, cutTreeContextMenuItem, pasteTreeContextMenuItem, pasteCutContextMenuItem } = useCopyPaste('data-object')
const { copyTreeContextMenuItem, cutTreeContextMenuItem, pasteTreeContextMenuItem, pasteCutContextMenuItem, nodeTask, storedNode } = useCopyPaste('data-object')
const { lockTreeContextMenuItem, lockAndPropagateTreeContextMenuItem, unlockTreeContextMenuItem, unlockAndPropagateTreeContextMenuItem, isLockMenuHidden } = useLock('data-object')
const {
pasteAsChildRecursiveTreeContextMenuItem,
pasteRecursiveUpdatingReferencesTreeContextMenuItem,
pasteAsChildTreeContextMenuItem,
pasteOnlyContentsTreeContextMenuItem,
isPasteMenuHidden
} = usePaste({ storedNode, nodeTask })
const { addObjectTreeContextMenuItem } = useAddObject()

const items: DropdownMenuProps['items'] = [
addObjectTreeContextMenuItem(node),
addFolderTreeContextMenuItem(node),
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't show paste twice:

image

label: t('element.tree.paste'),
key: 'paste',
icon: <Icon value={ 'paste' } />,
hidden: isPasteMenuHidden(node),
children: [
pasteAsChildRecursiveTreeContextMenuItem(node),
pasteRecursiveUpdatingReferencesTreeContextMenuItem(node),
pasteAsChildTreeContextMenuItem(node),
pasteOnlyContentsTreeContextMenuItem(node)
]
},
renameTreeContextMenuItem(node),
copyTreeContextMenuItem(node),
pasteTreeContextMenuItem(node),
cutTreeContextMenuItem(node),
pasteCutContextMenuItem(parseInt(node.id)),
deleteTreeContextMenuItem(node),

{
label: t('element.tree.context-menu.advanced'),
key: 'advanced',
Original file line number Diff line number Diff line change
@@ -25,35 +25,42 @@ import { checkElementPermission } from '@Pimcore/modules/element/permissions/per
import { type Element } from '@Pimcore/modules/element/element-helper'
import { useTreePermission } from '../../tree/provider/tree-permission-provider/use-tree-permission'
import { TreePermission } from '../../../perspectives/enums/tree-permission'
import { markNodeDeleting, refreshSourceNode, refreshTargetNode, setNodeFetching } from '@Pimcore/components/element-tree/element-tree-slice'
import {
markNodeDeleting,
refreshSourceNode,
refreshTargetNode,
setNodeFetching
} from '@Pimcore/components/element-tree/element-tree-slice'
import { useAppDispatch } from '@Pimcore/app/store'
import { isUndefined } from 'lodash'
import { useTreeId } from '../../tree/provider/tree-id-provider/use-tree-id'

type ElementPartial = Pick<Element, 'id' | 'parentId'>
type StoreNode = TreeNodeProps | Element | undefined

export interface UseCopyPasteHookReturn {
storedNode: StoreNode
nodeTask: 'copy' | 'cut' | undefined
copy: (node: TreeNodeProps) => void
cut: (node: TreeNodeProps) => void
paste: (parentId: number) => Promise<void>
paste: (parentId: number, cloneParameters?: CloneParameters, node?: StoreNode) => Promise<void>
pasteCut: (parentId: number) => Promise<void>
move: (props: MoveProps) => Promise<void>
copyTreeContextMenuItem: (node: TreeNodeProps) => ItemType
copyContextMenuItem: (node: Element, onFinish?: () => void) => ItemType
cutTreeContextMenuItem: (node: TreeNodeProps) => ItemType
cutContextMenuItem: (node: Element, onFinish?: () => void) => ItemType
pasteTreeContextMenuItem: (node: TreeNodeProps) => ItemType
pasteContextMenuItem: (node: Element, onFinish?: () => void) => ItemType
pasteCutContextMenuItem: (parentId: number) => ItemType
}

type elementPartial = Pick<Element, 'id' | 'parentId'>

export interface MoveProps {
currentElement: elementPartial
targetElement: elementPartial
currentElement: ElementPartial
targetElement: ElementPartial
}

export const useCopyPaste = (elementType: ElementType): UseCopyPasteHookReturn => {
const [storedNode, setStoredNode] = useState<TreeNodeProps | Element | undefined>()
const [storedNode, setStoredNode] = useState<StoreNode>()
const [nodeTask, setNodeTask] = useState<'copy' | 'cut' | undefined>()
const { elementPatch, elementClone } = useElementApi(elementType)
const { t } = useTranslation()
@@ -101,14 +108,18 @@ export const useCopyPaste = (elementType: ElementType): UseCopyPasteHookReturn =
}
}

const paste = async (parentId: number, cloneParameters: CloneParameters = { recursive: true, updateReferences: true }): Promise<void> => {
if (storedNode === undefined) {
const paste = async (
parentId: number,
cloneParameters: CloneParameters = { recursive: true, updateReferences: true },
node: StoreNode = storedNode
): Promise<void> => {
if (node === undefined) {
return
}

const id = typeof storedNode.id === 'number'
? storedNode.id
: parseInt(storedNode.id)
const id = typeof node.id === 'number'
? node.id
: parseInt(node.id)

const cloneResponse = await elementClone({
id,
@@ -230,19 +241,6 @@ export const useCopyPaste = (elementType: ElementType): UseCopyPasteHookReturn =
}
}

const pasteContextMenuItem = (node: Element, onFinish?: () => void): ItemType => {
return {
label: t('element.tree.paste'),
key: 'paste',
icon: <Icon value={ 'paste' } />,
hidden: (storedNode === undefined || nodeTask !== 'copy') || !checkElementPermission(node.permissions, 'create'),
onClick: async () => {
await paste(node.id)
onFinish?.()
}
}
}

const pasteCutContextMenuItem = (parentId: number): ItemType => {
return {
label: t('element.tree.paste-cut'),
@@ -256,6 +254,8 @@ export const useCopyPaste = (elementType: ElementType): UseCopyPasteHookReturn =
}

return {
storedNode,
nodeTask,
copy,
cut,
paste,
@@ -266,7 +266,6 @@ export const useCopyPaste = (elementType: ElementType): UseCopyPasteHookReturn =
cutTreeContextMenuItem,
cutContextMenuItem,
pasteTreeContextMenuItem,
pasteContextMenuItem,
pasteCutContextMenuItem
}
}

This file was deleted.

This file was deleted.

12 changes: 0 additions & 12 deletions public/build/3f707b62-23be-435d-9ad9-c8f75d7c9795/entrypoints.json

This file was deleted.

14 changes: 0 additions & 14 deletions public/build/3f707b62-23be-435d-9ad9-c8f75d7c9795/manifest.json

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"entrypoints": {
"main": {
"js": [
"/bundles/pimcorestudioui/build/4b6ac96a-b972-4c39-a445-8a9c828ff769/main.js"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"bundles/pimcorestudioui/build/4b6ac96a-b972-4c39-a445-8a9c828ff769/main.js": "/bundles/pimcorestudioui/build/4b6ac96a-b972-4c39-a445-8a9c828ff769/main.js"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"entrypoints": {
"vendor": {
"js": [
"/bundles/pimcorestudioui/build/6bd14887-335a-402a-922d-4a7cf6f0b7de/vendor.js"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"bundles/pimcorestudioui/build/6bd14887-335a-402a-922d-4a7cf6f0b7de/vendor.js": "/bundles/pimcorestudioui/build/6bd14887-335a-402a-922d-4a7cf6f0b7de/vendor.js"
}

This file was deleted.

This file was deleted.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions public/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/entrypoints.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"entrypoints": {
"core-dll": {
"css": [
"/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/core-dll.css"
],
"js": [
"/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/core-dll.js"
]
}
}
}
14 changes: 14 additions & 0 deletions public/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/core-dll.css": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/core-dll.css",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/core-dll.js": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/core-dll.js",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/105.js": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/105.js",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/fonts/Lato-Light.ttf": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/fonts/Lato-Light.c7400fca.ttf",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/fonts/Lato-Regular.ttf": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/fonts/Lato-Regular.9d883d54.ttf",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/fonts/Lato-Bold.ttf": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/fonts/Lato-Bold.636be8de.ttf",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/spritesheet.svg": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/spritesheet.a4e0eb7a.svg",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/spritesheet-2x.png": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/spritesheet-2x.7ea3a6d4.png",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/spritesheet.png": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/spritesheet.ef32ea2b.png",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/marker-icon.png": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/marker-icon.2b3e1faf.png",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/layers-2x.png": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/layers-2x.8f2c4d11.png",
"bundles/pimcorestudioui/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/layers.png": "/bundles/pimcorestudioui/build/d5256e89-fa5a-4e2f-82b9-6abae46c6806/images/layers.416d9136.png"
}
Binary file modified public/build/studio-npm-package.tgz
Binary file not shown.
6 changes: 5 additions & 1 deletion translations/studio.en.yaml
Original file line number Diff line number Diff line change
@@ -394,6 +394,10 @@ element.tree.context-menu.expand-children: Expand Children
asset.tree.context-menu.clear-thumbnails: Clear Thumbnails
element.tree.copy: Copy
element.tree.paste: Paste
element.tree.paste-as-child: Paste as child
element.tree.paste-as-child-recursive: Paste as child (recursive)
element.tree.paste-recursive-updating-references: Paste recursive, updating references
element.tree.paste-only-contents: Paste only contents here
element.tree.cut: Cut
element.tree.paste-cut: Paste cut-out element
element.lock: Lock
@@ -691,4 +695,4 @@ detached-tab.draft-tooltip-addon: You can publish in the associated element/obj
details: Details
element: Element
entries: entries
entry: entry
entry: entry