Skip to content

Commit

Permalink
Merge pull request #79 from SkyCryptWebsite/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
DarthGigi authored Jan 8, 2025
2 parents bde5e59 + 41f988f commit 5ef1d8e
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 199 deletions.
18 changes: 0 additions & 18 deletions a.js

This file was deleted.

88 changes: 85 additions & 3 deletions src/lib/components/Item.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import { getRarityClass, removeFormatting, renderLore } from "$lib/shared/helper";
import { cn, flyAndScale } from "$lib/shared/utils";
import type { ProcessedSkyBlockItem, ProcessedSkyblockPet } from "$lib/types/global";
import { Avatar, Button, Tooltip } from "bits-ui";
import { Avatar, Button, Dialog, Tooltip } from "bits-ui";
import Image from "lucide-svelte/icons/image";
import { getContext } from "svelte";
import { writable } from "svelte/store";
import { fade } from "svelte/transition";
import { Drawer } from "vaul-svelte";
type Props = {
Expand All @@ -31,8 +33,18 @@
const shine = $derived(enchanted || skyblockItem.shiny);
const packData = $derived(packConfigs.find((pack) => pack.id === skyblockItem.texture_pack));
const showNumbers = $derived(showCount && (skyblockItem.Count ?? 0) > 1);
const showDialog = writable<boolean>(false);
const showDialogDelayed = writable<boolean>(false);
const isHover = getContext<IsHover>("isHover");
showDialog.subscribe((value) => {
setTimeout(() => showDialogDelayed.set(value), 0);
});
showDialogDelayed.subscribe((value) => {
if (!value) setTimeout(() => showDialog.set(value), 300);
});
</script>

{#snippet item()}
Expand All @@ -56,7 +68,7 @@

{#snippet tooltip()}
<Tooltip.Root group="armor" openDelay={0} closeDelay={0}>
<Tooltip.Trigger class={cn(`nice-colors-dark relative flex aspect-square items-center justify-center overflow-clip rounded-lg`, isInventory ? "p-0" : `p-2 ${bgColor}`, { shine: enchanted })}>
<Tooltip.Trigger class={cn(`nice-colors-dark relative flex aspect-square items-center justify-center overflow-clip rounded-lg`, isInventory ? "p-0" : `p-2 ${bgColor}`, { shine: enchanted })} onclick={() => showDialog.set(!$showDialog)}>
<Avatar.Root>
<Avatar.Image loading="lazy" src={$page.url.origin + piece.texture_path} alt={piece.display_name} class="data-[enchanted=true]:enchanted h-auto w-14 select-none" data-enchanted={enchanted} />
<Avatar.Fallback>
Expand All @@ -72,7 +84,7 @@
</div>
{/if}
</Tooltip.Trigger>
<Tooltip.Content class="z-50 flex max-h-[calc(96%-3rem)] w-max min-w-96 max-w-[calc(100vw-2.5rem)] select-text flex-col overflow-hidden rounded-lg bg-background-lore font-icomoon" transition={flyAndScale} transitionConfig={{ x: -8, duration: 150 }} sideOffset={8} side="right" align="center">
<Tooltip.Content class="pointer-events-none z-50 flex max-h-[calc(96%-3rem)] w-max min-w-96 max-w-[calc(100vw-2.5rem)] select-text flex-col overflow-hidden rounded-lg bg-background-lore font-icomoon" transition={flyAndScale} transitionConfig={{ x: -8, duration: 150 }} sideOffset={8} side="right" align="center">
<div class={cn(`nice-colors-dark flex flex-nowrap items-center justify-center gap-4 p-5`, bgColor)}>
<Avatar.Root>
<Avatar.Image loading="lazy" src={$page.url.origin + piece.texture_path} alt={piece.display_name} class="data-[enchanted=true]:enchanted h-auto w-8 flex-none overflow-hidden" data-enchanted={enchanted} />
Expand Down Expand Up @@ -137,6 +149,76 @@
</Tooltip.Root>
{/snippet}

{#if isHover.current && $showDialog}
<Dialog.Root bind:open={$showDialogDelayed}>
<Dialog.Portal>
<Dialog.Overlay transition={fade} transitionConfig={{ duration: 150 }} class="fixed inset-0 z-40 bg-black/80" />
<Dialog.Content class="fixed left-[50%] top-[50%] z-50 flex max-h-[calc(96%-3rem)] w-max min-w-96 max-w-[calc(100vw-2.5rem)] -translate-x-1/2 -translate-y-1/2 select-text flex-col overflow-hidden rounded-lg bg-background-lore font-icomoon" transition={flyAndScale} transitionConfig={{ x: -8, duration: 150 }}>
<div class={cn(`nice-colors-dark flex flex-nowrap items-center justify-center gap-4 p-5`, bgColor)}>
<Avatar.Root>
<Avatar.Image loading="lazy" src={$page.url.origin + piece.texture_path} alt={piece.display_name} class="data-[enchanted=true]:enchanted h-auto w-8 flex-none overflow-hidden" data-enchanted={enchanted} />
<Avatar.Fallback>
<Image class="size-8" />
</Avatar.Fallback>
</Avatar.Root>

<p class="relative flex-1 text-center text-lg font-semibold uppercase data-[multicolor=true]:rounded-full data-[multicolor=true]:bg-background-lore data-[multicolor=true]:px-2 data-[multicolor=true]:py-1 data-[multicolor=false]:text-text" data-multicolor={isMulticolor}>
{@html isMulticolor ? itemNameHtml : removeFormatting(itemNameHtml)}
</p>
</div>
<div class="mx-auto w-full max-w-md overflow-auto p-6 font-semibold leading-snug">
{#each skyblockItem.lore as lore}
{@html renderLore(lore)}
{/each}
{#if skyblockItem.containsItems && skyblockItem.containsItems.length > 0}
<div class="mt-4 border-t border-text/10 pt-4">
<div class="grid grid-cols-9 gap-1">
{#each skyblockItem.containsItems.slice(0, Math.min(skyblockItem.containsItems.length, 54)) as containedItem}
{#if containedItem.texture_path}
<div class="flex aspect-square items-center justify-center rounded bg-text/[0.04]">
<ContainedItem piece={containedItem} />
</div>
{:else}
<div class="aspect-square rounded bg-text/[0.04]"></div>
{/if}
{/each}
</div>
</div>
{/if}

{#if packData}
<div class="pt-4">
<Button.Root href={packData.link} target="_blank">
<div class="flex items-center justify-between gap-4 rounded-[0.625rem] bg-text/[0.05] p-2 transition-colors hover:bg-text/[0.08]">
<div class="flex items-center gap-2">
<Avatar.Root class="shrink-0 select-none">
<Avatar.Image src="/resourcepacks/{packData.folder}/pack.png" alt={packData.name} class="pointer-events-none aspect-square size-10 h-full select-none rounded-lg" />
<Avatar.Fallback class="flex size-10 items-center justify-center rounded-lg bg-icon/90 text-center font-semibold uppercase">
{packData.name.slice(0, 2)}
</Avatar.Fallback>
</Avatar.Root>
<div class="flex flex-col">
<div class="font-semibold text-link">
<span class="underline">
{packData.name}
</span>
<span class="text-sm text-text/60">{packData.version}</span>
</div>
<div class="text-sm text-text/60">
by <span class="text-text/80">{packData.author}</span>
</div>
</div>
</div>
</div>
</Button.Root>
</div>
{/if}
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
{/if}

{#snippet drawer()}
<Drawer.Root shouldScaleBackground={true} setBackgroundColorOnScale={false}>
<Drawer.Trigger class="nice-colors-dark">
Expand Down
6 changes: 4 additions & 2 deletions src/lib/components/Navbar.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script lang="ts">
import { replaceState } from "$app/navigation";
import { page } from "$app/state";
import { Button, ScrollArea } from "bits-ui";
import { onMount } from "svelte";
Expand Down Expand Up @@ -91,7 +93,7 @@
if (isIntersecting) {
let newHash;
newHash = "#" + element.id;
history.replaceState({}, document.title, newHash);
replaceState(newHash, page.state);
for (const link of navBarLinks) {
if (link.hash === newHash) {
activeSection = link.hash.slice(1);
Expand Down Expand Up @@ -124,7 +126,7 @@
});
</script>

<ScrollArea.Root type="scroll" class="navbar group sticky top-0 z-20 sm:top-[calc(3rem+env(safe-area-inset-top,0))]" data-pinned={pinned}>
<ScrollArea.Root type="scroll" class="navbar group sticky top-[calc(3rem+env(safe-area-inset-top,0))] z-20" data-pinned={pinned}>
<ScrollArea.Viewport>
<ScrollArea.Content class="!flex flex-nowrap items-center justify-center gap-2 whitespace-nowrap pb-2 font-semibold text-text/80">
<div class="absolute bottom-[0.4375rem] z-[1] h-[2px] w-[calc(100%+0.5rem)] bg-icon"></div>
Expand Down
86 changes: 47 additions & 39 deletions src/lib/components/Skin3D.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,69 @@
import { getProfileCtx } from "$ctx/profile.svelte";
import { cn } from "$lib/shared/utils";
import * as skinview3d from "skinview3d";
import { onMount } from "svelte";
import { onDestroy } from "svelte";
const { profile } = getProfileCtx();
const ctx = getProfileCtx();
const uuid = $derived(ctx.profile.uuid);
let { class: className }: { class: string | undefined } = $props();
let viewer: skinview3d.SkinViewer;
let minecraftAvatar: HTMLCanvasElement;
let viewer = $state<skinview3d.SkinViewer>();
let minecraftAvatar = $state<HTMLCanvasElement>();
let canvasIsLoading = $state<boolean>(true);
let loadedUuid = "";
/*
function onResize() {
if (minecraftAvatar && viewer) {
if (minecraftAvatar.offsetWidth / minecraftAvatar.offsetHeight < 0.6) {
viewer.setSize(minecraftAvatar.offsetWidth, minecraftAvatar.offsetWidth * 2);
} else {
viewer.setSize(minecraftAvatar.offsetHeight / 2, minecraftAvatar.offsetHeight);
}
const FIXED_WIDTH = 500;
const FIXED_HEIGHT = 1000;
function updateViewerSize() {
if (minecraftAvatar && minecraftAvatar.parentElement && viewer) {
viewer.setSize(minecraftAvatar.parentElement.clientWidth, window.innerHeight);
}
}
*/
onMount(() => {
const createSkinviewer = async () => {
const minecraftAvatarContainerDimensions = minecraftAvatar.getBoundingClientRect();
const cape = await fetch(`https://crafatar.com/capes/${profile.uuid}`, {
method: "HEAD"
}).catch(() => ({ ok: false }));
const updateSkinViewer = async (uuid: string) => {
if (loadedUuid === uuid) return;
canvasIsLoading = true;
const cape = await fetch(`https://crafatar.com/capes/${uuid}`, {
method: "HEAD"
}).catch(() => ({ ok: false }));
if (!viewer) {
viewer = new skinview3d.SkinViewer({
canvas: minecraftAvatar,
width: minecraftAvatarContainerDimensions.width,
height: minecraftAvatarContainerDimensions.height,
skin: `https://crafatar.com/skins/${profile.uuid}`,
cape: cape.ok ? `https://crafatar.com/capes/${profile.uuid}` : undefined,
animation: new skinview3d.IdleAnimation()
width: FIXED_WIDTH,
height: FIXED_HEIGHT,
animation: new skinview3d.IdleAnimation(),
preserveDrawingBuffer: true
});
}
viewer.camera.position.set(-18, -3, 78);
viewer.controls.enableZoom = false;
viewer.controls.enablePan = true;
viewer.controls.enableRotate = true;
viewer.canvas.removeAttribute("tabindex");
await viewer.loadSkin(`https://crafatar.com/skins/${uuid}`);
if (cape.ok) {
await viewer.loadCape(`https://crafatar.com/capes/${uuid}`);
} else {
viewer.resetCape();
}
canvasIsLoading = false;
};
viewer.camera.position.set(-18, -3, 78);
viewer.controls.enableZoom = false;
viewer.controls.enablePan = true;
viewer.controls.enableRotate = true;
viewer.canvas.removeAttribute("tabindex");
createSkinviewer();
canvasIsLoading = false;
};
// window.addEventListener("resize", onResize);
$effect(() => {
updateSkinViewer(uuid);
});
return () => {
canvasIsLoading = true;
viewer.dispose();
// window.removeEventListener("resize", onResize);
};
onDestroy(() => {
viewer?.dispose();
});
</script>

<svelte:window onresize={updateViewerSize} />

<canvas bind:this={minecraftAvatar} class={cn("size-full transform-gpu overflow-hidden transition-opacity duration-[3s]", className)} class:opacity-100={!canvasIsLoading} class:opacity-0={canvasIsLoading}></canvas>
51 changes: 13 additions & 38 deletions src/lib/components/Wardrobe.svelte
Original file line number Diff line number Diff line change
@@ -1,46 +1,21 @@
<script lang="ts">
import Item from "$lib/components/Item.svelte";
import type { IsHover } from "$lib/hooks/is-hover.svelte";
import type { ProcessedSkyBlockItem } from "$types/stats";
import { Avatar, Collapsible } from "bits-ui";
import { getContext } from "svelte";
import { writable } from "svelte/store";
import { slide } from "svelte/transition";
import { Avatar } from "bits-ui";
export let wardrobeItems: ProcessedSkyBlockItem[];
let { wardrobeItems }: { wardrobeItems: ProcessedSkyBlockItem[] } = $props();
const highestItem = wardrobeItems.find((piece) => piece && piece.display_name);
const pieces = ["helmet", "chestplate", "leggings", "boots"];
const isHover = getContext<IsHover>("isHover");
const expanded = writable<boolean>(false);
if (!isHover.current) {
expanded.set(true);
}
</script>

<Collapsible.Root bind:open={$expanded} disabled={!isHover.current}>
<Collapsible.Trigger asChild let:builder>
<div use:builder.action {...builder} class="mt-2 flex flex-col gap-2">
{#if !$expanded}
{#if highestItem}
<Item piece={highestItem} />
{/if}
{:else}
<Collapsible.Content transition={slide} class="flex flex-col gap-2">
{#each wardrobeItems as piece, index}
{#if piece && piece.display_name}
<Item {piece} />
{:else}
<Avatar.Root class="rounded-lg bg-background-lore p-2">
<Avatar.Image class="size-14" loading="eager" src={`/img/textures/item/empty_armor_slot_${pieces[index]}.png`} />
</Avatar.Root>
{/if}
{/each}
</Collapsible.Content>
{/if}
</div>
</Collapsible.Trigger>
</Collapsible.Root>
<div class="mt-2 flex flex-col gap-2">
{#each wardrobeItems as piece, index}
{#if piece && piece.display_name}
<Item {piece} />
{:else}
<Avatar.Root class="rounded-lg bg-background-lore p-2">
<Avatar.Image class="size-14" loading="eager" src={`/img/textures/item/empty_armor_slot_${pieces[index]}.png`} />
</Avatar.Root>
{/if}
{/each}
</div>
30 changes: 18 additions & 12 deletions src/lib/components/header/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@
import HeaderInfo from "$lib/components/header/Info.svelte";
import Settings from "$lib/components/header/Settings.svelte";
import { Avatar, Button } from "bits-ui";
import { Control, Field, FieldErrors } from "formsnap";
import { Control, Field } from "formsnap";
import CircleAlert from "lucide-svelte/icons/circle-alert";
import LoaderCircle from "lucide-svelte/icons/loader-circle";
import Search from "lucide-svelte/icons/search";
import { superForm } from "sveltekit-superforms";
import { zodClient } from "sveltekit-superforms/adapters";
import { schema } from "../../../routes/schema";
const form = superForm(page.data.searchForm, {
validators: zodClient(schema)
validators: zodClient(schema),
validationMethod: "oninput"
});
const { form: formData, enhance, errors, tainted } = form;
const { form: formData, enhance, errors, tainted, submitting, isTainted } = form;
</script>

<header class="fixed bottom-0 left-0 z-30 h-12 w-full overflow-clip bg-header px-2.5 pl-[max(0.625rem,env(safe-area-inset-left))] pr-[max(0.625rem,env(safe-area-inset-right))] pt-[env(safe-area-inset-top,0)] leading-[3rem] @container sm:top-0">
<header class="fixed left-0 top-0 z-30 h-12 w-full overflow-clip bg-header px-2.5 pb-[env(safe-area-inset-bottom,0)] pl-[max(0.625rem,env(safe-area-inset-left))] pr-[max(0.625rem,env(safe-area-inset-right))] pt-[env(safe-area-inset-top,0)] leading-[3rem] @container">
<div class="flex h-full w-full justify-center @md:justify-between">
<div class="flex gap-2">
<Button.Root href="/" class="flex items-center justify-center gap-2 font-bold">
Expand All @@ -30,20 +33,23 @@
</div>

{#if page.url.pathname.startsWith("/stats")}
<div class="mx-auto my-1.5 hidden w-full max-w-lg px-4 @[38rem]:block">
<form method="POST" action="/search" use:enhance class="relative flex h-full w-full items-center justify-start overflow-clip rounded-[1.125rem] bg-background/20">
<div class="mx-auto my-1.5 w-full max-w-lg px-4 @[38rem]:block">
<form method="POST" action="/search" use:enhance class="relative flex h-full w-4/5 items-center justify-start overflow-clip rounded-[1.125rem] bg-background/20 @[38rem]:w-full">
<Field {form} name="query">
<Control>
{#snippet children({ props })}
<input {...props} type="search" required placeholder="Enter username" class="flex-shrink flex-grow bg-transparent px-4 pr-14 font-semibold text-text transition-colors duration-300 placeholder:text-text/80 hover:bg-background/20 focus-visible:bg-background/20 focus-visible:outline-none" bind:value={$formData.query} />
<input {...props} type="search" required placeholder="Enter username" class="peer h-full w-full flex-shrink rounded-r-3xl bg-transparent pl-2 pr-0 text-xs font-semibold text-text outline-none transition-[colors_border-radius_opacity] duration-300 placeholder:text-text/80 hover:rounded-r-none hover:bg-background/20 focus-visible:rounded-r-none focus-visible:bg-background/20 focus-visible:outline-none focus-visible:ring-transparent @[38rem]:flex-grow @[38rem]:pl-4 @[38rem]:text-base" bind:value={$formData.query} />
{/snippet}
</Control>
{#if $formData.query.length > 0 && $tainted?.query && $errors.query}
<FieldErrors class="text-center text-sm font-semibold text-text/80" />
{/if}
</Field>
<Button.Root type="submit" class="absolute right-0 z-10 flex h-full items-center justify-center rounded-[1.125rem] bg-background/15 px-4">
<Search class="size-6 text-text" />
<Button.Root type="submit" class="flex aspect-square h-full items-center justify-center rounded-full bg-background/15 transition-all duration-300 peer-hover:rounded-l-none peer-hover:bg-background/20 peer-focus-visible:rounded-l-none peer-focus-visible:bg-background/20 @[38rem]:px-4">
{#if $formData.query.length > 0 && isTainted($tainted?.query) && $errors.query !== undefined}
<CircleAlert class="size-4 text-text @[38rem]:size-6" />
{:else if $submitting}
<LoaderCircle class="size-4 animate-spin text-text @[38rem]:size-6" />
{:else}
<Search class="size-4 text-text @[38rem]:size-6" />
{/if}
</Button.Root>
</form>
</div>
Expand Down
Loading

0 comments on commit 5ef1d8e

Please sign in to comment.