Skip to content

Commit fd1ce68

Browse files
committed
Implement toggle all and clear completed
1 parent 5cc58b2 commit fd1ce68

File tree

2 files changed

+107
-47
lines changed

2 files changed

+107
-47
lines changed

apps/tree-todo-list/src/model.ts

Lines changed: 96 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
import { combine, createEvent, createStore, sample } from 'effector';
2-
import { keyval, lazy, KeyvalWithState } from '@effector/model';
1+
import {
2+
combine,
3+
createEvent,
4+
createStore,
5+
type EventCallable,
6+
sample,
7+
} from 'effector';
8+
import { KeyOrKeys, keyval, lazy, type Keyval } from '@effector/model';
39
import { createAction } from 'effector-action';
410

511
type InputTodo = {
@@ -49,9 +55,11 @@ export const todoList = keyval(() => {
4955
}
5056
});
5157

52-
const childsList = keyval(todoList) as KeyvalWithState<
58+
const childsList = keyval(todoList) as Keyval<
5359
TodoInputShape,
54-
TodoShape
60+
TodoShape,
61+
{ removeCompleted: EventCallable<void>; toggleAll: EventCallable<void> },
62+
any
5563
>;
5664

5765
const $subtasksTotal = lazy(() => {
@@ -106,20 +114,14 @@ export const todoList = keyval(() => {
106114
target: $completed,
107115
});
108116

109-
const [addSubtask] = lazy(['event'], () => [
110-
createAction({
111-
source: $todoDraft,
112-
target: { todoDraft: $todoDraft, add: childsList.edit.add },
113-
fn(target, todoDraft) {
114-
target.add({
115-
id: createID(),
116-
title: todoDraft,
117-
subtasks: [],
118-
});
119-
target.todoDraft.reinit();
120-
},
121-
}),
122-
]);
117+
const [addSubtask, removeCompleted, toggleAll] = lazy(
118+
['event', 'event', 'event'],
119+
() => [
120+
createAddTodo(childsList),
121+
createRemoveCompleted(childsList),
122+
createToggleAll(childsList),
123+
],
124+
);
123125

124126
return {
125127
key: 'id',
@@ -139,11 +141,86 @@ export const todoList = keyval(() => {
139141
editMode,
140142
toggleCompleted,
141143
addSubtask,
144+
removeCompleted,
145+
toggleAll,
142146
},
143147
optional: ['completed', 'editing', 'titleDraft'],
144148
};
145149
});
146150

151+
function createRemoveCompleted(
152+
todoList: Keyval<
153+
any,
154+
TodoShape,
155+
{ removeCompleted: EventCallable<void> },
156+
any
157+
>,
158+
) {
159+
return createAction({
160+
source: todoList.$keys,
161+
target: {
162+
removeCompletedNestedChilds: todoList.api.removeCompleted,
163+
/** effector-action messing with function payloads so we need to wrap data to pass thru it */
164+
removeItems: todoList.edit.remove.prepend<{
165+
fn: (entity: TodoShape) => boolean;
166+
}>(({ fn }) => fn),
167+
},
168+
fn(target, childKeys) {
169+
target.removeCompletedNestedChilds({
170+
key: childKeys,
171+
data: Array.from(childKeys, () => undefined),
172+
});
173+
target.removeItems({
174+
fn: ({ completed }) => completed,
175+
});
176+
},
177+
});
178+
}
179+
180+
function createToggleAll(
181+
todoList: Keyval<any, TodoShape, { toggleAll: EventCallable<void> }, any>,
182+
) {
183+
return createAction({
184+
source: todoList.$keys,
185+
target: {
186+
toggleAllNestedChilds: todoList.api.toggleAll,
187+
mapItems: todoList.edit.map,
188+
},
189+
fn(target, childKeys) {
190+
target.toggleAllNestedChilds({
191+
key: childKeys,
192+
data: Array.from(childKeys, () => undefined),
193+
});
194+
target.mapItems({
195+
keys: childKeys,
196+
map: ({ completed }) => ({ completed: !completed }),
197+
});
198+
},
199+
});
200+
}
201+
202+
function createAddTodo(todoList: Keyval<any, TodoShape, any, any>) {
203+
return createAction({
204+
source: $todoDraft,
205+
target: {
206+
add: todoList.edit.add,
207+
todoDraft: $todoDraft,
208+
},
209+
fn(target, todoDraft) {
210+
if (!todoDraft.trim()) return;
211+
target.add({
212+
id: createID(),
213+
title: todoDraft.trim(),
214+
subtasks: [],
215+
});
216+
target.todoDraft.reinit();
217+
},
218+
});
219+
}
220+
221+
export const removeCompleted = createRemoveCompleted(todoList);
222+
export const toggleAll = createToggleAll(todoList);
223+
147224
export const $totalSize = combine(todoList.$items, (items) => {
148225
return items.reduce((acc, { subtasksTotal }) => acc + 1 + subtasksTotal, 0);
149226
});
@@ -156,22 +233,7 @@ function createID() {
156233
return `id-${Math.random().toString(36).slice(2, 10)}`;
157234
}
158235

159-
export const addTodo = createAction({
160-
source: $todoDraft,
161-
target: {
162-
add: todoList.edit.add,
163-
todoDraft: $todoDraft,
164-
},
165-
fn(target, todoDraft) {
166-
if (!todoDraft.trim()) return;
167-
target.add({
168-
id: createID(),
169-
title: todoDraft.trim(),
170-
subtasks: [],
171-
});
172-
target.todoDraft.reinit();
173-
},
174-
});
236+
export const addTodo = createAddTodo(todoList);
175237

176238
function addIds(inputs: InputTodo[]): TodoInputShape[] {
177239
return inputs.map(({ title, subtasks = [] }) => ({

apps/tree-todo-list/src/view/App.tsx

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@ import type { KeyboardEvent, ChangeEvent } from 'react';
22
import { useUnit } from 'effector-react';
33
import { useEntityList } from '@effector/model-react';
44

5-
import { todoList, $todoDraft, editDraft, addTodo } from '../model';
5+
import {
6+
todoList,
7+
$todoDraft,
8+
editDraft,
9+
addTodo,
10+
removeCompleted,
11+
toggleAll,
12+
} from '../model';
613
import { TodoCount } from './TodoCount';
714
import { TodoFilters } from './TodoFilters';
815
import { TodoItem } from './TodoItem';
916

1017
import './main.css';
1118

1219
export const App = () => {
13-
const [todoDraft, onDraftChange, saveDraft] = useUnit([
14-
$todoDraft,
15-
editDraft,
16-
addTodo,
17-
]);
20+
const [todoDraft, onDraftChange, saveDraft, onRemoveCompleted, onToggleAll] =
21+
useUnit([$todoDraft, editDraft, addTodo, removeCompleted, toggleAll]);
1822
const onChangeDraft = (e: ChangeEvent<HTMLInputElement>) => {
1923
onDraftChange(e.target.value);
2024
};
@@ -23,12 +27,6 @@ export const App = () => {
2327
e.preventDefault();
2428
saveDraft();
2529
};
26-
const onToggleAll = (e: ChangeEvent<HTMLInputElement>) => {
27-
// toggleAll(e.currentTarget.checked);
28-
};
29-
const onClearCompleted = () => {
30-
// clearCompleted();
31-
};
3230
return (
3331
<section className="todoapp">
3432
<div>
@@ -60,7 +58,7 @@ export const App = () => {
6058
<button
6159
type="button"
6260
className="clear-completed"
63-
onClick={onClearCompleted}
61+
onClick={onRemoveCompleted}
6462
>
6563
Clear completed
6664
</button>

0 commit comments

Comments
 (0)