import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import toast, { Toaster } from 'react-hot-toast'
import emojiRegex from 'emoji-regex'
import { useClipboard } from 'use-clipboard-copy'
import { ParsedUrlQuery } from 'querystring'
import { FunctionComponent, useEffect, useRef, useState } from 'react'
import { ImageDecorator } from 'react-viewer/lib/ViewerProps'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import { getExtension, getFileIcon, hasKey } from '../utils/getFileIcon'
import { extensions, preview } from '../utils/getPreviewType'
import { getBaseUrl, useProtectedSWRInfinite } from '../utils/tools'
import { VideoPreview } from './previews/VideoPreview'
import { AudioPreview } from './previews/AudioPreview'
import Loading from './Loading'
import FourOhFour from './FourOhFour'
import Auth from './Auth'
import TextPreview from './previews/TextPreview'
import MarkdownPreview from './previews/MarkdownPreview'
import CodePreview from './previews/CodePreview'
import OfficePreview from './previews/OfficePreview'
import DownloadBtn from './DownloadBtn'
// Disabling SSR for some previews (image gallery view, and PDF view)
const ReactViewer = dynamic(() => import('react-viewer'), { ssr: false })
const PDFPreview = dynamic(() => import('./previews/PDFPreview'), { ssr: false })
const EPUBPreview = dynamic(() => import('./previews/EPUBPreview'), { ssr: false })
/**
* Convert raw bits file/folder size into a human readable string
*
* @param size File or folder size, in raw bits
* @returns Human readable form of the file or folder size
*/
const humanFileSize = (size: number) => {
if (size < 1024) return size + ' B'
const i = Math.floor(Math.log(size) / Math.log(1024))
const num = size / Math.pow(1024, i)
const round = Math.round(num)
const formatted = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round
return `${formatted} ${'KMGTPEZY'[i - 1]}B`
}
/**
* Convert url query into path string
*
* @param query Url query property
* @returns Path string
*/
const queryToPath = (query?: ParsedUrlQuery) => {
if (query) {
const { path } = query
if (!path) return '/'
if (typeof path === 'string') return `/${encodeURIComponent(path)}`
return `/${path.map(p => encodeURIComponent(p)).join('/')}`
}
return '/'
}
const FileListItem: FunctionComponent<{
fileContent: { id: string; name: string; size: number; file: Object; lastModifiedDateTime: string }
}> = ({ fileContent: c }) => {
const emojiIcon = emojiRegex().exec(c.name)
const renderEmoji = emojiIcon && !emojiIcon.index
return (
{/*
{c.file ? c.file.mimeType : 'folder'}
*/}
{renderEmoji ? (
{emojiIcon ? emojiIcon[0] : '📁'}
) : (
)}
{renderEmoji ? c.name.replace(emojiIcon ? emojiIcon[0] : '', '').trim() : c.name}
{new Date(c.lastModifiedDateTime).toLocaleString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
})}
{humanFileSize(c.size)}
)
}
const Checkbox: FunctionComponent<{
checked: 0|1|2, onChange: () => void, title: string, indeterminate?: boolean
}> = ({ checked, onChange, title, indeterminate }) => {
const ref = useRef(null)
useEffect(() => {
if (ref.current) {
ref.current.checked = Boolean(checked)
if (indeterminate) {
ref.current.indeterminate = checked == 1
}
}
}, [ref, checked, indeterminate])
const handleClick = () => { ref.current ? ref.current.click() : null }
return (
)
}
const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) => {
const [imageViewerVisible, setImageViewerVisibility] = useState(false)
const [activeImageIdx, setActiveImageIdx] = useState(0)
const [selected, setSelected] = useState<{[key: string]: boolean}>({})
const [totalSelected, setTotalSelected] = useState<0|1|2>(0)
const router = useRouter()
const clipboard = useClipboard()
const path = queryToPath(query)
const { data, error, size, setSize } = useProtectedSWRInfinite(path)
if (error) {
return (
{error.message.includes('401') ?
:
}
)
}
if (!data) {
return (
)
}
const fileIsImage = (fileName: string) => {
const fileExtension = getExtension(fileName)
if (hasKey(extensions, fileExtension)) {
if (extensions[fileExtension] === preview.image) {
return true
}
}
return false
}
const responses: any[] = data ? [].concat(...data) : []
const isLoadingInitialData = !data && !error
const isLoadingMore = isLoadingInitialData || (size > 0 && data && typeof data[size - 1] === 'undefined')
const isEmpty = data?.[0]?.length === 0
const isReachingEnd = isEmpty || (data && typeof data[data.length - 1]?.next === 'undefined')
const onlyOnePage = data && typeof data[0].next === 'undefined'
if ('folder' in responses[0]) {
// Image preview rendering preparations
const imagesInFolder: ImageDecorator[] = []
const imageIndexDict: { [key: string]: number } = {}
let imageIndex = 0
// README rendering preparations
let renderReadme = false
let readmeFile = null
// Expand list of API returns into flattened file data
const children = [].concat(...responses.map(r => r.folder.value))
children.forEach((c: any) => {
if (fileIsImage(c.name)) {
imagesInFolder.push({
src: c['@microsoft.graph.downloadUrl'],
alt: c.name,
downloadUrl: c['@microsoft.graph.downloadUrl'],
})
imageIndexDict[c.id] = imageIndex
imageIndex += 1
}
if (c.name.toLowerCase() === 'readme.md') {
renderReadme = true
readmeFile = c
}
})
// File selection
const genTotalSelected = (selected: {[key: string]: boolean}) => {
const selectInfo = children.map((c :any) => Boolean(selected[c.id]))
const [hasT, hasF] = [selectInfo.some(i => i), selectInfo.some(i => !i)]
console.log(hasT, hasF)
return hasT && hasF ? 1 : (!hasF ? 2 : 0)
}
const toggleItemSelected = (id: string) => {
let val
if (selected[id]) {
val = {...selected}
delete val[id]
} else {
val = {...selected, [id]: true}
}
setSelected(val)
setTotalSelected(genTotalSelected(val))
}
const toggleTotalSelected = () => {
if (genTotalSelected(selected) == 2) {
setSelected({})
setTotalSelected(0)
} else {
setSelected(Object.fromEntries(children.map((c :any) => [c.id, true])))
setTotalSelected(2)
}
}
return (
Name
Last Modified
Size
Actions
{imagesInFolder.length !== 0 && (
{
setImageViewerVisibility(false)
}}
customToolbar={toolbars => {
toolbars[0].render =
toolbars[1].render =
toolbars[2].render =
toolbars[3].render =
toolbars[4].render =
toolbars[9].render =
return toolbars.concat([
{
key: 'copy',
render: ,
onClick: i => {
clipboard.copy(i.alt ? `${getBaseUrl()}/api?path=${path + '/' + i.alt}&raw=true` : '')
toast.success('Copied image permanent link to clipboard.')
},
},
])
}}
/>
)}
{children.map((c: any) => (
{
e.preventDefault()
if (!c.folder && fileIsImage(c.name)) {
setActiveImageIdx(imageIndexDict[c.id])
setImageViewerVisibility(true)
} else {
router.push(`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`)
}
}}
>
{c.folder ? (
{
clipboard.copy(`${getBaseUrl()}${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`)
toast.success('Copied folder permalink.')
}}
>
) : (
{
clipboard.copy(`${getBaseUrl()}/api?path=${path === '/' ? '' : path}/${c.name}&raw=true`)
toast.success('Copied raw file permalink.')
}}
>
)}
toggleItemSelected(c.id)}
title={"Select file"}
/>
))}
{!onlyOnePage && (
- showing {size} page{size > 1 ? 's' : ''} of {isLoadingMore ? '...' : children.length} files -
setSize(size + 1)}
disabled={isLoadingMore || isReachingEnd}
>
{isLoadingMore ? (
<>
Loading ... {' '}
>
) : isReachingEnd ? (
No more files
) : (
<>
Load more
>
)}
)}
{renderReadme && (
)}
)
}
if ('file' in responses[0] && responses.length === 1) {
const { file } = responses[0]
const downloadUrl = file['@microsoft.graph.downloadUrl']
const fileName = file.name
const fileExtension = fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2).toLowerCase()
if (hasKey(extensions, fileExtension)) {
switch (extensions[fileExtension]) {
case preview.image:
return (
{/* eslint-disable-next-line @next/next/no-img-element */}
)
case preview.text:
return
case preview.code:
return
case preview.markdown:
return
case preview.video:
return
case preview.audio:
return
case preview.pdf:
return
case preview.office:
return
case preview.epub:
return
default:
return {fileName}
}
}
return (
<>
>
)
}
return (
)
}
export default FileListing