refactor: repo (#1070)

This commit is contained in:
Mo
2022-06-07 07:18:41 -05:00
committed by GitHub
parent 4c65784421
commit f4ef63693c
1102 changed files with 5786 additions and 3366 deletions

View File

@@ -0,0 +1,122 @@
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER } from '@/Constants/Constants'
import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
import FileMenuOptions from './FileMenuOptions'
type Props = {
filesController: FilesController
selectionController: SelectedItemsController
}
const FileContextMenu: FunctionComponent<Props> = observer(({ filesController, selectionController }) => {
const { showFileContextMenu, setShowFileContextMenu, fileContextMenuLocation } = filesController
const [contextMenuStyle, setContextMenuStyle] = useState<React.CSSProperties>({
top: 0,
left: 0,
visibility: 'hidden',
})
const [contextMenuMaxHeight, setContextMenuMaxHeight] = useState<number | 'auto'>('auto')
const contextMenuRef = useRef<HTMLDivElement>(null)
const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) => setShowFileContextMenu(open))
useCloseOnClickOutside(contextMenuRef, () => filesController.setShowFileContextMenu(false))
const reloadContextMenuLayout = useCallback(() => {
const { clientHeight } = document.documentElement
const defaultFontSize = window.getComputedStyle(document.documentElement).fontSize
const maxContextMenuHeight = parseFloat(defaultFontSize) * MAX_MENU_SIZE_MULTIPLIER
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
const footerHeightInPx = footerElementRect?.height
let openUpBottom = true
if (footerHeightInPx) {
const bottomSpace = clientHeight - footerHeightInPx - fileContextMenuLocation.y
const upSpace = fileContextMenuLocation.y
if (maxContextMenuHeight > bottomSpace) {
if (upSpace > maxContextMenuHeight) {
openUpBottom = false
setContextMenuMaxHeight('auto')
} else {
if (upSpace > bottomSpace) {
setContextMenuMaxHeight(upSpace - MENU_MARGIN_FROM_APP_BORDER)
openUpBottom = false
} else {
setContextMenuMaxHeight(bottomSpace - MENU_MARGIN_FROM_APP_BORDER)
}
}
} else {
setContextMenuMaxHeight('auto')
}
}
if (openUpBottom) {
setContextMenuStyle({
top: fileContextMenuLocation.y,
left: fileContextMenuLocation.x,
visibility: 'visible',
})
} else {
setContextMenuStyle({
bottom: clientHeight - fileContextMenuLocation.y,
left: fileContextMenuLocation.x,
visibility: 'visible',
})
}
}, [fileContextMenuLocation.x, fileContextMenuLocation.y])
useEffect(() => {
if (showFileContextMenu) {
reloadContextMenuLayout()
}
}, [reloadContextMenuLayout, showFileContextMenu])
useEffect(() => {
window.addEventListener('resize', reloadContextMenuLayout)
return () => {
window.removeEventListener('resize', reloadContextMenuLayout)
}
}, [reloadContextMenuLayout])
return (
<div
ref={contextMenuRef}
className="sn-dropdown min-w-60 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed"
style={{
...contextMenuStyle,
maxHeight: contextMenuMaxHeight,
}}
>
<FileMenuOptions
filesController={filesController}
selectionController={selectionController}
closeOnBlur={closeOnBlur}
closeMenu={() => setShowFileContextMenu(false)}
shouldShowRenameOption={false}
shouldShowAttachOption={false}
/>
</div>
)
})
FileContextMenu.displayName = 'FileContextMenu'
const FileContextMenuWrapper: FunctionComponent<Props> = ({ filesController, selectionController }) => {
const { showFileContextMenu } = filesController
const { selectedFiles } = selectionController
const selectedFile = selectedFiles[0]
if (!showFileContextMenu || !selectedFile) {
return null
}
return <FileContextMenu filesController={filesController} selectionController={selectionController} />
}
export default observer(FileContextMenuWrapper)

View File

@@ -0,0 +1,148 @@
import { FunctionComponent, useCallback, useMemo } from 'react'
import { PopoverFileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import Icon from '@/Components/Icon/Icon'
import Switch from '@/Components/Switch/Switch'
import { observer } from 'mobx-react-lite'
import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
type Props = {
closeMenu: () => void
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
filesController: FilesController
selectionController: SelectedItemsController
isFileAttachedToNote?: boolean
renameToggleCallback?: (isRenamingFile: boolean) => void
shouldShowRenameOption: boolean
shouldShowAttachOption: boolean
}
const FileMenuOptions: FunctionComponent<Props> = ({
closeMenu,
closeOnBlur,
filesController,
selectionController,
isFileAttachedToNote,
renameToggleCallback,
shouldShowRenameOption,
shouldShowAttachOption,
}) => {
const { selectedFiles } = selectionController
const { handleFileAction } = filesController
const hasProtectedFiles = useMemo(() => selectedFiles.some((file) => file.protected), [selectedFiles])
const onPreview = useCallback(() => {
void handleFileAction({
type: PopoverFileItemActionType.PreviewFile,
payload: {
file: selectedFiles[0],
otherFiles: selectedFiles.length > 1 ? selectedFiles : filesController.allFiles,
},
})
closeMenu()
}, [closeMenu, filesController.allFiles, handleFileAction, selectedFiles])
const onDetach = useCallback(() => {
const file = selectedFiles[0]
void handleFileAction({
type: PopoverFileItemActionType.DetachFileToNote,
payload: { file },
})
closeMenu()
}, [closeMenu, handleFileAction, selectedFiles])
const onAttach = useCallback(() => {
const file = selectedFiles[0]
void handleFileAction({
type: PopoverFileItemActionType.AttachFileToNote,
payload: { file },
})
closeMenu()
}, [closeMenu, handleFileAction, selectedFiles])
return (
<>
<button onBlur={closeOnBlur} className="sn-dropdown-item focus:bg-info-backdrop" onClick={onPreview}>
<Icon type="file" className="mr-2 color-neutral" />
Preview file
</button>
{selectedFiles.length === 1 && (
<>
{isFileAttachedToNote ? (
<button onBlur={closeOnBlur} className="sn-dropdown-item focus:bg-info-backdrop" onClick={onDetach}>
<Icon type="link-off" className="mr-2 color-neutral" />
Detach from note
</button>
) : shouldShowAttachOption ? (
<button onBlur={closeOnBlur} className="sn-dropdown-item focus:bg-info-backdrop" onClick={onAttach}>
<Icon type="link" className="mr-2 color-neutral" />
Attach to note
</button>
) : null}
</>
)}
<div className="min-h-1px my-1 bg-border"></div>
<button
className="sn-dropdown-item justify-between focus:bg-info-backdrop"
onClick={() => {
void filesController.setProtectionForFiles(!hasProtectedFiles, selectionController.selectedFiles)
}}
onBlur={closeOnBlur}
>
<span className="flex items-center">
<Icon type="password" className="mr-2 color-neutral" />
Password protection
</span>
<Switch
className="px-0 pointer-events-none"
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
checked={hasProtectedFiles}
/>
</button>
<div className="min-h-1px my-1 bg-border"></div>
<button
onBlur={closeOnBlur}
className="sn-dropdown-item focus:bg-info-backdrop"
onClick={() => {
void filesController.downloadFiles(selectionController.selectedFiles)
}}
>
<Icon type="download" className="mr-2 color-neutral" />
Download
</button>
{shouldShowRenameOption && (
<button
onBlur={closeOnBlur}
className="sn-dropdown-item focus:bg-info-backdrop"
onClick={() => {
renameToggleCallback?.(true)
}}
>
<Icon type="pencil" className="mr-2 color-neutral" />
Rename
</button>
)}
<button
onBlur={closeOnBlur}
className="sn-dropdown-item focus:bg-info-backdrop"
onClick={() => {
void filesController.deleteFilesPermanently(selectionController.selectedFiles)
}}
>
<Icon type="trash" className="mr-2 color-danger" />
<span className="color-danger">Delete permanently</span>
</button>
{selectedFiles.length === 1 && (
<div className="px-3 pt-1.5 pb-0.5 text-xs color-neutral font-medium">
<div>
<span className="font-semibold">File ID:</span> {selectedFiles[0].uuid}
</div>
</div>
)}
</>
)
}
export default observer(FileMenuOptions)

View File

@@ -0,0 +1,93 @@
import Icon from '@/Components/Icon/Icon'
import VisuallyHidden from '@reach/visually-hidden'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
import { useCallback, useRef, useState } from 'react'
import { observer } from 'mobx-react-lite'
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import FileMenuOptions from './FileMenuOptions'
import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
type Props = {
filesController: FilesController
selectionController: SelectedItemsController
}
const FilesOptionsPanel = ({ filesController, selectionController }: Props) => {
const [open, setOpen] = useState(false)
const [position, setPosition] = useState({
top: 0,
right: 0,
})
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto')
const buttonRef = useRef<HTMLButtonElement>(null)
const panelRef = useRef<HTMLDivElement>(null)
const [closeOnBlur] = useCloseOnBlur(panelRef, setOpen)
const onDisclosureChange = useCallback(async () => {
const rect = buttonRef.current?.getBoundingClientRect()
if (rect) {
const { clientHeight } = document.documentElement
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
const footerHeightInPx = footerElementRect?.height
if (footerHeightInPx) {
setMaxHeight(clientHeight - rect.bottom - footerHeightInPx - 2)
}
setPosition({
top: rect.bottom,
right: document.body.clientWidth - rect.right,
})
setOpen((open) => !open)
}
}, [])
return (
<Disclosure open={open} onChange={onDisclosureChange}>
<DisclosureButton
onKeyDown={(event) => {
if (event.key === 'Escape') {
setOpen(false)
}
}}
onBlur={closeOnBlur}
ref={buttonRef}
className="sn-icon-button border-contrast"
>
<VisuallyHidden>Actions</VisuallyHidden>
<Icon type="more" className="block" />
</DisclosureButton>
<DisclosurePanel
onKeyDown={(event) => {
if (event.key === 'Escape') {
setOpen(false)
buttonRef.current?.focus()
}
}}
ref={panelRef}
style={{
...position,
maxHeight,
}}
className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed"
onBlur={closeOnBlur}
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
>
{open && (
<FileMenuOptions
filesController={filesController}
selectionController={selectionController}
closeOnBlur={closeOnBlur}
closeMenu={() => {
setOpen(false)
}}
shouldShowAttachOption={false}
shouldShowRenameOption={false}
/>
)}
</DisclosurePanel>
</Disclosure>
)
}
export default observer(FilesOptionsPanel)