apphouse
Version:
Component library for React that uses observable state management and theme-able components.
220 lines (190 loc) • 5.39 kB
text/typescript
import { makeAutoObservable } from 'mobx';
import { AppLayoutType } from '../models/AppLayout';
import { Route } from './routing/route.interface';
import { omit } from '../utils/obj/omit';
import { keys } from '../utils/obj/keys';
const DEFAULT_APP_NAME = 'Apphouse App';
class PartialView {
id: string;
open: boolean;
updatedAt: number;
constructor(props: { id: string; open: boolean }) {
const { id, open } = props;
this.id = id;
this.open = open;
this.updatedAt = new Date().getTime();
if (open) {
this.updatedAt = new Date().getTime();
}
makeAutoObservable(this);
}
setOpen(open: boolean) {
this.open = open;
this.updatedAt = new Date().getTime();
}
}
export interface IView {
appName?: string;
appLayout?: AppLayoutType;
routes?: Route[];
}
/**
* A model to handle views in the app.
* This will be similar to a route in a traditional web app.
*/
class View {
path: string;
title: string;
updated: number;
tabIndex?: number;
tabId?: string;
sectionId?: string;
params?: { [key: string]: string };
routes: Route[];
/**
* Handles partial views states such as tabs, sections, etc.
* To be able to access them from anywhere in the app.
*/
views: { [key: string]: PartialView };
constructor(props: IView = {}) {
this.title = props.appName || DEFAULT_APP_NAME;
this.path = '/';
this.updated = 0;
this.sectionId = undefined;
this.tabId = undefined;
this.tabIndex = 0;
this.params = {};
this.views = {};
this.routes = props.routes || [];
makeAutoObservable(this);
}
get openedViewsByMostRecent() {
return Object.keys(this.views)
.map((key) => this.views[key])
.filter((view) => view.open)
.sort((a, b) => b.updatedAt - a.updatedAt)
.slice()
.reverse();
}
get currentPath() {
let path = this.path;
const params = omit(this.params, ['path']);
const openParams = keys(params).filter((key) => this.views[key]?.open);
openParams.forEach((key) => {
if (path.includes(`:${key}`) && params && params[key]) {
path = path.replace(`:${key}`, params[key]);
}
});
const mostRecentViewsPath = this.openedViewsByMostRecent.map((v) => v.id);
// We still have a bug here that yields to a wrong path
if (mostRecentViewsPath.length > 1) {
const tail = path.split('/').pop();
const _mostRecent = mostRecentViewsPath.slice();
const mostRecent = _mostRecent.shift();
if (tail === mostRecent) {
return `${path}/${_mostRecent.join('')}`;
}
return `${path}/${mostRecentViewsPath.join('/')}`;
} else {
const mostRecent = mostRecentViewsPath.join('/');
if (path.includes(mostRecent) || path.includes(`:${mostRecent}`)) {
return path;
}
return `${path}/${mostRecent}`;
}
}
get currentParams() {
if (this.updated > 0) {
return this.params || {};
}
return this.params || {};
}
get crumbs() {
return this.path.split('/').filter((crumb) => crumb !== '');
}
open = (path: string, title?: string, onOpen?: () => void) => {
this.setPath(path);
this.setTitle(title || DEFAULT_APP_NAME);
this.setTabId(undefined);
onOpen && onOpen();
};
openTab = (tabId?: string, tabIndex?: number) => {
this.setTabId(tabId);
this.setTabIndex(tabIndex || 0);
};
closeTab = () => {
this.setTabId(undefined);
this.setTabIndex(0);
};
openSection = () => {
this.setSectionId(this.tabId);
};
setParams = (params: { [key: string]: string }): void => {
this.params = params;
};
setRoutes = (routes: Route[]): void => {
this.routes = routes;
};
/**
* Sets a partial view in the app
* @param id string the id of the partial view
* @param open boolean to set the partial view open or closed
*/
setView = (id: string, open: boolean) => {
if (this.views[id]) {
this.views[id].setOpen(open);
} else {
this.views[id] = new PartialView({ id, open });
}
if (!open) {
this.setPath(this.params?.path || '/');
}
};
toggleView = (id: string) => {
if (this.views[id]) {
this.views[id].setOpen(!this.views[id].open);
} else {
this.setView(id, true);
}
};
closeAllOpenViews = () => {
Object.keys(this.views).forEach((key) => {
this.setView(key, false);
});
};
getViewStatus = (id: string): boolean => {
if (this.views[id]) {
return this.views[id].open;
}
return false;
};
deleteAllViews = () => {
this.views = {};
};
closeMostRecentOpenedView = () => {
// Get the most recent opened view
const mostRecentOpenedView = Object.values(this.views)
.filter((v) => v.open)
.sort((a, b) => b.updatedAt - a.updatedAt)[0];
if (mostRecentOpenedView) {
this.setView(mostRecentOpenedView.id, false);
this.setPath(this.params?.path || '/');
}
};
private setSectionId = (sectionId?: string): void => {
this.sectionId = sectionId;
};
private setTabIndex = (index: number) => {
this.tabIndex = index;
};
private setTitle = (title: string) => {
this.title = title;
};
private setTabId = (locationTabId?: string) => {
this.tabId = locationTabId;
};
private setPath = (value: string) => {
this.path = value;
};
}
export default View;