fix: close menu & toggle notes list when note action occurs (#1601)

This commit is contained in:
Aman Harwara
2022-09-20 19:33:14 +05:30
committed by GitHub
parent 023d1665b6
commit 91d9364e95
7 changed files with 101 additions and 66 deletions

View File

@@ -175,14 +175,14 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
return ( return (
<PremiumModalProvider application={application} viewControllerManager={viewControllerManager}> <PremiumModalProvider application={application} viewControllerManager={viewControllerManager}>
<div className={platformString + ' main-ui-view sn-component'}> <ResponsivePaneProvider>
<div id="app" className={appClass + ' app app-column-container'}> <div className={platformString + ' main-ui-view sn-component'}>
<FileDragNDropProvider <div id="app" className={appClass + ' app app-column-container'}>
application={application} <FileDragNDropProvider
featuresController={viewControllerManager.featuresController} application={application}
filesController={viewControllerManager.filesController} featuresController={viewControllerManager.featuresController}
> filesController={viewControllerManager.filesController}
<ResponsivePaneProvider> >
<Navigation application={application} /> <Navigation application={application} />
<ContentListView <ContentListView
application={application} application={application}
@@ -197,52 +197,52 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
searchOptionsController={viewControllerManager.searchOptionsController} searchOptionsController={viewControllerManager.searchOptionsController}
/> />
<NoteGroupView application={application} /> <NoteGroupView application={application} />
</ResponsivePaneProvider> </FileDragNDropProvider>
</FileDragNDropProvider> </div>
<>
<Footer application={application} applicationGroup={mainApplicationGroup} />
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
<RevisionHistoryModal
application={application}
historyModalController={viewControllerManager.historyModalController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
subscriptionController={viewControllerManager.subscriptionController}
/>
</>
{renderChallenges()}
<>
<NotesContextMenu
application={application}
navigationController={viewControllerManager.navigationController}
notesController={viewControllerManager.notesController}
noteTagsController={viewControllerManager.noteTagsController}
historyModalController={viewControllerManager.historyModalController}
/>
<TagContextMenuWrapper
navigationController={viewControllerManager.navigationController}
featuresController={viewControllerManager.featuresController}
/>
<FileContextMenuWrapper
filesController={viewControllerManager.filesController}
selectionController={viewControllerManager.selectionController}
/>
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
<ConfirmSignoutContainer
applicationGroup={mainApplicationGroup}
viewControllerManager={viewControllerManager}
application={application}
/>
<ToastContainer />
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
<PermissionsModalWrapper application={application} />
</>
</div> </div>
</ResponsivePaneProvider>
<>
<Footer application={application} applicationGroup={mainApplicationGroup} />
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
<RevisionHistoryModal
application={application}
historyModalController={viewControllerManager.historyModalController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
subscriptionController={viewControllerManager.subscriptionController}
/>
</>
{renderChallenges()}
<>
<NotesContextMenu
application={application}
navigationController={viewControllerManager.navigationController}
notesController={viewControllerManager.notesController}
noteTagsController={viewControllerManager.noteTagsController}
historyModalController={viewControllerManager.historyModalController}
/>
<TagContextMenuWrapper
navigationController={viewControllerManager.navigationController}
featuresController={viewControllerManager.featuresController}
/>
<FileContextMenuWrapper
filesController={viewControllerManager.filesController}
selectionController={viewControllerManager.selectionController}
/>
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
<ConfirmSignoutContainer
applicationGroup={mainApplicationGroup}
viewControllerManager={viewControllerManager}
application={application}
/>
<ToastContainer />
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
<PermissionsModalWrapper application={application} />
</>
</div>
</PremiumModalProvider> </PremiumModalProvider>
) )
} }

View File

@@ -8,6 +8,8 @@ import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController' import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import HorizontalSeparator from '../Shared/HorizontalSeparator' import HorizontalSeparator from '../Shared/HorizontalSeparator'
import { formatSizeToReadableString } from '@standardnotes/filepicker' import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
type Props = { type Props = {
closeMenu: () => void closeMenu: () => void
@@ -30,6 +32,7 @@ const FileMenuOptions: FunctionComponent<Props> = ({
}) => { }) => {
const { selectedFiles } = selectionController const { selectedFiles } = selectionController
const { handleFileAction } = filesController const { handleFileAction } = filesController
const { toggleAppPane } = useResponsiveAppPane()
const hasProtectedFiles = useMemo(() => selectedFiles.some((file) => file.protected), [selectedFiles]) const hasProtectedFiles = useMemo(() => selectedFiles.some((file) => file.protected), [selectedFiles])
const hasSelectedMultipleFiles = useMemo(() => selectedFiles.length > 1, [selectedFiles.length]) const hasSelectedMultipleFiles = useMemo(() => selectedFiles.length > 1, [selectedFiles.length])
@@ -68,6 +71,11 @@ const FileMenuOptions: FunctionComponent<Props> = ({
closeMenu() closeMenu()
}, [closeMenu, handleFileAction, selectedFiles]) }, [closeMenu, handleFileAction, selectedFiles])
const closeMenuAndToggleFilesList = useCallback(() => {
toggleAppPane(AppPaneId.Items)
closeMenu()
}, [closeMenu, toggleAppPane])
return ( return (
<> <>
<button <button
@@ -139,6 +147,7 @@ const FileMenuOptions: FunctionComponent<Props> = ({
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none" className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
onClick={() => { onClick={() => {
closeMenuAndToggleFilesList()
void filesController.deleteFilesPermanently(selectionController.selectedFiles) void filesController.deleteFilesPermanently(selectionController.selectedFiles)
}} }}
> >

View File

@@ -23,10 +23,12 @@ const NotesContextMenu = ({
noteTagsController, noteTagsController,
historyModalController, historyModalController,
}: Props) => { }: Props) => {
const { contextMenuOpen, contextMenuClickLocation } = notesController const { contextMenuOpen, contextMenuClickLocation, setContextMenuOpen } = notesController
const contextMenuRef = useRef<HTMLDivElement>(null) const contextMenuRef = useRef<HTMLDivElement>(null)
const closeMenu = () => setContextMenuOpen(!contextMenuOpen)
return ( return (
<Popover <Popover
align="start" align="start"
@@ -37,7 +39,7 @@ const NotesContextMenu = ({
className="py-2" className="py-2"
open={contextMenuOpen} open={contextMenuOpen}
side="right" side="right"
togglePopover={() => notesController.setContextMenuOpen(!contextMenuOpen)} togglePopover={closeMenu}
> >
<div ref={contextMenuRef}> <div ref={contextMenuRef}>
<NotesOptions <NotesOptions
@@ -46,6 +48,7 @@ const NotesContextMenu = ({
notesController={notesController} notesController={notesController}
noteTagsController={noteTagsController} noteTagsController={noteTagsController}
historyModalController={historyModalController} historyModalController={historyModalController}
closeMenu={closeMenu}
/> />
</div> </div>
</Popover> </Popover>

View File

@@ -13,6 +13,8 @@ import { NotesOptionsProps } from './NotesOptionsProps'
import { NotesController } from '@/Controllers/NotesController' import { NotesController } from '@/Controllers/NotesController'
import HorizontalSeparator from '../Shared/HorizontalSeparator' import HorizontalSeparator from '../Shared/HorizontalSeparator'
import { formatDateForContextMenu } from '@/Utils/DateUtils' import { formatDateForContextMenu } from '@/Utils/DateUtils'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
type DeletePermanentlyButtonProps = { type DeletePermanentlyButtonProps = {
onClick: () => void onClick: () => void
@@ -175,8 +177,10 @@ const NotesOptions = ({
notesController, notesController,
noteTagsController, noteTagsController,
historyModalController, historyModalController,
closeMenu,
}: NotesOptionsProps) => { }: NotesOptionsProps) => {
const [altKeyDown, setAltKeyDown] = useState(false) const [altKeyDown, setAltKeyDown] = useState(false)
const { toggleAppPane } = useResponsiveAppPane()
const toggleOn = (condition: (note: SNNote) => boolean) => { const toggleOn = (condition: (note: SNNote) => boolean) => {
const notesMatchingAttribute = notes.filter(condition) const notesMatchingAttribute = notes.filter(condition)
@@ -252,11 +256,15 @@ const NotesOptions = ({
} }
}, [application, getNoteFileName, notes]) }, [application, getNoteFileName, notes])
const duplicateSelectedItems = useCallback(() => { const closeMenuAndToggleNotesList = useCallback(() => {
notes.forEach((note) => { toggleAppPane(AppPaneId.Items)
application.mutator.duplicateItem(note).catch(console.error) closeMenu()
}) }, [closeMenu, toggleAppPane])
}, [application, notes])
const duplicateSelectedItems = useCallback(async () => {
await Promise.all(notes.map((note) => application.mutator.duplicateItem(note).catch(console.error)))
closeMenuAndToggleNotesList()
}, [application.mutator, closeMenuAndToggleNotesList, notes])
const openRevisionHistoryModal = useCallback(() => { const openRevisionHistoryModal = useCallback(() => {
historyModalController.openModal(notesController.firstSelectedNote) historyModalController.openModal(notesController.firstSelectedNote)
@@ -365,8 +373,9 @@ const NotesOptions = ({
{unarchived && ( {unarchived && (
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none" className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
onClick={() => { onClick={async () => {
notesController.setArchiveSelectedNotes(true).catch(console.error) await notesController.setArchiveSelectedNotes(true).catch(console.error)
closeMenuAndToggleNotesList()
}} }}
> >
<Icon type="archive" className={iconClassWarning} /> <Icon type="archive" className={iconClassWarning} />
@@ -376,8 +385,9 @@ const NotesOptions = ({
{archived && ( {archived && (
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none" className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
onClick={() => { onClick={async () => {
notesController.setArchiveSelectedNotes(false).catch(console.error) await notesController.setArchiveSelectedNotes(false).catch(console.error)
closeMenuAndToggleNotesList()
}} }}
> >
<Icon type="unarchive" className={iconClassWarning} /> <Icon type="unarchive" className={iconClassWarning} />
@@ -389,6 +399,7 @@ const NotesOptions = ({
<DeletePermanentlyButton <DeletePermanentlyButton
onClick={async () => { onClick={async () => {
await notesController.deleteNotesPermanently() await notesController.deleteNotesPermanently()
closeMenuAndToggleNotesList()
}} }}
/> />
) : ( ) : (
@@ -396,6 +407,7 @@ const NotesOptions = ({
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none" className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
onClick={async () => { onClick={async () => {
await notesController.setTrashSelectedNotes(true) await notesController.setTrashSelectedNotes(true)
closeMenuAndToggleNotesList()
}} }}
> >
<Icon type="trash" className={iconClassDanger} /> <Icon type="trash" className={iconClassDanger} />
@@ -408,6 +420,7 @@ const NotesOptions = ({
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none" className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
onClick={async () => { onClick={async () => {
await notesController.setTrashSelectedNotes(false) await notesController.setTrashSelectedNotes(false)
closeMenuAndToggleNotesList()
}} }}
> >
<Icon type="restore" className={iconClassSuccess} /> <Icon type="restore" className={iconClassSuccess} />
@@ -416,12 +429,14 @@ const NotesOptions = ({
<DeletePermanentlyButton <DeletePermanentlyButton
onClick={async () => { onClick={async () => {
await notesController.deleteNotesPermanently() await notesController.deleteNotesPermanently()
closeMenuAndToggleNotesList()
}} }}
/> />
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none" className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
onClick={async () => { onClick={async () => {
await notesController.emptyTrash() await notesController.emptyTrash()
closeMenuAndToggleNotesList()
}} }}
> >
<div className="flex items-start"> <div className="flex items-start">

View File

@@ -55,6 +55,7 @@ const NotesOptionsPanel = ({
notesController={notesController} notesController={notesController}
noteTagsController={noteTagsController} noteTagsController={noteTagsController}
historyModalController={historyModalController} historyModalController={historyModalController}
closeMenu={toggleMenu}
/> />
</Popover> </Popover>
</> </>

View File

@@ -10,4 +10,5 @@ export type NotesOptionsProps = {
notesController: NotesController notesController: NotesController
noteTagsController: NoteTagsController noteTagsController: NoteTagsController
historyModalController: HistoryModalController historyModalController: HistoryModalController
closeMenu: () => void
} }

View File

@@ -1,6 +1,6 @@
import { ElementIds } from '@/Constants/ElementIDs' import { ElementIds } from '@/Constants/ElementIDs'
import { isMobileScreen } from '@/Utils' import { isMobileScreen } from '@/Utils'
import { useEffect, ReactNode, useMemo, createContext, useCallback, useContext, useState } from 'react' import { useEffect, ReactNode, useMemo, createContext, useCallback, useContext, useState, memo } from 'react'
import { AppPaneId } from './AppPaneMetadata' import { AppPaneId } from './AppPaneMetadata'
type ResponsivePaneData = { type ResponsivePaneData = {
@@ -24,6 +24,8 @@ type Props = {
children: ReactNode children: ReactNode
} }
const MemoizedChildren = memo(({ children }: Props) => <div>{children}</div>)
const ResponsivePaneProvider = ({ children }: Props) => { const ResponsivePaneProvider = ({ children }: Props) => {
const [currentSelectedPane, setCurrentSelectedPane] = useState<AppPaneId>( const [currentSelectedPane, setCurrentSelectedPane] = useState<AppPaneId>(
isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor, isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor,
@@ -63,7 +65,11 @@ const ResponsivePaneProvider = ({ children }: Props) => {
[currentSelectedPane, toggleAppPane], [currentSelectedPane, toggleAppPane],
) )
return <ResponsivePaneContext.Provider value={contextValue}>{children}</ResponsivePaneContext.Provider> return (
<ResponsivePaneContext.Provider value={contextValue}>
<MemoizedChildren children={children} />
</ResponsivePaneContext.Provider>
)
} }
export default ResponsivePaneProvider export default ResponsivePaneProvider