Merge pull request #236 from spencerwooo/flat-design-no-shadow

This commit is contained in:
Spencer Woo 2022-01-06 21:21:28 +08:00 committed by GitHub
commit b715e68240
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 7872 additions and 388 deletions

View file

@ -2,12 +2,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Image from 'next/image' import Image from 'next/image'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { FunctionComponent, useState } from 'react' import { FC, useState } from 'react'
import { matchProtectedRoute } from '../utils/protectedRouteHandler' import { matchProtectedRoute } from '../utils/protectedRouteHandler'
import useLocalStorage from '../utils/useLocalStorage' import useLocalStorage from '../utils/useLocalStorage'
const Auth: FunctionComponent<{ redirect: string }> = ({ redirect }) => { const Auth: FC<{ redirect: string }> = ({ redirect }) => {
const authTokenPath = matchProtectedRoute(redirect) const authTokenPath = matchProtectedRoute(redirect)
const router = useRouter() const router = useRouter()

View file

@ -1,21 +1,20 @@
import Link from 'next/link' import Link from 'next/link'
import { ParsedUrlQuery } from 'querystring' import { ParsedUrlQuery } from 'querystring'
import { FunctionComponent } from 'react'
const Breadcrumb: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) => { const Breadcrumb: React.FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
if (query) { if (query) {
const { path } = query const { path } = query
if (Array.isArray(path)) { if (Array.isArray(path)) {
return ( return (
<div className="dark:text-gray-300 no-scrollbar flex pb-4 overflow-x-scroll text-sm text-gray-600"> <div className="dark:text-gray-300 no-scrollbar flex pb-4 overflow-x-scroll text-sm text-gray-600 -mx-1">
<div className="hover:opacity-80 flex-shrink-0 p-1 transition-all duration-75"> <div className="hover:opacity-80 flex-shrink-0 px-1 transition-all duration-75">
<Link href="/">🚩 Home</Link> <Link href="/">🚩 Home</Link>
</div> </div>
{path.map((q: string, i: number) => ( {path.map((q: string, i: number) => (
<div key={i} className="flex items-center flex-shrink-0"> <div key={i} className="flex items-center flex-shrink-0">
<div>/</div> <div>/</div>
<div className="hover:opacity-80 p-1 transition-all duration-75"> <div className="hover:opacity-80 px-1 transition-all duration-75">
<Link <Link
href={`/${path href={`/${path
.slice(0, i + 1) .slice(0, i + 1)
@ -34,7 +33,7 @@ const Breadcrumb: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =>
return ( return (
<div className="dark:text-gray-300 hover:opacity-80 pb-4 text-sm text-gray-600 transition-all duration-75"> <div className="dark:text-gray-300 hover:opacity-80 pb-4 text-sm text-gray-600 transition-all duration-75">
<div className="p-1"> <div>
<Link href="/">🚩 Home</Link> <Link href="/">🚩 Home</Link>
</div> </div>
</div> </div>

View file

@ -4,7 +4,7 @@ import emojiRegex from 'emoji-regex'
import { useClipboard } from 'use-clipboard-copy' import { useClipboard } from 'use-clipboard-copy'
import { ParsedUrlQuery } from 'querystring' import { ParsedUrlQuery } from 'querystring'
import { FunctionComponent, MouseEventHandler, SetStateAction, useEffect, useRef, useState } from 'react' import { FC, MouseEventHandler, SetStateAction, useEffect, useRef, useState } from 'react'
import { ImageDecorator } from 'react-viewer/lib/ViewerProps' import { ImageDecorator } from 'react-viewer/lib/ViewerProps'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@ -32,6 +32,7 @@ import AudioPreview from './previews/AudioPreview'
import VideoPreview from './previews/VideoPreview' import VideoPreview from './previews/VideoPreview'
import DownloadButtonGroup from './DownloadBtnGtoup' import DownloadButtonGroup from './DownloadBtnGtoup'
import PDFPreview from './previews/PDFPreview' import PDFPreview from './previews/PDFPreview'
import { DownloadBtnContainer, PreviewContainer } from './previews/Containers'
// Disabling SSR for some previews (image gallery view, and PDF view) // Disabling SSR for some previews (image gallery view, and PDF view)
const ReactViewer = dynamic(() => import('react-viewer'), { ssr: false }) const ReactViewer = dynamic(() => import('react-viewer'), { ssr: false })
@ -68,14 +69,14 @@ const queryToPath = (query?: ParsedUrlQuery) => {
return '/' return '/'
} }
const FileListItem: FunctionComponent<{ const FileListItem: FC<{
fileContent: { id: string; name: string; size: number; file: Object; lastModifiedDateTime: string } fileContent: { id: string; name: string; size: number; file: Object; lastModifiedDateTime: string }
}> = ({ fileContent: c }) => { }> = ({ fileContent: c }) => {
const emojiIcon = emojiRegex().exec(c.name) const emojiIcon = emojiRegex().exec(c.name)
const renderEmoji = emojiIcon && !emojiIcon.index const renderEmoji = emojiIcon && !emojiIcon.index
return ( return (
<div className="grid items-center grid-cols-10 p-3 space-x-2 cursor-pointer"> <div className="grid items-center grid-cols-10 px-3 py-2.5 space-x-2 cursor-pointer">
<div className="md:col-span-6 flex items-center col-span-10 space-x-2 truncate"> <div className="md:col-span-6 flex items-center col-span-10 space-x-2 truncate">
{/* <div>{c.file ? c.file.mimeType : 'folder'}</div> */} {/* <div>{c.file ? c.file.mimeType : 'folder'}</div> */}
<div className="flex-shrink-0 w-5 text-center"> <div className="flex-shrink-0 w-5 text-center">
@ -106,7 +107,7 @@ const FileListItem: FunctionComponent<{
) )
} }
const Checkbox: FunctionComponent<{ const Checkbox: FC<{
checked: 0 | 1 | 2 checked: 0 | 1 | 2
onChange: () => void onChange: () => void
title: string title: string
@ -151,7 +152,7 @@ const Checkbox: FunctionComponent<{
) )
} }
const Downloading: FunctionComponent<{ title: string }> = ({ title }) => { const Downloading: FC<{ title: string }> = ({ title }) => {
return ( return (
<span title={title} className="p-2 rounded" role="status"> <span title={title} className="p-2 rounded" role="status">
<LoadingIcon <LoadingIcon
@ -163,7 +164,7 @@ const Downloading: FunctionComponent<{ title: string }> = ({ title }) => {
) )
} }
const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) => { const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
const [imageViewerVisible, setImageViewerVisibility] = useState(false) const [imageViewerVisible, setImageViewerVisibility] = useState(false)
const [activeImageIdx, setActiveImageIdx] = useState(0) const [activeImageIdx, setActiveImageIdx] = useState(0)
const [selected, setSelected] = useState<{ [key: string]: boolean }>({}) const [selected, setSelected] = useState<{ [key: string]: boolean }>({})
@ -188,16 +189,16 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
} }
return ( return (
<div className="dark:bg-gray-900 p-3 bg-white rounded"> <PreviewContainer>
{error.message.includes('401') ? <Auth redirect={path} /> : <FourOhFour errorMsg={error.message} />} {error.message.includes('401') ? <Auth redirect={path} /> : <FourOhFour errorMsg={error.message} />}
</div> </PreviewContainer>
) )
} }
if (!data) { if (!data) {
return ( return (
<div className="dark:bg-gray-900 p-3 bg-white rounded"> <PreviewContainer>
<Loading loadingText="Loading ..." /> <Loading loadingText="Loading ..." />
</div> </PreviewContainer>
) )
} }
@ -340,14 +341,19 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
} }
return ( return (
<>
<div className="dark:bg-gray-900 dark:text-gray-100 bg-white rounded"> <div className="dark:bg-gray-900 dark:text-gray-100 bg-white rounded">
<div className="dark:border-gray-700 grid items-center grid-cols-12 px-3 space-x-2 border-b border-gray-200"> <div className="dark:border-gray-500/30 grid items-center grid-cols-12 px-3 space-x-2 border-b border-gray-900/10">
<div className="md:col-span-6 col-span-12 font-bold py-3">Name</div> <div className="md:col-span-6 col-span-12 font-bold py-2 text-gray-600 dark:text-gray-300 uppercase tracking-widest text-xs">
<div className="md:block hidden col-span-3 font-bold">Last Modified</div> Name
<div className="md:block hidden font-bold">Size</div> </div>
<div className="md:block hidden font-bold">Actions</div> <div className="md:block hidden col-span-3 font-bold text-gray-600 dark:text-gray-300 uppercase tracking-widest text-xs">
<div className="md:block hidden font-bold"> Last Modified
<div className="md:flex dark:text-gray-400 hidden p-1 text-gray-700"> </div>
<div className="md:block hidden font-bold text-gray-600 dark:text-gray-300 uppercase tracking-widest text-xs">Size</div>
<div className="md:block hidden font-bold text-gray-600 dark:text-gray-300 uppercase tracking-widest text-xs">Actions</div>
<div className="md:block hidden font-bold text-gray-600 dark:text-gray-300 uppercase tracking-widest text-xs">
<div className="md:flex dark:text-gray-400 hidden px-1.5 py-1 text-gray-700">
<Checkbox <Checkbox
checked={totalSelected} checked={totalSelected}
onChange={toggleTotalSelected} onChange={toggleTotalSelected}
@ -363,7 +369,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
disabled={totalSelected === 0} disabled={totalSelected === 0}
onClick={handleSelectedDownload} onClick={handleSelectedDownload}
> >
<FontAwesomeIcon icon={['far', 'arrow-alt-circle-down']} /> <FontAwesomeIcon icon={['far', 'arrow-alt-circle-down']} size="lg" />
</button> </button>
)} )}
</div> </div>
@ -427,10 +433,10 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
<FileListItem fileContent={c} /> <FileListItem fileContent={c} />
</div> </div>
{c.folder ? ( {c.folder ? (
<div className="md:flex dark:text-gray-400 hidden p-1 text-gray-700"> <div className="md:flex dark:text-gray-400 hidden p-1.5 text-gray-700">
<span <span
title="Copy folder permalink" title="Copy folder permalink"
className="hover:bg-gray-300 dark:hover:bg-gray-600 p-2 rounded cursor-pointer" className="hover:bg-gray-300 dark:hover:bg-gray-600 px-1.5 py-1 rounded cursor-pointer"
onClick={() => { onClick={() => {
clipboard.copy(`${getBaseUrl()}${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`) clipboard.copy(`${getBaseUrl()}${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`)
toast('Copied folder permalink.', { icon: '👌' }) toast('Copied folder permalink.', { icon: '👌' })
@ -443,7 +449,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
) : ( ) : (
<span <span
title="Download folder" title="Download folder"
className="hover:bg-gray-300 dark:hover:bg-gray-600 p-2 rounded cursor-pointer" className="hover:bg-gray-300 dark:hover:bg-gray-600 px-1.5 py-1 rounded cursor-pointer"
onClick={() => { onClick={() => {
const p = `${path === '/' ? '' : path}/${encodeURIComponent(c.name)}` const p = `${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`
handleFolderDownload(p, c.id, c.name)() handleFolderDownload(p, c.id, c.name)()
@ -454,10 +460,10 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
)} )}
</div> </div>
) : ( ) : (
<div className="md:flex dark:text-gray-400 hidden p-1 text-gray-700"> <div className="md:flex dark:text-gray-400 hidden px-1.5 py-1 text-gray-700">
<span <span
title="Copy raw file permalink" title="Copy raw file permalink"
className="hover:bg-gray-300 dark:hover:bg-gray-600 p-2 rounded cursor-pointer" className="hover:bg-gray-300 dark:hover:bg-gray-600 px-1.5 py-1 rounded cursor-pointer"
onClick={() => { onClick={() => {
clipboard.copy( clipboard.copy(
`${getBaseUrl()}/api?path=${path === '/' ? '' : path}/${encodeURIComponent(c.name)}&raw=true` `${getBaseUrl()}/api?path=${path === '/' ? '' : path}/${encodeURIComponent(c.name)}&raw=true`
@ -469,7 +475,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
</span> </span>
<a <a
title="Download file" title="Download file"
className="hover:bg-gray-300 dark:hover:bg-gray-600 p-2 rounded cursor-pointer" className="hover:bg-gray-300 dark:hover:bg-gray-600 p-1 rounded cursor-pointer"
href={c['@microsoft.graph.downloadUrl']} href={c['@microsoft.graph.downloadUrl']}
> >
<FontAwesomeIcon icon={['far', 'arrow-alt-circle-down']} /> <FontAwesomeIcon icon={['far', 'arrow-alt-circle-down']} />
@ -537,13 +543,13 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
</button> </button>
</div> </div>
)} )}
</div>
{renderReadme && ( {renderReadme && (
<div className="dark:border-gray-700 border-t"> <div className="mt-4">
<MarkdownPreview file={readmeFile} path={path} standalone={false} /> <MarkdownPreview file={readmeFile} path={path} standalone={false} />
</div> </div>
)} )}
</div> </>
) )
} }
@ -557,10 +563,10 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
switch (extensions[fileExtension]) { switch (extensions[fileExtension]) {
case preview.image: case preview.image:
return ( return (
<div className="w-full p-3 bg-white rounded"> <PreviewContainer>
{/* eslint-disable-next-line @next/next/no-img-element */} {/* eslint-disable-next-line @next/next/no-img-element */}
<img className="mx-auto" src={downloadUrl} alt={fileName} /> <img className="mx-auto" src={downloadUrl} alt={fileName} />
</div> </PreviewContainer>
) )
case preview.text: case preview.text:
@ -588,28 +594,28 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
return <EPUBPreview file={file} /> return <EPUBPreview file={file} />
default: default:
return <div className="dark:bg-gray-900 bg-white rounded">{fileName}</div> return <PreviewContainer>{fileName}</PreviewContainer>
} }
} }
return ( return (
<> <>
<div className="dark:bg-gray-900 p-3 bg-white rounded"> <PreviewContainer>
<FourOhFour <FourOhFour
errorMsg={`Preview for file ${fileName} is not available, download directly with the button below.`} errorMsg={`Preview for file ${fileName} is not available, download directly with the button below.`}
/> />
</div> </PreviewContainer>
<div className="mt-4"> <DownloadBtnContainer>
<DownloadButtonGroup downloadUrl={downloadUrl} /> <DownloadButtonGroup downloadUrl={downloadUrl} />
</div> </DownloadBtnContainer>
</> </>
) )
} }
return ( return (
<div className="dark:bg-gray-900 p-3 bg-white rounded"> <PreviewContainer>
<FourOhFour errorMsg={`Cannot preview ${path}`} /> <FourOhFour errorMsg={`Cannot preview ${path}`} />
</div> </PreviewContainer>
) )
} }
export default FileListing export default FileListing

View file

@ -7,7 +7,12 @@ const createFooterMarkup = () => {
} }
const Footer = () => { const Footer = () => {
return <div className="p-4 text-sm text-gray-400" dangerouslySetInnerHTML={createFooterMarkup()}></div> return (
<div
className="p-4 text-xs font-medium text-gray-400 w-full text-center border-t border-gray-900/10 dark:border-gray-500/30"
dangerouslySetInnerHTML={createFooterMarkup()}
></div>
)
} }
export default Footer export default Footer

View file

@ -1,7 +1,6 @@
import Image from 'next/image' import Image from 'next/image'
import { FunctionComponent } from 'react'
const FourOhFour: FunctionComponent<{ errorMsg: string }> = ({ errorMsg }) => { const FourOhFour: React.FC<{ errorMsg: string }> = ({ errorMsg }) => {
return ( return (
<div className="my-12"> <div className="my-12">
<div className="w-1/3 mx-auto"> <div className="w-1/3 mx-auto">

View file

@ -1,6 +1,4 @@
import { FunctionComponent } from 'react' const Loading: React.FC<{ loadingText: string }> = ({ loadingText }) => {
const Loading: FunctionComponent<{ loadingText: string }> = ({ loadingText }) => {
return ( return (
<div className="dark:text-white flex items-center justify-center py-32 space-x-1 rounded"> <div className="dark:text-white flex items-center justify-center py-32 space-x-1 rounded">
<LoadingIcon className="animate-spin w-5 h-5 mr-3 -ml-1" /> <LoadingIcon className="animate-spin w-5 h-5 mr-3 -ml-1" />
@ -10,7 +8,7 @@ const Loading: FunctionComponent<{ loadingText: string }> = ({ loadingText }) =>
} }
// As there is no CSS-in-JS styling system, pass class list to override styles // As there is no CSS-in-JS styling system, pass class list to override styles
export const LoadingIcon: FunctionComponent<{ className?: string }> = ({ className }) => { export const LoadingIcon: React.FC<{ className?: string }> = ({ className }) => {
return ( return (
<svg className={className} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <svg className={className} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" /> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />

View file

@ -41,11 +41,11 @@ const Navbar = () => {
} }
return ( return (
<div className="bg-white dark:bg-gray-900 sticky top-0 bg-opacity-80 border-b border-gray-900/10 backdrop-blur-md z-[100]"> <div className="bg-white dark:bg-gray-900 dark:border-gray-500/30 sticky top-0 bg-opacity-80 border-b border-gray-900/10 backdrop-blur-md z-[100]">
<Toaster /> <Toaster />
<div className="flex items-center justify-between w-full max-w-5xl mx-auto pr-4 py-1"> <div className="flex items-center justify-between w-full mx-auto px-4 py-1">
<Link href="/"> <Link href="/" passHref>
<a className="dark:text-white hover:opacity-80 flex items-center p-2 space-x-2"> <a className="dark:text-white hover:opacity-80 flex items-center p-2 space-x-2">
<Image src={siteConfig.icon} alt="icon" width="25" height="25" priority /> <Image src={siteConfig.icon} alt="icon" width="25" height="25" priority />
<span className="sm:block hidden font-bold">{siteConfig.title}</span> <span className="sm:block hidden font-bold">{siteConfig.title}</span>

View file

@ -1,8 +1,9 @@
import { FunctionComponent, useState } from 'react' import { FC, useState } from 'react'
import ReactPlayer from 'react-player' import ReactPlayer from 'react-player'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import DownloadButtonGroup from '../DownloadBtnGtoup' import DownloadButtonGroup from '../DownloadBtnGtoup'
import { DownloadBtnContainer, PreviewContainer } from './Containers'
enum PlayerState { enum PlayerState {
Loading, Loading,
@ -11,12 +12,12 @@ enum PlayerState {
Paused, Paused,
} }
const AudioPreview: FunctionComponent<{ file: any }> = ({ file }) => { const AudioPreview: FC<{ file: any }> = ({ file }) => {
const [playerStatus, setPlayerStatus] = useState(PlayerState.Loading) const [playerStatus, setPlayerStatus] = useState(PlayerState.Loading)
return ( return (
<> <>
<div className="dark:bg-gray-900 dark:text-white w-full p-3 bg-white rounded"> <PreviewContainer>
<div className="md:flex-row md:space-x-4 flex flex-col space-y-4"> <div className="md:flex-row md:space-x-4 flex flex-col space-y-4">
<div className="dark:bg-gray-700 aspect-square flex items-center justify-center w-full md:w-40 transition-all duration-75 bg-gray-100 rounded"> <div className="dark:bg-gray-700 aspect-square flex items-center justify-center w-full md:w-40 transition-all duration-75 bg-gray-100 rounded">
{playerStatus === PlayerState.Loading ? ( {playerStatus === PlayerState.Loading ? (
@ -72,11 +73,11 @@ const AudioPreview: FunctionComponent<{ file: any }> = ({ file }) => {
/> />
</div> </div>
</div> </div>
</div> </PreviewContainer>
<div className="border-t-gray-200 dark:border-t-gray-700 border-t p-2 sticky bottom-0 left-0 right-0 z-10 bg-white bg-opacity-80 backdrop-blur-md dark:bg-gray-900"> <DownloadBtnContainer>
<DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} /> <DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} />
</div> </DownloadBtnContainer>
</> </>
) )
} }

View file

@ -1,46 +1,47 @@
import { useEffect, FunctionComponent } from 'react' import { useEffect, FC } from 'react'
import Prism from 'prismjs' import Prism from 'prismjs'
import { getExtension } from '../../utils/getFileIcon' import { getExtension } from '../../utils/getFileIcon'
import { useStaleSWR } from '../../utils/fetchWithSWR' import useFileContent from '../../utils/fetchOnMount'
import FourOhFour from '../FourOhFour' import FourOhFour from '../FourOhFour'
import Loading from '../Loading' import Loading from '../Loading'
import DownloadButtonGroup from '../DownloadBtnGtoup' import DownloadButtonGroup from '../DownloadBtnGtoup'
import { DownloadBtnContainer, PreviewContainer } from './Containers'
const CodePreview: FunctionComponent<{ file: any }> = ({ file }) => { const CodePreview: FC<{ file: any }> = ({ file }) => {
const { data, error } = useStaleSWR({ url: file['@microsoft.graph.downloadUrl'] }) const { content, error, validating } = useFileContent(file['@microsoft.graph.downloadUrl'])
useEffect(() => { useEffect(() => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
Prism.highlightAll() Prism.highlightAll()
} }
}, [data]) }, [validating])
if (error) { if (error) {
return ( return (
<div className="dark:bg-gray-900 p-3 bg-white rounded"> <PreviewContainer>
<FourOhFour errorMsg={error.message} /> <FourOhFour errorMsg={error} />
</div> </PreviewContainer>
) )
} }
if (!data) { if (validating) {
return ( return (
<div className="dark:bg-gray-900 p-3 bg-white rounded"> <PreviewContainer>
<Loading loadingText="Loading file content..." /> <Loading loadingText="Loading file content..." />
</div> </PreviewContainer>
) )
} }
return ( return (
<div> <div>
<div className="markdown-body p-3 bg-gray-900 rounded"> <PreviewContainer>
<pre className={`language-${getExtension(file.name)}`}> <pre className={`language-${getExtension(file.name)}`}>
<code>{data}</code> <code className="font-mono">{content}</code>
</pre> </pre>
</div> </PreviewContainer>
<div className="border-t-gray-200 dark:border-t-gray-700 border-t p-2 sticky bottom-0 left-0 right-0 z-10 bg-white bg-opacity-80 backdrop-blur-md dark:bg-gray-900"> <DownloadBtnContainer>
<DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} /> <DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} />
</div> </DownloadBtnContainer>
</div> </div>
) )
} }

View file

@ -0,0 +1,11 @@
export function PreviewContainer({ children }): JSX.Element {
return <div className="dark:bg-gray-900 p-3 bg-white rounded dark:text-white">{children}</div>
}
export function DownloadBtnContainer({ children }): JSX.Element {
return (
<div className="border-t rounded border-gray-900/10 dark:border-gray-500/30 p-2 sticky bottom-0 left-0 right-0 z-10 bg-white bg-opacity-80 backdrop-blur-md dark:bg-gray-900">
{children}
</div>
)
}

View file

@ -1,11 +1,12 @@
import { FunctionComponent, useEffect, useRef, useState } from 'react' import { FC, useEffect, useRef, useState } from 'react'
import { ReactReader } from 'react-reader' import { ReactReader } from 'react-reader'
import type { Rendition } from 'epubjs' import type { Rendition } from 'epubjs'
import Loading from '../Loading' import Loading from '../Loading'
import DownloadButtonGroup from '../DownloadBtnGtoup' import DownloadButtonGroup from '../DownloadBtnGtoup'
import { DownloadBtnContainer } from './Containers'
const EPUBPreview: FunctionComponent<{file: any}> = ({ file }) => { const EPUBPreview: FC<{ file: any }> = ({ file }) => {
const [epubContainerWidth, setEpubContainerWidth] = useState(400) const [epubContainerWidth, setEpubContainerWidth] = useState(400)
const epubContainer = useRef<HTMLDivElement>(null) const epubContainer = useRef<HTMLDivElement>(null)
@ -41,7 +42,7 @@ const EPUBPreview: FunctionComponent<{file: any}> = ({ file }) => {
<div style={{ position: 'absolute', width: epubContainerWidth, height: '70vh' }}> <div style={{ position: 'absolute', width: epubContainerWidth, height: '70vh' }}>
<ReactReader <ReactReader
url={file['@microsoft.graph.downloadUrl']} url={file['@microsoft.graph.downloadUrl']}
getRendition={(rendition) => fixEpub(rendition)} getRendition={rendition => fixEpub(rendition)}
loadingView={<Loading loadingText="Loading EPUB ..." />} loadingView={<Loading loadingText="Loading EPUB ..." />}
location={location} location={location}
locationChanged={onLocationChange} locationChanged={onLocationChange}
@ -51,9 +52,9 @@ const EPUBPreview: FunctionComponent<{file: any}> = ({ file }) => {
</div> </div>
</div> </div>
</div> </div>
<div className="border-t-gray-200 dark:border-t-gray-700 border-t p-2 sticky bottom-0 left-0 right-0 z-10 bg-white bg-opacity-80 backdrop-blur-md dark:bg-gray-900"> <DownloadBtnContainer>
<DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} /> <DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} />
</div> </DownloadBtnContainer>
</div> </div>
) )
} }

View file

@ -1,4 +1,4 @@
import { useEffect, FunctionComponent, CSSProperties } from 'react' import { useEffect, FC, CSSProperties } from 'react'
import Prism from 'prismjs' import Prism from 'prismjs'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import gfm from 'remark-gfm' import gfm from 'remark-gfm'
@ -11,14 +11,15 @@ import 'katex/dist/katex.min.css'
import FourOhFour from '../FourOhFour' import FourOhFour from '../FourOhFour'
import Loading from '../Loading' import Loading from '../Loading'
import DownloadButtonGroup from '../DownloadBtnGtoup' import DownloadButtonGroup from '../DownloadBtnGtoup'
import { useStaleSWR } from '../../utils/fetchWithSWR' import useFileContent from '../../utils/fetchOnMount'
import { DownloadBtnContainer, PreviewContainer } from './Containers'
const MarkdownPreview: FunctionComponent<{ file: any; path: string; standalone?: boolean }> = ({ const MarkdownPreview: FC<{ file: any; path: string; standalone?: boolean }> = ({
file, file,
path, path,
standalone = true, standalone = true,
}) => { }) => {
const { data, error } = useStaleSWR({ url: file['@microsoft.graph.downloadUrl'] }) const { content, error, validating } = useFileContent(file['@microsoft.graph.downloadUrl'])
// The parent folder of the markdown file, which is also the relative image folder // The parent folder of the markdown file, which is also the relative image folder
const parentPath = path.substring(0, path.lastIndexOf('/')) const parentPath = path.substring(0, path.lastIndexOf('/'))
@ -63,45 +64,41 @@ const MarkdownPreview: FunctionComponent<{ file: any; path: string; standalone?:
useEffect(() => { useEffect(() => {
Prism.highlightAll() Prism.highlightAll()
}, [data]) }, [content])
if (error) { if (error) {
return ( return (
<div className={`${standalone ? 'bg-white dark:bg-gray-900 rounded p-3' : ''}`}> <PreviewContainer>
<FourOhFour errorMsg={error.message} /> <FourOhFour errorMsg={error} />
</div> </PreviewContainer>
) )
} }
if (!data) { if (validating) {
return ( return (
<div className={standalone ? 'bg-white dark:bg-gray-900 rounded p-3' : ''}> <PreviewContainer>
<Loading loadingText="Loading file content..." /> <Loading loadingText="Loading file content..." />
</div> </PreviewContainer>
) )
} }
return ( return (
<div> <div>
<div <PreviewContainer>
className={ <div className="markdown-body">
standalone
? 'markdown-body bg-white dark:bg-gray-900 rounded p-3 dark:text-white'
: 'markdown-body p-3 dark:text-white'
}
>
{/* Using rehypeRaw to render HTML inside Markdown is potentially dangerous, use under safe environments. (#18) */} {/* Using rehypeRaw to render HTML inside Markdown is potentially dangerous, use under safe environments. (#18) */}
<ReactMarkdown <ReactMarkdown
remarkPlugins={[gfm, remarkMath]} remarkPlugins={[gfm, remarkMath]}
rehypePlugins={[rehypeKatex, rehypeRaw as any]} rehypePlugins={[rehypeKatex, rehypeRaw as any]}
components={relativeImagePathRenderer} components={relativeImagePathRenderer}
> >
{data} {content}
</ReactMarkdown> </ReactMarkdown>
</div> </div>
</PreviewContainer>
{standalone && ( {standalone && (
<div className="border-t-gray-200 dark:border-t-gray-700 border-t p-2 sticky bottom-0 left-0 right-0 z-10 bg-white bg-opacity-80 backdrop-blur-md dark:bg-gray-900"> <DownloadBtnContainer>
<DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} /> <DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} />
</div> </DownloadBtnContainer>
)} )}
</div> </div>
) )

View file

@ -1,9 +1,10 @@
import { FunctionComponent, useEffect, useRef, useState } from 'react' import { FC, useEffect, useRef, useState } from 'react'
import Preview from 'preview-office-docs' import Preview from 'preview-office-docs'
import DownloadButtonGroup from '../DownloadBtnGtoup' import DownloadButtonGroup from '../DownloadBtnGtoup'
import { DownloadBtnContainer } from './Containers'
const OfficePreview: FunctionComponent<{ file: any }> = ({ file }) => { const OfficePreview: FC<{ file: any }> = ({ file }) => {
const docContainer = useRef<HTMLDivElement>(null) const docContainer = useRef<HTMLDivElement>(null)
const [docContainerWidth, setDocContainerWidth] = useState(600) const [docContainerWidth, setDocContainerWidth] = useState(600)
@ -20,9 +21,9 @@ const OfficePreview: FunctionComponent<{ file: any }> = ({ file }) => {
height="600" height="600"
/> />
</div> </div>
<div className="border-t-gray-200 dark:border-t-gray-700 border-t p-2 sticky bottom-0 left-0 right-0 z-10 bg-white bg-opacity-80 backdrop-blur-md dark:bg-gray-900"> <DownloadBtnContainer>
<DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} /> <DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} />
</div> </DownloadBtnContainer>
</div> </div>
) )
} }

View file

@ -1,8 +1,7 @@
import { FunctionComponent } from 'react'
import DownloadButtonGroup from '../DownloadBtnGtoup' import DownloadButtonGroup from '../DownloadBtnGtoup'
import { DownloadBtnContainer } from './Containers'
const PDFEmbedPreview: FunctionComponent<{ file: any }> = ({ file }) => { const PDFEmbedPreview: React.FC<{ file: any }> = ({ file }) => {
// const url = `/api/proxy?url=${encodeURIComponent(file['@microsoft.graph.downloadUrl'])}&inline=true` // const url = `/api/proxy?url=${encodeURIComponent(file['@microsoft.graph.downloadUrl'])}&inline=true`
const url = `https://mozilla.github.io/pdf.js/web/viewer.html?file=${encodeURIComponent( const url = `https://mozilla.github.io/pdf.js/web/viewer.html?file=${encodeURIComponent(
file['@microsoft.graph.downloadUrl'] file['@microsoft.graph.downloadUrl']
@ -13,9 +12,9 @@ const PDFEmbedPreview: FunctionComponent<{ file: any }> = ({ file }) => {
<div className="w-full rounded overflow-hidden" style={{ height: '90vh' }}> <div className="w-full rounded overflow-hidden" style={{ height: '90vh' }}>
<iframe src={url} frameBorder="0" width="100%" height="100%"></iframe> <iframe src={url} frameBorder="0" width="100%" height="100%"></iframe>
</div> </div>
<div className="border-t-gray-200 dark:border-t-gray-700 border-t p-2 sticky bottom-0 left-0 right-0 z-10 bg-white bg-opacity-80 backdrop-blur-md dark:bg-gray-900"> <DownloadBtnContainer>
<DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} /> <DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} />
</div> </DownloadBtnContainer>
</div> </div>
) )
} }

View file

@ -1,35 +1,43 @@
import { FunctionComponent } from 'react'
import FourOhFour from '../FourOhFour' import FourOhFour from '../FourOhFour'
import Loading from '../Loading' import Loading from '../Loading'
import DownloadButtonGroup from '../DownloadBtnGtoup' import DownloadButtonGroup from '../DownloadBtnGtoup'
import { useStaleSWR } from '../../utils/fetchWithSWR' import useFileContent from '../../utils/fetchOnMount'
import { DownloadBtnContainer, PreviewContainer } from './Containers'
const TextPreview: FunctionComponent<{ file: any }> = ({ file }) => { const TextPreview = ({ file }) => {
const { data, error } = useStaleSWR({ url: file['@microsoft.graph.downloadUrl'] }) const { content, error, validating } = useFileContent(file['@microsoft.graph.downloadUrl'])
if (error) { if (error) {
return ( return (
<div className="dark:bg-gray-900 p-3 bg-white rounded"> <PreviewContainer>
<FourOhFour errorMsg={error.message} /> <FourOhFour errorMsg={error} />
</div> </PreviewContainer>
) )
} }
if (!data) {
if (validating) {
return ( return (
<div className="dark:bg-gray-900 p-3 bg-white rounded"> <PreviewContainer>
<Loading loadingText="Loading file content..." /> <Loading loadingText="Loading file content..." />
</div> </PreviewContainer>
)
}
if (!content) {
return (
<PreviewContainer>
<FourOhFour errorMsg="File is empty." />
</PreviewContainer>
) )
} }
return ( return (
<div> <div>
<div className="dark:bg-gray-900 dark:text-gray-100 p-3 bg-white rounded"> <PreviewContainer>
<pre className="md:p-3 p-0 overflow-scroll">{data}</pre> <pre className="md:p-3 p-0 overflow-x-scroll text-sm">{content}</pre>
</div> </PreviewContainer>
<div className="border-t-gray-200 dark:border-t-gray-700 border-t p-2 sticky bottom-0 left-0 right-0 z-10 bg-white bg-opacity-80 backdrop-blur-md dark:bg-gray-900"> <DownloadBtnContainer>
<DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} /> <DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} />
</div> </DownloadBtnContainer>
</div> </div>
) )
} }

View file

@ -1,4 +1,3 @@
import { FunctionComponent } from 'react'
import ReactPlayer from 'react-player' import ReactPlayer from 'react-player'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useClipboard } from 'use-clipboard-copy' import { useClipboard } from 'use-clipboard-copy'
@ -6,14 +5,15 @@ import toast from 'react-hot-toast'
import { getBaseUrl } from '../../utils/getBaseUrl' import { getBaseUrl } from '../../utils/getBaseUrl'
import { DownloadButton } from '../DownloadBtnGtoup' import { DownloadButton } from '../DownloadBtnGtoup'
import { DownloadBtnContainer, PreviewContainer } from './Containers'
const VideoPreview: FunctionComponent<{ file: any }> = ({ file }) => { const VideoPreview: React.FC<{ file: any }> = ({ file }) => {
const { asPath } = useRouter() const { asPath } = useRouter()
const clipboard = useClipboard() const clipboard = useClipboard()
return ( return (
<> <>
<div className="dark:bg-gray-900 p-3 bg-white rounded"> <PreviewContainer>
<ReactPlayer <ReactPlayer
className="aspect-video" className="aspect-video"
url={file['@microsoft.graph.downloadUrl']} url={file['@microsoft.graph.downloadUrl']}
@ -22,9 +22,10 @@ const VideoPreview: FunctionComponent<{ file: any }> = ({ file }) => {
height="100%" height="100%"
config={{ file: { forceVideo: true } }} config={{ file: { forceVideo: true } }}
/> />
</div> </PreviewContainer>
<div className="flex flex-wrap justify-center mt-4 gap-2"> <DownloadBtnContainer>
<div className="flex flex-wrap justify-center gap-2">
<DownloadButton <DownloadButton
onClickCallback={() => window.open(file['@microsoft.graph.downloadUrl'])} onClickCallback={() => window.open(file['@microsoft.graph.downloadUrl'])}
btnColor="blue" btnColor="blue"
@ -44,7 +45,7 @@ const VideoPreview: FunctionComponent<{ file: any }> = ({ file }) => {
clipboard.copy(`${getBaseUrl()}/api?path=${asPath}&raw=true`) clipboard.copy(`${getBaseUrl()}/api?path=${asPath}&raw=true`)
toast.success('Copied direct link to clipboard.') toast.success('Copied direct link to clipboard.')
}} }}
btnColor="yellow" btnColor="pink"
btnText="Copy direct link" btnText="Copy direct link"
btnIcon="copy" btnIcon="copy"
/> />
@ -65,6 +66,7 @@ const VideoPreview: FunctionComponent<{ file: any }> = ({ file }) => {
btnImage="/players/potplayer.png" btnImage="/players/potplayer.png"
/> />
</div> </div>
</DownloadBtnContainer>
</> </>
) )
} }

7489
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -24,3 +24,7 @@
.markdown-body ol { .markdown-body ol {
@apply list-decimal; @apply list-decimal;
} }
pre[class*='language-'],
code[class*='language-'] {
@apply font-mono !important;
}

View file

@ -30,7 +30,7 @@ module.exports = {
}, },
colors: { colors: {
gray: { gray: {
850: '#2E2E34' 850: '#222226'
} }
} }
} }

20
utils/fetchOnMount.ts Normal file
View file

@ -0,0 +1,20 @@
import axios from 'axios'
import { useEffect, useState } from 'react'
// Custom hook to fetch raw file content on mount
export default function useFileContent(odRawUrl: string): { content: string; error: string; validating: boolean } {
const [content, setContent] = useState('')
const [validating, setValidating] = useState(true)
const [error, setError] = useState('')
useEffect(() => {
axios
.get(odRawUrl)
.then(res => setContent(res.data))
.catch(e => setError(e.message))
.finally(() => {
setValidating(false)
})
}, [odRawUrl])
return { content, error, validating }
}

View file

@ -56,10 +56,11 @@ export function useProtectedSWRInfinite(path: string = '') {
return [`/api?path=${path}&next=${previousPageData.next}`, hashedToken] return [`/api?path=${path}&next=${previousPageData.next}`, hashedToken]
} }
const revalidationOptions = { // const revalidationOptions = {
revalidateOnMount: !(cache.has(`arg@"/api?path=${path}"@null`) || cache.has(`/api?path=${path}`)), // revalidateOnMount: !(cache.has(`arg@"/api?path=${path}"@null`) || cache.has(`/api?path=${path}`)),
revalidateOnFocus: false, // revalidateOnFocus: false,
revalidateOnReconnect: true, // revalidateOnReconnect: true,
} // }
return useSWRInfinite(getNextKey, fetcher, revalidationOptions) // return useSWRInfinite(getNextKey, fetcher, revalidationOptions)
return useSWRInfinite(getNextKey, fetcher)
} }