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

Refactor message notifications handling, compress image, update dependencies, and improve error handling #124

Merged
merged 8 commits into from
May 22, 2024
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
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@jill64/universal-sanitizer": "^1.2.11",
"@lucia-auth/adapter-prisma": "4.0.1",
"@sveltejs/adapter-vercel": "^5.3.0",
"@sveltejs/kit": "^2.5.9",
"@sveltejs/kit": "^2.5.10",
"@sveltejs/vite-plugin-svelte": "^3.1.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.7",
Expand All @@ -38,7 +38,7 @@
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.3",
"prettier-plugin-tailwindcss": "^0.5.14",
"shiki": "^1.5.2",
"shiki": "^1.6.0",
"svelte": "^4.2.17",
"svelte-check": "^3.7.1",
"svelte-headless-table": "^0.18.2",
Expand All @@ -63,19 +63,19 @@
"@tiptap/pm": "^2.4.0",
"@tiptap/starter-kit": "^2.4.0",
"@vercel/speed-insights": "^1.0.10",
"bits-ui": "^0.21.8",
"bits-ui": "^0.21.9",
"cloudinary": "^2.2.0",
"clsx": "^2.1.1",
"cmdk-sv": "^0.0.17",
"css-tree": "^2.3.1",
"date-fns": "^3.6.0",
"fast-average-color-node": "^3.0.0",
"firebase": "^10.12.0",
"firebase-admin": "^12.1.0",
"firebase": "^10.12.1",
"firebase-admin": "^12.1.1",
"formsnap": "^1.0.0",
"headview3d": "^3.0.2",
"lucia": "^3.2.0",
"lucide-svelte": "^0.378.0",
"lucide-svelte": "^0.379.0",
"mode-watcher": "^0.3.0",
"numerable": "^0.3.15",
"oslo": "^1.2.0",
Expand All @@ -85,7 +85,7 @@
"skinview3d": "^3.0.1",
"super-sitemap": "^0.14.14",
"svelte-interactions": "^0.2.0",
"svelte-persisted-store": "^0.9.2",
"svelte-persisted-store": "^0.9.4",
"svelte-sonner": "^0.3.24",
"sveltekit-rate-limiter": "^0.5.1",
"sveltekit-superforms": "^2.13.1",
Expand Down
343 changes: 176 additions & 167 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

82 changes: 55 additions & 27 deletions src/lib/components/MinionsListBox.svelte
Original file line number Diff line number Diff line change
@@ -1,50 +1,75 @@
<script lang="ts">
import * as Avatar from "$lib/components/ui/avatar";
import { Button } from "$lib/components/ui/button";
import * as Command from "$lib/components/ui/command";
import * as Popover from "$lib/components/ui/popover";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import { cn } from "$lib/utils";
import Check from "lucide-svelte/icons/check";
import ChevronsUpDown from "lucide-svelte/icons/chevrons-up-down";
import SearchX from "lucide-svelte/icons/search-x";
import { createEventDispatcher, tick } from "svelte";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import { writable } from "svelte/store";

export let minionType: { id: string; generator: string; texture: string; maxTier: number }[];
export let search: string = "";
export let showReset: boolean;
export let variant: "rounded" | "half-rounded" = "rounded";

const dispatch = createEventDispatcher<{ onSelect: { id: string; generator: string; texture: string; maxTier: number } }>();
const dispatch = createEventDispatcher<{ onSelect: { id: string; generator: string; texture: string; maxTier: number | undefined } }>();

let open = false;
let value = "";
const open = writable<boolean>(false);
const value = writable<string>(search);

// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
open.set(false);

tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>

<Popover.Root bind:open let:ids>
<Popover.Root bind:open={$open} let:ids>
<Popover.Trigger asChild let:builder>
<Button builders={[builder]} variant="outline" role="combobox" type="button" class={cn("relative w-40 cursor-default justify-between rounded-md border border-input bg-background py-1.5 pl-3 text-left shadow-sm focus:outline-none focus:ring-2 focus:ring-ring sm:text-sm sm:leading-6 md:w-44", !value && "text-muted-foreground")} aria-label="Select a minion" aria-haspopup="listbox" aria-expanded={open}>
<div class="flex">
{#if minionType.find((f) => f.generator === value)}
<img loading="lazy" src={`https://res.cloudinary.com/minionah/image/upload/v1/minions/head/${minionType.find((f) => f.generator === value)?.id}`} class="mr-2 h-6 w-6" alt={value} />
<span class="capitalize">
{minionType
.find((f) => f.generator === value)
?.generator.replace(/_/g, " ")
.toLowerCase()
.charAt(0) + value.slice(1).toLowerCase().replace(/_/g, " ")}
</span>
{:else}
<span>Select a minion</span>
{/if}
</div>
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
<div class="flex items-center">
<Button builders={[builder]} variant="outline" role="combobox" type="button" class={cn("relative w-40 cursor-default justify-between rounded-md border border-input bg-background py-1.5 pl-3 text-left shadow-sm focus:z-10 focus:outline-none focus:ring-2 focus:ring-ring sm:text-sm sm:leading-6 md:w-44", !$value && "text-muted-foreground", $value && showReset && "rounded-r-none border-r-0", variant === "half-rounded" && "rounded-r-none border-r-0")} aria-label="Select a minion" aria-haspopup="listbox" aria-expanded={$open}>
<div class="flex">
{#if minionType.find((f) => f.generator === $value)}
<Avatar.Root class="mr-2 size-6 flex-shrink-0 overflow-visible rounded-full">
<Avatar.Image src={`https://res.cloudinary.com/minionah/image/upload/v1/minions/head/${minionType.find((f) => f.generator === $value)?.id}`} alt={$value} class="pointer-events-none h-full w-full overflow-visible" />
<Avatar.Fallback class="border-2 border-accent bg-accent text-xs">{$value.slice(0, 2).toUpperCase()}</Avatar.Fallback>
</Avatar.Root>
<span class="capitalize">
{minionType
.find((f) => f.generator === $value)
?.generator.replace(/_/g, " ")
.toLowerCase()
.charAt(0) + $value.slice(1).toLowerCase().replace(/_/g, " ")}
</span>
{:else}
<span>Select a minion</span>
{/if}
</div>
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>

{#if $value && showReset}
<Button
variant="outline"
class={cn("relative rounded-md rounded-l-none border-r border-input bg-background py-1.5 pl-3 text-left shadow-sm focus:z-10 focus:outline-none focus:ring-2 focus:ring-ring sm:text-sm sm:leading-6")}
on:click={() => {
value.set("");
dispatch("onSelect", { id: "", generator: "", texture: "", maxTier: undefined });
}}
aria-label="Reset search">
<SearchX class="h-4 w-4 shrink-0 opacity-50" />
</Button>
{/if}
</div>
</Popover.Trigger>
<Popover.Content class="mt-1 w-[200] border border-border bg-popover p-0">
<Command.Root class="max-h-56 overflow-hidden border-none bg-popover text-base sm:text-sm">
Expand All @@ -56,18 +81,21 @@
<Command.Item
value={minionType.generator}
onSelect={() => {
value = minionType.generator;
value.set(minionType.generator);
dispatch("onSelect", minionType);
closeAndFocusTrigger(ids.trigger);
}}
class="justify-between text-popover-foreground aria-selected:bg-background">
<div class="inline-flex items-center">
<img src={`https://res.cloudinary.com/minionah/image/upload/v1/minions/head/${minionType.id}`} class="mr-2 h-6 w-6" alt={minionType.generator} />
<Avatar.Root class="mr-2 size-6 flex-shrink-0 overflow-visible rounded-full">
<Avatar.Image src={`https://res.cloudinary.com/minionah/image/upload/v1/minions/head/${minionType.id}`} alt={minionType.generator} class="pointer-events-none h-full w-full overflow-visible" />
<Avatar.Fallback class="border-2 border-accent bg-accent text-xs">{minionType.generator.slice(0, 2).toUpperCase()}</Avatar.Fallback>
</Avatar.Root>
<span class="capitalize">
{minionType.generator.replace(/_/g, " ").toLowerCase().charAt(0).toUpperCase() + minionType.generator.slice(1).toLowerCase().replace(/_/g, " ")}
{minionType.generator.replace(/_/g, " ").toLowerCase()}
</span>
</div>
<Check class={cn("mr-2 h-4 w-4", value !== minionType.generator && "text-transparent")} />
<Check class={cn("mr-2 h-4 w-4", $value !== minionType.generator && "text-transparent")} />
</Command.Item>
{/each}
</ScrollArea>
Expand Down
52 changes: 52 additions & 0 deletions src/lib/components/SearchSelect.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script context="module" lang="ts">
export enum SearchType {
Minion = "Minion",
User = "User"
}
</script>

<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { cn } from "$lib/utils";
import ChevronDown from "lucide-svelte/icons/chevron-down";
import { createEventDispatcher } from "svelte";
import Pickaxe from "lucide-svelte/icons/pickaxe";
import Users from "lucide-svelte/icons/users";

export let searchType: SearchType = SearchType.Minion;

const dispatch = createEventDispatcher<{ onSelect: SearchType }>();
</script>

<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button builders={[builder]} variant="outline" class={cn("relative rounded-md rounded-l-none border-r border-input bg-background py-1.5 pl-3 text-left shadow-sm focus:z-10 focus:outline-none focus:ring-2 focus:ring-ring sm:text-sm sm:leading-6")} aria-label="Reset search">
<ChevronDown class="h-4 w-4 shrink-0 opacity-50" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="rounded-md border-input bg-background">
<DropdownMenu.Group class="text-muted-foreground">
<DropdownMenu.Label>Search for</DropdownMenu.Label>
{#if searchType !== SearchType.Minion}
<DropdownMenu.Item
class="transition-colors hover:bg-accent hover:text-accent-foreground"
on:click={() => {
searchType = SearchType.Minion;
dispatch("onSelect", SearchType.Minion);
}}>
<Pickaxe class="mr-1 size-4" /> Minions
</DropdownMenu.Item>
{:else}
<DropdownMenu.Item
class="transition-colors hover:bg-accent hover:text-accent-foreground"
on:click={() => {
searchType = SearchType.User;
dispatch("onSelect", SearchType.User);
}}>
<Users class="mr-1 size-4" /> Users
</DropdownMenu.Item>
{/if}
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
106 changes: 106 additions & 0 deletions src/lib/components/UsersListBox.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<script lang="ts">
import * as Avatar from "$lib/components/ui/avatar";
import { Button } from "$lib/components/ui/button";
import * as Command from "$lib/components/ui/command";
import * as Popover from "$lib/components/ui/popover";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import { cn } from "$lib/utils";
import Check from "lucide-svelte/icons/check";
import ChevronsUpDown from "lucide-svelte/icons/chevrons-up-down";
import SearchX from "lucide-svelte/icons/search-x";
import { createEventDispatcher, tick } from "svelte";
import { writable } from "svelte/store";

type User = {
id: string;
username: string;
};

export let users: User[];
export let search: string;
export let showReset: boolean;
export let variant: "rounded" | "half-rounded" = "rounded";

const dispatch = createEventDispatcher<{ onSelect: User }>();

const open = writable<boolean>(false);
const value = writable<string>(search);

// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open.set(false);

tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>

<Popover.Root bind:open={$open} let:ids>
<Popover.Trigger asChild let:builder>
<div class="flex items-center">
<Button builders={[builder]} variant="outline" role="combobox" type="button" class={cn("relative w-40 cursor-default justify-between rounded-md border border-input bg-background py-1.5 pl-3 text-left shadow-sm focus:z-10 focus:outline-none focus:ring-2 focus:ring-ring sm:text-sm sm:leading-6 md:w-44", !$value && "text-muted-foreground", $value && showReset && "rounded-r-none border-r-0", variant === "half-rounded" && "rounded-r-none border-r-0")} aria-label="Select a user" aria-haspopup="listbox" aria-expanded={$open}>
<div class="flex">
{#if users.find((f) => f.id === $value)}
<Avatar.Root class="mr-2 size-6 flex-shrink-0 overflow-visible rounded-full">
<Avatar.Image src={`https://res.cloudinary.com/minionah/image/upload/v1/users/avatars/${users.find((f) => f.id === $value)?.id}`} alt={$value} class="pointer-events-none h-full w-full overflow-visible" />
<Avatar.Fallback class="border-2 border-accent bg-accent text-xs uppercase">{users.find((f) => f.id === $value)?.username.slice(0, 2)}</Avatar.Fallback>
</Avatar.Root>
<span>
{users.find((f) => f.id === $value)?.username}
</span>
{:else}
<span>Select a user</span>
{/if}
</div>
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>

{#if $value && showReset}
<Button
variant="outline"
class={cn("relative rounded-md rounded-l-none border-r border-input bg-background py-1.5 pl-3 text-left shadow-sm focus:z-10 focus:outline-none focus:ring-2 focus:ring-ring sm:text-sm sm:leading-6")}
on:click={() => {
value.set("");
dispatch("onSelect", { id: "", username: "" });
}}
aria-label="Reset search">
<SearchX class="h-4 w-4 shrink-0 opacity-50" />
</Button>
{/if}
</div>
</Popover.Trigger>
<Popover.Content class="mt-1 w-[200] border border-border bg-popover p-0">
<Command.Root class="max-h-56 overflow-hidden border-none bg-popover text-base sm:text-sm">
<Command.Input autofocus placeholder="Search for a user" class="border-0 text-popover-foreground focus:shadow-none focus:outline-0 focus:ring-0" />
<Command.Empty>No users found</Command.Empty>
<Command.Group>
<ScrollArea class="h-40 rounded-md">
{#each users as user}
<Command.Item
value={user.username}
onSelect={() => {
value.set(user.id);
dispatch("onSelect", user);
closeAndFocusTrigger(ids.trigger);
}}
class="justify-between text-popover-foreground aria-selected:bg-background">
<div class="inline-flex items-center">
<Avatar.Root class="mr-2 size-6 flex-shrink-0 overflow-visible rounded-full">
<Avatar.Image src={`https://res.cloudinary.com/minionah/image/upload/v1/users/avatars/${user.id}`} alt={user.username} class="pointer-events-none h-full w-full overflow-visible" />
<Avatar.Fallback class="border-2 border-accent bg-accent text-xs uppercase">{user.username.slice(0, 2)}</Avatar.Fallback>
</Avatar.Root>
<span>
{user.username}
</span>
</div>
<Check class={cn("mr-2 h-4 w-4", $value !== user.id && "text-transparent")} />
</Command.Item>
{/each}
</ScrollArea>
</Command.Group>
</Command.Root>
</Popover.Content>
</Popover.Root>
2 changes: 1 addition & 1 deletion src/lib/components/card/card-item-minion.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
class="group rounded bg-accent p-1 text-sm text-muted-foreground focus:outline-none focus:ring-4 focus:ring-transparent"
use:pressAction
on:press={() => {
handleSearchSignal(minion.minion.generator.replace(/_/g, " ").toLowerCase().charAt(0).toUpperCase() + minion.minion.generator.slice(1).toLowerCase().replace(/_/g, " "));
handleSearchSignal(minion.minion.generator);
}}>
<Search class="h-5 w-5 transition-colors duration-300 group-hover:text-white" />
</button>
Expand Down
11 changes: 6 additions & 5 deletions src/routes/(main)/(protected)/profile/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@

let canvasIsLoading = true;

let maxtier: number = 12;
let maxtier: number | undefined = 12;

let tierListDisabled = true;

Expand Down Expand Up @@ -167,14 +167,15 @@
<Form.Control let:attrs>
<Form.Label>Minion</Form.Label>
<MinionsListBox
showReset={false}
on:onSelect={({ detail }) => {
maxtier = detail.maxTier;
$formDataCreate.type = detail.generator;
if ($constraintsCreate.tier?.min && $constraintsCreate.tier?.max) {
const minTier = Number($constraintsCreate.tier.min);
const maxTier = Number($constraintsCreate.tier.max);
if ($constraintsCreate.tier?.min && $constraintsCreate.tier?.max && maxtier) {
const minTierConstraint = Number($constraintsCreate.tier.min);
const maxTierConstraint = Number($constraintsCreate.tier.max);

if (maxtier > minTier && maxtier <= maxTier) {
if (maxtier > minTierConstraint && maxtier <= maxTierConstraint) {
tierListDisabled = false;
}
}
Expand Down
Loading
Loading