New rewrite with svelte and pocketbase
This commit is contained in:
parent
72bfc2ed89
commit
160617af60
95 changed files with 4402 additions and 0 deletions
17
frontend/src/lib/components/Card.svelte
Normal file
17
frontend/src/lib/components/Card.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script>
|
||||
let { name = "", url = "#", sanitizedName = "", smallImage = "", normalImage="", price = 0, cardmarketUri = "", numberOfDecks = 0, numberOfPossibleDecks = undefined, synergy = undefined } = $props()
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col w-full gap-0">
|
||||
<a class="w-full" href={url}><img src={normalImage} alt={"Scan de carte pour " + name} class="w-full rounded-md aspect-[488/680]" loading="lazy"/></a>
|
||||
<a href={cardmarketUri} target="_blank" class="text-xs w-full text-center mt-2">{price}€</a>
|
||||
<span class="w-full text-center text-xs">{name}</span>
|
||||
{#if numberOfPossibleDecks == undefined}
|
||||
<span class="w-full text-center">{numberOfDecks} Decks</span>
|
||||
{:else}
|
||||
<span class="w-full text-center">{numberOfDecks} Decks sur {numberOfPossibleDecks} ({numberOfPossibleDecks != 0 ? Math.round(100 * (numberOfDecks / numberOfPossibleDecks)) : 0}%)</span>
|
||||
{/if}
|
||||
{#if synergy != undefined}
|
||||
<span class="w-full text-center">Synergy {synergy > 0 ? "+" + Math.round(100*synergy).toString() : Math.round(100*synergy).toString()}%</span>
|
||||
{/if}
|
||||
</div>
|
7
frontend/src/lib/components/CardGrid.svelte
Normal file
7
frontend/src/lib/components/CardGrid.svelte
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 pl-4 pr-4 md:grid-cols-3 lg:grid-cols-5 gap-4 w-full max-w-6xl">
|
||||
{@render children()}
|
||||
</div>
|
38
frontend/src/lib/components/CardSearch.svelte
Normal file
38
frontend/src/lib/components/CardSearch.svelte
Normal file
|
@ -0,0 +1,38 @@
|
|||
<script>
|
||||
import Input from "./base/Input.svelte";
|
||||
|
||||
let { dialog = $bindable() } = $props()
|
||||
|
||||
let searchQuery = $state("")
|
||||
let searchResult = $state([])
|
||||
|
||||
async function search() {
|
||||
searchResult = []
|
||||
if(searchQuery.length > 3) {
|
||||
fetch("/api/search?q=" + searchQuery)
|
||||
.then( response => response.json() )
|
||||
.then( data => { searchResult = data; console.log(data) } )
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog class="max-w-96 w-full backdrop:backdrop-blur-sm backdrop:bg-black/30 p-4 rounded-md"
|
||||
style="margin: auto; width: calc(100% - 20px);"
|
||||
bind:this={dialog}
|
||||
onclick={(e) => { if (e.target === dialog) dialog.close(); }}>
|
||||
<div>
|
||||
<Input type="text" placeholder="Rechercher des cartes" bind:value={searchQuery} oninput={search} focus/>
|
||||
<div class="mt-4 flex flex-col gap-2 h-64 overflow-y-scroll items-center">
|
||||
{#if searchResult.length == 0}
|
||||
<span class="mt-8 text-xs">Pas de résultat...</span>
|
||||
{:else}
|
||||
{#each searchResult as result}
|
||||
<a class="flex flex-col gap-0 w-full" href={result.Url}>
|
||||
<span>{result.Name}</span>
|
||||
<span class="text-xs">{result.SetName}</span>
|
||||
</a>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
3
frontend/src/lib/components/Footer.svelte
Normal file
3
frontend/src/lib/components/Footer.svelte
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="w-full flex flex-row justify-center text-xs text-center p-4">
|
||||
<span>Brawlset is unofficial Fan Content permitted under the Fan Content Policy. Not approved/endorsed by Wizards. Portions of the materials used are property of Wizards of the Coast. ©Wizards of the Coast LLC.</span>
|
||||
</div>
|
226
frontend/src/lib/components/Navbar.svelte
Normal file
226
frontend/src/lib/components/Navbar.svelte
Normal file
|
@ -0,0 +1,226 @@
|
|||
<script>
|
||||
import { getContext, onMount } from "svelte";
|
||||
import Blue from "./icons/Blue.svelte";
|
||||
import White from "./icons/White.svelte";
|
||||
import Black from "./icons/Black.svelte";
|
||||
import Colorless from "./icons/Colorless.svelte";
|
||||
import Red from "./icons/Red.svelte";
|
||||
import Green from "./icons/Green.svelte";
|
||||
import IconBase from "./icons/IconBase.svelte";
|
||||
import CardSearch from "./CardSearch.svelte";
|
||||
import SearchIcon from "./icons/SearchIcon.svelte";
|
||||
import DeckIcon from "./icons/DeckIcon.svelte";
|
||||
import MenuIcon from "./icons/MenuIcon.svelte";
|
||||
|
||||
let dialog
|
||||
|
||||
let username = $state("")
|
||||
|
||||
let brawlsets = $state([])
|
||||
let isLoggedIn = $state(false)
|
||||
|
||||
onMount( async () => {
|
||||
const storageData = window.localStorage.getItem("pocketbase_auth")
|
||||
if (storageData != null) {
|
||||
const jsonData = JSON.parse(storageData)
|
||||
username = jsonData.record.name
|
||||
isLoggedIn = true
|
||||
}
|
||||
|
||||
fetch("/json/misc/brawlsets")
|
||||
.then( response => response.json() )
|
||||
.then( data => { brawlsets = data; console.log(data) } )
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<div class="font-inter-tight flex flex-row justify-between w-screen p-4 h-16">
|
||||
<a class="flex flex-row gap-2 items-center" href="/">
|
||||
<img alt="brawlset website logo" src="/assets/logo.png" class="h-8" />
|
||||
<span class="font-beleren text-3xl mt-2 bg-gradient-to-r from-black to-orange-500 bg-clip-text text-transparent">BrawlSet</span>
|
||||
</a>
|
||||
<div class="hidden flex-row gap-4 items-center md:flex">
|
||||
<details class="dropdown w-0 md:w-fit">
|
||||
<summary role="button">
|
||||
<a class="cursor-pointer text-stone-500">Commandants</a>
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Top commandants</li>
|
||||
<li>
|
||||
<div>Mono</div>
|
||||
<ul>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><White/><span>Blanc</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><Blue/><span>Bleu</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><Black/><span>Noir</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><Red/><span>Rouge</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><Green/><span>Vert</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><Colorless/><span>Incolor</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div>2 couleurs</div>
|
||||
<ul>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/></div><span>Azorius</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Black/></div><span>Dimir</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Red/></div><span>Rakdos</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><Green/></div><span>Gruul</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><White/></div><span>Selesnya</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Black/></div><span>Orzhov</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Red/></div><span>Izzet</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Green/></div><span>Golgari</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><White/></div><span>Boros</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><Blue/></div><span>Simic</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div>3 couleurs</div>
|
||||
<ul>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/><Black/></div><span>Esper</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Black/><Red/></div><span>Grixis</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Red/><Green/></div><span>Jund</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><Green/><White/></div><span>Naya</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><White/><Blue/></div><span>Bant</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Black/><Green/></div><span>Abzan</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Red/><White/></div><span>Jeskai</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Green/><Blue/></div><span>Sultai</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><White/><Black/></div><span>Mardu</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><Blue/><Red/></div><span>Temur</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div>4+ couleurs</div>
|
||||
<ul>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/><Black/><Red/></div><span>Yore-Tiller</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Black/><Red/><Green/></div><span>Glint-Eye</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Red/><Green/><White/></div><span>Dune-Brood</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><Green/><White/><Blue/></div><span>Ink-Treader</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><White/><Blue/><Black/></div><span>Witch-Maw</span></a></li>
|
||||
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/><Black/><Red/><Green/></div><span>5 couleurs</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<details class="dropdown">
|
||||
<summary role="button">
|
||||
<a class="cursor-pointer text-stone-500">BSets</a>
|
||||
</summary>
|
||||
<ul>
|
||||
{#each brawlsets.slice(0,5) as brawlset}
|
||||
<li>
|
||||
<a class="flex flex-row gap-2 items-center" href={"/bset/" + brawlset.SanitizedName}>
|
||||
<div class="flex flex-row gap-1">
|
||||
{#each brawlset.IconsSvgUri as icon}
|
||||
<IconBase src={icon} alt="brawlset icon"/>
|
||||
{/each}
|
||||
</div>
|
||||
{brawlset.Name}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
<li><a href="/bset/all">Plus de BSets...</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<a class="cursor-pointer text-stone-500" href="/regles">Règles</a>
|
||||
<a class="cursor-pointer text-stone-500" href="/faq">F.A.Q</a>
|
||||
</div>
|
||||
<div class="flex flex-row gap-4 items-center text-stone-500">
|
||||
<button class="flex flex-row items-center" onclick={() => dialog.showModal()}>
|
||||
<SearchIcon class="visible w-fit md:invisible md:w-0" />
|
||||
<span class="invisible w-0 md:w-fit md:visible">Rechercher</span>
|
||||
</button>
|
||||
{#if isLoggedIn}
|
||||
<a class="cursor-pointer flex flex-row items-center" href="/decks">
|
||||
<DeckIcon class="visible w-fit md:invisible md:w-0" />
|
||||
<span class="invisible w-0 md:w-fit md:visible">Decks</span>
|
||||
</a>
|
||||
<a class="cursor-pointer hidden md:inline" href="/profil">
|
||||
<span class="invisible w-0 md:w-fit md:visible">{username}</span>
|
||||
</a>
|
||||
{:else}
|
||||
<a class="cursor-pointer" href="/connexion">Connexion</a>
|
||||
<a class="cursor-pointer" href="#">Inscription</a>
|
||||
{/if}
|
||||
<details class="dropdown inline md:hidden">
|
||||
<summary role="button">
|
||||
<a class="cursor-pointer"><MenuIcon /></a>
|
||||
</summary>
|
||||
<ul>
|
||||
{#each brawlsets.slice(0,5) as brawlset}
|
||||
<li>
|
||||
<a class="flex flex-row gap-2 items-center" href={"/bset/" + brawlset.SanitizedName}>
|
||||
<div class="flex flex-row gap-1">
|
||||
{#each brawlset.IconsSvgUri as icon}
|
||||
<IconBase src={icon} alt="brawlset icon"/>
|
||||
{/each}
|
||||
</div>
|
||||
{brawlset.Name}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
<li><a href="/bset/all">Plus de BSets...</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardSearch bind:dialog={dialog} />
|
||||
|
||||
<style>
|
||||
/* Close the dropdown with outside clicks */
|
||||
.dropdown > summary::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown[open] > summary::before {
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown summary {
|
||||
list-style: none;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.dropdown ul {
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
z-index: 2;
|
||||
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #D3D3D3;
|
||||
box-shadow: 0px 4px 5px #D3D3D3;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.dropdown li {
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown ul ul {
|
||||
display: none;
|
||||
left: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.dropdown li:hover {
|
||||
background-color: hsl(240 4.8% 95.9%);
|
||||
}
|
||||
|
||||
.dropdown li:hover > ul {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
8
frontend/src/lib/components/base/Button.svelte
Normal file
8
frontend/src/lib/components/base/Button.svelte
Normal file
|
@ -0,0 +1,8 @@
|
|||
<script>
|
||||
|
||||
let { children, ...restProps } = $props()
|
||||
</script>
|
||||
|
||||
<button class="bg-orange-500 cursor-pointer text-white rounded-md p-2" {...restProps}>
|
||||
{@render children()}
|
||||
</button>
|
5
frontend/src/lib/components/base/Input.svelte
Normal file
5
frontend/src/lib/components/base/Input.svelte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
let { type, placeholder = "", name = "", value = $bindable(), ...restProps} = $props()
|
||||
</script>
|
||||
|
||||
<input type={type} placeholder={placeholder} name={name} class="flex h-auto min-h-9 w-full border-l-orange-500 border-l-4 bg-transparent px-3 py-1 text-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" bind:value {...restProps} />
|
5
frontend/src/lib/components/icons/Black.svelte
Normal file
5
frontend/src/lib/components/icons/Black.svelte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import IconBase from "./IconBase.svelte";
|
||||
</script>
|
||||
|
||||
<IconBase src="https://svgs.scryfall.io/card-symbols/B.svg" alt="Black mana icon"/>
|
5
frontend/src/lib/components/icons/Blue.svelte
Normal file
5
frontend/src/lib/components/icons/Blue.svelte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import IconBase from "./IconBase.svelte";
|
||||
</script>
|
||||
|
||||
<IconBase src="https://svgs.scryfall.io/card-symbols/U.svg" alt="Blue mana icon"/>
|
5
frontend/src/lib/components/icons/Colorless.svelte
Normal file
5
frontend/src/lib/components/icons/Colorless.svelte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import IconBase from "./IconBase.svelte";
|
||||
</script>
|
||||
|
||||
<IconBase src="https://svgs.scryfall.io/card-symbols/C.svg" alt="Colorless mana icon"/>
|
6
frontend/src/lib/components/icons/DeckIcon.svelte
Normal file
6
frontend/src/lib/components/icons/DeckIcon.svelte
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...$$props}>
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<rect width="18" height="18" x="3" y="3" rx="2" />
|
||||
<path d="M3 9a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2M3 11h3c.8 0 1.6.3 2.1.9l1.1.9c1.6 1.6 4.1 1.6 5.7 0l1.1-.9c.5-.5 1.3-.9 2.1-.9H21" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 399 B |
5
frontend/src/lib/components/icons/Green.svelte
Normal file
5
frontend/src/lib/components/icons/Green.svelte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import IconBase from "./IconBase.svelte";
|
||||
</script>
|
||||
|
||||
<IconBase src="https://svgs.scryfall.io/card-symbols/G.svg" alt="Green mana icon"/>
|
5
frontend/src/lib/components/icons/IconBase.svelte
Normal file
5
frontend/src/lib/components/icons/IconBase.svelte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
let { src, alt } = $props();
|
||||
</script>
|
||||
|
||||
<img src={src} alt={alt} loading="lazy" class="min-h-4 min-w-4 size-4" />
|
3
frontend/src/lib/components/icons/MenuIcon.svelte
Normal file
3
frontend/src/lib/components/icons/MenuIcon.svelte
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...$$props}>
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 12h16M4 6h16M4 18h16" />
|
||||
</svg>
|
After Width: | Height: | Size: 240 B |
5
frontend/src/lib/components/icons/Red.svelte
Normal file
5
frontend/src/lib/components/icons/Red.svelte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import IconBase from "./IconBase.svelte";
|
||||
</script>
|
||||
|
||||
<IconBase src="https://svgs.scryfall.io/card-symbols/R.svg" alt="Red mana icon"/>
|
6
frontend/src/lib/components/icons/SearchIcon.svelte
Normal file
6
frontend/src/lib/components/icons/SearchIcon.svelte
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...$$props}>
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21l-4.3-4.3" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 279 B |
5
frontend/src/lib/components/icons/White.svelte
Normal file
5
frontend/src/lib/components/icons/White.svelte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import IconBase from "./IconBase.svelte";
|
||||
</script>
|
||||
|
||||
<IconBase src="https://svgs.scryfall.io/card-symbols/W.svg" alt="White mana icon"/>
|
1
frontend/src/lib/index.ts
Normal file
1
frontend/src/lib/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
Loading…
Add table
Add a link
Reference in a new issue