import { makeAutoObservable, runInAction } from "mobx"; import type { RuntimeTodo, Todo } from "../types"; import { buildTodoTree, filterTodoTree } from "../utils/todoTree"; import { store } from "./RootStore"; export class RuntimeTodoStore { items: RuntimeTodo[] = []; dirty: boolean = false; private saveTimer: number | undefined; constructor() { makeAutoObservable(this); } public setFromServer(items: Todo[]) { this.items = buildTodoTree(items); } public modifyTodo(item: RuntimeTodo, mutation: Partial) { runInAction(() => { Object.assign(item, mutation); const serverItem = store.server.getItemByUid(item.uid); const keys = Object.keys(mutation) as (keyof Todo)[]; item.dirty = !serverItem || keys.some((key) => item[key] !== serverItem[key]); this.setDirty(); }); } private setDirty(value: boolean = true) { this.dirty = value; if (this.saveTimer) clearTimeout(this.saveTimer); this.saveTimer = setTimeout(this.saveToServer.bind(this), 3000); } public async saveToServer(): Promise { if (this.saveTimer) clearTimeout(this.saveTimer); const dirtyItems = Array.from(filterTodoTree(this.items, (item) => item.dirty)); if (dirtyItems.length === 0) { return; } // Snapshot what we're about to send: if a todo gets edited again // while its save is in flight, the response no longer reflects its // current state, so it must stay dirty (setDirty() already queued a // fresh save for it). const summarySnapshot = new Map(dirtyItems.map((item) => [item.uid, item.summary])); const savedUids = new Set(await store.server.save(dirtyItems)); runInAction(() => { for (const item of dirtyItems) { if (savedUids.has(item.uid) && item.summary === summarySnapshot.get(item.uid)) { item.dirty = false; } } this.dirty = Array.from(filterTodoTree(this.items, (item) => item.dirty)).length > 0; }); } }