image preview as gallery
This commit is contained in:
parent
40ac051d33
commit
d437402577
|
@ -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
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
module.exports = {
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
domains: ['public.dm.files.1drv.com'],
|
||||
},
|
||||
}
|
||||
|
|
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
57
utils/getPreviewType.ts
Normal 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 }
|
Loading…
Reference in a new issue