stoat-for-desktop/components/state/stores/Layout.ts

198 lines
4.4 KiB
TypeScript

import { paramsFromPathname } from "@revolt/routing";
import { State } from "..";
import { AbstractStore } from ".";
/**
* Static section IDs
*/
export enum LAYOUT_SECTIONS {
PRIMARY_SIDEBAR = "PRIMARY_SIDEBAR",
MEMBER_SIDEBAR = "MEMBER_SIDEBAR",
MENTION_REPLY = "MENTION_REPLY",
MATURE = "nsfw",
}
export interface TypeLayout {
/**
* URL to redirect to after login
*/
nextPath?: string;
/**
* The current section of the program we are in
*
* This can currently either be:
* - home
* - discover
* - a server ID
*/
activeInterface: "home" | "discover" | string;
/**
* Current path within an interface
*/
activePath: Record<TypeLayout["activeInterface"], string>;
/**
* Open (or closed) sections of the UI
*
* Only the contrary is ever stored
*/
openSections: Record<string, boolean>;
}
/**
* Handles layout and navigation of the app.
*/
export class Layout extends AbstractStore<"layout", TypeLayout> {
/**
* Construct store
* @param state State
*/
constructor(state: State) {
super(state, "layout");
}
/**
* Hydrate external context
*/
hydrate(): void {
/** nothing needs to be done */
}
/**
* Generate default values
*/
default(): TypeLayout {
return {
activeInterface: "home",
activePath: {
home: "/",
discover: "/discover/servers",
},
openSections: {},
};
}
/**
* Validate the given data to see if it is compliant and return a compliant object
*/
clean(input: Partial<TypeLayout>): TypeLayout {
const layout: TypeLayout = this.default();
if (typeof input.nextPath === "string") {
layout.nextPath = input.nextPath;
}
if (typeof input.activeInterface === "string") {
layout.activeInterface = input.activeInterface;
}
if (typeof input.activePath === "object") {
for (const interfaceId of Object.keys(input.activePath)) {
if (typeof input.activePath[interfaceId] === "string") {
layout.activePath[interfaceId] = input.activePath[interfaceId];
}
}
}
if (typeof input.openSections === "object") {
for (const section of Object.keys(input.openSections)) {
if (typeof input.openSections[section] === "boolean") {
layout.openSections[section] = input.openSections[section];
}
}
}
return layout;
}
/**
* Pop the next redirect path
*/
popNextPath() {
const nextUrl = this.get().nextPath;
this.set("nextPath", undefined);
return nextUrl;
}
/**
* Get the last active path in the app
*/
getLastActivePath() {
const section = this.get().activeInterface;
return this.get().activePath[section] ?? "/";
}
/**
* Get the last active discover path in the app
*/
getLastActiveDiscoverPath() {
return this.get().activePath["discover"];
}
/**
* Get the last active server path
*/
getLastActiveServerPath(serverId: string) {
return this.get().activePath[serverId] ?? `/server/${serverId}`;
}
/**
* Set the next redirect path
*/
setNextPath(pathname: string) {
this.set("nextPath", pathname);
}
/**
* Set the last active path in the app
*/
setLastActivePath(pathname: string) {
if (pathname.startsWith("/settings") || pathname.startsWith("/invite"))
return;
const params = paramsFromPathname(pathname);
const section = pathname.startsWith("/discover")
? "discover"
: (params.serverId ?? "home");
this.set("activeInterface", section);
this.set("activePath", section, pathname);
}
/**
* Get state of a section
* @param id Section ID
* @param defaultValue Default state value
* @returns Whether the section is open
*/
getSectionState(id: string, defaultValue = false) {
return this.get().openSections[id] ?? defaultValue;
}
/**
* Set the state of a section
* @param id Section ID
* @param value New state value
* @param defaultValue Default state value
*/
setSectionState(id: string, value: boolean, defaultValue = false) {
this.set("openSections", id, value === defaultValue ? undefined! : value);
}
/**
* Toggle state of a section
* @param id Section ID
* @param defaultValue Default state value
*/
toggleSectionState(id: string, defaultValue?: boolean) {
this.setSectionState(
id,
!this.getSectionState(id, defaultValue),
defaultValue,
);
}
}