feat: add files button to multiple selection view (#1067)

This commit is contained in:
Aman Harwara
2022-06-06 23:50:11 +05:30
committed by GitHub
parent 59dcca18d6
commit 218e7a3d06
14 changed files with 222 additions and 96 deletions

View File

@@ -191,7 +191,12 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
{renderChallenges()} {renderChallenges()}
<> <>
<NotesContextMenu application={application} viewControllerManager={viewControllerManager} /> <NotesContextMenu
application={application}
navigationController={viewControllerManager.navigationController}
notesController={viewControllerManager.notesController}
noteTagsController={viewControllerManager.noteTagsController}
/>
<TagsContextMenuWrapper viewControllerManager={viewControllerManager} /> <TagsContextMenuWrapper viewControllerManager={viewControllerManager} />
<FileContextMenuWrapper <FileContextMenuWrapper
filesController={viewControllerManager.filesController} filesController={viewControllerManager.filesController}

View File

@@ -6,7 +6,7 @@ import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react' import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
import Icon from '@/Components/Icon/Icon' import Icon from '@/Components/Icon/Icon'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur' import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
import { ContentType, FileItem, SNNote } from '@standardnotes/snjs' import { FileItem, SNNote } from '@standardnotes/snjs'
import { addToast, ToastType } from '@standardnotes/stylekit' import { addToast, ToastType } from '@standardnotes/stylekit'
import { StreamingFileReader } from '@standardnotes/filepicker' import { StreamingFileReader } from '@standardnotes/filepicker'
import AttachedFilesPopover from './AttachedFilesPopover' import AttachedFilesPopover from './AttachedFilesPopover'
@@ -18,6 +18,7 @@ import { FilePreviewModalController } from '@/Controllers/FilePreviewModalContro
import { NavigationController } from '@/Controllers/Navigation/NavigationController' import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { FeaturesController } from '@/Controllers/FeaturesController' import { FeaturesController } from '@/Controllers/FeaturesController'
import { FilesController } from '@/Controllers/FilesController' import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
type Props = { type Props = {
application: WebApplication application: WebApplication
@@ -26,6 +27,7 @@ type Props = {
filesController: FilesController filesController: FilesController
navigationController: NavigationController navigationController: NavigationController
notesController: NotesController notesController: NotesController
selectionController: SelectedItemsController
onClickPreprocessing?: () => Promise<void> onClickPreprocessing?: () => Promise<void>
} }
@@ -36,8 +38,12 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
filePreviewModalController, filePreviewModalController,
navigationController, navigationController,
notesController, notesController,
selectionController,
onClickPreprocessing, onClickPreprocessing,
}: Props) => { }: Props) => {
const { allFiles, attachedFiles } = filesController
const attachedFilesCount = attachedFiles.length
const premiumModal = usePremiumModal() const premiumModal = usePremiumModal()
const note: SNNote | undefined = notesController.firstSelectedNote const note: SNNote | undefined = notesController.firstSelectedNote
@@ -63,22 +69,14 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
const [currentTab, setCurrentTab] = useState( const [currentTab, setCurrentTab] = useState(
navigationController.isInFilesView ? PopoverTabs.AllFiles : PopoverTabs.AttachedFiles, navigationController.isInFilesView ? PopoverTabs.AllFiles : PopoverTabs.AttachedFiles,
) )
const [allFiles, setAllFiles] = useState<FileItem[]>([])
const [attachedFiles, setAttachedFiles] = useState<FileItem[]>([]) const isAttachedTabDisabled = navigationController.isInFilesView || selectionController.selectedItemsCount > 1
const attachedFilesCount = attachedFiles.length
useEffect(() => { useEffect(() => {
const unregisterFileStream = application.streamItems(ContentType.File, () => { if (isAttachedTabDisabled && currentTab === PopoverTabs.AttachedFiles) {
setAllFiles(application.items.getDisplayableFiles()) setCurrentTab(PopoverTabs.AllFiles)
if (note) {
setAttachedFiles(application.items.getFilesForNote(note))
}
})
return () => {
unregisterFileStream()
} }
}, [application, note]) }, [currentTab, isAttachedTabDisabled])
const toggleAttachedFilesMenu = useCallback(async () => { const toggleAttachedFilesMenu = useCallback(async () => {
const rect = buttonRef.current?.getBoundingClientRect() const rect = buttonRef.current?.getBoundingClientRect()
@@ -304,7 +302,7 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
currentTab={currentTab} currentTab={currentTab}
isDraggingFiles={isDraggingFiles} isDraggingFiles={isDraggingFiles}
setCurrentTab={setCurrentTab} setCurrentTab={setCurrentTab}
attachedTabDisabled={navigationController.isInFilesView} attachedTabDisabled={isAttachedTabDisabled}
/> />
)} )}
</DisclosurePanel> </DisclosurePanel>

View File

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

View File

@@ -5,13 +5,32 @@ 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 { WebApplication } from '@/Application/Application'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { FilePreviewModalController } from '@/Controllers/FilePreviewModalController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NotesController } from '@/Controllers/NotesController'
import AttachedFilesButton from '../AttachedFilesPopover/AttachedFilesButton'
type Props = { type Props = {
application: WebApplication
featuresController: FeaturesController
filePreviewModalController: FilePreviewModalController
filesController: FilesController filesController: FilesController
navigationController: NavigationController
notesController: NotesController
selectionController: SelectedItemsController selectionController: SelectedItemsController
} }
const MultipleSelectedFiles = ({ filesController, selectionController }: Props) => { const MultipleSelectedFiles = ({
application,
filesController,
featuresController,
filePreviewModalController,
navigationController,
notesController,
selectionController,
}: Props) => {
const count = selectionController.selectedFilesCount const count = selectionController.selectedFilesCount
const cancelMultipleSelection = useCallback(() => { const cancelMultipleSelection = useCallback(() => {
@@ -23,6 +42,17 @@ const MultipleSelectedFiles = ({ filesController, selectionController }: Props)
<div className="flex items-center justify-between p-4 w-full"> <div className="flex items-center justify-between p-4 w-full">
<h1 className="sk-h1 font-bold m-0">{count} selected files</h1> <h1 className="sk-h1 font-bold m-0">{count} selected files</h1>
<div className="flex"> <div className="flex">
<div className="mr-3">
<AttachedFilesButton
application={application}
featuresController={featuresController}
filePreviewModalController={filePreviewModalController}
filesController={filesController}
navigationController={navigationController}
notesController={notesController}
selectionController={selectionController}
/>
</div>
<FileOptionsPanel filesController={filesController} selectionController={selectionController} /> <FileOptionsPanel filesController={filesController} selectionController={selectionController} />
</div> </div>
</div> </div>

View File

@@ -1,4 +1,3 @@
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { IlNotesIcon } from '@standardnotes/icons' import { IlNotesIcon } from '@standardnotes/icons'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel' import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel'
@@ -6,18 +5,41 @@ import { WebApplication } from '@/Application/Application'
import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton' import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton'
import Button from '../Button/Button' import Button from '../Button/Button'
import { useCallback } from 'react' import { useCallback } from 'react'
import AttachedFilesButton from '../AttachedFilesPopover/AttachedFilesButton'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { FilePreviewModalController } from '@/Controllers/FilePreviewModalController'
import { FilesController } from '@/Controllers/FilesController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NotesController } from '@/Controllers/NotesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
type Props = { type Props = {
application: WebApplication application: WebApplication
viewControllerManager: ViewControllerManager featuresController: FeaturesController
filePreviewModalController: FilePreviewModalController
filesController: FilesController
navigationController: NavigationController
notesController: NotesController
noteTagsController: NoteTagsController
selectionController: SelectedItemsController
} }
const MultipleSelectedNotes = ({ application, viewControllerManager }: Props) => { const MultipleSelectedNotes = ({
const count = viewControllerManager.notesController.selectedNotesCount application,
featuresController,
filePreviewModalController,
filesController,
navigationController,
notesController,
noteTagsController,
selectionController,
}: Props) => {
const count = notesController.selectedNotesCount
const cancelMultipleSelection = useCallback(() => { const cancelMultipleSelection = useCallback(() => {
viewControllerManager.selectionController.cancelMultipleSelection() selectionController.cancelMultipleSelection()
}, [viewControllerManager]) }, [selectionController])
return ( return (
<div className="flex flex-col h-full items-center"> <div className="flex flex-col h-full items-center">
@@ -25,9 +47,25 @@ const MultipleSelectedNotes = ({ application, viewControllerManager }: Props) =>
<h1 className="sk-h1 font-bold m-0">{count} selected notes</h1> <h1 className="sk-h1 font-bold m-0">{count} selected notes</h1>
<div className="flex"> <div className="flex">
<div className="mr-3"> <div className="mr-3">
<PinNoteButton viewControllerManager={viewControllerManager} /> <AttachedFilesButton
application={application}
featuresController={featuresController}
filePreviewModalController={filePreviewModalController}
filesController={filesController}
navigationController={navigationController}
notesController={notesController}
selectionController={selectionController}
/>
</div> </div>
<NotesOptionsPanel application={application} viewControllerManager={viewControllerManager} /> <div className="mr-3">
<PinNoteButton notesController={notesController} />
</div>
<NotesOptionsPanel
application={application}
navigationController={navigationController}
notesController={notesController}
noteTagsController={noteTagsController}
/>
</div> </div>
</div> </div>
<div className="flex-grow flex flex-col justify-center items-center w-full max-w-md"> <div className="flex-grow flex flex-col justify-center items-center w-full max-w-md">

View File

@@ -83,13 +83,27 @@ class NoteGroupView extends PureComponent<Props, State> {
return ( return (
<div id={ElementIds.EditorColumn} className="h-full app-column app-column-third"> <div id={ElementIds.EditorColumn} className="h-full app-column app-column-third">
{this.state.showMultipleSelectedNotes && ( {this.state.showMultipleSelectedNotes && (
<MultipleSelectedNotes application={this.application} viewControllerManager={this.viewControllerManager} /> <MultipleSelectedNotes
application={this.application}
filesController={this.viewControllerManager.filesController}
selectionController={this.viewControllerManager.selectionController}
featuresController={this.viewControllerManager.featuresController}
filePreviewModalController={this.viewControllerManager.filePreviewModalController}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
/>
)} )}
{this.state.showMultipleSelectedFiles && ( {this.state.showMultipleSelectedFiles && (
<MultipleSelectedFiles <MultipleSelectedFiles
application={this.application}
filesController={this.viewControllerManager.filesController} filesController={this.viewControllerManager.filesController}
selectionController={this.viewControllerManager.selectionController} selectionController={this.viewControllerManager.selectionController}
featuresController={this.viewControllerManager.featuresController}
filePreviewModalController={this.viewControllerManager.filePreviewModalController}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
/> />
)} )}

View File

@@ -950,6 +950,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
filesController={this.viewControllerManager.filesController} filesController={this.viewControllerManager.filesController}
navigationController={this.viewControllerManager.navigationController} navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController} notesController={this.viewControllerManager.notesController}
selectionController={this.viewControllerManager.selectionController}
/> />
</div> </div>
<div className="mr-3"> <div className="mr-3">
@@ -961,13 +962,15 @@ class NoteView extends PureComponent<NoteViewProps, State> {
</div> </div>
<div className="mr-3"> <div className="mr-3">
<PinNoteButton <PinNoteButton
viewControllerManager={this.viewControllerManager} notesController={this.viewControllerManager.notesController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction} onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/> />
</div> </div>
<NotesOptionsPanel <NotesOptionsPanel
application={this.application} application={this.application}
viewControllerManager={this.viewControllerManager} navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction} onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/> />
</div> </div>

View File

@@ -1,29 +1,31 @@
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur' import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside' import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import NotesOptions from '@/Components/NotesOptions/NotesOptions' import NotesOptions from '@/Components/NotesOptions/NotesOptions'
import { useCallback, useEffect, useRef } from 'react' import { useCallback, useEffect, useRef } from 'react'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { NotesController } from '@/Controllers/NotesController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
type Props = { type Props = {
application: WebApplication application: WebApplication
viewControllerManager: ViewControllerManager navigationController: NavigationController
notesController: NotesController
noteTagsController: NoteTagsController
} }
const NotesContextMenu = ({ application, viewControllerManager }: Props) => { const NotesContextMenu = ({ application, navigationController, notesController, noteTagsController }: Props) => {
const { contextMenuOpen, contextMenuPosition, contextMenuMaxHeight } = viewControllerManager.notesController const { contextMenuOpen, contextMenuPosition, contextMenuMaxHeight } = notesController
const contextMenuRef = useRef<HTMLDivElement>(null) const contextMenuRef = useRef<HTMLDivElement>(null)
const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) => const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) => notesController.setContextMenuOpen(open))
viewControllerManager.notesController.setContextMenuOpen(open),
)
useCloseOnClickOutside(contextMenuRef, () => viewControllerManager.notesController.setContextMenuOpen(false)) useCloseOnClickOutside(contextMenuRef, () => notesController.setContextMenuOpen(false))
const reloadContextMenuLayout = useCallback(() => { const reloadContextMenuLayout = useCallback(() => {
viewControllerManager.notesController.reloadContextMenuLayout() notesController.reloadContextMenuLayout()
}, [viewControllerManager]) }, [notesController])
useEffect(() => { useEffect(() => {
window.addEventListener('resize', reloadContextMenuLayout) window.addEventListener('resize', reloadContextMenuLayout)
@@ -41,7 +43,13 @@ const NotesContextMenu = ({ application, viewControllerManager }: Props) => {
maxHeight: contextMenuMaxHeight, maxHeight: contextMenuMaxHeight,
}} }}
> >
<NotesOptions application={application} viewControllerManager={viewControllerManager} closeOnBlur={closeOnBlur} /> <NotesOptions
application={application}
closeOnBlur={closeOnBlur}
navigationController={navigationController}
notesController={notesController}
noteTagsController={noteTagsController}
/>
</div> </div>
) : null ) : null
} }

View File

@@ -1,16 +1,20 @@
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuStyle' import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuStyle'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure' import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react' import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
import Icon from '@/Components/Icon/Icon' import Icon from '@/Components/Icon/Icon'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur' import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NotesController } from '@/Controllers/NotesController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
type Props = { type Props = {
viewControllerManager: ViewControllerManager navigationController: NavigationController
notesController: NotesController
noteTagsController: NoteTagsController
} }
const AddTagOption: FunctionComponent<Props> = ({ viewControllerManager }) => { const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesController, noteTagsController }) => {
const menuContainerRef = useRef<HTMLDivElement>(null) const menuContainerRef = useRef<HTMLDivElement>(null)
const menuRef = useRef<HTMLDivElement>(null) const menuRef = useRef<HTMLDivElement>(null)
const menuButtonRef = useRef<HTMLButtonElement>(null) const menuButtonRef = useRef<HTMLButtonElement>(null)
@@ -84,22 +88,22 @@ const AddTagOption: FunctionComponent<Props> = ({ viewControllerManager }) => {
}} }}
className="sn-dropdown min-w-80 flex flex-col py-2 max-h-120 max-w-xs fixed overflow-y-auto" className="sn-dropdown min-w-80 flex flex-col py-2 max-h-120 max-w-xs fixed overflow-y-auto"
> >
{viewControllerManager.navigationController.tags.map((tag) => ( {navigationController.tags.map((tag) => (
<button <button
key={tag.uuid} key={tag.uuid}
className="sn-dropdown-item sn-dropdown-item--no-icon max-w-80" className="sn-dropdown-item sn-dropdown-item--no-icon max-w-80"
onBlur={closeOnBlur} onBlur={closeOnBlur}
onClick={() => { onClick={() => {
viewControllerManager.notesController.isTagInSelectedNotes(tag) notesController.isTagInSelectedNotes(tag)
? viewControllerManager.notesController.removeTagFromSelectedNotes(tag).catch(console.error) ? notesController.removeTagFromSelectedNotes(tag).catch(console.error)
: viewControllerManager.notesController.addTagToSelectedNotes(tag).catch(console.error) : notesController.addTagToSelectedNotes(tag).catch(console.error)
}} }}
> >
<span <span
className={`whitespace-nowrap overflow-hidden overflow-ellipsis className={`whitespace-nowrap overflow-hidden overflow-ellipsis
${viewControllerManager.notesController.isTagInSelectedNotes(tag) ? 'font-bold' : ''}`} ${notesController.isTagInSelectedNotes(tag) ? 'font-bold' : ''}`}
> >
{viewControllerManager.noteTagsController.getLongTitle(tag)} {noteTagsController.getLongTitle(tag)}
</span> </span>
</button> </button>
))} ))}

View File

@@ -1,6 +1,5 @@
import { KeyboardKey } from '@/Services/IOService' import { KeyboardKey } from '@/Services/IOService'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure' import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
import { SNNote } from '@standardnotes/snjs' import { SNNote } from '@standardnotes/snjs'
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react' import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
@@ -10,7 +9,6 @@ import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuSty
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur' import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
type ChangeEditorOptionProps = { type ChangeEditorOptionProps = {
viewControllerManager: ViewControllerManager
application: WebApplication application: WebApplication
note: SNNote note: SNNote
} }

View File

@@ -1,9 +1,8 @@
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import Icon from '@/Components/Icon/Icon' import Icon from '@/Components/Icon/Icon'
import Switch from '@/Components/Switch/Switch' import Switch from '@/Components/Switch/Switch'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { useState, useEffect, useMemo, useCallback, FunctionComponent } from 'react' import { useState, useEffect, useMemo, useCallback, FunctionComponent } from 'react'
import { SNApplication, SNNote } from '@standardnotes/snjs' import { SNApplication, SNComponent, SNNote } from '@standardnotes/snjs'
import { KeyboardModifier } from '@/Services/IOService' import { KeyboardModifier } from '@/Services/IOService'
import ChangeEditorOption from './ChangeEditorOption' import ChangeEditorOption from './ChangeEditorOption'
import { BYTES_IN_ONE_MEGABYTE } from '@/Constants/Constants' import { BYTES_IN_ONE_MEGABYTE } from '@/Constants/Constants'
@@ -11,6 +10,7 @@ import ListedActionsOption from './ListedActionsOption'
import AddTagOption from './AddTagOption' import AddTagOption from './AddTagOption'
import { addToast, dismissToast, ToastType } from '@standardnotes/stylekit' import { addToast, dismissToast, ToastType } from '@standardnotes/stylekit'
import { NotesOptionsProps } from './NotesOptionsProps' import { NotesOptionsProps } from './NotesOptionsProps'
import { NotesController } from '@/Controllers/NotesController'
type DeletePermanentlyButtonProps = { type DeletePermanentlyButtonProps = {
closeOnBlur: NotesOptionsProps['closeOnBlur'] closeOnBlur: NotesOptionsProps['closeOnBlur']
@@ -121,15 +121,15 @@ const NoteAttributes: FunctionComponent<{
} }
const SpellcheckOptions: FunctionComponent<{ const SpellcheckOptions: FunctionComponent<{
viewControllerManager: ViewControllerManager editorForNote: SNComponent | undefined
notesController: NotesController
note: SNNote note: SNNote
}> = ({ viewControllerManager, note }) => { }> = ({ editorForNote, notesController, note }) => {
const editor = viewControllerManager.application.componentManager.editorForNote(note) const spellcheckControllable = Boolean(!editorForNote || editorForNote.package_info.spellcheckControl)
const spellcheckControllable = Boolean(!editor || editor.package_info.spellcheckControl)
const noteSpellcheck = !spellcheckControllable const noteSpellcheck = !spellcheckControllable
? true ? true
: note : note
? viewControllerManager.notesController.getSpellcheckStateForNote(note) ? notesController.getSpellcheckStateForNote(note)
: undefined : undefined
return ( return (
@@ -137,7 +137,7 @@ const SpellcheckOptions: FunctionComponent<{
<button <button
className="sn-dropdown-item justify-between px-3 py-1" className="sn-dropdown-item justify-between px-3 py-1"
onClick={() => { onClick={() => {
viewControllerManager.notesController.toggleGlobalSpellcheckForNote(note).catch(console.error) notesController.toggleGlobalSpellcheckForNote(note).catch(console.error)
}} }}
disabled={!spellcheckControllable} disabled={!spellcheckControllable}
> >
@@ -169,7 +169,13 @@ const NoteSizeWarning: FunctionComponent<{
) : null ) : null
} }
const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: NotesOptionsProps) => { const NotesOptions = ({
application,
navigationController,
notesController,
noteTagsController,
closeOnBlur,
}: NotesOptionsProps) => {
const [altKeyDown, setAltKeyDown] = useState(false) const [altKeyDown, setAltKeyDown] = useState(false)
const toggleOn = (condition: (note: SNNote) => boolean) => { const toggleOn = (condition: (note: SNNote) => boolean) => {
@@ -178,7 +184,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
return notesMatchingAttribute.length > notesNotMatchingAttribute.length return notesMatchingAttribute.length > notesNotMatchingAttribute.length
} }
const notes = viewControllerManager.notesController.selectedNotes const notes = notesController.selectedNotes
const hidePreviews = toggleOn((note) => note.hidePreview) const hidePreviews = toggleOn((note) => note.hidePreview)
const locked = toggleOn((note) => note.locked) const locked = toggleOn((note) => note.locked)
const protect = toggleOn((note) => note.protected) const protect = toggleOn((note) => note.protected)
@@ -189,6 +195,11 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
const pinned = notes.some((note) => note.pinned) const pinned = notes.some((note) => note.pinned)
const unpinned = notes.some((note) => !note.pinned) const unpinned = notes.some((note) => !note.pinned)
const editorForNote = useMemo(
() => application.componentManager.editorForNote(notes[0]),
[application.componentManager, notes],
)
useEffect(() => { useEffect(() => {
const removeAltKeyObserver = application.io.addKeyObserver({ const removeAltKeyObserver = application.io.addKeyObserver({
modifiers: [KeyboardModifier.Alt], modifiers: [KeyboardModifier.Alt],
@@ -248,8 +259,8 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
}, [application, notes]) }, [application, notes])
const openRevisionHistoryModal = useCallback(() => { const openRevisionHistoryModal = useCallback(() => {
viewControllerManager.notesController.setShowRevisionHistoryModal(true) notesController.setShowRevisionHistoryModal(true)
}, [viewControllerManager]) }, [notesController])
return ( return (
<> <>
@@ -265,7 +276,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
<button <button
className="sn-dropdown-item justify-between" className="sn-dropdown-item justify-between"
onClick={() => { onClick={() => {
viewControllerManager.notesController.setLockSelectedNotes(!locked) notesController.setLockSelectedNotes(!locked)
}} }}
onBlur={closeOnBlur} onBlur={closeOnBlur}
> >
@@ -278,7 +289,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
<button <button
className="sn-dropdown-item justify-between" className="sn-dropdown-item justify-between"
onClick={() => { onClick={() => {
viewControllerManager.notesController.setHideSelectedNotePreviews(!hidePreviews) notesController.setHideSelectedNotePreviews(!hidePreviews)
}} }}
onBlur={closeOnBlur} onBlur={closeOnBlur}
> >
@@ -291,7 +302,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
<button <button
className="sn-dropdown-item justify-between" className="sn-dropdown-item justify-between"
onClick={() => { onClick={() => {
viewControllerManager.notesController.setProtectSelectedNotes(!protect).catch(console.error) notesController.setProtectSelectedNotes(!protect).catch(console.error)
}} }}
onBlur={closeOnBlur} onBlur={closeOnBlur}
> >
@@ -304,19 +315,23 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
{notes.length === 1 && ( {notes.length === 1 && (
<> <>
<div className="min-h-1px my-2 bg-border"></div> <div className="min-h-1px my-2 bg-border"></div>
<ChangeEditorOption viewControllerManager={viewControllerManager} application={application} note={notes[0]} /> <ChangeEditorOption application={application} note={notes[0]} />
</> </>
)} )}
<div className="min-h-1px my-2 bg-border"></div> <div className="min-h-1px my-2 bg-border"></div>
{viewControllerManager.navigationController.tagsCount > 0 && ( {navigationController.tagsCount > 0 && (
<AddTagOption viewControllerManager={viewControllerManager} /> <AddTagOption
navigationController={navigationController}
notesController={notesController}
noteTagsController={noteTagsController}
/>
)} )}
{unpinned && ( {unpinned && (
<button <button
onBlur={closeOnBlur} onBlur={closeOnBlur}
className="sn-dropdown-item" className="sn-dropdown-item"
onClick={() => { onClick={() => {
viewControllerManager.notesController.setPinSelectedNotes(true) notesController.setPinSelectedNotes(true)
}} }}
> >
<Icon type="pin" className={iconClass} /> <Icon type="pin" className={iconClass} />
@@ -328,7 +343,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
onBlur={closeOnBlur} onBlur={closeOnBlur}
className="sn-dropdown-item" className="sn-dropdown-item"
onClick={() => { onClick={() => {
viewControllerManager.notesController.setPinSelectedNotes(false) notesController.setPinSelectedNotes(false)
}} }}
> >
<Icon type="unpin" className={iconClass} /> <Icon type="unpin" className={iconClass} />
@@ -348,7 +363,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
onBlur={closeOnBlur} onBlur={closeOnBlur}
className="sn-dropdown-item" className="sn-dropdown-item"
onClick={() => { onClick={() => {
viewControllerManager.notesController.setArchiveSelectedNotes(true).catch(console.error) notesController.setArchiveSelectedNotes(true).catch(console.error)
}} }}
> >
<Icon type="archive" className={iconClassWarning} /> <Icon type="archive" className={iconClassWarning} />
@@ -360,7 +375,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
onBlur={closeOnBlur} onBlur={closeOnBlur}
className="sn-dropdown-item" className="sn-dropdown-item"
onClick={() => { onClick={() => {
viewControllerManager.notesController.setArchiveSelectedNotes(false).catch(console.error) notesController.setArchiveSelectedNotes(false).catch(console.error)
}} }}
> >
<Icon type="unarchive" className={iconClassWarning} /> <Icon type="unarchive" className={iconClassWarning} />
@@ -372,7 +387,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
<DeletePermanentlyButton <DeletePermanentlyButton
closeOnBlur={closeOnBlur} closeOnBlur={closeOnBlur}
onClick={async () => { onClick={async () => {
await viewControllerManager.notesController.deleteNotesPermanently() await notesController.deleteNotesPermanently()
}} }}
/> />
) : ( ) : (
@@ -380,7 +395,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
onBlur={closeOnBlur} onBlur={closeOnBlur}
className="sn-dropdown-item" className="sn-dropdown-item"
onClick={async () => { onClick={async () => {
await viewControllerManager.notesController.setTrashSelectedNotes(true) await notesController.setTrashSelectedNotes(true)
}} }}
> >
<Icon type="trash" className={iconClassDanger} /> <Icon type="trash" className={iconClassDanger} />
@@ -393,7 +408,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
onBlur={closeOnBlur} onBlur={closeOnBlur}
className="sn-dropdown-item" className="sn-dropdown-item"
onClick={async () => { onClick={async () => {
await viewControllerManager.notesController.setTrashSelectedNotes(false) await notesController.setTrashSelectedNotes(false)
}} }}
> >
<Icon type="restore" className={iconClassSuccess} /> <Icon type="restore" className={iconClassSuccess} />
@@ -402,21 +417,21 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
<DeletePermanentlyButton <DeletePermanentlyButton
closeOnBlur={closeOnBlur} closeOnBlur={closeOnBlur}
onClick={async () => { onClick={async () => {
await viewControllerManager.notesController.deleteNotesPermanently() await notesController.deleteNotesPermanently()
}} }}
/> />
<button <button
onBlur={closeOnBlur} onBlur={closeOnBlur}
className="sn-dropdown-item" className="sn-dropdown-item"
onClick={async () => { onClick={async () => {
await viewControllerManager.notesController.emptyTrash() await notesController.emptyTrash()
}} }}
> >
<div className="flex items-start"> <div className="flex items-start">
<Icon type="trash-sweep" className="color-danger mr-2" /> <Icon type="trash-sweep" className="color-danger mr-2" />
<div className="flex-row"> <div className="flex-row">
<div className="color-danger">Empty Trash</div> <div className="color-danger">Empty Trash</div>
<div className="text-xs">{viewControllerManager.notesController.trashedNotesCount} notes in Trash</div> <div className="text-xs">{notesController.trashedNotesCount} notes in Trash</div>
</div> </div>
</div> </div>
</button> </button>
@@ -427,7 +442,7 @@ const NotesOptions = ({ application, viewControllerManager, closeOnBlur }: Notes
<div className="min-h-1px my-2 bg-border"></div> <div className="min-h-1px my-2 bg-border"></div>
<ListedActionsOption application={application} note={notes[0]} /> <ListedActionsOption application={application} note={notes[0]} />
<div className="min-h-1px my-2 bg-border"></div> <div className="min-h-1px my-2 bg-border"></div>
<SpellcheckOptions viewControllerManager={viewControllerManager} note={notes[0]} /> <SpellcheckOptions editorForNote={editorForNote} notesController={notesController} note={notes[0]} />
<div className="min-h-1px my-2 bg-border"></div> <div className="min-h-1px my-2 bg-border"></div>
<NoteAttributes application={application} note={notes[0]} /> <NoteAttributes application={application} note={notes[0]} />
<NoteSizeWarning note={notes[0]} /> <NoteSizeWarning note={notes[0]} />

View File

@@ -1,4 +1,3 @@
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import Icon from '@/Components/Icon/Icon' import Icon from '@/Components/Icon/Icon'
import VisuallyHidden from '@reach/visually-hidden' import VisuallyHidden from '@reach/visually-hidden'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur' import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
@@ -8,14 +7,25 @@ import { observer } from 'mobx-react-lite'
import NotesOptions from './NotesOptions' import NotesOptions from './NotesOptions'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants' import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import { NotesController } from '@/Controllers/NotesController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
type Props = { type Props = {
application: WebApplication application: WebApplication
viewControllerManager: ViewControllerManager navigationController: NavigationController
notesController: NotesController
noteTagsController: NoteTagsController
onClickPreprocessing?: () => Promise<void> onClickPreprocessing?: () => Promise<void>
} }
const NotesOptionsPanel = ({ application, viewControllerManager, onClickPreprocessing }: Props) => { const NotesOptionsPanel = ({
application,
navigationController,
notesController,
noteTagsController,
onClickPreprocessing,
}: Props) => {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [position, setPosition] = useState({ const [position, setPosition] = useState({
top: 0, top: 0,
@@ -82,7 +92,9 @@ const NotesOptionsPanel = ({ application, viewControllerManager, onClickPreproce
{open && ( {open && (
<NotesOptions <NotesOptions
application={application} application={application}
viewControllerManager={viewControllerManager} navigationController={navigationController}
notesController={notesController}
noteTagsController={noteTagsController}
closeOnBlur={closeOnBlur} closeOnBlur={closeOnBlur}
/> />
)} )}

View File

@@ -1,8 +1,12 @@
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager' import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NotesController } from '@/Controllers/NotesController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
export type NotesOptionsProps = { export type NotesOptionsProps = {
application: WebApplication application: WebApplication
viewControllerManager: ViewControllerManager navigationController: NavigationController
notesController: NotesController
noteTagsController: NoteTagsController
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
} }

View File

@@ -1,21 +1,17 @@
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import VisuallyHidden from '@reach/visually-hidden' import VisuallyHidden from '@reach/visually-hidden'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback } from 'react' import { FunctionComponent, useCallback } from 'react'
import Icon from '@/Components/Icon/Icon' import Icon from '@/Components/Icon/Icon'
import { NotesController } from '@/Controllers/NotesController'
type Props = { type Props = {
viewControllerManager: ViewControllerManager
className?: string className?: string
notesController: NotesController
onClickPreprocessing?: () => Promise<void> onClickPreprocessing?: () => Promise<void>
} }
const PinNoteButton: FunctionComponent<Props> = ({ const PinNoteButton: FunctionComponent<Props> = ({ className = '', notesController, onClickPreprocessing }: Props) => {
viewControllerManager, const notes = notesController.selectedNotes
className = '',
onClickPreprocessing,
}: Props) => {
const notes = viewControllerManager.notesController.selectedNotes
const pinned = notes.some((note) => note.pinned) const pinned = notes.some((note) => note.pinned)
const togglePinned = useCallback(async () => { const togglePinned = useCallback(async () => {
@@ -23,11 +19,11 @@ const PinNoteButton: FunctionComponent<Props> = ({
await onClickPreprocessing() await onClickPreprocessing()
} }
if (!pinned) { if (!pinned) {
viewControllerManager.notesController.setPinSelectedNotes(true) notesController.setPinSelectedNotes(true)
} else { } else {
viewControllerManager.notesController.setPinSelectedNotes(false) notesController.setPinSelectedNotes(false)
} }
}, [viewControllerManager, onClickPreprocessing, pinned]) }, [onClickPreprocessing, pinned, notesController])
return ( return (
<button className={`sn-icon-button border-contrast ${pinned ? 'toggled' : ''} ${className}`} onClick={togglePinned}> <button className={`sn-icon-button border-contrast ${pinned ? 'toggled' : ''} ${className}`} onClick={togglePinned}>