aboutsummaryrefslogtreecommitdiff
path: root/src/routes
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/player/[player]/[profile].svelte73
-rw-r--r--src/routes/player/[player]/index.svelte39
-rw-r--r--src/routes/todos/_api.ts22
-rw-r--r--src/routes/todos/index.svelte186
-rw-r--r--src/routes/todos/index.ts52
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')}`);
-};