diff --git a/components/FileListing.tsx b/components/FileListing.tsx index f1d7fd0..67b9b9b 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -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 ( +
+
+ {/*
{c.file ? c.file.mimeType : 'folder'}
*/} +
+ +
+
{c.name}
+
+
+ {new Date(c.lastModifiedDateTime).toLocaleString(undefined, { + dateStyle: 'short', + timeStyle: 'short', + })} +
+
{humanFileSize(c.size)}
+
+ ) +} + 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
Failed to load
- if (!data) + if (!data) { return (
= ({ query }) =
Loading
) + } - const { - files: { children }, - } = data - return ( -
-
-
Name
-
Created
-
Size
-
- {children.map((c: any) => ( - -
-
- {/*
{c.file ? c.file.mimeType : 'folder'}
*/} -
- -
-
{c.name}
-
-
- {new Date(c.createdDateTime).toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'short' })} -
-
{humanFileSize(c.size)}
+ 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 ( +
+
+
Name
+
Last Modified
+
Size
+
+ + {imagesInFolder.length !== 0 && ( + { + setImageViewerVisibility(false) + }} + /> + )} + + {children.map((c: any) => ( +
{ + if (!c.folder && fileIsImage(c.name)) { + setActiveImageIdx(imageIndexDict[c.id]) + setImageViewerVisibility(true) + } else { + e.preventDefault() + router.push(`${path === '/' ? '' : path}/${c.name}`) + } + }} + > +
- - ))} -
- ) + ))} +
+ ) + } + + 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 ( +
+
+
{downloadUrl}
+
+
+ ) + + default: + return
{fileName}
+ } + } + } + + return
404
} export default FileListing diff --git a/next.config.js b/next.config.js index 0d60710..d5e534b 100644 --- a/next.config.js +++ b/next.config.js @@ -1,3 +1,6 @@ module.exports = { reactStrictMode: true, + images: { + domains: ['public.dm.files.1drv.com'], + }, } diff --git a/package-lock.json b/package-lock.json index 3fa5a22..7b62d29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 9512b0b..f7c9394 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/pages/api/index.ts b/pages/api/index.ts index 2c692db..62032ea 100644 --- a/pages/api/index.ts +++ b/pages/api/index.ts @@ -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 } diff --git a/utils/getFileIcon.ts b/utils/getFileIcon.ts index 4013659..e181366 100644 --- a/utils/getFileIcon.ts +++ b/utils/getFileIcon.ts @@ -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 = (obj: O, key: PropertyKey): key is keyof O => { +export const hasKey = (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 } diff --git a/utils/getPreviewType.ts b/utils/getPreviewType.ts new file mode 100644 index 0000000..5169dc1 --- /dev/null +++ b/utils/getPreviewType.ts @@ -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 }