diff --git a/index.html b/index.html index 6964dc2ba..cb2c17793 100644 --- a/index.html +++ b/index.html @@ -148,13 +148,7 @@
-
-

Upcoming

-
- -
    -
    -
    +

    The Queue is Empty.

    @@ -201,7 +195,29 @@ -
    +
    +
    + + + + + ytify is a resource efficient audio streaming client for YouTube & YTMusic. Learn + more about how to use it effectively. +
    +
    +

    + + To support with connectivity costs donations can be made through the United Payments Interface at + animesh.5383@waicici, even a small amount can help. +

    + What can one + individual do to deal with climate change? +
    +
    @@ -219,7 +235,7 @@
    - + @@ -287,11 +303,11 @@
  • -
  • - Sort Title A↔Z +
  • +
  • -
  • - Sort Artist A↔Z +
  • +
  • @@ -335,4 +351,4 @@

    Playlist, channel or your collection items show - \ No newline at end of file + diff --git a/src/components/Settings/app.ts b/src/components/Settings/app.ts index a881051ae..a8e6c1a54 100644 --- a/src/components/Settings/app.ts +++ b/src/components/Settings/app.ts @@ -61,9 +61,8 @@ export default function() { notify(i18n('settings_reload')); }, onmount: (target) => { - const { linkHost } = state; - if (linkHost) - target.value = linkHost; + if (state.linkHost) + target.value = state.linkHost; }, children: html` @@ -81,9 +80,7 @@ export default function() { setState('dlFormat', e.target.value); }, onmount: (target) => { - const { dlFormat } = state; - if (dlFormat) - target.value = dlFormat as 'opus'; + target.value = state.dlFormat; }, children: html` @@ -100,8 +97,7 @@ export default function() { setState('shareAction', e.target.value); }, onmount: (target) => { - if (state.shareAction) - target.value = state.shareAction; + target.value = state.shareAction; }, children: html` diff --git a/src/components/Settings/personalize.ts b/src/components/Settings/personalize.ts index c91c8f527..b0e2da384 100644 --- a/src/components/Settings/personalize.ts +++ b/src/components/Settings/personalize.ts @@ -47,11 +47,11 @@ export default function() { ${ToggleSwitch({ id: "custom_theme", name: 'settings_use_custom_color', - checked: Boolean(state.customTheme), + checked: Boolean(state.customColor), handler: e => { let colorString = ''; - if (!state.customTheme) { + if (!state.customColor) { const rgbText = i18n('settings_custom_color_prompt'); const str = prompt(rgbText, '174,174,174'); if (str) @@ -59,7 +59,7 @@ export default function() { else e.preventDefault(); } - setState('customTheme', colorString); + setState('customColor', colorString); themer(); } })} diff --git a/src/components/SuperCollectionList.ts b/src/components/SuperCollectionList.ts index 119590e19..9c90feea4 100644 --- a/src/components/SuperCollectionList.ts +++ b/src/components/SuperCollectionList.ts @@ -7,11 +7,9 @@ import { render } from 'uhtml'; import { setState, state } from '../lib/store'; -let name = state.defaultSuperCollection as SuperCollection; +let { defaultSuperCollection } = state; -if (name) - document.getElementById('r.' + name)?.toggleAttribute('checked') -else name = 'featured'; +document.getElementById('r.' + defaultSuperCollection)?.toggleAttribute('checked') superCollectionList.addEventListener('click', superClick); @@ -30,13 +28,13 @@ superCollectionSelector.addEventListener('click', e => { if (elm.value !== 'for_you') setState('defaultSuperCollection', elm.value); - name = elm.value; + defaultSuperCollection = elm.value; main(); }); export default async function main() { const db = getDB(); - const data = await loadData(name, db); + const data = await loadData(defaultSuperCollection, db); const template = ItemsLoader(data as string); render(superCollectionList, template); } diff --git a/src/components/WatchVideo.ts b/src/components/WatchVideo.ts index bbf4fbfd9..fe9302cdf 100644 --- a/src/components/WatchVideo.ts +++ b/src/components/WatchVideo.ts @@ -157,7 +157,7 @@ export default async function(dialog: HTMLDialogElement) { id: 'videoCodecSelector', label: '', handler: (_) => { - video.src = proxyHandler(_.target.value); + video.src = proxyHandler(_.target.value, true); video.currentTime = audio.currentTime; if (savedQ) setState('watchMode', _.target.selectedOptions[0].textContent as string); @@ -173,7 +173,7 @@ export default async function(dialog: HTMLDialogElement) { }`, onmount: (_) => { if (savedQ) - video.src = proxyHandler(_.value); + video.src = proxyHandler(_.value, true); } }) : ''} @@ -193,7 +193,7 @@ export default async function(dialog: HTMLDialogElement) { ${footerTemplate} `); - audio.src = proxyHandler(audioArray[0].url); + audio.src = proxyHandler(audioArray[0].url, true); audio.currentTime = video.currentTime; loadingScreen.close(); } diff --git a/src/lib/libraryUtils.ts b/src/lib/libraryUtils.ts index 719ea97f6..17e96f6f4 100644 --- a/src/lib/libraryUtils.ts +++ b/src/lib/libraryUtils.ts @@ -1,7 +1,8 @@ -import { goTo, notify, renderCollection } from "./utils"; +import { goTo, hostResolver, notify } from "./utils"; import { listBtnsContainer, listContainer, listSection, listTitle, loadingScreen, removeFromListBtn, sortCollectionBtn } from "./dom"; import { store } from "./store"; import { render, html } from "uhtml"; +import StreamItem from "../components/StreamItem"; export const reservedCollections = ['discover', 'history', 'favorites', 'listenLater', 'channels', 'playlists']; @@ -83,6 +84,24 @@ export function createCollection(title: string) { store.addToCollectionOptions.push(title); } +export function renderCollection( + data: (DOMStringMap | CollectionItem)[], + draggable = false, + fragment: DocumentFragment | HTMLDivElement = listContainer +) { + render(fragment, html`${data.map(v => + StreamItem({ + id: v.id || '', + href: hostResolver(`/watch?v=${v.id}`), + title: v.title || '', + author: v.author, + duration: v.duration || '', + channelUrl: v.channelUrl, + draggable: draggable + }) + ) + }`); +} export async function fetchCollection( id: string | null, @@ -90,10 +109,12 @@ export async function fetchCollection( ) { if (!id) return; + + const display = shared ? 'Shared Collection' : id const isReserved = reservedCollections.includes(id); const isReversed = listContainer.classList.contains('reverse'); - listTitle.textContent = decodeURIComponent(id); + listTitle.textContent = decodeURIComponent(display); shared ? await getSharedCollection(id) : @@ -123,7 +144,7 @@ export async function fetchCollection( location.origin + location.pathname + (shared ? '?si=' : '?collection=') + id ); - document.title = (shared ? 'Shared Collection' : id) + ' - ytify'; + document.title = display + ' - ytify'; } diff --git a/src/lib/store.ts b/src/lib/store.ts index e3a09e3f3..ee7f4542b 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -12,7 +12,7 @@ export let state = { linkHost: '', dlFormat: 'opus', theme: 'auto', - customTheme: '', + customColor: '', roundness: '0.4rem', searchSuggestions: true, searchFilter: '', @@ -90,11 +90,9 @@ export const store: { }, linkHost: string, searchQuery: string, - superCollectionType: 'featured' | 'collections' | 'channels' | 'feed' | 'playlists', addToCollectionOptions: string[], actionsMenu: CollectionItem, list: List & Record<'url' | 'type' | 'uploader', string>, - downloadFormat: 'opus' | 'wav' | 'mp3' | 'ogg' } = { player: { @@ -137,7 +135,6 @@ export const store: { }, linkHost: state.linkHost || location.origin, searchQuery: '', - superCollectionType: state.defaultSuperCollection as 'featured', actionsMenu: { id: '', title: '', diff --git a/src/lib/utils.ts b/src/lib/utils.ts index a746d03eb..839c7fac5 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,9 +1,7 @@ -import { audio, listContainer, settingsContainer, title } from "./dom"; +import { audio, settingsContainer, title } from "./dom"; import { getThumbIdFromLink } from "./imageUtils"; import player from "./player"; import { state, store } from "./store"; -import { html, render } from 'uhtml'; -import StreamItem from "../components/StreamItem"; import fetchList from "../modules/fetchList"; import { fetchCollection, removeFromCollection } from "./libraryUtils"; import { i18n } from "../scripts/i18n.ts"; @@ -137,27 +135,6 @@ export async function errorHandler( } - -export function renderCollection( - data: (DOMStringMap | CollectionItem)[], - draggable = false, - fragment: DocumentFragment | undefined = undefined -) { - render(fragment || listContainer, html`${data.map(v => - StreamItem({ - id: v.id || '', - href: hostResolver(`/watch?v=${v.id}`), - title: v.title || '', - author: v.author, - duration: v.duration || '', - channelUrl: v.channelUrl, - draggable: draggable - }) - ) - }`); -} - - // TLDR : Stream Item Click Action export async function superClick(e: Event) { const elem = e.target as HTMLAnchorElement & { dataset: CollectionItem }; diff --git a/src/locales/en.json b/src/locales/en.json index af4022434..4d23fc28d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -41,7 +41,7 @@ "search_filter_music_albums": "Albums", "search_filter_music_playlists": "Playlists", "search_filter_sort_by": "Sort by", - "search_filter_date": "Date", + "search_filter_date": "Time", "search_filter_views": "Views", "library_discover": "Discover", "library_history": "History", @@ -60,6 +60,7 @@ "library_clean": "Clean Library", "library_clean_prompt": "Are you sure you want to clear $ items from the library?", "library_import_prompt": "This will merge your current library with the imported library, continue?", + "library_imported": "Library has been imported successfully", "list_play": "Play All", "list_enqueue": "Enqueue All", "list_import": "Import as Collection", @@ -72,6 +73,8 @@ "list_share": "Share Collection", "list_radio": "Start Radio", "list_sort": "Sort Manually", + "list_sort_title": "Sort by Title A↔Z", + "list_sort_author": "Sort by Author A↔Z", "list_info": "Playlist, channel or your collection items show here", "list_prompt_delete": "Are you sure you want to delete the collection $ ?", "list_prompt_clear": "Are you sure you want to clear $ ?", diff --git a/src/locales/es.json b/src/locales/es.json index 753ff1b46..53f82c5b2 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -66,11 +66,11 @@ "list_set_title": "Establecer título", "list_clear_all": "Limpiar todo", "list_remove": "Eliminar", - "list_delete": "Borrar", - "list_rename": "Renombrar", - "list_share": "Compartir", - "list_radio": "Radio", - "list_sort": "Ordenar", + "list_delete": "Borrar colección", + "list_rename": "Renombrar colección", + "list_share": "Compartir colección", + "list_radio": "Abrir radio", + "list_sort": "Ordenar manualmente", "list_info": "La lista de reproducción, el canal o los elementos de tu colección se muestran aquí", "list_prompt_delete": "¿Estás seguro de que quieres eliminar la colección $?", "list_prompt_clear": "¿Estás seguro de que quieres borrar $?", @@ -83,18 +83,18 @@ "actions_menu_view_artist": "Ver artista", "actions_menu_view_lyrics": "Ver letra", "actions_menu_view_channel": "Ver canal", - "actions_menu_debug_info": "Información de depuración", - "collection_selector_add_to": "Agregar a", - "collection_selector_create_new": "Crear nueva colección", + "actions_menu_debug_info": "Ver detalles", + "collection_selector_add_to": "Agregar a la colección", + "collection_selector_create_new": "Crear una nueva colección", "collection_selector_favorites": "Favoritos", - "collection_selector_listen_later": "Escúchalo más tarde", + "collection_selector_listen_later": "Escúchar más tarde", "settings_custom_instance": "Usar instancia personalizada", "settings_enter_piped_api": "Ingresar URL de la API de Piped:", "settings_enter_invidious_api": "Ingresar URL de la API de Invidious:", "settings_language": "Idioma", - "settings_links_host": "Host de enlaces", + "settings_links_host": "Enlaces de host", "settings_download_format": "Formato de descarga", - "settings_pwa_share_action": "Acción de compartir PWA", + "settings_pwa_share_action": "Acción para compartir a PWA", "settings_pwa_play": "Reproducir", "settings_pwa_download": "Descargar", "settings_pwa_always_ask": "Preguntar siempre", @@ -165,5 +165,6 @@ "pwa_share_prompt": "Haz clic en Aceptar para reproducir, haz clic en Cancelar para descargar", "settings_watchmode": "Modo de vídeo", "upcoming_filter_lt10": "Filtro < 10:00", - "upcoming_filter_ytm": "Filtrar YTM" + "upcoming_filter_ytm": "Filtrar YTM", + "settings_pwa_watch": "Mirar" } diff --git a/src/modules/listUtils.ts b/src/modules/listUtils.ts index 498ffde7e..9f9852ecd 100644 --- a/src/modules/listUtils.ts +++ b/src/modules/listUtils.ts @@ -67,7 +67,7 @@ export function shareCollection(data: Collection) { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(data), + body: JSON.stringify(Object.values(data)), }) .then(res => res.text()) .then(_ => { diff --git a/src/modules/start.ts b/src/modules/start.ts index 66db69106..5920b0e5e 100644 --- a/src/modules/start.ts +++ b/src/modules/start.ts @@ -6,11 +6,10 @@ import fetchList from '../modules/fetchList'; import { fetchCollection } from "../lib/libraryUtils"; import '../scripts/library'; import '../scripts/queue'; -import { render, html } from 'uhtml'; export default async function() { - const { customInstance, shareAction } = state; + const { customInstance, shareAction, HLS } = state; if (customInstance) { @@ -32,7 +31,7 @@ export default async function() { }); - if (state.HLS) { + if (HLS) { // handling bitrates with HLS will increase complexity, better to detach from DOM bitrateSelector.remove(); if (store.player.legacy) return; @@ -75,31 +74,12 @@ export default async function() { loadingScreen.close(); } if (params.has('q')) { + searchlist.innerHTML = ''; superInput.value = params.get('q') || ''; if (params.has('f')) searchFilters.value = params.get('f') || ''; superInput.dispatchEvent(new KeyboardEvent('keydown', { 'key': 'Enter' })); - } else - render(searchlist, html` -
    - - - - - ytify is a resource efficient audio streaming client for YouTube & YTMusic. Learn more about how to use it effectively. -
    -
    -

    - - To support with connectivity costs donations can be made through the United Payments Interface at animesh.5383@waicici, even a small amount can help. -

    - What can one individual do to deal with climate change? -
    - `); + } const collection = params.get('collection'); diff --git a/src/modules/supermix.ts b/src/modules/supermix.ts index 5c08f47a0..71e2b05e4 100644 --- a/src/modules/supermix.ts +++ b/src/modules/supermix.ts @@ -1,6 +1,7 @@ import { listBtnsContainer, listContainer, listSection, loadingScreen } from "../lib/dom"; +import { renderCollection } from "../lib/libraryUtils"; import { store } from "../lib/store"; -import { convertSStoHHMMSS, goTo, renderCollection } from "../lib/utils"; +import { convertSStoHHMMSS, goTo } from "../lib/utils"; export default async function(ids: string[]) { diff --git a/src/scripts/library.ts b/src/scripts/library.ts index 7abbda357..361306a12 100644 --- a/src/scripts/library.ts +++ b/src/scripts/library.ts @@ -3,6 +3,7 @@ import { addToCollection, getDB, removeFromCollection, saveDB, toCollection } fr import { state, store } from "../lib/store"; import { render, html } from "uhtml"; import { i18n } from "./i18n"; +import { notify } from "../lib/utils"; const libraryActions = document.getElementById('libraryActions'); @@ -45,7 +46,7 @@ async function importLibrary(e: FileEv) { for (const collection in newDB) for (const item in newDB[collection]) toCollection(collection, newDB[collection][item], oldDB) saveDB(oldDB); - location.reload(); + notify(i18n('library_imported')); }; function exportLibrary() { diff --git a/src/scripts/list.ts b/src/scripts/list.ts index 201f963b6..bdccbd637 100644 --- a/src/scripts/list.ts +++ b/src/scripts/list.ts @@ -1,8 +1,8 @@ import { clearListBtn, deleteCollectionBtn, enqueueBtn, importListBtn, listBtnsContainer, listContainer, openInYtBtn, playAllBtn, shareCollectionBtn, removeFromListBtn, renameCollectionBtn, subscribeListBtn, radioCollectionBtn, sortCollectionBtn, queuelist, sortByTitleBtn, sortByAuthorBtn } from '../lib/dom'; -import { goTo, hostResolver, renderCollection } from '../lib/utils'; +import { goTo, hostResolver } from '../lib/utils'; import { store } from '../lib/store'; import { importList, subscribeList, shareCollection } from '../modules/listUtils'; -import { getDB, saveDB } from '../lib/libraryUtils'; +import { getDB, saveDB, renderCollection } from '../lib/libraryUtils'; import Sortable, { type SortableEvent } from 'sortablejs'; import { render, html } from 'uhtml'; import { i18n } from './i18n'; diff --git a/src/scripts/queue.ts b/src/scripts/queue.ts index d6c395d4c..4dcd9c388 100644 --- a/src/scripts/queue.ts +++ b/src/scripts/queue.ts @@ -53,15 +53,12 @@ const template = html` if (state.shuffle) shuffleBtn.className = 'on'; }} - @click=${(e: Event) => { - const btn = e.currentTarget as HTMLElement; - - btn.classList.toggle('on'); - setState('shuffle', btn.classList.contains('on')); - - shuffle(); - }}> - ${i18n('upcoming_shuffle')} + @click=${shuffle}> + { + shuffleBtn.classList.toggle('on'); + setState('shuffle', shuffleBtn.classList.contains('on')); + }} + class="ri-shuffle-line">${i18n('upcoming_shuffle')}
  • { const queueItem = e.target as HTMLAnchorElement & { dataset: CollectionItem }; if (!queueItem.classList.contains('streamItem')) return; - const { id } = queueItem.dataset || ''; + const { id } = queueItem.dataset; + if (!id) return; function addToTrash() { const current = sessionStorage.getItem('trashHistory') || ''; @@ -198,8 +196,9 @@ queuelist.addEventListener('click', e => { sessionStorage.setItem('trashHistory', current + id); } - if (queueItem.classList.contains('delete')) - addToTrash(); + const removeState = queueItem.classList.contains('delete'); + + if (removeState) addToTrash(); else player(id); const { list } = store.queue; @@ -209,7 +208,7 @@ queuelist.addEventListener('click', e => { list.splice(index, 1); queuelist.children[index].remove(); - if (state.shuffle) + if (state.shuffle && !removeState) shuffle(); }); diff --git a/src/scripts/theme.ts b/src/scripts/theme.ts index 66c1e6933..36295f589 100644 --- a/src/scripts/theme.ts +++ b/src/scripts/theme.ts @@ -109,7 +109,7 @@ function colorInjector(colorArray: number[]) { function themer() { const initColor = '127,127,127'; - const custom = state.customTheme || (store.player.legacy ? initColor : ''); + const custom = state.customColor || (store.player.legacy ? initColor : ''); if (state.loadImage && store.stream.id && !custom) import('../modules/extractColorFromImage') diff --git a/src/stylesheets/upcoming.css b/src/stylesheets/upcoming.css index 7e57f17a9..cf83bc76a 100644 --- a/src/stylesheets/upcoming.css +++ b/src/stylesheets/upcoming.css @@ -4,42 +4,63 @@ #queuetools { + width: 96%; + display: grid; + grid-template-columns: 1fr 1fr; + list-style: none; + gap: 0.5rem 1rem; + padding: 0.5rem 2%; + margin-bottom: 0.5rem; + background: var(--onBg); + border-bottom-left-radius: var(--roundness); + border-bottom-right-radius: var(--roundness); + + @media(orientation:landscape) { + grid-template-columns: 1fr 1fr 1fr; + } li { - i { - padding: 0.5rem; - border-radius: var(--roundness); - } + user-select: none; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + padding: 0.3rem 0.2rem; + border-radius: var(--roundness); - &:hover.on i { - filter: invert(); + i { + padding: 0.2rem; } - &:nth-child(5).on i { + &:nth-child(5).on { background: #a0a2; color: #a0a; } - &:nth-child(4).on i { + &:nth-child(4).on { background: #aa02; color: #aa0; } - &:nth-child(3).on i { + &:nth-child(3).on { background: #1bb2; color: #1bb; } - &:nth-child(2).on i { + &:nth-child(2).on { background: #f222; color: #f22; } - &:nth-child(1).on i { + &:nth-child(1).on { background: #55f2; color: #55f; } + &:hover { + text-decoration: underline dotted; + } + + } } \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 97dba05c4..53adca8ec 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,7 +10,7 @@ export default defineConfig(({ command }) => ({ define: { Locales: readdirSync(resolve(__dirname, './src/locales')).map(file => file.slice(0, 2)), Build: JSON.stringify( - ((today = new Date()) => `${today.getDate()} ${today.toLocaleString('default', { month: 'short' })} ${today.getFullYear()}`)() + ((today = new Date()) => `${today.getDate()} ${today.toLocaleString('default', { month: 'short' })}`)() ), }, plugins: [