diff --git a/index.html b/index.html index a35206aac..bf76c4319 100644 --- a/index.html +++ b/index.html @@ -209,29 +209,6 @@

Upcoming streams show here

-
- - - - - - - - - - - - - - - - -
-
@@ -364,4 +341,4 @@

Playlist, channel or your collection items show - + \ No newline at end of file diff --git a/package.json b/package.json index bb37cb3f3..1007f9931 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,30 @@ { "name": "ytify", "type": "module", - "version": "7.8", "scripts": { "dev": "vite", "build": "tsc && vite build", - "preview": "vite preview", - "update": "npx npm-check-updates -u" + "preview": "vite preview" }, "dependencies": { - "hls.js": "^1.5.20", - "solid-js": "^1.9.4", - "sortablejs": "^1.15.6" + "hls.js": "^1.6.2", + "solid-js": "^1.9.6", + "sortablejs": "^1.15.6", + "uhtml": "^4.7.1" }, "devDependencies": { - "@netlify/blobs": "^8.1.0", - "@netlify/edge-functions": "^2.11.1", - "@types/node": "^22.13.1", + "@netlify/blobs": "^9.1.1", + "@netlify/edge-functions": "^2.12.0", + "@types/node": "^22.15.17", "@types/sortablejs": "^1.15.8", - "autoprefixer": "^10.4.20", + "autoprefixer": "^10.4.21", "eruda": "^3.4.1", - "typescript": "^5.7.3", - "vite": "^6.1.0", - "vite-plugin-pwa": "^0.21.1", - "vite-plugin-solid": "^2.11.1" + "typescript": "^5.8.3", + "vite": "^6.3.5", + "vite-plugin-pwa": "^1.0.0", + "vite-plugin-solid": "^2.11.6" }, "browserslist": [ "defaults" ] -} +} \ No newline at end of file diff --git a/public/logo192.png b/public/logo192.png old mode 100644 new mode 100755 index 1b244ba6d..5e8c42dda Binary files a/public/logo192.png and b/public/logo192.png differ diff --git a/public/logo192old.png b/public/logo192old.png new file mode 100644 index 000000000..1b244ba6d Binary files /dev/null and b/public/logo192old.png differ diff --git a/public/logo512.png b/public/logo512.png old mode 100644 new mode 100755 index 3a2548748..41fce3bc7 Binary files a/public/logo512.png and b/public/logo512.png differ diff --git a/public/logo512old.png b/public/logo512old.png new file mode 100644 index 000000000..3a2548748 Binary files /dev/null and b/public/logo512old.png differ diff --git a/src/components/ActionsMenu.css b/src/components/ActionsMenu.css index e06e9a797..e1f819d6e 100644 --- a/src/components/ActionsMenu.css +++ b/src/components/ActionsMenu.css @@ -56,4 +56,4 @@ font-size: inherit; margin-left: -3px; width: 9ch; -} \ No newline at end of file +} diff --git a/src/components/ItemsLoader.tsx b/src/components/ItemsLoader.tsx index bedbb2e18..e3d73960d 100644 --- a/src/components/ItemsLoader.tsx +++ b/src/components/ItemsLoader.tsx @@ -1,11 +1,17 @@ import { createEffect, createSignal, For, Show } from "solid-js"; import { generateImageUrl, getThumbIdFromLink } from "../lib/imageUtils"; -import { convertSStoHHMMSS, hostResolver } from "../lib/utils"; +import { convertSStoHHMMSS, hostResolver, i18n } from "../lib/utils"; import ListItem from "./ListItem"; import StreamItem from "./StreamItem"; const numFormatter = (num: number): string => Intl.NumberFormat('en', { notation: 'compact' }).format(num); +const reservedCollections = { + discover: ['ri-compass-3-line', 'library_discover'], + history: ['ri-memories-line', 'library_history'], + favorites: ['ri-heart-fill', 'library_favorites'], + listenLater: ['ri-calendar-schedule-line', 'library_listen_later'] +} export default function ItemsLoader(data: { itemsArray: StreamItem[] }) { const [items, setItems] = createSignal(data.itemsArray); @@ -23,7 +29,16 @@ export default function ItemsLoader(data: { itemsArray: StreamItem[] }) { item.type === 'collection' ? - {item.name} + + + {item.name} + + }> + + {i18n(reservedCollections[item.name as 'history'][1] as 'library_history')} + : (item.type === 'stream' || item.type === 'video') ? diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 269b8b6a8..d513022d1 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -182,6 +182,7 @@ export default function() { }} > + diff --git a/src/components/SuperCollectionList.tsx b/src/components/SuperCollectionList.tsx index 055cf1b3d..7d603d73b 100644 --- a/src/components/SuperCollectionList.tsx +++ b/src/components/SuperCollectionList.tsx @@ -75,11 +75,12 @@ function loadForYou(db: Library) { function loadCollections(db: Library) { const keys = Object.keys(db); - return keys.length ? - keys - .filter(v => !reservedCollections.includes(v)) - .map(v => ({ type: 'collection', name: v })) : - 'No Collections in Library'; + return (keys.length ? + keys : reservedCollections) + .filter(v => v !== 'channels' && v !== 'playlists') + .map(v => ({ type: 'collection', name: v })); + + } // APAC : artists | playlists | albums | channels diff --git a/src/components/UpdatePrompt.ts b/src/components/UpdatePrompt.ts new file mode 100644 index 000000000..55f9ad124 --- /dev/null +++ b/src/components/UpdatePrompt.ts @@ -0,0 +1,32 @@ +import './UpdatePrompt.css'; +import { i18n } from "../lib/utils"; +import { html } from 'uhtml'; + +export default async function UpdatePrompt() { + + const commitsSrc = 'https://api.github.com/repos/n-ce/ytify/commits/main'; + const commitsLink = 'https://github.com/n-ce/ytify/commits'; + + const list = await fetch(commitsSrc) + .then(res => res.json()) + .then(data => data.commit.message.split('-')) + .then(data => data.map((text: string) => (html`
  • ${text}
  • `))) + .catch(() => html`
  • Failed to load update data from Github.
  • `); + + + return html` +
      + ${list} +
      +
    • { open(commitsLink) }}> + ${i18n('updater_changelog_full')} +
    • +
    + + + + `; + +} diff --git a/src/components/UpdatePrompt.tsx b/src/components/UpdatePrompt.tsx deleted file mode 100644 index da096eb92..000000000 --- a/src/components/UpdatePrompt.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createSignal, onMount } from "solid-js"; -import './UpdatePrompt.css'; -import { i18n } from "../lib/utils"; - -export default function UpdatePrompt(handleUpdate: () => void) { - - const [list, setList] = createSignal([
  • Loading Update
  • ]); - const [fullList, setFullList] = createSignal(['']); - let dialog!: HTMLDialogElement; - - onMount(async () => { - const data = await fetch('https://api.github.com/repos/n-ce/ytify/commits/main').then(res => res.json()); - const list = data.commit.message.split('-'); - const e = list.map((text: string) => (
  • {text}
  • )) - setList(e); - }); - - const handleFullList = () => - fetch('https://raw.githubusercontent.com/wiki/n-ce/ytify/Changelog.md') - .then(res => res.text()) - .then(text => text.split('\n')) - .then(e => setFullList(e)); - - - return ( - -
      - {list()} -
      - {fullList().length > 2 ? - fullList().map((text: string) => (
    • {text}
    • )) - : -
    • {i18n('updater_changelog_full')}
    • - } -
    - - - - -
    - ); - -} diff --git a/src/lib/libraryUtils.ts b/src/lib/libraryUtils.ts index f059793bf..36955eed1 100644 --- a/src/lib/libraryUtils.ts +++ b/src/lib/libraryUtils.ts @@ -11,9 +11,6 @@ export function saveDB(data: Library, change: string = '') { dispatchEvent(new CustomEvent('dbchange', { detail: { db: data, change: change } })); } -export const getCollection = (name: string) => (document.getElementById(name)).lastElementChild; - - export function removeFromCollection( collection: string, id: string @@ -156,7 +153,7 @@ function getLocalCollection( if (collection === 'discover') { for (const i in data) - if ((data[i] as CollectionItem & { frequency: number }).frequency < 2) + if (usePagination && (data[i] as CollectionItem & { frequency: number }).frequency < 2) delete db.discover?.[i]; saveDB(db); } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9040ab201..80ff0804f 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -179,6 +179,12 @@ export async function superClick(e: Event) { const eld = elem.dataset; const elc = elem.classList.contains.bind(elem.classList); + const rcn = { + Discover: 'discover', + History: 'history', + Favorites: 'favorites', + 'Listen Later': 'listenLater' + } if (elc('streamItem')) return elc('delete') ? @@ -186,7 +192,12 @@ export async function superClick(e: Event) { : player(eld.id); else if (elc('clxn_item')) - fetchCollection(elem.textContent as string); + fetchCollection( + (elem.textContent! in rcn) ? + rcn[elem.textContent as 'History'] : + elem.textContent as string + ); + else if (elc('ri-more-2-fill')) { actionsMenu.showModal(); diff --git a/src/locales/en.json b/src/locales/en.json index 8df4abcea..03f5ca2b2 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -97,6 +97,7 @@ "settings_download_format": "Download Format", "settings_pwa_share_action": "PWA Share Action", "settings_pwa_play": "Play", + "settings_pwa_watch": "Watch", "settings_pwa_download": "Download", "settings_pwa_always_ask": "Always ask", "settings_search": "Search", diff --git a/src/locales/ro.json b/src/locales/ro.json index b43dddb42..2f613d243 100644 --- a/src/locales/ro.json +++ b/src/locales/ro.json @@ -5,8 +5,8 @@ "nav_upcoming": "Urmează", "player_audiostreams_setup": "Setarea acum AudioStreams…", "player_audiostreams_insert": "Inserarea sursei de audio în player…", - "player_livestreams_hls": "", - "player_audiostreams_null": "", + "player_livestreams_hls": "Activează HLS pentru a asculta fluxuri live!", + "player_audiostreams_null": "Nu s-au găsit fluxuri audio", "player_now_playing": "Redarea Acum", "player_channel": "Canal", "player_volume": "Volum", @@ -18,11 +18,11 @@ "player_seek_backward": "Derulare înapoi", "player_seek_forward": "Derulare înainte", "player_play_next": "Redă următorul", - "upcoming_clear": "Golește", + "upcoming_clear": "Golește Coada", "upcoming_shuffle": "Amestecă", "upcoming_remove": "Elimină", "upcoming_filter": "Filtru < 10:00", - "upcoming_enqueue_related": "Adaugă în coadă fluxuri relevante", + "upcoming_enqueue_related": "Adaugă la coadă fluxuri relevante", "upcoming_allow_duplicates": "Permite Duplicări", "upcoming_info": "Fluxuri viitoare arată aici", "fetchlist_url_null": "", @@ -55,13 +55,13 @@ "library_for_you": "Pentru Tine", "library_import": "Importă", "library_export": "Exportă", - "library_clean": "Golire", + "library_clean": "Golire Biblotecă", "library_clean_prompt": "Ești sigur că vrei să golești $ articole din bibliotecă?", - "library_import_prompt": "", + "library_import_prompt": "Asta va combina bibloteca ta curentă cu bibloteca importată, continuă?", "list_play": "Redă", "list_enqueue": "Adaugă la coadă", "list_import": "Importă", - "list_imported": "", + "list_imported": "$ a fost importat la colecțiile tale.", "list_set_title": "Setează Titlu", "list_clear_all": "Golește tot", "list_remove": "Elimină", @@ -82,20 +82,20 @@ "actions_menu_debug_info": "Informații debug", "collection_selector_add_to": "Adaugă la", "collection_selector_create_new": "Creează o nouă Colecție", - "collection_selector_favorites": "", - "collection_selector_listen_later": "", - "settings_custom_instance": "", - "settings_enter_piped_api": "", - "settings_enter_invidious_api": "", - "settings_language": "", + "collection_selector_favorites": "Favorite", + "collection_selector_listen_later": "Ascultă mai târziu", + "settings_custom_instance": "Folosește instanța custom", + "settings_enter_piped_api": "Introdu Piped API URL :", + "settings_enter_invidious_api": "Introdu Invidious API URL :", + "settings_language": "Limbă", "settings_links_host": "", - "settings_download_format": "", - "settings_pwa_share_action": "", - "settings_pwa_play": "", - "settings_pwa_download": "", - "settings_pwa_always_ask": "", + "settings_download_format": "Descarcă Format-ul", + "settings_pwa_share_action": "PWA Acțiunea Partajare", + "settings_pwa_play": "Redă", + "settings_pwa_download": "Descarcă", + "settings_pwa_always_ask": "Intreabă intotdeauna", "settings_search": "Căutare", - "settings_set_songs_as_default_filter": "", + "settings_set_songs_as_default_filter": "Setează Melodii ca Filtru Implicit", "settings_display_suggestions": "Afișare sugestii", "settings_playback": "Redare", "settings_hq_audio": "Audio de cea mai înaltă calitate", @@ -108,20 +108,20 @@ "settings_store_discoveries": "Storează Descoperirele", "settings_clear_discoveries": "Asta va goli $ discoperirile tale existente, continuă?", "settings_store_history": "Storează Istoric", - "settings_clear_history": "", - "settings_import_from_piped": "", - "settings_interface": "", - "settings_load_images": "", - "settings_roundness": "", - "settings_roundness_none": "", + "settings_clear_history": "Asta va goli $ articole din istoricul tău, continuă?", + "settings_import_from_piped": "Importă Playlist-uri din Piped", + "settings_interface": "Interfață", + "settings_load_images": "Încarcă Imagini", + "settings_roundness": "Rotunjire", + "settings_roundness_none": "Niciunul", "settings_roundness_lighter": "", - "settings_roundness_light": "", + "settings_roundness_light": "Luminat", "settings_roundness_heavy": "", "settings_roundness_heavier": "", "settings_use_custom_color": "", "settings_custom_color_prompt": "", "settings_theming_scheme": "", - "settings_theming_scheme_dynamic": "", + "settings_theming_scheme_dynamic": "Dinamic", "settings_theming_scheme_system": "Sistem", "settings_theming_scheme_light": "Luminos", "settings_theming_scheme_dark": "Întunecat", @@ -133,18 +133,18 @@ "settings_parental_controls": "", "settings_pin_toggle": "", "settings_pin_message": "", - "settings_pin_prompt": "", - "settings_pin_incorrect": "", + "settings_pin_prompt": "Întrodu PIN", + "settings_pin_incorrect": "Încorrect PIN!", "settings_feedback_placeholder": "", - "settings_feedback_submit": "", - "settings_changelog": "", - "settings_clear_cache": "", - "settings_restore": "", - "settings_export": "", - "settings_import": "", + "settings_feedback_submit": "Trimite Feedback", + "settings_changelog": "Changelog", + "settings_clear_cache": "Golește Cache", + "settings_restore": "Restaurează setările", + "settings_export": "Exportă Setări", + "settings_import": "Importă Setări", "piped_enter_auth": "", "piped_enter_username": "", - "piped_enter_password": "", + "piped_enter_password": "Parolă", "piped_success_auth": "", "piped_failed_auth": "", "piped_success_imported": "", @@ -155,9 +155,14 @@ "piped_failed_token": "", "piped_success_logged": "", "updater_changelog_full": "", - "updater_update": "", + "updater_update": "Actualizare", "updater_later": "", "pwa_share_prompt": "", "settings_watchmode": "Modul video", - "settings_library_sync": "Sincronizare Cloud" + "settings_library_sync": "Sincronizare Cloud", + "upcoming_change": "Schimbările se vor aplica începând cu următorul flux", + "list_prompt_clear": "Ești sigur că vrei să golești $ ?", + "upcoming_filter_lt10": "Filtru < 10:00", + "list_prompt_rename": "Introdu un titlu nou", + "list_prompt_delete": "Ești sigur că vrei să ștergi colecția $ ?" } diff --git a/src/main.ts b/src/main.ts index 81266a218..e3c11f85c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,7 @@ import './scripts/list'; import './scripts/search'; import './scripts/library'; import { render } from 'solid-js/web'; +import { render as uhtml } from 'uhtml'; import { actionsMenu, superCollectionList } from './lib/dom'; addEventListener('DOMContentLoaded', async () => { @@ -28,10 +29,22 @@ addEventListener('DOMContentLoaded', async () => { await import('virtual:pwa-register').then(pwa => { const handleUpdate = pwa.registerSW({ onNeedRefresh() { - import('./components/UpdatePrompt').then(mod => - render(() => mod.default(handleUpdate), - document.body - )); + const dialog = document.createElement('dialog') as HTMLDialogElement; + dialog.id = 'changelog'; + dialog.open = true; + dialog.addEventListener('click', (e) => { + const elm = e.target as HTMLButtonElement; + if (elm.id === 'updateBtn' || elm.closest('#updateBtn')) + handleUpdate(); + if (elm.id === 'laterBtn' || elm.closest('#laterBtn')) { + dialog.close(); + dialog.remove(); + } + }) + + import('./components/UpdatePrompt') + .then(async mod => uhtml(dialog, await mod.default())) + .then(() => document.body.appendChild(dialog)); } }); }); diff --git a/src/modules/setDiscoveries.ts b/src/modules/setDiscoveries.ts index 65aa79463..d4cc15e72 100644 --- a/src/modules/setDiscoveries.ts +++ b/src/modules/setDiscoveries.ts @@ -1,7 +1,6 @@ -import { listAnchor } from "../lib/dom"; import { addListToCollection, getDB } from "../lib/libraryUtils"; -import { params, store } from "../lib/store"; -import { convertSStoHHMMSS, goTo } from "../lib/utils"; +import { store } from "../lib/store"; +import { convertSStoHHMMSS } from "../lib/utils"; export default function( id: string, @@ -62,9 +61,4 @@ export default function( // insert the upgraded collection to discover; addListToCollection('discover', Object.fromEntries(array), db); - // just in case we are already in the discover collection - if (listAnchor.classList.contains('view') && params.get('collection') === 'discover') - goTo('discover'); - - } diff --git a/src/modules/start.ts b/src/modules/start.ts index 740bdf6b9..279f76744 100644 --- a/src/modules/start.ts +++ b/src/modules/start.ts @@ -4,6 +4,7 @@ import { $, getDownloadLink, i18n, idFromURL, proxyHandler } from '../lib/utils' import { bitrateSelector, searchFilters, superInput, audio, loadingScreen, ytifyIcon, searchlist } from '../lib/dom'; import fetchList from '../modules/fetchList'; import { fetchCollection } from "../lib/libraryUtils"; +import { render } from 'solid-js/web'; export default async function() { @@ -58,7 +59,12 @@ export default async function() { if (id) { loadingScreen.showModal(); - if (isPWA && shareAction) { + if (isPWA && shareAction === 'watch') { + store.actionsMenu.id = id; + import('../components/WatchVideo') + .then(mod => render(mod.default, document.body)); + } + else if (isPWA && shareAction) { const a = $('a'); const l = await getDownloadLink(store.actionsMenu.id); if (l) { diff --git a/src/scripts/audioEvents.ts b/src/scripts/audioEvents.ts index 22f25bb25..fa9af3660 100644 --- a/src/scripts/audioEvents.ts +++ b/src/scripts/audioEvents.ts @@ -1,9 +1,9 @@ -import { audio, listAnchor, playButton, progress, queuelist, title } from "../lib/dom"; +import { audio, playButton, progress, queuelist, title } from "../lib/dom"; import player from "../lib/player"; -import { convertSStoHHMMSS, goTo, removeSaved, save } from "../lib/utils"; +import { convertSStoHHMMSS, removeSaved, save } from "../lib/utils"; import { getSaved, params, store } from "../lib/store"; import { appendToQueuelist, firstItemInQueue } from "./queue"; -import { addToCollection, getCollection } from "../lib/libraryUtils"; +import { addToCollection } from "../lib/libraryUtils"; import audioErrorHandler from "../modules/audioErrorHandler"; import getStreamData from "../modules/getStreamData"; @@ -58,21 +58,10 @@ audio.onplaying = function() { if (getSaved('history') === 'off') return; - const firstElementInHistory = getCollection('history').firstElementChild; - - if (firstElementInHistory?.dataset.id !== id) - historyTimeoutId = window.setTimeout(() => { - if (historyID === id) { - addToCollection('history', store.stream); - // just in case we are already in the history collection - if ( - listAnchor.classList.contains('view') && - params.get('collection') === 'history' - ) - goTo('history'); - - } - }, 1e4); + historyTimeoutId = window.setTimeout(() => { + if (historyID === id) + addToCollection('history', store.stream, 'addNew'); + }, 1e4); } audio.onpause = function() { diff --git a/src/scripts/library.ts b/src/scripts/library.ts index 57f819bfb..ec0204265 100644 --- a/src/scripts/library.ts +++ b/src/scripts/library.ts @@ -1,12 +1,11 @@ import { favButton, favIcon } from "../lib/dom"; -import { addToCollection, fetchCollection, getDB, removeFromCollection, saveDB, toCollection } from "../lib/libraryUtils"; +import { addToCollection, getDB, removeFromCollection, saveDB, toCollection } from "../lib/libraryUtils"; import { $, i18n, notify, removeSaved } from "../lib/utils"; import { getSaved, store } from "../lib/store"; const importBtn = document.getElementById('upload') as HTMLInputElement; const exportBtn = document.getElementById('exportBtn') as HTMLButtonElement; const cleanBtn = document.getElementById('cleanLibraryBtn') as HTMLButtonElement; -const collectionContainer = document.getElementById('collections') as HTMLDivElement; importBtn.addEventListener('change', async () => { const newDB = JSON.parse(await (importBtn.files)[0].text()); @@ -47,12 +46,6 @@ favButton.addEventListener('click', () => { }); -collectionContainer.addEventListener('click', e => { - e.preventDefault(); - const elm = e.target as HTMLAnchorElement; - if (elm.classList.contains('collectionItem')) - fetchCollection(elm.id); -}); const dbhash = getSaved('dbsync'); const hashpoint = location.origin + '/dbs/' + dbhash; diff --git a/src/stylesheets/library.css b/src/stylesheets/library.css index 53a0f4875..4cd6c6cd2 100644 --- a/src/stylesheets/library.css +++ b/src/stylesheets/library.css @@ -1,35 +1,6 @@ #library { width: 100%; - #collections { - margin: 0 4vmin; - padding: 0; - - - a { - border: var(--border); - background: var(--onBg); - border-radius: var(--roundness); - padding: 2vmin 3vmin; - margin: 0 2vmin; - margin-top: 6vmin; - padding-right: calc(3vmin + 0.4rem); - display: inline-flex; - align-items: center; - - &::after { - content: attr(aria-label); - } - - i { - font-weight: initial; - pointer-events: none; - margin-right: 0.4rem; - } - } - - } - select { margin: 1rem 0 1%; background: var(--onBg);