diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js
index b5cac9abccdc8..e2e7d66c7ad80 100644
--- a/.storybook/webpack.config.js
+++ b/.storybook/webpack.config.js
@@ -42,7 +42,6 @@ module.exports = async ({ config }) => {
},
},
},
- 'react-docgen-typescript-loader',
],
});
diff --git a/app/ui-sidenav/client/sidebarHeader.js b/app/ui-sidenav/client/sidebarHeader.js
index b748f03678358..4ca2d53506bce 100644
--- a/app/ui-sidenav/client/sidebarHeader.js
+++ b/app/ui-sidenav/client/sidebarHeader.js
@@ -159,8 +159,6 @@ const toolbarButtons = (/* user */) => [{
type: 'open',
id: 'administration',
action: () => {
- SideNav.setFlex('adminFlex');
- SideNav.openFlex();
FlowRouter.go('admin', { group: 'info' });
popover.close();
},
diff --git a/app/ui-utils/client/lib/SideNav.js b/app/ui-utils/client/lib/SideNav.js
index 116e9c8bdc502..5f0da9b4cc6f8 100644
--- a/app/ui-utils/client/lib/SideNav.js
+++ b/app/ui-utils/client/lib/SideNav.js
@@ -32,11 +32,13 @@ export const SideNav = new class {
}
if (window.DISABLE_ANIMATION === true) {
+ !this.flexNav.opened && this.setFlex();
this.animating = false;
return typeof callback === 'function' && callback();
}
return setTimeout(() => {
+ !this.flexNav.opened && this.setFlex();
this.animating = false;
return typeof callback === 'function' && callback();
}, 500);
@@ -62,10 +64,7 @@ export const SideNav = new class {
return this.flexNav.opened;
}
- setFlex(template, data) {
- if (data == null) {
- data = {};
- }
+ setFlex(template, data = {}) {
Session.set('flex-nav-template', template);
return Session.set('flex-nav-data', data);
}
diff --git a/app/ui-utils/client/lib/openRoom.js b/app/ui-utils/client/lib/openRoom.js
index 71b35a397d0c0..29f05583bc428 100644
--- a/app/ui-utils/client/lib/openRoom.js
+++ b/app/ui-utils/client/lib/openRoom.js
@@ -31,15 +31,18 @@ const getDomOfLoading = mem(function getDomOfLoading() {
function replaceCenterDomBy(dom) {
document.dispatchEvent(new CustomEvent('main-content-destroyed'));
- const mainNode = document.querySelector('.main-content');
- if (mainNode) {
- for (const child of Array.from(mainNode.children)) {
- if (child) { mainNode.removeChild(child); }
- }
- mainNode.appendChild(dom);
- }
-
- return mainNode;
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const mainNode = document.querySelector('.main-content');
+ if (mainNode) {
+ for (const child of Array.from(mainNode.children)) {
+ if (child) { mainNode.removeChild(child); }
+ }
+ mainNode.appendChild(dom);
+ }
+ resolve(mainNode);
+ }, 1);
+ });
}
const waitUntilRoomBeInserted = async (type, rid) => new Promise((resolve) => {
@@ -69,7 +72,7 @@ export const openRoom = async function(type, name) {
if (settings.get('Accounts_AllowAnonymousRead')) {
BlazeLayout.render('main');
}
- replaceCenterDomBy(getDomOfLoading());
+ await replaceCenterDomBy(getDomOfLoading());
return;
}
@@ -85,7 +88,7 @@ export const openRoom = async function(type, name) {
}
const roomDom = RoomManager.getDomOfRoom(type + name, room._id, roomTypes.getConfig(type).mainTemplate);
- const mainNode = replaceCenterDomBy(roomDom);
+ const mainNode = await replaceCenterDomBy(roomDom);
if (mainNode) {
if (roomDom.classList.contains('room-container')) {
diff --git a/client/admin/AdministrationLayout.tsx b/client/admin/AdministrationLayout.tsx
new file mode 100644
index 0000000000000..8cb5090d413d6
--- /dev/null
+++ b/client/admin/AdministrationLayout.tsx
@@ -0,0 +1,16 @@
+import React, { useEffect, FC } from 'react';
+
+import { SideNav } from '../../app/ui-utils/client';
+
+const AdministrationLayout: FC = ({ children }) => {
+ useEffect(() => {
+ SideNav.setFlex('adminFlex');
+ SideNav.openFlex();
+ }, []);
+
+ return <>
+ {children}
+ >;
+};
+
+export default AdministrationLayout;
diff --git a/client/admin/AdministrationRouter.js b/client/admin/AdministrationRouter.js
index ed329405bdd6d..0812ca904a6aa 100644
--- a/client/admin/AdministrationRouter.js
+++ b/client/admin/AdministrationRouter.js
@@ -1,19 +1,19 @@
-import React, { lazy, useMemo, Suspense, useEffect } from 'react';
+import React, { lazy, useMemo, Suspense } from 'react';
-import { SideNav } from '../../app/ui-utils/client';
+import AdministrationLayout from './AdministrationLayout';
+import PrivilegedSettingsProvider from './PrivilegedSettingsProvider';
import PageSkeleton from './PageSkeleton';
function AdministrationRouter({ lazyRouteComponent, ...props }) {
- useEffect(() => {
- SideNav.setFlex('adminFlex');
- SideNav.openFlex();
- }, []);
-
const LazyRouteComponent = useMemo(() => lazy(lazyRouteComponent), [lazyRouteComponent]);
- return }>
-
- ;
+ return
+
+ }>
+
+
+
+ ;
}
export default AdministrationRouter;
diff --git a/client/admin/PrivateSettingsCachedCollection.js b/client/admin/PrivateSettingsCachedCollection.js
deleted file mode 100644
index a2d0bf40ea907..0000000000000
--- a/client/admin/PrivateSettingsCachedCollection.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CachedCollection } from '../../app/ui-cached-collection';
-import { Notifications } from '../../app/notifications/client';
-
-export class PrivateSettingsCachedCollection extends CachedCollection {
- constructor() {
- super({
- name: 'private-settings',
- eventType: 'onLogged',
- });
- }
-
- async setupListener(eventType, eventName) {
- // private settings also need to listen to a change of authorizations for the setting-based authorizations
- Notifications[eventType || this.eventType](eventName || this.eventName, async (t, { _id, ...record }) => {
- this.log('record received', t, { _id, ...record });
- this.collection.upsert({ _id }, record);
- this.sync();
- });
- }
-}
diff --git a/client/admin/PrivateSettingsCachedCollection.ts b/client/admin/PrivateSettingsCachedCollection.ts
new file mode 100644
index 0000000000000..c1ea4b3dacd4b
--- /dev/null
+++ b/client/admin/PrivateSettingsCachedCollection.ts
@@ -0,0 +1,29 @@
+import { CachedCollection } from '../../app/ui-cached-collection/client';
+import { Notifications } from '../../app/notifications/client';
+
+export class PrivateSettingsCachedCollection extends CachedCollection {
+ constructor() {
+ super({
+ name: 'private-settings',
+ eventType: 'onLogged',
+ });
+ }
+
+ async setupListener(): Promise {
+ Notifications.onLogged(this.eventName, async (t: string, { _id, ...record }: { _id: string }) => {
+ this.log('record received', t, { _id, ...record });
+ this.collection.upsert({ _id }, record);
+ this.sync();
+ });
+ }
+
+ static instance: PrivateSettingsCachedCollection;
+
+ static get(): PrivateSettingsCachedCollection {
+ if (!PrivateSettingsCachedCollection.instance) {
+ PrivateSettingsCachedCollection.instance = new PrivateSettingsCachedCollection();
+ }
+
+ return PrivateSettingsCachedCollection.instance;
+ }
+}
diff --git a/client/admin/settings/SettingsState.js b/client/admin/PrivilegedSettingsProvider.js
similarity index 63%
rename from client/admin/settings/SettingsState.js
rename to client/admin/PrivilegedSettingsProvider.js
index ce4016cd27864..911cd00e859d8 100644
--- a/client/admin/settings/SettingsState.js
+++ b/client/admin/PrivilegedSettingsProvider.js
@@ -1,20 +1,11 @@
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { Mongo } from 'meteor/mongo';
-import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
+import { Tracker } from 'meteor/tracker';
+import React, { useEffect, useMemo, useReducer, useRef, useState } from 'react';
-import { PrivateSettingsCachedCollection } from '../PrivateSettingsCachedCollection';
-import { PrivateSettingsContext } from '../../contexts/PrivateSettingsContext';
-
-let privateSettingsCachedCollection; // Remove this singleton (╯°□°)╯︵ ┻━┻
-
-const getPrivateSettingsCachedCollection = () => {
- if (privateSettingsCachedCollection) {
- return [privateSettingsCachedCollection, Promise.resolve()];
- }
-
- privateSettingsCachedCollection = new PrivateSettingsCachedCollection();
-
- return [privateSettingsCachedCollection, privateSettingsCachedCollection.init()];
-};
+import { PrivilegedSettingsContext } from '../contexts/PrivilegedSettingsContext';
+import { useAtLeastOnePermission } from '../contexts/AuthorizationContext';
+import { PrivateSettingsCachedCollection } from './PrivateSettingsCachedCollection';
const compareStrings = (a = '', b = '') => {
if (a === b || (!a && !b)) {
@@ -79,39 +70,37 @@ const settingsReducer = (states, { type, payload }) => {
return states;
};
-export function SettingsState({ children }) {
+function AuthorizedPrivilegedSettingsProvider({ cachedCollection, children }) {
const [isLoading, setLoading] = useState(true);
- const [subscribers] = useState(new Set());
+ const subscribersRef = useRef();
+ if (!subscribersRef.current) {
+ subscribersRef.current = new Set();
+ }
const stateRef = useRef({ settings: [], persistedSettings: [] });
- const enhancedReducer = useCallback((state, action) => {
- const newState = settingsReducer(state, action);
-
- stateRef.current = newState;
-
- subscribers.forEach((subscriber) => {
- subscriber(newState);
- });
-
- return newState;
- }, [settingsReducer, subscribers]);
+ const [state, dispatch] = useReducer(settingsReducer, { settings: [], persistedSettings: [] });
+ stateRef.current = state;
- const [, dispatch] = useReducer(enhancedReducer, { settings: [], persistedSettings: [] });
+ subscribersRef.current.forEach((subscriber) => {
+ subscriber(state);
+ });
const collectionsRef = useRef({});
useEffect(() => {
- const [privateSettingsCachedCollection, loadingPromise] = getPrivateSettingsCachedCollection();
-
const stopLoading = () => {
setLoading(false);
};
- loadingPromise.then(stopLoading, stopLoading);
+ if (!Tracker.nonreactive(() => cachedCollection.ready.get())) {
+ cachedCollection.init().then(stopLoading, stopLoading);
+ } else {
+ stopLoading();
+ }
- const { collection: persistedSettingsCollection } = privateSettingsCachedCollection;
+ const { collection: persistedSettingsCollection } = cachedCollection;
const settingsCollection = new Mongo.Collection(null);
collectionsRef.current = {
@@ -163,21 +152,21 @@ export function SettingsState({ children }) {
const updateTimersRef = useRef({});
- const updateAtCollection = useCallback(({ _id, ...data }) => {
+ const updateAtCollection = useMutableCallback(({ _id, ...data }) => {
const { current: { settingsCollection } } = collectionsRef;
const { current: updateTimers } = updateTimersRef;
clearTimeout(updateTimers[_id]);
updateTimers[_id] = setTimeout(() => {
settingsCollection.update(_id, { $set: data });
}, 70);
- }, [collectionsRef, updateTimersRef]);
+ });
- const hydrate = useCallback((changes) => {
+ const hydrate = useMutableCallback((changes) => {
changes.forEach(updateAtCollection);
dispatch({ type: 'hydrate', payload: changes });
- }, [updateAtCollection, dispatch]);
+ });
- const isDisabled = useCallback(({ blocked, enableQuery }) => {
+ const isDisabled = useMutableCallback(({ blocked, enableQuery }) => {
if (blocked) {
return true;
}
@@ -190,28 +179,39 @@ export function SettingsState({ children }) {
const queries = [].concat(typeof enableQuery === 'string' ? JSON.parse(enableQuery) : enableQuery);
return !queries.every((query) => !!settingsCollection.findOne(query));
- }, [collectionsRef]);
+ });
const contextValue = useMemo(() => ({
- subscribers,
+ authorized: true,
+ loading: isLoading,
+ subscribers: subscribersRef.current,
stateRef,
hydrate,
isDisabled,
}), [
- subscribers,
- stateRef,
+ isLoading,
hydrate,
isDisabled,
]);
- return ;
+ return ;
+}
+
+function PrivilegedSettingsProvider({ children }) {
+ const hasPermission = useAtLeastOnePermission([
+ 'view-privileged-setting',
+ 'edit-privileged-setting',
+ 'manage-selected-settings',
+ ]);
+
+ if (!hasPermission) {
+ return children;
+ }
+
+ return ;
}
-export {
- usePrivateSettingsGroup as useGroup,
- usePrivateSettingsSection as useSection,
- usePrivateSettingActions as useSettingActions,
- usePrivateSettingDisabledState as useSettingDisabledState,
- usePrivateSettingsSectionChangedState as useSectionChangedState,
- usePrivateSetting as useSetting,
-} from '../../contexts/PrivateSettingsContext';
+export default PrivilegedSettingsProvider;
diff --git a/client/admin/adminFlex.html b/client/admin/adminFlex.html
deleted file mode 100644
index 0402664008c2e..0000000000000
--- a/client/admin/adminFlex.html
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
diff --git a/client/admin/adminFlex.js b/client/admin/adminFlex.js
deleted file mode 100644
index ad4752da223cf..0000000000000
--- a/client/admin/adminFlex.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import _ from 'underscore';
-import s from 'underscore.string';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Template } from 'meteor/templating';
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import { FlowRouter } from 'meteor/kadira:flow-router';
-
-import { settings } from '../../app/settings';
-import { menu, SideNav, Layout } from '../../app/ui-utils/client';
-import { t } from '../../app/utils/client';
-import { PrivateSettingsCachedCollection } from './PrivateSettingsCachedCollection';
-import { hasAtLeastOnePermission } from '../../app/authorization/client';
-import { sidebarItems } from './sidebarItems';
-import './adminFlex.html';
-
-Template.adminFlex.onCreated(function() {
- this.settingsFilter = new ReactiveVar('');
- if (settings.cachedCollectionPrivate == null) {
- settings.cachedCollectionPrivate = new PrivateSettingsCachedCollection();
- settings.collectionPrivate = settings.cachedCollectionPrivate.collection;
- settings.cachedCollectionPrivate.init();
- }
-});
-
-Template.adminFlex.helpers({
- isEmbedded: () => Layout.isEmbedded(),
- sidebarItems: () => sidebarItems.get()
- .filter((sidebarItem) => !sidebarItem.permissionGranted || sidebarItem.permissionGranted())
- .map(({ _id, i18nLabel, icon, href }) => ({
- name: t(i18nLabel || _id),
- icon,
- pathSection: href,
- darken: true,
- isLightSidebar: true,
- active: href === FlowRouter.getRouteName(),
- })),
- hasSettingPermission: () =>
- hasAtLeastOnePermission(['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings']),
- groups: () => {
- const filter = Template.instance().settingsFilter.get();
- const query = {
- type: 'group',
- };
- let groups = [];
- if (filter) {
- const filterRegex = new RegExp(s.escapeRegExp(filter), 'i');
- const records = settings.collectionPrivate.find().fetch();
- records.forEach(function(record) {
- if (filterRegex.test(TAPi18n.__(record.i18nLabel || record._id))) {
- groups.push(record.group || record._id);
- }
- });
- groups = _.unique(groups);
- if (groups.length > 0) {
- query._id = {
- $in: groups,
- };
- }
- }
-
- if (filter && groups.length === 0) {
- return [];
- }
-
- return settings.collectionPrivate.find(query)
- .fetch()
- .map((item) => ({ ...item, name: t(item.i18nLabel || item._id) }))
- .sort(({ name: a }, { name: b }) => (a.toLowerCase() >= b.toLowerCase() ? 1 : -1))
- .map(({ _id, name }) => ({
- name,
- pathSection: 'admin',
- pathGroup: _id,
- darken: true,
- isLightSidebar: true,
- active: _id === FlowRouter.getParam('group'),
- }));
- },
-});
-
-Template.adminFlex.events({
- 'click [data-action="close"]'() {
- if (Layout.isEmbedded()) {
- menu.close();
- return;
- }
-
- SideNav.closeFlex();
- },
- 'keyup [name=settings-search]'(e, t) {
- t.settingsFilter.set(e.target.value);
- },
-});
diff --git a/client/admin/index.js b/client/admin/index.js
index eb3b7e29f7ae9..a962afc2ff1c9 100644
--- a/client/admin/index.js
+++ b/client/admin/index.js
@@ -1,4 +1,2 @@
-import './adminFlex';
-
export { registerAdminRoute } from './routes';
export { registerAdminSidebarItem } from './sidebarItems';
diff --git a/client/admin/settings/GroupSelector.js b/client/admin/settings/GroupSelector.js
index 2b4e9e45e4e66..551765b44d280 100644
--- a/client/admin/settings/GroupSelector.js
+++ b/client/admin/settings/GroupSelector.js
@@ -1,13 +1,13 @@
import React from 'react';
-import { usePrivateSettingsGroup } from '../../contexts/PrivateSettingsContext';
+import { usePrivilegedSettingsGroup } from '../../contexts/PrivilegedSettingsContext';
import { AssetsGroupPage } from './groups/AssetsGroupPage';
import { OAuthGroupPage } from './groups/OAuthGroupPage';
import { GenericGroupPage } from './groups/GenericGroupPage';
import { GroupPage } from './GroupPage';
export function GroupSelector({ groupId }) {
- const group = usePrivateSettingsGroup(groupId);
+ const group = usePrivilegedSettingsGroup(groupId);
if (!group) {
return ;
diff --git a/client/admin/settings/Section.js b/client/admin/settings/Section.js
index e249c7ca04d95..6adfea97a1c07 100644
--- a/client/admin/settings/Section.js
+++ b/client/admin/settings/Section.js
@@ -2,15 +2,15 @@ import { Accordion, Box, Button, FieldGroup, Skeleton } from '@rocket.chat/fusel
import React from 'react';
import {
- usePrivateSettingsSection,
- usePrivateSettingsSectionChangedState,
-} from '../../contexts/PrivateSettingsContext';
+ usePrivilegedSettingsSection,
+ usePrivilegedSettingsSectionChangedState,
+} from '../../contexts/PrivilegedSettingsContext';
import { useTranslation } from '../../contexts/TranslationContext';
import { Setting } from './Setting';
export function Section({ children, groupId, hasReset = true, help, sectionName, solo }) {
- const section = usePrivateSettingsSection(groupId, sectionName);
- const changed = usePrivateSettingsSectionChangedState(groupId, sectionName);
+ const section = usePrivilegedSettingsSection(groupId, sectionName);
+ const changed = usePrivilegedSettingsSectionChangedState(groupId, sectionName);
const t = useTranslation();
diff --git a/client/admin/settings/Setting.js b/client/admin/settings/Setting.js
index 86add049c19f1..38e3b205f059b 100644
--- a/client/admin/settings/Setting.js
+++ b/client/admin/settings/Setting.js
@@ -2,7 +2,7 @@ import { Callout, Field, Flex, InputBox, Margins, Skeleton } from '@rocket.chat/
import React, { memo, useEffect, useMemo, useState, useCallback } from 'react';
import MarkdownText from '../../components/basic/MarkdownText';
-import { usePrivateSetting } from '../../contexts/PrivateSettingsContext';
+import { usePrivilegedSetting } from '../../contexts/PrivilegedSettingsContext';
import { useTranslation } from '../../contexts/TranslationContext';
import { GenericSettingInput } from './inputs/GenericSettingInput';
import { BooleanSettingInput } from './inputs/BooleanSettingInput';
@@ -70,7 +70,7 @@ export function Setting({ settingId, sectionChanged }) {
update,
reset,
...setting
- } = usePrivateSetting(settingId);
+ } = usePrivilegedSetting(settingId);
const t = useTranslation();
diff --git a/client/admin/settings/SettingsRoute.js b/client/admin/settings/SettingsRoute.js
index 42f5323f10440..e0c4b80695d6d 100644
--- a/client/admin/settings/SettingsRoute.js
+++ b/client/admin/settings/SettingsRoute.js
@@ -1,17 +1,12 @@
import React from 'react';
-import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext';
+import { usePrivilegedSettingsAuthorized } from '../../contexts/PrivilegedSettingsContext';
import { useRouteParameter } from '../../contexts/RouterContext';
import { GroupSelector } from './GroupSelector';
import NotAuthorizedPage from '../NotAuthorizedPage';
-import { SettingsState } from './SettingsState';
export function SettingsRoute() {
- const hasPermission = useAtLeastOnePermission([
- 'view-privileged-setting',
- 'edit-privileged-setting',
- 'manage-selected-settings',
- ]);
+ const hasPermission = usePrivilegedSettingsAuthorized();
const groupId = useRouteParameter('group');
@@ -19,9 +14,7 @@ export function SettingsRoute() {
return ;
}
- return
-
- ;
+ return ;
}
export default SettingsRoute;
diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js
new file mode 100644
index 0000000000000..935a81dd4751d
--- /dev/null
+++ b/client/admin/sidebar/AdminSidebar.js
@@ -0,0 +1,154 @@
+import { css } from '@rocket.chat/css-in-js';
+import { Box, Button, Icon, SearchInput, Scrollable, Skeleton } from '@rocket.chat/fuselage';
+import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
+import React, { useCallback, useState, useMemo, useEffect } from 'react';
+
+import { menu, SideNav, Layout } from '../../../app/ui-utils/client';
+import { useReactiveValue } from '../../hooks/useReactiveValue';
+import { useTranslation } from '../../contexts/TranslationContext';
+import { useRoutePath, useCurrentRoute } from '../../contexts/RouterContext';
+import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext';
+import { sidebarItems } from '../sidebarItems';
+import PrivilegedSettingsProvider from '../PrivilegedSettingsProvider';
+import { usePrivilegedSettingsGroups } from '../../contexts/PrivilegedSettingsContext';
+
+const SidebarItem = React.memo(({ permissionGranted, pathGroup, href, icon, label, currentPath }) => {
+ const params = useMemo(() => ({ group: pathGroup }), [pathGroup]);
+ const path = useRoutePath(href, params);
+ const isActive = path === currentPath || false;
+ if (permissionGranted && !permissionGranted()) { return null; }
+ return
+
+ {icon && }
+ {label}
+
+ ;
+});
+
+const SidebarItemsAssembler = React.memo(({ items, currentPath }) => {
+ const t = useTranslation();
+ return items.map(({
+ href,
+ i18nLabel,
+ name,
+ icon,
+ permissionGranted,
+ pathGroup,
+ }) => );
+});
+
+const AdminSidebarPages = ({ currentPath }) => {
+ const items = useReactiveValue(() => sidebarItems.get());
+
+ return
+
+ ;
+};
+
+const AdminSidebarSettings = ({ currentPath }) => {
+ const t = useTranslation();
+ const [filter, setFilter] = useState('');
+ const handleChange = useCallback((e) => setFilter(e.currentTarget.value), []);
+
+ const groups = usePrivilegedSettingsGroups(useDebouncedValue(filter, 400));
+ const isLoadingGroups = false; // TODO: get from PrivilegedSettingsContext
+
+ return
+ {t('Settings')}
+
+ }
+ className={['asdsads']}
+ />
+
+
+ {isLoadingGroups && }
+ {!isLoadingGroups && !!groups.length && ({
+ name: t(group.i18nLabel || group._id),
+ href: 'admin',
+ pathGroup: group._id,
+ }))}
+ currentPath={currentPath}
+ />}
+ {!isLoadingGroups && !groups.length && {t('Nothing_found')}}
+
+ ;
+};
+
+export default function AdminSidebar() {
+ const t = useTranslation();
+
+ const canViewSettings = useAtLeastOnePermission(['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings']);
+
+ const closeAdminFlex = useCallback(() => {
+ if (Layout.isEmbedded()) {
+ menu.close();
+ return;
+ }
+
+ SideNav.closeFlex();
+ }, []);
+
+ const currentRoute = useCurrentRoute();
+ const currentPath = useRoutePath(...currentRoute);
+
+ useEffect(() => {
+ if (!currentPath.startsWith('/admin/')) {
+ SideNav.closeFlex();
+ }
+ }, [currentRoute]);
+
+ // TODO: uplift this provider
+ return
+
+
+ {t('Administration')}
+
+
+
+
+
+ {canViewSettings && }
+
+
+
+ ;
+}
diff --git a/client/admin/sidebarItems.js b/client/admin/sidebarItems.js
index c5112049b571b..657069b63a10a 100644
--- a/client/admin/sidebarItems.js
+++ b/client/admin/sidebarItems.js
@@ -1,6 +1,7 @@
import { ReactiveVar } from 'meteor/reactive-var';
import { hasPermission } from '../../app/authorization/client';
+import { createTemplateForComponent } from '../reactAdapters';
export const sidebarItems = new ReactiveVar([]);
@@ -8,6 +9,8 @@ export const registerAdminSidebarItem = (itemOptions) => {
sidebarItems.set([...sidebarItems.get(), itemOptions]);
};
+createTemplateForComponent('adminFlex', () => import('./sidebar/AdminSidebar'));
+
registerAdminSidebarItem({
href: 'admin-info',
i18nLabel: 'Info',
diff --git a/client/contexts/PrivateSettingsContext.ts b/client/contexts/PrivilegedSettingsContext.ts
similarity index 65%
rename from client/contexts/PrivateSettingsContext.ts
rename to client/contexts/PrivilegedSettingsContext.ts
index e900175f54245..da463d6de2da4 100644
--- a/client/contexts/PrivateSettingsContext.ts
+++ b/client/contexts/PrivilegedSettingsContext.ts
@@ -1,6 +1,7 @@
import { useDebouncedCallback, useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { Tracker } from 'meteor/tracker';
-import { createContext, useContext, RefObject, useState, useEffect, useLayoutEffect } from 'react';
+import { createContext, useContext, RefObject, useState, useEffect, useLayoutEffect, useMemo, useCallback } from 'react';
+import { useSubscription } from 'use-subscription';
import { useReactiveValue } from '../hooks/useReactiveValue';
import { useBatchSettingsDispatch } from './SettingsContext';
@@ -8,8 +9,8 @@ import { useToastMessageDispatch } from './ToastMessagesContext';
import { useTranslation, useLoadLanguage } from './TranslationContext';
import { useUser } from './UserContext';
-type Setting = object & {
- _id: unknown;
+export type PrivilegedSetting = object & {
+ _id: string;
type: string;
blocked: boolean;
enableQuery: unknown;
@@ -20,27 +21,34 @@ type Setting = object & {
packageValue: unknown;
packageEditor: unknown;
editor: unknown;
+ sorter: string;
+ i18nLabel: string;
disabled?: boolean;
update?: () => void;
reset?: () => void;
};
-type PrivateSettingsState = {
- settings: Setting[];
- persistedSettings: Setting[];
+export type PrivilegedSettingsState = {
+ settings: PrivilegedSetting[];
+ persistedSettings: PrivilegedSetting[];
};
type EqualityFunction = (a: T, b: T) => boolean;
-type PrivateSettingsContextValue = {
- subscribers: Set<(state: PrivateSettingsState) => void>;
- stateRef: RefObject;
+// TODO: split editing into another context
+type PrivilegedSettingsContextValue = {
+ authorized: boolean;
+ loading: boolean;
+ subscribers: Set<(state: PrivilegedSettingsState) => void>;
+ stateRef: RefObject;
hydrate: (changes: any[]) => void;
- isDisabled: (setting: Setting) => boolean;
+ isDisabled: (setting: PrivilegedSetting) => boolean;
};
-export const PrivateSettingsContext = createContext({
- subscribers: new Set<(state: PrivateSettingsState) => void>(),
+export const PrivilegedSettingsContext = createContext({
+ authorized: false,
+ loading: false,
+ subscribers: new Set<(state: PrivilegedSettingsState) => void>(),
stateRef: {
current: {
settings: [],
@@ -51,14 +59,59 @@ export const PrivateSettingsContext = createContext
isDisabled: () => false,
});
+export const usePrivilegedSettingsAuthorized = (): boolean =>
+ useContext(PrivilegedSettingsContext).authorized;
+
+export const useIsPrivilegedSettingsLoading = (): boolean =>
+ useContext(PrivilegedSettingsContext).loading;
+
+export const usePrivilegedSettingsGroups = (filter?: string): any => {
+ const { stateRef, subscribers } = useContext(PrivilegedSettingsContext);
+ const t = useTranslation();
+
+ const getCurrentValue = useCallback(() => {
+ const filterRegex = filter ? new RegExp(filter, 'i') : null;
+
+ const filterPredicate = (setting: PrivilegedSetting): boolean =>
+ !filterRegex || filterRegex.test(t(setting.i18nLabel || setting._id));
+
+ const groupIds = Array.from(new Set(
+ (stateRef.current?.persistedSettings ?? [])
+ .filter(filterPredicate)
+ .map((setting) => setting.group || setting._id),
+ ));
+
+ return (stateRef.current?.persistedSettings ?? [])
+ .filter(({ type, group, _id }) => type === 'group' && groupIds.includes(group || _id))
+ .sort((a, b) => t(a.i18nLabel || a._id).localeCompare(t(b.i18nLabel || b._id)));
+ }, [filter]);
+
+ const subscribe = useCallback((cb) => {
+ const handleUpdate = (): void => {
+ cb(getCurrentValue());
+ };
+
+ subscribers.add(handleUpdate);
+
+ return (): void => {
+ subscribers.delete(handleUpdate);
+ };
+ }, [getCurrentValue]);
+
+ return useSubscription(useMemo(() => ({
+ getCurrentValue,
+ subscribe,
+ }), [getCurrentValue, subscribe]));
+};
+
const useSelector = (
- selector: (state: PrivateSettingsState) => T,
+ selector: (state: PrivilegedSettingsState) => T,
equalityFunction: EqualityFunction = Object.is,
): T | null => {
- const { subscribers, stateRef } = useContext(PrivateSettingsContext);
+ const { subscribers, stateRef } = useContext(PrivilegedSettingsContext);
const [value, setValue] = useState(() => (stateRef.current ? selector(stateRef.current) : null));
- const handleUpdate = useMutableCallback((state: PrivateSettingsState) => {
+ const handleUpdate = useMutableCallback((state: PrivilegedSettingsState) => {
const newValue = selector(state);
if (!value || !equalityFunction(newValue, value)) {
@@ -81,7 +134,7 @@ const useSelector = (
return value;
};
-export const usePrivateSettingsGroup = (groupId: string): any => {
+export const usePrivilegedSettingsGroup = (groupId: string): any => {
const group = useSelector((state) => state.settings.find(({ _id, type }) => _id === groupId && type === 'group'));
const filterSettings = (settings: any[]): any[] => settings.filter(({ group }) => group === groupId);
@@ -90,7 +143,7 @@ export const usePrivateSettingsGroup = (groupId: string): any => {
const sections = useSelector((state) => Array.from(new Set(filterSettings(state.settings).map(({ section }) => section || ''))), (a, b) => a.length === b.length && a.join() === b.join());
const batchSetSettings = useBatchSettingsDispatch();
- const { stateRef, hydrate } = useContext(PrivateSettingsContext);
+ const { stateRef, hydrate } = useContext(PrivilegedSettingsContext);
const dispatchToastMessage = useToastMessageDispatch() as any;
const t = useTranslation() as (key: string, ...args: any[]) => string;
@@ -148,7 +201,7 @@ export const usePrivateSettingsGroup = (groupId: string): any => {
return group && { ...group, sections, changed, save, cancel };
};
-export const usePrivateSettingsSection = (groupId: string, sectionName?: string): any => {
+export const usePrivilegedSettingsSection = (groupId: string, sectionName?: string): any => {
sectionName = sectionName || '';
const filterSettings = (settings: any[]): any[] =>
@@ -157,7 +210,7 @@ export const usePrivateSettingsSection = (groupId: string, sectionName?: string)
const canReset = useSelector((state) => filterSettings(state.settings).some(({ value, packageValue }) => JSON.stringify(value) !== JSON.stringify(packageValue)));
const settingsIds = useSelector((state) => filterSettings(state.settings).map(({ _id }) => _id), (a, b) => a.length === b.length && a.join() === b.join());
- const { stateRef, hydrate, isDisabled } = useContext(PrivateSettingsContext);
+ const { stateRef, hydrate, isDisabled } = useContext(PrivilegedSettingsContext);
const reset = useMutableCallback(() => {
const state = stateRef.current;
@@ -186,11 +239,11 @@ export const usePrivateSettingsSection = (groupId: string, sectionName?: string)
};
};
-export const usePrivateSettingActions = (persistedSetting: Setting | null | undefined): {
+export const usePrivilegedSettingActions = (persistedSetting: PrivilegedSetting | null | undefined): {
update: () => void;
reset: () => void;
} => {
- const { hydrate } = useContext(PrivateSettingsContext);
+ const { hydrate } = useContext(PrivilegedSettingsContext);
const update = useDebouncedCallback(({ value, editor }) => {
const changes = [{
@@ -217,24 +270,24 @@ export const usePrivateSettingActions = (persistedSetting: Setting | null | unde
return { update, reset };
};
-export const usePrivateSettingDisabledState = (setting: Setting | null | undefined): boolean => {
- const { isDisabled } = useContext(PrivateSettingsContext);
+export const usePrivilegedSettingDisabledState = (setting: PrivilegedSetting | null | undefined): boolean => {
+ const { isDisabled } = useContext(PrivilegedSettingsContext);
return useReactiveValue(() => (setting ? isDisabled(setting) : false), [setting?.blocked, setting?.enableQuery]) as unknown as boolean;
};
-export const usePrivateSettingsSectionChangedState = (groupId: string, sectionName: string): boolean =>
+export const usePrivilegedSettingsSectionChangedState = (groupId: string, sectionName: string): boolean =>
!!useSelector((state) =>
state.settings.some(({ group, section, changed }) =>
group === groupId && ((!sectionName && !section) || (sectionName === section)) && changed));
-export const usePrivateSetting = (_id: string): Setting | null | undefined => {
- const selectSetting = (settings: Setting[]): Setting | undefined => settings.find((setting) => setting._id === _id);
+export const usePrivilegedSetting = (_id: string): PrivilegedSetting | null | undefined => {
+ const selectSetting = (settings: PrivilegedSetting[]): PrivilegedSetting | undefined => settings.find((setting) => setting._id === _id);
const setting = useSelector((state) => selectSetting(state.settings));
const persistedSetting = useSelector((state) => selectSetting(state.persistedSettings));
- const { update, reset } = usePrivateSettingActions(persistedSetting);
- const disabled = usePrivateSettingDisabledState(persistedSetting);
+ const { update, reset } = usePrivilegedSettingActions(persistedSetting);
+ const disabled = usePrivilegedSettingDisabledState(persistedSetting);
if (!setting) {
return null;
diff --git a/package-lock.json b/package-lock.json
index 0427269a54ab3..566d084ec16c9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6206,6 +6206,15 @@
"@types/react": "*"
}
},
+ "@types/react-dom": {
+ "version": "16.9.8",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz",
+ "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-syntax-highlighter": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz",
@@ -6643,93 +6652,6 @@
"@xtuc/long": "4.2.1"
}
},
- "@webpack-contrib/schema-utils": {
- "version": "1.0.0-beta.0",
- "resolved": "https://registry.npmjs.org/@webpack-contrib/schema-utils/-/schema-utils-1.0.0-beta.0.tgz",
- "integrity": "sha512-LonryJP+FxQQHsjGBi6W786TQB1Oym+agTpY0c+Kj8alnIw+DLUJb6SI8Y1GHGhLCH1yPRrucjObUmxNICQ1pg==",
- "dev": true,
- "requires": {
- "ajv": "^6.1.0",
- "ajv-keywords": "^3.1.0",
- "chalk": "^2.3.2",
- "strip-ansi": "^4.0.0",
- "text-table": "^0.2.0",
- "webpack-log": "^1.1.2"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
- "dev": true
- },
- "ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
- "requires": {
- "color-convert": "^1.9.0"
- }
- },
- "chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
- "requires": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- }
- },
- "color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
- "requires": {
- "color-name": "1.1.3"
- }
- },
- "color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
- "dev": true
- },
- "strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^3.0.0"
- }
- },
- "supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
- "requires": {
- "has-flag": "^3.0.0"
- }
- },
- "webpack-log": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.2.0.tgz",
- "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==",
- "dev": true,
- "requires": {
- "chalk": "^2.1.0",
- "log-symbols": "^2.1.0",
- "loglevelnext": "^1.0.1",
- "uuid": "^3.1.0"
- }
- }
- }
- },
"@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -12811,16 +12733,6 @@
}
}
},
- "d": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
- "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
- "dev": true,
- "requires": {
- "es5-ext": "^0.10.50",
- "type": "^1.0.1"
- }
- },
"d3-array": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
@@ -13874,34 +13786,12 @@
"is-symbol": "^1.0.2"
}
},
- "es5-ext": {
- "version": "0.10.53",
- "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
- "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
- "dev": true,
- "requires": {
- "es6-iterator": "~2.0.3",
- "es6-symbol": "~3.1.3",
- "next-tick": "~1.0.0"
- }
- },
"es5-shim": {
"version": "4.5.14",
"resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.5.14.tgz",
"integrity": "sha512-7SwlpL+2JpymWTt8sNLuC2zdhhc+wrfe5cMPI2j0o6WsPdfAiPwmFy2f0AocPB4RQVBOZ9kNTgi5YF7TdhkvEg==",
"dev": true
},
- "es6-iterator": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
- "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
- "dev": true,
- "requires": {
- "d": "1",
- "es5-ext": "^0.10.35",
- "es6-symbol": "^3.1.1"
- }
- },
"es6-promise": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz",
@@ -13921,16 +13811,6 @@
"integrity": "sha512-E9kK/bjtCQRpN1K28Xh4BlmP8egvZBGJJ+9GtnzOwt7mdqtrjHFuVGr7QJfdjBIKqrlU5duPf3pCBoDrkjVYFg==",
"dev": true
},
- "es6-symbol": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
- "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
- "dev": true,
- "requires": {
- "d": "^1.0.1",
- "ext": "^1.1.2"
- }
- },
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -14996,23 +14876,6 @@
}
}
},
- "ext": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
- "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
- "dev": true,
- "requires": {
- "type": "^2.0.0"
- },
- "dependencies": {
- "type": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
- "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==",
- "dev": true
- }
- }
- },
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -20853,16 +20716,6 @@
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
"integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po="
},
- "loglevelnext": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.5.tgz",
- "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==",
- "dev": true,
- "requires": {
- "es6-symbol": "^3.1.1",
- "object.assign": "^4.1.0"
- }
- },
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
@@ -22877,12 +22730,6 @@
"integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==",
"dev": true
},
- "next-tick": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
- "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
- "dev": true
- },
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -26672,63 +26519,6 @@
}
}
},
- "react-docgen-typescript": {
- "version": "1.16.3",
- "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-1.16.3.tgz",
- "integrity": "sha512-xYISCr8mFKfV15talgpicOF/e0DudTucf1BXzu/HteMF4RM3KsfxXkhWybZC3LTVbYrdbammDV26Z4Yuk+MoWg==",
- "dev": true
- },
- "react-docgen-typescript-loader": {
- "version": "3.7.2",
- "resolved": "https://registry.npmjs.org/react-docgen-typescript-loader/-/react-docgen-typescript-loader-3.7.2.tgz",
- "integrity": "sha512-fNzUayyUGzSyoOl7E89VaPKJk9dpvdSgyXg81cUkwy0u+NBvkzQG3FC5WBIlXda0k/iaxS+PWi+OC+tUiGxzPA==",
- "dev": true,
- "requires": {
- "@webpack-contrib/schema-utils": "^1.0.0-beta.0",
- "loader-utils": "^1.2.3",
- "react-docgen-typescript": "^1.15.0"
- },
- "dependencies": {
- "big.js": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
- "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
- "dev": true
- },
- "emojis-list": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
- "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
- "dev": true
- },
- "json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
- "dev": true,
- "requires": {
- "minimist": "^1.2.0"
- }
- },
- "loader-utils": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
- "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
- "dev": true,
- "requires": {
- "big.js": "^5.2.2",
- "emojis-list": "^3.0.0",
- "json5": "^1.0.1"
- }
- },
- "minimist": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
- "dev": true
- }
- }
- },
"react-dom": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz",
@@ -30635,12 +30425,6 @@
}
}
},
- "type": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
- "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
- "dev": true
- },
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
diff --git a/package.json b/package.json
index 3e3b71fdd8816..2e50c68c415df 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
"@types/mocha": "^7.0.2",
"@types/mock-require": "^2.0.0",
"@types/mongodb": "^3.5.8",
+ "@types/react-dom": "^16.9.8",
"@typescript-eslint/eslint-plugin": "^2.11.0",
"@typescript-eslint/parser": "^2.11.0",
"acorn": "^6.4.1",
@@ -106,7 +107,6 @@
"postcss-url": "^8.0.0",
"progress": "^2.0.2",
"proxyquire": "^2.1.0",
- "react-docgen-typescript-loader": "^3.7.2",
"simple-git": "^1.107.0",
"source-map": "^0.5.6",
"stylelint": "^9.9.0",