add thumbnail api and update grid view thumbnail fetching

This commit is contained in:
myl7
2022-02-09 15:19:54 +08:00
parent 2959370f80
commit 197336d42d
3 changed files with 101 additions and 26 deletions
+23 -18
View File
@@ -11,9 +11,20 @@ import { formatModifiedDateTime } from '../utils/fileDetails'
import { getReadablePath } from '../utils/getReadablePath'
import { Checkbox, ChildIcon, Downloading, formatChildName } from './FileListing'
const GridItem = ({ c }: { c: OdFolderChildren }) => {
const GridItem = ({ c, path }: { c: OdFolderChildren; path: string }) => {
// We use the generated medium thumbnail for rendering preview images
const thumbnail = c.thumbnails && c.thumbnails.length > 0 ? c.thumbnails[0].medium : null
const thumbnailUrl =
'folder' in c
? // Folders don't have thumbnails
null
: c.thumbnails
? // Most OneDrive versions, including E5 developer, should have thumbnails returned
c.thumbnails.length > 0
? c.thumbnails[0].medium.url
: null
: // According to OneDrive docs, OneDrive for Business and SharePoint does not
// (can not retrieve thumbnails via expand). But currently we only see OneDrive 世纪互联 really does not.
`/api/thumbnail?path=${path}`
// Some thumbnails are broken, so we check for onerror event in the image component
const [brokenThumbnail, setBrokenThumbnail] = useState(false)
@@ -21,11 +32,11 @@ const GridItem = ({ c }: { c: OdFolderChildren }) => {
return (
<div className="space-y-2">
<div className="h-32 overflow-hidden rounded border border-gray-900/10 dark:border-gray-500/30">
{thumbnail && !brokenThumbnail ? (
{thumbnailUrl && !brokenThumbnail ? (
// eslint-disable-next-line @next/next/no-img-element
<img
className="h-full w-full object-cover object-top"
src={thumbnail.url}
src={thumbnailUrl}
alt={c.name}
onError={() => setBrokenThumbnail(true)}
/>
@@ -69,6 +80,9 @@ const FolderGridLayout = ({
const { t } = useTranslation()
// Get item path from item name
const getItemPath = (name: string) => `${path === '/' ? '' : path}/${encodeURIComponent(name)}`
return (
<div className="rounded bg-white dark:bg-gray-900 dark:text-gray-100">
<div className="flex items-center border-b border-gray-900/10 px-3 text-xs font-bold uppercase tracking-widest text-gray-600 dark:border-gray-500/30 dark:text-gray-400">
@@ -108,9 +122,7 @@ const FolderGridLayout = ({
title={t('Copy folder permalink')}
className="cursor-pointer rounded px-1.5 py-1 hover:bg-gray-300 dark:hover:bg-gray-600"
onClick={() => {
clipboard.copy(
`${getBaseUrl()}${getReadablePath(`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`)}`
)
clipboard.copy(`${getBaseUrl()}${getReadablePath(getItemPath(c.name))}`)
toast(t('Copied folder permalink.'), { icon: '👌' })
}}
>
@@ -122,10 +134,7 @@ const FolderGridLayout = ({
<span
title={t('Download folder')}
className="cursor-pointer rounded px-1.5 py-1 hover:bg-gray-300 dark:hover:bg-gray-600"
onClick={() => {
const p = `${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`
handleFolderDownload(p, c.id, c.name)()
}}
onClick={handleFolderDownload(getItemPath(c.name), c.id, c.name)}
>
<FontAwesomeIcon icon={['far', 'arrow-alt-circle-down']} />
</span>
@@ -137,11 +146,7 @@ const FolderGridLayout = ({
title={t('Copy raw file permalink')}
className="cursor-pointer rounded px-1.5 py-1 hover:bg-gray-300 dark:hover:bg-gray-600"
onClick={() => {
clipboard.copy(
`${getBaseUrl()}/api?path=${getReadablePath(
`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`
)}&raw=true`
)
clipboard.copy(`${getBaseUrl()}/api?path=${getReadablePath(getItemPath(c.name))}&raw=true`)
toast.success(t('Copied raw file permalink.'))
}}
>
@@ -172,9 +177,9 @@ const FolderGridLayout = ({
)}
</div>
<Link href={`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`} passHref>
<Link href={getItemPath(c.name)} passHref>
<a>
<GridItem c={c} />
<GridItem c={c} path={getItemPath(c.name)} />
</a>
</Link>
</div>
+18 -8
View File
@@ -90,6 +90,23 @@ export async function getAccessToken(): Promise<string> {
return ''
}
/**
* Match protected routes in site config to get path to required auth token
* @param path Path cleaned in advance
* @returns Path to required auth token. If not required, return empty string.
*/
export function getAuthTokenPath(path: string) {
const protectedRoutes = siteConfig.protectedRoutes
let authTokenPath = ''
for (const r of protectedRoutes) {
if (path.startsWith(r)) {
authTokenPath = `${r}/.password`
break
}
}
return authTokenPath
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// If method is POST, then the API is called by the client to store acquired tokens
if (req.method === 'POST') {
@@ -135,14 +152,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
// Handle authentication through .password
const protectedRoutes = siteConfig.protectedRoutes
let authTokenPath = ''
for (const r of protectedRoutes) {
if (cleanPath.startsWith(r)) {
authTokenPath = `${r}/.password`
break
}
}
const authTokenPath = getAuthTokenPath(cleanPath)
// Fetch password from remote file content
if (authTokenPath !== '') {
+60
View File
@@ -0,0 +1,60 @@
import type { OdThumbnail } from '../../types'
import { posix as pathPosix } from 'path'
import axios from 'axios'
import type { NextApiRequest, NextApiResponse } from 'next'
import { encodePath, getAccessToken, getAuthTokenPath } from '.'
import apiConfig from '../../config/api.config'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// Get access token from storage
const accessToken = await getAccessToken()
// Get item thumbnails by its path since we will later check if it is protected
const { path = '' } = req.query
// Sometimes the path parameter is defaulted to '[...path]' which we need to handle
if (path === '[...path]') {
res.status(400).json({ error: 'No path specified.' })
return
}
// If the path is not a valid path, return 400
if (typeof path !== 'string') {
res.status(400).json({ error: 'Path query invalid.' })
return
}
const cleanPath = pathPosix.resolve('/', pathPosix.normalize(path))
// Check if the path is protected
const authTokenPath = getAuthTokenPath(cleanPath)
// Currently protected paths are rejected to avoid file content leak
if (authTokenPath) {
res.status(404).json({ error: 'Protected pathes are not allowed.' })
return
}
const requestPath = encodePath(cleanPath)
// Handle response from OneDrive API
const requestUrl = `${apiConfig.driveApi}/root${requestPath}`
// Whether path is root, which requires some special treatment
const isRoot = requestPath === ''
try {
const { data } = await axios.get(`${requestUrl}${isRoot ? '' : ':'}/thumbnails`, {
headers: { Authorization: `Bearer ${accessToken}` },
})
const thumbnailUrl = data.value && data.value.length > 0 ? (data.value[0] as OdThumbnail).medium.url : null
if (thumbnailUrl) {
res.redirect(thumbnailUrl)
} else {
res.status(400).json({ error: "The item doesn't have a valid thumbnail." })
}
} catch (error: any) {
res.status(error.response.status).json({ error: error.response.data })
}
return
}