Adding local search to collection and wishlist. Populating search terms in both if they don't exist on load from local storage.

This commit is contained in:
Bradley Shellnut 2022-12-28 22:21:55 -08:00
parent 458693b073
commit 8f93dde537
12 changed files with 164 additions and 96 deletions

View file

@ -27,6 +27,7 @@
"eslint": "^8.30.0", "eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"just-clone": "^6.2.0",
"just-debounce-it": "^3.2.0", "just-debounce-it": "^3.2.0",
"postcss": "^8.4.20", "postcss": "^8.4.20",
"postcss-color-functional-notation": "^4.2.4", "postcss-color-functional-notation": "^4.2.4",

View file

@ -22,6 +22,7 @@ specifiers:
eslint-config-prettier: ^8.5.0 eslint-config-prettier: ^8.5.0
eslint-plugin-svelte3: ^4.0.0 eslint-plugin-svelte3: ^4.0.0
feather-icons: ^4.29.0 feather-icons: ^4.29.0
just-clone: ^6.2.0
just-debounce-it: ^3.2.0 just-debounce-it: ^3.2.0
postcss: ^8.4.20 postcss: ^8.4.20
postcss-color-functional-notation: ^4.2.4 postcss-color-functional-notation: ^4.2.4
@ -72,6 +73,7 @@ devDependencies:
eslint: 8.30.0 eslint: 8.30.0
eslint-config-prettier: 8.5.0_eslint@8.30.0 eslint-config-prettier: 8.5.0_eslint@8.30.0
eslint-plugin-svelte3: 4.0.0_khrjkzzv5v2x7orkj5o7sxbz3a eslint-plugin-svelte3: 4.0.0_khrjkzzv5v2x7orkj5o7sxbz3a
just-clone: 6.2.0
just-debounce-it: 3.2.0 just-debounce-it: 3.2.0
postcss: 8.4.20 postcss: 8.4.20
postcss-color-functional-notation: 4.2.4_postcss@8.4.20 postcss-color-functional-notation: 4.2.4_postcss@8.4.20
@ -1566,6 +1568,10 @@ packages:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
dev: true dev: true
/just-clone/6.2.0:
resolution: {integrity: sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==}
dev: true
/just-debounce-it/3.2.0: /just-debounce-it/3.2.0:
resolution: {integrity: sha512-WXzwLL0745uNuedrCsCs3rpmfD6DBaf7uuVwaq98/8dafURfgQaBsSpjiPp5+CW6Vjltwy9cOGI6qE71b3T8iQ==} resolution: {integrity: sha512-WXzwLL0745uNuedrCsCs3rpmfD6DBaf7uuVwaq98/8dafURfgQaBsSpjiPp5+CW6Vjltwy9cOGI6qE71b3T8iQ==}
dev: true dev: true

View file

@ -53,8 +53,8 @@
title={`View ${game.name}`} title={`View ${game.name}`}
data-sveltekit-preload-data data-sveltekit-preload-data
> >
<Image src={game.thumb_url} alt={`Image of ${game.name}`} /> <!-- <Image src={game.thumb_url} alt={`Image of ${game.name}`} /> -->
<!-- <img src={game.thumb_url} alt={`Image of ${game.name}`} /> --> <img src={game.thumb_url} alt={`Image of ${game.name}`} loading="lazy" decoding="async" />
<!-- loading="lazy" decoding="async" --> <!-- loading="lazy" decoding="async" -->
</a> </a>

View file

@ -55,7 +55,7 @@
<ListboxOptions static class="options"> <ListboxOptions static class="options">
{#each pageSizes as size (size)} {#each pageSizes as size (size)}
<ListboxOption <ListboxOption
value={size} value={`${size}`}
disabled={pageSizeInputDisabled} disabled={pageSizeInputDisabled}
class={({ active }) => (active ? 'active option' : 'option')} class={({ active }) => (active ? 'active option' : 'option')}
style="display: flex; gap: 1rem; padding: 1rem;" style="display: flex; gap: 1rem; padding: 1rem;"

View file

@ -136,7 +136,9 @@
// TODO: Add cache for certain number of pages so back and forth doesn't request data again // TODO: Add cache for certain number of pages so back and forth doesn't request data again
</script> </script>
<form id="search-form" action="/search" method="get"> <form id="search-form" action="/search" method="get" on:submit={() => {
skip = 0;
}}>
<div class="search"> <div class="search">
<fieldset class="text-search" aria-busy={submitting} disabled={submitting}> <fieldset class="text-search" aria-busy={submitting} disabled={submitting}>
<label for="q"> <label for="q">

23
src/lib/stores/search.ts Normal file
View file

@ -0,0 +1,23 @@
import { writable } from 'svelte/store';
import type { SavedGameType } from '../types';
export const createSearchStore = (data: SavedGameType[]) => {
const { subscribe, set, update } = writable({
data,
filtered: data,
search: ''
});
return {
subscribe,
set,
update
};
};
export const searchHandler = (store) => {
const searchTerm = store.search.toLowerCase() || '';
store.filtered = store.data.filter((item) => {
return item.searchTerms.toLowerCase().includes(searchTerm);
});
};

View file

@ -46,6 +46,7 @@ export type SavedGameType = {
thumb_url: string; thumb_url: string;
players: string; players: string;
playtime: string; playtime: string;
searchTerms: string;
}; };
export type GameType = { export type GameType = {

View file

@ -6,7 +6,8 @@ export function convertToSavedGame(game: GameType | SavedGameType): SavedGameTyp
name: game.name, name: game.name,
thumb_url: game.thumb_url, thumb_url: game.thumb_url,
players: game.players, players: game.players,
playtime: game.playtime playtime: game.playtime,
searchTerms: `${game.name.toLowerCase()}`
}; };
} }

View file

@ -2,6 +2,7 @@
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { navigating } from '$app/stores'; import { navigating } from '$app/stores';
import debounce from 'just-debounce-it'; import debounce from 'just-debounce-it';
import clone from 'just-clone';
import { Toy } from '@leveluptuts/svelte-toy'; import { Toy } from '@leveluptuts/svelte-toy';
// import '../app.postcss'; // import '../app.postcss';
import Analytics from '$lib/components/analytics.svelte'; import Analytics from '$lib/components/analytics.svelte';
@ -16,6 +17,7 @@
import { toast } from '$lib/components/toast/toast'; import { toast } from '$lib/components/toast/toast';
import Toast from '$lib/components/toast/Toast.svelte'; import Toast from '$lib/components/toast/Toast.svelte';
import '$root/styles/styles.pcss'; import '$root/styles/styles.pcss';
import type { SavedGameType } from '$root/lib/types';
$: { $: {
if ($navigating) { if ($navigating) {
@ -34,14 +36,24 @@
let collectionEmpty = $collectionStore.length === 0 || false; let collectionEmpty = $collectionStore.length === 0 || false;
let wishlistEmpty = $wishlistStore.length === 0 || false; let wishlistEmpty = $wishlistStore.length === 0 || false;
if (wishlistEmpty && localStorage?.wishlist && localStorage?.wishlist?.length !== 0) { if (wishlistEmpty && localStorage?.wishlist && localStorage?.wishlist?.length !== 0) {
const wishlist = JSON.parse(localStorage.wishlist); const wishlist: SavedGameType[] = JSON.parse(localStorage.wishlist);
if (wishlist?.length !== 0) { if (wishlist?.length !== 0) {
for (const item of wishlist) {
if (!item?.searchTerms) {
item.searchTerms = `${item?.name?.toLowerCase()}`;
}
}
wishlistStore.addAll(wishlist); wishlistStore.addAll(wishlist);
} }
} }
if (collectionEmpty && localStorage?.collection && localStorage?.collection?.length !== 0) { if (collectionEmpty && localStorage?.collection && localStorage?.collection?.length !== 0) {
const collection = JSON.parse(localStorage.collection); const collection: SavedGameType[] = JSON.parse(localStorage.collection);
if (collection?.length !== 0) { if (collection?.length !== 0) {
for (const item of collection) {
if (!item?.searchTerms) {
item.searchTerms = `${item?.name?.toLowerCase()}`;
}
}
collectionStore.addAll(collection); collectionStore.addAll(collection);
} }
} }

View file

@ -5,9 +5,9 @@
import Random from '$lib/components/random/index.svelte'; import Random from '$lib/components/random/index.svelte';
export let data: PageData; export let data: PageData;
console.log('data', data); // console.log('data', data);
export let form: ActionData; export let form: ActionData;
console.log('form', form); // console.log('form', form);
</script> </script>
<svelte:head> <svelte:head>

View file

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { tick, onDestroy } from 'svelte';
import Game from '$lib/components/game/index.svelte'; import Game from '$lib/components/game/index.svelte';
import { collectionStore } from '$lib/stores/collectionStore'; import { collectionStore } from '$lib/stores/collectionStore';
import type { GameType, SavedGameType } from '$root/lib/types'; import type { GameType, SavedGameType } from '$root/lib/types';
@ -6,15 +7,24 @@
import Pagination from '$root/lib/components/pagination/index.svelte'; import Pagination from '$root/lib/components/pagination/index.svelte';
import RemoveCollectionDialog from '$root/lib/components/dialog/RemoveCollectionDialog.svelte'; import RemoveCollectionDialog from '$root/lib/components/dialog/RemoveCollectionDialog.svelte';
import RemoveWishlistDialog from '$root/lib/components/dialog/RemoveWishlistDialog.svelte'; import RemoveWishlistDialog from '$root/lib/components/dialog/RemoveWishlistDialog.svelte';
import { tick } from 'svelte'; import { createSearchStore, searchHandler } from '$root/lib/stores/search';
let gameToRemove: GameType | SavedGameType; let gameToRemove: GameType | SavedGameType;
let pageSize = 10; let pageSize = 10;
let page = 1; let page = 1;
$: totalItems = $collectionStore.length; const searchStore = createSearchStore($collectionStore);
console.log('searchStore', $searchStore);
const unsubscribe = searchStore.subscribe((model) => searchHandler(model));
onDestroy(() => {
unsubscribe();
});
$: skip = (page - 1) * pageSize; $: skip = (page - 1) * pageSize;
$: gamesShown = $collectionStore.slice(skip, skip + pageSize); $: gamesShown = $searchStore.filtered.slice(skip, skip + pageSize);
$: totalItems = $searchStore.search === '' ? $collectionStore.length : $searchStore.filtered.length;
interface RemoveGameEvent extends Event { interface RemoveGameEvent extends Event {
detail: GameType | SavedGameType; detail: GameType | SavedGameType;
@ -66,6 +76,7 @@
</svelte:head> </svelte:head>
<h1>Your Collection</h1> <h1>Your Collection</h1>
<input type="text" id="search" name="search" bind:value={$searchStore.search} />
<div class="games"> <div class="games">
<div class="games-list"> <div class="games-list">

View file

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { tick, onDestroy } from 'svelte';
import Game from '$lib/components/game/index.svelte'; import Game from '$lib/components/game/index.svelte';
import { wishlistStore } from '$lib/stores/wishlistStore'; import { wishlistStore } from '$lib/stores/wishlistStore';
import type { GameType, SavedGameType } from '$root/lib/types'; import type { GameType, SavedGameType } from '$root/lib/types';
@ -6,15 +7,24 @@
import Pagination from '$root/lib/components/pagination/index.svelte'; import Pagination from '$root/lib/components/pagination/index.svelte';
import RemoveWishlistDialog from '$root/lib/components/dialog/RemoveWishlistDialog.svelte'; import RemoveWishlistDialog from '$root/lib/components/dialog/RemoveWishlistDialog.svelte';
import RemoveCollectionDialog from '$root/lib/components/dialog/RemoveCollectionDialog.svelte'; import RemoveCollectionDialog from '$root/lib/components/dialog/RemoveCollectionDialog.svelte';
import { tick } from 'svelte'; import { createSearchStore, searchHandler } from '$root/lib/stores/search';
let gameToRemove: GameType | SavedGameType; let gameToRemove: GameType | SavedGameType;
let pageSize = 10; let pageSize = 10;
let page = 1; let page = 1;
$: totalItems = $wishlistStore.length; const searchStore = createSearchStore($wishlistStore);
console.log('searchStore', $searchStore);
const unsubscribe = searchStore.subscribe((model) => searchHandler(model));
onDestroy(() => {
unsubscribe();
});
$: skip = (page - 1) * pageSize; $: skip = (page - 1) * pageSize;
$: gamesShown = $wishlistStore.slice(skip, skip + pageSize); $: gamesShown = $searchStore.filtered.slice(skip, skip + pageSize);
$: totalItems = $searchStore.search === '' ? $wishlistStore.length : $searchStore.filtered.length;
interface RemoveGameEvent extends Event { interface RemoveGameEvent extends Event {
detail: GameType | SavedGameType; detail: GameType | SavedGameType;
@ -62,6 +72,7 @@
</svelte:head> </svelte:head>
<h1>Your Wishlist</h1> <h1>Your Wishlist</h1>
<input type="text" id="search" name="search" bind:value={$searchStore.search} />
<div class="games"> <div class="games">
<div class="games-list"> <div class="games-list">