Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: file drop handling for super notes #1990

Merged
merged 4 commits into from Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -29,6 +29,7 @@ import AndroidBackHandlerProvider from '@/NativeMobileWeb/useAndroidBackHandler'
import ConfirmDeleteAccountContainer from '@/Components/ConfirmDeleteAccountModal/ConfirmDeleteAccountModal'
import DarkModeHandler from '../DarkModeHandler/DarkModeHandler'
import ApplicationProvider from './ApplicationProvider'
import { ErrorBoundary } from '@/Utils/ErrorBoundary'

type Props = {
application: WebApplication
Expand Down Expand Up @@ -219,7 +220,9 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
searchOptionsController={viewControllerManager.searchOptionsController}
linkingController={viewControllerManager.linkingController}
/>
<NoteGroupView application={application} />
<ErrorBoundary>
<NoteGroupView application={application} />
</ErrorBoundary>
</FileDragNDropProvider>
</div>

Expand Down
Expand Up @@ -9,10 +9,12 @@ import FilePlugin from './Plugins/EncryptedFilePlugin/FilePlugin'
import BlockPickerMenuPlugin from './Plugins/BlockPickerPlugin/BlockPickerPlugin'
import { ErrorBoundary } from '@/Utils/ErrorBoundary'
import { LinkingController } from '@/Controllers/LinkingController'
import LinkingControllerProvider from './Contexts/LinkingControllerProvider'
import LinkingControllerProvider from '../../Controllers/LinkingControllerProvider'
import { BubbleNode } from './Plugins/ItemBubblePlugin/Nodes/BubbleNode'
import ItemBubblePlugin from './Plugins/ItemBubblePlugin/ItemBubblePlugin'
import { NodeObserverPlugin } from './Plugins/NodeObserverPlugin/NodeObserverPlugin'
import { FilesController } from '@/Controllers/FilesController'
import FilesControllerProvider from '@/Controllers/FilesControllerProvider'

const StringEllipses = '...'
const NotePreviewCharLimit = 160
Expand All @@ -21,9 +23,10 @@ type Props = {
application: WebApplication
note: SNNote
linkingController: LinkingController
filesController: FilesController
}

export const BlockEditor: FunctionComponent<Props> = ({ note, application, linkingController }) => {
export const BlockEditor: FunctionComponent<Props> = ({ note, application, linkingController, filesController }) => {
const controller = useRef(new BlockEditorController(note, application))

const handleChange = useCallback(
Expand Down Expand Up @@ -51,19 +54,21 @@ export const BlockEditor: FunctionComponent<Props> = ({ note, application, linki
<div className="relative h-full w-full p-5">
<ErrorBoundary>
<LinkingControllerProvider controller={linkingController}>
<BlocksEditorComposer initialValue={note.text} nodes={[FileNode, BubbleNode]}>
<BlocksEditor
onChange={handleChange}
className="relative relative resize-none text-base focus:shadow-none focus:outline-none"
>
<ItemSelectionPlugin currentNote={note} />
<FilePlugin />
<ItemBubblePlugin />
<BlockPickerMenuPlugin />
<NodeObserverPlugin nodeType={BubbleNode} onRemove={handleBubbleRemove} />
<NodeObserverPlugin nodeType={FileNode} onRemove={handleBubbleRemove} />
</BlocksEditor>
</BlocksEditorComposer>
<FilesControllerProvider controller={filesController}>
<BlocksEditorComposer initialValue={note.text} nodes={[FileNode, BubbleNode]}>
<BlocksEditor
onChange={handleChange}
className="relative relative resize-none text-base focus:shadow-none focus:outline-none"
>
<ItemSelectionPlugin currentNote={note} />
<FilePlugin />
<ItemBubblePlugin />
<BlockPickerMenuPlugin />
<NodeObserverPlugin nodeType={BubbleNode} onRemove={handleBubbleRemove} />
<NodeObserverPlugin nodeType={FileNode} onRemove={handleBubbleRemove} />
</BlocksEditor>
</BlocksEditorComposer>
</FilesControllerProvider>
</LinkingControllerProvider>
</ErrorBoundary>
</div>
Expand Down
Expand Up @@ -6,9 +6,12 @@ import { FileNode } from './Nodes/FileNode'
import { $createParagraphNode, $insertNodes, $isRootOrShadowRoot, COMMAND_PRIORITY_EDITOR } from 'lexical'
import { $createFileNode } from './Nodes/FileUtils'
import { $wrapNodeInElement } from '@lexical/utils'
import { useFilesController } from '@/Controllers/FilesControllerProvider'
import { FilesControllerEvent } from '@/Controllers/FilesController'

export default function FilePlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext()
const filesController = useFilesController()

useEffect(() => {
if (!editor.hasNodes([FileNode])) {
Expand All @@ -19,7 +22,6 @@ export default function FilePlugin(): JSX.Element | null {
INSERT_FILE_COMMAND,
(payload) => {
const fileNode = $createFileNode(payload)
// $insertNodeToNearestRoot(fileNode)
$insertNodes([fileNode])
if ($isRootOrShadowRoot(fileNode.getParentOrThrow())) {
$wrapNodeInElement(fileNode, $createParagraphNode).selectEnd()
Expand All @@ -31,5 +33,16 @@ export default function FilePlugin(): JSX.Element | null {
)
}, [editor])

useEffect(() => {
const disposer = filesController.addEventObserver((event, data) => {
if (event === FilesControllerEvent.FileUploadedToNote) {
const fileUuid = data[FilesControllerEvent.FileUploadedToNote].uuid
editor.dispatchCommand(INSERT_FILE_COMMAND, fileUuid)
}
})

return disposer
}, [filesController, editor])

return null
}
Expand Up @@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react'
import { useApplication } from '@/Components/ApplicationView/ApplicationProvider'
import LinkedItemBubble from '@/Components/LinkedItems/LinkedItemBubble'
import { createLinkFromItem } from '@/Utils/Items/Search/createLinkFromItem'
import { useLinkingController } from '@/Components/BlockEditor/Contexts/LinkingControllerProvider'
import { useLinkingController } from '@/Controllers/LinkingControllerProvider'
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
import { useResponsiveAppPane } from '@/Components/ResponsivePane/ResponsivePaneProvider'
import { LexicalNode } from 'lexical'
Expand Down
Expand Up @@ -9,7 +9,7 @@ import { ContentType, SNNote } from '@standardnotes/snjs'
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
import Popover from '@/Components/Popover/Popover'
import { INSERT_BUBBLE_COMMAND, INSERT_FILE_COMMAND } from '../Commands'
import { useLinkingController } from '../../Contexts/LinkingControllerProvider'
import { useLinkingController } from '../../../../Controllers/LinkingControllerProvider'
import { PopoverClassNames } from '../ClassNames'

type Props = {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/javascripts/Components/NoteView/NoteView.tsx
Expand Up @@ -8,7 +8,6 @@ import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedIte
import { ElementIds } from '@/Constants/ElementIDs'
import { PrefDefaults } from '@/Constants/PrefDefaults'
import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings'
import { featureTrunkEnabled, FeatureTrunkName } from '@/FeatureTrunk'
import { log, LoggingDomain } from '@/Logging'
import { debounce, isDesktopApplication, isMobileScreen } from '@/Utils'
import { classNames } from '@/Utils/ConcatenateClassNames'
Expand Down Expand Up @@ -996,7 +995,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
const renderHeaderOptions = isMobileScreen() ? !this.state.plaintextEditorFocused : true

const editorMode =
featureTrunkEnabled(FeatureTrunkName.Blocks) && this.note.noteType === NoteType.Blocks
this.note.noteType === NoteType.Blocks
? 'blocks'
: this.state.editorStateDidLoad && !this.state.editorComponentViewer && !this.state.textareaUnloading
? 'plain'
Expand Down Expand Up @@ -1155,6 +1154,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
application={this.application}
note={this.note}
linkingController={this.viewControllerManager.linkingController}
filesController={this.viewControllerManager.filesController}
/>
</div>
)}
Expand Down
@@ -1,11 +1,14 @@
import { CrossControllerEvent } from '../CrossControllerEvent'
import { InternalEventBus, InternalEventPublishStrategy } from '@standardnotes/snjs'
import { InternalEventBus, InternalEventPublishStrategy, removeFromArray } from '@standardnotes/snjs'
import { WebApplication } from '../../Application/Application'
import { Disposer } from '@/Types/Disposer'

export abstract class AbstractViewController {
type ControllerEventObserver<Event = void, EventData = void> = (event: Event, data: EventData) => void

export abstract class AbstractViewController<Event = void, EventData = void> {
dealloced = false
protected disposers: Disposer[] = []
private eventObservers: ControllerEventObserver<Event, EventData>[] = []

constructor(public application: WebApplication, protected eventBus: InternalEventBus) {}

Expand All @@ -23,5 +26,19 @@ export abstract class AbstractViewController {
}

;(this.disposers as unknown) = undefined

this.eventObservers.length = 0
}

addEventObserver(observer: ControllerEventObserver<Event, EventData>): () => void {
this.eventObservers.push(observer)

return () => {
removeFromArray(this.eventObservers, observer)
}
}

notifyEvent(event: Event, data: EventData): void {
this.eventObservers.forEach((observer) => observer(event, data))
}
}
16 changes: 15 additions & 1 deletion packages/web/src/javascripts/Controllers/FilesController.ts
Expand Up @@ -34,7 +34,17 @@ const NonMutatingFileActions = [PopoverFileItemActionType.DownloadFile, PopoverF

type FileContextMenuLocation = { x: number; y: number }

export class FilesController extends AbstractViewController {
export type FilesControllerEventData = {
[FilesControllerEvent.FileUploadedToNote]: {
uuid: string
}
}

export enum FilesControllerEvent {
FileUploadedToNote,
}

export class FilesController extends AbstractViewController<FilesControllerEvent, FilesControllerEventData> {
allFiles: FileItem[] = []
attachedFiles: FileItem[] = []
showFileContextMenu = false
Expand Down Expand Up @@ -388,6 +398,10 @@ export class FilesController extends AbstractViewController {
type: ToastType.Success,
message: `Uploaded file "${uploadedFile.name}"`,
})

this.notifyEvent(FilesControllerEvent.FileUploadedToNote, {
[FilesControllerEvent.FileUploadedToNote]: { uuid: uploadedFile.uuid },
})
}

return uploadedFiles
Expand Down
@@ -0,0 +1,35 @@
import { ReactNode, createContext, useContext, memo } from 'react'
import { observer } from 'mobx-react-lite'
import { FilesController } from '@/Controllers/FilesController'

const FilesControllerContext = createContext<FilesController | undefined>(undefined)

export const useFilesController = () => {
const value = useContext(FilesControllerContext)

if (!value) {
throw new Error('Component must be a child of <FilesControllerProvider />')
}

return value
}

type ChildrenProps = {
children: ReactNode
}

type ProviderProps = {
controller: FilesController
} & ChildrenProps

const MemoizedChildren = memo(({ children }: ChildrenProps) => <>{children}</>)

const FilesControllerProvider = ({ controller, children }: ProviderProps) => {
return (
<FilesControllerContext.Provider value={controller}>
<MemoizedChildren children={children} />
</FilesControllerContext.Provider>
)
}

export default observer(FilesControllerProvider)
@@ -1,5 +1,4 @@
import { ReactNode, createContext, useContext, memo } from 'react'

import { observer } from 'mobx-react-lite'
import { LinkingController } from '@/Controllers/LinkingController'

Expand Down