Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
cd3cc2a697
|
|||
|
7161070435
|
@@ -5,6 +5,7 @@ import ListSubheader from '@mui/material/ListSubheader'
|
|||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
import Skeleton from '@mui/material/Skeleton'
|
import Skeleton from '@mui/material/Skeleton'
|
||||||
|
import LinearProgress from '@mui/material/LinearProgress'
|
||||||
import {useTheme, useColorScheme, styled} from '@mui/material/styles'
|
import {useTheme, useColorScheme, styled} from '@mui/material/styles'
|
||||||
import Table from '@mui/material/Table'
|
import Table from '@mui/material/Table'
|
||||||
import TableHead from '@mui/material/TableHead'
|
import TableHead from '@mui/material/TableHead'
|
||||||
@@ -13,12 +14,12 @@ import TableCell, {tableCellClasses} from '@mui/material/TableCell'
|
|||||||
import TableRow from '@mui/material/TableRow'
|
import TableRow from '@mui/material/TableRow'
|
||||||
import TableContainer from '@mui/material/TableContainer'
|
import TableContainer from '@mui/material/TableContainer'
|
||||||
import Paper from '@mui/material/Paper'
|
import Paper from '@mui/material/Paper'
|
||||||
import FileSaver from 'file-saver'
|
|
||||||
import DescriptionIcon from '@mui/icons-material/Description'
|
import DescriptionIcon from '@mui/icons-material/Description'
|
||||||
import LibraryMusicIcon from '@mui/icons-material/LibraryMusic'
|
import LibraryMusicIcon from '@mui/icons-material/LibraryMusic'
|
||||||
import {Link} from '@mui/material'
|
import {Link} from '@mui/material'
|
||||||
|
|
||||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
||||||
|
const audioMetaCache = {}
|
||||||
|
|
||||||
const StyledTableCell = styled(TableCell)(({theme}) => ({
|
const StyledTableCell = styled(TableCell)(({theme}) => ({
|
||||||
[`&.${tableCellClasses.head}`]: {
|
[`&.${tableCellClasses.head}`]: {
|
||||||
@@ -51,7 +52,8 @@ function formatSize(size) {
|
|||||||
export default function FilesList({files}) {
|
export default function FilesList({files}) {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const {mode} = useColorScheme()
|
const {mode} = useColorScheme()
|
||||||
const [audioMeta, setAudioMeta] = useState({})
|
const [audioMeta, setAudioMeta] = useState(audioMetaCache)
|
||||||
|
const [downloading, setDownloading] = useState({})
|
||||||
|
|
||||||
const musicFiles = files.filter(file => file.mime.startsWith('audio'))
|
const musicFiles = files.filter(file => file.mime.startsWith('audio'))
|
||||||
const pdfFiles = files.filter(file => file.mime === 'application/pdf')
|
const pdfFiles = files.filter(file => file.mime === 'application/pdf')
|
||||||
@@ -77,14 +79,21 @@ export default function FilesList({files}) {
|
|||||||
const results = {}
|
const results = {}
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
audioFiles.map(async file => {
|
audioFiles.map(async file => {
|
||||||
|
if (file.id in audioMetaCache) {
|
||||||
|
results[file.id] = audioMetaCache[file.id]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${apiUrl}${file.url}`, {
|
const response = await fetch(`${apiUrl}${file.url}`, {
|
||||||
headers: {Range: 'bytes=0-262143'},
|
headers: {Range: 'bytes=0-262143'},
|
||||||
})
|
})
|
||||||
const buffer = await response.arrayBuffer()
|
const buffer = await response.arrayBuffer()
|
||||||
const meta = await mm.parseBuffer(new Uint8Array(buffer), {mimeType: file.mime, skipCovers: true})
|
const meta = await mm.parseBuffer(new Uint8Array(buffer), {mimeType: file.mime, skipCovers: true})
|
||||||
|
audioMetaCache[file.id] = meta.format
|
||||||
results[file.id] = meta.format
|
results[file.id] = meta.format
|
||||||
} catch {
|
} catch {
|
||||||
|
audioMetaCache[file.id] = null
|
||||||
results[file.id] = null
|
results[file.id] = null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -140,9 +149,42 @@ export default function FilesList({files}) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClick = (e, url, fileName) => {
|
const handleClick = async (e, url, fileName, fileId) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
FileSaver.saveAs(url, fileName)
|
if (fileId in downloading) return
|
||||||
|
|
||||||
|
setDownloading(prev => ({...prev, [fileId]: 0}))
|
||||||
|
try {
|
||||||
|
const response = await fetch(url)
|
||||||
|
const contentLength = +response.headers.get('content-length')
|
||||||
|
const reader = response.body.getReader()
|
||||||
|
const chunks = []
|
||||||
|
let received = 0
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const {done, value} = await reader.read()
|
||||||
|
if (done) break
|
||||||
|
chunks.push(value)
|
||||||
|
received += value.length
|
||||||
|
if (contentLength) {
|
||||||
|
setDownloading(prev => ({...prev, [fileId]: Math.round(received / contentLength * 100)}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob(chunks, {type: response.headers.get('content-type')})
|
||||||
|
const blobUrl = URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = blobUrl
|
||||||
|
a.download = fileName
|
||||||
|
a.click()
|
||||||
|
URL.revokeObjectURL(blobUrl)
|
||||||
|
} finally {
|
||||||
|
setDownloading(prev => {
|
||||||
|
const next = {...prev}
|
||||||
|
delete next[fileId]
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -177,13 +219,24 @@ export default function FilesList({files}) {
|
|||||||
<Link
|
<Link
|
||||||
href='#'
|
href='#'
|
||||||
underline='hover'
|
underline='hover'
|
||||||
sx={{fontWeight: 'bold'}}
|
sx={{fontWeight: 'bold', pointerEvents: file.id in downloading ? 'none' : 'auto'}}
|
||||||
aria-label='download'
|
aria-label='download'
|
||||||
onClick={e => handleClick(e, `${apiUrl}${file.url}`, file.name)}
|
onClick={e => handleClick(e, `${apiUrl}${file.url}`, file.name, file.id)}
|
||||||
>
|
>
|
||||||
{file.name}
|
{file.name}
|
||||||
</Link>
|
</Link>
|
||||||
<small style={{marginLeft: 3}}>({formatSize(file.size)})</small>
|
<small style={{marginLeft: 3}}>({formatSize(file.size)})</small>
|
||||||
|
{file.id in downloading && (
|
||||||
|
<Box sx={{mt: 0.5}}>
|
||||||
|
<LinearProgress
|
||||||
|
variant={downloading[file.id] > 0 ? 'determinate' : 'indeterminate'}
|
||||||
|
value={downloading[file.id]}
|
||||||
|
/>
|
||||||
|
<Typography variant='caption' sx={{color: 'text.secondary'}}>
|
||||||
|
{downloading[file.id]} %
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user