diff options
Diffstat (limited to 'src/routes')
-rw-r--r-- | src/routes/player/[player]/[profile].svelte | 73 | ||||
-rw-r--r-- | src/routes/player/[player]/index.svelte | 39 | ||||
-rw-r--r-- | src/routes/todos/_api.ts | 22 | ||||
-rw-r--r-- | src/routes/todos/index.svelte | 186 | ||||
-rw-r--r-- | src/routes/todos/index.ts | 52 |
5 files changed, 65 insertions, 307 deletions
diff --git a/src/routes/player/[player]/[profile].svelte b/src/routes/player/[player]/[profile].svelte index e28dae7..1fed489 100644 --- a/src/routes/player/[player]/[profile].svelte +++ b/src/routes/player/[player]/[profile].svelte @@ -23,6 +23,7 @@ <script lang="ts"> import Inventories from '$lib/sections/Inventories.svelte' import Username from '$lib/minecraft/Username.svelte' + import StatList from '$lib/sections/StatList.svelte' import Infobox from '$lib/sections/Infobox.svelte' import Skills from '$lib/sections/Skills.svelte' import { generateInfobox } from '$lib/profile' @@ -32,11 +33,14 @@ import Head from '$lib/Head.svelte' import Toc from '$lib/Toc.svelte' - export let data + import type { CleanMemberProfile } from '$lib/APITypes' + import { cleanId } from '$lib/utils' + import Collapsible from '$lib/Collapsible.svelte' + + export let data: CleanMemberProfile export let pack: string const categories = [ - 'skills', 'deaths', 'kills', 'auctions', @@ -59,7 +63,7 @@ <Head title="{data.member.username}'s SkyBlock profile ({data.member.profileName})" - description={generateInfobox(data, { meta: true }).join('\n')} + description={generateInfobox(data).join('\n')} metaTitle={(data.member.rank.name ? `[${data.member.rank.name}] ` : '') + `${data.member.username}\'s SkyBlock profile (${data.member.profileName})`} /> @@ -67,9 +71,10 @@ <main> <h1> - <Username player={data.member} headType="3d" /> - {#if data.customization?.emoji} - <span class="profile-emoji"><Emoji value={data.customization.emoji} /></span> + <!-- this is weird like this so svelte doesn't add whitespace --> + <Username player={data.member} headType="3d" />{#if data.customization?.emoji}<span + class="profile-emoji"><Emoji value={data.customization.emoji} /></span + > {/if} ({data.member.profileName}) </h1> @@ -78,7 +83,7 @@ <Toc {categories} /> - {#if data.member.skills.length > 0} + {#if data.member.skills && data.member.skills.length > 0} <section id="skills" class="profile-skills"> <h2>Skills</h2> <Skills {data} /> @@ -89,38 +94,30 @@ <div> <div id="categories"> - {#if data.member.inventories.armor} + {#if data.member.inventories?.armor} <section id="armor" class:armor-float={data.member.inventories.inventory}> <h2>Armor</h2> <Armor {data} {pack} /> </section> {/if} - {#if data.member.inventories.inventory} + {#if data.member.inventories?.inventory} <section id="inventories"> <h2>Inventories</h2> <Inventories {data} {pack} /> </section> {/if} - - <!-- {%- if data.member.inventories.inventory -%} - <section id="inventories"> - <h2>Inventories</h2> - {%- include 'sections/inventories.njk' -%} - </section> - {%- endif -%} - {%- asyncAll category in categories -%} - {%- set sectionContents -%} - {% with { data: data, category: category } %} - {%- include 'sections/' + category + '.njk' -%} - {% endwith %} - {%- endset -%} - {%- if sectionContents|trim and sectionContents|trim != '<ul></ul>' -%} - <section id="{{ category }}" class="collapsible"> - <h2>{{ category|replace('_', ' ')|title }}</h2> - {{- sectionContents|safe -}} - </section> - {%- endif -%} - {%- endall -%} --> + {#if data.member.stats} + {#each categories as category} + {#if data.member.stats?.find(s => s.category === category)} + <section id={category}> + <Collapsible> + <h2 slot="title">{cleanId(category)}</h2> + <StatList stats={data.member.stats.filter(s => s.category === category)} /> + </Collapsible> + </section> + {/if} + {/each} + {/if} </div> </div> </main> @@ -132,4 +129,22 @@ margin: 1em; margin-top: 1.6em; } + + #armor.armor-float { + float: left; + } + + #armor { + margin-right: 2em; + height: 16em; + } + + #inventories { + display: inline-block; + min-height: 16em; + } + + section { + margin-bottom: 0.5em; + } </style> diff --git a/src/routes/player/[player]/index.svelte b/src/routes/player/[player]/index.svelte index 974f74b..8242887 100644 --- a/src/routes/player/[player]/index.svelte +++ b/src/routes/player/[player]/index.svelte @@ -15,7 +15,7 @@ return { redirect: `/player/${data.player.username}`, status: 302, - } + } as any } return { @@ -27,23 +27,26 @@ </script> <script lang="ts"> + import type { CleanProfile, CleanUser } from '$lib/APITypes' import Username from '$lib/minecraft/Username.svelte' import Header from '$lib/Header.svelte' import Head from '$lib/Head.svelte' - export let data + export let data: CleanUser - let activeProfile = null + let activeProfile: CleanProfile | null = null let activeProfileLastSave: number = 0 - for (const profile of data.profiles) { - for (const member of profile.members) { - if (member.uuid === data.player.uuid && member.last_save > activeProfileLastSave) { - activeProfile = profile - activeProfileLastSave = member.last_save - } + if (data.profiles) + for (const profile of data.profiles) { + if (profile.members) + for (const member of profile.members) { + if (member.uuid === data.player?.uuid && member.last_save > activeProfileLastSave) { + activeProfile = profile + activeProfileLastSave = member.last_save + } + } } - } const isActiveProfileOnline = Date.now() / 1000 - 60 < activeProfileLastSave @@ -55,35 +58,35 @@ {@html bodyStyle} </svelte:head> -<Head title="{data.player.username}'s SkyBlock profiles" /> +<Head title={data.player ? `${data.player.username}'s SkyBlock profiles` : 'Invalid player'} /> <Header /> <main> <h1><Username player={data.player} headType="3d" />'s profiles</h1> <ul class="profile-list"> - {#each data.profiles as profile} + {#each data.profiles ?? [] as profile} <li class="profile-list-item" - class:profile-list-item-active={profile.uuid === activeProfile.uuid} - class:profile-list-item-online={profile.uuid === activeProfile.uuid && + class:profile-list-item-active={profile.uuid === activeProfile?.uuid} + class:profile-list-item-online={profile.uuid === activeProfile?.uuid && isActiveProfileOnline} > <a class="profile-name" - href="/player/{data.player.username}/{profile.name}" + href="/player/{data.player?.username}/{profile.name}" sveltekit:prefetch > {profile.name} </a> <span class="profile-members"> - {#if profile.members.length > 1} - {#each profile.members as player} + {#if (profile.members?.length ?? 0) > 1} + {#each profile.members ?? [] as player} <span class="member"> <Username {player} headType="2d" - hyperlinkToProfile={player.uuid != data.player.uuid} + hyperlinkToProfile={player.uuid != data.player?.uuid} /> </span> {/each} diff --git a/src/routes/todos/_api.ts b/src/routes/todos/_api.ts deleted file mode 100644 index f8bcf73..0000000 --- a/src/routes/todos/_api.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - This module is used by the /todos and /todos/[uid] - endpoints to make calls to api.svelte.dev, which stores todos - for each user. The leading underscore indicates that this is - a private module, _not_ an endpoint — visiting /todos/_api - will net you a 404 response. - - (The data on the todo app will expire periodically; no - guarantees are made. Don't use it to organise your life.) -*/ - -const base = 'https://api.svelte.dev'; - -export async function api(request: Request, resource: string, data?: Record<string, unknown>) { - return fetch(`${base}/${resource}`, { - method: request.method, - headers: { - 'content-type': 'application/json' - }, - body: data && JSON.stringify(data) - }); -} diff --git a/src/routes/todos/index.svelte b/src/routes/todos/index.svelte deleted file mode 100644 index e23c1a1..0000000 --- a/src/routes/todos/index.svelte +++ /dev/null @@ -1,186 +0,0 @@ -<script lang="ts"> - import { enhance } from '$lib/form' - import { scale } from 'svelte/transition' - import { flip } from 'svelte/animate' - - type Todo = { - uid: string - created_at: Date - text: string - done: boolean - pending_delete: boolean - } - - export let todos: Todo[] -</script> - -<svelte:head> - <title>Todos</title> -</svelte:head> - -<div class="todos"> - <h1>Todos</h1> - - <form - class="new" - action="/todos" - method="post" - use:enhance={{ - result: async ({ form }) => { - form.reset() - }, - }} - > - <input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" /> - </form> - - {#each todos as todo (todo.uid)} - <div - class="todo" - class:done={todo.done} - transition:scale|local={{ start: 0.7 }} - animate:flip={{ duration: 200 }} - > - <form - action="/todos?_method=PATCH" - method="post" - use:enhance={{ - pending: ({ data }) => { - todo.done = !!data.get('done') - }, - }} - > - <input type="hidden" name="uid" value={todo.uid} /> - <input type="hidden" name="done" value={todo.done ? '' : 'true'} /> - <button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" /> - </form> - - <form class="text" action="/todos?_method=PATCH" method="post" use:enhance> - <input type="hidden" name="uid" value={todo.uid} /> - <input aria-label="Edit todo" type="text" name="text" value={todo.text} /> - <button class="save" aria-label="Save todo" /> - </form> - - <form - action="/todos?_method=DELETE" - method="post" - use:enhance={{ - pending: () => (todo.pending_delete = true), - }} - > - <input type="hidden" name="uid" value={todo.uid} /> - <button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} /> - </form> - </div> - {/each} -</div> - -<style> - .todos { - width: 100%; - max-width: var(--column-width); - margin: var(--column-margin-top) auto 0 auto; - line-height: 1; - } - - .new { - margin: 0 0 0.5rem 0; - } - - input { - border: 1px solid transparent; - } - - input:focus-visible { - box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1); - border: 1px solid #ff3e00 !important; - outline: none; - } - - .new input { - font-size: 28px; - width: 100%; - padding: 0.5em 1em 0.3em 1em; - box-sizing: border-box; - background: rgba(255, 255, 255, 0.05); - border-radius: 8px; - text-align: center; - } - - .todo { - display: grid; - grid-template-columns: 2rem 1fr 2rem; - grid-gap: 0.5rem; - align-items: center; - margin: 0 0 0.5rem 0; - padding: 0.5rem; - background-color: white; - border-radius: 8px; - filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1)); - transform: translate(-1px, -1px); - transition: filter 0.2s, transform 0.2s; - } - - .done { - transform: none; - opacity: 0.4; - filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.1)); - } - - form.text { - position: relative; - display: flex; - align-items: center; - flex: 1; - } - - .todo input { - flex: 1; - padding: 0.5em 2em 0.5em 0.8em; - border-radius: 3px; - } - - .todo button { - width: 2em; - height: 2em; - border: none; - background-color: transparent; - background-position: 50% 50%; - background-repeat: no-repeat; - } - - button.toggle { - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 50%; - box-sizing: border-box; - background-size: 1em auto; - } - - .done .toggle { - background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - } - - .delete { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); - opacity: 0.2; - } - - .delete:hover, - .delete:focus { - transition: opacity 0.2s; - opacity: 1; - } - - .save { - position: absolute; - right: 0; - opacity: 0; - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A"); - } - - .todo input:focus + .save, - .save:focus { - transition: opacity 0.2s; - opacity: 1; - } -</style> diff --git a/src/routes/todos/index.ts b/src/routes/todos/index.ts deleted file mode 100644 index 129b60a..0000000 --- a/src/routes/todos/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { api } from './_api'; -import type { RequestHandler } from '@sveltejs/kit'; - -export const get: RequestHandler = async ({ request, locals }) => { - // locals.userid comes from src/hooks.js - const response = await api(request, `todos/${locals.userid}`); - - if (response.status === 404) { - // user hasn't created a todo list. - // start with an empty array - return { - body: { - todos: [] - } - }; - } - - if (response.ok) { - return { - body: { - todos: await response.json() - } - }; - } - - return { - status: response.status - }; -}; - -export const post: RequestHandler = async ({ request, locals }) => { - const form = await request.formData(); - - return api(request, `todos/${locals.userid}`, { - text: form.get('text') - }); -}; - -export const patch: RequestHandler = async ({ request, locals }) => { - const form = await request.formData(); - - return api(request, `todos/${locals.userid}/${form.get('uid')}`, { - text: form.has('text') ? form.get('text') : undefined, - done: form.has('done') ? !!form.get('done') : undefined - }); -}; - -export const del: RequestHandler = async ({ request, locals }) => { - const form = await request.formData(); - - return api(request, `todos/${locals.userid}/${form.get('uid')}`); -}; |