Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add search functionality and tab support for inventory items #108

Merged
merged 7 commits into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -214,11 +214,7 @@
}

@utility shine {
@apply overflow-hidden;

&::before {
@apply animate-shine absolute inset-0 z-8 -translate-y-full transform bg-linear-to-t from-[oklch(1_0_0_/_0)] via-[oklch(1_0_0_/_0.50)] to-[oklch(0.77_0.09_243.21_/_0)] content-[''];
}
@apply before:animate-shine overflow-hidden before:absolute before:inset-0 before:z-8 before:bg-linear-to-t before:from-[oklch(1_0_0_/_0)] before:via-[oklch(1_0_0_/_0.50)] before:to-[oklch(0.77_0.09_243.21_/_0)];
}

@utility enchanted {
Expand Down
11 changes: 8 additions & 3 deletions src/lib/components/Item.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@
import { Avatar, Tooltip } from "bits-ui";
import Image from "lucide-svelte/icons/image";
import { getContext } from "svelte";
import { type FadeParams } from "svelte/transition";
import Content from "./item/item-content.svelte";

type Props = {
piece: ProcessedSkyBlockItem | ProcessedSkyblockPet;
isInventory?: boolean;
showCount?: boolean;
showRecombobulated?: boolean;
tab?: {
name: string;
icon: string;
};
inTransitionConfig?: FadeParams;
};

let { piece, isInventory, showCount, showRecombobulated }: Props = $props();
let { piece, isInventory, showCount, showRecombobulated, tab, inTransitionConfig }: Props = $props();

Check failure on line 25 in src/lib/components/Item.svelte

View workflow job for this annotation

GitHub Actions / Check linting (es-lint), formatting (prettier), and svelte checks (svelte-check)

'inTransitionConfig' is assigned a value but never used. Allowed unused vars must match /^_/u

const skyblockItem = $derived(piece as ProcessedSkyBlockItem);
const bgColor = $derived(getRarityClass(piece.rarity ?? ("common".toLowerCase() as string), "bg"));
Expand Down Expand Up @@ -57,7 +62,7 @@
</Tooltip.Trigger>
{#if isHover.current}
<Tooltip.Content class="bg-background-lore font-icomoon z-50 flex max-h-[calc(96%-3rem)] max-w-lg flex-col overflow-hidden rounded-lg select-text" transition={flyAndScale} transitionConfig={{ x: -8, duration: 150 }} sideOffset={8} side="right" align="center">
<Content {piece} {isInventory} {showCount} {showRecombobulated} />
<Content {piece} {tab} />
</Tooltip.Content>
{/if}
</Tooltip.Root>
29 changes: 23 additions & 6 deletions src/lib/components/item/item-content.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

type Props = {
piece: ProcessedSkyBlockItem | ProcessedSkyblockPet;
isInventory?: boolean;
showCount?: boolean;
showRecombobulated?: boolean;
isDrawer?: boolean;
tab?: {
name: string;
icon: string;
};
};

let { piece, isDrawer }: Props = $props();
let { piece, isDrawer, tab }: Props = $props();

const skyblockItem = $derived(piece as ProcessedSkyBlockItem);
const itemName = $derived(skyblockItem.display_name ?? "???");
Expand Down Expand Up @@ -60,8 +60,25 @@
</div>
{/if}

{#if typeof tab === "object" && tab.icon}
<div class="mt-4">
<div class="bg-text/[0.05] hover:bg-text/[0.08] flex items-center justify-between gap-4 rounded-[0.625rem] p-2 transition-colors">
<div class="flex items-center gap-2">
<Avatar.Root class="shrink-0 select-none">
<Avatar.Image loading="lazy" src={tab.icon} alt={tab.name} class="pointer-events-none aspect-square size-10 h-full rounded-lg select-none" />
<Avatar.Fallback class="bg-icon/90 flex size-10 items-center justify-center rounded-lg text-center font-semibold uppercase">
{tab.name.slice(0, 2)}
</Avatar.Fallback>
</Avatar.Root>
<div class="text-link font-semibold">
You can find this item in the <span class="capitalize">{tab.name}</span> tab
</div>
</div>
</div>
</div>
{/if}
{#if packData}
<div class="pt-4">
<div class="mt-4">
<Button.Root href={packData.link} target="_blank">
<div class="bg-text/[0.05] hover:bg-text/[0.08] flex items-center justify-between gap-4 rounded-[0.625rem] p-2 transition-colors">
<div class="flex items-center gap-2">
Expand Down
194 changes: 130 additions & 64 deletions src/lib/sections/stats/Inventory.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@
import { cubicInOut } from "svelte/easing";
import { crossfade, fade } from "svelte/transition";

type Tabs = {
id: string;
icon: string;
items: ProcessedSkyBlockItem[];
gap: number;
};

let { order }: { order: number } = $props();
let openTab = $state<string>("inv");
let searchValue = $state<string>("");

const ctx = getProfileCtx();
const profile = $derived(ctx.profile);
Expand All @@ -26,7 +34,7 @@
const quiver = $derived(profile.items.quiver);
const museum = $derived(profile.items.museum);

const tabs = $derived(
const tabs = $derived<Tabs[]>(
[
{
id: "inv",
Expand Down Expand Up @@ -93,10 +101,36 @@
icon: "/api/head/a6cc486c2be1cb9dfcb2e53dd9a3e9a883bfadb27cb956f1896d602b4067",
items: rift_enderchest,
gap: 45
},
{
id: "search",
icon: "/api/item/EYE_OF_ENDER",
items: [],
gap: 45
}
].filter((tab) => tab.items.length > 0)
].filter((tab) => tab.id === "search" || tab.items.length > 0)
);

const allItems = $derived.by(() => {
return tabs.reduce((acc, tab) => {
acc.push(...tab.items);
return acc;
}, [] as ProcessedSkyBlockItem[]);
});

const searchedItems = $derived.by(() => {
const search = searchValue.trim();
if (!search) return [];
const searchedItem = allItems
.map((item) => {
const tab = tabs.find((t) => t.items.includes(item));
return { item, sourceTab: { name: tab?.id || "", icon: tab?.icon || "" } };
})
.filter((item) => item.item.display_name?.toLowerCase().includes(searchValue.toLowerCase()))
.slice(0, 45);
return searchedItem;
});

const [send, receive] = crossfade({
duration: 300,
easing: cubicInOut
Expand Down Expand Up @@ -137,69 +171,11 @@
{#each tabs as tab}
<Tabs.Content value={tab.id}>
{#if tab.id === "storage" || tab.id === "museum"}
<Tabs.Root value={tab.id}>
<Tabs.List class="grid grid-cols-[repeat(9,minmax(1.875rem,4.875rem))] place-content-center gap-1 pt-5 @md:gap-1.5 @xl:gap-2">
{#each tab.items as item, index}
<Tabs.Trigger let:builder asChild value={item.texture_path ? index.toString() : "undefined"}>
<div use:builder.action {...builder} class="group">
{#if item.texture_path}
<div class="group-data-[state=active]:bg-text/10 group-data-[state=inactive]:bg-text/[0.04] flex aspect-square items-center justify-center rounded-sm">
<Item piece={item} isInventory={true} showRecombobulated={false} />
</div>
{:else}
{@render emptyItem()}
{/if}
</div>
</Tabs.Trigger>
{/each}
</Tabs.List>

{#each tab.items as item, index}
<Tabs.Content value={index.toString()}>
<div class="grid grid-cols-[repeat(9,minmax(1.875rem,4.875rem))] place-content-center gap-1 pt-5 @md:gap-1.5 @xl:gap-2">
{#if item?.containsItems}
{#each item.containsItems as containedItem, index2}
{#if index2 > 0}
{#if index2 % 54 === 0}
{@render gap()}
{/if}
{/if}
<Tabs.Content value={index.toString()}>
{#if containedItem.texture_path}
<div class="bg-text/[0.04] flex aspect-square items-center justify-center rounded-sm">
<Item piece={containedItem} isInventory={true} showRecombobulated={false} showCount={true} />
</div>
{:else}
{@render emptyItem()}
{/if}
</Tabs.Content>
{/each}
{/if}
</div>
</Tabs.Content>
{/each}
</Tabs.Root>
{@render multipleInventorySection(tab)}
{:else if tab.id == "search"}
{@render searchSection()}
{:else}
<div class="grid grid-cols-[repeat(9,minmax(1.875rem,4.875rem))] place-content-center gap-1 pt-5 @md:gap-1.5 @xl:gap-2">
{#each tab.items as item, index}
{#if index > 0}
{#if index % tab.gap === 0}
{@render gap()}
{/if}
{/if}
{#if item.texture_path}
<div class="bg-text/[0.04] flex aspect-square items-center justify-center rounded-sm">
{#if tab.id === "inv"}
<Item piece={{ ...item, rarity: item.rarity ?? "uncommon" } as ProcessedSkyBlockItem} isInventory={true} showRecombobulated={false} showCount={true} />
{:else}
<Item piece={item} isInventory={true} showRecombobulated={false} showCount={true} />
{/if}
</div>
{:else}
{@render emptyItem()}
{/if}
{/each}
</div>
{@render inventorySection(tab)}
{/if}
</Tabs.Content>
{/each}
Expand All @@ -209,10 +185,100 @@
{/if}
</CollapsibleSection>

{#snippet itemSnippet(item: ProcessedSkyBlockItem, index: number, tab?: { name: string; icon: string })}
<Item piece={item} isInventory={true} showRecombobulated={false} showCount={true} inTransitionConfig={{ duration: 300, delay: 5 * (index + 1) }} {tab} />
{/snippet}

{#snippet emptyItem()}
<div class="bg-text/[0.04] aspect-square rounded-sm"></div>
{/snippet}

{#snippet gap()}
<hr class="col-span-full h-4 border-0" />
{/snippet}

{#snippet searchSection()}
<input type="search" placeholder="Search inventory" class="bg-text/10 text-text placeholder:text-text/80 mx-auto mt-4 block w-1/5 rounded-lg px-2 py-2 font-normal focus-visible:outline-none" bind:value={searchValue} />
{#if searchValue !== "" && searchedItems.length === 0}
<p class="mx-auto w-fit leading-6">No items found.</p>
{:else if searchValue !== ""}
<div class="grid grid-cols-[repeat(9,minmax(1.875rem,4.875rem))] place-content-center gap-1 pt-5 @md:gap-1.5 @xl:gap-2">
{#each searchedItems as item, index}
{#if item.item}
<div class="bg-text/[0.04] flex aspect-square items-center justify-center rounded-sm">
{@render itemSnippet(item.item, index, item.sourceTab)}
</div>
{:else}
{@render emptyItem()}
{/if}
{/each}
</div>
{/if}
{/snippet}

{#snippet multipleInventorySection(tab: Tabs)}
<Tabs.Root value={tab.id}>
<Tabs.List class="grid grid-cols-[repeat(9,minmax(1.875rem,4.875rem))] place-content-center gap-1 pt-5 @md:gap-1.5 @xl:gap-2">
{#each tab.items as item, index}
<Tabs.Trigger let:builder asChild value={item.texture_path ? index.toString() : "undefined"}>
<div use:builder.action {...builder} class="group">
{#if item.texture_path}
<div class="group-data-[state=active]:bg-text/10 group-data-[state=inactive]:bg-text/[0.04] flex aspect-square items-center justify-center rounded-sm">
{@render itemSnippet(item, index)}
</div>
{:else}
{@render emptyItem()}
{/if}
</div>
</Tabs.Trigger>
{/each}
</Tabs.List>
{#each tab.items as item, index}
<Tabs.Content value={index.toString()}>
<div class="grid grid-cols-[repeat(9,minmax(1.875rem,4.875rem))] place-content-center gap-1 pt-5 @md:gap-1.5 @xl:gap-2">
{#if item?.containsItems}
{#each item.containsItems as containedItem, index2}
{#if index2 > 0}
{#if index2 % 54 === 0}
{@render gap()}
{/if}
{/if}
<Tabs.Content value={index.toString()}>
{#if containedItem.texture_path}
<div class="bg-text/[0.04] flex aspect-square items-center justify-center rounded-sm">
{@render itemSnippet(containedItem, index2)}
</div>
{:else}
{@render emptyItem()}
{/if}
</Tabs.Content>
{/each}
{/if}
</div>
</Tabs.Content>
{/each}
</Tabs.Root>
{/snippet}

{#snippet inventorySection(tab: Tabs)}
<div class="grid grid-cols-[repeat(9,minmax(1.875rem,4.875rem))] place-content-center gap-1 pt-5 @md:gap-1.5 @xl:gap-2">
{#each tab.items as item, index}
{#if index > 0}
{#if index % tab.gap === 0}
{@render gap()}
{/if}
{/if}
{#if item.texture_path}
<div class="bg-text/[0.04] flex aspect-square items-center justify-center rounded-sm">
{#if tab.id === "inv"}
{@render itemSnippet({ ...item, rarity: item.rarity ?? "uncommon" } as ProcessedSkyBlockItem, index)}
{:else}
{@render itemSnippet(item, index)}
{/if}
</div>
{:else}
{@render emptyItem()}
{/if}
{/each}
</div>
{/snippet}
Loading