feat: Added "Add tag" option to file context menu

This commit is contained in:
Aman Harwara
2023-01-05 01:16:35 +05:30
parent 30dda73e90
commit ba4b6a580b
11 changed files with 160 additions and 42 deletions

View File

@@ -216,6 +216,8 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
<FileContextMenuWrapper <FileContextMenuWrapper
filesController={viewControllerManager.filesController} filesController={viewControllerManager.filesController}
selectionController={viewControllerManager.selectionController} selectionController={viewControllerManager.selectionController}
navigationController={viewControllerManager.navigationController}
linkingController={viewControllerManager.linkingController}
/> />
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} /> <PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
<ConfirmSignoutContainer <ConfirmSignoutContainer

View File

@@ -358,6 +358,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
filesController={filesController} filesController={filesController}
featuresController={featuresController} featuresController={featuresController}
linkingController={linkingController} linkingController={linkingController}
navigationController={navigationController}
/> />
) : ( ) : (
<ContentList <ContentList

View File

@@ -1,4 +1,6 @@
import { FilesController } from '@/Controllers/FilesController' import { FilesController } from '@/Controllers/FilesController'
import { LinkingController } from '@/Controllers/LinkingController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController' import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react' import { FunctionComponent } from 'react'
@@ -9,36 +11,47 @@ import FileMenuOptions from './FileMenuOptions'
type Props = { type Props = {
filesController: FilesController filesController: FilesController
selectionController: SelectedItemsController selectionController: SelectedItemsController
linkingController: LinkingController
navigationController: NavigationController
} }
const FileContextMenu: FunctionComponent<Props> = observer(({ filesController, selectionController }) => { const FileContextMenu: FunctionComponent<Props> = observer(
const { showFileContextMenu, setShowFileContextMenu, fileContextMenuLocation } = filesController ({ filesController, selectionController, linkingController, navigationController }) => {
const { selectedFiles } = selectionController const { showFileContextMenu, setShowFileContextMenu, fileContextMenuLocation } = filesController
const { selectedFiles } = selectionController
return ( return (
<Popover <Popover
open={showFileContextMenu} open={showFileContextMenu}
anchorPoint={fileContextMenuLocation} anchorPoint={fileContextMenuLocation}
togglePopover={() => setShowFileContextMenu(!showFileContextMenu)} togglePopover={() => setShowFileContextMenu(!showFileContextMenu)}
align="start" align="start"
className="py-2" className="py-2"
> >
<Menu a11yLabel="File context menu" isOpen={showFileContextMenu}> <Menu a11yLabel="File context menu" isOpen={showFileContextMenu}>
<FileMenuOptions <FileMenuOptions
filesController={filesController} filesController={filesController}
selectedFiles={selectedFiles} linkingController={linkingController}
closeMenu={() => setShowFileContextMenu(false)} navigationController={navigationController}
shouldShowRenameOption={false} selectedFiles={selectedFiles}
shouldShowAttachOption={false} closeMenu={() => setShowFileContextMenu(false)}
/> shouldShowRenameOption={false}
</Menu> shouldShowAttachOption={false}
</Popover> />
) </Menu>
}) </Popover>
)
},
)
FileContextMenu.displayName = 'FileContextMenu' FileContextMenu.displayName = 'FileContextMenu'
const FileContextMenuWrapper: FunctionComponent<Props> = ({ filesController, selectionController }) => { const FileContextMenuWrapper: FunctionComponent<Props> = ({
filesController,
linkingController,
navigationController,
selectionController,
}) => {
const { showFileContextMenu } = filesController const { showFileContextMenu } = filesController
const { selectedFiles } = selectionController const { selectedFiles } = selectionController
@@ -48,7 +61,14 @@ const FileContextMenuWrapper: FunctionComponent<Props> = ({ filesController, sel
return null return null
} }
return <FileContextMenu filesController={filesController} selectionController={selectionController} /> return (
<FileContextMenu
filesController={filesController}
linkingController={linkingController}
navigationController={navigationController}
selectionController={selectionController}
/>
)
} }
export default observer(FileContextMenuWrapper) export default observer(FileContextMenuWrapper)

View File

@@ -11,10 +11,16 @@ import MenuItem from '../Menu/MenuItem'
import { FileContextMenuBackupOption } from './FileContextMenuBackupOption' import { FileContextMenuBackupOption } from './FileContextMenuBackupOption'
import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem' import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
import { FileItem } from '@standardnotes/snjs' import { FileItem } from '@standardnotes/snjs'
import AddTagOption from '../NotesOptions/AddTagOption'
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
import { LinkingController } from '@/Controllers/LinkingController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
type Props = { type Props = {
closeMenu: () => void closeMenu: () => void
filesController: FilesController filesController: FilesController
linkingController: LinkingController
navigationController: NavigationController
isFileAttachedToNote?: boolean isFileAttachedToNote?: boolean
renameToggleCallback?: (isRenamingFile: boolean) => void renameToggleCallback?: (isRenamingFile: boolean) => void
shouldShowRenameOption: boolean shouldShowRenameOption: boolean
@@ -25,6 +31,8 @@ type Props = {
const FileMenuOptions: FunctionComponent<Props> = ({ const FileMenuOptions: FunctionComponent<Props> = ({
closeMenu, closeMenu,
filesController, filesController,
linkingController,
navigationController,
isFileAttachedToNote, isFileAttachedToNote,
renameToggleCallback, renameToggleCallback,
shouldShowRenameOption, shouldShowRenameOption,
@@ -82,6 +90,12 @@ const FileMenuOptions: FunctionComponent<Props> = ({
) : null} ) : null}
</> </>
)} )}
<AddTagOption
navigationController={navigationController}
linkingController={linkingController}
selectedItems={selectedFiles}
iconClassName={`text-neutral mr-2 ${MenuItemIconSize}`}
/>
<MenuSwitchButtonItem <MenuSwitchButtonItem
checked={hasProtectedFiles} checked={hasProtectedFiles}
onChange={(hasProtectedFiles) => { onChange={(hasProtectedFiles) => {

View File

@@ -6,13 +6,22 @@ import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import Popover from '../Popover/Popover' import Popover from '../Popover/Popover'
import RoundIconButton from '../Button/RoundIconButton' import RoundIconButton from '../Button/RoundIconButton'
import Menu from '../Menu/Menu' import Menu from '../Menu/Menu'
import { LinkingController } from '@/Controllers/LinkingController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
type Props = { type Props = {
filesController: FilesController filesController: FilesController
selectionController: SelectedItemsController selectionController: SelectedItemsController
linkingController: LinkingController
navigationController: NavigationController
} }
const FilesOptionsPanel = ({ filesController, selectionController }: Props) => { const FilesOptionsPanel = ({
filesController,
linkingController,
navigationController,
selectionController,
}: Props) => {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null) const buttonRef = useRef<HTMLButtonElement>(null)
@@ -25,6 +34,8 @@ const FilesOptionsPanel = ({ filesController, selectionController }: Props) => {
<Menu a11yLabel="File options panel" isOpen={isOpen}> <Menu a11yLabel="File options panel" isOpen={isOpen}>
<FileMenuOptions <FileMenuOptions
filesController={filesController} filesController={filesController}
linkingController={linkingController}
navigationController={navigationController}
selectedFiles={selectionController.selectedFiles} selectedFiles={selectionController.selectedFiles}
closeMenu={() => { closeMenu={() => {
setIsOpen(false) setIsOpen(false)

View File

@@ -112,6 +112,8 @@ const FileViewWithoutProtection = ({ application, viewControllerManager, file }:
<FileOptionsPanel <FileOptionsPanel
filesController={viewControllerManager.filesController} filesController={viewControllerManager.filesController}
selectionController={viewControllerManager.selectionController} selectionController={viewControllerManager.selectionController}
linkingController={viewControllerManager.linkingController}
navigationController={viewControllerManager.navigationController}
/> />
</div> </div>
</div> </div>

View File

@@ -31,8 +31,19 @@ import { LinkingController } from '@/Controllers/LinkingController'
import { FeaturesController } from '@/Controllers/FeaturesController' import { FeaturesController } from '@/Controllers/FeaturesController'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery' import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
import { useApplication } from '../ApplicationProvider' import { useApplication } from '../ApplicationProvider'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
const ContextMenuCell = ({ files, filesController }: { files: FileItem[]; filesController: FilesController }) => { const ContextMenuCell = ({
files,
filesController,
navigationController,
linkingController,
}: {
files: FileItem[]
filesController: FilesController
navigationController: NavigationController
linkingController: LinkingController
}) => {
const [contextMenuVisible, setContextMenuVisible] = useState(false) const [contextMenuVisible, setContextMenuVisible] = useState(false)
const anchorElementRef = useRef<HTMLButtonElement>(null) const anchorElementRef = useRef<HTMLButtonElement>(null)
@@ -61,6 +72,8 @@ const ContextMenuCell = ({ files, filesController }: { files: FileItem[]; filesC
> >
<Menu a11yLabel="File context menu" isOpen={contextMenuVisible}> <Menu a11yLabel="File context menu" isOpen={contextMenuVisible}>
<FileMenuOptions <FileMenuOptions
linkingController={linkingController}
navigationController={navigationController}
closeMenu={() => { closeMenu={() => {
setContextMenuVisible(false) setContextMenuVisible(false)
}} }}
@@ -160,9 +173,16 @@ type Props = {
filesController: FilesController filesController: FilesController
featuresController: FeaturesController featuresController: FeaturesController
linkingController: LinkingController linkingController: LinkingController
navigationController: NavigationController
} }
const FilesTableView = ({ application, filesController, featuresController, linkingController }: Props) => { const FilesTableView = ({
application,
filesController,
featuresController,
linkingController,
navigationController,
}: Props) => {
const files = application.items const files = application.items
.getDisplayableNotesAndFiles() .getDisplayableNotesAndFiles()
.filter((item) => item.content_type === ContentType.File) as FileItem[] .filter((item) => item.content_type === ContentType.File) as FileItem[]
@@ -312,12 +332,22 @@ const FilesTableView = ({ application, filesController, featuresController, link
featuresController={featuresController} featuresController={featuresController}
linkingController={linkingController} linkingController={linkingController}
/> />
<ContextMenuCell files={[file]} filesController={filesController} /> <ContextMenuCell
files={[file]}
filesController={filesController}
linkingController={linkingController}
navigationController={navigationController}
/>
</div> </div>
) )
}, },
selectionActions: (fileIds) => ( selectionActions: (fileIds) => (
<ContextMenuCell files={files.filter((file) => fileIds.includes(file.uuid))} filesController={filesController} /> <ContextMenuCell
files={files.filter((file) => fileIds.includes(file.uuid))}
filesController={filesController}
linkingController={linkingController}
navigationController={navigationController}
/>
), ),
showSelectionActions: true, showSelectionActions: true,
}) })
@@ -347,6 +377,8 @@ const FilesTableView = ({ application, filesController, featuresController, link
shouldShowRenameOption={false} shouldShowRenameOption={false}
shouldShowAttachOption={false} shouldShowAttachOption={false}
selectedFiles={[contextMenuFile]} selectedFiles={[contextMenuFile]}
linkingController={linkingController}
navigationController={navigationController}
/> />
</Menu> </Menu>
</Popover> </Popover>

View File

@@ -5,13 +5,22 @@ import { useCallback } from 'react'
import FileOptionsPanel from '../FileContextMenu/FileOptionsPanel' import FileOptionsPanel from '../FileContextMenu/FileOptionsPanel'
import { FilesController } from '@/Controllers/FilesController' import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController' import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { LinkingController } from '@/Controllers/LinkingController'
type Props = { type Props = {
filesController: FilesController filesController: FilesController
selectionController: SelectedItemsController selectionController: SelectedItemsController
linkingController: LinkingController
navigationController: NavigationController
} }
const MultipleSelectedFiles = ({ filesController, selectionController }: Props) => { const MultipleSelectedFiles = ({
filesController,
selectionController,
linkingController,
navigationController,
}: Props) => {
const count = selectionController.selectedFilesCount const count = selectionController.selectedFilesCount
const cancelMultipleSelection = useCallback(() => { const cancelMultipleSelection = useCallback(() => {
@@ -23,7 +32,12 @@ const MultipleSelectedFiles = ({ filesController, selectionController }: Props)
<div className="flex w-full items-center justify-between p-4"> <div className="flex w-full items-center justify-between p-4">
<h1 className="m-0 text-lg font-bold">{count} selected files</h1> <h1 className="m-0 text-lg font-bold">{count} selected files</h1>
<div> <div>
<FileOptionsPanel filesController={filesController} selectionController={selectionController} /> <FileOptionsPanel
filesController={filesController}
selectionController={selectionController}
linkingController={linkingController}
navigationController={navigationController}
/>
</div> </div>
</div> </div>
<div className="flex min-h-full w-full max-w-md flex-grow flex-col items-center justify-center"> <div className="flex min-h-full w-full max-w-md flex-grow flex-col items-center justify-center">

View File

@@ -118,6 +118,8 @@ class NoteGroupView extends AbstractComponent<Props, State> {
<MultipleSelectedFiles <MultipleSelectedFiles
filesController={this.viewControllerManager.filesController} filesController={this.viewControllerManager.filesController}
selectionController={this.viewControllerManager.selectionController} selectionController={this.viewControllerManager.selectionController}
navigationController={this.viewControllerManager.navigationController}
linkingController={this.viewControllerManager.linkingController}
/> />
)} )}
{shouldNotShowMultipleSelectedItems && hasControllers && ( {shouldNotShowMultipleSelectedItems && hasControllers && (

View File

@@ -2,22 +2,28 @@ import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useRef, useState } from 'react' import { FunctionComponent, useCallback, useRef, useState } from 'react'
import Icon from '@/Components/Icon/Icon' import Icon from '@/Components/Icon/Icon'
import { NavigationController } from '@/Controllers/Navigation/NavigationController' import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NotesController } from '@/Controllers/NotesController/NotesController'
import { KeyboardKey } from '@standardnotes/ui-services' import { KeyboardKey } from '@standardnotes/ui-services'
import Popover from '../Popover/Popover' import Popover from '../Popover/Popover'
import { IconType } from '@standardnotes/snjs' import { classNames, DecryptedItemInterface, IconType, SNTag } from '@standardnotes/snjs'
import { getTitleForLinkedTag } from '@/Utils/Items/Display/getTitleForLinkedTag' import { getTitleForLinkedTag } from '@/Utils/Items/Display/getTitleForLinkedTag'
import { useApplication } from '../ApplicationProvider' import { useApplication } from '../ApplicationProvider'
import MenuItem from '../Menu/MenuItem' import MenuItem from '../Menu/MenuItem'
import Menu from '../Menu/Menu' import Menu from '../Menu/Menu'
import { LinkingController } from '@/Controllers/LinkingController'
type Props = { type Props = {
navigationController: NavigationController navigationController: NavigationController
notesController: NotesController linkingController: LinkingController
selectedItems: DecryptedItemInterface[]
iconClassName: string iconClassName: string
} }
const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesController, iconClassName }) => { const AddTagOption: FunctionComponent<Props> = ({
navigationController,
linkingController,
selectedItems,
iconClassName,
}) => {
const application = useApplication() const application = useApplication()
const menuContainerRef = useRef<HTMLDivElement>(null) const menuContainerRef = useRef<HTMLDivElement>(null)
const buttonRef = useRef<HTMLButtonElement>(null) const buttonRef = useRef<HTMLButtonElement>(null)
@@ -28,6 +34,18 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
setIsOpen((isOpen) => !isOpen) setIsOpen((isOpen) => !isOpen)
}, []) }, [])
const isTagLinkedToSelectedItems = (tag: SNTag) => {
return selectedItems.every((item) => application.getItemTags(item).find((itemTag) => itemTag.uuid === tag.uuid))
}
const linkTagToSelectedItems = (tag: SNTag) => {
selectedItems.forEach((item) => linkingController.linkItems(item, tag))
}
const unlinkTagFromSelectedItems = (tag: SNTag) => {
selectedItems.forEach((item) => linkingController.unlinkItems(item, tag))
}
return ( return (
<div ref={menuContainerRef}> <div ref={menuContainerRef}>
<MenuItem <MenuItem
@@ -59,9 +77,7 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
<MenuItem <MenuItem
key={tag.uuid} key={tag.uuid}
onClick={() => { onClick={() => {
notesController.isTagInSelectedNotes(tag) isTagLinkedToSelectedItems(tag) ? unlinkTagFromSelectedItems(tag) : linkTagToSelectedItems(tag)
? notesController.removeTagFromSelectedNotes(tag).catch(console.error)
: notesController.addTagToSelectedNotes(tag).catch(console.error)
}} }}
> >
{tag.iconString && ( {tag.iconString && (
@@ -72,8 +88,10 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
/> />
)} )}
<span <span
className={`overflow-hidden overflow-ellipsis whitespace-nowrap className={classNames(
${notesController.isTagInSelectedNotes(tag) ? 'font-bold' : ''}`} 'overflow-hidden overflow-ellipsis whitespace-nowrap',
isTagLinkedToSelectedItems(tag) ? 'font-bold' : '',
)}
> >
{getTitleForLinkedTag(tag, application)?.longTitle} {getTitleForLinkedTag(tag, application)?.longTitle}
</span> </span>

View File

@@ -42,6 +42,7 @@ const NotesOptions = ({
application, application,
navigationController, navigationController,
notesController, notesController,
linkingController,
historyModalController, historyModalController,
closeMenu, closeMenu,
}: NotesOptionsProps) => { }: NotesOptionsProps) => {
@@ -214,7 +215,8 @@ const NotesOptions = ({
<AddTagOption <AddTagOption
iconClassName={iconClass} iconClassName={iconClass}
navigationController={navigationController} navigationController={navigationController}
notesController={notesController} selectedItems={notes}
linkingController={linkingController}
/> />
)} )}
<MenuItem <MenuItem