apphouse
Version:
Component library for React that uses observable state management and theme-able components.
225 lines (205 loc) • 5.65 kB
text/typescript
import { makeAutoObservable } from 'mobx';
import { swapItems } from '../utils/list/swapItems';
/**
* Minimum requirements values for an item in the list
*/
export interface IListItem {
/**
* Unique id for the item
*/
id: string;
}
/**
* A model to handle lists of items
*/
export class List<T extends IListItem> {
/**
* Key value pairs of all items in the list
*/
private stack: { [id: string]: T } = {};
/**
* Array of ids only, it keeps track of the order of the items
*/
private order: string[];
/**
* Initializes the list
* The list is initialized empty by default
*/
constructor(list?: T[]) {
// the order of the items is the array of ids
// this needs to be initialized first
this.order = list?.map((item) => item.id) || [];
this.set(list || []);
makeAutoObservable(this);
}
/**
* Get the array of all items in the list, the list will be ordered
* @returns array of items
*/
get values(): T[] {
// note that the item is not find it will return null
return this.order.map((id) => this.stack[id]);
}
/**
* returns the count of items in the list
*/
get length(): number {
return this.order.length;
}
/**
* Update the list with an array of items
* use this method to update the list with an array of items
* this method resets the list with theses items
* use addValues instead if you want to append to the list
* @param values array of items to initialize the list with
*/
private updateValues(values?: T[]) {
values?.forEach((item) => {
this.updateValue(item);
});
}
/**
* Update the list with an item
* If the item already exists it will be updated
* If the item does not exist it will be created
* @param value item to be added/updated in the list
*/
private updateValue(value?: T) {
if (value) {
this.stack[value.id] = value;
if (!this.order.includes(value.id)) {
this.order = [...this.order, value.id];
}
}
}
/**
* Utility method to get the index of an item in the list
* @param id id of the item
* @returns the index of the item or -1 if not found
*/
private getIndex = (id: string): number => {
const list = this.order;
return list.findIndex((item) => item === id);
};
/**
* Get an item from the list by id
* @param id id of the item or the index of the item in the list
* @returns the item or undefined if not found
*/
get = (id: string | number): T | undefined => {
if (typeof id === 'number') {
return this.getByIndex(id);
}
return this.stack[id];
};
/**
* Get an item from the list by index
* @param index the index of the item in the list
* @returns the item or undefined if not found
*/
private getByIndex = (index: number): T | undefined => {
const id = this.order[index];
return this.get(id);
};
/**
* Add an item to the list or update it if it already exists
* @param item item to be added/updated in the list
*/
set = (item: T | T[]) => {
if (typeof item === 'object' && Array.isArray(item)) {
this.updateValues(item);
} else {
this.updateValue(item);
}
};
/**
* Deletes the last item in the list and returns that item
* @returns the last item in the list
*/
pop = (): T | undefined => {
const lastItemId = this.order[this.order.length - 1];
if (lastItemId) {
const lastItem = this.stack[lastItemId];
this.delete(lastItemId);
return lastItem;
}
return undefined;
};
/**
* Deletes the first item in the list and returns that item
* @returns the first item in the list
*/
shift = (): T | undefined => {
const firstItemId = this.order[0];
if (firstItemId) {
const firstItem = this.stack[firstItemId];
this.delete(firstItemId);
return firstItem;
}
return undefined;
};
/**
* Get the last item of the list
* @returns the last item in the list
*/
peakLast = () => {
const lastItemId = this.order[this.order.length - 1];
if (lastItemId) {
return this.stack[lastItemId];
}
return undefined;
};
/**
* Get the first item of the list
* @returns the first item in the list
*/
peakFirst = () => {
const firstItemId = this.order[0];
if (firstItemId) {
return this.stack[firstItemId];
}
return undefined;
};
/**
* Delete an item from the list
* @param id id of the item to be removed
* @returns true if item was removed, false if item was not found
*/
delete = (id: string): boolean => {
// first remove the id from the order
this.order = this.order.filter((itemId) => itemId !== id);
// then delete the item from the stack
if (this.stack[id]) {
delete this.stack[id];
return true;
}
return false;
};
/**
* Swaps two items in the list
* @param index1 index of the first item to be swapped
* @param index2 index of the second item to be swapped
*/
swap = (index1: number, index2: number): void => {
const list = this.order;
const updatedList = swapItems<string>(list, index1, index2);
this.order = updatedList;
};
/**
* Swaps two items in the list by id
* @param id1 the id of the first item to be swapped
* @param id2 the id of the second item to be swapped
*/
swapById = (id1: string, id2: string): void => {
const index1 = this.getIndex(id1);
const index2 = this.getIndex(id2);
this.swap(index1, index2);
};
/**
* clears the list
*/
reset = () => {
this.order = [];
this.stack = {};
};
}