From 865c9cc345aa105714dfe3ccf1d1c0a9a6a75f7f Mon Sep 17 00:00:00 2001 From: piotrruss Date: Sun, 19 Sep 2021 12:47:21 +0200 Subject: youtube & player apps fixes --- apps/Player/components/App.js | 82 ++++++++++++++++++++---- apps/Player/components/Buttons.js | 1 - apps/Player/components/Video.js | 63 +++++++++++++++---- apps/Player/styles/_player.scss | 121 ++++++++++++++++++++++++++--------- apps/Youtube/components/App.js | 128 ++++++++++++++++++++------------------ components/App.js | 6 +- components/Header.js | 17 +++-- configs/translations.js | 52 +++++++++------- helpers/windowActions.js | 6 +- package-lock.json | 31 ++++++--- package.json | 3 +- pages/api/youtube/player.js | 32 ---------- pages/api/youtube/playlist.js | 26 ++++++++ pages/api/youtube/search.js | 9 ++- pages/api/youtube/video.js | 26 ++++++++ pages/index.js | 9 ++- styles/global.scss | 5 ++ styles/global/_themes.scss | 6 +- styles/global/_window.scss | 5 -- styles/main/_header.scss | 29 ++++++++- 20 files changed, 457 insertions(+), 200 deletions(-) delete mode 100644 pages/api/youtube/player.js create mode 100644 pages/api/youtube/playlist.js create mode 100644 pages/api/youtube/video.js diff --git a/apps/Player/components/App.js b/apps/Player/components/App.js index f7b0557..6633bc8 100644 --- a/apps/Player/components/App.js +++ b/apps/Player/components/App.js @@ -1,15 +1,20 @@ import styles from '../styles/Player.module.scss' import { useState, useEffect } from 'react' import useSettings from 'hooks/useSettings' +import useMediaQuery from 'hooks/useMediaQuery' import Video from './Video' import Buttons from './Buttons' import { Splash } from 'components' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faList, faTrashAlt, faCaretSquareRight, faInfinity, faAlignJustify } from '@fortawesome/free-solid-svg-icons' const App = ({ list }) => { const { t } = useSettings() + const smallDevice = useMediaQuery('(max-width: 40em)') const [current, setCurrent] = useState(null) const [playlist, setPlaylist] = useState(null) const [showPlaylist, setShowPlaylist] = useState(false) + const [details, setDetails] = useState({ show: false }) useEffect(() => { if (list) { @@ -40,16 +45,14 @@ const App = ({ list }) => { useEffect(() => { if (playlist) { - const { items, enqueue } = playlist - if (typeof window !== 'undefined' && playlist && playlist.length > 0) { - window.localStorage.setItem('playlist', JSON.stringify(enqueue ? [...playlist, ...items] : items)) + window.localStorage.setItem('playlist', JSON.stringify(playlist)) setShowPlaylist(true) } if (current === null && list) { setCurrent( - enqueue + list.enqueue ? playlist && playlist.length > 1 ? playlist.length - 1 : 0 @@ -59,7 +62,35 @@ const App = ({ list }) => { } }, [playlist]) - if (!playlist) return + useEffect(() => { + smallDevice && showPlaylist && setDetails(d => ({ ...d, show: false })) + }, [showPlaylist]) + + useEffect(() => { + smallDevice && details && details.show && setShowPlaylist(false) + }, [details && details.show]) + + const remove = (e, i) => { + e.stopPropagation() + if (current === i) { + setCurrent(null) + } + setPlaylist(p => p.filter((_, j) => j !== i)) + if (current > i) { + setCurrent(c => c - 1) + } + } + + if (!playlist) { + return ( + <> +
+
+ +
+ + ) + } return ( <> @@ -69,16 +100,24 @@ const App = ({ list }) => { onClick={() => { setShowPlaylist(p => !p) }} className={current ? 'active' : null} > - {t('player_playlist_default')} +
-
{}}>+
+ +
{ setDetails(d => ({ ...d, show: !d.show })) }}> + +
- {current !== null && ( -
@@ -89,11 +128,19 @@ const App = ({ list }) => { playlist.map((item, i) => (
  • { setCurrent(i) }} - className={current === i ? styles.activeItem : ''} + className={current === i ? 'active' : ''} key={item.id} > + {(i + 1) + '.'} - {item.title} + {item.title} + remove(e, i)}> + +
  • )) ) @@ -102,7 +149,18 @@ const App = ({ list }) => { ) } -
    setShowPlaylist(false)}><
    +
    +
    + {details && ( +
    +
    +                {details.title}
    +              
    +
    +                {details.description}
    +              
    +
    + )}
    diff --git a/apps/Player/components/Buttons.js b/apps/Player/components/Buttons.js index 14452a0..efc8555 100644 --- a/apps/Player/components/Buttons.js +++ b/apps/Player/components/Buttons.js @@ -3,7 +3,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' const Buttons = ({ current, setCurrent, playlist }) => ( <> -
    0 ? '' : 'iconOff'} onClick={() => { current !== null && current > 0 && setCurrent(c => c - 1) }} diff --git a/apps/Player/components/Video.js b/apps/Player/components/Video.js index 7152967..7028f1f 100644 --- a/apps/Player/components/Video.js +++ b/apps/Player/components/Video.js @@ -1,25 +1,66 @@ -import { useRef } from 'react' +import { useState, useEffect, useRef } from 'react' import Splash from 'components/Splash' +import fetchJson from 'helpers/fetchJson' -const Video = ({ playlist, current, setCurrent }) => { - if (!playlist) return null - +const Video = ({ playlist, current, setCurrent, audioOnly = false, setDetails }) => { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(null) const videoEl = useRef() - const sources = playlist[current]?.sources const handleEnd = () => { setCurrent(current === playlist.length - 1 ? null : current + 1) } + useEffect(() => { + setLoading(true) + if (current === null) { + setData(null) + setDetails(d => ({ ...d, show: false })) + } + + switch (playlist[current].type.split('_')[0]) { + case 'yt': + fetchJson('/api/youtube/video', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id: playlist[current].id }) + }) + .then(v => { + setData({ + id: v.videoDetails.videoId, + formats: v.formats + .filter(v => !v.isHLS && v.hasAudio && (audioOnly ? !v.hasVideo : v.hasVideo)) + .sort((a, b) => audioOnly ? a.audioBitrate < b.audioBitrate : a.bitrate < b.bitrate) + }) + setDetails(d => ({ + ...d, + title: v.videoDetails.title, + description: v.videoDetails.description + })) + }) + .catch(() => console.log('error fetching video')) + .finally(() => setLoading(false)) + break + default: + } + }, [playlist && playlist[current].id]) + return ( - sources + data && !loading ? ( -
    diff --git a/components/App.js b/components/App.js index 9faa9c9..f6a297b 100644 --- a/components/App.js +++ b/components/App.js @@ -1,11 +1,13 @@ import { useEffect, useRef } from 'react' +import useApps from 'hooks/useApps' import useSettings from 'hooks/useSettings' import useMediaQuery from 'hooks/useMediaQuery' import { close, toggleMin, toggleMax, move, focus } from 'helpers/windowActions' import { faArrowUp, faExpandAlt, faTimes, faCompressAlt } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -const App = ({ children, app, setApps }) => { +const App = ({ children, app }) => { + const { apps, setApps } = useApps() const { t } = useSettings() const winRef = useRef(null) const forceMax = useMediaQuery(`(max-width: ${app.width}), (max-height: ${app.height})`) @@ -36,7 +38,7 @@ const App = ({ children, app, setApps }) => {
    {children}
    {app.buttons.includes('min') && ( - { e.preventDefault(); e.stopPropagation(); toggleMin(app.name, setApps) }}> + { e.preventDefault(); e.stopPropagation(); toggleMin(app.name, apps, setApps) }}> )} diff --git a/components/Header.js b/components/Header.js index 87c31e8..5e4ff12 100644 --- a/components/Header.js +++ b/components/Header.js @@ -8,7 +8,7 @@ import { focus, toggleMin, open, close } from 'helpers/windowActions' import appList from 'configs/appList' import useSettings from 'hooks/useSettings' import useApps from 'hooks/useApps' -import { faTimes } from '@fortawesome/free-solid-svg-icons' +import { faArrowUp, faTimes } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' const Header = () => { @@ -30,7 +30,7 @@ const Header = () => { const handleClick = (app, setApps) => { if (app.min) { - toggleMin(app.name, setApps) + toggleMin(app.name, apps, setApps) } focus(app.name, setApps) } @@ -54,13 +54,18 @@ const Header = () => { onClick={() => handleClick(app, setApps)} > a && a.name === app.name) === apps.length - 1 ? 'active' : '' + } style={{ - ...apps.findIndex(a => a && a.name === app.name) === apps.length - 1 ? { fontWeight: 600 } : {}, ...app.min ? { color: '#888' } : {} }} > - {t(app.name)} - close(app.name, setApps)}> + {t(app.name)} + { e.stopPropagation(); toggleMin(app.name, apps, setApps) }}> + + + { e.stopPropagation(); close(app.name, setApps) }}> @@ -69,7 +74,7 @@ const Header = () => { }) }
  • - setShowApps(false)} className='window__button'>{t('close')} +
    setShowApps(false)} className='window__button'>{t('close')}
  • diff --git a/configs/translations.js b/configs/translations.js index ff7de3c..30d6432 100644 --- a/configs/translations.js +++ b/configs/translations.js @@ -76,12 +76,13 @@ const translations = { notes_remove_confirm: 'Do you want to remove note?', player_playlist_empty: 'Your playlist is empty', player_playlist_default: 'Default playlist', - youtube_videos: 'Videos', - youtube_music: 'Music', - youtube_live: 'Live', - youtube_channels: 'Channels', - youtube_playlists: 'Playlists', - youtube_enqueue: 'Enqueue' + yt_video: 'Videos', + yt_video_count: ' videos', + yt_live: 'Live', + yt_playlist: 'Playlists', + yt_enqueue: 'Enqueue', + yt_views: 'views', + yt_watching: 'watching' }, pl: { register_user: 'Zarejestruj użytkownika', @@ -160,12 +161,13 @@ const translations = { notes_remove_confirm: 'Czy na pewno chcesz usunąć notatkę?', player_playlist_empty: 'Lista otwarzania jest pusta', player_playlist_default: 'Domyślna lista', - youtube_videos: 'Wideo', - youtube_music: 'Muzyka', - youtube_live: 'Na żywo', - youtube_channels: 'Kanały', - youtube_playlists: 'Playlisty', - youtube_enqueue: 'Dodaj do listy' + yt_video: 'Wideo', + yt_video_count: ' klipów', + yt_live: 'Na żywo', + yt_playlist: 'Playlisty', + yt_enqueue: 'Dodaj do listy', + yt_views: 'wyświetleń', + yt_watching: 'oglądających' }, es: { register_user: 'Registrar al usuario', @@ -244,12 +246,13 @@ const translations = { notes_remove_confirm: '¿Estás seguro de que deseas eliminar la nota?', player_playlist_empty: 'La lista está vacía', player_playlist_default: 'Lista predeterminada', - youtube_videos: 'Video', - youtube_music: 'Música', - youtube_live: 'En vivo', - youtube_channels: 'Canales', - youtube_playlists: 'Listas', - youtube_enqueue: 'Agregar' + yt_video: 'Video', + yt_video_count: ' clips', + yt_live: 'En vivo', + yt_playlist: 'Listas', + yt_enqueue: 'Agregar', + yt_views: 'visitas', + yt_watching: 'espectadores' }, de: { register_user: 'Registrieren Sie den Benutzer', @@ -328,12 +331,13 @@ const translations = { notes_remove_confirm: 'Möchten Sie die Notiz wirklich löschen? ', player_playlist_empty: 'Playlist ist leer', player_playlist_default: 'Standardliste', - youtube_videos: 'Video', - youtube_music: 'Musik', - youtube_live: 'Live', - youtube_channels: 'Kanäle', - youtube_playlists: 'Playlists', - youtube_enqueue: 'Hinzufügen' + yt_video: 'Video', + yt_video_count: ' Clips', + yt_live: 'Live', + yt_playlist: 'Playlists', + yt_enqueue: 'Hinzufügen', + yt_views: 'Aufrufe', + yt_watching: 'Zuschauer' } } diff --git a/helpers/windowActions.js b/helpers/windowActions.js index ac86ea5..7cc09be 100644 --- a/helpers/windowActions.js +++ b/helpers/windowActions.js @@ -30,11 +30,13 @@ export const unfocus = (appName, setApps) => { }) } -export const toggleMin = (appName, setApps) => { +export const toggleMin = (appName, apps, setApps) => { setApps(apps => ([ ...apps.map(a => a.name === appName ? { ...a, min: !a.min } : a) ])) - unfocus(appName, setApps) + apps.find(a => a.name === appName).min + ? focus(appName, setApps) + : unfocus(appName, setApps) } export const toggleMax = (appName, setApps) => setApps(apps => ([ diff --git a/package-lock.json b/package-lock.json index f34d1e2..68e2b95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "myapps", "version": "1.0.0", "license": "MIT", "dependencies": { @@ -23,7 +24,8 @@ "sass": "^1.35.1", "scrape-youtube": "^2.1.9", "swr": "^1.0.0", - "ytdl-core": "^4.9.1" + "ytdl-core": "^4.9.1", + "ytpl": "^2.2.3" }, "devDependencies": { "@next/eslint-plugin-next": "^11.1.2", @@ -1717,7 +1719,6 @@ "dependencies": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -4047,8 +4048,7 @@ "bson": "^1.1.4", "denque": "^1.4.1", "optional-require": "^1.0.3", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" + "safe-buffer": "^5.1.2" }, "engines": { "node": ">=4" @@ -4195,10 +4195,6 @@ "@next/polyfill-module": "11.1.2", "@next/react-dev-overlay": "11.1.2", "@next/react-refresh-utils": "11.1.2", - "@next/swc-darwin-arm64": "11.1.2", - "@next/swc-darwin-x64": "11.1.2", - "@next/swc-linux-x64-gnu": "11.1.2", - "@next/swc-win32-x64-msvc": "11.1.2", "@node-rs/helper": "1.2.1", "assert": "2.0.0", "ast-types": "0.13.2", @@ -6286,6 +6282,17 @@ "engines": { "node": ">=10" } + }, + "node_modules/ytpl": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/ytpl/-/ytpl-2.2.3.tgz", + "integrity": "sha512-d18HibT8wzEWzWsXFS6u6kUKCCFL20roFVwpjyGCzl+nP8sOAz5xSLafxLflkkoeXJU8AFuO7BEsJXIREASvFQ==", + "dependencies": { + "miniget": "^4.2.1" + }, + "engines": { + "node": ">=8" + } } }, "dependencies": { @@ -11072,6 +11079,14 @@ "miniget": "^4.0.0", "sax": "^1.1.3" } + }, + "ytpl": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/ytpl/-/ytpl-2.2.3.tgz", + "integrity": "sha512-d18HibT8wzEWzWsXFS6u6kUKCCFL20roFVwpjyGCzl+nP8sOAz5xSLafxLflkkoeXJU8AFuO7BEsJXIREASvFQ==", + "requires": { + "miniget": "^4.2.1" + } } } } diff --git a/package.json b/package.json index a331983..0cf9415 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "sass": "^1.35.1", "scrape-youtube": "^2.1.9", "swr": "^1.0.0", - "ytdl-core": "^4.9.1" + "ytdl-core": "^4.9.1", + "ytpl": "^2.2.3" }, "devDependencies": { "@next/eslint-plugin-next": "^11.1.2", diff --git a/pages/api/youtube/player.js b/pages/api/youtube/player.js deleted file mode 100644 index a6d3a7e..0000000 --- a/pages/api/youtube/player.js +++ /dev/null @@ -1,32 +0,0 @@ -import withSession from 'hocs/withSession' -import ytdl from 'ytdl-core' - -const getId = url => { - const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/ - const match = url.match(regExp) - return (match && match[7].length === 11) ? match[7] : false -} - -export default withSession(async (req, res) => { - switch (req.method) { - case 'POST': - try { - const user = req.session.get('user') - const { url } = req.body - - if (!user || !user?.isVerified || !url) { - throw new Error('Something went wrong') - } - - const info = await ytdl.getInfo(getId(url)) - - res.status(200).json(info) - } catch (error) { - res.status(400).json([]) - } - break - default: - res.status(400).send() - break - } -}) diff --git a/pages/api/youtube/playlist.js b/pages/api/youtube/playlist.js new file mode 100644 index 0000000..e75e00d --- /dev/null +++ b/pages/api/youtube/playlist.js @@ -0,0 +1,26 @@ +import withSession from 'hocs/withSession' +import ytpl from 'ytpl' + +export default withSession(async (req, res) => { + switch (req.method) { + case 'POST': + try { + const user = req.session.get('user') + const { id } = req.body + + if (!user || !user?.isVerified || !id) { + throw new Error('Something went wrong') + } + + const info = await ytpl(id, { gl: user.language || 'en' }) + + res.status(200).json(info) + } catch (error) { + res.status(400).json([]) + } + break + default: + res.status(400).send() + break + } +}) diff --git a/pages/api/youtube/search.js b/pages/api/youtube/search.js index b17d008..f1b461f 100644 --- a/pages/api/youtube/search.js +++ b/pages/api/youtube/search.js @@ -12,9 +12,14 @@ export default withSession(async (req, res) => { throw new Error('Something went wrong') } - const video = await youtube.search(quote) + const results = await youtube.search(quote[0], { + ...quote[1], + requestOptions: { + headers: { 'Accept-Language': user.language || 'en' } + } + }) - res.status(200).json(video) + res.status(200).json(results) } catch (error) { res.status(400).json([]) } diff --git a/pages/api/youtube/video.js b/pages/api/youtube/video.js new file mode 100644 index 0000000..aeb2e8b --- /dev/null +++ b/pages/api/youtube/video.js @@ -0,0 +1,26 @@ +import withSession from 'hocs/withSession' +import ytdl from 'ytdl-core' + +export default withSession(async (req, res) => { + switch (req.method) { + case 'POST': + try { + const user = req.session.get('user') + const { id } = req.body + + if (!user || !user?.isVerified || !id) { + throw new Error('Something went wrong') + } + + const info = await ytdl.getInfo(id, { lang: user.language || 'en' }) + + res.status(200).json(info) + } catch (error) { + res.status(400).json([]) + } + break + default: + res.status(400).send() + break + } +}) diff --git a/pages/index.js b/pages/index.js index af25f7b..a13a840 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,4 +1,5 @@ import styles from 'styles/Main.module.scss' +import { useMemo } from 'react' import Image from 'next/image' import useUser from 'hooks/useUser' import useSettings from 'hooks/useSettings' @@ -70,7 +71,7 @@ const Home = () => { app={app} setApps={setApps} > - + ) })} @@ -79,4 +80,10 @@ const Home = () => { ) } +const AppContainer = ({ AppComponent, appProps }) => { + const Component = useMemo(() => , [appProps]) + + return Component +} + export default Home diff --git a/styles/global.scss b/styles/global.scss index f72a66b..ad4a51a 100644 --- a/styles/global.scss +++ b/styles/global.scss @@ -53,3 +53,8 @@ textarea, input { display: none!important; } } + +.active { + color: var(--color-text-alt)!important; + font-weight: 600; +} diff --git a/styles/global/_themes.scss b/styles/global/_themes.scss index e0b146f..4127c4c 100644 --- a/styles/global/_themes.scss +++ b/styles/global/_themes.scss @@ -2,7 +2,7 @@ --color-bg: #50a3a2; --color-bg-alt: #53e3a6; --color-text: #333; - --color-text-alt: #111; + --color-text-alt: #000; --color-decor: #ccc; --color-glass: rgb(151, 215, 200, .9); --color-glass-alt: rgba(255, 255, 255, 0.5); @@ -31,7 +31,7 @@ --color-bg: #71b0d7; --color-bg-alt: #a4e4fd; --color-text: #333; - --color-text-alt: #111; + --color-text-alt: #000; --color-decor: #ccc; --color-glass: rgba(151, 192, 230, 0.9); --color-glass-alt: rgba(255, 255, 255, 0.5); @@ -59,7 +59,7 @@ .black { --color-bg: #000; --color-bg-alt: #222; - --color-text: #eee; + --color-text: #ddd; --color-text-alt: #fff; --color-decor: #ccc; --color-glass: rgb(20, 20, 20, .9); diff --git a/styles/global/_window.scss b/styles/global/_window.scss index cf78752..9f224a9 100644 --- a/styles/global/_window.scss +++ b/styles/global/_window.scss @@ -137,11 +137,6 @@ pointer-events: none; } - .active { - color: var(--color-text-alt); - font-weight: 600; - } - @media(max-width: 40em) { height: 3em; overflow: auto; diff --git a/styles/main/_header.scss b/styles/main/_header.scss index e6ac70f..ea75f5e 100644 --- a/styles/main/_header.scss +++ b/styles/main/_header.scss @@ -28,10 +28,33 @@ & > li { display: inline-block; - & > span > span { - padding: .75em; + @media(max-width: 40em) { + & > span:first-of-type { + display: flex!important; + width: 100%; + } + + & > span > span:first-of-type { + flex-grow: 1; + } + } + + & > span > span:nth-of-type(2), + & > span > span:nth-of-type(3) { + margin: .25em .25em .25em .5em; + padding: .5em; display: inline-block; - margin-left: .5em; + border-top: 1px solid var(--color-window-border-top); + border-bottom: 1px solid var(--color-window-border-bottom); + border-radius: .5em; + background-color: var(--color-window-content); + } + + & > span > span:nth-of-type(2) { + margin-left: 1em; + } + + & > span > span:nth-of-type(3) { color: var(--color-error); } -- cgit v1.2.3