import orderBy from "lodash/orderBy"; import { autobind, noop } from "./utils"; import { action, computed, observable, when } from "mobx"; export interface ItemObject { getId(): string; getName(): string; } @autobind() export abstract class ItemStore { abstract loadAll(): Promise; protected defaultSorting = (item: T) => item.getName(); @observable isLoading = false; @observable isLoaded = false; @observable items = observable.array([], { deep: false }); @observable selectedItemsIds = observable.map(); @computed get selectedItems(): T[] { return this.items.filter(item => this.selectedItemsIds.get(item.getId())); } getByName(name: string, ...args: any[]): T; getByName(name: string): T { return this.items.find(item => item.getName() === name); } @action protected sortItems(items: T[] = this.items, sorting?: ((item: T) => any)[], order?: "asc" | "desc"): T[] { return orderBy(items, sorting || this.defaultSorting, order); } protected async createItem(...args: any[]): Promise; @action protected async createItem(request: () => Promise) { const newItem = await request(); const item = this.items.find(item => item.getId() === newItem.getId()); if (item) { return item; } else { const items = this.sortItems([...this.items, newItem]); this.items.replace(items); return newItem; } } protected async loadItems(...args: any[]): Promise; @action protected async loadItems(request: () => Promise, sortItems = true) { if (this.isLoading) { await when(() => !this.isLoading); return; } this.isLoading = true; try { let items = await request(); if (sortItems) items = this.sortItems(items); this.items.replace(items); this.isLoaded = true; } finally { this.isLoading = false; } } protected async loadItem(...args: any[]): Promise @action protected async loadItem(request: () => Promise, sortItems = true) { const item = await request().catch(() => null); if (item) { const existingItem = this.items.find(el => el.getId() === item.getId()); if (existingItem) { const index = this.items.findIndex(item => item === existingItem); this.items.splice(index, 1, item); } else { let items = [...this.items, item]; if (sortItems) items = this.sortItems(items); this.items.replace(items); } return item; } } @action protected async updateItem(item: T, request: () => Promise) { const updatedItem = await request(); const index = this.items.findIndex(i => i.getId() === item.getId()); this.items.splice(index, 1, updatedItem); return updatedItem; } @action protected async removeItem(item: T, request: () => Promise) { await request(); this.items.remove(item); this.selectedItemsIds.delete(item.getId()); } isSelected(item: T) { return !!this.selectedItemsIds.get(item.getId()); } @action select(item: T) { this.selectedItemsIds.set(item.getId(), true); } @action unselect(item: T) { this.selectedItemsIds.delete(item.getId()); } @action toggleSelection(item: T) { if (this.isSelected(item)) { this.unselect(item); } else { this.select(item); } } @action toggleSelectionAll(visibleItems: T[] = this.items) { const allSelected = visibleItems.every(this.isSelected); if (allSelected) { visibleItems.forEach(this.unselect); } else { visibleItems.forEach(this.select); } } isSelectedAll(visibleItems: T[] = this.items) { if (!visibleItems.length) return false; return visibleItems.every(this.isSelected); } @action resetSelection() { this.selectedItemsIds.clear(); } @action reset() { this.resetSelection(); this.items.clear(); this.selectedItemsIds.clear(); this.isLoaded = false; this.isLoading = false; } async removeSelectedItems?(): Promise; subscribe(...args: any[]) { return noop; } * [Symbol.iterator]() { yield* this.items; } }