chore: do new design mockup
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<body class="bg-theme-950" data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
|
||||
|
17
src/lib/api/ezpp.ts
Normal file
17
src/lib/api/ezpp.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import ky from 'ky';
|
||||
|
||||
const BANCHO_ENDPOINT = 'https://c.ez-pp.farm/';
|
||||
|
||||
export const ezppfarm = {
|
||||
ping: async (): Promise<number | undefined> => {
|
||||
try {
|
||||
const start = Date.now();
|
||||
const request = await ky(BANCHO_ENDPOINT);
|
||||
if (!request.ok) return undefined;
|
||||
const ping = Date.now() - start;
|
||||
return ping;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
};
|
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AlertDialogPrimitive.ActionProps = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Action bind:ref class={cn(buttonVariants(), className)} {...restProps} />
|
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AlertDialogPrimitive.CancelProps = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Cancel
|
||||
bind:ref
|
||||
class={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
|
||||
{...restProps}
|
||||
/>
|
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { AlertDialog as AlertDialogPrimitive, type WithoutChild } from "bits-ui";
|
||||
import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
portalProps,
|
||||
...restProps
|
||||
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
|
||||
portalProps?: AlertDialogPrimitive.PortalProps;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Portal {...portalProps}>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
bind:ref
|
||||
class={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</AlertDialogPrimitive.Portal>
|
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AlertDialogPrimitive.DescriptionProps = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Description
|
||||
bind:ref
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
/>
|
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn("flex flex-col space-y-2 text-center sm:text-left", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AlertDialogPrimitive.OverlayProps = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Overlay
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
18
src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
Normal file
18
src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
level = 3,
|
||||
...restProps
|
||||
}: AlertDialogPrimitive.TitleProps = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Title
|
||||
bind:ref
|
||||
class={cn("text-lg font-semibold", className)}
|
||||
{level}
|
||||
{...restProps}
|
||||
/>
|
39
src/lib/components/ui/alert-dialog/index.ts
Normal file
39
src/lib/components/ui/alert-dialog/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
import Title from "./alert-dialog-title.svelte";
|
||||
import Action from "./alert-dialog-action.svelte";
|
||||
import Cancel from "./alert-dialog-cancel.svelte";
|
||||
import Footer from "./alert-dialog-footer.svelte";
|
||||
import Header from "./alert-dialog-header.svelte";
|
||||
import Overlay from "./alert-dialog-overlay.svelte";
|
||||
import Content from "./alert-dialog-content.svelte";
|
||||
import Description from "./alert-dialog-description.svelte";
|
||||
|
||||
const Root = AlertDialogPrimitive.Root;
|
||||
const Trigger = AlertDialogPrimitive.Trigger;
|
||||
const Portal = AlertDialogPrimitive.Portal;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Title,
|
||||
Action,
|
||||
Cancel,
|
||||
Portal,
|
||||
Footer,
|
||||
Header,
|
||||
Trigger,
|
||||
Overlay,
|
||||
Content,
|
||||
Description,
|
||||
//
|
||||
Root as AlertDialog,
|
||||
Title as AlertDialogTitle,
|
||||
Action as AlertDialogAction,
|
||||
Cancel as AlertDialogCancel,
|
||||
Portal as AlertDialogPortal,
|
||||
Footer as AlertDialogFooter,
|
||||
Header as AlertDialogHeader,
|
||||
Trigger as AlertDialogTrigger,
|
||||
Overlay as AlertDialogOverlay,
|
||||
Content as AlertDialogContent,
|
||||
Description as AlertDialogDescription,
|
||||
};
|
@@ -8,5 +8,5 @@
|
||||
<div
|
||||
style="background: url(https://osu.direct/api/media/background/{prop.beatmapId})"
|
||||
class="absolute top-0 left-0 w-full h-full !bg-cover -z-10 pointer-events-none blur opacity-10 rounded"
|
||||
></div> -->
|
||||
></div>
|
||||
</div>
|
||||
|
@@ -131,10 +131,10 @@
|
||||
@keyframes beat {
|
||||
0%,
|
||||
100% {
|
||||
scale: 1.08;
|
||||
scale: 1;
|
||||
}
|
||||
90% {
|
||||
scale: 1;
|
||||
scale: 1.08;
|
||||
}
|
||||
}
|
||||
@keyframes beat-pulse {
|
||||
|
7
src/lib/components/ui/progress/index.ts
Normal file
7
src/lib/components/ui/progress/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Root from "./progress.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Progress,
|
||||
};
|
44
src/lib/components/ui/progress/progress.svelte
Normal file
44
src/lib/components/ui/progress/progress.svelte
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { Progress as ProgressPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
max = 100,
|
||||
value,
|
||||
indeterminate = false,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<ProgressPrimitive.RootProps & { indeterminate?: boolean }> = $props();
|
||||
</script>
|
||||
|
||||
<ProgressPrimitive.Root
|
||||
bind:ref
|
||||
class={cn('bg-secondary relative h-4 w-full overflow-hidden rounded-full', className)}
|
||||
value={indeterminate ? max : value}
|
||||
{max}
|
||||
{...restProps}
|
||||
>
|
||||
<div
|
||||
class="bg-primary h-full w-full flex-1 transition-all {indeterminate ? 'animate-slide' : ''} rounded-lg"
|
||||
style={`transform: translateX(-${100 - (100 * ((indeterminate ? max : value) ?? 0)) / (max ?? 1)}%);`}
|
||||
></div>
|
||||
</ProgressPrimitive.Root>
|
||||
|
||||
<style lang="scss">
|
||||
.animate-slide {
|
||||
animation: 2s infinite forwards indeterminate;
|
||||
}
|
||||
@keyframes indeterminate {
|
||||
0%,
|
||||
2%,
|
||||
98%,
|
||||
100% {
|
||||
transform: translateX(-99%);
|
||||
}
|
||||
49%,
|
||||
51% {
|
||||
transform: translateX(99%);
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
let {
|
||||
loadingText,
|
||||
progress,
|
||||
indeterminate,
|
||||
}: { loadingText: string; progress: number; indeterminate: boolean } =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="relative flex items-center justify-center w-[80vw] h-10 bg-gray-900/70 border rounded overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="absolute h-full rounded top-0 left-0 bg-pink-600 transition-all"
|
||||
style="width: {indeterminate
|
||||
? 100
|
||||
: Math.min(100, Math.max(0, progress))}%;"
|
||||
></div>
|
||||
<p class="text-xs z-10">{loadingText}</p>
|
||||
</div>
|
34
src/lib/components/ui/select/index.ts
Normal file
34
src/lib/components/ui/select/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
import GroupHeading from "./select-group-heading.svelte";
|
||||
import Item from "./select-item.svelte";
|
||||
import Content from "./select-content.svelte";
|
||||
import Trigger from "./select-trigger.svelte";
|
||||
import Separator from "./select-separator.svelte";
|
||||
import ScrollDownButton from "./select-scroll-down-button.svelte";
|
||||
import ScrollUpButton from "./select-scroll-up-button.svelte";
|
||||
|
||||
const Root = SelectPrimitive.Root;
|
||||
const Group = SelectPrimitive.Group;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Group,
|
||||
GroupHeading,
|
||||
Item,
|
||||
Content,
|
||||
Trigger,
|
||||
Separator,
|
||||
ScrollDownButton,
|
||||
ScrollUpButton,
|
||||
//
|
||||
Root as Select,
|
||||
Group as SelectGroup,
|
||||
GroupHeading as SelectGroupHeading,
|
||||
Item as SelectItem,
|
||||
Content as SelectContent,
|
||||
Trigger as SelectTrigger,
|
||||
Separator as SelectSeparator,
|
||||
ScrollDownButton as SelectScrollDownButton,
|
||||
ScrollUpButton as SelectScrollUpButton,
|
||||
};
|
39
src/lib/components/ui/select/select-content.svelte
Normal file
39
src/lib/components/ui/select/select-content.svelte
Normal file
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive, type WithoutChild } from "bits-ui";
|
||||
import SelectScrollUpButton from "./select-scroll-up-button.svelte";
|
||||
import SelectScrollDownButton from "./select-scroll-down-button.svelte";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.ContentProps> & {
|
||||
portalProps?: SelectPrimitive.PortalProps;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Portal {...portalProps}>
|
||||
<SelectPrimitive.Content
|
||||
bind:ref
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-popover text-popover-foreground relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
class={cn(
|
||||
"h-[var(--bits-select-anchor-height)] w-full min-w-[var(--bits-select-anchor-width)] p-1"
|
||||
)}
|
||||
>
|
||||
{@render children?.()}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
16
src/lib/components/ui/select/select-group-heading.svelte
Normal file
16
src/lib/components/ui/select/select-group-heading.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: SelectPrimitive.GroupHeadingProps = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.GroupHeading
|
||||
bind:ref
|
||||
class={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...restProps}
|
||||
/>
|
37
src/lib/components/ui/select/select-item.svelte
Normal file
37
src/lib/components/ui/select/select-item.svelte
Normal file
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import Check from "@lucide/svelte/icons/check";
|
||||
import { Select as SelectPrimitive, type WithoutChild } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value,
|
||||
label,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.ItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Item
|
||||
bind:ref
|
||||
{value}
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-primary-800/30 data-[highlighted]:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ selected, highlighted })}
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if selected}
|
||||
<Check class="size-4" />
|
||||
{/if}
|
||||
</span>
|
||||
{#if childrenProp}
|
||||
{@render childrenProp({ selected, highlighted })}
|
||||
{:else}
|
||||
{label || value}
|
||||
{/if}
|
||||
{/snippet}
|
||||
</SelectPrimitive.Item>
|
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import ChevronDown from "@lucide/svelte/icons/chevron-down";
|
||||
import { Select as SelectPrimitive, type WithoutChildrenOrChild } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
bind:ref
|
||||
class={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChevronDown class="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
19
src/lib/components/ui/select/select-scroll-up-button.svelte
Normal file
19
src/lib/components/ui/select/select-scroll-up-button.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import ChevronUp from "@lucide/svelte/icons/chevron-up";
|
||||
import { Select as SelectPrimitive, type WithoutChildrenOrChild } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<SelectPrimitive.ScrollUpButtonProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
bind:ref
|
||||
class={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChevronUp class="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
13
src/lib/components/ui/select/select-separator.svelte
Normal file
13
src/lib/components/ui/select/select-separator.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { Separator as SeparatorPrimitive } from "bits-ui";
|
||||
import { Separator } from "$lib/components/ui/separator/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: SeparatorPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<Separator bind:ref class={cn("bg-muted -mx-1 my-1 h-px", className)} {...restProps} />
|
24
src/lib/components/ui/select/select-trigger.svelte
Normal file
24
src/lib/components/ui/select/select-trigger.svelte
Normal file
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive, type WithoutChild } from "bits-ui";
|
||||
import ChevronDown from "@lucide/svelte/icons/chevron-down";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.TriggerProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Trigger
|
||||
bind:ref
|
||||
class={cn(
|
||||
"border-input bg-background ring-offset-background data-[placeholder]:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronDown class="size-4 opacity-50" />
|
||||
</SelectPrimitive.Trigger>
|
7
src/lib/components/ui/separator/index.ts
Normal file
7
src/lib/components/ui/separator/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Root from "./separator.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
};
|
22
src/lib/components/ui/separator/separator.svelte
Normal file
22
src/lib/components/ui/separator/separator.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { Separator as SeparatorPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
orientation = "horizontal",
|
||||
...restProps
|
||||
}: SeparatorPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<SeparatorPrimitive.Root
|
||||
bind:ref
|
||||
class={cn(
|
||||
"bg-border shrink-0",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "min-h-full w-[1px]",
|
||||
className
|
||||
)}
|
||||
{orientation}
|
||||
{...restProps}
|
||||
/>
|
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import Minimize from "lucide-svelte/icons/minus";
|
||||
import Close from "lucide-svelte/icons/x";
|
||||
|
||||
import Logo from "$assets/logo.png";
|
||||
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { onMount } from "svelte";
|
||||
@@ -17,8 +19,11 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div data-tauri-drag-region class="titlebar z-[9999]">
|
||||
<!-- <p class="mr-auto ms-2 text-sm">EZPPLauncher</p> -->
|
||||
<div data-tauri-drag-region class="titlebar z-[99999] border-b border-theme-800/90">
|
||||
<div class="mr-auto ms-2 flex flex-row gap-2 items-center text-[1.05rem] font-semibold">
|
||||
<img src={Logo} alt="EZPP Launcher Logo" class="h-11 w-11 inline-block" />
|
||||
<span>EZPPLauncher</span>
|
||||
</div>
|
||||
<div class="titlebar-button" id="titlebar-minimize">
|
||||
<Minimize size={18} />
|
||||
</div>
|
||||
@@ -29,7 +34,7 @@
|
||||
|
||||
<style lang="scss">
|
||||
.titlebar {
|
||||
height: 30px;
|
||||
height: 50px;
|
||||
/* background: #040612; */
|
||||
user-select: none;
|
||||
display: flex;
|
||||
@@ -40,12 +45,13 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-bottom: 10px;
|
||||
pointer-events: all !important;
|
||||
}
|
||||
.titlebar-button {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 45px;
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
44
src/lib/global.ts
Normal file
44
src/lib/global.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { ezppfarm } from './api/ezpp';
|
||||
|
||||
export const server_ping = writable<number | undefined>(undefined);
|
||||
const server_connection_fails = writable(0);
|
||||
|
||||
export const online_friends = writable<number | undefined>(undefined);
|
||||
|
||||
export const beatmap_sets = writable<number | undefined>(undefined)
|
||||
|
||||
export const setupValues = () => {
|
||||
updatePing();
|
||||
updateFriends();
|
||||
updateBeatmapSets();
|
||||
const pingUpdater = setInterval(updatePing, 5000 * 2);
|
||||
const friendUpdater = setInterval(updateFriends, 5000 * 2);
|
||||
|
||||
return () => {
|
||||
clearInterval(pingUpdater);
|
||||
clearInterval(friendUpdater);
|
||||
};
|
||||
};
|
||||
|
||||
const updatePing = async () => {
|
||||
const serverPing = await ezppfarm.ping();
|
||||
if (!serverPing) {
|
||||
server_connection_fails.update((num) => num + 1);
|
||||
} else {
|
||||
server_connection_fails.set(0);
|
||||
server_ping.set(serverPing);
|
||||
}
|
||||
};
|
||||
|
||||
const updateFriends = async () => {
|
||||
await new Promise((res) => setTimeout(res, Math.random() * 1000));
|
||||
const onlineFriends = Math.round(Math.random() * 10);
|
||||
online_friends.set(onlineFriends);
|
||||
};
|
||||
|
||||
const updateBeatmapSets = async () => {
|
||||
await new Promise((res) => setTimeout(res, Math.random() * 1500));
|
||||
const beatmapSets = Math.round(Math.random() * 5000);
|
||||
beatmap_sets.set(beatmapSets);
|
||||
};
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts">
|
||||
import Titlebar from "@/components/ui/titlebar/titlebar.svelte";
|
||||
import "../app.css";
|
||||
import Titlebar from '@/components/ui/titlebar/titlebar.svelte';
|
||||
import '../app.css';
|
||||
import { setupValues } from '@/global';
|
||||
import { onMount } from 'svelte';
|
||||
let { children } = $props();
|
||||
|
||||
onMount(setupValues);
|
||||
</script>
|
||||
|
||||
<Titlebar />
|
||||
|
@@ -1,95 +1,219 @@
|
||||
<script lang="ts">
|
||||
import Background from "@/components/ui/background/background.svelte";
|
||||
import Button from "@/components/ui/button/button.svelte";
|
||||
import Logo from "@/components/ui/logo/logo.svelte";
|
||||
import * as Avatar from "@/components/ui/avatar";
|
||||
import * as DropdownMenu from "@/components/ui/dropdown-menu";
|
||||
import Progressbar from "@/components/ui/progressbar/progressbar.svelte";
|
||||
import Settings from "lucide-svelte/icons/settings";
|
||||
import LogOut from "lucide-svelte/icons/log-out";
|
||||
import Heart from "lucide-svelte/icons/heart";
|
||||
import { badgeVariants } from "@/components/ui/badge";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
let progress = $state(0);
|
||||
let extended = $state(false);
|
||||
import * as Avatar from '@/components/ui/avatar';
|
||||
import Badge from '@/components/ui/badge/badge.svelte';
|
||||
import Button from '@/components/ui/button/button.svelte';
|
||||
import * as Select from '@/components/ui/select';
|
||||
import { beatmap_sets, online_friends, server_ping } from '@/global';
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import { LoaderCircle, Logs, Music2, Play, Users, Wifi, Gamepad2 } from 'lucide-svelte';
|
||||
import { Circle } from 'radix-icons-svelte';
|
||||
import NumberFlow from '@number-flow/svelte';
|
||||
import * as AlertDialog from '@/components/ui/alert-dialog';
|
||||
import Progress from '@/components/ui/progress/progress.svelte';
|
||||
|
||||
let beatmapId = $state(3820896);
|
||||
const current = WebviewWindow.getCurrent();
|
||||
current.setAlwaysOnTop(true);
|
||||
let selectedTab = $state('home');
|
||||
let launching = $state(false);
|
||||
</script>
|
||||
|
||||
<div class="relative h-screen w-screen">
|
||||
<Background {beatmapId} />
|
||||
<div class="absolute z-20 top-2 right-2 py-7">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<div class="relative">
|
||||
<p
|
||||
class={twMerge(
|
||||
badgeVariants(),
|
||||
"p-0 h-5 w-5 absolute -right-0.5 -top-0.5 z-50 !bg-pink-600 border-2 border-pink-800 text-white"
|
||||
)}
|
||||
>
|
||||
<Heart class="h-3 w-3 m-auto p-0" />
|
||||
</p>
|
||||
<Avatar.Root class="border-[3px] z-40">
|
||||
<Avatar.AvatarFallback>U</Avatar.AvatarFallback>
|
||||
<Avatar.AvatarImage src="https://a.ez-pp.farm/1001"
|
||||
></Avatar.AvatarImage>
|
||||
</Avatar.Root>
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content class="w-48 max-w-48 mx-2" side="bottom">
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.GroupHeading class="truncate"
|
||||
>Hello, Quetzalcoatl!</DropdownMenu.GroupHeading
|
||||
>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item class="cursor-pointer">
|
||||
<Settings class="mr-2 size-4" />
|
||||
<span>Settings</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="cursor-pointer">
|
||||
<LogOut class="mr-2 size-4" />
|
||||
<span>Log out</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
<AlertDialog.Root bind:open={launching}>
|
||||
<AlertDialog.Content class="bg-theme-950 border-theme-900">
|
||||
<div class="flex flex-col items-center justify-center gap-2">
|
||||
<Progress indeterminate />
|
||||
<span>Downloading update files...</span>
|
||||
</div>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
|
||||
<div
|
||||
class="absolute top-0 left-0 py-3 w-full h-screen flex flex-col gap-16 items-center justify-end overflow-hidden"
|
||||
>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<Logo {beatmapId} {extended} onclick={() => (extended = !extended)} />
|
||||
<div class="grid grid-cols-[0.41fr_1fr] mt-[50px] h-[calc(100vh-50px)]">
|
||||
<div class="w-full h-full border-r border-theme-800/90 flex flex-col gap-6 py-3">
|
||||
<div class="flex flex-col items-center gap-2 border-b pb-6">
|
||||
<Avatar.Root class="w-20 h-20 border-2 border-theme-800">
|
||||
<Avatar.Image src="https://a.ez-pp.farm/0" />
|
||||
<Avatar.Fallback class="bg-theme-900">
|
||||
<LoaderCircle class="animate-spin" size={32} />
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<span class="font-semibold text-2xl text-theme-50">User</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-6 h-full px-3">
|
||||
<Select.Root type="single">
|
||||
<Select.Trigger
|
||||
class="border-theme-800/90 bg-theme-900/90 !text-muted-foreground font-semibold"
|
||||
>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Circle class="text-muted-foreground" />
|
||||
osu!vn
|
||||
</div>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="bg-theme-950 border border-theme-900 rounded-lg">
|
||||
<Select.Item value="light">osu!vn</Select.Item>
|
||||
<Select.Item value="dark">osu!rx</Select.Item>
|
||||
<Select.Item value="system">osu!ap</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<div class="bg-theme-900/90 border border-theme-800/90 rounded-lg p-2">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Logs class="text-muted-foreground" size="16" />
|
||||
<span class="font-semibold text-muted-foreground text-sm">Mode Stats</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 mt-2 border-t border-theme-800 pt-2 pb-2">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span class="text-sm text-muted-foreground font-semibold">Rank</span>
|
||||
<span class="text-lg font-semibold text-theme-50">#727</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span class="text-sm text-muted-foreground font-semibold">PP</span>
|
||||
<span class="text-lg font-semibold text-theme-50">727</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-[1fr_auto] border-t border-theme-800 pt-2">
|
||||
<span class="text-sm text-muted-foreground font-semibold">Accuracy</span>
|
||||
<span class="text-sm font-semibold text-end text-theme-50">72.72%</span>
|
||||
|
||||
<span class="text-sm text-muted-foreground font-semibold">Play Count</span>
|
||||
<span class="text-sm font-semibold text-end text-theme-50">727</span>
|
||||
|
||||
<span class="text-sm text-muted-foreground font-semibold">Play Time</span>
|
||||
<span class="text-sm font-semibold text-end text-theme-50">727h</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-auto bg-theme-900/90 border border-theme-800/90 rounded-lg p-2">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Users class="text-muted-foreground" size="16" />
|
||||
<span class="font-semibold text-muted-foreground text-sm"> Friends </span>
|
||||
</div>
|
||||
<Badge class="h-5 bg-green-500/20 hover:bg-green-500/20 text-green-500">3 online</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6">
|
||||
<div
|
||||
class="{extended
|
||||
? 'opacity-100 translate-y-0'
|
||||
: 'opacity-0 translate-y-1'} flex flex-row gap-1 items-center transition-all select-none"
|
||||
class="flex flex-row flex-nowrap h-11 w-full bg-theme-800/50 border border-theme-800/90 rounded-lg p-[4px]"
|
||||
>
|
||||
<Progressbar
|
||||
loadingText="Waiting for launch..."
|
||||
{progress}
|
||||
indeterminate={false}
|
||||
/>
|
||||
<button
|
||||
class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab ===
|
||||
'home'
|
||||
? 'bg-white/70 border border-white/60 text-theme-950'
|
||||
: ''} transition-all"
|
||||
onclick={() => (selectedTab = 'home')}
|
||||
>
|
||||
Home
|
||||
</button>
|
||||
<button
|
||||
class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab ===
|
||||
'settings'
|
||||
? 'bg-white/70 border border-white/60 text-theme-950'
|
||||
: ''} transition-all"
|
||||
onclick={() => (selectedTab = 'settings')}
|
||||
>
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="my-auto bg-theme-900/90 flex flex-col items-center justify-center gap-6 border border-theme-800/90 rounded-lg p-6"
|
||||
>
|
||||
<div class="grid grid-cols-3 w-full gap-3">
|
||||
<div
|
||||
class="bg-theme-800/90 border border-theme-700/90 rounded-lg px-2 py-4 w-full flex flex-col gap-1 items-center justify-center"
|
||||
>
|
||||
<div class="flex items-center justify-center p-2 rounded-lg bg-blue-500/20">
|
||||
<Music2 class="text-blue-500" size="26" />
|
||||
</div>
|
||||
<div class="relative font-bold text-xl text-blue-400">
|
||||
<div
|
||||
class="absolute top-1 left-1/2 -translate-x-1/2 {!$beatmap_sets
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'} transition-opacity duration-1000"
|
||||
>
|
||||
<LoaderCircle class="animate-spin" />
|
||||
</div>
|
||||
<div
|
||||
class="{!$beatmap_sets
|
||||
? 'opacity-0'
|
||||
: 'opacity-100'} transition-opacity duration-1000"
|
||||
>
|
||||
<NumberFlow value={$beatmap_sets ?? 0} trend={0} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted-foreground text-sm">Beatmap Sets imported</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-theme-800/90 border border-theme-700/90 rounded-lg px-2 py-4 w-full flex flex-col gap-1 items-center justify-center"
|
||||
>
|
||||
<div class="flex items-center justify-center p-2 rounded-lg bg-yellow-500/20">
|
||||
<Users class="text-yellow-500" size="26" />
|
||||
</div>
|
||||
<div class="relative font-bold text-xl text-yellow-400">
|
||||
<div
|
||||
class="absolute top-1 left-1/2 -translate-x-1/2 {!$online_friends
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'} transition-opacity duration-1000"
|
||||
>
|
||||
<LoaderCircle class="animate-spin" />
|
||||
</div>
|
||||
<div
|
||||
class="{!$online_friends
|
||||
? 'opacity-0'
|
||||
: 'opacity-100'} transition-opacity duration-1000"
|
||||
>
|
||||
<NumberFlow value={$online_friends ?? 0} trend={0} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted-foreground text-sm">Friends online</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-theme-800/90 border border-theme-700/90 rounded-lg px-2 py-4 w-full flex flex-col gap-1 items-center justify-center"
|
||||
>
|
||||
<div class="flex items-center justify-center p-2 rounded-lg bg-green-500/20">
|
||||
<Wifi class="text-green-500" size="26" />
|
||||
</div>
|
||||
<div class="relative font-bold text-xl text-green-400">
|
||||
<div
|
||||
class="absolute top-1 left-1/2 -translate-x-1/2 {!$server_ping
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'} transition-opacity duration-1000"
|
||||
>
|
||||
<LoaderCircle class="animate-spin" />
|
||||
</div>
|
||||
<div
|
||||
class="{!$server_ping ? 'opacity-0' : 'opacity-100'} transition-opacity duration-1000"
|
||||
>
|
||||
<NumberFlow value={$server_ping ?? 0} trend={0} suffix="ms" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted-foreground text-sm">Ping to Server</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
size="lg"
|
||||
onclick={() => {
|
||||
console.log(progress);
|
||||
if (progress >= 100) {
|
||||
progress = 0;
|
||||
} else {
|
||||
progress += 10;
|
||||
}
|
||||
launching = true;
|
||||
setTimeout(() => {
|
||||
launching = false;
|
||||
}, 5000);
|
||||
}}
|
||||
>
|
||||
<Play />
|
||||
Launch
|
||||
</Button>
|
||||
</div>
|
||||
<div class="mt-auto bg-theme-900/90 border border-theme-800/90 rounded-lg px-6 py-3">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Gamepad2 class="text-muted-foreground" size="24" />
|
||||
<span class="font-semibold text-muted-foreground text-sm">Client Info</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-[1fr_auto] gap-1 mt-2 border-t border-theme-800 pt-2 px-2 pb-2">
|
||||
<span class="text-sm text-muted-foreground font-semibold">osu! Version</span>
|
||||
<span class="text-sm font-semibold text-end text-theme-50">20250626.1</span>
|
||||
|
||||
<span class="text-sm text-muted-foreground font-semibold">Beatmap Sets</span>
|
||||
<span class="text-sm font-semibold text-end text-theme-50">{$beatmap_sets}</span>
|
||||
|
||||
<span class="text-sm text-muted-foreground font-semibold">Skins</span>
|
||||
<span class="text-sm font-semibold text-end text-theme-50">727</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user