feat: remove attached files button from files view (#1193)

This commit is contained in:
Aman Harwara
2022-07-02 17:21:51 +05:30
committed by GitHub
parent f437ed6ce4
commit f3a6aaaf2e
6 changed files with 237 additions and 184 deletions

View File

@@ -24,6 +24,7 @@ import FileContextMenuWrapper from '@/Components/FileContextMenu/FileContextMenu
import PermissionsModalWrapper from '@/Components/PermissionsModal/PermissionsModalWrapper'
import { PanelResizedData } from '@/Types/PanelResizedData'
import TagContextMenuWrapper from '@/Components/Tags/TagContextMenuWrapper'
import FileDragNDropProvider from '../FileDragNDropProvider/FileDragNDropProvider'
type Props = {
application: WebApplication
@@ -176,19 +177,25 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
<PremiumModalProvider application={application} viewControllerManager={viewControllerManager}>
<div className={platformString + ' main-ui-view sn-component'}>
<div id="app" className={appClass + ' app app-column-container'}>
<Navigation application={application} />
<ContentListView
<FileDragNDropProvider
application={application}
accountMenuController={viewControllerManager.accountMenuController}
featuresController={viewControllerManager.featuresController}
filesController={viewControllerManager.filesController}
itemListController={viewControllerManager.itemListController}
navigationController={viewControllerManager.navigationController}
noAccountWarningController={viewControllerManager.noAccountWarningController}
noteTagsController={viewControllerManager.noteTagsController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
/>
<NoteGroupView application={application} />
>
<Navigation application={application} />
<ContentListView
application={application}
accountMenuController={viewControllerManager.accountMenuController}
filesController={viewControllerManager.filesController}
itemListController={viewControllerManager.itemListController}
navigationController={viewControllerManager.navigationController}
noAccountWarningController={viewControllerManager.noAccountWarningController}
noteTagsController={viewControllerManager.noteTagsController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
/>
<NoteGroupView application={application} />
</FileDragNDropProvider>
</div>
<>

View File

@@ -6,19 +6,18 @@ import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
import Icon from '@/Components/Icon/Icon'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
import { FileItem, SNNote } from '@standardnotes/snjs'
import { addToast, ToastType } from '@standardnotes/toast'
import { StreamingFileReader } from '@standardnotes/filepicker'
import AttachedFilesPopover from './AttachedFilesPopover'
import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { PopoverTabs } from './PopoverTabs'
import { isHandlingFileDrag } from '@/Utils/DragTypeCheck'
import { NotesController } from '@/Controllers/NotesController'
import { FilePreviewModalController } from '@/Controllers/FilePreviewModalController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { useFileDragNDrop } from '@/Components/FileDragNDropProvider/FileDragNDropProvider'
import { FileItem, SNNote } from '@standardnotes/snjs'
import { addToast, ToastType } from '@standardnotes/toast'
type Props = {
application: WebApplication
@@ -120,7 +119,7 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
if (!note) {
addToast({
type: ToastType.Error,
message: 'Could not attach file because selected note was deleted',
message: 'Could not attach file because selected note was unselected or deleted',
})
return
}
@@ -130,135 +129,36 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
[application.items, note],
)
const [isDraggingFiles, setIsDraggingFiles] = useState(false)
const dragCounter = useRef(0)
const { isDraggingFiles, addFilesDragInCallback, addFilesDropCallback } = useFileDragNDrop()
const handleDrag = useCallback(
(event: DragEvent) => {
if (isHandlingFileDrag(event, application)) {
event.preventDefault()
event.stopPropagation()
}
},
[application],
)
useEffect(() => {
if (isDraggingFiles && !open) {
void toggleAttachedFilesMenu()
}
}, [isDraggingFiles, open, toggleAttachedFilesMenu])
const handleDragIn = useCallback(
(event: DragEvent) => {
if (!isHandlingFileDrag(event, application)) {
return
}
const filesDragInCallback = useCallback((tab: PopoverTabs) => {
setCurrentTab(tab)
}, [])
event.preventDefault()
event.stopPropagation()
useEffect(() => {
addFilesDragInCallback(filesDragInCallback)
}, [addFilesDragInCallback, filesDragInCallback])
switch ((event.target as HTMLElement).id) {
case PopoverTabs.AllFiles:
setCurrentTab(PopoverTabs.AllFiles)
break
case PopoverTabs.AttachedFiles:
setCurrentTab(PopoverTabs.AttachedFiles)
break
}
dragCounter.current = dragCounter.current + 1
if (event.dataTransfer?.items.length) {
setIsDraggingFiles(true)
if (!open) {
toggleAttachedFilesMenu().catch(console.error)
}
}
},
[open, toggleAttachedFilesMenu, application],
)
const handleDragOut = useCallback(
(event: DragEvent) => {
if (!isHandlingFileDrag(event, application)) {
return
}
event.preventDefault()
event.stopPropagation()
dragCounter.current = dragCounter.current - 1
if (dragCounter.current > 0) {
return
}
setIsDraggingFiles(false)
},
[application],
)
const handleDrop = useCallback(
(event: DragEvent) => {
if (!isHandlingFileDrag(event, application)) {
return
}
event.preventDefault()
event.stopPropagation()
setIsDraggingFiles(false)
if (!featuresController.hasFiles) {
prospectivelyShowFilesPremiumModal()
return
}
if (event.dataTransfer?.items.length) {
Array.from(event.dataTransfer.items).forEach(async (item) => {
const fileOrHandle = StreamingFileReader.available()
? ((await item.getAsFileSystemHandle()) as FileSystemFileHandle)
: item.getAsFile()
if (!fileOrHandle) {
return
}
const uploadedFiles = await filesController.uploadNewFile(fileOrHandle)
if (!uploadedFiles) {
return
}
if (currentTab === PopoverTabs.AttachedFiles) {
uploadedFiles.forEach((file) => {
attachFileToNote(file).catch(console.error)
})
}
const filesDropCallback = useCallback(
(uploadedFiles: FileItem[]) => {
if (currentTab === PopoverTabs.AttachedFiles) {
uploadedFiles.forEach((file) => {
attachFileToNote(file).catch(console.error)
})
event.dataTransfer.clearData()
dragCounter.current = 0
}
},
[
filesController,
featuresController.hasFiles,
attachFileToNote,
currentTab,
application,
prospectivelyShowFilesPremiumModal,
],
[attachFileToNote, currentTab],
)
useEffect(() => {
window.addEventListener('dragenter', handleDragIn)
window.addEventListener('dragleave', handleDragOut)
window.addEventListener('dragover', handleDrag)
window.addEventListener('drop', handleDrop)
return () => {
window.removeEventListener('dragenter', handleDragIn)
window.removeEventListener('dragleave', handleDragOut)
window.removeEventListener('dragover', handleDrag)
window.removeEventListener('drop', handleDrop)
}
}, [handleDragIn, handleDrop, handleDrag, handleDragOut])
addFilesDropCallback(filesDropCallback)
}, [addFilesDropCallback, filesDropCallback])
return (
<div ref={containerRef}>

View File

@@ -0,0 +1,181 @@
import { WebApplication } from '@/Application/Application'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { FilesController } from '@/Controllers/FilesController'
import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { isHandlingFileDrag } from '@/Utils/DragTypeCheck'
import { StreamingFileReader } from '@standardnotes/filepicker'
import { FileItem } from '@standardnotes/snjs'
import { useMemo, useState, createContext, ReactNode, useRef, useCallback, useEffect, useContext } from 'react'
import { PopoverTabs } from '../AttachedFilesPopover/PopoverTabs'
type FilesDragInCallback = (tab: PopoverTabs) => void
type FilesDropCallback = (uploadedFiles: FileItem[]) => void
type FileDnDContextData = {
isDraggingFiles: boolean
addFilesDragInCallback: (callback: FilesDragInCallback) => void
addFilesDropCallback: (callback: FilesDropCallback) => void
}
export const FileDnDContext = createContext<FileDnDContextData | null>(null)
export const useFileDragNDrop = () => {
const value = useContext(FileDnDContext)
if (!value) {
throw new Error('Current component must be a child of <FileDragNDropProvider />')
}
return value
}
type Props = {
application: WebApplication
featuresController: FeaturesController
filesController: FilesController
children: ReactNode
}
const FileDragNDropProvider = ({ application, children, featuresController, filesController }: Props) => {
const premiumModal = usePremiumModal()
const [isDraggingFiles, setIsDraggingFiles] = useState(false)
const filesDragInCallbackRef = useRef<FilesDragInCallback>()
const filesDropCallbackRef = useRef<FilesDropCallback>()
const addFilesDragInCallback = useCallback((callback: FilesDragInCallback) => {
filesDragInCallbackRef.current = callback
}, [])
const addFilesDropCallback = useCallback((callback: FilesDropCallback) => {
filesDropCallbackRef.current = callback
}, [])
const dragCounter = useRef(0)
const handleDrag = useCallback(
(event: DragEvent) => {
if (isHandlingFileDrag(event, application)) {
event.preventDefault()
event.stopPropagation()
}
},
[application],
)
const handleDragIn = useCallback(
(event: DragEvent) => {
if (!isHandlingFileDrag(event, application)) {
return
}
event.preventDefault()
event.stopPropagation()
switch ((event.target as HTMLElement).id) {
case PopoverTabs.AllFiles:
filesDragInCallbackRef.current?.(PopoverTabs.AllFiles)
break
case PopoverTabs.AttachedFiles:
filesDragInCallbackRef.current?.(PopoverTabs.AttachedFiles)
break
}
dragCounter.current = dragCounter.current + 1
if (event.dataTransfer?.items.length) {
setIsDraggingFiles(true)
}
},
[application],
)
const handleDragOut = useCallback(
(event: DragEvent) => {
if (!isHandlingFileDrag(event, application)) {
return
}
event.preventDefault()
event.stopPropagation()
dragCounter.current = dragCounter.current - 1
if (dragCounter.current > 0) {
return
}
setIsDraggingFiles(false)
},
[application],
)
const handleDrop = useCallback(
(event: DragEvent) => {
if (!isHandlingFileDrag(event, application)) {
setIsDraggingFiles(false)
return
}
event.preventDefault()
event.stopPropagation()
setIsDraggingFiles(false)
if (!featuresController.hasFiles) {
premiumModal.activate('Files')
return
}
if (event.dataTransfer?.items.length) {
Array.from(event.dataTransfer.items).forEach(async (item) => {
const fileOrHandle = StreamingFileReader.available()
? ((await item.getAsFileSystemHandle()) as FileSystemFileHandle)
: item.getAsFile()
if (!fileOrHandle) {
return
}
const uploadedFiles = await filesController.uploadNewFile(fileOrHandle)
if (!uploadedFiles) {
return
}
filesDropCallbackRef.current?.(uploadedFiles)
})
event.dataTransfer.clearData()
dragCounter.current = 0
}
},
[application, featuresController.hasFiles, filesController, premiumModal],
)
useEffect(() => {
window.addEventListener('dragenter', handleDragIn)
window.addEventListener('dragleave', handleDragOut)
window.addEventListener('dragover', handleDrag)
window.addEventListener('drop', handleDrop)
return () => {
window.removeEventListener('dragenter', handleDragIn)
window.removeEventListener('dragleave', handleDragOut)
window.removeEventListener('dragover', handleDrag)
window.removeEventListener('drop', handleDrop)
}
}, [handleDragIn, handleDrop, handleDrag, handleDragOut])
const contextValue = useMemo(() => {
return {
isDraggingFiles,
addFilesDragInCallback,
addFilesDropCallback,
}
}, [addFilesDragInCallback, addFilesDropCallback, isDraggingFiles])
return <FileDnDContext.Provider value={contextValue}>{children}</FileDnDContext.Provider>
}
export default FileDragNDropProvider

View File

@@ -1,7 +1,6 @@
import { ElementIds } from '@/Constants/ElementIDs'
import { observer } from 'mobx-react-lite'
import { ChangeEventHandler, useCallback, useRef } from 'react'
import AttachedFilesButton from '@/Components/AttachedFilesPopover/AttachedFilesButton'
import FileOptionsPanel from '@/Components/FileContextMenu/FileOptionsPanel'
import FilePreview from '@/Components/FilePreview/FilePreview'
import { FileViewProps } from './FileViewProps'
@@ -53,17 +52,6 @@ const FileViewWithoutProtection = ({ application, viewControllerManager, file }:
</div>
</div>
<div className="flex items-center">
<div className="mr-3">
<AttachedFilesButton
application={application}
featuresController={viewControllerManager.featuresController}
filePreviewModalController={viewControllerManager.filePreviewModalController}
filesController={viewControllerManager.filesController}
navigationController={viewControllerManager.navigationController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
/>
</div>
<FileOptionsPanel
filesController={viewControllerManager.filesController}
selectionController={viewControllerManager.selectionController}

View File

@@ -5,32 +5,13 @@ import { useCallback } from 'react'
import FileOptionsPanel from '../FileContextMenu/FileOptionsPanel'
import { FilesController } from '@/Controllers/FilesController'
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 = {
application: WebApplication
featuresController: FeaturesController
filePreviewModalController: FilePreviewModalController
filesController: FilesController
navigationController: NavigationController
notesController: NotesController
selectionController: SelectedItemsController
}
const MultipleSelectedFiles = ({
application,
filesController,
featuresController,
filePreviewModalController,
navigationController,
notesController,
selectionController,
}: Props) => {
const MultipleSelectedFiles = ({ filesController, selectionController }: Props) => {
const count = selectionController.selectedFilesCount
const cancelMultipleSelection = useCallback(() => {
@@ -41,18 +22,7 @@ const MultipleSelectedFiles = ({
<div className="flex h-full flex-col items-center">
<div className="flex w-full items-center justify-between p-4">
<h1 className="m-0 text-lg font-bold">{count} selected files</h1>
<div className="flex">
<div className="mr-3">
<AttachedFilesButton
application={application}
featuresController={featuresController}
filePreviewModalController={filePreviewModalController}
filesController={filesController}
navigationController={navigationController}
notesController={notesController}
selectionController={selectionController}
/>
</div>
<div>
<FileOptionsPanel filesController={filesController} selectionController={selectionController} />
</div>
</div>

View File

@@ -6,6 +6,7 @@ import NoteView from '@/Components/NoteView/NoteView'
import MultipleSelectedFiles from '../MultipleSelectedFiles/MultipleSelectedFiles'
import { ElementIds } from '@/Constants/ElementIDs'
import FileView from '@/Components/FileView/FileView'
import { FileDnDContext } from '@/Components/FileDragNDropProvider/FileDragNDropProvider'
type State = {
showMultipleSelectedNotes: boolean
@@ -19,6 +20,9 @@ type Props = {
}
class NoteGroupView extends PureComponent<Props, State> {
static override contextType = FileDnDContext
declare context: React.ContextType<typeof FileDnDContext>
private removeChangeObserver!: () => void
constructor(props: Props) {
@@ -77,6 +81,8 @@ class NoteGroupView extends PureComponent<Props, State> {
}
override render() {
const fileDragNDropContext = this.context
const shouldNotShowMultipleSelectedItems =
!this.state.showMultipleSelectedNotes && !this.state.showMultipleSelectedFiles
@@ -98,16 +104,17 @@ class NoteGroupView extends PureComponent<Props, State> {
{this.state.showMultipleSelectedFiles && (
<MultipleSelectedFiles
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}
/>
)}
{this.viewControllerManager.navigationController.isInFilesView && fileDragNDropContext?.isDraggingFiles && (
<div className="absolute bottom-8 left-1/2 z-dropdown-menu -translate-x-1/2 rounded bg-info px-5 py-3 text-info-contrast shadow-main">
Drop your files to upload them to Standard Notes
</div>
)}
{shouldNotShowMultipleSelectedItems && this.state.controllers.length > 0 && (
<>
{this.state.controllers.map((controller) => {