mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Adding skeleton component from carbon, adding wishlist button to game page, fixing mapping, and css styles.
This commit is contained in:
parent
06a7d342a3
commit
ea32e9e6ff
10 changed files with 215 additions and 43 deletions
115
src/lib/components/SkeletonPlaceholder.svelte
Normal file
115
src/lib/components/SkeletonPlaceholder.svelte
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<!-- Taken from carbon design system svelte -->
|
||||
<!-- https://github.com/carbon-design-system/carbon-components-svelte/blob/master/src/SkeletonPlaceholder/SkeletonPlaceholder.svelte -->
|
||||
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<div
|
||||
class:bx--skeleton__placeholder={true}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
/>
|
||||
|
||||
<style lang="scss">
|
||||
@keyframes skeleton {
|
||||
0% {
|
||||
opacity: 0.3;
|
||||
-webkit-transform: scaleX(0);
|
||||
transform: scaleX(0);
|
||||
-webkit-transform-origin: left;
|
||||
transform-origin: left;
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scaleX(1);
|
||||
transform: scaleX(1);
|
||||
-webkit-transform-origin: left;
|
||||
transform-origin: left;
|
||||
}
|
||||
28% {
|
||||
-webkit-transform: scaleX(1);
|
||||
transform: scaleX(1);
|
||||
-webkit-transform-origin: right;
|
||||
transform-origin: right;
|
||||
}
|
||||
51% {
|
||||
-webkit-transform: scaleX(0);
|
||||
transform: scaleX(0);
|
||||
-webkit-transform-origin: right;
|
||||
transform-origin: right;
|
||||
}
|
||||
58% {
|
||||
-webkit-transform: scaleX(0);
|
||||
transform: scaleX(0);
|
||||
-webkit-transform-origin: right;
|
||||
transform-origin: right;
|
||||
}
|
||||
82% {
|
||||
-webkit-transform: scaleX(1);
|
||||
transform: scaleX(1);
|
||||
-webkit-transform-origin: right;
|
||||
transform-origin: right;
|
||||
}
|
||||
83% {
|
||||
-webkit-transform: scaleX(1);
|
||||
transform: scaleX(1);
|
||||
-webkit-transform-origin: left;
|
||||
transform-origin: left;
|
||||
}
|
||||
96% {
|
||||
-webkit-transform: scaleX(0);
|
||||
transform: scaleX(0);
|
||||
-webkit-transform-origin: left;
|
||||
transform-origin: left;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
-webkit-transform: scaleX(0);
|
||||
transform: scaleX(0);
|
||||
-webkit-transform-origin: left;
|
||||
transform-origin: left;
|
||||
}
|
||||
}
|
||||
|
||||
.bx--skeleton__placeholder {
|
||||
--cds-skeleton-01: #474747;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: var(--cds-skeleton-01, #e5e5e5);
|
||||
box-shadow: none;
|
||||
pointer-events: none;
|
||||
width: 6.25rem;
|
||||
height: 6.25rem;
|
||||
}
|
||||
|
||||
.bx--skeleton__placeholder:hover,
|
||||
.bx--skeleton__placeholder:focus,
|
||||
.bx--skeleton__placeholder:active {
|
||||
border: none;
|
||||
cursor: default;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.bx--skeleton__placeholder::before {
|
||||
--cds-skeleton-02: #525252;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-webkit-animation: 3000ms ease-in-out skeleton infinite;
|
||||
animation: 3000ms ease-in-out skeleton infinite;
|
||||
background: var(--cds-skeleton-02, #c6c6c6);
|
||||
content: '';
|
||||
will-change: transform-origin, transform, opacity;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.bx--skeleton__placeholder::before {
|
||||
-webkit-animation: none;
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -10,7 +10,6 @@
|
|||
import { browser } from '$app/environment';
|
||||
|
||||
export let game: GameType | SavedGameType;
|
||||
export let minimal: boolean = false;
|
||||
export let detailed: boolean = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
|
@ -23,6 +22,24 @@
|
|||
dispatch('handleRemoveCollection', game);
|
||||
}
|
||||
|
||||
// Naive and assumes description is only on our GameType at the moment
|
||||
function isGameType(game: GameType | SavedGameType): game is GameType {
|
||||
return (game as GameType).description !== undefined;
|
||||
}
|
||||
|
||||
// function lazy(img: HTMLImageElement) {
|
||||
// function loaded() {
|
||||
// img.classList.add('loaded');
|
||||
// img.classList.remove('loading');
|
||||
// }
|
||||
// if (img.complete) {
|
||||
// loaded();
|
||||
// } else {
|
||||
// img.classList.add('loading');
|
||||
// img.onload = () => loaded();
|
||||
// }
|
||||
// }
|
||||
|
||||
$: existsInCollection = $collectionStore.find((item: SavedGameType) => item.id === game.id);
|
||||
$: existsInWishlist = $wishlistStore.find((item: SavedGameType) => item.id === game.id);
|
||||
</script>
|
||||
|
|
@ -31,15 +48,16 @@
|
|||
<h2>{game.name}</h2>
|
||||
<a class="thumbnail" href={`/game/${game.id}`}>
|
||||
<img src={game.thumb_url} alt={`Image of ${game.name}`} />
|
||||
<!-- loading="lazy" decoding="async" -->
|
||||
</a>
|
||||
|
||||
<div class="game-details">
|
||||
<p>Players: {game.min_players} - {game.max_players}</p>
|
||||
<p>Players: {game.players}</p>
|
||||
<p>Time: {game.playtime} minutes</p>
|
||||
{#if game?.min_age}
|
||||
{#if isGameType(game) && game?.min_age}
|
||||
<p>Min Age: {game.min_age}</p>
|
||||
{/if}
|
||||
{#if detailed}
|
||||
{#if detailed && isGameType(game) && game?.description}
|
||||
<div class="description">{@html game.description}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
// const responseData = await response.json();
|
||||
// console.log('responseData', responseData);
|
||||
// gameStore.add(responseData?.game);
|
||||
const game = gameStore.add(mapSavedGameToGame(randomGame));
|
||||
gameStore.add(mapSavedGameToGame(randomGame));
|
||||
boredState.update((n) => ({ ...n, loading: false }));
|
||||
} else {
|
||||
toast.send('Error!', { duration: 3000, type: ToastType.ERROR, dismissible: true });
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
export let form: ActionData;
|
||||
console.log('advanced search form', form);
|
||||
let submitting = $boredState?.loading;
|
||||
let minAge = 1;
|
||||
let minPlayers = 1;
|
||||
let maxPlayers = 1;
|
||||
let exactMinPlayers = false;
|
||||
let exactMaxPlayers = false;
|
||||
let minAge = form?.minAge || 1;
|
||||
let minPlayers = form?.minPlayers || 1;
|
||||
let maxPlayers = form?.maxPlayers || 1;
|
||||
let exactMinPlayers = form?.exactMinPlayers || false;
|
||||
let exactMaxPlayers = form?.exactMaxPlayers || false;
|
||||
</script>
|
||||
|
||||
<fieldset class="advanced-search" aria-busy={submitting} disabled={submitting}>
|
||||
|
|
|
|||
|
|
@ -39,9 +39,8 @@ export type SavedGameType = {
|
|||
id: string;
|
||||
name: string;
|
||||
thumb_url: string;
|
||||
min_players: number;
|
||||
max_players: number;
|
||||
playtime: string
|
||||
players: string;
|
||||
playtime: string;
|
||||
}
|
||||
|
||||
export type GameType = {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,46 @@
|
|||
import type { GameType, SavedGameType } from '$lib/types';
|
||||
|
||||
export function convertToSavedGame(game: GameType | SavedGameType): SavedGameType {
|
||||
return {
|
||||
id: game.id,
|
||||
name: game.name,
|
||||
thumb_url: game.thumb_url,
|
||||
players: game.players,
|
||||
playtime: game.playtime
|
||||
};
|
||||
}
|
||||
|
||||
export function mapSavedGameToGame(game: SavedGameType): GameType {
|
||||
const { id, name, thumb_url, min_players, max_players, playtime } = game;
|
||||
const { id, name, thumb_url, players, playtime } = game;
|
||||
|
||||
return {
|
||||
id,
|
||||
handle: '',
|
||||
name,
|
||||
url: '',
|
||||
edit_url: '',
|
||||
thumb_url,
|
||||
min_players,
|
||||
max_players,
|
||||
image_url: '',
|
||||
price: 0,
|
||||
price_ca: 0,
|
||||
price_uk: 0,
|
||||
price_au: 0,
|
||||
msrp: 0,
|
||||
year_published: new Date().getFullYear(),
|
||||
min_players: 0,
|
||||
max_players: 0,
|
||||
min_playtime: 0,
|
||||
max_playtime: 0,
|
||||
min_age: 0,
|
||||
description: '',
|
||||
description_preview: '',
|
||||
players,
|
||||
playtime
|
||||
};
|
||||
}
|
||||
|
||||
export function mapAPIGameToBoredGame(game): GameType {
|
||||
// TODO: TYpe API response
|
||||
export function mapAPIGameToBoredGame(game: any): GameType {
|
||||
const {
|
||||
id,
|
||||
handle,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,7 @@
|
|||
import { collectionStore } from '$lib/stores/collectionStore';
|
||||
import { toast } from '$lib/components/toast/toast';
|
||||
import { ToastType, type GameType, type SavedGameType } from '$lib/types';
|
||||
|
||||
function convertToSavedGame(game: GameType | SavedGameType): SavedGameType {
|
||||
return {
|
||||
id: game.id,
|
||||
name: game.name,
|
||||
thumb_url: game.thumb_url,
|
||||
};
|
||||
}
|
||||
import { convertToSavedGame } from './gameMapper';
|
||||
|
||||
export function addToCollection(game: GameType | SavedGameType) {
|
||||
collectionStore.add(convertToSavedGame(game));
|
||||
|
|
|
|||
|
|
@ -1,14 +1,7 @@
|
|||
import { wishlistStore } from '$lib/stores/wishlistStore';
|
||||
import { toast } from '$lib/components/toast/toast';
|
||||
import { ToastType, type GameType, type SavedGameType } from '$lib/types';
|
||||
|
||||
function convertToSavedGame(game: GameType | SavedGameType): SavedGameType {
|
||||
return {
|
||||
id: game.id,
|
||||
name: game.name,
|
||||
thumb_url: game.thumb_url,
|
||||
};
|
||||
}
|
||||
import { convertToSavedGame } from './gameMapper';
|
||||
|
||||
export function addToWishlist(game: GameType | SavedGameType) {
|
||||
wishlistStore.add(convertToSavedGame(game));
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
import Random from '$lib/components/random/index.svelte';
|
||||
import Pagination from '$lib/components/pagination/index.svelte';
|
||||
import RemoveWishlistDialog from '$root/lib/components/dialog/RemoveWishlistDialog.svelte';
|
||||
import SkeletonPlaceholder from '$root/lib/components/SkeletonPlaceholder.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
export let form: ActionData;
|
||||
|
|
@ -130,6 +131,7 @@
|
|||
</section>
|
||||
</div>
|
||||
|
||||
<SkeletonPlaceholder style="height: 12rem; width: 12rem; border-radius: 10px;" />
|
||||
{#if $gameStore?.length > 0}
|
||||
<div class="games">
|
||||
<h1>Games Found:</h1>
|
||||
|
|
@ -138,7 +140,6 @@
|
|||
<Game
|
||||
on:handleRemoveWishlist={handleRemoveWishlist}
|
||||
on:handleRemoveCollection={handleRemoveCollection}
|
||||
minimal
|
||||
{game}
|
||||
/>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
} from '@rgossiaux/svelte-heroicons/outline';
|
||||
import type { GameType, SavedGameType } from '$lib/types';
|
||||
import { collectionStore } from '$lib/stores/collectionStore';
|
||||
import { wishlistStore } from '$lib/stores/wishlistStore';
|
||||
import Button from '$lib/components/button/index.svelte';
|
||||
import RemoveCollectionDialog from '$root/lib/components/dialog/RemoveCollectionDialog.svelte';
|
||||
import { addToCollection } from '$lib/util/manipulateCollection';
|
||||
|
|
@ -16,30 +17,34 @@
|
|||
import { boredState } from '$root/lib/stores/boredState';
|
||||
import { browser } from '$app/environment';
|
||||
import LinkWithIcon from '$root/lib/components/LinkWithIcon.svelte';
|
||||
import { addToWishlist } from '$root/lib/util/manipulateWishlist';
|
||||
import RemoveWishlistDialog from '$root/lib/components/dialog/RemoveWishlistDialog.svelte';
|
||||
|
||||
$: existsInCollection = $collectionStore.find((item: SavedGameType) => item.id === game.id);
|
||||
$: existsInWishlist = $wishlistStore.find((item: SavedGameType) => item.id === game.id);
|
||||
|
||||
export let data: PageData;
|
||||
export let game: GameType = data?.game;
|
||||
console.log('page game', game);
|
||||
let seeMore: boolean = false;
|
||||
console.log(game?.description?.indexOf('</p>'));
|
||||
let firstParagraphEnd = 0;
|
||||
if (game?.description?.indexOf('</p>') > 0) {
|
||||
firstParagraphEnd = game?.description?.indexOf('</p>') + 4;
|
||||
} else if (game?.description?.indexOf('</ p>') > 0) {
|
||||
firstParagraphEnd = game?.description?.indexOf('</ p>') + 5;
|
||||
}
|
||||
console.log('firstParagraphEnd', firstParagraphEnd);
|
||||
|
||||
function removeGame() {
|
||||
function remoceFromCollection() {
|
||||
boredState.update((n) => ({
|
||||
...n,
|
||||
dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: game }
|
||||
}));
|
||||
if (browser) {
|
||||
localStorage.collection = JSON.stringify($collectionStore);
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromWishList() {
|
||||
boredState.update((n) => ({
|
||||
...n,
|
||||
dialog: { isOpen: true, content: RemoveWishlistDialog, additionalData: game }
|
||||
}));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -55,7 +60,7 @@
|
|||
<img src={game.image_url} alt={`Image of ${game.name}`} />
|
||||
</a>
|
||||
</div>
|
||||
<div style="display: grid; place-items: center;">
|
||||
<div style="display: grid; place-items: center; gap: 2rem;">
|
||||
<div class="details">
|
||||
<p>Year: {game?.year_published}</p>
|
||||
<p>Players: {game.players}</p>
|
||||
|
|
@ -70,7 +75,7 @@
|
|||
</div>
|
||||
<div>
|
||||
{#if existsInCollection}
|
||||
<Button size="md" kind="danger" icon on:click={() => removeGame()}>
|
||||
<Button size="md" kind="danger" icon on:click={() => remoceFromCollection()}>
|
||||
Remove from collection <MinusCircleIcon width="24" height="24" />
|
||||
</Button>
|
||||
{:else}
|
||||
|
|
@ -89,6 +94,27 @@
|
|||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
{#if existsInWishlist}
|
||||
<Button size="md" kind="danger" icon on:click={() => removeFromWishList()}>
|
||||
Remove from wishlist <MinusCircleIcon width="24" height="24" />
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
size="md"
|
||||
kind="primary"
|
||||
icon
|
||||
on:click={() => {
|
||||
addToWishlist(game);
|
||||
if (browser) {
|
||||
localStorage.wishlist = JSON.stringify($wishlistStore);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Add to wishlist <PlusCircleIcon width="24" height="24" />
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{#if firstParagraphEnd > 0}
|
||||
|
|
|
|||
Loading…
Reference in a new issue