Skip to content

Commit 995cfa3

Browse files
committed
refactor: state management and types
1 parent 9935fe4 commit 995cfa3

File tree

5 files changed

+135
-255
lines changed

5 files changed

+135
-255
lines changed

src/App.svelte

Lines changed: 96 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -23,150 +23,133 @@
2323
import "@ui5/webcomponents-icons/dist/loan.js";
2424
import "@ui5/webcomponents-icons/dist/globe.js";
2525
import TodoList from "./lib/TodoList.svelte";
26-
import { todos, doneTodos } from "./stores/stores";
27-
import type { TodoItemT } from "./types/TodoItem.type";
26+
import { todoStore } from "./stores/stores.svelte";
2827
import Dialog from "@ui5/webcomponents/dist/Dialog.js";
2928
import Header from "./lib/Header.svelte";
29+
import type { ListSelectionChangeEventDetail } from "@ui5/webcomponents/dist/List.js";
30+
import type { DatePickerChangeEventDetail, DatePickerInputEventDetail } from "@ui5/webcomponents/dist/DatePicker.js";
31+
import type { ItemDeleteEvent, ItemEditEvent } from "./lib/todoitem.event";
3032
3133
setTheme("sap_horizon");
3234
33-
const dialogHeaderText: string = "Edit Todo";
34-
3535
// Elements
3636
let dialog = $state<Dialog | null>();
37-
let dialogTextArea = $state();
38-
let dialogDatePicker = $state();
3937
40-
// Create ToDo Fields
41-
let itemInputValue;
42-
let itemDateInputValue;
38+
let createTodoFields = $state({
39+
text: "",
40+
date: "",
41+
});
4342
4443
// Edit Dialog fields
45-
let itemEditText: string = $state("");
46-
let itemEditDate: string = $state("");
47-
let selectedEditItem: number;
48-
49-
// Event Handlers
50-
51-
const handleItemInput = (event) => {
52-
itemInputValue = event.target.value;
44+
type DialogFields = {
45+
id: number | null;
46+
text: string;
47+
date: string;
5348
};
5449
55-
const handleDateInput = (event) => {
56-
itemDateInputValue = event.detail.value;
57-
};
50+
let dialogFields = $state<DialogFields>({
51+
id: null,
52+
text: "",
53+
date: "",
54+
});
5855
59-
const handleAdd = (event) => {
60-
const newTodo: TodoItemT = {
61-
id: $todos.length + 1,
62-
desc: itemInputValue,
63-
deadline: itemDateInputValue,
64-
done: false,
65-
};
66-
todos.update((todos) => [...todos, newTodo]);
67-
};
56+
// Event Handlers
6857
69-
const handleDone = (event) => {
70-
const selectedItem = event.detail.selectedItems[0];
71-
const selectedId = selectedItem.getAttribute("data-key");
58+
const handleItemInput = (event: any) => {
59+
createTodoFields.text = event.target?.value;
60+
};
7261
73-
const newlySelected = $todos.filter((todo) => {
74-
return selectedId === todo.id.toString();
75-
})[0];
62+
const handleDateInput = (event: CustomEvent<DatePickerInputEventDetail>) => (createTodoFields.date = event.detail.value);
7663
77-
newlySelected.done = true;
64+
const handleAdd = () =>
65+
todoStore.add({
66+
desc: createTodoFields.text,
67+
deadline: createTodoFields.date,
68+
});
7869
79-
doneTodos.update((doneTodos) => [...doneTodos, newlySelected]);
70+
const handleToggleDone = (event: CustomEvent<ListSelectionChangeEventDetail>) => {
71+
const { selectedItems, previouslySelectedItems } = event.detail;
8072
81-
todos.update((todos) =>
82-
todos.filter((todo) => {
83-
return selectedId !== todo.id.toString();
84-
}),
85-
);
86-
};
73+
if (selectedItems.length === previouslySelectedItems.length) {
74+
// No change in selection
75+
return;
76+
}
8777
88-
const handleUndone = (event) => {
89-
const selectedItems = event.detail.selectedItems;
90-
const selectedIds = selectedItems.map((item) => item.getAttribute("data-key"));
91-
92-
const newlyDeselected = $doneTodos
93-
.filter((todo) => {
94-
return selectedIds.indexOf(todo.id.toString()) === -1;
95-
})
96-
.map((item) => {
97-
return { ...item, done: false };
98-
});
99-
100-
doneTodos.update((doneTodos) =>
101-
doneTodos.filter((todo) => {
102-
return selectedIds.indexOf(todo.id.toString()) > -1;
103-
}),
104-
);
105-
106-
todos.update((todos) => [...todos, ...newlyDeselected]);
78+
if (selectedItems.length > previouslySelectedItems.length) {
79+
const newlySelectedItems = selectedItems.filter((item) => !previouslySelectedItems.includes(item));
80+
if (newlySelectedItems.length === 0) {
81+
return;
82+
}
83+
const newlySelectedItemId = newlySelectedItems[0]?.getAttribute("data-key");
84+
if (!newlySelectedItemId) {
85+
return;
86+
}
87+
todoStore.toggleDone(Number(newlySelectedItemId));
88+
} else if (selectedItems.length < previouslySelectedItems.length) {
89+
const newlyDeselectedItems = previouslySelectedItems.filter((item) => !selectedItems.includes(item));
90+
if (newlyDeselectedItems.length === 0) {
91+
return;
92+
}
93+
const newlyDeselectedItemId = newlyDeselectedItems[0]?.getAttribute("data-key");
94+
if (!newlyDeselectedItemId) {
95+
return;
96+
}
97+
todoStore.toggleDone(Number(newlyDeselectedItemId));
98+
} else {
99+
console.debug("Selection not changed");
100+
}
107101
};
108102
109-
const handleRemove = (event) => {
110-
const filteredTodos = $todos.filter((todo) => todo.id !== event.detail.id);
111-
todos.set(filteredTodos);
112-
113-
const filteredDoneTodos = $doneTodos.filter((todo) => todo.id !== event.detail.id);
114-
doneTodos.set(filteredDoneTodos);
103+
const handleRemove = (event: CustomEvent<ItemDeleteEvent>) => {
104+
todoStore.remove(event.detail.id);
115105
};
116106
117-
const handleEdit = (event) => {
118-
const matchedTodos = $todos.filter((todo) => todo.id === event.detail.id);
107+
const handleEdit = (event: CustomEvent<ItemEditEvent>) => {
108+
const matchedTodo = todoStore.todos.find((todo) => todo.id === event.detail.id);
119109
120-
let todoObj;
121-
if (matchedTodos.length) {
122-
todoObj = matchedTodos[0];
123-
} else {
124-
todoObj = $doneTodos.filter((todo) => todo.id === event.detail.id)[0];
110+
if (!matchedTodo) {
111+
console.warn(`Todo item with id ${event.detail.id} not found.`);
112+
return;
125113
}
126114
127-
itemEditText = todoObj.desc;
128-
itemEditDate = todoObj.deadline;
129-
selectedEditItem = todoObj.id;
115+
dialogFields = {
116+
id: matchedTodo.id,
117+
text: matchedTodo.desc,
118+
date: matchedTodo.deadline,
119+
};
130120
131121
if (dialog) {
132122
dialog.open = true;
133123
}
134124
};
135125
136126
const saveEdits = () => {
137-
const edittedText = dialogTextArea.value;
138-
const edittedDate = dialogDatePicker.value;
139-
140-
todos.update((todos) =>
141-
todos.map((todo) => {
142-
if (todo.id === selectedEditItem) {
143-
todo.desc = edittedText;
144-
todo.deadline = edittedDate;
145-
}
146-
return todo;
147-
}),
148-
);
149-
150-
doneTodos.update((doneTodos) =>
151-
doneTodos.map((todo) => {
152-
if (todo.id === selectedEditItem) {
153-
todo.desc = edittedText;
154-
todo.deadline = edittedDate;
155-
}
156-
return todo;
157-
}),
158-
);
127+
if (!dialogFields.id) {
128+
console.warn("No valid todo id found for editing.");
129+
return;
130+
}
131+
132+
todoStore.update(dialogFields.id, {
133+
desc: dialogFields.text,
134+
deadline: dialogFields.date,
135+
});
159136
160137
if (dialog) {
138+
dialogFields = { id: null, text: "", date: "" };
161139
dialog.open = false;
162140
}
163141
};
164142
165143
const cancelEdits = () => {
166144
if (dialog) {
145+
dialogFields = { id: null, text: "", date: "" };
167146
dialog.open = false;
168147
}
169148
};
149+
150+
// Derived Stores
151+
const doneTodos = $derived(todoStore.todos.filter((t) => t.done));
152+
const undoneTodos = $derived(todoStore.todos.filter((t) => !t.done));
170153
</script>
171154

172155
<main class="app">
@@ -180,30 +163,35 @@
180163
<div class="create-todo-wrapper">
181164
<ui5-input id="add-input" oninput={handleItemInput} placeholder="Type a task..."></ui5-input>
182165
<ui5-date-picker id="date-picker" oninput={handleDateInput} onchange={handleDateInput} format-pattern="dd/MM/yyyy"></ui5-date-picker>
183-
<ui5-button id="add-btn" onclick={handleAdd} design="Emphasized"> Add Todo </ui5-button>
166+
<ui5-button type="button" id="add-btn" onclick={handleAdd} design="Emphasized">Add Todo</ui5-button>
184167
</div>
185168

186169
<section class="list-todo-wrapper">
187-
<ui5-panel class="list-todos-panel" header-text="Incompleted Tasks" collapsed={!$todos.length || undefined}>
188-
<TodoList items={$todos} on:item-edit={handleEdit} on:item-delete={handleRemove} on:selection-change={handleDone} />
170+
<ui5-panel class="list-todos-panel" header-text="Incompleted Tasks" collapsed={!undoneTodos.length || undefined}>
171+
<TodoList items={undoneTodos} on:item-edit={handleEdit} on:item-delete={handleRemove} on:selection-change={handleToggleDone} />
189172
</ui5-panel>
190173

191-
<ui5-panel class="list-todos-panel" header-text="Completed Tasks" collapsed={!$todos.length || undefined}>
192-
<TodoList items={$doneTodos} on:item-edit={handleEdit} on:item-delete={handleRemove} on:selection-change={handleUndone} />
174+
<ui5-panel class="list-todos-panel" header-text="Completed Tasks" collapsed={!doneTodos.length || undefined}>
175+
<TodoList items={doneTodos} on:item-edit={handleEdit} on:item-delete={handleRemove} on:selection-change={handleToggleDone} />
193176
</ui5-panel>
194177
</section>
195178
</section>
196179

197-
<ui5-dialog bind:this={dialog} header-text={dialogHeaderText}>
180+
<ui5-dialog bind:this={dialog} header-text="Edit Todo">
198181
<div class="dialog-content">
199182
<div class="edit-wrapper">
200183
<ui5-label>Title:</ui5-label>
201-
<ui5-textarea class="title-textarea" show-exceeded-text maxlength="24" bind:this={dialogTextArea} value={itemEditText}></ui5-textarea>
184+
<ui5-textarea class="title-textarea" show-exceeded-text maxlength={24} value={dialogFields.text} onchange={(event: any) => (dialogFields.text = event.target.value)}></ui5-textarea>
202185
</div>
203186

204187
<div class="edit-wrapper date-edit-fields">
205188
<ui5-label>Date:</ui5-label>
206-
<ui5-date-picker bind:this={dialogDatePicker} format-pattern="dd/MM/yyyy" value={itemEditDate}></ui5-date-picker>
189+
<ui5-date-picker
190+
bind:this={dialogFields.date}
191+
format-pattern="dd/MM/yyyy"
192+
value={dialogFields.date}
193+
onchange={(event: CustomEvent<DatePickerChangeEventDetail>) => (dialogFields.date = event.detail.value)}
194+
></ui5-date-picker>
207195
</div>
208196
</div>
209197

@@ -212,87 +200,6 @@
212200
<ui5-button class="dialog-footer-btn--save" design="Emphasized" onclick={saveEdits}>Save</ui5-button>
213201
</div>
214202
</ui5-dialog>
215-
216-
<ui5-popover bind:this={themeSettingsPopover} class="app-bar-theming-popover" placement="Bottom" horizontal-align="End" header-text="Theme">
217-
<ui5-list selection-mode="Single" onselection-change={handleThemeChange}>
218-
<ui5-li icon="palette" data-theme="sap_horizon" selected>SAP Horizon Morning</ui5-li>
219-
<ui5-li icon="palette" data-theme="sap_horizon_dark">SAP Horizon Evening</ui5-li>
220-
<ui5-li icon="palette" data-theme="sap_horizon_hcb">SAP Horizon HCB</ui5-li>
221-
<ui5-li icon="palette" data-theme="sap_horizon_hcw">SAP Horizon HCW</ui5-li>
222-
<ui5-li icon="palette" data-theme="sap_fiori_3">SAP Quartz Light</ui5-li>
223-
<ui5-li icon="palette" data-theme="sap_fiori_3_dark">SAP Quartz Dark</ui5-li>
224-
<ui5-li icon="palette" data-theme="sap_fiori_3_hcb">SAP Quartz HCB</ui5-li>
225-
<ui5-li icon="palette" data-theme="sap_fiori_3_hcw">SAP Quartz HCW</ui5-li>
226-
</ui5-list>
227-
</ui5-popover>
228-
229-
<ui5-popover bind:this={profileSettingsPopover} id="profile-pop" class="app-bar-profile-popover" placement="Bottom" horizontal-align="End">
230-
<div class="profile-settings">
231-
<ui5-avatar size="M" initials="JD"></ui5-avatar>
232-
<div class="profile-text">
233-
<ui5-title level="H3">John Doe</ui5-title>
234-
<ui5-label>Svelte Developer</ui5-label>
235-
</div>
236-
</div>
237-
238-
<div class="profile-settings-list">
239-
<ui5-list selection-mode="Single" separators="None" onitem-click={handleProfileSettingsSelect} bind:this={profileSettingsPopover}>
240-
<ui5-li icon="settings" data-key="settings">Settings</ui5-li>
241-
<ui5-li icon="sys-help" data-key="help">Help</ui5-li>
242-
<ui5-li icon="log" data-key="sign-out">Sign out</ui5-li>
243-
</ui5-list>
244-
</div>
245-
</ui5-popover>
246-
247-
<ui5-dialog bind:this={references.dialog.settings} header-text="Profile Settings" draggable>
248-
<div>
249-
<div class="profile-rtl-switch centered">
250-
<div class="profile-rtl-switch-title">
251-
<ui5-label class="profile-rtl-switch-text">RTL</ui5-label>
252-
</div>
253-
<ui5-switch onchange={handleRtlSwitchChange}></ui5-switch>
254-
</div>
255-
</div>
256-
257-
<div class="profile-rtl-switch centered">
258-
<div class="profile-rtl-switch-title">
259-
<ui5-label class="profile-rtl-switch-text">Compact</ui5-label>
260-
</div>
261-
<ui5-switch onchange={handleContentDensitySwitchChange}></ui5-switch>
262-
</div>
263-
264-
<div class="dialog-button">
265-
<ui5-button onclick={handleSettingsDialogCloseButtonClick} design="Emphasized">Close</ui5-button>
266-
</div>
267-
</ui5-dialog>
268-
269-
<ui5-dialog bind:this={references.dialog.help}>
270-
<div slot="header" class="help-header" id="header-title-align">
271-
<ui5-icon name="sys-help"></ui5-icon>
272-
Help
273-
</div>
274-
275-
<div class="help-header" id="header-logo-align">
276-
<img class="app-header-logo" alt="logo" slot="logo" src={logo} />
277-
<ui5-title level="H5">UI5 Web Components Svelte Sample App</ui5-title>
278-
</div>
279-
280-
<p class="help-dialog-text">
281-
<b>Release</b>: b225.20220729335 <br />
282-
<b>Server</b>: pk21443x3132 <br />
283-
<b>Timestamp</b>: 2022-08-18T10:29:03.159+0200 <br />
284-
<b>Company ID</b>: SAP <br />
285-
<b>UI version</b>: SAP Fiori <br />
286-
<b>Edition</b>: Enterprise <br />
287-
<b>Admin version</b>: Svelte Admin <br />
288-
</p>
289-
<hr />
290-
<span class="help-dialog-text">For more information, please visit our <a href="https://github.com/UI5/sample-webcomponents-svelte" target="_blank">documentation</a>.</span>
291-
<p />
292-
<div class="dialog-button">
293-
<ui5-button design="Emphasized" onclick={handleHelpDialogCloseButtonClick}>Close</ui5-button>
294-
</div>
295-
</ui5-dialog>
296203
</main>
297204

298205
<style scoped>

src/lib/TodoItem.svelte

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import { createEventDispatcher } from "svelte";
33
import "@ui5/webcomponents/dist/ListItemCustom";
44
import type { TodoItemT } from "../types/TodoItem.type";
5+
import type { ItemEditEvent, ItemDeleteEvent } from "./todoitem.event";
56
6-
77
interface Props {
88
// Props
99
item: TodoItemT;
@@ -17,15 +17,17 @@
1717
const dispatcher = createEventDispatcher();
1818
1919
const handleEditPress = () => {
20-
dispatcher("item-edit", {
20+
const event: ItemEditEvent = {
2121
id: item.id,
22-
});
22+
};
23+
dispatcher<string>("item-edit", event);
2324
};
2425
2526
const handleDeletePress = () => {
26-
dispatcher("item-delete", {
27+
const event: ItemDeleteEvent = {
2728
id: item.id,
28-
});
29+
};
30+
dispatcher("item-delete", event);
2931
};
3032
</script>
3133

0 commit comments

Comments
 (0)