|
23 | 23 | import "@ui5/webcomponents-icons/dist/loan.js"; |
24 | 24 | import "@ui5/webcomponents-icons/dist/globe.js"; |
25 | 25 | 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"; |
28 | 27 | import Dialog from "@ui5/webcomponents/dist/Dialog.js"; |
29 | 28 | 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 { BaseTextArea } from "@ui5/webcomponents/dist/TextArea.js"; |
| 32 | + import type { ItemDeleteEvent, ItemEditEvent } from "./lib/todoitem.event"; |
30 | 33 |
|
31 | 34 | setTheme("sap_horizon"); |
32 | 35 |
|
33 | | - const dialogHeaderText: string = "Edit Todo"; |
34 | | -
|
35 | 36 | // Elements |
36 | 37 | let dialog = $state<Dialog | null>(); |
37 | | - let dialogTextArea = $state(); |
38 | | - let dialogDatePicker = $state(); |
39 | 38 |
|
40 | | - // Create ToDo Fields |
41 | | - let itemInputValue; |
42 | | - let itemDateInputValue; |
| 39 | + let createTodoFields = $state({ |
| 40 | + text: "", |
| 41 | + date: "", |
| 42 | + }); |
43 | 43 |
|
44 | 44 | // 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; |
| 45 | + type DialogFields = { |
| 46 | + id: number | null; |
| 47 | + text: string; |
| 48 | + date: string; |
53 | 49 | }; |
54 | 50 |
|
55 | | - const handleDateInput = (event) => { |
56 | | - itemDateInputValue = event.detail.value; |
57 | | - }; |
| 51 | + let dialogFields = $state<DialogFields>({ |
| 52 | + id: null, |
| 53 | + text: "", |
| 54 | + date: "", |
| 55 | + }); |
58 | 56 |
|
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 | | - }; |
| 57 | + // Event Handlers |
68 | 58 |
|
69 | | - const handleDone = (event) => { |
70 | | - const selectedItem = event.detail.selectedItems[0]; |
71 | | - const selectedId = selectedItem.getAttribute("data-key"); |
| 59 | + const handleItemInput = (event: any) => { |
| 60 | + console.log(event.target.value); |
| 61 | + createTodoFields.text = event.target?.value; |
| 62 | + }; |
72 | 63 |
|
73 | | - const newlySelected = $todos.filter((todo) => { |
74 | | - return selectedId === todo.id.toString(); |
75 | | - })[0]; |
| 64 | + const handleDateInput = (event: CustomEvent<DatePickerInputEventDetail>) => (createTodoFields.date = event.detail.value); |
76 | 65 |
|
77 | | - newlySelected.done = true; |
| 66 | + const handleAdd = () => |
| 67 | + todoStore.add({ |
| 68 | + desc: createTodoFields.text, |
| 69 | + deadline: createTodoFields.date, |
| 70 | + }); |
78 | 71 |
|
79 | | - doneTodos.update((doneTodos) => [...doneTodos, newlySelected]); |
| 72 | + const handleToggleDone = (event: CustomEvent<ListSelectionChangeEventDetail>) => { |
| 73 | + const { selectedItems, previouslySelectedItems } = event.detail; |
80 | 74 |
|
81 | | - todos.update((todos) => |
82 | | - todos.filter((todo) => { |
83 | | - return selectedId !== todo.id.toString(); |
84 | | - }), |
85 | | - ); |
86 | | - }; |
| 75 | + if (selectedItems.length === previouslySelectedItems.length) { |
| 76 | + // No change in selection |
| 77 | + return; |
| 78 | + } |
87 | 79 |
|
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]); |
| 80 | + if (selectedItems.length > previouslySelectedItems.length) { |
| 81 | + console.debug("An item was selected"); |
| 82 | + const newlySelectedItems = selectedItems.filter((item) => !previouslySelectedItems.includes(item)); |
| 83 | + if (newlySelectedItems.length === 0) { |
| 84 | + return; |
| 85 | + } |
| 86 | + const newlySelectedItemId = newlySelectedItems[0]?.getAttribute("data-key"); |
| 87 | + if (!newlySelectedItemId) { |
| 88 | + return; |
| 89 | + } |
| 90 | + todoStore.toggleDone(Number(newlySelectedItemId)); |
| 91 | + } else if (selectedItems.length < previouslySelectedItems.length) { |
| 92 | + console.debug("An item was deselected"); |
| 93 | +
|
| 94 | + const newlyDeselectedItems = previouslySelectedItems.filter((item) => !selectedItems.includes(item)); |
| 95 | + if (newlyDeselectedItems.length === 0) { |
| 96 | + return; |
| 97 | + } |
| 98 | + const newlyDeselectedItemId = newlyDeselectedItems[0]?.getAttribute("data-key"); |
| 99 | + if (!newlyDeselectedItemId) { |
| 100 | + return; |
| 101 | + } |
| 102 | + todoStore.toggleDone(Number(newlyDeselectedItemId)); |
| 103 | + } else { |
| 104 | + console.debug("Selection not changed"); |
| 105 | + } |
107 | 106 | }; |
108 | 107 |
|
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); |
| 108 | + const handleRemove = (event: CustomEvent<ItemDeleteEvent>) => { |
| 109 | + todoStore.remove(event.detail.id); |
115 | 110 | }; |
116 | 111 |
|
117 | | - const handleEdit = (event) => { |
118 | | - const matchedTodos = $todos.filter((todo) => todo.id === event.detail.id); |
| 112 | + const handleEdit = (event: CustomEvent<ItemEditEvent>) => { |
| 113 | + const matchedTodo = todoStore.todos.find((todo) => todo.id === event.detail.id); |
119 | 114 |
|
120 | | - let todoObj; |
121 | | - if (matchedTodos.length) { |
122 | | - todoObj = matchedTodos[0]; |
123 | | - } else { |
124 | | - todoObj = $doneTodos.filter((todo) => todo.id === event.detail.id)[0]; |
| 115 | + if (!matchedTodo) { |
| 116 | + console.warn(`Todo item with id ${event.detail.id} not found.`); |
| 117 | + return; |
125 | 118 | } |
126 | 119 |
|
127 | | - itemEditText = todoObj.desc; |
128 | | - itemEditDate = todoObj.deadline; |
129 | | - selectedEditItem = todoObj.id; |
| 120 | + dialogFields = { |
| 121 | + id: matchedTodo.id, |
| 122 | + text: matchedTodo.desc, |
| 123 | + date: matchedTodo.deadline, |
| 124 | + }; |
130 | 125 |
|
131 | 126 | if (dialog) { |
132 | 127 | dialog.open = true; |
133 | 128 | } |
134 | 129 | }; |
135 | 130 |
|
136 | 131 | 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 | | - ); |
| 132 | + if (!dialogFields.id) { |
| 133 | + console.warn("No valid todo id found for editing."); |
| 134 | + return; |
| 135 | + } |
| 136 | +
|
| 137 | + todoStore.update(dialogFields.id, { |
| 138 | + desc: dialogFields.text, |
| 139 | + deadline: dialogFields.date, |
| 140 | + }); |
159 | 141 |
|
160 | 142 | if (dialog) { |
| 143 | + dialogFields = { id: null, text: "", date: "" }; |
161 | 144 | dialog.open = false; |
162 | 145 | } |
163 | 146 | }; |
164 | 147 |
|
165 | 148 | const cancelEdits = () => { |
166 | 149 | if (dialog) { |
| 150 | + dialogFields = { id: null, text: "", date: "" }; |
167 | 151 | dialog.open = false; |
168 | 152 | } |
169 | 153 | }; |
| 154 | +
|
| 155 | + // Derived Stores |
| 156 | + const doneTodos = $derived(todoStore.todos.filter((t) => t.done)); |
| 157 | + const undoneTodos = $derived(todoStore.todos.filter((t) => !t.done)); |
170 | 158 | </script> |
171 | 159 |
|
172 | 160 | <main class="app"> |
|
180 | 168 | <div class="create-todo-wrapper"> |
181 | 169 | <ui5-input id="add-input" oninput={handleItemInput} placeholder="Type a task..."></ui5-input> |
182 | 170 | <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> |
| 171 | + <ui5-button type="button" id="add-btn" onclick={handleAdd} design="Emphasized">Add Todo</ui5-button> |
184 | 172 | </div> |
185 | 173 |
|
186 | 174 | <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} /> |
| 175 | + <ui5-panel class="list-todos-panel" header-text="Incompleted Tasks" collapsed={!undoneTodos.length || undefined}> |
| 176 | + <TodoList items={undoneTodos} on:item-edit={handleEdit} on:item-delete={handleRemove} on:selection-change={handleToggleDone} /> |
189 | 177 | </ui5-panel> |
190 | 178 |
|
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} /> |
| 179 | + <ui5-panel class="list-todos-panel" header-text="Completed Tasks" collapsed={!doneTodos.length || undefined}> |
| 180 | + <TodoList items={doneTodos} on:item-edit={handleEdit} on:item-delete={handleRemove} on:selection-change={handleToggleDone} /> |
193 | 181 | </ui5-panel> |
194 | 182 | </section> |
195 | 183 | </section> |
196 | 184 |
|
197 | | - <ui5-dialog bind:this={dialog} header-text={dialogHeaderText}> |
| 185 | + <ui5-dialog bind:this={dialog} header-text="Edit Todo"> |
198 | 186 | <div class="dialog-content"> |
199 | 187 | <div class="edit-wrapper"> |
200 | 188 | <ui5-label>Title:</ui5-label> |
201 | | - <ui5-textarea class="title-textarea" show-exceeded-text maxlength="24" bind:this={dialogTextArea} value={itemEditText}></ui5-textarea> |
| 189 | + <ui5-textarea class="title-textarea" show-exceeded-text maxlength={24} value={dialogFields.text} onchange={(event: any) => (dialogFields.text = event.target.value)}></ui5-textarea> |
202 | 190 | </div> |
203 | 191 |
|
204 | 192 | <div class="edit-wrapper date-edit-fields"> |
205 | 193 | <ui5-label>Date:</ui5-label> |
206 | | - <ui5-date-picker bind:this={dialogDatePicker} format-pattern="dd/MM/yyyy" value={itemEditDate}></ui5-date-picker> |
| 194 | + <ui5-date-picker |
| 195 | + bind:this={dialogFields.date} |
| 196 | + format-pattern="dd/MM/yyyy" |
| 197 | + value={dialogFields.date} |
| 198 | + onchange={(event: CustomEvent<DatePickerChangeEventDetail>) => (dialogFields.date = event.detail.value)} |
| 199 | + ></ui5-date-picker> |
207 | 200 | </div> |
208 | 201 | </div> |
209 | 202 |
|
|
212 | 205 | <ui5-button class="dialog-footer-btn--save" design="Emphasized" onclick={saveEdits}>Save</ui5-button> |
213 | 206 | </div> |
214 | 207 | </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> |
296 | 208 | </main> |
297 | 209 |
|
298 | 210 | <style scoped> |
|
0 commit comments