feat: generic items list (#1035)

This commit is contained in:
Aman Harwara
2022-05-22 18:51:46 +05:30
committed by GitHub
parent 1643311d08
commit 6401da2570
76 changed files with 1808 additions and 1281 deletions

View File

@@ -1,4 +1,10 @@
import {
PopoverFileItemAction,
PopoverFileItemActionType,
} from '@/Components/AttachedFilesPopover/PopoverFileItemAction'
import { PopoverTabs } from '@/Components/AttachedFilesPopover/PopoverTabs'
import { BYTES_IN_ONE_MEGABYTE } from '@/Constants'
import { confirmDialog } from '@/Services/AlertService'
import { concatenateUint8Arrays } from '@/Utils/ConcatenateUint8Arrays'
import {
ClassicFileReader,
@@ -7,11 +13,202 @@ import {
ClassicFileSaver,
parseFileName,
} from '@standardnotes/filepicker'
import { ClientDisplayableError, FileItem } from '@standardnotes/snjs'
import { ChallengeReason, ClientDisplayableError, ContentType, FileItem } from '@standardnotes/snjs'
import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/stylekit'
import { action, computed, makeObservable, observable, reaction } from 'mobx'
import { WebApplication } from '../Application'
import { AbstractState } from './AbstractState'
import { AppState } from './AppState'
const UnprotectedFileActions = [PopoverFileItemActionType.ToggleFileProtection]
const NonMutatingFileActions = [PopoverFileItemActionType.DownloadFile, PopoverFileItemActionType.PreviewFile]
type FileContextMenuLocation = { x: number; y: number }
export class FilesState extends AbstractState {
allFiles: FileItem[] = []
attachedFiles: FileItem[] = []
showFileContextMenu = false
fileContextMenuLocation: FileContextMenuLocation = { x: 0, y: 0 }
constructor(application: WebApplication, override appState: AppState, appObservers: (() => void)[]) {
super(application, appState)
makeObservable(this, {
allFiles: observable,
attachedFiles: observable,
showFileContextMenu: observable,
fileContextMenuLocation: observable,
selectedFiles: computed,
reloadAllFiles: action,
reloadAttachedFiles: action,
setShowFileContextMenu: action,
setFileContextMenuLocation: action,
})
appObservers.push(
application.streamItems(ContentType.File, () => {
this.reloadAllFiles()
this.reloadAttachedFiles()
}),
reaction(
() => appState.notes.selectedNotes,
() => {
this.reloadAttachedFiles()
},
),
)
}
get selectedFiles() {
return this.appState.selectedItems.getSelectedItems<FileItem>(ContentType.File)
}
setShowFileContextMenu = (enabled: boolean) => {
this.showFileContextMenu = enabled
}
setFileContextMenuLocation = (location: FileContextMenuLocation) => {
this.fileContextMenuLocation = location
}
reloadAllFiles = () => {
this.allFiles = this.application.items.getDisplayableFiles()
}
reloadAttachedFiles = () => {
const note = this.appState.notes.firstSelectedNote
if (note) {
this.attachedFiles = this.application.items.getFilesForNote(note)
}
}
deleteFile = async (file: FileItem) => {
const shouldDelete = await confirmDialog({
text: `Are you sure you want to permanently delete "${file.name}"?`,
confirmButtonStyle: 'danger',
})
if (shouldDelete) {
const deletingToastId = addToast({
type: ToastType.Loading,
message: `Deleting file "${file.name}"...`,
})
await this.application.files.deleteFile(file)
addToast({
type: ToastType.Success,
message: `Deleted file "${file.name}"`,
})
dismissToast(deletingToastId)
}
}
attachFileToNote = async (file: FileItem) => {
const note = this.appState.notes.firstSelectedNote
if (!note) {
addToast({
type: ToastType.Error,
message: 'Could not attach file because selected note was deleted',
})
return
}
await this.application.items.associateFileWithNote(file, note)
}
detachFileFromNote = async (file: FileItem) => {
const note = this.appState.notes.firstSelectedNote
if (!note) {
addToast({
type: ToastType.Error,
message: 'Could not attach file because selected note was deleted',
})
return
}
await this.application.items.disassociateFileWithNote(file, note)
}
toggleFileProtection = async (file: FileItem) => {
let result: FileItem | undefined
if (file.protected) {
result = await this.application.mutator.unprotectFile(file)
} else {
result = await this.application.mutator.protectFile(file)
}
const isProtected = result ? result.protected : file.protected
return isProtected
}
authorizeProtectedActionForFile = async (file: FileItem, challengeReason: ChallengeReason) => {
const authorizedFiles = await this.application.protections.authorizeProtectedActionForItems([file], challengeReason)
const isAuthorized = authorizedFiles.length > 0 && authorizedFiles.includes(file)
return isAuthorized
}
renameFile = async (file: FileItem, fileName: string) => {
await this.application.items.renameFile(file, fileName)
}
handleFileAction = async (
action: PopoverFileItemAction,
currentTab: PopoverTabs,
): Promise<{
didHandleAction: boolean
}> => {
const file = action.type !== PopoverFileItemActionType.RenameFile ? action.payload : action.payload.file
let isAuthorizedForAction = true
const requiresAuthorization = file.protected && !UnprotectedFileActions.includes(action.type)
if (requiresAuthorization) {
isAuthorizedForAction = await this.authorizeProtectedActionForFile(file, ChallengeReason.AccessProtectedFile)
}
if (!isAuthorizedForAction) {
return {
didHandleAction: false,
}
}
switch (action.type) {
case PopoverFileItemActionType.AttachFileToNote:
await this.attachFileToNote(file)
break
case PopoverFileItemActionType.DetachFileToNote:
await this.detachFileFromNote(file)
break
case PopoverFileItemActionType.DeleteFile:
await this.deleteFile(file)
break
case PopoverFileItemActionType.DownloadFile:
await this.downloadFile(file)
break
case PopoverFileItemActionType.ToggleFileProtection: {
const isProtected = await this.toggleFileProtection(file)
action.callback(isProtected)
break
}
case PopoverFileItemActionType.RenameFile:
await this.renameFile(file, action.payload.name)
break
case PopoverFileItemActionType.PreviewFile:
this.appState.filePreviewModal.activate(
file,
currentTab === PopoverTabs.AllFiles ? this.allFiles : this.attachedFiles,
)
break
}
if (!NonMutatingFileActions.includes(action.type)) {
this.application.sync.sync().catch(console.error)
}
return {
didHandleAction: true,
}
}
public async downloadFile(file: FileItem): Promise<void> {
let downloadingToastId = ''