Skip to content

Fix Context Menu Behavior in Dropdown Component #2358

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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
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
11,260 changes: 4,427 additions & 6,833 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@
"@types/jest": "^29.5.14",
"@types/node": "^14.18.36",
"@types/path-browserify": "^1.0.0",
"@types/prop-types": "^15.7.14",
"@types/react-copy-to-clipboard": "^5.0.7",
"@typescript-eslint/parser": "^5.62.0",
"aegir": "^42.2.2",
"autoprefixer": "^10.4.7",
Expand Down
10 changes: 5 additions & 5 deletions src/files/FilesPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import ReactJoyride from 'react-joyride'
// Lib
import { filesTour } from '../lib/tours.js'
// Components
import ContextMenu from './context-menu/ContextMenu.js'
import ContextMenu from './context-menu'
import withTour from '../components/tour/withTour.js'
import InfoBoxes from './info-boxes/InfoBoxes.js'
import FilePreview from './file-preview/FilePreview.js'
import FilesList from './files-list/FilesList.js'
import FilesGrid from './files-grid/files-grid.js'
import FilesGrid from './files-grid/files-grid.tsx'
import { ViewList, ViewModule } from '../icons/stroke-icons.js'
import { getJoyrideLocales } from '../helpers/i8n.js'

Expand Down Expand Up @@ -152,12 +152,12 @@ const FilesPage = ({
}
}

setContextMenu({
isOpen: !contextMenu.isOpen,
setContextMenu((prev) => ({
isOpen: !prev.isOpen,
translateX,
translateY,
file
})
}))
}

const MainView = ({ t, files, remotePins, pendingPins, failedPins, doExploreUserProvidedPath }) => {
Expand Down
1 change: 1 addition & 0 deletions src/files/breadcrumbs/Breadcrumbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const DropableBreadcrumb = ({ index, link, immutable, onAddFiles, onMove, onClic

const handleOnContextMenuHandle = async (ev) => {
ev.preventDefault()
ev.stopPropagation()

const { path } = link
const sanitizedPath = path.substring(path.indexOf('/', 1), path.length)
Expand Down
161 changes: 0 additions & 161 deletions src/files/context-menu/ContextMenu.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/files/context-menu/ContextMenu.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @ts-check
import ContextMenu from './ContextMenu.js'
import ContextMenu from './index.tsx'

/**
* @type {import('@storybook/react').Meta}
Expand Down
158 changes: 158 additions & 0 deletions src/files/context-menu/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React, { useRef, useEffect, forwardRef } from 'react'
import { WithTranslation, withTranslation } from 'react-i18next'
import { Dropdown, DropdownMenu, Option } from '../dropdown/dropdown.tsx'
import StrokeCopy from '../../icons/StrokeCopy.js'
import StrokeShare from '../../icons/StrokeShare.js'
import StrokeSpeaker from '../../icons/StrokeSpeaker.js'
import StrokePencil from '../../icons/StrokePencil.js'
import StrokeIpld from '../../icons/StrokeIpld.js'
import StrokeTrash from '../../icons/StrokeTrash.js'
import StrokeDownload from '../../icons/StrokeDownload.js'
import StrokeData from '../../icons/StrokeData.js'
import StrokePin from '../../icons/StrokePin.js'
import { cliCmdKeys } from '../../bundles/files/consts.js'
import { CopyToClipboard } from 'react-copy-to-clipboard'

interface ContextMenuProps extends WithTranslation {
isMfs: boolean
isOpen: boolean
isUnknown: boolean
hash?: string
cid?: string
pinned?: boolean
handleClick: () => void
translateX: number
translateY: number
left: number
onRemove?: () => void
onRename?: () => void
onDownload?: () => void
onDownloadCar?: () => void
onInspect?: () => void
onShare?: () => void
onPublish?: () => void
onPinning?: () => void
doSetCliOptions?: (options?: unknown) => void
className?: string
autofocus?: boolean
isCliTutorModeEnabled?: boolean
}

const ContextMenu = forwardRef<HTMLDivElement, ContextMenuProps>((props, ref) => {
const dropdownMenuRef = useRef<HTMLDivElement>(null)

// Handle autofocus
useEffect(() => {
if (props.autofocus && props.isOpen && dropdownMenuRef.current) {
const firstButton = dropdownMenuRef.current.querySelector('button')
if (firstButton) {
firstButton.focus()
}
}
}, [props.autofocus, props.isOpen])

const wrap = (name: string, cliOptions?: unknown) => () => {
if (name === 'onCliTutorMode' && cliOptions && props.doSetCliOptions) {
props.doSetCliOptions(cliOptions)
}
const handler = props[name as keyof ContextMenuProps]
if (typeof handler === 'function') {
handler()
}
props.handleClick()
}

const {
t, onRename, onRemove, onDownload, onInspect, onShare, onDownloadCar, onPublish,
translateX, translateY, className, isMfs, isUnknown, isCliTutorModeEnabled
} = props

return (
<div ref={ref} className='relative'>
<Dropdown className={className}>
<DropdownMenu
ref={dropdownMenuRef}
top={-8}
arrowMarginRight='11px'
left='calc(100% - 200px)'
translateX={-translateX}
translateY={-translateY}
open={props.isOpen}
onDismiss={props.handleClick}>
{onShare && (
<Option onClick={wrap('onShare')}>
<StrokeShare className='w2 mr2 fill-aqua' />
{t('actions.share')}
</Option>
)}
<CopyToClipboard text={String(props.cid)} onCopy={props.handleClick}>
<Option>
<StrokeCopy className='w2 mr2 fill-aqua' />
{t('actions.copyHash')}
</Option>
</CopyToClipboard>
{onInspect && (
<Option onClick={wrap('onInspect')}>
<StrokeIpld className='w2 mr2 fill-aqua' />
{t('app:actions.inspect')}
</Option>
)}
<Option
onClick={wrap('onPinning')}
isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={wrap('onCliTutorMode', cliCmdKeys.PIN_OBJECT)}>
<StrokePin className='w2 mr2 fill-aqua' />
{t('app:actions.setPinning')}
</Option>
{!isUnknown && onDownload && (
<Option
onClick={wrap('onDownload')}
isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={wrap('onCliTutorMode', cliCmdKeys.DOWNLOAD_OBJECT_COMMAND)}>
<StrokeDownload className='w2 mr2 fill-aqua' />
{t('app:actions.download')}
</Option>
)}
{!isUnknown && onDownloadCar && (
<Option
onClick={wrap('onDownloadCar')}
isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={wrap('onCliTutorMode', cliCmdKeys.DOWNLOAD_CAR_COMMAND)}>
<StrokeData className='w2 mr2 fill-aqua' />
{t('app:actions.downloadCar')}
</Option>
)}
{!isUnknown && isMfs && onRename && (
<Option
onClick={wrap('onRename')}
isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={wrap('onCliTutorMode', cliCmdKeys.RENAME_IPFS_OBJECT)}>
<StrokePencil className='w2 mr2 fill-aqua' />
{t('app:actions.rename')}
</Option>
)}
{!isUnknown && isMfs && onRemove && (
<Option
onClick={wrap('onRemove')}
isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={wrap('onCliTutorMode', cliCmdKeys.REMOVE_FILE_FROM_IPFS)}>
<StrokeTrash className='w2 mr2 fill-aqua' />
{t('app:actions.remove')}
</Option>
)}
{onPublish && (
<Option
onClick={wrap('onPublish')}
isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={wrap('onCliTutorMode', cliCmdKeys.PUBLISH_WITH_IPNS)}>
<StrokeSpeaker className='w2 mr2 fill-aqua' />
{t('actions.publishWithIpns')}
</Option>
)}
</DropdownMenu>
</Dropdown>
</div>
)
})

export default withTranslation('files', { withRef: true })(ContextMenu)
Loading