* Big upgrades * WIP: Stash * Stash, 30 flow errors left * Downgrade mobx * WIP * When I understand the difference between class and instance methods * 💚 * Fixes: File import Model saving edge cases pinning and starring docs Collection editing Upgrade mobx devtools * Notification settings saving works * Disabled settings * Document mailer * Working notifications * Colletion created notification Ensure not notified for own actions * Tidy up * Document updated event only for document creation Add indexes Notification setting on user creation * Commentary * Fixed: Notification setting on signup * Fix document move / duplicate stale data Add BaseModel.refresh method * Fixes: Title in sidebar not updated after editing document * 💚 * Improve / restore error handling Better handle offline errors * 👕
163 lines
3.9 KiB
JavaScript
163 lines
3.9 KiB
JavaScript
// @flow
|
|
import invariant from 'invariant';
|
|
import { observable, set, action, computed, runInAction } from 'mobx';
|
|
import { orderBy } from 'lodash';
|
|
import { client } from 'utils/ApiClient';
|
|
import RootStore from 'stores/RootStore';
|
|
import BaseModel from '../models/BaseModel';
|
|
import type { PaginationParams } from 'types';
|
|
|
|
type Action = 'list' | 'info' | 'create' | 'update' | 'delete';
|
|
|
|
function modelNameFromClassName(string) {
|
|
return string.charAt(0).toLowerCase() + string.slice(1);
|
|
}
|
|
|
|
export const DEFAULT_PAGINATION_LIMIT = 25;
|
|
|
|
export default class BaseStore<T: BaseModel> {
|
|
@observable data: Map<string, T> = new Map();
|
|
@observable isFetching: boolean = false;
|
|
@observable isSaving: boolean = false;
|
|
@observable isLoaded: boolean = false;
|
|
|
|
model: Class<T>;
|
|
modelName: string;
|
|
rootStore: RootStore;
|
|
actions: Action[] = ['list', 'info', 'create', 'update', 'delete'];
|
|
|
|
constructor(rootStore: RootStore, model: Class<T>) {
|
|
this.rootStore = rootStore;
|
|
this.model = model;
|
|
this.modelName = modelNameFromClassName(model.name);
|
|
}
|
|
|
|
@action
|
|
clear() {
|
|
this.data.clear();
|
|
}
|
|
|
|
@action
|
|
add = (item: Object): T => {
|
|
const Model = this.model;
|
|
|
|
if (!(item instanceof Model)) {
|
|
const existing: ?T = this.data.get(item.id);
|
|
if (existing) {
|
|
set(existing, item);
|
|
return existing;
|
|
} else {
|
|
item = new Model(item, this);
|
|
}
|
|
}
|
|
|
|
this.data.set(item.id, item);
|
|
return item;
|
|
};
|
|
|
|
@action
|
|
remove(id: string): void {
|
|
this.data.delete(id);
|
|
}
|
|
|
|
save(params: Object) {
|
|
if (params.id) return this.update(params);
|
|
return this.create(params);
|
|
}
|
|
|
|
@action
|
|
async create(params: Object) {
|
|
if (!this.actions.includes('create')) {
|
|
throw new Error(`Cannot create ${this.modelName}`);
|
|
}
|
|
this.isSaving = true;
|
|
|
|
try {
|
|
const res = await client.post(`/${this.modelName}s.create`, params);
|
|
|
|
invariant(res && res.data, 'Data should be available');
|
|
return this.add(res.data);
|
|
} finally {
|
|
this.isSaving = false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
async update(params: Object): * {
|
|
if (!this.actions.includes('update')) {
|
|
throw new Error(`Cannot update ${this.modelName}`);
|
|
}
|
|
this.isSaving = true;
|
|
|
|
try {
|
|
const res = await client.post(`/${this.modelName}s.update`, params);
|
|
|
|
invariant(res && res.data, 'Data should be available');
|
|
return this.add(res.data);
|
|
} finally {
|
|
this.isSaving = false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
async delete(item: T) {
|
|
if (!this.actions.includes('delete')) {
|
|
throw new Error(`Cannot delete ${this.modelName}`);
|
|
}
|
|
this.isSaving = true;
|
|
|
|
try {
|
|
await client.post(`/${this.modelName}s.delete`, { id: item.id });
|
|
return this.remove(item.id);
|
|
} finally {
|
|
this.isSaving = false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
async fetch(id: string, options?: Object = {}): Promise<*> {
|
|
if (!this.actions.includes('info')) {
|
|
throw new Error(`Cannot fetch ${this.modelName}`);
|
|
}
|
|
|
|
let item = this.data.get(id);
|
|
if (item && !options.force) return item;
|
|
|
|
this.isFetching = true;
|
|
|
|
try {
|
|
const res = await client.post(`/${this.modelName}s.info`, { id });
|
|
invariant(res && res.data, 'Data should be available');
|
|
return this.add(res.data);
|
|
} finally {
|
|
this.isFetching = false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
async fetchPage(params: ?PaginationParams): Promise<*> {
|
|
if (!this.actions.includes('list')) {
|
|
throw new Error(`Cannot list ${this.modelName}`);
|
|
}
|
|
this.isFetching = true;
|
|
|
|
try {
|
|
const res = await client.post(`/${this.modelName}s.list`, params);
|
|
|
|
invariant(res && res.data, 'Data not available');
|
|
runInAction(`list#${this.modelName}`, () => {
|
|
res.data.forEach(this.add);
|
|
this.isLoaded = true;
|
|
});
|
|
} finally {
|
|
this.isFetching = false;
|
|
}
|
|
}
|
|
|
|
@computed
|
|
get orderedData(): T[] {
|
|
// $FlowIssue
|
|
return orderBy(Array.from(this.data.values()), 'createdAt', 'desc');
|
|
}
|
|
}
|