mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-22 07:00:58 -05:00
Add MobX store, create observable User.
This commit is contained in:
parent
781fa5de10
commit
cf3930b094
10 changed files with 247 additions and 61 deletions
|
@ -82,6 +82,8 @@
|
||||||
"markdown-it-sub": "^1.0.0",
|
"markdown-it-sub": "^1.0.0",
|
||||||
"markdown-it-sup": "^1.0.0",
|
"markdown-it-sup": "^1.0.0",
|
||||||
"mediasoup-client": "npm:@insertish/mediasoup-client@3.6.36-esnext",
|
"mediasoup-client": "npm:@insertish/mediasoup-client@3.6.36-esnext",
|
||||||
|
"mobx": "^6.3.2",
|
||||||
|
"mobx-react-lite": "^3.2.0",
|
||||||
"preact-context-menu": "^0.1.5",
|
"preact-context-menu": "^0.1.5",
|
||||||
"preact-i18n": "^2.4.0-preactx",
|
"preact-i18n": "^2.4.0-preactx",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { BrowserRouter as Router } from "react-router-dom";
|
||||||
|
|
||||||
import State from "../redux/State";
|
import State from "../redux/State";
|
||||||
|
|
||||||
|
import MobXState from "../mobx/State";
|
||||||
import { Children } from "../types/Preact";
|
import { Children } from "../types/Preact";
|
||||||
import Locale from "./Locale";
|
import Locale from "./Locale";
|
||||||
import Settings from "./Settings";
|
import Settings from "./Settings";
|
||||||
|
@ -14,6 +15,7 @@ export default function Context({ children }: { children: Children }) {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<State>
|
<State>
|
||||||
|
<MobXState>
|
||||||
<Theme>
|
<Theme>
|
||||||
<Settings>
|
<Settings>
|
||||||
<Locale>
|
<Locale>
|
||||||
|
@ -25,6 +27,7 @@ export default function Context({ children }: { children: Children }) {
|
||||||
</Locale>
|
</Locale>
|
||||||
</Settings>
|
</Settings>
|
||||||
</Theme>
|
</Theme>
|
||||||
|
</MobXState>
|
||||||
</State>
|
</State>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { AuthState } from "../../redux/reducers/auth";
|
||||||
|
|
||||||
import Preloader from "../../components/ui/Preloader";
|
import Preloader from "../../components/ui/Preloader";
|
||||||
|
|
||||||
|
import { useData } from "../../mobx/State";
|
||||||
import { Children } from "../../types/Preact";
|
import { Children } from "../../types/Preact";
|
||||||
import { useIntermediate } from "../intermediate/Intermediate";
|
import { useIntermediate } from "../intermediate/Intermediate";
|
||||||
import { registerEvents, setReconnectDisallowed } from "./events";
|
import { registerEvents, setReconnectDisallowed } from "./events";
|
||||||
|
@ -157,9 +158,10 @@ function Context({ auth, children }: Props) {
|
||||||
};
|
};
|
||||||
}, [client, auth.active]);
|
}, [client, auth.active]);
|
||||||
|
|
||||||
|
const store = useData();
|
||||||
useEffect(
|
useEffect(
|
||||||
() => registerEvents({ operations }, setStatus, client),
|
() => registerEvents({ operations }, setStatus, client, store),
|
||||||
[client],
|
[client, store],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { StateUpdater } from "preact/hooks";
|
||||||
|
|
||||||
import { dispatch } from "../../redux";
|
import { dispatch } from "../../redux";
|
||||||
|
|
||||||
|
import { DataStore } from "../../mobx";
|
||||||
|
import { useData } from "../../mobx/State";
|
||||||
import { ClientOperations, ClientStatus } from "./RevoltClient";
|
import { ClientOperations, ClientStatus } from "./RevoltClient";
|
||||||
|
|
||||||
export var preventReconnect = false;
|
export var preventReconnect = false;
|
||||||
|
@ -18,6 +20,7 @@ export function registerEvents(
|
||||||
{ operations }: { operations: ClientOperations },
|
{ operations }: { operations: ClientOperations },
|
||||||
setStatus: StateUpdater<ClientStatus>,
|
setStatus: StateUpdater<ClientStatus>,
|
||||||
client: Client,
|
client: Client,
|
||||||
|
store: DataStore,
|
||||||
) {
|
) {
|
||||||
function attemptReconnect() {
|
function attemptReconnect() {
|
||||||
if (preventReconnect) return;
|
if (preventReconnect) return;
|
||||||
|
@ -45,6 +48,7 @@ export function registerEvents(
|
||||||
},
|
},
|
||||||
|
|
||||||
packet: (packet: ClientboundNotification) => {
|
packet: (packet: ClientboundNotification) => {
|
||||||
|
store.packet(packet);
|
||||||
switch (packet.type) {
|
switch (packet.type) {
|
||||||
case "ChannelStartTyping": {
|
case "ChannelStartTyping": {
|
||||||
if (packet.user === client.user?._id) return;
|
if (packet.user === client.user?._id) return;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Collection from "revolt.js/dist/maps/Collection";
|
||||||
|
|
||||||
import { useContext, useEffect, useState } from "preact/hooks";
|
import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
//#region Hooks v1
|
//#region Hooks v1 (deprecated)
|
||||||
import { AppContext } from "./RevoltClient";
|
import { AppContext } from "./RevoltClient";
|
||||||
|
|
||||||
export interface HookContext {
|
export interface HookContext {
|
||||||
|
@ -238,7 +238,7 @@ export function useServerPermission(id: string, context?: HookContext) {
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Hooks v2
|
//#region Hooks v2 (deprecated)
|
||||||
type CollectionKeys = Exclude<
|
type CollectionKeys = Exclude<
|
||||||
keyof PickProperties<Client, Collection<any>>,
|
keyof PickProperties<Client, Collection<any>>,
|
||||||
undefined
|
undefined
|
||||||
|
@ -249,7 +249,7 @@ interface Depedency {
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useData<T>(
|
export function useDataDeprecated<T>(
|
||||||
cb: (client: Client) => T,
|
cb: (client: Client) => T,
|
||||||
dependencies: Depedency[],
|
dependencies: Depedency[],
|
||||||
): T {
|
): T {
|
||||||
|
|
26
src/mobx/State.tsx
Normal file
26
src/mobx/State.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { createContext } from "preact";
|
||||||
|
import { useContext } from "preact/hooks";
|
||||||
|
|
||||||
|
import { DataStore } from ".";
|
||||||
|
import { Children } from "../types/Preact";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: Children;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DataContext = createContext<DataStore>(null!);
|
||||||
|
|
||||||
|
// ! later we can do seamless account switching, by hooking this into Redux
|
||||||
|
// ! and monitoring changes to active account and hence swapping stores.
|
||||||
|
// although this may need more work since we need a Client per account too.
|
||||||
|
const store = new DataStore();
|
||||||
|
|
||||||
|
export default function StateLoader(props: Props) {
|
||||||
|
return (
|
||||||
|
<DataContext.Provider value={store}>
|
||||||
|
{props.children}
|
||||||
|
</DataContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useData = () => useContext(DataContext);
|
95
src/mobx/index.ts
Normal file
95
src/mobx/index.ts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import isEqual from "lodash.isequal";
|
||||||
|
import {
|
||||||
|
makeAutoObservable,
|
||||||
|
observable,
|
||||||
|
autorun,
|
||||||
|
runInAction,
|
||||||
|
reaction,
|
||||||
|
makeObservable,
|
||||||
|
action,
|
||||||
|
extendObservable,
|
||||||
|
} from "mobx";
|
||||||
|
import { Attachment, Users } from "revolt.js/dist/api/objects";
|
||||||
|
import { RemoveUserField } from "revolt.js/dist/api/routes";
|
||||||
|
import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
|
||||||
|
|
||||||
|
type Nullable<T> = T | null;
|
||||||
|
function toNullable<T>(data?: T) {
|
||||||
|
return typeof data === "undefined" ? null : data;
|
||||||
|
}
|
||||||
|
|
||||||
|
class User {
|
||||||
|
_id: string;
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
avatar: Nullable<Attachment>;
|
||||||
|
badges: Nullable<number>;
|
||||||
|
status: Nullable<Users.Status>;
|
||||||
|
relationship: Nullable<Users.Relationship>;
|
||||||
|
online: Nullable<boolean>;
|
||||||
|
|
||||||
|
constructor(data: Users.User) {
|
||||||
|
this._id = data._id;
|
||||||
|
this.username = data.username;
|
||||||
|
|
||||||
|
this.avatar = toNullable(data.avatar);
|
||||||
|
this.badges = toNullable(data.badges);
|
||||||
|
this.status = toNullable(data.status);
|
||||||
|
this.relationship = toNullable(data.relationship);
|
||||||
|
this.online = toNullable(data.online);
|
||||||
|
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action update(data: Partial<Users.User>, clear?: RemoveUserField) {
|
||||||
|
const apply = (key: keyof Users.User) => {
|
||||||
|
// This code has been tested.
|
||||||
|
// @ts-expect-error
|
||||||
|
if (data[key] && !isEqual(this[key], data[key])) {
|
||||||
|
// @ts-expect-error
|
||||||
|
this[key] = data[key];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (clear) {
|
||||||
|
case "Avatar":
|
||||||
|
this.avatar = null;
|
||||||
|
break;
|
||||||
|
case "StatusText": {
|
||||||
|
if (this.status) {
|
||||||
|
this.status.text = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply("avatar");
|
||||||
|
apply("badges");
|
||||||
|
apply("status");
|
||||||
|
apply("relationship");
|
||||||
|
apply("online");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DataStore {
|
||||||
|
@observable users = new Map<string, User>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
packet(packet: ClientboundNotification) {
|
||||||
|
switch (packet.type) {
|
||||||
|
case "Ready": {
|
||||||
|
for (let user of packet.users) {
|
||||||
|
this.users.set(user._id, new User(user));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "UserUpdate": {
|
||||||
|
this.users.get(packet.id)?.update(packet.data, packet.clear);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
import { Wrench } from "@styled-icons/boxicons-solid";
|
import { Wrench } from "@styled-icons/boxicons-solid";
|
||||||
|
import { isObservable, isObservableProp } from "mobx";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { Channels } from "revolt.js/dist/api/objects";
|
import { Channels } from "revolt.js/dist/api/objects";
|
||||||
|
|
||||||
import { useContext } from "preact/hooks";
|
import { useContext } from "preact/hooks";
|
||||||
|
@ -7,10 +9,13 @@ import PaintCounter from "../../lib/PaintCounter";
|
||||||
import { TextReact } from "../../lib/i18n";
|
import { TextReact } from "../../lib/i18n";
|
||||||
|
|
||||||
import { AppContext } from "../../context/revoltjs/RevoltClient";
|
import { AppContext } from "../../context/revoltjs/RevoltClient";
|
||||||
import { useData, useUserPermission } from "../../context/revoltjs/hooks";
|
import { useUserPermission } from "../../context/revoltjs/hooks";
|
||||||
|
|
||||||
|
import UserIcon from "../../components/common/user/UserIcon";
|
||||||
import Header from "../../components/ui/Header";
|
import Header from "../../components/ui/Header";
|
||||||
|
|
||||||
|
import { useData } from "../../mobx/State";
|
||||||
|
|
||||||
export default function Developer() {
|
export default function Developer() {
|
||||||
// const voice = useContext(VoiceContext);
|
// const voice = useContext(VoiceContext);
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
|
@ -35,7 +40,10 @@ export default function Developer() {
|
||||||
fields={{ provider: <b>GAMING!</b> }}
|
fields={{ provider: <b>GAMING!</b> }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DataTest />
|
<ObserverTest />
|
||||||
|
<ObserverTest2 />
|
||||||
|
<ObserverTest3 />
|
||||||
|
<ObserverTest4 />
|
||||||
<div style={{ padding: "16px" }}>
|
<div style={{ padding: "16px" }}>
|
||||||
{/*<span>
|
{/*<span>
|
||||||
<b>Voice Status:</b> {VoiceStatus[voice.status]}
|
<b>Voice Status:</b> {VoiceStatus[voice.status]}
|
||||||
|
@ -55,29 +63,66 @@ export default function Developer() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DataTest() {
|
const ObserverTest = observer(() => {
|
||||||
const channel_id = (
|
const client = useContext(AppContext);
|
||||||
useContext(AppContext)
|
const store = useData();
|
||||||
.channels.toArray()
|
|
||||||
.find((x) => x.channel_type === "Group") as Channels.GroupChannel
|
|
||||||
)._id;
|
|
||||||
|
|
||||||
const data = useData(
|
|
||||||
(client) => {
|
|
||||||
return {
|
|
||||||
name: (client.channels.get(channel_id) as Channels.GroupChannel)
|
|
||||||
.name,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[{ key: "channels", id: channel_id }],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: "16px" }}>
|
<div style={{ padding: "16px" }}>
|
||||||
Channel name: {data.name}
|
<p>
|
||||||
<div style={{ width: "24px" }}>
|
username:{" "}
|
||||||
|
{store.users.get(client.user!._id)?.username ?? "no user!"}
|
||||||
<PaintCounter small />
|
<PaintCounter small />
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
const ObserverTest2 = observer(() => {
|
||||||
|
const client = useContext(AppContext);
|
||||||
|
const store = useData();
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "16px" }}>
|
||||||
|
<p>
|
||||||
|
status:{" "}
|
||||||
|
{JSON.stringify(store.users.get(client.user!._id)?.status) ??
|
||||||
|
"none"}
|
||||||
|
<PaintCounter small />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ObserverTest3 = observer(() => {
|
||||||
|
const client = useContext(AppContext);
|
||||||
|
const store = useData();
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "16px" }}>
|
||||||
|
<p>
|
||||||
|
avatar{" "}
|
||||||
|
<UserIcon
|
||||||
|
size={64}
|
||||||
|
attachment={
|
||||||
|
store.users.get(client.user!._id)?.avatar ?? undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<PaintCounter small />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ObserverTest4 = observer(() => {
|
||||||
|
const client = useContext(AppContext);
|
||||||
|
const store = useData();
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "16px" }}>
|
||||||
|
<p>
|
||||||
|
status text:{" "}
|
||||||
|
{JSON.stringify(
|
||||||
|
store.users.get(client.user!._id)?.status?.text,
|
||||||
|
) ?? "none"}
|
||||||
|
<PaintCounter small />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -16,9 +16,8 @@
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"jsxFactory": "h",
|
"jsxFactory": "h",
|
||||||
"jsxFragmentFactory": "Fragment",
|
"jsxFragmentFactory": "Fragment",
|
||||||
"types": [
|
"types": ["vite-plugin-pwa/client"],
|
||||||
"vite-plugin-pwa/client"
|
"experimentalDecorators": true
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": ["src", "ui/ui.tsx"]
|
"include": ["src", "ui/ui.tsx"]
|
||||||
}
|
}
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -3077,6 +3077,16 @@ minimist@^1.2.5:
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||||
|
|
||||||
|
mobx-react-lite@^3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.2.0.tgz#331d7365a6b053378dfe9c087315b4e41c5df69f"
|
||||||
|
integrity sha512-q5+UHIqYCOpBoFm/PElDuOhbcatvTllgRp3M1s+Hp5j0Z6XNgDbgqxawJ0ZAUEyKM8X1zs70PCuhAIzX1f4Q/g==
|
||||||
|
|
||||||
|
mobx@^6.3.2:
|
||||||
|
version "6.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.2.tgz#125590961f702a572c139ab69392bea416d2e51b"
|
||||||
|
integrity sha512-xGPM9dIE1qkK9Nrhevp0gzpsmELKU4MFUJRORW/jqxVFIHHWIoQrjDjL8vkwoJYY3C2CeVJqgvl38hgKTalTWg==
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
|
Loading…
Reference in a new issue