feat: initial todo list implementation

This commit is contained in:
2025-05-16 23:16:29 +02:00
parent ebdc7f87e5
commit 0290256bc1
6 changed files with 116 additions and 33 deletions

View File

@@ -3,6 +3,8 @@
import { getGeneralData, getStationSongs } from '@/utils';
import { onMount } from 'svelte';
import { useIsMobile } from '@/isMobile.svelte';
import Window from '../ui/window/window.svelte';
import TodoList from './todo-list.svelte';
// svelte-ignore non_reactive_update
let audioElement: HTMLAudioElement;
@@ -233,4 +235,16 @@
autoplay
preload="none"
></audio>
{/each}
{/each}
<Window
title="Todo List"
showTitleBar={true}
showCloseButton={true}
width={320}
height={400}
onClose={() => appState.showTodoList = false}
show={appState.showTodoList}
>
<TodoList></TodoList>
</Window>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import { state as appState } from '@/state.svelte';
import Check from '@lucide/svelte/icons/check';
import Button from '../ui/button/button.svelte';
</script>
<div
class="absolute left-2 top-1/2 transform -translate-y-1/2 p-4 bg-white/10 backdrop-blur-lg rounded-xl shadow-lg"
>
<Button size="icon" onclick={() => (appState.showTodoList = !appState.showTodoList)}>
<Check class="size-4" />
</Button>
</div>

View File

@@ -0,0 +1,50 @@
<script lang="ts">
import { state as appState } from '@/state.svelte'
import Button from '../ui/button/button.svelte';
import Plus from '@lucide/svelte/icons/plus';
import X from '@lucide/svelte/icons/x';
import { useIsMobile } from '@/isMobile.svelte';
let newTodoText = '';
let mobile = useIsMobile();
function addTodo() {
if (newTodoText.trim()) {
appState.todoList = [...appState.todoList, newTodoText];
newTodoText = '';
}
}
function removeTodo(index: number) {
appState.todoList = appState.todoList.filter((_, i) => i !== index);
}
</script>
<div class="flex gap-2 mb-6">
<input
bind:value={newTodoText}
placeholder="Add a new task..."
class="flex-1 pl-3 rounded-lg bg-white bg-opacity-10 border border-white border-opacity-20 text-white placeholder-white placeholder-opacity-60 focus:outline-none focus:ring-2 focus:ring-white focus:ring-opacity-40"
on:keypress={(e) => e.key === 'Enter' && addTodo()}
/>
<Button onclick={addTodo} class="bg-white bg-opacity-20 hover:bg-opacity-30 text-white px-4 py-2 rounded-lg" size="icon"><Plus /></Button>
</div>
{#if appState.todoList.length === 0}
<p class="text-white text-opacity-70 text-center py-4">No tasks yet. Add one above!</p>
{:else}
<ul class="space-y-2">
{#each appState.todoList as todo, index}
<li class="flex items-center justify-between p-3 bg-white bg-opacity-10 rounded-lg group hover:bg-opacity-15 transition-all">
<span class="text-white">{todo}</span>
<button
on:click={() => removeTodo(index)}
class="text-white hover:text-red-300 focus:outline-none"
aria-label="Remove todo"
>
<X class="size-5" />
</button>
</li>
{/each}
</ul>
{/if}

View File

@@ -14,6 +14,7 @@
showTitleBar = true,
showCloseButton = true,
onClose = () => {},
show = false,
initialZIndex = 50,
}: {
children?: Snippet;
@@ -25,6 +26,7 @@
showTitleBar?: boolean;
showCloseButton?: boolean;
onClose?: () => void;
show?: boolean;
initialZIndex?: number;
} = $props();
@@ -107,37 +109,36 @@
});
</script>
<div
bind:this={windowRef}
class="fixed flex flex-col bg-white/10 backdrop-blur-md border border-white/20 shadow-lg rounded-lg overflow-hidden"
style="width: {width}px; height: {height}px; left: {x}px; top: {y}px; z-index: {zIndex};"
onmousedown={handleMouseDown}
role="dialog"
tabindex="0"
>
{#if showTitleBar}
<div
bind:this={headerRef}
class="h-8 px-3 flex items-center justify-between bg-black/10 border-b border-white/10 select-none"
style="cursor: {isDragging ? 'grabbing' : 'grab'};"
>
<span class="text-sm font-medium text-white/90">{title}</span>
{#if showCloseButton}
<button
onclick={onClose}
class="w-5 h-5 flex items-center justify-center text-white/70 hover:text-white hover:bg-red-500/50 rounded-sm transition-colors"
aria-label="Close window"
>
<X class="size-4" />
</button>
{/if}
{#if show}
<div
bind:this={windowRef}
class="fixed flex flex-col bg-white/10 backdrop-blur-md border border-white/20 shadow-lg rounded-lg overflow-hidden"
style="width: {width}px; height: {height}px; left: {x}px; top: {y}px; z-index: {zIndex};"
onmousedown={handleMouseDown}
role="dialog"
tabindex="0"
>
{#if showTitleBar}
<div
bind:this={headerRef}
class="h-8 px-3 flex items-center justify-between bg-black/10 border-b border-white/10 select-none"
style="cursor: {isDragging ? 'grabbing' : 'grab'};"
>
<span class="text-sm font-medium text-white/90">{title}</span>
{#if showCloseButton}
<button
onclick={onClose}
class="w-5 h-5 flex items-center justify-center text-white/70 hover:text-white hover:bg-red-500/50 rounded-sm transition-colors"
aria-label="Close window"
>
<X class="size-4" />
</button>
{/if}
</div>
{/if}
<div class="flex-1 p-1 overflow-auto bg-transparent" role="dialog" tabindex="0">
{@render children?.()}
</div>
{/if}
<div class="flex-1 p-1 overflow-auto bg-transparent" role="dialog" tabindex="0">
{@render children?.()}
</div>
</div>
<style>
</style>
{/if}

View File

@@ -23,6 +23,9 @@ export const state = $state({
windowZIndexCounter: 50,
todoList: [] as string[],
showTodoList: false,
// in daemon.svelte
togglePlay: (() => {}) as () => void,
});

View File

@@ -6,6 +6,7 @@
import { state } from '@/state.svelte';
import BackgroundAnalyzer from '@/components/app/bg-analyzer.svelte';
import Title from '@/components/app/title.svelte';
import LeftBar from '@/components/app/left-bar.svelte';
</script>
<BgImage />
@@ -28,5 +29,6 @@
</div>
{:else if state.hasInteracted}
<Title />
<LeftBar />
<BottomBar />
{/if}