Superpowered to do lists. No signup required.

Merge pull request #1 from zeucapua/refactor-store

♻️ refactor local storage store

authored by zeu.dev and committed by GitHub dc733f33 e95e284d

Changed files
+47 -67
src
+20 -52
src/lib/stores.svelte.ts
··· 2 const browser_exists = (typeof window !== "undefined") && (typeof (document) !== "undefined"); 3 const storage = browser_exists ? localStorage : null; 4 5 - // Color Theme (from DaisyUI) 6 - const initial_theme : string = getInitialTheme(); 7 8 - function getInitialTheme() { 9 - const initial_local = storage?.getItem("local_theme"); 10 - if (initial_local) { 11 - console.log("getInitial", initial_local); 12 - return initial_local 13 } 14 - return "default"; 15 - } 16 17 - function localTheme() { 18 - let theme : string = $state(initial_theme); 19 - 20 - function updateTheme(new_theme : string) { 21 if (browser_exists) { 22 - theme = new_theme; 23 - storage?.setItem("local_theme", theme); 24 } 25 } 26 27 return { 28 - get theme() { return theme; }, 29 - updateTheme 30 } 31 } 32 33 - export const color_theme = localTheme(); 34 35 // Task + Todo List 36 export type Task = { ··· 39 is_completed: boolean; 40 } 41 42 - const initial_list : Task[] = getInitialList(); 43 - 44 - function getInitialList() { 45 - const initial_local = storage?.getItem("local_todo_list"); 46 - if (initial_local) { return JSON.parse(initial_local) as Task[]; } 47 - return []; 48 - } 49 - 50 - function localTodoList() { 51 - let tasks : Task[] = $state(initial_list); 52 - 53 - function addTask(new_task : Task) { 54 - tasks.push(new_task); 55 - } 56 - 57 - function removeTask(id : string) { 58 - tasks = tasks.filter((t) => t.id !== id); 59 - } 60 - 61 - function update() { 62 - if (browser_exists) { 63 - storage?.setItem("local_todo_list", JSON.stringify(tasks)); 64 - } 65 - } 66 - 67 - return { 68 - get tasks() { return tasks; }, 69 - addTask, 70 - removeTask, 71 - update 72 - } 73 - } 74 - 75 - export const todo_list = localTodoList();
··· 2 const browser_exists = (typeof window !== "undefined") && (typeof (document) !== "undefined"); 3 const storage = browser_exists ? localStorage : null; 4 5 + // Generalized Local Storage 6 + function persisted<T>(key: string, default_value: T) { 7 + let value : T = $state(default_value); 8 9 + const initial_local = storage?.getItem(key); 10 + if (initial_local) { 11 + try { 12 + value = JSON.parse(initial_local).value as T; 13 + if (!value) { update(default_value); } 14 + } 15 + catch (e) { 16 + update(default_value); 17 + } 18 } 19 20 + function update(new_value : T) { 21 if (browser_exists) { 22 + value = new_value; 23 + storage?.setItem(key, JSON.stringify({ value })); 24 } 25 } 26 27 return { 28 + get value() { return value; }, 29 + update 30 } 31 } 32 33 + // Color Theme (from DaisyUI) 34 + export const color_theme = persisted<string>("local_theme", "default"); 35 36 // Task + Todo List 37 export type Task = { ··· 40 is_completed: boolean; 41 } 42 43 + export const todo_list = persisted<Task[]>("local_todo_list", []);
+2 -2
src/routes/+layout.svelte
··· 43 "sunset", 44 ]; 45 46 - let theme_input = $state(color_theme.theme); 47 - $effect(() => color_theme.updateTheme(theme_input)); 48 </script> 49 50 <svelte:head>
··· 43 "sunset", 44 ]; 45 46 + let theme_input = $state(color_theme.value); 47 + $effect(() => color_theme.update(theme_input)); 48 </script> 49 50 <svelte:head>
+25 -13
src/routes/+page.svelte
··· 1 <script lang="ts"> 2 import { todo_list, type Task } from "$lib/stores.svelte"; 3 4 - let id = 0; 5 let description = $state(""); 6 7 function randomizeId() { 8 let random_id = id; 9 - while (todo_list.tasks.find(t => t.id === `${random_id}`)) { 10 random_id = Math.floor(Math.random() * 100); 11 } 12 id = random_id; ··· 14 15 function addTask() { 16 randomizeId(); 17 - todo_list.addTask({ 18 - id: `${id}`, 19 - description, 20 - is_completed: false 21 - }); 22 description = ""; 23 } 24 25 - $effect(() => todo_list.update()); 26 </script> 27 28 <ul class="flex flex-col gap-8 overflow-y-scroll h-full max-h-[48rem] p-2"> 29 - {#each todo_list.tasks as task : Task} 30 <li class="group flex gap-4 items-center"> 31 <input 32 type="checkbox" ··· 39 class={`text-lg lg:text-xl input lg:input-lg w-full max-w-lg ${task.is_completed && "line-through"}`} 40 /> 41 <button 42 - onclick={() => todo_list.removeTask(task.id)} 43 class="lg:invisible lg:group-hover:visible btn btn-error" 44 > 45 <img ··· 53 54 </ul> 55 56 - <form onsubmit={addTask} class="flex gap-4"> 57 <input 58 type="text" 59 bind:value={description} 60 class="text-lg lg:text-xl input input-bordered lg:input-lg w-full max-w-lg" 61 /> 62 - <button class="btn lg:btn-lg btn-primary">Add</button> 63 - </form> 64
··· 1 <script lang="ts"> 2 import { todo_list, type Task } from "$lib/stores.svelte"; 3 4 + $effect(() => console.log(todo_list)); 5 + 6 + // TODO: get better ID management 7 + let id = $state(0); 8 let description = $state(""); 9 10 function randomizeId() { 11 let random_id = id; 12 + while (todo_list.value.find(t => t.id === `${random_id}`)) { 13 random_id = Math.floor(Math.random() * 100); 14 } 15 id = random_id; ··· 17 18 function addTask() { 19 randomizeId(); 20 + todo_list.update( 21 + [...todo_list.value, { 22 + id: `${id}`, 23 + description, 24 + is_completed: false 25 + }] 26 + ); 27 description = ""; 28 } 29 30 + function removeTask(id: string) { 31 + todo_list.update( 32 + todo_list.value.filter((t) => t.id !== id) 33 + ); 34 + } 35 + 36 + // update when tasks change (description, is_completed) 37 + $effect(() => todo_list.update(todo_list.value)); 38 </script> 39 40 <ul class="flex flex-col gap-8 overflow-y-scroll h-full max-h-[48rem] p-2"> 41 + {#each todo_list.value as task : Task} 42 <li class="group flex gap-4 items-center"> 43 <input 44 type="checkbox" ··· 51 class={`text-lg lg:text-xl input lg:input-lg w-full max-w-lg ${task.is_completed && "line-through"}`} 52 /> 53 <button 54 + onclick={() => removeTask(task.id)} 55 class="lg:invisible lg:group-hover:visible btn btn-error" 56 > 57 <img ··· 65 66 </ul> 67 68 + <section class="flex gap-4"> 69 <input 70 type="text" 71 bind:value={description} 72 class="text-lg lg:text-xl input input-bordered lg:input-lg w-full max-w-lg" 73 /> 74 + <button onclick={addTask} class="btn lg:btn-lg btn-primary">Add</button> 75 + </section> 76