mirror of https://github.com/vizality/vizality
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
7.4 KiB
205 lines
7.4 KiB
/**
|
|
*
|
|
*/
|
|
|
|
import { findInReactTree, findInTree, getOwnerInstance } from '@vizality/util/react';
|
|
import { getModule, getModuleByDisplayName } from '@vizality/webpack';
|
|
import { waitForElement, joinClassNames } from '@vizality/util/dom';
|
|
import { patch, unpatch } from '@vizality/patcher';
|
|
import { useForceUpdate } from '@vizality/hooks';
|
|
import { isArray } from '@vizality/util/array';
|
|
import { Events } from '@vizality/constants';
|
|
import { Builtin } from '@vizality/entities';
|
|
import React, { useEffect } from 'react';
|
|
|
|
export default class Router extends Builtin {
|
|
/**
|
|
* Starts up the builtin.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async start () {
|
|
await this.patchRouter();
|
|
await this.patchViews();
|
|
await this.patchSidebar();
|
|
await this.forceRouterUpdate();
|
|
$vz.api.routes.on(Events.VIZALITY_ROUTE_ADD, this.forceRouterUpdate);
|
|
$vz.api.routes.on('routeRemove', this.forceRouterUpdate);
|
|
}
|
|
|
|
/**
|
|
* Shuts down the builtin.
|
|
* @returns {void}
|
|
*/
|
|
stop () {
|
|
$vz.api.routes.off(Events.VIZALITY_ROUTE_ADD, this.forceRouterUpdate);
|
|
$vz.api.routes.off('routeRemove', this.forceRouterUpdate);
|
|
unpatch('vz-router-routes');
|
|
unpatch('vz-router-views');
|
|
unpatch('vz-router-sidebar');
|
|
this.forceRouterUpdate();
|
|
}
|
|
|
|
/**
|
|
* Patches the app's route renderer.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async patchRouter () {
|
|
const { container } = await getModule('container', 'downloadProgressCircle', true);
|
|
const RouteRenderer = getOwnerInstance(await waitForElement(`.${container}`));
|
|
patch('vz-router-routes', RouteRenderer?.props?.children[0], 'type', (_, res) => {
|
|
try {
|
|
const { children } = findInReactTree(res, m => isArray(m.children) && m.children.length > 5);
|
|
children.push(
|
|
...$vz.api.routes.getAllRoutes()?.map(route => ({
|
|
...children[0],
|
|
props: {
|
|
/**
|
|
* Not exactly sure what this property is, but Discord added it around
|
|
* March of 2021 and sounds like something we would want to have set to true.
|
|
*/
|
|
disableTrack: true,
|
|
render: () => {
|
|
const Render = route.render;
|
|
return <Render />;
|
|
},
|
|
path: `/vizality${route.path}`
|
|
}
|
|
}))
|
|
);
|
|
} catch (err) {
|
|
return this.error('There was a problem patching the routes!', err);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Patches the app's views.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async patchViews () {
|
|
const Views = await getModuleByDisplayName('ViewsWithMainInterface', true);
|
|
patch('vz-router-views', Views?.prototype, 'render', (_, res) => {
|
|
try {
|
|
const routes = findInTree(res, n => isArray(n) && n[0]?.key && n[0].props?.path && n[0].props.render);
|
|
if (!isArray(routes)) {
|
|
return;
|
|
}
|
|
routes[routes.length - 2].props.path = [
|
|
...new Set(
|
|
routes[routes.length - 2]?.props?.path?.concat(
|
|
$vz.api.routes.getAllRoutes()?.map(route => `/vizality${route.path}`)
|
|
)
|
|
)
|
|
];
|
|
} catch (err) {
|
|
return this.error('There was a problem patching the views!', err);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Patches Discord's channel sidebar so that we can decide to render a custom one or
|
|
* none at all when registering new routes.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async patchSidebar () {
|
|
try {
|
|
const { panels } = await getModule('panels', true);
|
|
const instance = getOwnerInstance(await waitForElement(`.${panels}`));
|
|
const Routes = await getModule('handleRouteChange', true);
|
|
patch('vz-router-sidebar', instance?.props?.children[0], 'type', (_, res) => {
|
|
try {
|
|
const forceUpdate = useForceUpdate();
|
|
/**
|
|
* Handles collapsing/expanding the dashboard sidebar.
|
|
* @param {string} setting Setting key
|
|
*/
|
|
const handleSidebarCollapse = setting => {
|
|
/**
|
|
* If it's the setting we're looking for, rerender the sidebar.
|
|
*/
|
|
if (setting === 'dashboardSidebarCollapse') {
|
|
forceUpdate();
|
|
}
|
|
};
|
|
/**
|
|
* Add listeners for route changes and addon settings toggle actions.
|
|
*/
|
|
useEffect(() => {
|
|
$vz.api.settings
|
|
.on(Events.VIZALITY_SETTING_UPDATE, handleSidebarCollapse);
|
|
Routes.listeners.add(forceUpdate);
|
|
/**
|
|
* Remove the listeners on unmount.
|
|
*/
|
|
return () => {
|
|
$vz.api.settings
|
|
.removeListener(Events.VIZALITY_SETTING_UPDATE, handleSidebarCollapse);
|
|
Routes.listeners.delete(forceUpdate);
|
|
};
|
|
}, []);
|
|
const content = findInReactTree(res, n => n?.className?.startsWith('content-'));
|
|
const child = content?.children[0];
|
|
if (child?.type?.displayName === 'ChannelSidebar') {
|
|
const oType = child.type;
|
|
child.type = props => {
|
|
try {
|
|
let ret = Reflect.apply(oType, null, [ props ]);
|
|
if ($vz.api.routes.getLocation()?.pathname?.startsWith('/vizality')) {
|
|
/**
|
|
* Add and remove collapsed and expanded attributes based on the setting.
|
|
*/
|
|
ret.props.className = joinClassNames('vz-dashboard-sidebar', ret.props.className);
|
|
ret.props['vz-collapsed'] = $vz.settings.get('dashboardSidebarCollapse', false) && '';
|
|
ret.props['vz-expanded'] = !$vz.settings.get('dashboardSidebarCollapse', false) && '';
|
|
const rawPath = $vz.api.routes.getLocation().pathname.substring('vizality/'.length);
|
|
const route = $vz.api.routes.getAllRoutes()?.find(rte => rawPath.startsWith(rte.path));
|
|
if (route && route.sidebar) {
|
|
const Sidebar = route.sidebar;
|
|
ret.props.children[0] = <Sidebar />;
|
|
/**
|
|
* If the sidebar property is false, don't render one.
|
|
*/
|
|
} else {
|
|
ret = null;
|
|
}
|
|
}
|
|
return ret;
|
|
} catch (err) {
|
|
return this.error('There was a problem patching ChannelSidebar!', err);
|
|
}
|
|
};
|
|
}
|
|
} catch (err) {
|
|
return this.error('There was a problem patching the channels sidebar!', err);
|
|
}
|
|
});
|
|
} catch (err) {
|
|
return this.error('There was a problem patching the channels sidebar!', err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forces updates on the app's views and routes.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async forceRouterUpdate () {
|
|
try {
|
|
/**
|
|
* Views
|
|
*/
|
|
const { app } = await getModule('app', true);
|
|
const viewsInstance = getOwnerInstance(await waitForElement(`.${app}`));
|
|
findInTree(viewsInstance?._reactInternals, n => n?.historyUnlisten, { walkable: [ 'child', 'stateNode' ] })?.forceUpdate();
|
|
/**
|
|
* Routes
|
|
*/
|
|
const { container } = await getModule('container', 'downloadProgressCircle', true);
|
|
const routesInstance = getOwnerInstance(await waitForElement(`.${container}`));
|
|
routesInstance?.forceUpdate();
|
|
} catch (err) {
|
|
return this.error('There was a problem force updating the views or routes!', err);
|
|
}
|
|
}
|
|
}
|