Skip to content
Merged

lgtm #89

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions MyMusicClientSveltePwa/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion MyMusicClientSveltePwa/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mymusicclientsveltepwa",
"private": true,
"version": "0.1.11-beta",
"version": "0.1.12",
"type": "module",
"scripts": {
"dev": "vite --host",
Expand Down
9 changes: 5 additions & 4 deletions MyMusicClientSveltePwa/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import { onMount, onDestroy, setContext } from "svelte";
import { get, writable } from "svelte/store";
import { initializeRouteService, pathName, navigateTo, component, componentParams } from "./lib/scripts/routeService.js";
import { initializeRouteService, pathName, navigateTo, component as currentView, componentParams } from "./lib/scripts/routeService.js";
import PlayerBar from "./lib/components/PlayerBar.svelte";
import Modals from "./lib/components/Modals.svelte";
import { initializePlaylistService, deleteCurrentPlaylist } from "./lib/scripts/playlistService.js";
Expand All @@ -14,7 +14,7 @@
import SearchBar from "./lib/components/SearchBar.svelte";

$: $pathName;
$: $component;
$: $currentView;

// @ts-ignore
export const version = __APP_VERSION__;
Expand All @@ -33,7 +33,7 @@
}

onMount(() => {
component.subscribe((value) => {
currentView.subscribe((value) => {
// Whenever the component changes, enable auto scroll
preventAutoScroll()
});
Expand All @@ -59,6 +59,7 @@
<header class="top-bar">
<div class="top-bar-title text-center">MyMusicBox<span style="font-size: 0.8rem;">(v{version})</span></div>
<div class="row">
<!-- Configurable via js/storage/options etc per page / component -->
<div class="col-12 mt-2 justify-content-center">
<!-- Search Bar -->
<SearchBar />
Expand All @@ -69,7 +70,7 @@
<!-- Scrollable Content -->
<main class="{($autoScroll ? "scrollable-content" :"none-scrollable-content")} ">
<div class="container-fluid">
<svelte:component this={$component} {...$componentParams} />
<svelte:component this={$currentView} {...$componentParams} />
</div>
</main>

Expand Down
3 changes: 1 addition & 2 deletions MyMusicClientSveltePwa/src/lib/components/Playlist.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
songCount = getCachedPlaylistSongs(playlist.id).length;
});
</script>

{#if playlist}
<!-- Playlist Card -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
Expand All @@ -38,7 +37,7 @@
</article>
</div>
{:else}
<p>No playlist available.</p>
<p>No playlist founnd with id: {playlist.id}.</p>
{/if}

<style>
Expand Down
43 changes: 39 additions & 4 deletions MyMusicClientSveltePwa/src/lib/components/SearchBar.svelte
Original file line number Diff line number Diff line change
@@ -1,19 +1,54 @@
<script>
// @ts-nocheck
import { searchQuery } from "../scripts/util";
import { getSearchQueryFromStorage, setSearchQueryInStorage } from "../scripts/storageService";
import { onMount } from "svelte";
import { component } from "../scripts/routeService";

let query = '';

onMount(() => {
// Initialize the search query from storage
const storedQuery = getSearchQueryFromStorage();
if (storedQuery && storedQuery.length > 0) {
searchQuery.set(storedQuery);
query = storedQuery;
}

// There is a x on right side of the input that triggers a 'search' event when clicked
// it cleares the input but we also need to clear our stores and storage... :))))) javaScript
document.getElementById('search-input').addEventListener('search', (e) => {
searchQuery.set('');
setSearchQueryInStorage('');
query = '';
});

// update component on search query change
component.subscribe(() => {
searchQuery.set(getSearchQueryFromStorage());
});
});
</script>

<div class="search border border-1 border-dark" role="search" aria-label="Search music">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" d="M21 21l-4.35-4.35" />
<circle cx="11" cy="11" r="6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<input
<input
id="search-input"
bind:value={query}
type="search"
on:keydown={(e) => {
searchQuery.set(e.target.value);
on:keyup={(e) => {
setSearchQueryInStorage(query);
searchQuery.set(query);
}}
on:change={(e) => {
console.log("change event", e);
setSearchQueryInStorage(query);
searchQuery.set(query);
}}
placeholder="Search music #not working jet..."
placeholder="Search and you shall find..."
aria-label="Search"
/>
</div>
Expand Down
74 changes: 52 additions & 22 deletions MyMusicClientSveltePwa/src/lib/pages/Playlists.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,64 @@
import SongComponent from "../components/Song.svelte";
import VirtualList from "@sveltejs/svelte-virtual-list/VirtualList.svelte";
import Song from "../components/Song.svelte";
import { searchQuery } from "../scripts/util.js";

const updateIntervalTimeOutInMs = 750; // Update every 750 ms
const updateIntervalTimeOutInMs = 250; // Update every 250 ms 1/4 of a second
let intervalId;
let updating = false;
let start;
let end;

export let playlistId = -1;
let visibleCount = 100;

function loadMore() {
visibleCount += 100;
}

let songs = writable([]);
let readableSongs = [];
let visibleSongs = writable([]);
let searchQueryUnsubscribe;
let filter = "";

let componentParameters = $props();
let playlistId = componentParameters["playlistId"];

onMount(() => {
searchQueryUnsubscribe = searchQuery.subscribe((value) => {
if (!value) {
filter = "";
visibleSongs.set(readableSongs);
return;
}

visibleSongs.set(readableSongs.filter((song) => song.name.toLowerCase().includes(value.toLowerCase())));

filter = value;
});

let auto = getContext("preventAutoScroll");
auto(null);

songs.set(getCachedPlaylistSongs(playlistId));
setContext("playOrPauseSong", playOrPause);
readableSongs = getCachedPlaylistSongs(playlistId);

// This is javascript and it might break, it might not until we look in to the box like shrowdinger's cat, but for bugs
// @me-not-having-the-enegy-to-write-a-better-filter-function
if (filter && filter.length > 0) {
visibleSongs.set(readableSongs.filter((song) => song.name.toLowerCase().includes(filter.toLowerCase())));
} else {
visibleSongs.set(readableSongs);
}

intervalId = setInterval(() => {
if (updating) return; // Prevent multiple updates at the same time
updating = true;
readableSongs = getCachedPlaylistSongs(playlistId);

// This is javascript and it might break, it might not until we look in to the box like shrowdinger's cat, but for bugs
// @me-not-having-the-enegy-to-write-a-better-filter-function
if (filter && filter.length > 0) {
visibleSongs.set(readableSongs.filter((song) => song.name.toLowerCase().includes(filter.toLowerCase())));
} else {
visibleSongs.set(readableSongs);
}

songs.set(readableSongs);
updateCurrentPlaylist(playlistId);
updating = false;
Expand All @@ -49,28 +78,29 @@

onDestroy(() => {
clearInterval(intervalId);
if (searchQueryUnsubscribe) searchQueryUnsubscribe(); // stop listening
});
</script>

{#if readableSongs.length > 0}
<!-- <p>showing items {start}-{end}</p> -->
<div class='container-cs border border-dark'>
<VirtualList items={readableSongs} bind:start bind:end let:item>
<SongComponent song={item} {playlistId} />
</VirtualList>
</div>
{#if $visibleSongs && $visibleSongs.length > 0}
<!-- <p>showing items {start}-{end}</p> -->
<div class="container-cs border border-dark">
<VirtualList items={$visibleSongs} let:item>
<SongComponent song={item} playlistId={componentParameters["playlistId"]} />
</VirtualList>
</div>
{:else}
<p>No songs in playlist.</p>
<p>No songs found in playlist.</p>
{/if}

<style>
.container-cs {
.container-cs {
background: #2a2a2a;
border-top: 1px solid #333;
border-bottom: 1px solid #333;
/* min-height: calc(100vh - 30vh); */
height: calc(100vh - 35vh);
border-top: 1px solid #333;
border-bottom: 1px solid #333;
/* min-height: calc(100vh - 30vh); */
height: calc(100vh - 35vh);
padding-right: unset;
padding-left: unset;
}
}
</style>
9 changes: 9 additions & 0 deletions MyMusicClientSveltePwa/src/lib/scripts/storageService.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const CurrentSongIndexKey = "currentSongIndex";
const CurrentShuffeldPlaylistKey = "currentShuffledPlaylist";
const CurrentSongTimeKey = "currentSongTime";
const ConfigKey = "appConfig";
const ConfigKeySearchQyery = "searchQuery";

export let appConfiguration = writable(getConfiguration());

Expand All @@ -18,6 +19,14 @@ export function setConfiguration(config) {
setItem(ConfigKey, config);
}

export function setSearchQueryInStorage(query) {
setItem(ConfigKeySearchQyery, query);
}

export function getSearchQueryFromStorage() {
return getItem(ConfigKeySearchQyery) || '';
}

export function setCachedPlaylists(playlists) {
setItem(PlaylistsKey, playlists);
}
Expand Down
49 changes: 31 additions & 18 deletions MyMusicClientSveltePwa/src/lib/scripts/util.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { writable } from "svelte/store";

export let searchQuery = writable();
export let searchQuery = writable('');

// Mulberry32 PRNG
function mulberry32(seed) {
return function () {
let t = (seed += 0x6d2b79f5);
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
return ((t ^ (t >>> 16)) >>> 0) / 4294967296;
};
}

Expand All @@ -25,45 +25,58 @@ function generateSeed() {

// Fisher-Yates shuffle with optional seed
export function shuffleArray(array) {
const seed = generateSeed();
const rng = mulberry32(seed);

for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
const rng = mulberry32(generateSeed());
let i = array.length;
while (i > 1) {
const j = Math.floor(rng() * i--);
[array[i], array[j]] = [array[j], array[i]];
}

return array;
}

export function getSearchParameters() {
const searchParams = new URLSearchParams(window.location.search);
const result = {};
for (const [key, value] of searchParams.entries()) {
if (result[key]) {
// If key already exists, convert to array or push into existing array
result[key] = Array.isArray(result[key]) ? [...result[key], parseValue(value)] : [result[key], parseValue(value)];
for (const [key, value] of new URLSearchParams(window.location.search)) {
const parsed = parseValue(value);
if (key in result) {
result[key] = Array.isArray(result[key]) ? [...result[key], parsed] : [result[key], parsed];
} else {
result[key] = parseValue(value);
result[key] = parsed;
}
}
return result;
}

/**
* Parses a string value to its appropriate type.
* @param {string} value
* @returns {boolean|number|string|null|undefined}
*/
export function parseValue(value) {
if (value === "true") return true;
if (value === "false") return false;
if (!isNaN(value) && value.trim() !== "") return Number(value);
if (value == null) return value;
const trimmed = value.trim();
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed);
return value;
}

/**
* Creates a URL search string from an object of parameters.
* @param {Object} params
* @returns {string}
*/
export function createSearchParameters(params) {
const searchParams = new URLSearchParams();
for (const key in params) {
if (Array.isArray(params[key])) {
params[key].forEach((value) => searchParams.append(key, value));
const val = params[key];
if (val == null) continue;
if (Array.isArray(val)) {
for (const v of val) searchParams.append(key, v);
} else if (typeof val === "object") {
searchParams.set(key, JSON.stringify(val));
} else {
searchParams.set(key, params[key]);
searchParams.set(key, val);
}
}
return searchParams.toString();
Expand Down