diff --git a/components/files/files-dialog.js b/components/files/files-dialog.js
new file mode 100644
index 0000000..59d70aa
--- /dev/null
+++ b/components/files/files-dialog.js
@@ -0,0 +1,52 @@
+import {useState} from 'react'
+import PropTypes from 'prop-types'
+import Button from '@mui/material/Button'
+import Dialog from '@mui/material/Dialog'
+import DialogActions from '@mui/material/DialogActions'
+import DialogContent from '@mui/material/DialogContent'
+import useMediaQuery from '@mui/material/useMediaQuery'
+import {useTheme} from '@mui/material/styles'
+
+import DownloadForOfflineIcon from '@mui/icons-material/DownloadForOffline'
+import FilesList from './files-list'
+
+export default function FilesDialog({files}) {
+ const [open, setOpen] = useState(false)
+ const theme = useTheme()
+ const fullScreen = useMediaQuery(theme.breakpoints.down('md'))
+
+ const handleClickOpen = () => {
+ setOpen(true)
+ }
+
+ const handleClose = () => {
+ setOpen(false)
+ }
+
+ return (
+ <>
+ } sx={{marginBottom: 2}} variant='outlined' onClick={handleClickOpen}>
+ Télécharger
+
+
+ >
+ )
+}
+
+FilesDialog.propTypes = {
+ files: PropTypes.array.isRequired
+}
diff --git a/components/files/files-list.js b/components/files/files-list.js
new file mode 100644
index 0000000..3c0ba78
--- /dev/null
+++ b/components/files/files-list.js
@@ -0,0 +1,248 @@
+import PropTypes from 'prop-types'
+import List from '@mui/material/List'
+import ListSubheader from '@mui/material/ListSubheader'
+import Typography from '@mui/material/Typography'
+import Box from '@mui/material/Box'
+import {useTheme, useColorScheme, styled} from '@mui/material/styles'
+import Table from '@mui/material/Table'
+import TableHead from '@mui/material/TableHead'
+import TableBody from '@mui/material/TableBody'
+import TableCell, {tableCellClasses} from '@mui/material/TableCell'
+import TableRow from '@mui/material/TableRow'
+import TableContainer from '@mui/material/TableContainer'
+import Paper from '@mui/material/Paper'
+import FileSaver from 'file-saver'
+import DescriptionIcon from '@mui/icons-material/Description'
+import LibraryMusicIcon from '@mui/icons-material/LibraryMusic'
+import {Link} from '@mui/material'
+
+const apiUrl = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
+
+const StyledTableCell = styled(TableCell)(({theme}) => ({
+ [`&.${tableCellClasses.head}`]: {
+ backgroundColor: theme.palette.common.black,
+ color: theme.palette.common.white,
+ },
+ [`&.${tableCellClasses.body}`]: {
+ fontSize: 14,
+ },
+}))
+
+const StyledTableRow = styled(TableRow)(({theme}) => ({
+ '&:nth-of-type(odd)': {
+ backgroundColor: theme.palette.action.hover,
+ },
+ '&:last-child td, &:last-child th': {
+ border: 0,
+ },
+}))
+
+function formatSize(size) {
+ if (size < 1000) {
+ return Math.round(size) + ' Kb'
+ }
+
+ const mbSize = size / 1000
+ return Math.round(mbSize) + ' Mb'
+}
+
+export default function FilesList({files}) {
+ const theme = useTheme()
+ const {mode} = useColorScheme()
+
+ const musicFiles = files.filter(file => file.attributes.mime.startsWith('audio'))
+ const pdfFiles = files.filter(file => file.attributes.mime === 'application/pdf')
+
+ const sortedMusicFiles = musicFiles.sort((a, b) => {
+ const extensionOrder = {
+ '.flac': 0,
+ '.ogg': 1,
+ '.aac': 2,
+ '.mp3': 3
+ }
+ return extensionOrder[a.attributes.ext.toLowerCase()] - extensionOrder[b.attributes.ext.toLowerCase()]
+ })
+
+ const handleClick = (e, url, fileName) => {
+ e.stopPropagation()
+ FileSaver.saveAs(url, fileName)
+ }
+
+ const getQuality = (extension, caption) => {
+ switch (extension) {
+ case '.ogg':
+ case '.aac':
+ case '.mp3':
+ return (
+ Faible
+ )
+ case '.flac':
+ if (caption === 'MAX') {
+ return (
+ {caption}
+ )
+ }
+
+ if (caption === 'HAUTE') {
+ return (
+ {caption}
+ )
+ }
+
+ return (
+ Haute
+ )
+
+ default:
+ return
+ }
+ }
+
+ return (
+ <>
+ {musicFiles.length > 0 && (
+
+
+
+
+ Musiques
+
+
+
+
+ MAX : Jusqu’à 24-bit, 96 kHz, ≃ 3000 kbps (flac)
HAUTE : 16 bits, ≃ 900 kbps (flac)
FAIBLE : 320 kbps (ogg / aac / mp3)
+
+
+ QUALITÉ
+
+
+
+
+ {sortedMusicFiles.map(file => (
+
+
+ {getQuality(file.attributes.ext.toLowerCase(), file?.attributes?.caption?.toUpperCase())}
+
+
+ handleClick(e, `${apiUrl}${file.attributes.url}`, file.attributes.name)}
+ >
+ {file.attributes.name}
+
+ ({formatSize(file.attributes.size)})
+
+
+ ))}
+
+
+
+
+ )}
+
+ {pdfFiles.length > 0 && (
+
+
+
+
+ Paroles
+
+
+
+
+
+
+ LANGUE
+
+
+
+
+ {pdfFiles.map(file => (
+
+
+
+ {file.attributes.caption}
+
+
+
+ handleClick(e, `${apiUrl}${file.attributes.url}`, file.attributes.name)}
+ >
+ {file.attributes.name}
+
+ ({formatSize(file.attributes.size)})
+
+
+ ))}
+
+
+
+
+ )}
+ >
+ )
+}
+
+FilesList.propTypes = {
+ files: PropTypes.array.isRequired
+}
diff --git a/components/teks/teks.js b/components/teks/teks.js
index 515fcf3..6dc349e 100644
--- a/components/teks/teks.js
+++ b/components/teks/teks.js
@@ -16,6 +16,7 @@ import ExplicitIcon from '@mui/icons-material/Explicit'
import {formatJsonString, getAlias} from '../../lib/utils/format'
import LicenseModal from '../cc/license-modal'
+import FilesDialog from '../files/files-dialog'
import EntegreMizik from './entegre-mizik'
import OkiMizik from './oki-mizik'
import DiferansDialog from './diferans-dialog'
@@ -183,6 +184,9 @@ export default function Teks({parole}) {
)}
+ {parole?.files?.data && (
+
+ )}
{(parole.okiMizikID || parole.streamAudio.length > 0 || parole.gadeEmbed) && (
diff --git a/lib/oki-api.js b/lib/oki-api.js
index 4a976e6..59f18cd 100644
--- a/lib/oki-api.js
+++ b/lib/oki-api.js
@@ -42,6 +42,9 @@ export async function jwennTeksEpiSlug(slug) {
couverture: {
populate: '*'
},
+ files: {
+ populate: '*'
+ },
artistes: {
fields: ['alias', 'slug', 'musicBrainzUrl', 'photo'],
populate: {
diff --git a/package.json b/package.json
index b0409af..6bc59ce 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"date-fns": "^2.16.1",
"dotenv": "^16.0.0",
"express": "^4.17.1",
+ "file-saver": "^2.0.5",
"lodash": "^4.17.21",
"next": "^14.1.4",
"next-auth": "^4.22.1",
diff --git a/yarn.lock b/yarn.lock
index 9dc6eec..87ea8c4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4631,6 +4631,11 @@ file-entry-cache@^6.0.1:
dependencies:
flat-cache "^3.0.4"
+file-saver@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
+ integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
+
filelist@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"