From 09abe691a4a286f538caaaae1cd779494501a67f Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Fri, 29 May 2020 20:36:31 -0300 Subject: [PATCH 01/21] WIP --- client/admin/AdministrationRouter.js | 3 +- client/admin/sidebar/AdminSidebar.js | 73 +++++++++++++++++++ .../sidebar/useSettingsCollectionPrivate.js | 65 +++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 client/admin/sidebar/AdminSidebar.js create mode 100644 client/admin/sidebar/useSettingsCollectionPrivate.js diff --git a/client/admin/AdministrationRouter.js b/client/admin/AdministrationRouter.js index ed329405bdd6d..fa72fa19db54d 100644 --- a/client/admin/AdministrationRouter.js +++ b/client/admin/AdministrationRouter.js @@ -2,10 +2,11 @@ import React, { lazy, useMemo, Suspense, useEffect } from 'react'; import { SideNav } from '../../app/ui-utils/client'; import PageSkeleton from './PageSkeleton'; +import { createTemplateForComponent } from '../reactAdapters'; function AdministrationRouter({ lazyRouteComponent, ...props }) { useEffect(() => { - SideNav.setFlex('adminFlex'); + SideNav.setFlex(createTemplateForComponent('AdminSidebar', () => import('./sidebar/AdminSidebar'))); SideNav.openFlex(); }, []); diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js new file mode 100644 index 0000000000000..422c326c57211 --- /dev/null +++ b/client/admin/sidebar/AdminSidebar.js @@ -0,0 +1,73 @@ +import React, { useCallback, useState } from 'react'; +import { Box, Button, Icon, SearchInput, Scrollable } from '@rocket.chat/fuselage'; + +import { menu, SideNav, Layout } from '../../../app/ui-utils/client'; +import { useReactiveValue } from '../../hooks/useReactiveValue'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useRoute } from '../../contexts/RouterContext'; +import { sidebarItems } from '../sidebarItems'; +import { useSettingsGroupsFiltered } from './useSettingsCollectionPrivate'; + +const SidebarItems = ({ items }) => { + const t = useTranslation(); + return + {items.map(({ href, i18nLabel: label, icon, permissionGranted }) => { + if (permissionGranted && !permissionGranted()) { return null; } + const router = useRoute(href); + const handleClick = useCallback(() => router.push({}), []); + return + {icon && } + {t(label)} + ; + })} + ; +}; + +const SidebarSettings = ({ groups, filter, setFilter }) => { + const t = useTranslation(); + const handleChange = useCallback((e) => setFilter(e.currentTarget.value), []); + return + {t('Settings')} + + }/> + + + {groups.map(({ pathSection, pathGroup, name, permissionGranted }) => { + if (permissionGranted && !permissionGranted()) { return null; } + return + {name} + ; + })} + + ; +}; + +export default function AdminSidebar() { + const t = useTranslation(); + const sidebarItemsArray = useReactiveValue(() => sidebarItems.get()); + const [filter, setFilter] = useState(); + + const settingsGroups = useSettingsGroupsFiltered(filter); + + const closeAdminFlex = useCallback(() => { + if (Layout.isEmbedded()) { + menu.close(); + return; + } + + SideNav.closeFlex(); + }, []); + + return + + {t('Administration')} + + + + + + + + + ; +} diff --git a/client/admin/sidebar/useSettingsCollectionPrivate.js b/client/admin/sidebar/useSettingsCollectionPrivate.js new file mode 100644 index 0000000000000..26ef9cd6c2e27 --- /dev/null +++ b/client/admin/sidebar/useSettingsCollectionPrivate.js @@ -0,0 +1,65 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; + +import { PrivateSettingsCachedCollection } from '../PrivateSettingsCachedCollection'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { settings } from '../../../app/settings'; + + +export const useSettingsGroupsFiltered = (filterText) => { + const t = useTranslation(); + const [collection, setCollection] = useState(settings.collectionPrivate); + + const filter = useDebouncedValue(filterText, 400); + + useEffect(() => { + (async () => { + if (!settings.cachedCollectionPrivate) { + settings.cachedCollectionPrivate = new PrivateSettingsCachedCollection(); + settings.collectionPrivate = settings.cachedCollectionPrivate.collection; + await settings.cachedCollectionPrivate.init(); + } + setCollection(settings.collectionPrivate); + })(); + }, []); + + const groups = useMemo(() => { + if (!settings.cachedCollectionPrivate) { return []; } + const query = { + type: 'group', + }; + + const groups = []; + if (filter) { + const filterRegex = new RegExp(filter, 'i'); + const records = collection.find().fetch(); + records.forEach(function(record) { + if (filterRegex.test(t(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 collection.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, + })); + }, [filter, settings.cachedCollectionPrivate]); + + return groups; +}; From a894a4b27048f14f8adda1b40d542727eb8ea8b8 Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Mon, 1 Jun 2020 15:57:05 -0300 Subject: [PATCH 02/21] Ready for review --- client/admin/sidebar/AdminSidebar.js | 92 ++++++++++++++----- client/admin/sidebar/styles.css | 8 ++ ...rivate.js => useSettingsGroupsFiltered.js} | 14 ++- 3 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 client/admin/sidebar/styles.css rename client/admin/sidebar/{useSettingsCollectionPrivate.js => useSettingsGroupsFiltered.js} (84%) diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index 422c326c57211..af9783d563d30 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -1,53 +1,92 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useState, useMemo } from 'react'; import { Box, Button, Icon, SearchInput, Scrollable } from '@rocket.chat/fuselage'; import { menu, SideNav, Layout } from '../../../app/ui-utils/client'; import { useReactiveValue } from '../../hooks/useReactiveValue'; import { useTranslation } from '../../contexts/TranslationContext'; -import { useRoute } from '../../contexts/RouterContext'; +import { useRoutePath, useCurrentRoute } from '../../contexts/RouterContext'; +import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext'; import { sidebarItems } from '../sidebarItems'; -import { useSettingsGroupsFiltered } from './useSettingsCollectionPrivate'; +import { useSettingsGroupsFiltered } from './useSettingsGroupsFiltered'; -const SidebarItems = ({ items }) => { +import './styles.css'; + +const SidebarItem = ({ permissionGranted, pathGroup, href, icon, label, currentPath }) => { + if (permissionGranted && !permissionGranted()) { return null; } + const params = useMemo(() => ({ group: pathGroup }), [pathGroup]); + const path = useRoutePath(href, params); + const isActive = path === currentPath || false; + return useMemo(() => + {icon && } + {label} + , [path, label, name, icon]); +}; + +const SidebarItemsAssembler = ({ items, currentPath }) => { const t = useTranslation(); + return items.map(({ + href, + i18nLabel, + name, + icon, + permissionGranted, + pathGroup, + }) => ); +}; + +const AdminSidebarPages = ({ currentPath }) => { + const items = useReactiveValue(() => sidebarItems.get()); + return - {items.map(({ href, i18nLabel: label, icon, permissionGranted }) => { - if (permissionGranted && !permissionGranted()) { return null; } - const router = useRoute(href); - const handleClick = useCallback(() => router.push({}), []); - return - {icon && } - {t(label)} - ; - })} + {useMemo(() => , [items, currentPath])} ; }; -const SidebarSettings = ({ groups, filter, setFilter }) => { +const AdminSidebarSettings = ({ currentPath }) => { const t = useTranslation(); + const [filter, setFilter] = useState(''); const handleChange = useCallback((e) => setFilter(e.currentTarget.value), []); + + const groups = useSettingsGroupsFiltered(filter); + + const showGroups = !!groups.length; + return {t('Settings')} }/> - {groups.map(({ pathSection, pathGroup, name, permissionGranted }) => { - if (permissionGranted && !permissionGranted()) { return null; } - return - {name} - ; - })} + {showGroups && } + {!showGroups && {t('Nothing_found')}} ; }; export default function AdminSidebar() { const t = useTranslation(); - const sidebarItemsArray = useReactiveValue(() => sidebarItems.get()); - const [filter, setFilter] = useState(); - const settingsGroups = useSettingsGroupsFiltered(filter); + const canViewSettings = useAtLeastOnePermission(['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings']); const closeAdminFlex = useCallback(() => { if (Layout.isEmbedded()) { @@ -58,6 +97,9 @@ export default function AdminSidebar() { SideNav.closeFlex(); }, []); + const currentRoute = useCurrentRoute(); + const currentPath = useRoutePath(...currentRoute); + return {t('Administration')} @@ -65,8 +107,8 @@ export default function AdminSidebar() { - - + + {canViewSettings && } ; diff --git a/client/admin/sidebar/styles.css b/client/admin/sidebar/styles.css new file mode 100644 index 0000000000000..3ffbf4d02d048 --- /dev/null +++ b/client/admin/sidebar/styles.css @@ -0,0 +1,8 @@ +.rcx-sidebar--item { + &:hover { + background-color: var(--sidebar-background-light-hover); + } + &-active, &:active { + background-color: var(--sidebar-background-light-active); + } +} \ No newline at end of file diff --git a/client/admin/sidebar/useSettingsCollectionPrivate.js b/client/admin/sidebar/useSettingsGroupsFiltered.js similarity index 84% rename from client/admin/sidebar/useSettingsCollectionPrivate.js rename to client/admin/sidebar/useSettingsGroupsFiltered.js index 26ef9cd6c2e27..a6f7c7162eeb2 100644 --- a/client/admin/sidebar/useSettingsCollectionPrivate.js +++ b/client/admin/sidebar/useSettingsGroupsFiltered.js @@ -6,11 +6,11 @@ import { useTranslation } from '../../contexts/TranslationContext'; import { settings } from '../../../app/settings'; -export const useSettingsGroupsFiltered = (filterText) => { +export const useSettingsGroupsFiltered = (textFilter) => { const t = useTranslation(); const [collection, setCollection] = useState(settings.collectionPrivate); - const filter = useDebouncedValue(filterText, 400); + const filter = useDebouncedValue(textFilter, 400); useEffect(() => { (async () => { @@ -23,8 +23,8 @@ export const useSettingsGroupsFiltered = (filterText) => { })(); }, []); - const groups = useMemo(() => { - if (!settings.cachedCollectionPrivate) { return []; } + return useMemo(() => { + if (!collection) { return []; } const query = { type: 'group', }; @@ -56,10 +56,8 @@ export const useSettingsGroupsFiltered = (filterText) => { .sort(({ name: a }, { name: b }) => (a.toLowerCase() >= b.toLowerCase() ? 1 : -1)) .map(({ _id, name }) => ({ name, - pathSection: 'admin', + href: 'admin', pathGroup: _id, })); - }, [filter, settings.cachedCollectionPrivate]); - - return groups; + }, [filter, collection]); }; From 541ba8001cf452849647e835108ad33b6d3496bb Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Mon, 1 Jun 2020 15:58:55 -0300 Subject: [PATCH 03/21] lint --- client/admin/sidebar/styles.css | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/client/admin/sidebar/styles.css b/client/admin/sidebar/styles.css index 3ffbf4d02d048..520887b4336c3 100644 --- a/client/admin/sidebar/styles.css +++ b/client/admin/sidebar/styles.css @@ -1,8 +1,10 @@ .rcx-sidebar--item { - &:hover { - background-color: var(--sidebar-background-light-hover); - } - &-active, &:active { - background-color: var(--sidebar-background-light-active); - } -} \ No newline at end of file + &:hover { + background-color: var(--sidebar-background-light-hover); + } + + &-active, + &:active { + background-color: var(--sidebar-background-light-active); + } +} From a259ed8c348176e596766f814befcb59c0c81d54 Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Mon, 1 Jun 2020 20:06:41 -0300 Subject: [PATCH 04/21] Review semi-complete --- client/admin/AdministrationRouter.js | 3 ++- client/admin/sidebar/AdminSidebar.js | 16 ++++++++++++---- client/admin/sidebar/styles.css | 10 ---------- .../admin/sidebar/useSettingsGroupsFiltered.js | 4 ++-- 4 files changed, 16 insertions(+), 17 deletions(-) delete mode 100644 client/admin/sidebar/styles.css diff --git a/client/admin/AdministrationRouter.js b/client/admin/AdministrationRouter.js index fa72fa19db54d..211b1657d96df 100644 --- a/client/admin/AdministrationRouter.js +++ b/client/admin/AdministrationRouter.js @@ -6,7 +6,8 @@ import { createTemplateForComponent } from '../reactAdapters'; function AdministrationRouter({ lazyRouteComponent, ...props }) { useEffect(() => { - SideNav.setFlex(createTemplateForComponent('AdminSidebar', () => import('./sidebar/AdminSidebar'))); + const templateName = createTemplateForComponent('AdminSidebar', () => import('./sidebar/AdminSidebar')); + SideNav.setFlex(templateName); SideNav.openFlex(); }, []); diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index af9783d563d30..b983cc7fe8033 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -1,5 +1,6 @@ import React, { useCallback, useState, useMemo } from 'react'; import { Box, Button, Icon, SearchInput, Scrollable } from '@rocket.chat/fuselage'; +import { css } from '@rocket.chat/css-in-js'; import { menu, SideNav, Layout } from '../../../app/ui-utils/client'; import { useReactiveValue } from '../../hooks/useReactiveValue'; @@ -9,7 +10,15 @@ import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext'; import { sidebarItems } from '../sidebarItems'; import { useSettingsGroupsFiltered } from './useSettingsGroupsFiltered'; -import './styles.css'; +const style = css` + &:hover, + &.active:hover { + background-color: var(--sidebar-background-light-hover); + } + + &.active { + background-color: var(--sidebar-background-light-active); + }`; const SidebarItem = ({ permissionGranted, pathGroup, href, icon, label, currentPath }) => { if (permissionGranted && !permissionGranted()) { return null; } @@ -26,12 +35,11 @@ const SidebarItem = ({ permissionGranted, pathGroup, href, icon, label, currentP display='flex' flexDirection='row' alignItems='center' - rcx-sidebar--item - rcx-sidebar--item-active={isActive} + className={[isActive && 'active', style].filter(Boolean)} > {icon && } {label} - , [path, label, name, icon]); + , [path, label, name, icon, isActive]); }; const SidebarItemsAssembler = ({ items, currentPath }) => { diff --git a/client/admin/sidebar/styles.css b/client/admin/sidebar/styles.css deleted file mode 100644 index 520887b4336c3..0000000000000 --- a/client/admin/sidebar/styles.css +++ /dev/null @@ -1,10 +0,0 @@ -.rcx-sidebar--item { - &:hover { - background-color: var(--sidebar-background-light-hover); - } - - &-active, - &:active { - background-color: var(--sidebar-background-light-active); - } -} diff --git a/client/admin/sidebar/useSettingsGroupsFiltered.js b/client/admin/sidebar/useSettingsGroupsFiltered.js index a6f7c7162eeb2..f2ac9439e092e 100644 --- a/client/admin/sidebar/useSettingsGroupsFiltered.js +++ b/client/admin/sidebar/useSettingsGroupsFiltered.js @@ -13,14 +13,14 @@ export const useSettingsGroupsFiltered = (textFilter) => { const filter = useDebouncedValue(textFilter, 400); useEffect(() => { - (async () => { + (async function initCollection() { if (!settings.cachedCollectionPrivate) { settings.cachedCollectionPrivate = new PrivateSettingsCachedCollection(); settings.collectionPrivate = settings.cachedCollectionPrivate.collection; await settings.cachedCollectionPrivate.init(); } setCollection(settings.collectionPrivate); - })(); + }()); }, []); return useMemo(() => { From 147908f5fa28924d341794f16bbc526a5de2adb8 Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Thu, 4 Jun 2020 18:04:52 -0300 Subject: [PATCH 05/21] Fix scroll + added loading --- client/admin/sidebar/AdminSidebar.js | 13 ++++--- .../sidebar/useSettingsGroupsFiltered.js | 37 ++++++++++--------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index b983cc7fe8033..16e59c06ac92d 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -1,5 +1,5 @@ import React, { useCallback, useState, useMemo } from 'react'; -import { Box, Button, Icon, SearchInput, Scrollable } from '@rocket.chat/fuselage'; +import { Box, Button, Icon, SearchInput, Scrollable, Skeleton } from '@rocket.chat/fuselage'; import { css } from '@rocket.chat/css-in-js'; import { menu, SideNav, Layout } from '../../../app/ui-utils/client'; @@ -75,7 +75,7 @@ const AdminSidebarSettings = ({ currentPath }) => { const [filter, setFilter] = useState(''); const handleChange = useCallback((e) => setFilter(e.currentTarget.value), []); - const groups = useSettingsGroupsFiltered(filter); + const [groups, loading] = useSettingsGroupsFiltered(filter); const showGroups = !!groups.length; @@ -85,8 +85,9 @@ const AdminSidebarSettings = ({ currentPath }) => { }/> - {showGroups && } - {!showGroups && {t('Nothing_found')}} + {loading && } + {!loading && showGroups && } + {!loading && !showGroups && {t('Nothing_found')}} ; }; @@ -108,13 +109,13 @@ export default function AdminSidebar() { const currentRoute = useCurrentRoute(); const currentPath = useRoutePath(...currentRoute); - return + return {t('Administration')} - + {canViewSettings && } diff --git a/client/admin/sidebar/useSettingsGroupsFiltered.js b/client/admin/sidebar/useSettingsGroupsFiltered.js index f2ac9439e092e..db76fee318831 100644 --- a/client/admin/sidebar/useSettingsGroupsFiltered.js +++ b/client/admin/sidebar/useSettingsGroupsFiltered.js @@ -1,30 +1,34 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState, useEffect } from 'react'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import { PrivateSettingsCachedCollection } from '../PrivateSettingsCachedCollection'; -import { useTranslation } from '../../contexts/TranslationContext'; import { settings } from '../../../app/settings'; - +import { useTranslation } from '../../contexts/TranslationContext'; +import { PrivateSettingsCachedCollection } from '../PrivateSettingsCachedCollection'; export const useSettingsGroupsFiltered = (textFilter) => { const t = useTranslation(); + const [collection, setCollection] = useState(settings.collectionPrivate); + const [loading, setLoading] = useState(true); + const filter = useDebouncedValue(textFilter, 400); useEffect(() => { - (async function initCollection() { + (async function getCollection() { if (!settings.cachedCollectionPrivate) { settings.cachedCollectionPrivate = new PrivateSettingsCachedCollection(); settings.collectionPrivate = settings.cachedCollectionPrivate.collection; await settings.cachedCollectionPrivate.init(); } setCollection(settings.collectionPrivate); + setLoading(false); }()); }, []); return useMemo(() => { - if (!collection) { return []; } + if (loading) { return [[], loading]; } + const query = { type: 'group', }; @@ -35,10 +39,10 @@ export const useSettingsGroupsFiltered = (textFilter) => { const records = collection.find().fetch(); records.forEach(function(record) { if (filterRegex.test(t(record.i18nLabel || record._id))) { - groups.push(record.group || record._id); + !groups.includes(record.group || record._id) && groups.push(record.group || record._id); } }); - // groups = _.unique(groups); + if (groups.length > 0) { query._id = { $in: groups, @@ -47,17 +51,14 @@ export const useSettingsGroupsFiltered = (textFilter) => { } if (filter && groups.length === 0) { - return []; + return [[], loading]; } - return collection.find(query) + const result = collection.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, - href: 'admin', - pathGroup: _id, - })); - }, [filter, collection]); + .map((item) => ({ name: t(item.i18nLabel || item._id), href: 'admin', pathGroup: item._id })) + .sort(({ name: a }, { name: b }) => (a.toLowerCase() >= b.toLowerCase() ? 1 : -1)); + + return [result, loading]; + }, [filter, collection, loading]); }; From b6f4e83e8c5aa8b843da3944fd1b3e2f6d93fc55 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Mon, 8 Jun 2020 15:37:38 -0300 Subject: [PATCH 06/21] Use ghost close button --- client/admin/sidebar/AdminSidebar.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index 16e59c06ac92d..4631f729bedcc 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -112,7 +112,9 @@ export default function AdminSidebar() { return {t('Administration')} - + From 0aefa52e9f3c1c8e5d114387cac3958daeecc3cb Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Tue, 9 Jun 2020 13:33:00 -0300 Subject: [PATCH 07/21] Fix safari shrinking items. --- client/admin/sidebar/AdminSidebar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index 4631f729bedcc..7a8ece6a3850f 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -65,7 +65,7 @@ const SidebarItemsAssembler = ({ items, currentPath }) => { const AdminSidebarPages = ({ currentPath }) => { const items = useReactiveValue(() => sidebarItems.get()); - return + return {useMemo(() => , [items, currentPath])} ; }; @@ -79,7 +79,7 @@ const AdminSidebarSettings = ({ currentPath }) => { const showGroups = !!groups.length; - return + return {t('Settings')} }/> From 18e48c85e2e00a53ab016d5ff2c1758ee5cee617 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Tue, 9 Jun 2020 16:46:54 -0300 Subject: [PATCH 08/21] Remove unused webpack loader --- .storybook/webpack.config.js | 1 - client/admin/sidebar/AdminSidebar.js | 24 +-- package-lock.json | 225 --------------------------- package.json | 1 - 4 files changed, 13 insertions(+), 238 deletions(-) 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/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index 7a8ece6a3850f..a9c7fcb5be4b0 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -10,16 +10,6 @@ import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext'; import { sidebarItems } from '../sidebarItems'; import { useSettingsGroupsFiltered } from './useSettingsGroupsFiltered'; -const style = css` - &:hover, - &.active:hover { - background-color: var(--sidebar-background-light-hover); - } - - &.active { - background-color: var(--sidebar-background-light-active); - }`; - const SidebarItem = ({ permissionGranted, pathGroup, href, icon, label, currentPath }) => { if (permissionGranted && !permissionGranted()) { return null; } const params = useMemo(() => ({ group: pathGroup }), [pathGroup]); @@ -35,7 +25,19 @@ const SidebarItem = ({ permissionGranted, pathGroup, href, icon, label, currentP display='flex' flexDirection='row' alignItems='center' - className={[isActive && 'active', style].filter(Boolean)} + className={[ + isActive && 'active', + css` + &:hover, + &.active:hover { + background-color: var(--sidebar-background-light-hover); + } + + &.active { + background-color: var(--sidebar-background-light-active); + } + `, + ].filter(Boolean)} > {icon && } {label} diff --git a/package-lock.json b/package-lock.json index 0427269a54ab3..8fec42d7dc460 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6643,93 +6643,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 +12724,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 +13777,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 +13802,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 +14867,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 +20707,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 +22721,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 +26510,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 +30416,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..9a2640653dbbb 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,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", From 206c114762c3a86c5cf02aedc7dd40c1e20ac458 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Tue, 9 Jun 2020 17:19:59 -0300 Subject: [PATCH 09/21] Rename "PrivateSettings" as "PrivilegedSettings" --- client/admin/settings/GroupSelector.js | 2 +- ...State.js => PrivilegedSettingsProvider.js} | 15 ++----- client/admin/settings/Section.js | 2 +- client/admin/settings/Setting.js | 2 +- client/admin/settings/SettingsRoute.js | 6 +-- client/admin/sidebar/AdminSidebar.js | 4 +- .../sidebar/useSettingsGroupsFiltered.js | 2 +- ...ontext.ts => PrivilegedSettingsContext.ts} | 42 +++++++++---------- 8 files changed, 33 insertions(+), 42 deletions(-) rename client/admin/settings/{SettingsState.js => PrivilegedSettingsProvider.js} (90%) rename client/contexts/{PrivateSettingsContext.ts => PrivilegedSettingsContext.ts} (83%) diff --git a/client/admin/settings/GroupSelector.js b/client/admin/settings/GroupSelector.js index 2b4e9e45e4e66..9105e6fc41fce 100644 --- a/client/admin/settings/GroupSelector.js +++ b/client/admin/settings/GroupSelector.js @@ -1,6 +1,6 @@ import React from 'react'; -import { usePrivateSettingsGroup } from '../../contexts/PrivateSettingsContext'; +import { usePrivateSettingsGroup } from '../../contexts/PrivilegedSettingsContext'; import { AssetsGroupPage } from './groups/AssetsGroupPage'; import { OAuthGroupPage } from './groups/OAuthGroupPage'; import { GenericGroupPage } from './groups/GenericGroupPage'; diff --git a/client/admin/settings/SettingsState.js b/client/admin/settings/PrivilegedSettingsProvider.js similarity index 90% rename from client/admin/settings/SettingsState.js rename to client/admin/settings/PrivilegedSettingsProvider.js index ce4016cd27864..71517ac89bfb6 100644 --- a/client/admin/settings/SettingsState.js +++ b/client/admin/settings/PrivilegedSettingsProvider.js @@ -2,7 +2,7 @@ import { Mongo } from 'meteor/mongo'; import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; import { PrivateSettingsCachedCollection } from '../PrivateSettingsCachedCollection'; -import { PrivateSettingsContext } from '../../contexts/PrivateSettingsContext'; +import { PrivilegedSettingsContext } from '../../contexts/PrivilegedSettingsContext'; let privateSettingsCachedCollection; // Remove this singleton (╯°□°)╯︵ ┻━┻ @@ -79,7 +79,7 @@ const settingsReducer = (states, { type, payload }) => { return states; }; -export function SettingsState({ children }) { +export function PrivilegedSettingsProvider({ children }) { const [isLoading, setLoading] = useState(true); const [subscribers] = useState(new Set()); @@ -204,14 +204,5 @@ export function SettingsState({ children }) { isDisabled, ]); - return ; + return ; } - -export { - usePrivateSettingsGroup as useGroup, - usePrivateSettingsSection as useSection, - usePrivateSettingActions as useSettingActions, - usePrivateSettingDisabledState as useSettingDisabledState, - usePrivateSettingsSectionChangedState as useSectionChangedState, - usePrivateSetting as useSetting, -} from '../../contexts/PrivateSettingsContext'; diff --git a/client/admin/settings/Section.js b/client/admin/settings/Section.js index e249c7ca04d95..22cbe1dd29101 100644 --- a/client/admin/settings/Section.js +++ b/client/admin/settings/Section.js @@ -4,7 +4,7 @@ import React from 'react'; import { usePrivateSettingsSection, usePrivateSettingsSectionChangedState, -} from '../../contexts/PrivateSettingsContext'; +} from '../../contexts/PrivilegedSettingsContext'; import { useTranslation } from '../../contexts/TranslationContext'; import { Setting } from './Setting'; diff --git a/client/admin/settings/Setting.js b/client/admin/settings/Setting.js index 86add049c19f1..d85bc2d70d030 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 { usePrivateSetting } from '../../contexts/PrivilegedSettingsContext'; import { useTranslation } from '../../contexts/TranslationContext'; import { GenericSettingInput } from './inputs/GenericSettingInput'; import { BooleanSettingInput } from './inputs/BooleanSettingInput'; diff --git a/client/admin/settings/SettingsRoute.js b/client/admin/settings/SettingsRoute.js index 42f5323f10440..00ab8311a7ccc 100644 --- a/client/admin/settings/SettingsRoute.js +++ b/client/admin/settings/SettingsRoute.js @@ -4,7 +4,7 @@ import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext'; import { useRouteParameter } from '../../contexts/RouterContext'; import { GroupSelector } from './GroupSelector'; import NotAuthorizedPage from '../NotAuthorizedPage'; -import { SettingsState } from './SettingsState'; +import { PrivilegedSettingsProvider } from './PrivilegedSettingsProvider'; export function SettingsRoute() { const hasPermission = useAtLeastOnePermission([ @@ -19,9 +19,9 @@ export function SettingsRoute() { return ; } - return + return - ; + ; } export default SettingsRoute; diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index a9c7fcb5be4b0..996a2b7c4b4c6 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -8,7 +8,7 @@ import { useTranslation } from '../../contexts/TranslationContext'; import { useRoutePath, useCurrentRoute } from '../../contexts/RouterContext'; import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext'; import { sidebarItems } from '../sidebarItems'; -import { useSettingsGroupsFiltered } from './useSettingsGroupsFiltered'; +import { usePrivateSettingsGroupsFiltered } from './useSettingsGroupsFiltered'; const SidebarItem = ({ permissionGranted, pathGroup, href, icon, label, currentPath }) => { if (permissionGranted && !permissionGranted()) { return null; } @@ -77,7 +77,7 @@ const AdminSidebarSettings = ({ currentPath }) => { const [filter, setFilter] = useState(''); const handleChange = useCallback((e) => setFilter(e.currentTarget.value), []); - const [groups, loading] = useSettingsGroupsFiltered(filter); + const [groups, loading] = usePrivateSettingsGroupsFiltered(filter); const showGroups = !!groups.length; diff --git a/client/admin/sidebar/useSettingsGroupsFiltered.js b/client/admin/sidebar/useSettingsGroupsFiltered.js index db76fee318831..7ed89474644ea 100644 --- a/client/admin/sidebar/useSettingsGroupsFiltered.js +++ b/client/admin/sidebar/useSettingsGroupsFiltered.js @@ -5,7 +5,7 @@ import { settings } from '../../../app/settings'; import { useTranslation } from '../../contexts/TranslationContext'; import { PrivateSettingsCachedCollection } from '../PrivateSettingsCachedCollection'; -export const useSettingsGroupsFiltered = (textFilter) => { +export const usePrivateSettingsGroupsFiltered = (textFilter) => { const t = useTranslation(); const [collection, setCollection] = useState(settings.collectionPrivate); diff --git a/client/contexts/PrivateSettingsContext.ts b/client/contexts/PrivilegedSettingsContext.ts similarity index 83% rename from client/contexts/PrivateSettingsContext.ts rename to client/contexts/PrivilegedSettingsContext.ts index e900175f54245..4dc112af364ac 100644 --- a/client/contexts/PrivateSettingsContext.ts +++ b/client/contexts/PrivilegedSettingsContext.ts @@ -8,7 +8,7 @@ import { useToastMessageDispatch } from './ToastMessagesContext'; import { useTranslation, useLoadLanguage } from './TranslationContext'; import { useUser } from './UserContext'; -type Setting = object & { +type PrivilegedSetting = object & { _id: unknown; type: string; blocked: boolean; @@ -25,22 +25,22 @@ type Setting = object & { reset?: () => void; }; -type PrivateSettingsState = { - settings: Setting[]; - persistedSettings: Setting[]; +type PrivilegedSettingsState = { + settings: PrivilegedSetting[]; + persistedSettings: PrivilegedSetting[]; }; type EqualityFunction = (a: T, b: T) => boolean; -type PrivateSettingsContextValue = { - subscribers: Set<(state: PrivateSettingsState) => void>; - stateRef: RefObject; +type PrivilegedSettingsContextValue = { + 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({ + subscribers: new Set<(state: PrivilegedSettingsState) => void>(), stateRef: { current: { settings: [], @@ -52,13 +52,13 @@ export const PrivateSettingsContext = createContext }); 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)) { @@ -90,7 +90,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; @@ -157,7 +157,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 +186,11 @@ export const usePrivateSettingsSection = (groupId: string, sectionName?: string) }; }; -export const usePrivateSettingActions = (persistedSetting: Setting | null | undefined): { +export const usePrivateSettingActions = (persistedSetting: PrivilegedSetting | null | undefined): { update: () => void; reset: () => void; } => { - const { hydrate } = useContext(PrivateSettingsContext); + const { hydrate } = useContext(PrivilegedSettingsContext); const update = useDebouncedCallback(({ value, editor }) => { const changes = [{ @@ -217,8 +217,8 @@ export const usePrivateSettingActions = (persistedSetting: Setting | null | unde return { update, reset }; }; -export const usePrivateSettingDisabledState = (setting: Setting | null | undefined): boolean => { - const { isDisabled } = useContext(PrivateSettingsContext); +export const usePrivateSettingDisabledState = (setting: PrivilegedSetting | null | undefined): boolean => { + const { isDisabled } = useContext(PrivilegedSettingsContext); return useReactiveValue(() => (setting ? isDisabled(setting) : false), [setting?.blocked, setting?.enableQuery]) as unknown as boolean; }; @@ -227,8 +227,8 @@ export const usePrivateSettingsSectionChangedState = (groupId: string, sectionNa 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 usePrivateSetting = (_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)); From 109b47733a0c3679a17fd299faaa6b5822a15085 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Tue, 9 Jun 2020 17:40:44 -0300 Subject: [PATCH 10/21] Uplift PrivilegedSettingsProvider --- client/admin/AdministrationRouter.js | 5 ++- .../PrivilegedSettingsProvider.js | 24 +++++++++++-- client/admin/settings/GroupSelector.js | 4 +-- client/admin/settings/Section.js | 8 ++--- client/admin/settings/Setting.js | 4 +-- client/admin/settings/SettingsRoute.js | 13 ++----- client/admin/sidebar/AdminSidebar.js | 34 +++++++++++-------- .../sidebar/useSettingsGroupsFiltered.js | 2 +- client/contexts/PrivilegedSettingsContext.ts | 20 ++++++----- 9 files changed, 68 insertions(+), 46 deletions(-) rename client/admin/{settings => }/PrivilegedSettingsProvider.js (88%) diff --git a/client/admin/AdministrationRouter.js b/client/admin/AdministrationRouter.js index 211b1657d96df..2ebc3c61dba79 100644 --- a/client/admin/AdministrationRouter.js +++ b/client/admin/AdministrationRouter.js @@ -3,6 +3,7 @@ import React, { lazy, useMemo, Suspense, useEffect } from 'react'; import { SideNav } from '../../app/ui-utils/client'; import PageSkeleton from './PageSkeleton'; import { createTemplateForComponent } from '../reactAdapters'; +import PrivilegedSettingsProvider from './PrivilegedSettingsProvider'; function AdministrationRouter({ lazyRouteComponent, ...props }) { useEffect(() => { @@ -14,7 +15,9 @@ function AdministrationRouter({ lazyRouteComponent, ...props }) { const LazyRouteComponent = useMemo(() => lazy(lazyRouteComponent), [lazyRouteComponent]); return }> - + + + ; } diff --git a/client/admin/settings/PrivilegedSettingsProvider.js b/client/admin/PrivilegedSettingsProvider.js similarity index 88% rename from client/admin/settings/PrivilegedSettingsProvider.js rename to client/admin/PrivilegedSettingsProvider.js index 71517ac89bfb6..6dd7461f787b3 100644 --- a/client/admin/settings/PrivilegedSettingsProvider.js +++ b/client/admin/PrivilegedSettingsProvider.js @@ -1,8 +1,9 @@ import { Mongo } from 'meteor/mongo'; import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; -import { PrivateSettingsCachedCollection } from '../PrivateSettingsCachedCollection'; -import { PrivilegedSettingsContext } from '../../contexts/PrivilegedSettingsContext'; +import { PrivateSettingsCachedCollection } from './PrivateSettingsCachedCollection'; +import { PrivilegedSettingsContext } from '../contexts/PrivilegedSettingsContext'; +import { useAtLeastOnePermission } from '../contexts/AuthorizationContext'; let privateSettingsCachedCollection; // Remove this singleton (╯°□°)╯︵ ┻━┻ @@ -79,7 +80,7 @@ const settingsReducer = (states, { type, payload }) => { return states; }; -export function PrivilegedSettingsProvider({ children }) { +function AuthorizedPrivilegedSettingsProvider({ children }) { const [isLoading, setLoading] = useState(true); const [subscribers] = useState(new Set()); @@ -193,6 +194,7 @@ export function PrivilegedSettingsProvider({ children }) { }, [collectionsRef]); const contextValue = useMemo(() => ({ + authorized: true, subscribers, stateRef, hydrate, @@ -206,3 +208,19 @@ export function PrivilegedSettingsProvider({ children }) { return ; } + +function PrivilegedSettingsProvider({ children }) { + const hasPermission = useAtLeastOnePermission([ + 'view-privileged-setting', + 'edit-privileged-setting', + 'manage-selected-settings', + ]); + + if (!hasPermission) { + return children; + } + + return ; +} + +export default PrivilegedSettingsProvider; diff --git a/client/admin/settings/GroupSelector.js b/client/admin/settings/GroupSelector.js index 9105e6fc41fce..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/PrivilegedSettingsContext'; +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 22cbe1dd29101..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, + 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 d85bc2d70d030..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/PrivilegedSettingsContext'; +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 00ab8311a7ccc..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 { PrivilegedSettingsProvider } from './PrivilegedSettingsProvider'; 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 index 996a2b7c4b4c6..0f9ef21b058e2 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -8,7 +8,8 @@ import { useTranslation } from '../../contexts/TranslationContext'; import { useRoutePath, useCurrentRoute } from '../../contexts/RouterContext'; import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext'; import { sidebarItems } from '../sidebarItems'; -import { usePrivateSettingsGroupsFiltered } from './useSettingsGroupsFiltered'; +import { usePrivilegedSettingsGroupsFiltered } from './useSettingsGroupsFiltered'; +import PrivilegedSettingsProvider from '../PrivilegedSettingsProvider'; const SidebarItem = ({ permissionGranted, pathGroup, href, icon, label, currentPath }) => { if (permissionGranted && !permissionGranted()) { return null; } @@ -77,7 +78,7 @@ const AdminSidebarSettings = ({ currentPath }) => { const [filter, setFilter] = useState(''); const handleChange = useCallback((e) => setFilter(e.currentTarget.value), []); - const [groups, loading] = usePrivateSettingsGroupsFiltered(filter); + const [groups, loading] = usePrivilegedSettingsGroupsFiltered(filter); const showGroups = !!groups.length; @@ -111,18 +112,21 @@ export default function AdminSidebar() { const currentRoute = useCurrentRoute(); const currentPath = useRoutePath(...currentRoute); - return - - {t('Administration')} - - - - - - {canViewSettings && } + // TODO: uplift this provider + return + + + {t('Administration')} + - - ; + + + + {canViewSettings && } + + + + ; } diff --git a/client/admin/sidebar/useSettingsGroupsFiltered.js b/client/admin/sidebar/useSettingsGroupsFiltered.js index 7ed89474644ea..a0eabbf408e21 100644 --- a/client/admin/sidebar/useSettingsGroupsFiltered.js +++ b/client/admin/sidebar/useSettingsGroupsFiltered.js @@ -5,7 +5,7 @@ import { settings } from '../../../app/settings'; import { useTranslation } from '../../contexts/TranslationContext'; import { PrivateSettingsCachedCollection } from '../PrivateSettingsCachedCollection'; -export const usePrivateSettingsGroupsFiltered = (textFilter) => { +export const usePrivilegedSettingsGroupsFiltered = (textFilter) => { const t = useTranslation(); const [collection, setCollection] = useState(settings.collectionPrivate); diff --git a/client/contexts/PrivilegedSettingsContext.ts b/client/contexts/PrivilegedSettingsContext.ts index 4dc112af364ac..74a6253d0dc8a 100644 --- a/client/contexts/PrivilegedSettingsContext.ts +++ b/client/contexts/PrivilegedSettingsContext.ts @@ -33,6 +33,7 @@ type PrivilegedSettingsState = { type EqualityFunction = (a: T, b: T) => boolean; type PrivilegedSettingsContextValue = { + authorized: boolean; subscribers: Set<(state: PrivilegedSettingsState) => void>; stateRef: RefObject; hydrate: (changes: any[]) => void; @@ -40,6 +41,7 @@ type PrivilegedSettingsContextValue = { }; export const PrivilegedSettingsContext = createContext({ + authorized: false, subscribers: new Set<(state: PrivilegedSettingsState) => void>(), stateRef: { current: { @@ -51,6 +53,8 @@ export const PrivilegedSettingsContext = createContext false, }); +export const usePrivilegedSettingsAuthorized = (): boolean => useContext(PrivilegedSettingsContext).authorized; + const useSelector = ( selector: (state: PrivilegedSettingsState) => T, equalityFunction: EqualityFunction = Object.is, @@ -81,7 +85,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); @@ -148,7 +152,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[] => @@ -186,7 +190,7 @@ export const usePrivateSettingsSection = (groupId: string, sectionName?: string) }; }; -export const usePrivateSettingActions = (persistedSetting: PrivilegedSetting | null | undefined): { +export const usePrivilegedSettingActions = (persistedSetting: PrivilegedSetting | null | undefined): { update: () => void; reset: () => void; } => { @@ -217,24 +221,24 @@ export const usePrivateSettingActions = (persistedSetting: PrivilegedSetting | n return { update, reset }; }; -export const usePrivateSettingDisabledState = (setting: PrivilegedSetting | null | undefined): boolean => { +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): PrivilegedSetting | null | undefined => { +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; From 808a2cd6c8247ef9f9177e1fe8dccd6ad9e6220f Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 10 Jun 2020 00:57:20 -0300 Subject: [PATCH 11/21] Replace adminFlex template --- app/ui-sidenav/client/sidebarHeader.js | 2 - client/admin/AdministrationRouter.js | 5 +- client/admin/adminFlex.html | 42 ------------ client/admin/adminFlex.js | 92 -------------------------- client/admin/index.js | 2 - 5 files changed, 3 insertions(+), 140 deletions(-) delete mode 100644 client/admin/adminFlex.html delete mode 100644 client/admin/adminFlex.js 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/client/admin/AdministrationRouter.js b/client/admin/AdministrationRouter.js index 2ebc3c61dba79..9a9b1535fd063 100644 --- a/client/admin/AdministrationRouter.js +++ b/client/admin/AdministrationRouter.js @@ -5,10 +5,11 @@ import PageSkeleton from './PageSkeleton'; import { createTemplateForComponent } from '../reactAdapters'; import PrivilegedSettingsProvider from './PrivilegedSettingsProvider'; +createTemplateForComponent('adminFlex', () => import('./sidebar/AdminSidebar')); + function AdministrationRouter({ lazyRouteComponent, ...props }) { useEffect(() => { - const templateName = createTemplateForComponent('AdminSidebar', () => import('./sidebar/AdminSidebar')); - SideNav.setFlex(templateName); + SideNav.setFlex('adminFlex'); SideNav.openFlex(); }, []); 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'; From 8d6f8eb7c7c5c93602c1f2ef0046de4386ed846d Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 10 Jun 2020 01:02:47 -0300 Subject: [PATCH 12/21] Merge hook for sidebar into PrivilegedSettingsContext --- client/admin/sidebar/AdminSidebar.js | 36 +++++++---- .../sidebar/useSettingsGroupsFiltered.js | 64 ------------------- client/contexts/PrivilegedSettingsContext.ts | 53 +++++++++++++-- 3 files changed, 72 insertions(+), 81 deletions(-) delete mode 100644 client/admin/sidebar/useSettingsGroupsFiltered.js diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index 0f9ef21b058e2..660dfa09b0e17 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -1,6 +1,7 @@ -import React, { useCallback, useState, useMemo } from 'react'; -import { Box, Button, Icon, SearchInput, Scrollable, Skeleton } from '@rocket.chat/fuselage'; 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 } from 'react'; import { menu, SideNav, Layout } from '../../../app/ui-utils/client'; import { useReactiveValue } from '../../hooks/useReactiveValue'; @@ -8,8 +9,8 @@ import { useTranslation } from '../../contexts/TranslationContext'; import { useRoutePath, useCurrentRoute } from '../../contexts/RouterContext'; import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext'; import { sidebarItems } from '../sidebarItems'; -import { usePrivilegedSettingsGroupsFiltered } from './useSettingsGroupsFiltered'; import PrivilegedSettingsProvider from '../PrivilegedSettingsProvider'; +import { usePrivilegedSettingsGroups } from '../../contexts/PrivilegedSettingsContext'; const SidebarItem = ({ permissionGranted, pathGroup, href, icon, label, currentPath }) => { if (permissionGranted && !permissionGranted()) { return null; } @@ -68,7 +69,7 @@ const SidebarItemsAssembler = ({ items, currentPath }) => { const AdminSidebarPages = ({ currentPath }) => { const items = useReactiveValue(() => sidebarItems.get()); - return + return {useMemo(() => , [items, currentPath])} ; }; @@ -78,19 +79,30 @@ const AdminSidebarSettings = ({ currentPath }) => { const [filter, setFilter] = useState(''); const handleChange = useCallback((e) => setFilter(e.currentTarget.value), []); - const [groups, loading] = usePrivilegedSettingsGroupsFiltered(filter); - - const showGroups = !!groups.length; + const groups = usePrivilegedSettingsGroups(useDebouncedValue(filter, 400)); + const isLoadingGroups = false; // TODO: get from PrivilegedSettingsContext return {t('Settings')} - }/> + } + className={['asdsads']} + /> - - {loading && } - {!loading && showGroups && } - {!loading && !showGroups && {t('Nothing_found')}} + + {isLoadingGroups && } + {!isLoadingGroups && !!groups.length && ({ + name: t(group.i18nLabel || group._id), + href: 'admin', + pathGroup: group._id, + }))} + currentPath={currentPath} + />} + {!isLoadingGroups && !groups.length && {t('Nothing_found')}} ; }; diff --git a/client/admin/sidebar/useSettingsGroupsFiltered.js b/client/admin/sidebar/useSettingsGroupsFiltered.js deleted file mode 100644 index a0eabbf408e21..0000000000000 --- a/client/admin/sidebar/useSettingsGroupsFiltered.js +++ /dev/null @@ -1,64 +0,0 @@ -import { useMemo, useState, useEffect } from 'react'; -import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; - -import { settings } from '../../../app/settings'; -import { useTranslation } from '../../contexts/TranslationContext'; -import { PrivateSettingsCachedCollection } from '../PrivateSettingsCachedCollection'; - -export const usePrivilegedSettingsGroupsFiltered = (textFilter) => { - const t = useTranslation(); - - const [collection, setCollection] = useState(settings.collectionPrivate); - - const [loading, setLoading] = useState(true); - - const filter = useDebouncedValue(textFilter, 400); - - useEffect(() => { - (async function getCollection() { - if (!settings.cachedCollectionPrivate) { - settings.cachedCollectionPrivate = new PrivateSettingsCachedCollection(); - settings.collectionPrivate = settings.cachedCollectionPrivate.collection; - await settings.cachedCollectionPrivate.init(); - } - setCollection(settings.collectionPrivate); - setLoading(false); - }()); - }, []); - - return useMemo(() => { - if (loading) { return [[], loading]; } - - const query = { - type: 'group', - }; - - const groups = []; - if (filter) { - const filterRegex = new RegExp(filter, 'i'); - const records = collection.find().fetch(); - records.forEach(function(record) { - if (filterRegex.test(t(record.i18nLabel || record._id))) { - !groups.includes(record.group || record._id) && groups.push(record.group || record._id); - } - }); - - if (groups.length > 0) { - query._id = { - $in: groups, - }; - } - } - - if (filter && groups.length === 0) { - return [[], loading]; - } - - const result = collection.find(query) - .fetch() - .map((item) => ({ name: t(item.i18nLabel || item._id), href: 'admin', pathGroup: item._id })) - .sort(({ name: a }, { name: b }) => (a.toLowerCase() >= b.toLowerCase() ? 1 : -1)); - - return [result, loading]; - }, [filter, collection, loading]); -}; diff --git a/client/contexts/PrivilegedSettingsContext.ts b/client/contexts/PrivilegedSettingsContext.ts index 74a6253d0dc8a..2904eac72463b 100644 --- a/client/contexts/PrivilegedSettingsContext.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 PrivilegedSetting = object & { - _id: unknown; +export type PrivilegedSetting = object & { + _id: string; type: string; blocked: boolean; enableQuery: unknown; @@ -20,12 +21,14 @@ type PrivilegedSetting = object & { packageValue: unknown; packageEditor: unknown; editor: unknown; + sorter: string; + i18nLabel: string; disabled?: boolean; update?: () => void; reset?: () => void; }; -type PrivilegedSettingsState = { +export type PrivilegedSettingsState = { settings: PrivilegedSetting[]; persistedSettings: PrivilegedSetting[]; }; @@ -53,7 +56,47 @@ export const PrivilegedSettingsContext = createContext false, }); -export const usePrivilegedSettingsAuthorized = (): boolean => useContext(PrivilegedSettingsContext).authorized; +export const usePrivilegedSettingsAuthorized = (): boolean => + useContext(PrivilegedSettingsContext).authorized; + +export const usePrivilegedSettingsGroups = (filter?: string): any => { + const { subscribers, stateRef } = 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: PrivilegedSettingsState) => T, From 0f49120b8db4bbc2d9cc71c16d335fcf5fed8095 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 10 Jun 2020 13:09:36 -0300 Subject: [PATCH 13/21] Use PrivateSettingsCachedCollection as a singleton --- client/admin/AdministrationRouter.js | 8 ++--- .../admin/PrivateSettingsCachedCollection.js | 20 ----------- .../admin/PrivateSettingsCachedCollection.ts | 29 ++++++++++++++++ client/admin/PrivilegedSettingsProvider.js | 34 ++++++++----------- client/contexts/PrivilegedSettingsContext.ts | 7 +++- 5 files changed, 54 insertions(+), 44 deletions(-) delete mode 100644 client/admin/PrivateSettingsCachedCollection.js create mode 100644 client/admin/PrivateSettingsCachedCollection.ts diff --git a/client/admin/AdministrationRouter.js b/client/admin/AdministrationRouter.js index 9a9b1535fd063..0bcc4d5d767d7 100644 --- a/client/admin/AdministrationRouter.js +++ b/client/admin/AdministrationRouter.js @@ -15,11 +15,11 @@ function AdministrationRouter({ lazyRouteComponent, ...props }) { 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/PrivilegedSettingsProvider.js b/client/admin/PrivilegedSettingsProvider.js index 6dd7461f787b3..3f2873cc245a3 100644 --- a/client/admin/PrivilegedSettingsProvider.js +++ b/client/admin/PrivilegedSettingsProvider.js @@ -1,21 +1,10 @@ import { Mongo } from 'meteor/mongo'; +import { Tracker } from 'meteor/tracker'; import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; -import { PrivateSettingsCachedCollection } from './PrivateSettingsCachedCollection'; import { PrivilegedSettingsContext } from '../contexts/PrivilegedSettingsContext'; import { useAtLeastOnePermission } from '../contexts/AuthorizationContext'; - -let privateSettingsCachedCollection; // Remove this singleton (╯°□°)╯︵ ┻━┻ - -const getPrivateSettingsCachedCollection = () => { - if (privateSettingsCachedCollection) { - return [privateSettingsCachedCollection, Promise.resolve()]; - } - - privateSettingsCachedCollection = new PrivateSettingsCachedCollection(); - - return [privateSettingsCachedCollection, privateSettingsCachedCollection.init()]; -}; +import { PrivateSettingsCachedCollection } from './PrivateSettingsCachedCollection'; const compareStrings = (a = '', b = '') => { if (a === b || (!a && !b)) { @@ -80,7 +69,7 @@ const settingsReducer = (states, { type, payload }) => { return states; }; -function AuthorizedPrivilegedSettingsProvider({ children }) { +function AuthorizedPrivilegedSettingsProvider({ cachedCollection, children }) { const [isLoading, setLoading] = useState(true); const [subscribers] = useState(new Set()); @@ -104,15 +93,17 @@ function AuthorizedPrivilegedSettingsProvider({ children }) { 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 = { @@ -195,11 +186,13 @@ function AuthorizedPrivilegedSettingsProvider({ children }) { const contextValue = useMemo(() => ({ authorized: true, + loading: isLoading, subscribers, stateRef, hydrate, isDisabled, }), [ + isLoading, subscribers, stateRef, hydrate, @@ -220,7 +213,10 @@ function PrivilegedSettingsProvider({ children }) { return children; } - return ; + return ; } export default PrivilegedSettingsProvider; diff --git a/client/contexts/PrivilegedSettingsContext.ts b/client/contexts/PrivilegedSettingsContext.ts index 2904eac72463b..7031c71db4d24 100644 --- a/client/contexts/PrivilegedSettingsContext.ts +++ b/client/contexts/PrivilegedSettingsContext.ts @@ -37,6 +37,7 @@ type EqualityFunction = (a: T, b: T) => boolean; type PrivilegedSettingsContextValue = { authorized: boolean; + loading: boolean; subscribers: Set<(state: PrivilegedSettingsState) => void>; stateRef: RefObject; hydrate: (changes: any[]) => void; @@ -45,6 +46,7 @@ type PrivilegedSettingsContextValue = { export const PrivilegedSettingsContext = createContext({ authorized: false, + loading: false, subscribers: new Set<(state: PrivilegedSettingsState) => void>(), stateRef: { current: { @@ -59,8 +61,11 @@ export const PrivilegedSettingsContext = createContext useContext(PrivilegedSettingsContext).authorized; +export const useIsPrivilegedSettingsLoading = (): boolean => + useContext(PrivilegedSettingsContext).loading; + export const usePrivilegedSettingsGroups = (filter?: string): any => { - const { subscribers, stateRef } = useContext(PrivilegedSettingsContext); + const { stateRef, subscribers } = useContext(PrivilegedSettingsContext); const t = useTranslation(); const getCurrentValue = useCallback(() => { From 12b250fafd3596f3d4e88b3e4e130774e2b079e9 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 10 Jun 2020 14:54:58 -0300 Subject: [PATCH 14/21] Replace callback invalidations --- client/admin/PrivilegedSettingsProvider.js | 41 +++++++++----------- client/contexts/PrivilegedSettingsContext.ts | 1 + 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/client/admin/PrivilegedSettingsProvider.js b/client/admin/PrivilegedSettingsProvider.js index 3f2873cc245a3..911cd00e859d8 100644 --- a/client/admin/PrivilegedSettingsProvider.js +++ b/client/admin/PrivilegedSettingsProvider.js @@ -1,6 +1,7 @@ +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { Mongo } from 'meteor/mongo'; import { Tracker } from 'meteor/tracker'; -import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useReducer, useRef, useState } from 'react'; import { PrivilegedSettingsContext } from '../contexts/PrivilegedSettingsContext'; import { useAtLeastOnePermission } from '../contexts/AuthorizationContext'; @@ -72,23 +73,19 @@ const settingsReducer = (states, { type, payload }) => { 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); - }); + const [state, dispatch] = useReducer(settingsReducer, { settings: [], persistedSettings: [] }); + stateRef.current = state; - return newState; - }, [settingsReducer, subscribers]); - - const [, dispatch] = useReducer(enhancedReducer, { settings: [], persistedSettings: [] }); + subscribersRef.current.forEach((subscriber) => { + subscriber(state); + }); const collectionsRef = useRef({}); @@ -155,21 +152,21 @@ function AuthorizedPrivilegedSettingsProvider({ cachedCollection, 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; } @@ -182,19 +179,17 @@ function AuthorizedPrivilegedSettingsProvider({ cachedCollection, children }) { const queries = [].concat(typeof enableQuery === 'string' ? JSON.parse(enableQuery) : enableQuery); return !queries.every((query) => !!settingsCollection.findOne(query)); - }, [collectionsRef]); + }); const contextValue = useMemo(() => ({ authorized: true, loading: isLoading, - subscribers, + subscribers: subscribersRef.current, stateRef, hydrate, isDisabled, }), [ isLoading, - subscribers, - stateRef, hydrate, isDisabled, ]); diff --git a/client/contexts/PrivilegedSettingsContext.ts b/client/contexts/PrivilegedSettingsContext.ts index 7031c71db4d24..da463d6de2da4 100644 --- a/client/contexts/PrivilegedSettingsContext.ts +++ b/client/contexts/PrivilegedSettingsContext.ts @@ -35,6 +35,7 @@ export type PrivilegedSettingsState = { type EqualityFunction = (a: T, b: T) => boolean; +// TODO: split editing into another context type PrivilegedSettingsContextValue = { authorized: boolean; loading: boolean; From b21cbe4ca3db60abd89bd3811239c646513e6f0a Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 10 Jun 2020 18:07:12 -0300 Subject: [PATCH 15/21] Add ReactDOM types --- package-lock.json | 9 +++++++++ package.json | 1 + 2 files changed, 10 insertions(+) diff --git a/package-lock.json b/package-lock.json index 8fec42d7dc460..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", diff --git a/package.json b/package.json index 9a2640653dbbb..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", From 42063ee6871547357cfe6fa21f7d1185f5ff0d0e Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 10 Jun 2020 18:59:49 -0300 Subject: [PATCH 16/21] Add scaffolding of AdministrationLayout --- client/admin/AdministrationLayout.tsx | 16 +++++++++++ client/admin/AdministrationRouter.js | 22 ++++++--------- client/admin/LegacyRoute.tsx | 40 +++++++++++++++++++++++++++ client/admin/sidebarItems.js | 3 ++ 4 files changed, 67 insertions(+), 14 deletions(-) create mode 100644 client/admin/AdministrationLayout.tsx create mode 100644 client/admin/LegacyRoute.tsx 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 0bcc4d5d767d7..0812ca904a6aa 100644 --- a/client/admin/AdministrationRouter.js +++ b/client/admin/AdministrationRouter.js @@ -1,24 +1,18 @@ -import React, { lazy, useMemo, Suspense, useEffect } from 'react'; +import React, { lazy, useMemo, Suspense } from 'react'; -import { SideNav } from '../../app/ui-utils/client'; -import PageSkeleton from './PageSkeleton'; -import { createTemplateForComponent } from '../reactAdapters'; +import AdministrationLayout from './AdministrationLayout'; import PrivilegedSettingsProvider from './PrivilegedSettingsProvider'; - -createTemplateForComponent('adminFlex', () => import('./sidebar/AdminSidebar')); +import PageSkeleton from './PageSkeleton'; function AdministrationRouter({ lazyRouteComponent, ...props }) { - useEffect(() => { - SideNav.setFlex('adminFlex'); - SideNav.openFlex(); - }, []); - const LazyRouteComponent = useMemo(() => lazy(lazyRouteComponent), [lazyRouteComponent]); return - }> - - + + }> + + + ; } diff --git a/client/admin/LegacyRoute.tsx b/client/admin/LegacyRoute.tsx new file mode 100644 index 0000000000000..82fea2d5fc640 --- /dev/null +++ b/client/admin/LegacyRoute.tsx @@ -0,0 +1,40 @@ +import React, { useEffect, useRef, FC, useState } from 'react'; +import { Blaze } from 'meteor/blaze'; +import { Template } from 'meteor/templating'; + +import PageSkeleton from './PageSkeleton'; + +type LegacyRouteProps = { + action: () => Promise; +}; + +const LegacyRoute: FC = ({ action }) => { + const [template, setTemplate] = useState(); + const wrapperRef = useRef(null); + + useEffect(() => { + let mounted = true; + let view: Blaze.View; + + action().then((templateName) => { + if (!mounted || !wrapperRef.current || !templateName) { + return; + } + + setTemplate(Template[templateName]); + view = Blaze.render(Template[templateName], wrapperRef.current); + }); + + return (): void => { + mounted = false; + Blaze.remove(view); + }; + }, [action]); + + return <> +
+ {!template && } + ; +}; + +export default LegacyRoute; 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', From 54fd0cd72cb3a9548dc825c37411908413b7bd2b Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 10 Jun 2020 22:50:46 -0300 Subject: [PATCH 17/21] Remove LegacyRoute --- client/admin/LegacyRoute.tsx | 40 ------------------------------------ 1 file changed, 40 deletions(-) delete mode 100644 client/admin/LegacyRoute.tsx diff --git a/client/admin/LegacyRoute.tsx b/client/admin/LegacyRoute.tsx deleted file mode 100644 index 82fea2d5fc640..0000000000000 --- a/client/admin/LegacyRoute.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React, { useEffect, useRef, FC, useState } from 'react'; -import { Blaze } from 'meteor/blaze'; -import { Template } from 'meteor/templating'; - -import PageSkeleton from './PageSkeleton'; - -type LegacyRouteProps = { - action: () => Promise; -}; - -const LegacyRoute: FC = ({ action }) => { - const [template, setTemplate] = useState(); - const wrapperRef = useRef(null); - - useEffect(() => { - let mounted = true; - let view: Blaze.View; - - action().then((templateName) => { - if (!mounted || !wrapperRef.current || !templateName) { - return; - } - - setTemplate(Template[templateName]); - view = Blaze.render(Template[templateName], wrapperRef.current); - }); - - return (): void => { - mounted = false; - Blaze.remove(view); - }; - }, [action]); - - return <> -
- {!template && } - ; -}; - -export default LegacyRoute; From d27ac405d3da56c5100fb677bbb29a6b10e8fa39 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 10 Jun 2020 23:59:39 -0300 Subject: [PATCH 18/21] Wait for React route destruction before replace room's DOM node --- app/ui-utils/client/lib/openRoom.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) 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')) { From 0a08bc536d8ff6ea747dcd54e007b27fe1f7725b Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 11 Jun 2020 01:51:01 -0300 Subject: [PATCH 19/21] Auto Close Sidebar and focus status --- app/ui-utils/client/lib/SideNav.js | 7 +++---- client/admin/sidebar/AdminSidebar.js | 26 +++++++++++++++++--------- 2 files changed, 20 insertions(+), 13 deletions(-) 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/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index 660dfa09b0e17..42db08ca21f06 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -1,7 +1,7 @@ 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 } from 'react'; +import React, { useCallback, useState, useMemo, useEffect } from 'react'; import { menu, SideNav, Layout } from '../../../app/ui-utils/client'; import { useReactiveValue } from '../../hooks/useReactiveValue'; @@ -12,12 +12,12 @@ import { sidebarItems } from '../sidebarItems'; import PrivilegedSettingsProvider from '../PrivilegedSettingsProvider'; import { usePrivilegedSettingsGroups } from '../../contexts/PrivilegedSettingsContext'; -const SidebarItem = ({ permissionGranted, pathGroup, href, icon, label, currentPath }) => { - if (permissionGranted && !permissionGranted()) { return null; } +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; - return useMemo(() => {icon && } {label} - , [path, label, name, icon, isActive]); -}; + ; +}); -const SidebarItemsAssembler = ({ items, currentPath }) => { +const SidebarItemsAssembler = React.memo(({ items, currentPath }) => { const t = useTranslation(); return items.map(({ href, @@ -64,13 +66,13 @@ const SidebarItemsAssembler = ({ items, currentPath }) => { key={i18nLabel || name} currentPath={currentPath} />); -}; +}); const AdminSidebarPages = ({ currentPath }) => { const items = useReactiveValue(() => sidebarItems.get()); return - {useMemo(() => , [items, currentPath])} + ; }; @@ -124,6 +126,12 @@ export default function AdminSidebar() { const currentRoute = useCurrentRoute(); const currentPath = useRoutePath(...currentRoute); + useEffect(() => { + if (!currentPath.startsWith('/admin/')) { + SideNav.closeFlex(); + } + }, [currentRoute]); + // TODO: uplift this provider return From 8ab38b72d294212c92a116f931b09e277d11efd5 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 11 Jun 2020 02:21:49 -0300 Subject: [PATCH 20/21] placeholder --- client/admin/sidebar/AdminSidebar.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index 42db08ca21f06..94c234e8f9aa0 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -89,6 +89,7 @@ const AdminSidebarSettings = ({ currentPath }) => { } className={['asdsads']} From a17473def5d82a4c6e7711c49bbfdc6607d6d39a Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 11 Jun 2020 02:53:49 -0300 Subject: [PATCH 21/21] Paddings --- client/admin/sidebar/AdminSidebar.js | 31 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index 94c234e8f9aa0..935a81dd4751d 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -24,9 +24,6 @@ const SidebarItem = React.memo(({ permissionGranted, pathGroup, href, icon, labe pi='x24' key={path} href={path} - display='flex' - flexDirection='row' - alignItems='center' className={[ isActive && 'active', css` @@ -43,8 +40,14 @@ const SidebarItem = React.memo(({ permissionGranted, pathGroup, href, icon, labe `, ].filter(Boolean)} > - {icon && } - {label} + + {icon && } + {label} + ; }); @@ -71,7 +74,7 @@ const SidebarItemsAssembler = React.memo(({ items, currentPath }) => { const AdminSidebarPages = ({ currentPath }) => { const items = useReactiveValue(() => sidebarItems.get()); - return + return ; }; @@ -84,9 +87,9 @@ const AdminSidebarSettings = ({ currentPath }) => { const groups = usePrivilegedSettingsGroups(useDebouncedValue(filter, 400)); const isLoadingGroups = false; // TODO: get from PrivilegedSettingsContext - return - {t('Settings')} - + return + {t('Settings')} + { className={['asdsads']} /> - + {isLoadingGroups && } {!isLoadingGroups && !!groups.length && ({ @@ -136,11 +139,9 @@ export default function AdminSidebar() { // TODO: uplift this provider return - - {t('Administration')} - + + {t('Administration')} +