image preview as gallery

This commit is contained in:
spencerwooo 2021-06-23 23:51:23 +01:00
parent 40ac051d33
commit d437402577
No known key found for this signature in database
GPG key ID: 24CD550268849CA0
7 changed files with 232 additions and 40 deletions

View file

@ -2,12 +2,25 @@ import axios from 'axios'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { ParsedUrlQuery } from 'querystring'
import { FunctionComponent } from 'react'
import { FunctionComponent, useState } from 'react'
import useSWR from 'swr'
import Link from 'next/link'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import getFileIcon from '../utils/getFileIcon'
import { getExtension, getFileIcon, hasKey } from '../utils/getFileIcon'
import { extensions, preview } from '../utils/getPreviewType'
import { ImageDecorator } from 'react-viewer/lib/ViewerProps'
// View images as gallery
const ReactViewer = dynamic(() => import('react-viewer'), { 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))
@ -29,11 +42,39 @@ const queryToPath = (query?: ParsedUrlQuery) => {
const fetcher = (url: string) => axios.get(url).then(res => res.data)
const FileListItem: FunctionComponent<{
fileContent: { id: string; name: string; size: number; file: Object; lastModifiedDateTime: string }
}> = ({ fileContent: c }) => {
return (
<div className="p-3 grid grid-cols-10 items-center space-x-2 cursor-pointer hover:bg-gray-100">
<div className="flex space-x-2 items-center col-span-7 truncate">
{/* <div>{c.file ? c.file.mimeType : 'folder'}</div> */}
<div className="w-5 text-center">
<FontAwesomeIcon icon={c.file ? getFileIcon(c.name) : ['far', 'folder']} />
</div>
<div className="truncate">{c.name}</div>
</div>
<div className="font-mono text-sm col-span-2 text-gray-700">
{new Date(c.lastModifiedDateTime).toLocaleString(undefined, {
dateStyle: 'short',
timeStyle: 'short',
})}
</div>
<div className="font-mono text-sm text-gray-700">{humanFileSize(c.size)}</div>
</div>
)
}
const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) => {
const [imageViewerVisible, setImageViewerVisibility] = useState(false)
const [activeImageIdx, setActiveImageIdx] = useState(0)
const router = useRouter()
const path = queryToPath(query)
const { data, error } = useSWR(`/api?path=${path}`, fetcher)
if (error) return <div>Failed to load</div>
if (!data)
if (!data) {
return (
<div className="flex items-center justify-center bg-white shadow rounded py-32 space-x-1">
<svg
@ -52,36 +93,103 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
<div>Loading</div>
</div>
)
}
const {
files: { children },
} = data
return (
<div className="bg-white shadow rounded">
<div className="p-3 grid grid-cols-10 items-center space-x-2 border-b border-gray-200">
<div className="col-span-7 font-bold">Name</div>
<div className="font-bold col-span-2">Created</div>
<div className="font-bold">Size</div>
</div>
{children.map((c: any) => (
<Link href={`${path === '/' ? '' : path}/${c.name}`} key={c.id} passHref>
<div className="p-3 grid grid-cols-10 items-center space-x-2 cursor-pointer hover:bg-gray-100">
<div className="flex space-x-2 items-center col-span-7 truncate">
{/* <div>{c.file ? c.file.mimeType : 'folder'}</div> */}
<div className="w-5 text-center">
<FontAwesomeIcon icon={c.file ? getFileIcon(c.name) : ['far', 'folder']} />
</div>
<div className="truncate">{c.name}</div>
</div>
<div className="font-mono text-sm col-span-2 text-gray-700">
{new Date(c.createdDateTime).toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'short' })}
</div>
<div className="font-mono text-sm text-gray-700">{humanFileSize(c.size)}</div>
const resp = data.data
const fileIsImage = (fileName: string) => {
const fileExtension = getExtension(fileName)
if (hasKey(extensions, fileExtension)) {
if (extensions[fileExtension] === preview.image) {
return true
}
}
return false
}
if ('folder' in resp) {
const { children } = resp
const imagesInFolder: ImageDecorator[] = []
const imageIndexDict: { [key: string]: number } = {}
let imageIndex = 0
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
}
})
return (
<div className="bg-white shadow rounded">
<div className="p-3 grid grid-cols-10 items-center space-x-2 border-b border-gray-200">
<div className="col-span-7 font-bold">Name</div>
<div className="font-bold col-span-2">Last Modified</div>
<div className="font-bold">Size</div>
</div>
{imagesInFolder.length !== 0 && (
<ReactViewer
visible={imageViewerVisible}
activeIndex={activeImageIdx}
images={imagesInFolder}
drag={false}
rotatable={false}
noClose={true}
downloadable={true}
downloadInNewWindow={true}
onMaskClick={() => {
setImageViewerVisibility(false)
}}
/>
)}
{children.map((c: any) => (
<div
key={c.id}
onClick={e => {
if (!c.folder && fileIsImage(c.name)) {
setActiveImageIdx(imageIndexDict[c.id])
setImageViewerVisibility(true)
} else {
e.preventDefault()
router.push(`${path === '/' ? '' : path}/${c.name}`)
}
}}
>
<FileListItem fileContent={c} />
</div>
</Link>
))}
</div>
)
))}
</div>
)
}
if ('file' in resp) {
const downloadUrl = resp['@microsoft.graph.downloadUrl']
const fileName = resp.name
const fileExtension = fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2).toLowerCase()
if (hasKey(extensions, fileExtension)) {
switch (extensions[fileExtension]) {
case preview.image:
return (
<div className="bg-white shadow rounded">
<div className="p-3 text-center">
<div>{downloadUrl}</div>
</div>
</div>
)
default:
return <div className="bg-white shadow rounded">{fileName}</div>
}
}
}
return <div>404</div>
}
export default FileListing

View file

@ -1,3 +1,6 @@
module.exports = {
reactStrictMode: true,
images: {
domains: ['public.dm.files.1drv.com'],
},
}

17
package-lock.json generated
View file

@ -15,6 +15,7 @@
"next": "11.0.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-viewer": "^3.2.2",
"swr": "^0.5.6"
},
"devDependencies": {
@ -4647,6 +4648,14 @@
"node": ">=0.10.0"
}
},
"node_modules/react-viewer": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/react-viewer/-/react-viewer-3.2.2.tgz",
"integrity": "sha512-DHOq1x6cXsAViY43204ILRzLVR5ovP1MgzsC+LzZCWlInRuHjzAgpQZ8GzWm1CkiNYuHGwCxH36X0JUHl2xDSg==",
"dependencies": {
"classnames": "^2.2.5"
}
},
"node_modules/read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@ -9472,6 +9481,14 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
"integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg=="
},
"react-viewer": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/react-viewer/-/react-viewer-3.2.2.tgz",
"integrity": "sha512-DHOq1x6cXsAViY43204ILRzLVR5ovP1MgzsC+LzZCWlInRuHjzAgpQZ8GzWm1CkiNYuHGwCxH36X0JUHl2xDSg==",
"requires": {
"classnames": "^2.2.5"
}
},
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",

View file

@ -17,6 +17,7 @@
"next": "11.0.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-viewer": "^3.2.2",
"swr": "^0.5.6"
},
"devDependencies": {

View file

@ -47,15 +47,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (typeof path === 'string') {
const accessToken = await getAccessToken()
const requestUrl = `${apiConfig.driveApi}/root${encodePath(path)}`
const files = await axios.get(requestUrl, {
const { data } = await axios.get(requestUrl, {
headers: { Authorization: `Bearer ${accessToken}` },
params: {
select: 'name,size,id,createdDateTime',
expand: 'children(select=name,createdDateTime,eTag,size,id,file)',
select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file',
expand: 'children(select=@content.downloadUrl,name,lastModifiedDateTime,eTag,size,id,folder,file)',
},
})
res.status(200).json({ path, files: files.data })
res.status(200).json({ path, data })
return
}

View file

@ -1,4 +1,6 @@
const icons = {
import { IconPrefix, IconName } from '@fortawesome/fontawesome-common-types'
const icons: { [key: string]: [IconPrefix, IconName] } = {
image: ['far', 'file-image'],
pdf: ['far', 'file-pdf'],
word: ['far', 'file-word'],
@ -67,11 +69,15 @@ const extensions = {
* @param key The index key
* @returns Whether or not the key exists inside the object
*/
const hasKey = <O>(obj: O, key: PropertyKey): key is keyof O => {
export const hasKey = <O>(obj: O, key: PropertyKey): key is keyof O => {
return key in obj
}
export default function getFileIcon(fileName: string) {
const extension = fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2).toLowerCase()
export const getExtension = (fileName: string) => {
return fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2).toLowerCase()
}
export const getFileIcon = (fileName: string) => {
const extension = getExtension(fileName)
return hasKey(extensions, extension) ? extensions[extension] : icons.file
}

57
utils/getPreviewType.ts Normal file
View file

@ -0,0 +1,57 @@
const preview = {
markdown: 'markdown',
image: 'image',
text: 'text',
pdf: 'pdf',
code: 'code',
video: 'video',
audio: 'audio',
}
const extensions = {
gif: preview.image,
jpeg: preview.image,
jpg: preview.image,
png: preview.image,
webp: preview.image,
md: preview.markdown,
markdown: preview.markdown,
mdown: preview.markdown,
pdf: preview.pdf,
c: preview.code,
cpp: preview.code,
js: preview.code,
java: preview.code,
sh: preview.code,
cs: preview.code,
py: preview.code,
css: preview.code,
html: preview.code,
ts: preview.code,
vue: preview.code,
json: preview.code,
yaml: preview.code,
toml: preview.code,
txt: preview.text,
mp4: preview.video,
flv: preview.video,
webm: preview.video,
m3u8: preview.video,
mkv: preview.video,
mp3: preview.audio,
m4a: preview.audio,
aac: preview.audio,
wav: preview.audio,
ogg: preview.audio,
oga: preview.audio,
opus: preview.audio,
flac: preview.audio,
}
export { extensions, preview }