feat(mobx): add drafts and state context

This commit is contained in:
Paul Makles
2021-12-10 12:53:41 +00:00
parent fcfcd7809d
commit ac2ff24cf6
10 changed files with 285 additions and 14 deletions

16
src/mobx/Persistent.ts Normal file
View File

@@ -0,0 +1,16 @@
/**
* A data store which is persistent and should cache its data locally.
*/
export default interface Persistent<T> {
/**
* Override toJSON to serialise this data store.
* This will also force all subclasses to implement this method.
*/
toJSON(): unknown;
/**
* Hydrate this data store using given data.
* @param data Given data
*/
hydrate(data: T): void;
}

37
src/mobx/State.ts Normal file
View File

@@ -0,0 +1,37 @@
import { makeAutoObservable } from "mobx";
import { createContext } from "preact";
import { useContext } from "preact/hooks";
import Auth from "./stores/Auth";
import Draft from "./stores/Draft";
/**
* Handles global application state.
*/
export default class State {
auth: Auth;
draft: Draft;
/**
* Construct new State.
*/
constructor() {
this.auth = new Auth();
this.draft = new Draft();
makeAutoObservable(this);
}
}
const StateContext = createContext<State>(null!);
export const StateContextProvider = StateContext.Provider;
/**
* Get the application state
* @returns Application state
*/
export function useApplicationState() {
return useContext(StateContext);
}

14
src/mobx/TODO Normal file
View File

@@ -0,0 +1,14 @@
auth
drafts
experiments
last opened
locale
notifications
queue
section toggle
serevr config
settings
sync
themes
trusted links
unreads

6
src/mobx/objectUtil.ts Normal file
View File

@@ -0,0 +1,6 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function deleteKey(object: any, key: string) {
const newObject = { ...object };
delete newObject[key];
return newObject;
}

70
src/mobx/stores/Auth.ts Normal file
View File

@@ -0,0 +1,70 @@
import { makeAutoObservable } from "mobx";
import { Session } from "revolt-api/types/Auth";
import { Nullable } from "revolt.js/dist/util/null";
import Persistent from "../Persistent";
import { deleteKey } from "../objectUtil";
interface Data {
sessions: Record<string, Session>;
current?: string;
}
/**
* Handles account authentication, managing multiple
* accounts and their sessions.
*/
export default class Auth implements Persistent<Data> {
private sessions: Record<string, Session>;
private current: Nullable<string>;
/**
* Construct new Auth store.
*/
constructor() {
this.sessions = {};
this.current = null;
makeAutoObservable(this);
}
// eslint-disable-next-line require-jsdoc
toJSON() {
return {
sessions: this.sessions,
current: this.current,
};
}
// eslint-disable-next-line require-jsdoc
hydrate(data: Data) {
this.sessions = data.sessions;
if (data.current && this.sessions[data.current]) {
this.current = data.current;
}
}
/**
* Add a new session to the auth manager.
* @param session Session
*/
setSession(session: Session) {
this.sessions = {
...this.sessions,
[session.user_id]: session,
};
this.current = session.user_id;
}
/**
* Remove existing session by user ID.
* @param user_id User ID tied to session
*/
removeSession(user_id: string) {
this.sessions = deleteKey(this.sessions, user_id);
if (user_id == this.current) {
this.current = null;
}
}
}

80
src/mobx/stores/Draft.ts Normal file
View File

@@ -0,0 +1,80 @@
import { action, computed, makeAutoObservable, ObservableMap } from "mobx";
import Persistent from "../Persistent";
interface Data {
drafts: Record<string, string>;
}
/**
* Handles storing draft (currently being written) messages.
*/
export default class Draft implements Persistent<Data> {
private drafts: ObservableMap<string, string>;
/**
* Construct new Draft store.
*/
constructor() {
this.drafts = new ObservableMap();
makeAutoObservable(this);
}
// eslint-disable-next-line require-jsdoc
toJSON() {
return {
drafts: this.drafts,
};
}
// eslint-disable-next-line require-jsdoc
@action hydrate(data: Data) {
Object.keys(data.drafts).forEach((key) =>
this.drafts.set(key, data.drafts[key]),
);
}
/**
* Get draft for a channel.
* @param channel Channel ID
*/
@computed get(channel: string) {
return this.drafts.get(channel);
}
/**
* Check whether a channel has a draft.
* @param channel Channel ID
*/
@computed has(channel: string) {
return this.drafts.has(channel) && this.drafts.get(channel)!.length > 0;
}
/**
* Set draft for a channel.
* @param channel Channel ID
* @param content Draft content
*/
@action set(channel: string, content?: string) {
if (typeof content === "undefined") {
return this.clear(channel);
}
this.drafts.set(channel, content);
}
/**
* Clear draft from a channel.
* @param channel Channel ID
*/
@action clear(channel: string) {
this.drafts.delete(channel);
}
/**
* Reset and clear all drafts.
*/
@action reset() {
this.drafts.clear();
}
}