Skip to content
Merged
Show file tree
Hide file tree
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
12 changes: 10 additions & 2 deletions src/components/RightClick/RightClickAddMenu.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useContext } from 'react'
import { useLocation } from 'react-router-dom'

import { useSharingContext } from 'cozy-sharing'
import { useBreakpoints } from 'cozy-ui/transpiled/react/providers/Breakpoints'
Expand All @@ -14,9 +15,12 @@ const AddMenu = ({ children, ...props }) => {
const { onOpen } = useRightClick()
const { handleToggle, handleOfflineClick, isOffline } =
useContext(AddMenuContext)
const location = useLocation()

const isInViewerMode = location.pathname.includes('/file/')

if (!children) return null
if (!isDesktop)
if (!isDesktop || isInViewerMode)
return React.Children.map(children, child =>
React.isValidElement(child)
? React.cloneElement(child, {
Expand Down Expand Up @@ -46,11 +50,15 @@ const RightClickAddMenu = ({ children, ...props }) => {
const { isOpen, position } = useRightClick()
const { displayedFolder } = useDisplayedFolder()
const { hasWriteAccess } = useSharingContext()
const location = useLocation()

const isFolderReadOnly = displayedFolder
? !hasWriteAccess(displayedFolder._id, displayedFolder.driveId)
: false

const isInViewerMode = location.pathname.includes('/file/')
const shouldShowAddMenu = isOpen('AddMenu') && !isInViewerMode

return (
<AddMenuProvider
canCreateFolder={true}
Expand All @@ -62,7 +70,7 @@ const RightClickAddMenu = ({ children, ...props }) => {
componentsProps={{
AddMenu: {
anchorReference: 'anchorPosition',
anchorPosition: isOpen('AddMenu')
anchorPosition: shouldShowAddMenu
? { top: position.mouseY, left: position.mouseX }
: undefined
}
Expand Down
20 changes: 16 additions & 4 deletions src/components/RightClick/RightClickFileMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ import ActionsMenu from 'cozy-ui/transpiled/react/ActionsMenu'
import { useBreakpoints } from 'cozy-ui/transpiled/react/providers/Breakpoints'

import { useRightClick } from '@/components/RightClick/RightClickProvider'
import { getContextMenuActions } from '@/modules/actions/helpers'
import { useSelectionContext } from '@/modules/selection/SelectionProvider'

const RightClickFileMenu = ({ doc, actions, disabled, children, ...props }) => {
const RightClickFileMenu = ({
doc,
actions,
disabled,
children,
prefixMenuId,
...props
}) => {
const { position, isOpen, onOpen, onClose } = useRightClick()
const { isDesktop } = useBreakpoints()
const { selectedItems, isItemSelected } = useSelectionContext()

const contextMenuActions = getContextMenuActions(actions)

if (!children) return null
if (disabled || !isDesktop)
return React.Children.map(children, child =>
Expand All @@ -28,16 +38,18 @@ const RightClickFileMenu = ({ doc, actions, disabled, children, ...props }) => {
? React.cloneElement(child, {
...props,
onContextMenu: ev => {
onOpen(ev, `FileMenu-${doc._id}`)
onOpen(ev, `${prefixMenuId ?? 'FileMenu'}-${doc._id}}`)
ev.preventDefault()
ev.stopPropagation()
}
})
: null
)}
{isOpen(`FileMenu-${doc._id}`) && (
{isOpen(`${prefixMenuId ?? 'FileMenu'}-${doc._id}}`) && (
<ActionsMenu
open
docs={isItemSelected(doc._id) ? selectedItems : [doc]}
actions={actions}
actions={contextMenuActions}
anchorReference="anchorPosition"
anchorPosition={{ top: position.mouseY, left: position.mouseX }}
autoClose
Expand Down
11 changes: 5 additions & 6 deletions src/hooks/useMoreMenuActions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export const useMoreMenuActions = file => {
const showPrintAction = isPDFDoc && isPrintAvailable
const isCozySharing = window.location.pathname === '/preview'
const isSharedDrive = window.location.href.includes('/shareddrive/')
const isDisplayInSharedDrive = !(isSharedDrive && !canWriteToCurrentFolder)

const actions = makeActions(
[
Expand All @@ -61,10 +60,10 @@ export const useMoreMenuActions = file => {
download,
showPrintAction && print,
hr,
moveTo,
isDisplayInSharedDrive && duplicateTo,
isDisplayInSharedDrive && addToFavorites,
isDisplayInSharedDrive && removeFromFavorites,
!isSharedDrive && moveTo, // TO DO: Remove condtion when moving is available in shared drive
!isSharedDrive && duplicateTo, // TO DO: Remove condtion when duplicating is available in shared drive
addToFavorites,
removeFromFavorites,
hr,
versions,
hr,
Expand All @@ -80,7 +79,7 @@ export const useMoreMenuActions = file => {
refresh: () => navigate('..'),
navigate,
hasWriteAccess: canWriteToCurrentFolder,
canMove: isDisplayInSharedDrive,
canMove: canWriteToCurrentFolder,
isPublic: false,
allLoaded,
showAlert,
Expand Down
4 changes: 3 additions & 1 deletion src/modules/actions/components/addToFavorites.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ const addToFavorites = ({
label,
icon,
displayCondition: docs =>
docs.length > 0 && docs.every(doc => !doc.cozyMetadata?.favorite),
docs.length > 0 &&
docs.every(doc => !doc.cozyMetadata?.favorite) &&
!docs[0]?.driveId,
action: async (files): Promise<void> => {
try {
for (const file of files) {
Expand Down
4 changes: 3 additions & 1 deletion src/modules/actions/components/removeFromFavorites.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ const removeFromFavorites = ({
label,
icon,
displayCondition: docs =>
docs.length > 0 && docs.every(doc => doc.cozyMetadata?.favorite),
docs.length > 0 &&
docs.every(doc => doc.cozyMetadata?.favorite) &&
!docs[0]?.driveId,
action: async (files): Promise<void> => {
try {
for (const file of files) {
Expand Down
11 changes: 11 additions & 0 deletions src/modules/actions/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,14 @@ export const navigateToModalWithMultipleFile = ({
}
)
}

/**
* Returns the context menu visible actions
*
* @param {Object[]} actions - the list of actions
* @returns {Object[]} - the list of actions to be displayed
*/
export const getContextMenuActions = (actions = []) =>
actions.filter(
action => Object.values(action)[0]?.displayInContextMenu !== false
)
173 changes: 173 additions & 0 deletions src/modules/actions/helpers.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import {
navigateToModal,
navigateToModalWithMultipleFile,
getContextMenuActions
} from './helpers'

jest.mock('@/lib/path', () => ({
joinPath: jest.fn((...paths) => paths.join('/'))
}))

describe('actions helpers', () => {
describe('navigateToModal', () => {
let mockNavigate

beforeEach(() => {
mockNavigate = jest.fn()
})

afterEach(() => {
jest.clearAllMocks()
})

it('should navigate to modal with pathname and single file', () => {
const params = {
navigate: mockNavigate,
pathname: '/folder/123',
files: { id: 'file-123', name: 'test.pdf' },
path: 'preview'
}

navigateToModal(params)

expect(mockNavigate).toHaveBeenCalledWith(
'/folder/123/file/file-123/preview'
)
})

it('should navigate to modal with pathname and array of files', () => {
const params = {
navigate: mockNavigate,
pathname: '/folder/456',
files: [
{ id: 'file-1', name: 'first.pdf' },
{ id: 'file-2', name: 'second.pdf' }
],
path: 'edit'
}

navigateToModal(params)

expect(mockNavigate).toHaveBeenCalledWith('/folder/456/file/file-1/edit')
})
})

describe('navigateToModalWithMultipleFile', () => {
let mockNavigate

beforeEach(() => {
mockNavigate = jest.fn()
})

afterEach(() => {
jest.clearAllMocks()
})

it('should navigate with pathname, multiple files, and search params', () => {
const params = {
navigate: mockNavigate,
pathname: '/folder/123',
files: [
{ id: 'file-1', name: 'doc1.pdf' },
{ id: 'file-2', name: 'doc2.pdf' },
{ id: 'file-3', name: 'doc3.pdf' }
],
path: 'share',
search: 'tab=link'
}

navigateToModalWithMultipleFile(params)

expect(mockNavigate).toHaveBeenCalledWith(
{
pathname: '/folder/123/share',
search: '?tab=link'
},
{
state: { fileIds: ['file-1', 'file-2', 'file-3'] }
}
)
})

it('should navigate with pathname and multiple files without search params', () => {
const params = {
navigate: mockNavigate,
pathname: '/recent',
files: [
{ id: 'file-a', name: 'image1.jpg' },
{ id: 'file-b', name: 'image2.jpg' }
],
path: 'move'
}

navigateToModalWithMultipleFile(params)

expect(mockNavigate).toHaveBeenCalledWith(
{
pathname: '/recent/move',
search: ''
},
{
state: { fileIds: ['file-a', 'file-b'] }
}
)
})

it('should handle empty search parameter', () => {
const params = {
navigate: mockNavigate,
pathname: '/folder/456',
files: [
{ id: 'file-1', name: 'test1.pdf' },
{ id: 'file-2', name: 'test2.pdf' }
],
path: 'delete',
search: ''
}

navigateToModalWithMultipleFile(params)

expect(mockNavigate).toHaveBeenCalledWith(
{
pathname: '/folder/456/delete',
search: ''
},
{
state: { fileIds: ['file-1', 'file-2'] }
}
)
})
})

describe('getContextMenuActions', () => {
it('should return all actions when all have displayInContextMenu !== false', () => {
const actions = [
{ download: { displayInContextMenu: true, name: 'Download' } },
{ share: { name: 'Share' } }, // undefined displayInContextMenu should be included
{ rename: { displayInContextMenu: undefined, name: 'Rename' } }
]

const result = getContextMenuActions(actions)

expect(result).toEqual(actions)
expect(result).toHaveLength(3)
})

it('should filter out actions with displayInContextMenu: false', () => {
const actions = [
{ download: { displayInContextMenu: true, name: 'Download' } },
{ share: { displayInContextMenu: false, name: 'Share' } },
{ rename: { name: 'Rename' } },
{ delete: { displayInContextMenu: false, name: 'Delete' } }
]

const result = getContextMenuActions(actions)

expect(result).toEqual([
{ download: { displayInContextMenu: true, name: 'Download' } },
{ rename: { name: 'Rename' } }
])
expect(result).toHaveLength(2)
})
})
})
2 changes: 2 additions & 0 deletions src/modules/actions/selectAll.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const selectAllItems = ({ t, selectAll, isSelectAll, isMobile }) => {
name: 'selectAllItems',
label,
icon,
displayInSelectionBar: true,
displayInContextMenu: false,
displayCondition: files => files.length > 0,
action: () => selectAll(),
Component: makeComponent(label, icon)
Expand Down
3 changes: 2 additions & 1 deletion src/modules/actions/share.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const share = ({ t, hasWriteAccess, navigate, pathname, allLoaded }) => {
hasWriteAccess &&
files?.length === 1 &&
!isEncryptedFileOrFolder(files[0]) &&
!isSharedDriveFolder(files[0])
!isSharedDriveFolder(files[0]) &&
!files[0]?.driveId
)
},
action: files =>
Expand Down
Loading
Loading