mirror of
https://github.com/SrIzan10/lofi.git
synced 2026-06-06 00:56:53 +00:00
feat: playing and pausing
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lucide/svelte": "^0.488.0",
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/adapter-cloudflare": "^5.0.1",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import MusicPlayer from "@/components/app/music-player.svelte";
|
||||
import MusicPlayer from "@/components/app/now-playing.svelte";
|
||||
</script>
|
||||
|
||||
<div class="fixed bottom-5 left-2 right-2 z-50 flex items-center justify-between p-4 bg-white/10 backdrop-blur-lg rounded-xl shadow-lg">
|
||||
|
||||
102
src/lib/components/app/daemon.svelte
Normal file
102
src/lib/components/app/daemon.svelte
Normal file
@@ -0,0 +1,102 @@
|
||||
<script lang="ts">
|
||||
import { state } from "@/state.svelte";
|
||||
import { getGeneralData, getStationSongs } from "@/utils";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
// svelte-ignore non_reactive_update
|
||||
let audioElement: HTMLAudioElement;
|
||||
|
||||
onMount(async () => {
|
||||
const data = await getGeneralData();
|
||||
if (data) {
|
||||
state.presets = data.presets;
|
||||
state.stations = data.stations;
|
||||
state.backgrounds = data.backgrounds;
|
||||
state.atmospheres = data.atmospheres;
|
||||
|
||||
if (data.stations.length > 0 && state.currentStation === null) {
|
||||
state.currentStation = data.stations[0].id;
|
||||
}
|
||||
if (data.backgrounds.length > 0 && state.currentBackgroundId === null) {
|
||||
const firstActiveBg = data.backgrounds.find(bg => bg.isActive && !bg.parentId);
|
||||
state.currentBackgroundId = firstActiveBg ? firstActiveBg.id : (data.backgrounds[0]?.id || null);
|
||||
}
|
||||
} else {
|
||||
state.error = "Failed to load initial data (empty response).";
|
||||
}
|
||||
|
||||
const stationSongs = await getStationSongs(state.currentStation!);
|
||||
if (stationSongs) {
|
||||
state.songQueue = stationSongs;
|
||||
} else {
|
||||
state.error = "Failed to load songs.";
|
||||
}
|
||||
|
||||
if (state.songQueue.length > 0) {
|
||||
state.currentSong = state.songQueue[0];
|
||||
state.duration = state.currentSong.duration;
|
||||
} else {
|
||||
state.error = "No songs available.";
|
||||
}
|
||||
|
||||
const currentTime = new Date().getTime() / 1000;
|
||||
const startTime = new Date(state.currentSong!.startTime).getTime() / 1000;
|
||||
const endTime = new Date(state.currentSong!.endTime).getTime() / 1000;
|
||||
const duration = endTime - startTime;
|
||||
const elapsed = currentTime - startTime;
|
||||
if (elapsed > 0 && elapsed < duration) {
|
||||
state.currentTime = elapsed;
|
||||
} else {
|
||||
state.currentTime = 0;
|
||||
}
|
||||
|
||||
state.isLoading = false;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
console.log('asdf')
|
||||
// Run this effect whenever state.isPlaying or audioElement changes
|
||||
if (!audioElement) return;
|
||||
|
||||
if (state.isPlaying) {
|
||||
audioElement.play().catch(() => {
|
||||
state.error = "Audio playback failed. Please interact with the page first.";
|
||||
state.isPlaying = false;
|
||||
});
|
||||
} else {
|
||||
audioElement.pause();
|
||||
}
|
||||
|
||||
if (state.currentTime > 0) {
|
||||
audioElement.currentTime = state.currentTime;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if !state.hasInteracted}
|
||||
<button class="flex flex-col h-screen w-full items-center justify-center space-y-2 cursor-pointer" onclick={() => {
|
||||
state.hasInteracted = true
|
||||
}}>
|
||||
<p>Click anywhere on the screen</p>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if !state.isLoading}
|
||||
<audio
|
||||
bind:this={audioElement}
|
||||
src={`https://stream.chillhop.com/mp3/${state.currentSong!.fileId}`}
|
||||
autoplay
|
||||
onended={() => {
|
||||
state.currentSong = null;
|
||||
state.currentTime = 0;
|
||||
state.isPlaying = false;
|
||||
}}
|
||||
ontimeupdate={() => {
|
||||
const audio = document.querySelector("audio");
|
||||
if (audio) {
|
||||
state.currentTime = audio.currentTime;
|
||||
}
|
||||
}}
|
||||
class="hidden"
|
||||
></audio>
|
||||
{/if}
|
||||
@@ -1,6 +0,0 @@
|
||||
<script>
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Pause from "@lucide/svelte/icons/pause";
|
||||
|
||||
</script>
|
||||
<Button size="icon"><Pause /></Button>
|
||||
18
src/lib/components/app/now-playing.svelte
Normal file
18
src/lib/components/app/now-playing.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { state } from "@/state.svelte";
|
||||
import Pause from "@lucide/svelte/icons/pause";
|
||||
import Play from "@lucide/svelte/icons/play";
|
||||
|
||||
function togglePlay() {
|
||||
state.isPlaying = !state.isPlaying;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button size="icon" onclick={togglePlay} class="w-10 h-10 *:text-white">
|
||||
{#if state.isPlaying}
|
||||
<Pause />
|
||||
{:else}
|
||||
<Play />
|
||||
{/if}
|
||||
</Button>
|
||||
0
src/lib/components/app/station-dropdown.svelte
Normal file
0
src/lib/components/app/station-dropdown.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChildrenOrChild } from "bits-ui";
|
||||
import Check from "@lucide/svelte/icons/check";
|
||||
import Minus from "@lucide/svelte/icons/minus";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
bind:ref
|
||||
bind:checked
|
||||
bind:indeterminate
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex 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({ checked, indeterminate })}
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if indeterminate}
|
||||
<Minus class="size-4" />
|
||||
{:else}
|
||||
<Check class={cn("size-4", !checked && "text-transparent")} />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ContentProps & {
|
||||
portalProps?: DropdownMenuPrimitive.PortalProps;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Portal {...portalProps}>
|
||||
<DropdownMenuPrimitive.Content
|
||||
bind:ref
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md",
|
||||
"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 outline-none",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.GroupHeadingProps & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.GroupHeading
|
||||
bind:ref
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Item
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { type WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from "bits-ui";
|
||||
import Circle from "@lucide/svelte/icons/circle";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex 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({ checked })}
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if checked}
|
||||
<Circle class="size-2 fill-current" />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
bind:ref
|
||||
class={cn("bg-muted -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { type WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
class={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SubContentProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
bind:ref
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-lg focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from "bits-ui";
|
||||
import ChevronRight from "@lucide/svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<DropdownMenuPrimitive.SubTriggerProps> & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[state=open]:bg-accent flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronRight class="ml-auto" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
50
src/lib/components/ui/dropdown-menu/index.ts
Normal file
50
src/lib/components/ui/dropdown-menu/index.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
|
||||
import Content from "./dropdown-menu-content.svelte";
|
||||
import GroupHeading from "./dropdown-menu-group-heading.svelte";
|
||||
import Item from "./dropdown-menu-item.svelte";
|
||||
import Label from "./dropdown-menu-label.svelte";
|
||||
import RadioItem from "./dropdown-menu-radio-item.svelte";
|
||||
import Separator from "./dropdown-menu-separator.svelte";
|
||||
import Shortcut from "./dropdown-menu-shortcut.svelte";
|
||||
import SubContent from "./dropdown-menu-sub-content.svelte";
|
||||
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
|
||||
|
||||
const Sub = DropdownMenuPrimitive.Sub;
|
||||
const Root = DropdownMenuPrimitive.Root;
|
||||
const Trigger = DropdownMenuPrimitive.Trigger;
|
||||
const Group = DropdownMenuPrimitive.Group;
|
||||
const RadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||
|
||||
export {
|
||||
CheckboxItem,
|
||||
Content,
|
||||
Root as DropdownMenu,
|
||||
CheckboxItem as DropdownMenuCheckboxItem,
|
||||
Content as DropdownMenuContent,
|
||||
Group as DropdownMenuGroup,
|
||||
GroupHeading as DropdownMenuGroupHeading,
|
||||
Item as DropdownMenuItem,
|
||||
Label as DropdownMenuLabel,
|
||||
RadioGroup as DropdownMenuRadioGroup,
|
||||
RadioItem as DropdownMenuRadioItem,
|
||||
Separator as DropdownMenuSeparator,
|
||||
Shortcut as DropdownMenuShortcut,
|
||||
Sub as DropdownMenuSub,
|
||||
SubContent as DropdownMenuSubContent,
|
||||
SubTrigger as DropdownMenuSubTrigger,
|
||||
Trigger as DropdownMenuTrigger,
|
||||
Group,
|
||||
GroupHeading,
|
||||
Item,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioItem,
|
||||
Root,
|
||||
Separator,
|
||||
Shortcut,
|
||||
Sub,
|
||||
SubContent,
|
||||
SubTrigger,
|
||||
Trigger,
|
||||
};
|
||||
22
src/lib/state.svelte.ts
Normal file
22
src/lib/state.svelte.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { Song, Atmosphere, Preset, Station, Background } from "./types"; // Added Preset, Station, Background
|
||||
|
||||
export const state = $state({
|
||||
hasInteracted: false,
|
||||
currentStation: null as number | null,
|
||||
currentSong: null as Song | null,
|
||||
songQueue: [] as Song[],
|
||||
isPlaying: true,
|
||||
volume: 0.5,
|
||||
isMuted: false,
|
||||
currentBackgroundId: null as string | null,
|
||||
activeAtmospheres: {} as Record<string, number>, // { atmosphereId: volume (0-100) }
|
||||
isLoading: true,
|
||||
error: null as string | null,
|
||||
currentTime: 0,
|
||||
duration: 0,
|
||||
|
||||
presets: [] as Preset[],
|
||||
stations: [] as Station[],
|
||||
backgrounds: [] as Background[],
|
||||
atmospheres: [] as Atmosphere[],
|
||||
});
|
||||
77
src/lib/types.ts
Normal file
77
src/lib/types.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
export interface Song {
|
||||
id: number
|
||||
fileId: number
|
||||
artists: string
|
||||
title: string
|
||||
image: string
|
||||
likes: number
|
||||
featured?: string
|
||||
releaseDate: string
|
||||
releaseDateText: string
|
||||
duration: number
|
||||
isrc: string
|
||||
label: string
|
||||
spotifyId: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
}
|
||||
|
||||
export interface Preset {
|
||||
id: number;
|
||||
userId: number;
|
||||
name: string;
|
||||
backgroundId: string;
|
||||
stationId: string; // Represented as string in JSON
|
||||
atmospheres: string; // JSON stringified object
|
||||
sortOrder: number;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface StationMetaSocials {
|
||||
spotify: string | null;
|
||||
apple: string | null;
|
||||
}
|
||||
|
||||
export interface StationMetaIcon {
|
||||
static: string;
|
||||
}
|
||||
|
||||
// Structure within the base64 decoded 'meta' string for stations
|
||||
export interface DecodedStationMeta {
|
||||
shortDescription: string;
|
||||
icon: StationMetaIcon;
|
||||
socials: StationMetaSocials;
|
||||
}
|
||||
|
||||
|
||||
export interface Station {
|
||||
name: string;
|
||||
id: number;
|
||||
meta: string; // Base64 encoded JSON string (DecodedStationMeta)
|
||||
}
|
||||
|
||||
export interface Background {
|
||||
id: string;
|
||||
name: string;
|
||||
parentId: string | null;
|
||||
landscapeUrl: string;
|
||||
portraitUrl: string;
|
||||
thumbnailUrl: string;
|
||||
sortOrder: number;
|
||||
isActive: number; // 0 or 1
|
||||
}
|
||||
|
||||
export interface Atmosphere {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
sortOrder: number;
|
||||
urlMobile: string;
|
||||
}
|
||||
|
||||
export interface ChillhopData {
|
||||
presets: Preset[];
|
||||
stations: Station[];
|
||||
backgrounds: Background[];
|
||||
atmospheres: Atmosphere[];
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import type { ChillhopData, Song } from "./types";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
@@ -7,7 +8,12 @@ export function cn(...inputs: ClassValue[]) {
|
||||
|
||||
export async function getGeneralData() {
|
||||
const res = await fetch('https://stream.chillhop.com/presets')
|
||||
const data = await res.json();
|
||||
const data = await res.json() as ChillhopData;
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getStationSongs(stationId: number) {
|
||||
const res = await fetch(`https://stream.chillhop.com/live/${stationId}`)
|
||||
const data = await res.json() as Song[];
|
||||
return data;
|
||||
}
|
||||
@@ -1,9 +1,29 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import BgImage from "@/components/app/bg-image.svelte";
|
||||
import BottomBar from "@/components/app/bottom-bar.svelte";
|
||||
import Button from "@/components/ui/button/button.svelte";
|
||||
import Daemon from "@/components/app/daemon.svelte";
|
||||
import Spinner from '@lucide/svelte/icons/loader'
|
||||
import { state } from "@/state.svelte";
|
||||
|
||||
</script>
|
||||
|
||||
<BgImage />
|
||||
<BottomBar />
|
||||
<Daemon />
|
||||
|
||||
{#if state.isLoading && !state.hasInteracted}
|
||||
<div class="flex flex-col h-screen w-full items-center justify-center space-y-2">
|
||||
<Spinner class="size-10" />
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
{:else if state.isLoading && state.hasInteracted}
|
||||
<div class="flex flex-col h-screen w-full items-center justify-center space-y-2">
|
||||
<Spinner class="size-10" />
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
{:else if state.error}
|
||||
<div class="flex h-screen w-full items-center justify-center text-red-500">
|
||||
<p>Error: {state.error}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<BottomBar />
|
||||
{/if}
|
||||
@@ -1,4 +1,4 @@
|
||||
import adapter from "@sveltejs/adapter-cloudflare";
|
||||
import adapter from "@sveltejs/adapter-auto";
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
|
||||
@@ -659,6 +659,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz#f518101d1b2e12ce80854f1cd850d3b9fb91d710"
|
||||
integrity sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==
|
||||
|
||||
"@sveltejs/adapter-auto@^6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sveltejs/adapter-auto/-/adapter-auto-6.0.0.tgz#aac1245053c00cb05552b8a3b0ef77643d120f03"
|
||||
integrity sha512-7mR2/G7vlXakaOj6QBSG9dwBfTgWjV+UnEMB5Z6Xu0ZbdXda6c0su1fNkg0ab0zlilSkloMA2NjCna02/DR7sA==
|
||||
dependencies:
|
||||
import-meta-resolve "^4.1.0"
|
||||
|
||||
"@sveltejs/adapter-cloudflare@^5.0.1":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sveltejs/adapter-cloudflare/-/adapter-cloudflare-5.1.0.tgz#c51a05d0af550a85197517f99e79975f8ae557a3"
|
||||
|
||||
Reference in New Issue
Block a user