diff options
29 files changed, 749 insertions, 57 deletions
diff --git a/apps/Notes/components/List.js b/apps/Notes/components/List.js index 05d0711..0e6c8fa 100644 --- a/apps/Notes/components/List.js +++ b/apps/Notes/components/List.js @@ -60,22 +60,22 @@ const List = () => { </thead> <tbody> { - notes.length > 0 - ? (notes.sort(sortFn).map(note => ( - <ListItem - key={note._id} - note={note} - setAction={setAction} - setFetchedNote={setFetchedNote} - setLoading={setLoading} - /> - ))) - : ( - <tr> - <td>{t('notes_list_empty')}</td> - </tr> - ) -} + notes.length > 0 + ? (notes.sort(sortFn).map(note => ( + <ListItem + key={note._id} + note={note} + setAction={setAction} + setFetchedNote={setFetchedNote} + setLoading={setLoading} + /> + ))) + : ( + <tr> + <td>{t('notes_list_empty')}</td> + </tr> + ) + } </tbody> </table> </div> diff --git a/apps/Player/components/App.js b/apps/Player/components/App.js new file mode 100644 index 0000000..5879111 --- /dev/null +++ b/apps/Player/components/App.js @@ -0,0 +1,107 @@ +import styles from '../styles/Player.module.scss' +import { useState, useEffect } from 'react' +import useSettings from 'hooks/useSettings' +import Video from './Video' +import Buttons from './Buttons' +import { Splash } from 'components' + +const App = ({ list }) => { + const { t } = useSettings() + const [current, setCurrent] = useState(null) + const [playlist, setPlaylist] = useState(null) + const [showPlaylist, setShowPlaylist] = useState(false) + + useEffect(() => { + if (list) { + const { items, enqueue } = list + + setPlaylist( + p => enqueue && p + ? p.some(x => items.some(y => x.id === y.id)) + ? p + : [...p, ...items] + : items + ) + + if (enqueue) { + setShowPlaylist(true) + } + } else { + if (typeof window !== 'undefined') { + setPlaylist(JSON.parse(window.localStorage.getItem('playlist'))) + } + } + }, [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)) + setShowPlaylist(true) + } + + if (current === null && list) { + setCurrent( + enqueue + ? playlist && playlist.length > 1 + ? playlist.length - 1 + : 0 + : 0 + ) + } + } + }, [playlist]) + + if (!playlist) return <Splash /> + + return ( + <> + <div className='window__submenu'> + <div> + <div + onClick={() => { setShowPlaylist(p => !p) }} + className={current ? 'active' : null} + > + {t('player_playlist_default')} + </div> + <div onClick={() => {}}>+</div> + <Buttons current={current} setCurrent={setCurrent} playlist={playlist} /> + </div> + </div> + <div className={styles.player}> + <div> + {current !== null && ( + <Video playlist={playlist} current={current} setCurrent={setCurrent} /> + )} + </div> + <div style={showPlaylist ? {} : { transform: 'translateX(-110%)' }}> + <ul> + { + playlist && playlist.length > 0 + ? ( + playlist.map((item, i) => ( + <li + onClick={() => { setCurrent(i) }} + className={current === i ? styles.activeItem : ''} + key={item.id} + > + <span>{(i + 1) + '.'}</span> + {item.title} + </li> + )) + ) + : ( + <li>{t('player_playlist_empty')}</li> + ) + } + </ul> + <div onClick={() => setShowPlaylist(false)}><</div> + </div> + </div> + </> + ) +} + +export default App diff --git a/apps/Player/components/Buttons.js b/apps/Player/components/Buttons.js new file mode 100644 index 0000000..14452a0 --- /dev/null +++ b/apps/Player/components/Buttons.js @@ -0,0 +1,34 @@ +import { faStepForward, faStepBackward, faPlay, faStop } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +const Buttons = ({ current, setCurrent, playlist }) => ( + <> + <span /> + <div + className={current !== null && current > 0 ? '' : 'iconOff'} + onClick={() => { current !== null && current > 0 && setCurrent(c => c - 1) }} + > + <FontAwesomeIcon icon={faStepBackward} /> + </div> + <div + className={current === null ? 'iconOff' : ''} + onClick={() => { current !== null && setCurrent(null) }} + > + <FontAwesomeIcon icon={faStop} /> + </div> + <div + className={current === null ? '' : 'iconOff'} + onClick={() => { current === null && setCurrent(0) }} + > + <FontAwesomeIcon icon={faPlay} /> + </div> + <div + className={current !== null && current < playlist.length - 1 ? '' : 'iconOff'} + onClick={() => { current !== null && current < playlist.length - 1 && setCurrent(c => c + 1) }} + > + <FontAwesomeIcon icon={faStepForward} /> + </div> + </> +) + +export default Buttons diff --git a/apps/Player/components/Video.js b/apps/Player/components/Video.js new file mode 100644 index 0000000..7152967 --- /dev/null +++ b/apps/Player/components/Video.js @@ -0,0 +1,32 @@ +import { useRef } from 'react' +import Splash from 'components/Splash' + +const Video = ({ playlist, current, setCurrent }) => { + if (!playlist) return null + + const videoEl = useRef() + const sources = playlist[current]?.sources + + const handleEnd = () => { + setCurrent(current === playlist.length - 1 ? null : current + 1) + } + + return ( + sources + ? ( + <video onEnded={handleEnd} ref={videoEl} key={playlist[current]?.id} controls autoPlay> + { + sources.map(s => ( + <source src={s.url} type={s.mimeType} key={s.url} /> + )) + } + Your browser does not support the video tag. + </video> + ) + : ( + <Splash /> + ) + ) +} + +export default Video diff --git a/apps/Player/index.js b/apps/Player/index.js new file mode 100644 index 0000000..7971cc2 --- /dev/null +++ b/apps/Player/index.js @@ -0,0 +1,3 @@ +import Player from './components/App' + +export default Player diff --git a/apps/Player/styles/Player.module.scss b/apps/Player/styles/Player.module.scss new file mode 100644 index 0000000..b882295 --- /dev/null +++ b/apps/Player/styles/Player.module.scss @@ -0,0 +1 @@ +@import "player"; diff --git a/apps/Player/styles/_player.scss b/apps/Player/styles/_player.scss new file mode 100644 index 0000000..8cf37cd --- /dev/null +++ b/apps/Player/styles/_player.scss @@ -0,0 +1,70 @@ +.player { + height: 100%; + width: 100%; + position: relative; + background-color: #000; + + & > div:nth-of-type(1) { + height: calc(100% - 2em); + width: 100%; + padding-bottom: .5em; + + video { + height: 100%; + width: 100%; + object-fit: contain; + } + } + + & > div:nth-of-type(2) { + width: auto; + min-width: 25em; + position: absolute; + top: 0; + left: 0; + bottom: 2em; + background-color: var(--color-glass); + transition: .3s transform; + border-top-right-radius: 1em; + border-bottom-right-radius: 1em; + border-right: 1px solid var(--color-window-border-bottom); + + li { + padding: .5em 2em .5em .5em; + white-space: nowrap; + + span { + padding-right: 1em; + } + + @media(hover: hover) { + &:hover { + background-color: var(--color-glass-alt); + } + } + } + + & > div { + position: absolute; + top: 50%; + right: -0.5em; + padding: 2em .5em; + border-radius: .5em; + transform: translateY(-50%); + background-color: var(--color-window-menu-alt); + color: var(--color-text-alt); + border: 1px solid var(--color-window-border-bottom); + transition: .3s background; + + &:hover { + background-color: var(--color-window-menu); + } + } + } + +} + +.activeItem { + color: var(--color-text-alt); + font-weight: 600; +} diff --git a/apps/Youtube/components/App.js b/apps/Youtube/components/App.js new file mode 100644 index 0000000..d902346 --- /dev/null +++ b/apps/Youtube/components/App.js @@ -0,0 +1,126 @@ +import styles from '../styles/Youtube.module.scss' +import { useState } from 'react' +import fetchJson from 'helpers/fetchJson' +import useApps from 'hooks/useApps' +import useSettings from 'hooks/useSettings' +import { open } from 'helpers/windowActions' +import appList from 'configs/appList' +import Splash from 'components/Splash' + +const time = t => new Date(t * 1000).toISOString().substr(11, 8) + +const App = () => { + const { apps, setApps } = useApps() + const [results, setResults] = useState() + const [searching, setSearching] = useState(false) + const [sending, setSending] = useState(false) + const [enqueue, setEnqueue] = useState(false) + const [type, setType] = useState('youtube_videos') + const { t } = useSettings() + + const handleSearch = async e => { + e.preventDefault() + e.stopPropagation() + setSearching(true) + const quote = e.currentTarget.quote.value + + const results = await fetchJson('/api/youtube/search', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ quote }) + }) + + results?.videos && setResults(results.videos) + setSearching(false) + } + + const handlePlay = async url => { + setSending(true) + const data = await fetchJson('/api/youtube/player', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url }) + }) + + const list = { + items: [{ + id: data.videoDetails.videoId, + title: data.videoDetails.title, + type: 'youtube-video', + sources: data.formats.filter(v => v.hasAudio).sort((a, b) => a.bitrate > b.bitrate) + }], + enqueue + } + + apps && apps.length > 0 && apps.some(a => a && a.name === 'Player') + ? setApps(prev => prev.map(a => a.name === 'Player' ? { ...a, props: { list } } : a)) + : open({ appName: 'Player', ...appList.Player }, setApps, { list }) + + setSending(false) + } + + return ( + <> + <div className='window__submenu'> + <div> + {[ + 'youtube_videos', + 'youtube_music', + 'youtube_live', + 'youtube_channels', + 'youtube_playlists' + ].map(y => ( + <div + className={y === type ? 'active' : ''} + onClick={() => { setType(y); setResults() }} + key={y} + > + {t(y)} + </div> + ))} + <span /> + <div + className={enqueue ? '' : 'off'} + onClick={() => { setEnqueue(e => !e) }} + > + {t('youtube_enqueue')} + </div> + </div> + </div> + <div className={styles.results}> + <form onSubmit={handleSearch}> + <input type='text' name='quote' /> + <input type='submit' className='window__button' value={t('search')} /> + </form> + <div className='window__scroll'> + { + searching + ? ( + <Splash /> + ) + : ( + <ul> + { + results && results.length > 0 && results.map(r => ( + <li key={r.id} onClick={() => handlePlay(r.link)}> + <img loading='lazy' src={r.thumbnail} width={96} height={72} /> + <p>{time(r.duration)}</p> + <div> + <p>{r.title}</p> + <p>Views: {r.views}, uploaded: {r.uploaded || '-'}</p> + <p>{r.channel?.name}</p> + </div> + </li> + )) + } + {sending && <Splash fixed />} + </ul> + ) + } + </div> + </div> + </> + ) +} + +export default App diff --git a/apps/Youtube/index.js b/apps/Youtube/index.js new file mode 100644 index 0000000..8c4906c --- /dev/null +++ b/apps/Youtube/index.js @@ -0,0 +1,3 @@ +import Youtube from './components/App' + +export default Youtube diff --git a/apps/Youtube/styles/Youtube.module.scss b/apps/Youtube/styles/Youtube.module.scss new file mode 100644 index 0000000..eeeaea9 --- /dev/null +++ b/apps/Youtube/styles/Youtube.module.scss @@ -0,0 +1 @@ +@import "results"; diff --git a/apps/Youtube/styles/_results.scss b/apps/Youtube/styles/_results.scss new file mode 100644 index 0000000..854d1e5 --- /dev/null +++ b/apps/Youtube/styles/_results.scss @@ -0,0 +1,68 @@ +.results { + height: 100%; + + form { + padding: .5em; + justify-content: center; + align-items: center; + display: flex; + + input[type=text] { + background-color: var(--color-window-content); + color: var(--color-text-alt); + margin: 1em .5em 0; + height: 2.5rem; + border: none; + border-radius: .5em; + padding: 0.5rem; + font-size: 1rem; + border: 1px dashed var(--color-window-buttons); + flex-shrink: 1; + flex-grow: 1; + + &:placeholder { + font: inherit; + } + } + } + + ul { + display: block; + + & > li { + padding: .5em; + display: flex; + position: relative; + + @media(hover: hover) { + &:hover { + background-color: var(--color-button-alt); + } + } + + & > p { + position: absolute; + font-size: .8em; + background-color: #000; + color: #fff; + border-radius: .5em; + padding: .25em; + bottom: 0.25em; + left: 2em; + } + + & > div { + margin-left: 1em; + display: flex; + flex-direction: column; + // justify-content: space-between; + + & > p:nth-of-type(2) { + margin: .25em 0 .5em; + font-size: .8em; + color: var(--color-decor); + } + } + } + } +} diff --git a/apps/index.js b/apps/index.js index 6e264fd..465761b 100644 --- a/apps/index.js +++ b/apps/index.js @@ -1,2 +1,4 @@ export { default as Notes } from './Notes' +export { default as Player } from './Player' export { default as Settings } from './Settings' +export { default as Youtube } from './Youtube' diff --git a/components/App.js b/components/App.js index 84268cb..9faa9c9 100644 --- a/components/App.js +++ b/components/App.js @@ -10,9 +10,7 @@ const App = ({ children, app, setApps }) => { const winRef = useRef(null) const forceMax = useMediaQuery(`(max-width: ${app.width}), (max-height: ${app.height})`) - useEffect(() => { - move(app, winRef, setApps) - }, [app, setApps]) + useEffect(() => { move(app, winRef, setApps) }, []) return ( <div @@ -38,17 +36,17 @@ const App = ({ children, app, setApps }) => { <div className='window__content'>{children}</div> <div className='window__title-buttons'> {app.buttons.includes('min') && ( - <span onClick={() => toggleMin(app.name, setApps)}> + <span onClick={e => { e.preventDefault(); e.stopPropagation(); toggleMin(app.name, setApps) }}> <FontAwesomeIcon icon={faArrowUp} /> </span> )} {app.buttons.includes('max') && !forceMax && ( - <span onClick={() => toggleMax(app.name, setApps)}> + <span onClick={e => { e.preventDefault(); e.stopPropagation(); toggleMax(app.name, setApps) }}> <FontAwesomeIcon icon={app.max ? faCompressAlt : faExpandAlt} /> </span> )} {app.buttons.includes('close') && ( - <span onClick={() => close(app.name, setApps)}> + <span onClick={e => { e.preventDefault(); e.stopPropagation(); close(app.name, setApps) }}> <FontAwesomeIcon icon={faTimes} /> </span> )} diff --git a/components/Header.js b/components/Header.js index 32ec977..87c31e8 100644 --- a/components/Header.js +++ b/components/Header.js @@ -7,13 +7,15 @@ import fetchJson from 'helpers/fetchJson' 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 { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -const Header = ({ apps, setApps }) => { +const Header = () => { const [userMenu, setUserMenu] = useState(false) const [showApps, setShowApps] = useState(false) const { user, mutateUser } = useUser() + const { apps, setApps } = useApps() const { t } = useSettings() const router = useRouter() @@ -53,7 +55,7 @@ const Header = ({ apps, setApps }) => { > <span style={{ - ...apps.findIndex(a => a.name === app.name) === apps.length - 1 ? { fontWeight: 600 } : {}, + ...apps.findIndex(a => a && a.name === app.name) === apps.length - 1 ? { fontWeight: 600 } : {}, ...app.min ? { color: '#888' } : {} }} > diff --git a/components/Layout.js b/components/Layout.js index c4cc553..572e961 100644 --- a/components/Layout.js +++ b/components/Layout.js @@ -5,9 +5,7 @@ import useSettings from 'hooks/useSettings' import PropTypes from 'prop-types' const Layout = ({ - children, - apps, - setApps + children }) => { const { settings } = useSettings() @@ -18,7 +16,7 @@ const Layout = ({ <main> <div className='container'>{children}</div> </main> - <Header apps={apps} setApps={setApps} /> + <Header /> <Popup /> </section> ) diff --git a/configs/appList.js b/configs/appList.js index 6268e15..b5f1eac 100644 --- a/configs/appList.js +++ b/configs/appList.js @@ -1,8 +1,10 @@ -import { Notes, Settings } from 'apps' +import { Notes, Player, Settings, Youtube } from 'apps' const appList = { Notes: { component: Notes, icon: true, buttons: ['min', 'max', 'close'], height: '48em', width: '64em' }, - Settings: { component: Settings, icon: false, buttons: ['min'], height: '23em', width: '16em' } + Player: { component: Player, icon: true, buttons: ['min', 'max', 'close'], height: '48em', width: '64em' }, + Settings: { component: Settings, icon: false, buttons: ['min'], height: '23em', width: '16em' }, + Youtube: { component: Youtube, icon: true, buttons: ['min', 'max', 'close'], height: '48em', width: '64em' } } export default appList diff --git a/configs/translations.js b/configs/translations.js index 8e086d9..ff7de3c 100644 --- a/configs/translations.js +++ b/configs/translations.js @@ -21,6 +21,7 @@ const translations = { select_all: 'Select all', import: 'Import', export: 'Export', + search: 'Search', sort: 'Sort', sort_by: 'Sort by:', choose_files: 'Choose files', @@ -33,7 +34,9 @@ const translations = { modified: 'Modified', open_apps: 'Open apps:', Notes: 'Notes', + Player: 'Player', Settings: 'Settings', + Youtube: 'Youtube', mail_ver_subject: 'Verification of your new My Apps account', mail_ver_t1: 'Thank you for creating an account in My Apps.', mail_ver_t2: 'We are sending you the verification code:', @@ -70,7 +73,15 @@ const translations = { notes_updated_error: 'Could not update note', notes_removed: 'Note was removed', notes_removed_error: 'Could not remove note', - notes_remove_confirm: 'Do you want to remove note?' + 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' }, pl: { register_user: 'Zarejestruj użytkownika', @@ -94,6 +105,7 @@ const translations = { select_all: 'Wybierz wszystkie', import: 'Wyślij', export: 'Pobierz', + search: 'Szukaj', sort: 'Sortuj', sort_by: 'Sortuj wg:', choose_files: 'Wybierz pliki', @@ -106,7 +118,9 @@ const translations = { modified: 'Zmodyfikowano', open_apps: 'Otwarte aplikacje:', Notes: 'Notatki', + Player: 'Odtwarzacz', Settings: 'Ustawienia', + Youtube: 'Youtube', mail_ver_subject: 'Weryfikacja Twojego nowego konta w aplikacji My Apps.', mail_ver_t1: 'Dziękujemy za założenie konta w My Apps', mail_ver_t2: 'Przesyłamy Ci kod weryfikacyjny:', @@ -143,7 +157,15 @@ const translations = { notes_updated_error: 'Błąd podczas zapisu zmian w notatce', notes_removed: 'Usunięto notatkę', notes_removed_error: 'Błąd podczas usuwania notatki', - notes_remove_confirm: 'Czy na pewno chcesz usunąć notatkę?' + 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' }, es: { register_user: 'Registrar al usuario', @@ -151,7 +173,7 @@ const translations = { password: 'contraseña', confirm_password: 'confirmar la contraseña', color_theme: 'Elige un tema: ', - language: 'Lengua: ', + language: 'Idioma: ', login: 'Acceso', login_error: 'Error de inicio de sesión', logout: 'Cerrar sesión', @@ -167,6 +189,7 @@ const translations = { select_all: 'Elegir todos', import: 'Enviar', export: 'Descargar', + search: 'Buscar', sort: 'Ordenar', sort_by: 'Ordenar por:', choose_files: 'Seleccione archivos', @@ -179,7 +202,9 @@ const translations = { modified: 'Modificado', open_apps: 'Aplicaciones abiertas:', Notes: 'Notas', + Player: 'Jugador', Settings: 'Ajustes', + Youtube: 'Youtube', mail_ver_subject: 'Verificación de su nueva cuenta en la aplicación My Apps.', mail_ver_t1: 'Gracias por crear una cuenta con Mis aplicaciones', mail_ver_t2: 'Te enviamos un código de verificación: ', @@ -216,7 +241,15 @@ const translations = { notes_updated_error: 'Error al guardar cambios en la nota', notes_removed: 'Nota eliminada', notes_removed_error: 'Error al eliminar la nota', - notes_remove_confirm: '¿Estás seguro de que deseas eliminar la nota?' + 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' }, de: { register_user: 'Registrieren Sie den Benutzer', @@ -240,6 +273,7 @@ const translations = { select_all: 'Alles auswählen', import: 'Senden', export: 'Herunterladen', + search: 'Suche', sort: 'Sortieren', sort_by: 'Sortieren nach:', choose_files: 'Dateien auswählen', @@ -252,7 +286,9 @@ const translations = { modified: 'Geändert', open_apps: 'Anwendungen öffnen:', Notes: 'Anmerkungen', + Player: 'Spieler', Settings: 'Einstellungen', + Youtube: 'Youtube', mail_ver_subject: 'Bestätigen Ihres neuen Kontos in der Anwendung „Meine Apps“.', mail_ver_t1: 'Vielen Dank, dass Sie ein Konto bei My Apps erstellt haben', mail_ver_t2: 'Wir senden Ihnen einen Bestätigungscode: ', @@ -289,7 +325,15 @@ const translations = { notes_updated_error: 'Fehler beim Speichern der Änderungen an der Notiz', notes_removed: 'Notiz gelöscht', notes_removed_error: 'Fehler beim Löschen der Notiz', - notes_remove_confirm: 'Möchten Sie die Notiz wirklich löschen? ' + 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' } } diff --git a/helpers/windowActions.js b/helpers/windowActions.js index 090e20a..ac86ea5 100644 --- a/helpers/windowActions.js +++ b/helpers/windowActions.js @@ -1,16 +1,20 @@ -export const close = (appName, setApps) => { setApps(apps => apps.filter(a => a && a.name !== appName)) } +export const close = (appName, setApps) => { + setApps(apps => apps.filter(a => a && a.name !== appName)) +} -export const open = ({ appName, buttons, height, width }, setApps) => { +export const open = ({ appName, buttons, height, width }, setApps, props = {}) => { setApps(apps => ( - !apps.some(a => a.name === appName) - ? [...apps, { name: appName, min: false, max: false, height, width, pos: [], buttons }] - : apps + apps && apps.length > 0 + ? !apps.some(a => a.name === appName) + ? [...apps, { name: appName, min: false, max: false, height, width, pos: [], buttons, props }] + : apps + : [{ name: appName, min: false, max: false, height, width, pos: [], buttons, props }] )) } export const focus = (appName, setApps) => { setApps(apps => { - const i = apps.findIndex(a => a.name === appName) + const i = apps.length > 0 && apps.findIndex(a => a.name === appName) return i !== apps.length - 1 ? [...apps.filter((_, n) => n !== i), apps[i]] : apps @@ -49,7 +53,7 @@ export const move = (app, winRef, setApps) => { const x = pageX - shiftX const y = pageY - shiftY - 32 - setApps(apps => ([...apps.map(a => a.name === app.name + setApps(apps => ([...apps.map(a => a && a.name === app.name ? { ...a, pos: [x + 'px', y < 0 ? 0 : y + 'px'] } : a )])) diff --git a/hooks/useSettings.js b/hooks/useSettings.js index 44f5956..74092b2 100644 --- a/hooks/useSettings.js +++ b/hooks/useSettings.js @@ -16,7 +16,7 @@ export const SettingsProvider = ({ children }) => { setData(s) } - const t = key => data && data.language && translations[data.language][key] + const t = key => data && data.language && translations && translations.en[key] ? translations[data.language][key] ? translations[data.language][key] : translations.en[key] diff --git a/package-lock.json b/package-lock.json index ef1d51d..ba40bee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,9 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "sass": "^1.35.1", - "swr": "^1.0.0" + "scrape-youtube": "^2.1.9", + "swr": "^1.0.0", + "ytdl-core": "^4.9.1" }, "devDependencies": { "@next/eslint-plugin-next": "^11.1.2", @@ -4204,6 +4206,18 @@ "node": ">=10" } }, + "node_modules/m3u8stream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.4.tgz", + "integrity": "sha512-sco80Db+30RvcaIOndenX6E6oQNgTiBKeJbFPc+yDXwPQIkryfboEbCvXPlBRq3mQTCVPQO93TDVlfRwqpD35w==", + "dependencies": { + "miniget": "^4.0.0", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -4278,6 +4292,14 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, + "node_modules/miniget": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.1.tgz", + "integrity": "sha512-O/DduzDR6f+oDtVype9S/Qu5hhnx73EDYGyZKwU/qN82lehFZdfhoa4DT51SpsO+8epYrB3gcRmws56ROfTIoQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -5685,6 +5707,11 @@ "node": ">=8.9.0" } }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", @@ -5694,6 +5721,11 @@ "object-assign": "^4.1.1" } }, + "node_modules/scrape-youtube": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/scrape-youtube/-/scrape-youtube-2.1.9.tgz", + "integrity": "sha512-+quMZ0qEOhFw3YXkRf1RZh5GLHUn7MSmudEYvt/NHATMT7FvB4NHTg6N/ig9vC2mNiU1BR9MSHjtuv1aTOwauQ==" + }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -6590,6 +6622,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/ytdl-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.9.1.tgz", + "integrity": "sha512-6Jbp5RDhUEozlaJQAR+l8oV8AHsx3WUXxSyPxzE6wOIAaLql7Hjiy0ZM58wZoyj1YEenlEPjEqcJIjKYKxvHtQ==", + "dependencies": { + "m3u8stream": "^0.8.3", + "miniget": "^4.0.0", + "sax": "^1.1.3" + }, + "engines": { + "node": ">=10" + } } }, "dependencies": { @@ -9719,6 +9764,15 @@ "yallist": "^4.0.0" } }, + "m3u8stream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.4.tgz", + "integrity": "sha512-sco80Db+30RvcaIOndenX6E6oQNgTiBKeJbFPc+yDXwPQIkryfboEbCvXPlBRq3mQTCVPQO93TDVlfRwqpD35w==", + "requires": { + "miniget": "^4.0.0", + "sax": "^1.2.4" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -9780,6 +9834,11 @@ } } }, + "miniget": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.1.tgz", + "integrity": "sha512-O/DduzDR6f+oDtVype9S/Qu5hhnx73EDYGyZKwU/qN82lehFZdfhoa4DT51SpsO+8epYrB3gcRmws56ROfTIoQ==" + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -10845,6 +10904,11 @@ "chokidar": ">=3.0.0 <4.0.0" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", @@ -10854,6 +10918,11 @@ "object-assign": "^4.1.1" } }, + "scrape-youtube": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/scrape-youtube/-/scrape-youtube-2.1.9.tgz", + "integrity": "sha512-+quMZ0qEOhFw3YXkRf1RZh5GLHUn7MSmudEYvt/NHATMT7FvB4NHTg6N/ig9vC2mNiU1BR9MSHjtuv1aTOwauQ==" + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -11577,6 +11646,16 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, + "ytdl-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.9.1.tgz", + "integrity": "sha512-6Jbp5RDhUEozlaJQAR+l8oV8AHsx3WUXxSyPxzE6wOIAaLql7Hjiy0ZM58wZoyj1YEenlEPjEqcJIjKYKxvHtQ==", + "requires": { + "m3u8stream": "^0.8.3", + "miniget": "^4.0.0", + "sax": "^1.1.3" + } } } } diff --git a/package.json b/package.json index b153b0f..a331983 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "sass": "^1.35.1", - "swr": "^1.0.0" + "scrape-youtube": "^2.1.9", + "swr": "^1.0.0", + "ytdl-core": "^4.9.1" }, "devDependencies": { "@next/eslint-plugin-next": "^11.1.2", diff --git a/pages/api/youtube/player.js b/pages/api/youtube/player.js new file mode 100644 index 0000000..a6d3a7e --- /dev/null +++ b/pages/api/youtube/player.js @@ -0,0 +1,32 @@ +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/search.js b/pages/api/youtube/search.js new file mode 100644 index 0000000..b17d008 --- /dev/null +++ b/pages/api/youtube/search.js @@ -0,0 +1,26 @@ +import withSession from 'hocs/withSession' +import youtube from 'scrape-youtube' + +export default withSession(async (req, res) => { + switch (req.method) { + case 'POST': + try { + const user = req.session.get('user') + const { quote } = req.body + + if (!user || !user?.isVerified || !quote) { + throw new Error('Something went wrong') + } + + const video = await youtube.search(quote) + + res.status(200).json(video) + } catch (error) { + res.status(400).json([]) + } + break + default: + res.status(400).send() + break + } +}) diff --git a/pages/index.js b/pages/index.js index ac36c3b..af25f7b 100644 --- a/pages/index.js +++ b/pages/index.js @@ -70,7 +70,7 @@ const Home = () => { app={app} setApps={setApps} > - <AppComponent /> + <AppComponent {...app.props} /> </App> ) })} diff --git a/public/icons/notes.svg b/public/icons/notes.svg index f56da12..c9525d0 100644 --- a/public/icons/notes.svg +++ b/public/icons/notes.svg @@ -1 +1,13 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"><defs><pattern patternUnits="userSpaceOnUse" width="124.249" height="124.249" patternTransform="translate(-9.091 793.763) scale(2.10569)" id="a"><path fill="none" d="M0 0h124.249v124.249H0z"/><path fill="none" d="M5.811 4.423h114.895v115.401H5.811z"/><path d="M104.92 10.289c8.25.023 13.643 3.441 14.285 12.5l-.357 81.785c-.663 12.098-7.175 11.968-12.857 13.572l-51.964.178c-9.844-1.035-14.75-4.883-16.429-14.642l.893-79.822c.4-5.981 2.243-10.784 7.857-12.5l58.572-1.071z" fill="#fff" stroke="#000" stroke-width="3"/><path d="M129.92 124.11l-17.385 80.143c-2.936 11.593-9.448 10.453-15.635 11.046l-37.03-.207.844-77.663c.401-5.982 2.243-10.784 7.857-12.5l61.35-.82z" transform="translate(-22.223 -113.568)" opacity=".262"/><path d="M47.477 106.066l60.357-.252M47.098 96.09l60.357-.252M47.288 85.926l60.356-.252M46.909 75.95l60.357-.252M46.12 65.439l60.356-.253M45.741 55.464l60.357-.253M45.93 45.299l60.357-.252M45.552 35.324l60.356-.253" fill="none" stroke="#646464" stroke-width="2.5"/><path d="M14.92 80.646c-10.843-2.96-7.692-8.898-5.062-14.982l24.531-47.64c1.536-4.244 7.427-7.25 11.245-6.664l60.357-.357c3.71.86 8.788.808 4.286 7.143L82.82 71.84c-1.024 5.808-7.643 8.761-11.497 8.91l-56.403-.104z" fill="#5fd5d4" stroke="#000" stroke-width="3"/><path d="M43.313 27.915l45.713-.253M36.705 36.486l45.714-.252" fill="none" stroke="#171718" stroke-width="4"/><path transform="matrix(2.04511 0 0 1.47368 -149.542 -174.508)" d="M122.986 128.784c0 .662-.396 1.2-.884 1.2s-.884-.538-.884-1.2c0-.663.396-1.2.884-1.2s.884.537.884 1.2z" fill="#26272a"/><path d="M100.098 15.02c-7.037-7.32 3.267-13.747 4.286-6.25" fill="none" stroke="#838382" stroke-width="2"/><path transform="matrix(2.04511 0 0 1.47368 -160.01 -174.045)" d="M122.986 128.784c0 .662-.396 1.2-.884 1.2s-.884-.538-.884-1.2c0-.663.396-1.2.884-1.2s.884.537.884 1.2z" fill="#26272a"/><path d="M89.63 15.484c-7.038-7.322 3.266-13.747 4.285-6.25" fill="none" stroke="#838382" stroke-width="2"/><path transform="matrix(2.04511 0 0 1.47368 -170.746 -174.049)" d="M122.986 128.784c0 .662-.396 1.2-.884 1.2s-.884-.538-.884-1.2c0-.663.396-1.2.884-1.2s.884.537.884 1.2z" fill="#26272a"/><path d="M78.893 15.48C71.856 8.16 82.16 1.733 83.18 9.23" fill="none" stroke="#838382" stroke-width="2"/><g><path transform="matrix(2.04511 0 0 1.47368 -181.652 -173.796)" d="M122.986 128.784c0 .662-.396 1.2-.884 1.2s-.884-.538-.884-1.2c0-.663.396-1.2.884-1.2s.884.537.884 1.2z" fill="#26272a"/><path d="M67.988 15.733c-7.037-7.322 3.266-13.748 4.285-6.25" fill="none" stroke="#838382" stroke-width="2"/></g><g><path transform="matrix(2.04511 0 0 1.47368 -192.222 -173.587)" d="M122.986 128.784c0 .662-.396 1.2-.884 1.2s-.884-.538-.884-1.2c0-.663.396-1.2.884-1.2s.884.537.884 1.2z" fill="#26272a"/><path d="M57.418 15.942c-7.037-7.321 3.266-13.747 4.285-6.25" fill="none" stroke="#838382" stroke-width="2"/></g><g><path transform="matrix(2.04511 0 0 1.47368 -203.636 -173.46)" d="M122.986 128.784c0 .662-.396 1.2-.884 1.2s-.884-.538-.884-1.2c0-.663.396-1.2.884-1.2s.884.537.884 1.2z" fill="#26272a"/><path d="M46.003 16.068c-7.037-7.321 3.267-13.747 4.286-6.25" fill="none" stroke="#838382" stroke-width="2"/></g></pattern></defs><path fill="url(#a)" d="M-9.091 793.763h261.63v261.63H-9.091z" transform="translate(0 -796.362)"/></svg>
\ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" version="1"> + <path style="opacity:0.2" d="M 6,5 C 4.892,5 4,5.892 4,7 V 43 C 4,44.108 4.892,45 6,45 H 28 L 44,29 V 7 C 44,5.892 43.108,5 42,5 Z"/> + <path style="fill:#fecd38" d="M 6,4 C 4.892,4 4,4.892 4,6 V 42 C 4,43.108 4.892,44 6,44 H 28 L 44,28 V 6 C 44,4.892 43.108,4 42,4 Z"/> + <path style="fill:#ffe69b" d="M 44,28 H 30 C 28.895,28 28,28.895 28,30 V 44 Z"/> + <path style="opacity:0.2;fill:#ffffff" d="M 6,4 C 4.892,4 4,4.892 4,6 V 7 C 4,5.892 4.892,5 6,5 H 42 C 43.108,5 44,5.892 44,7 V 6 C 44,4.892 43.108,4 42,4 Z"/> + <path style="opacity:0.2;fill:#ffffff" d="M 30,28 C 28.895,28 28,28.895 28,30 V 31 C 28,29.895 28.895,29 30,29 H 43 L 44,28 Z"/> + <rect style="opacity:0.1" width="32" height="4" x="8" y="15" rx="2" ry="2"/> + <rect style="fill:#4f4f4f" width="32" height="4" x="8" y="14" rx="2" ry="2"/> + <rect style="opacity:0.1" width="22" height="4" x="8" y="23" rx="2" ry="2"/> + <rect style="fill:#4f4f4f" width="22" height="4" x="8" y="22" rx="2" ry="2"/> + <rect style="opacity:0.1" width="14" height="4" x="8" y="31" rx="2" ry="2"/> + <rect style="fill:#4f4f4f" width="14" height="4" x="8" y="30" rx="2" ry="2"/> +</svg> diff --git a/public/icons/player.svg b/public/icons/player.svg new file mode 100644 index 0000000..a870027 --- /dev/null +++ b/public/icons/player.svg @@ -0,0 +1,10 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" version="1.1"> + <path fill="#4f4f4f" d="m38 24a14 14 0 0 1 -14 14 14 14 0 0 1 -14 -14 14 14 0 0 1 14 -14 14 14 0 0 1 14 14z"/> + <path fill="#e4e4e4" d="m24 4c-11.08 0-20 8.92-20 20s8.92 20 20 20 20-8.92 20-20-8.92-20-20-20zm0 3a6 6 0 0 1 6 6 6 6 0 0 1 -6 6 6 6 0 0 1 -6 -6 6 6 0 0 1 6 -6zm-11 11a6 6 0 0 1 6 6 6 6 0 0 1 -6 6 6 6 0 0 1 -6 -6 6 6 0 0 1 6 -6zm22 0a6 6 0 0 1 6 6 6 6 0 0 1 -6 6 6 6 0 0 1 -6 -6 6 6 0 0 1 6 -6zm-11 11a6 6 0 0 1 6 6 6 6 0 0 1 -6 6 6 6 0 0 1 -6 -6 6 6 0 0 1 6 -6z"/> + <path fill="#4f4f4f" d="m23.366 20.9a1 1 0 0 1 -0.366 1.366 1 1 0 0 1 -1.366 -0.366 1 1 0 0 1 0.366 -1.366 1 1 0 0 1 1.366 0.366z"/> + <path fill="#4f4f4f" d="m26.366 26.1a1 1 0 0 1 -0.366 1.366 1 1 0 0 1 -1.366 -0.366 1 1 0 0 1 0.366 -1.366 1 1 0 0 1 1.366 0.366z"/> + <path fill="#4f4f4f" d="m27.098 23.3a1 1 0 0 1 -1.366 -0.366 1 1 0 0 1 0.366 -1.366 1 1 0 0 1 1.366 0.366 1 1 0 0 1 -0.366 1.366z"/> + <path fill="#4f4f4f" d="m21.902 26.3a1 1 0 0 1 -1.366 -0.366 1 1 0 0 1 0.366 -1.366 1 1 0 0 1 1.366 0.366 1 1 0 0 1 -0.366 1.366z"/> + <path fill="#fff" opacity=".2" d="m24 4c-11.08 0-20 8.92-20 20 0 0.168 0.0076 0.333 0.0117 0.5 0.2647-10.846 9.0763-19.5 19.988-19.5s19.724 8.654 19.988 19.5c0.004-0.167 0.012-0.332 0.012-0.5 0-11.08-8.92-20-20-20zm-5.9746 9.498a6 6 0 0 0 -0.025 0.502 6 6 0 0 0 6 6 6 6 0 0 0 6 -6 6 6 0 0 0 -0.025 -0.498 6 6 0 0 1 -5.975 5.498 6 6 0 0 1 -5.975 -5.502zm-11 11a6 6 0 0 0 -0.0246 0.502 6 6 0 0 0 6 6 6 6 0 0 0 6 -6 6 6 0 0 0 -0.025 -0.498 6 6 0 0 1 -5.975 5.498 6 6 0 0 1 -5.9742 -5.502zm22 0a6 6 0 0 0 -0.025 0.502 6 6 0 0 0 6 6 6 6 0 0 0 6 -6 6 6 0 0 0 -0.025 -0.498 6 6 0 0 1 -5.975 5.498 6 6 0 0 1 -5.975 -5.502zm-11 11a6 6 0 0 0 -0.025 0.502 6 6 0 0 0 6 6 6 6 0 0 0 6 -6 6 6 0 0 0 -0.025 -0.498 6 6 0 0 1 -5.975 5.498 6 6 0 0 1 -5.975 -5.502z"/> + <path opacity=".2" d="m24 7a6 6 0 0 0 -6 6 6 6 0 0 0 0.02539 0.4981 6 6 0 0 1 5.975 -5.498 6 6 0 0 1 5.975 5.502 6 6 0 0 0 0.025 -0.502 6 6 0 0 0 -6 -6zm-11 11a6 6 0 0 0 -6 6 6 6 0 0 0 0.0254 0.498 6 6 0 0 1 5.9746 -5.498 6 6 0 0 1 5.975 5.502 6 6 0 0 0 0.025 -0.502 6 6 0 0 0 -6 -6zm22 0a6 6 0 0 0 -6 6 6 6 0 0 0 0.02539 0.4981 6 6 0 0 1 5.975 -5.498 6 6 0 0 1 5.975 5.502 6 6 0 0 0 0.025 -0.502 6 6 0 0 0 -6 -6zm-30.988 6.5c-0.0041 0.167-0.0117 0.332-0.0117 0.5 0 11.08 8.92 20 20 20s20-8.92 20-20c0-0.1679-0.0076-0.3331-0.01172-0.5-0.264 10.846-9.076 19.5-19.988 19.5s-19.724-8.654-19.988-19.5zm19.988 4.5a6 6 0 0 0 -6 6 6 6 0 0 0 0.02539 0.4981 6 6 0 0 1 5.975 -5.498 6 6 0 0 1 5.975 5.502 6 6 0 0 0 0.025 -0.502 6 6 0 0 0 -6 -6z"/> +</svg> diff --git a/public/icons/youtube.svg b/public/icons/youtube.svg new file mode 100644 index 0000000..fd56bab --- /dev/null +++ b/public/icons/youtube.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" version="1"> + <path style="opacity:0.2" d="m 20.710958,38.974835 c -6.61137,-0.125659 -9.29483,-0.236612 -10.9357502,-0.452161 -1.20335,-0.158076 -1.79431,-0.312183 -2.46082,-0.641719 -1.10082,-0.544259 -1.78544,-1.303201 -2.27951,-2.526969 -0.45172,-1.118836 -0.63565,-2.007476 -0.80445,-3.886686 -0.19049,-2.120698 -0.20538,-2.504621 -0.22516,-5.80883 -0.0233,-3.88576 0.0236,-5.15909 0.28455,-7.738247 0.13684,-1.352125 0.33628,-2.250726 0.71103,-3.203634 0.34129,-0.867795 0.65288,-1.346721 1.21955,-1.874493 0.7122,-0.663311 1.49437,-1.066538 2.48179,-1.2794 1.4738402,-0.317725 8.1353702,-0.562695 15.3012502,-0.562695 7.165881,0 13.827411,0.24497 15.301251,0.562695 1.06587,0.229777 1.84815,0.654198 2.61141,1.416801 0.59738,0.596864 0.90506,1.139213 1.27121,2.240835 0.37728,1.135093 0.52197,2.139642 0.73635,5.112325 0.1018,1.411685 0.10178,7.910134 0,9.321124 -0.11911,1.650636 -0.26528,3.12057 -0.36261,3.64654 -0.25022,1.352245 -0.74166,2.618321 -1.29573,3.338169 -0.38014,0.493871 -1.13056,1.082328 -1.75807,1.378621 -0.63541,0.300025 -1.2408,0.448768 -2.21675,0.544677 -3.02421,0.297189 -12.259171,0.51418 -17.579501,0.413047 z"/> + <path style="fill:#f54b3e" d="m 20.710958,37.974834 c -6.61137,-0.125659 -9.29483,-0.236612 -10.9357502,-0.452161 -1.20335,-0.158076 -1.79431,-0.312183 -2.46082,-0.641719 -1.10082,-0.544259 -1.78544,-1.303201 -2.27951,-2.526969 -0.45172,-1.118836 -0.63565,-2.007476 -0.80445,-3.886686 -0.19049,-2.120698 -0.20538,-2.504621 -0.22516,-5.80883 -0.0233,-3.88576 0.0236,-5.15909 0.28455,-7.738247 0.13684,-1.352125 0.33628,-2.250726 0.71103,-3.203634 0.34129,-0.867795 0.65288,-1.346721 1.21955,-1.874493 0.7122,-0.663311 1.49437,-1.066538 2.48179,-1.2794 C 10.176028,10.24497 16.837558,10 24.003438,10 c 7.165881,0 13.827411,0.24497 15.301251,0.562695 1.06587,0.229777 1.84815,0.654198 2.61141,1.416801 0.59738,0.596864 0.90506,1.139213 1.27121,2.240835 0.37728,1.135093 0.52197,2.139642 0.73635,5.112325 0.1018,1.411685 0.10178,7.910134 0,9.321124 -0.11911,1.650636 -0.26528,3.12057 -0.36261,3.64654 -0.25022,1.352245 -0.74166,2.618321 -1.29573,3.338169 -0.38014,0.493871 -1.13056,1.082328 -1.75807,1.378621 -0.63541,0.300025 -1.2408,0.448768 -2.21675,0.544677 -3.02421,0.297189 -12.259171,0.51418 -17.579501,0.413047 z"/> + <path style="opacity:0.2" d="m 24.452263,28.397685 c 5.669439,-3.386921 5.547539,-3.312636 5.547539,-3.380633 0,-0.06363 -9.92357,-6.051366 -9.971956,-6.016909 -0.03268,0.02329 -0.03876,11.951433 -0.0066,11.989072 0.01208,0.01392 0.04644,0.01445 0.0763,0.0011 0.02987,-0.01327 1.98932,-1.17997 4.354226,-2.59276 z"/> + <path style="fill:#ffffff" d="m 24.452263,27.397687 c 5.669439,-3.386921 5.547539,-3.312636 5.547539,-3.380633 0,-0.06363 -9.92357,-6.051366 -9.971956,-6.016909 -0.03268,0.02329 -0.03876,11.951433 -0.0066,11.989072 0.01208,0.01392 0.04644,0.01445 0.0763,0.0011 0.02987,-0.01327 1.98932,-1.17997 4.354226,-2.59276 z"/> + <path style="fill:#ffffff;opacity:0.2" d="M 24.003906 10 C 16.838026 10 10.176965 10.244775 8.703125 10.5625 C 7.715705 10.775362 6.9329031 11.178486 6.2207031 11.841797 C 5.6540331 12.369569 5.34129 12.849002 5 13.716797 C 4.62525 14.669705 4.4259025 15.567797 4.2890625 16.919922 C 4.0281125 19.499079 3.9825594 20.772443 4.0058594 24.658203 C 4.0069096 24.833636 4.0106691 24.854007 4.0117188 25.013672 C 4.0004811 21.753034 4.0474033 20.308414 4.2890625 17.919922 C 4.4259025 16.567797 4.62525 15.669705 5 14.716797 C 5.34129 13.849002 5.6540331 13.369569 6.2207031 12.841797 C 6.9329031 12.178486 7.715705 11.775362 8.703125 11.5625 C 10.176965 11.244775 16.838026 11 24.003906 11 C 31.169787 11 37.830848 11.244775 39.304688 11.5625 C 40.370557 11.792277 41.152756 12.217866 41.916016 12.980469 C 42.513396 13.577333 42.82135 14.119081 43.1875 15.220703 C 43.56478 16.355796 43.709448 17.359348 43.923828 20.332031 C 43.970197 20.975045 43.991969 22.701878 43.996094 24.494141 C 44.001034 22.351559 43.979262 20.100747 43.923828 19.332031 C 43.709448 16.359348 43.56478 15.355796 43.1875 14.220703 C 42.82135 13.119081 42.513396 12.577333 41.916016 11.980469 C 41.152756 11.217866 40.370557 10.792277 39.304688 10.5625 C 37.830848 10.244775 31.169787 10 24.003906 10 z"/> +</svg> diff --git a/styles/global/_window.scss b/styles/global/_window.scss index c489796..cf78752 100644 --- a/styles/global/_window.scss +++ b/styles/global/_window.scss @@ -106,20 +106,41 @@ width: 100%; background: var(--color-window-menu); - & > div > div { - // cursor: pointer; - display: inline-block; - padding: .5em; - transition: .3s background; - white-space: nowrap; + & > div { + display: flex; - @media(hover: hover) { - &:hover { - background-color: var(--color-window-menu-alt); + & > div { + display: inline-block; + padding: .5em; + transition: .3s background; + white-space: nowrap; + + @media(hover: hover) { + &:hover { + background-color: var(--color-window-menu-alt); + } } } + + & > span { + flex-grow: 1; + } } + .off { + text-decoration: line-through solid var(--color-decor); + color: var(--color-decor); + } + + .iconOff { + color: var(--color-window-menu-alt); + pointer-events: none; + } + + .active { + color: var(--color-text-alt); + font-weight: 600; + } @media(max-width: 40em) { height: 3em; @@ -201,5 +222,13 @@ & > .window__content { top: 0; } + + .window__submenu > div { + @media(min-width: 40em) { + &:last-of-type { + margin-right: 5.5em; + } + } + } } } diff --git a/styles/main/_icon.scss b/styles/main/_icon.scss index a7c16ed..efa2ee0 100644 --- a/styles/main/_icon.scss +++ b/styles/main/_icon.scss @@ -1,7 +1,7 @@ .icon { text-decoration: none; display: inline-block; - padding: .5em; + padding: 1em; text-align: center; outline: none; |