From fad376b41501d1fcfc721373344e9d74e5f7b276 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Tue, 20 Sep 2022 21:14:24 +0300 Subject: [PATCH] refactor(ui): remove global providers [EE-4128] (#7578) --- app/assets/css/bootstrap-override.css | 4 ++ app/azure/react/views/index.ts | 23 ++++++-- app/docker/react/components/index.ts | 15 +++++- app/docker/react/views/containers.ts | 8 ++- app/docker/react/views/index.ts | 12 +++-- .../EdgeDevicesView/EdgeDevicesView.tsx | 3 -- app/edge/EdgeDevices/EdgeDevicesView/index.ts | 2 +- .../WaitingRoomView/WaitingRoomView.tsx | 3 -- app/edge/EdgeDevices/WaitingRoomView/index.ts | 2 +- app/edge/__module.js | 10 ++-- .../components/EdgeCheckInIntervalField.tsx | 13 ++--- app/edge/components/index.ts | 7 ++- app/edge/react/views/index.ts | 16 ++++-- app/portainer/__module.js | 2 - app/portainer/home/HomeView.tsx | 4 -- app/portainer/home/MotdPanel.tsx | 15 +++--- app/portainer/home/index.ts | 10 +--- app/portainer/hooks/UIStateProvider.tsx | 29 ---------- app/portainer/hooks/useUIState.tsx | 37 +++++++++++++ app/portainer/hooks/useUser.tsx | 1 + app/portainer/react/components/index.ts | 54 ++++++++++++++----- app/portainer/react/views/index.ts | 17 +++--- app/portainer/react/views/sidebar.ts | 8 ++- app/portainer/react/views/teams.ts | 13 ++++- app/portainer/react/views/update-schedules.ts | 18 +++++-- app/portainer/react/views/wizard.ts | 21 ++++++-- .../edge-compute/EdgeComputeSettingsView.tsx | 10 ++-- .../edge-compute/SettingsFDO/index.ts | 7 ++- app/react-tools/RootProvider.tsx | 24 --------- app/react-tools/react-query.ts | 2 + app/react-tools/react2angular.tsx | 31 ++++++----- app/react-tools/withCurrentUser.tsx | 24 +++++++++ app/react-tools/withI18nSuspense.tsx | 22 ++++++++ app/react-tools/withReactQuery.tsx | 26 +++++++++ app/react-tools/withUIRouter.tsx | 23 ++++++++ .../components/PageHeader/HeaderContainer.tsx | 1 + app/react/components/Widget/Widget.tsx | 1 + .../components/datatables/RowContext.tsx | 1 + .../datatables/useTableSettings.tsx | 1 + .../datatables/useZustandTableSettings.tsx | 1 + .../form-components/InputGroup/InputGroup.tsx | 1 + .../docker/containers/CreateView/Gpu.tsx} | 0 .../sidebar/Footer/UpdateNotifications.tsx | 13 ++--- app/react/sidebar/useSidebarState.tsx | 1 + package.json | 1 - yarn.lock | 49 +++-------------- 46 files changed, 372 insertions(+), 214 deletions(-) delete mode 100644 app/portainer/hooks/UIStateProvider.tsx create mode 100644 app/portainer/hooks/useUIState.tsx delete mode 100644 app/react-tools/RootProvider.tsx create mode 100644 app/react-tools/withCurrentUser.tsx create mode 100644 app/react-tools/withI18nSuspense.tsx create mode 100644 app/react-tools/withReactQuery.tsx create mode 100644 app/react-tools/withUIRouter.tsx rename app/{docker/react/views/gpu.tsx => react/docker/containers/CreateView/Gpu.tsx} (100%) diff --git a/app/assets/css/bootstrap-override.css b/app/assets/css/bootstrap-override.css index 9633f60cb..1ff9e7e7c 100644 --- a/app/assets/css/bootstrap-override.css +++ b/app/assets/css/bootstrap-override.css @@ -196,6 +196,10 @@ input:checked + .slider:before { padding: 1.5%; } +.widget .widget-icon .icon { + display: flex !important; +} + .widget .widget-body table thead { border-top: 1px solid var(--border-table-color); } diff --git a/app/azure/react/views/index.ts b/app/azure/react/views/index.ts index 6234d1286..44c9f8197 100644 --- a/app/azure/react/views/index.ts +++ b/app/azure/react/views/index.ts @@ -5,10 +5,25 @@ import { CreateView } from '@/react/azure/container-instances/CreateView'; import { ItemView } from '@/react/azure/container-instances/ItemView'; import { ListView } from '@/react/azure/container-instances/ListView'; import { DashboardView } from '@/react/azure/DashboardView'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; export const viewsModule = angular .module('portainer.azure.react.views', []) - .component('containerInstanceView', r2a(ItemView, [])) - .component('createContainerInstanceView', r2a(CreateView, [])) - .component('containerInstancesView', r2a(ListView, [])) - .component('dashboardView', r2a(DashboardView, [])).name; + .component( + 'containerInstanceView', + r2a(withUIRouter(withReactQuery(withCurrentUser(ItemView))), []) + ) + .component( + 'createContainerInstanceView', + r2a(withUIRouter(withReactQuery(withCurrentUser(CreateView))), []) + ) + .component( + 'containerInstancesView', + r2a(withUIRouter(withReactQuery(withCurrentUser(ListView))), []) + ) + .component( + 'dashboardView', + r2a(withUIRouter(withReactQuery(withCurrentUser(DashboardView))), []) + ).name; diff --git a/app/docker/react/components/index.ts b/app/docker/react/components/index.ts index da594a289..22ce5c716 100644 --- a/app/docker/react/components/index.ts +++ b/app/docker/react/components/index.ts @@ -5,12 +5,16 @@ import { StackContainersDatatable } from '@/react/docker/stacks/ItemView/StackCo import { ContainerQuickActions } from '@/react/docker/containers/components/ContainerQuickActions'; import { TemplateListDropdownAngular } from '@/react/docker/app-templates/TemplateListDropdown'; import { TemplateListSortAngular } from '@/react/docker/app-templates/TemplateListSort'; +import { Gpu } from '@/react/docker/containers/CreateView/Gpu'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; export const componentsModule = angular .module('portainer.docker.react.components', []) .component( 'containerQuickActions', - r2a(ContainerQuickActions, [ + r2a(withUIRouter(withCurrentUser(ContainerQuickActions)), [ 'containerId', 'nodeName', 'state', @@ -22,5 +26,12 @@ export const componentsModule = angular .component('templateListSort', TemplateListSortAngular) .component( 'stackContainersDatatable', - r2a(StackContainersDatatable, ['environment', 'stackName']) + r2a( + withUIRouter(withReactQuery(withCurrentUser(StackContainersDatatable))), + ['environment', 'stackName'] + ) + ) + .component( + 'gpu', + r2a(Gpu, ['values', 'onChange', 'gpus', 'usedGpus', 'usedAllGpus']) ).name; diff --git a/app/docker/react/views/containers.ts b/app/docker/react/views/containers.ts index 5c0650688..a01bb2a91 100644 --- a/app/docker/react/views/containers.ts +++ b/app/docker/react/views/containers.ts @@ -3,10 +3,16 @@ import angular from 'angular'; import { r2a } from '@/react-tools/react2angular'; import { ListView } from '@/react/docker/containers/ListView'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; export const containersModule = angular .module('portainer.docker.containers', []) - .component('containersView', r2a(ListView, ['endpoint'])) + .component( + 'containersView', + r2a(withUIRouter(withReactQuery(withCurrentUser(ListView))), ['endpoint']) + ) .config(config).name; diff --git a/app/docker/react/views/index.ts b/app/docker/react/views/index.ts index c14bf83e9..97a550467 100644 --- a/app/docker/react/views/index.ts +++ b/app/docker/react/views/index.ts @@ -1,15 +1,17 @@ import angular from 'angular'; -import { Gpu } from 'Docker/react/views/gpu'; import { ItemView as NetworksItemView } from '@/react/docker/networks/ItemView'; import { r2a } from '@/react-tools/react2angular'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; import { containersModule } from './containers'; export const viewsModule = angular .module('portainer.docker.react.views', [containersModule]) + .component( - 'gpu', - r2a(Gpu, ['values', 'onChange', 'gpus', 'usedGpus', 'usedAllGpus']) - ) - .component('networkDetailsView', r2a(NetworksItemView, [])).name; + 'networkDetailsView', + r2a(withUIRouter(withReactQuery(withCurrentUser(NetworksItemView))), []) + ).name; diff --git a/app/edge/EdgeDevices/EdgeDevicesView/EdgeDevicesView.tsx b/app/edge/EdgeDevices/EdgeDevicesView/EdgeDevicesView.tsx index 8a3a88911..d30e464c7 100644 --- a/app/edge/EdgeDevices/EdgeDevicesView/EdgeDevicesView.tsx +++ b/app/edge/EdgeDevices/EdgeDevicesView/EdgeDevicesView.tsx @@ -2,7 +2,6 @@ import { useState } from 'react'; import { useSettings } from '@/portainer/settings/queries'; import { useGroups } from '@/portainer/environment-groups/queries'; -import { r2a } from '@/react-tools/react2angular'; import { PageHeader } from '@@/PageHeader'; import { ViewLoading } from '@@/ViewLoading'; @@ -55,5 +54,3 @@ export function EdgeDevicesView() { ); } - -export const EdgeDevicesViewAngular = r2a(EdgeDevicesView, []); diff --git a/app/edge/EdgeDevices/EdgeDevicesView/index.ts b/app/edge/EdgeDevices/EdgeDevicesView/index.ts index ed950962d..4151ed27c 100644 --- a/app/edge/EdgeDevices/EdgeDevicesView/index.ts +++ b/app/edge/EdgeDevices/EdgeDevicesView/index.ts @@ -1 +1 @@ -export { EdgeDevicesView, EdgeDevicesViewAngular } from './EdgeDevicesView'; +export { EdgeDevicesView } from './EdgeDevicesView'; diff --git a/app/edge/EdgeDevices/WaitingRoomView/WaitingRoomView.tsx b/app/edge/EdgeDevices/WaitingRoomView/WaitingRoomView.tsx index 381b32aa1..7f5336af5 100644 --- a/app/edge/EdgeDevices/WaitingRoomView/WaitingRoomView.tsx +++ b/app/edge/EdgeDevices/WaitingRoomView/WaitingRoomView.tsx @@ -1,7 +1,6 @@ import { useRouter } from '@uirouter/react'; import { useEnvironmentList } from '@/portainer/environments/queries/useEnvironmentList'; -import { r2a } from '@/react-tools/react2angular'; import { EdgeTypes } from '@/portainer/environments/types'; import { InformationPanel } from '@@/InformationPanel'; @@ -58,5 +57,3 @@ export function WaitingRoomView() { ); } - -export const WaitingRoomViewAngular = r2a(WaitingRoomView, []); diff --git a/app/edge/EdgeDevices/WaitingRoomView/index.ts b/app/edge/EdgeDevices/WaitingRoomView/index.ts index a28e2c575..8239e6f45 100644 --- a/app/edge/EdgeDevices/WaitingRoomView/index.ts +++ b/app/edge/EdgeDevices/WaitingRoomView/index.ts @@ -1 +1 @@ -export { WaitingRoomView, WaitingRoomViewAngular } from './WaitingRoomView'; +export { WaitingRoomView } from './WaitingRoomView'; diff --git a/app/edge/__module.js b/app/edge/__module.js index 6bcf5112b..62a6d4557 100644 --- a/app/edge/__module.js +++ b/app/edge/__module.js @@ -1,15 +1,17 @@ import angular from 'angular'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { r2a } from '@/react-tools/react2angular'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; import edgeStackModule from './views/edge-stacks'; import { componentsModule } from './components'; -import { WaitingRoomViewAngular } from './EdgeDevices/WaitingRoomView'; +import { WaitingRoomView } from './EdgeDevices/WaitingRoomView'; import { reactModule } from './react'; -import { EdgeDevicesViewAngular } from './EdgeDevices/EdgeDevicesView'; angular .module('portainer.edge', [edgeStackModule, componentsModule, reactModule]) - .component('waitingRoomView', WaitingRoomViewAngular) - .component('edgeDevicesView', EdgeDevicesViewAngular) + .component('waitingRoomView', r2a(withUIRouter(withReactQuery(withCurrentUser(WaitingRoomView))), [])) .config(function config($stateRegistryProvider) { const edge = { name: 'edge', diff --git a/app/edge/components/EdgeCheckInIntervalField.tsx b/app/edge/components/EdgeCheckInIntervalField.tsx index 97272420b..43f133dcf 100644 --- a/app/edge/components/EdgeCheckInIntervalField.tsx +++ b/app/edge/components/EdgeCheckInIntervalField.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { useSettings } from '@/portainer/settings/queries'; import { r2a } from '@/react-tools/react2angular'; +import { withReactQuery } from '@/react-tools/withReactQuery'; import { FormControl } from '@@/form-components/FormControl'; import { Select } from '@@/form-components/Input'; @@ -58,14 +59,10 @@ export function EdgeCheckinIntervalField({ ); } -export const EdgeCheckinIntervalFieldAngular = r2a(EdgeCheckinIntervalField, [ - 'value', - 'onChange', - 'isDefaultHidden', - 'tooltip', - 'label', - 'readonly', -]); +export const EdgeCheckinIntervalFieldAngular = r2a( + withReactQuery(EdgeCheckinIntervalField), + ['value', 'onChange', 'isDefaultHidden', 'tooltip', 'label', 'readonly'] +); function useOptions(isDefaultHidden: boolean) { const [options, setOptions] = useState(checkinIntervalOptions); diff --git a/app/edge/components/index.ts b/app/edge/components/index.ts index 22d9cab86..8e2376c6d 100644 --- a/app/edge/components/index.ts +++ b/app/edge/components/index.ts @@ -2,6 +2,7 @@ import angular from 'angular'; import { r2a } from '@/react-tools/react2angular'; import { EdgeScriptForm } from '@/react/edge/components/EdgeScriptForm'; +import { withReactQuery } from '@/react-tools/withReactQuery'; import { EdgeCheckinIntervalFieldAngular } from './EdgeCheckInIntervalField'; @@ -9,6 +10,10 @@ export const componentsModule = angular .module('app.edge.components', []) .component( 'edgeScriptForm', - r2a(EdgeScriptForm, ['edgeInfo', 'commands', 'isNomadTokenVisible']) + r2a(withReactQuery(EdgeScriptForm), [ + 'edgeInfo', + 'commands', + 'isNomadTokenVisible', + ]) ) .component('edgeCheckinIntervalField', EdgeCheckinIntervalFieldAngular).name; diff --git a/app/edge/react/views/index.ts b/app/edge/react/views/index.ts index 83e58259a..2a0f22c21 100644 --- a/app/edge/react/views/index.ts +++ b/app/edge/react/views/index.ts @@ -1,6 +1,14 @@ import angular from 'angular'; -export const viewsModule = angular.module( - 'portainer.edge.react.views', - [] -).name; +import { EdgeDevicesView } from '@/edge/EdgeDevices/EdgeDevicesView'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { r2a } from '@/react-tools/react2angular'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; + +export const viewsModule = angular + .module('portainer.edge.react.views', []) + .component( + 'edgeDevicesView', + r2a(withUIRouter(withReactQuery(withCurrentUser(EdgeDevicesView))), []) + ).name; diff --git a/app/portainer/__module.js b/app/portainer/__module.js index a97dd5883..0dc526a5c 100644 --- a/app/portainer/__module.js +++ b/app/portainer/__module.js @@ -6,7 +6,6 @@ import settingsModule from './settings'; import featureFlagModule from './feature-flags'; import userActivityModule from './user-activity'; import servicesModule from './services'; -import homeModule from './home'; import { reactModule } from './react'; import { sidebarModule } from './react/views/sidebar'; import environmentsModule from './environments'; @@ -29,7 +28,6 @@ async function initAuthentication(authManager, Authentication, $rootScope, $stat angular .module('portainer.app', [ - homeModule, 'portainer.oauth', 'portainer.rbac', componentsModule, diff --git a/app/portainer/home/HomeView.tsx b/app/portainer/home/HomeView.tsx index bd01e8858..d6121b707 100644 --- a/app/portainer/home/HomeView.tsx +++ b/app/portainer/home/HomeView.tsx @@ -1,8 +1,6 @@ import { useRouter } from '@uirouter/react'; import { useState } from 'react'; -import { r2a } from '@/react-tools/react2angular'; - import { PageHeader } from '@@/PageHeader'; import * as notifications from '../services/notifications'; @@ -73,8 +71,6 @@ export function HomeView() { } } -export const HomeViewAngular = r2a(HomeView, []); - async function confirmEndpointSnapshot() { return confirmAsync({ title: buildTitle('Are you sure?'), diff --git a/app/portainer/home/MotdPanel.tsx b/app/portainer/home/MotdPanel.tsx index 4f7f6f8dd..83198e514 100644 --- a/app/portainer/home/MotdPanel.tsx +++ b/app/portainer/home/MotdPanel.tsx @@ -1,7 +1,7 @@ import { useQuery } from 'react-query'; import _ from 'lodash'; -import { useUIState } from '@/portainer/hooks/UIStateProvider'; +import { useUIState } from '@/portainer/hooks/useUIState'; import { InformationPanel } from '@@/InformationPanel'; @@ -10,9 +10,13 @@ import { getMotd } from './home.service'; export function MotdPanel() { const motd = useMotd(); - const [uiState, setUIState] = useUIState(); + const uiStateStore = useUIState(); - if (!motd || motd.Message === '' || motd.Hash === uiState.dismissedInfoHash) { + if ( + !motd || + motd.Message === '' || + motd.Hash === uiStateStore.dismissedInfoHash + ) { return null; } @@ -34,10 +38,7 @@ export function MotdPanel() { ); function onDismiss(hash: string) { - setUIState({ - ...uiState, - dismissedInfoHash: hash, - }); + uiStateStore.dismissMotd(hash); } } diff --git a/app/portainer/home/index.ts b/app/portainer/home/index.ts index 14d09b3e9..ee1498514 100644 --- a/app/portainer/home/index.ts +++ b/app/portainer/home/index.ts @@ -1,9 +1 @@ -import angular from 'angular'; - -import { EnvironmentListAngular } from './EnvironmentList'; -import { HomeViewAngular } from './HomeView'; - -export default angular - .module('portainer.app.home', []) - .component('homeView', HomeViewAngular) - .component('environmentList', EnvironmentListAngular).name; +export { HomeView } from './HomeView'; diff --git a/app/portainer/hooks/UIStateProvider.tsx b/app/portainer/hooks/UIStateProvider.tsx deleted file mode 100644 index 03f3cb900..000000000 --- a/app/portainer/hooks/UIStateProvider.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { createContext, PropsWithChildren, useContext } from 'react'; - -import { useLocalStorage } from '@/portainer/hooks/useLocalStorage'; - -interface UIState { - dismissedInfoPanels: Record; - dismissedInfoHash: string; - dismissedUpdateVersion: string; -} - -type UIStateService = [UIState, (state: UIState) => void]; - -const Context = createContext(null); - -export function useUIState() { - const context = useContext(Context); - - if (context == null) { - throw new Error('Should be nested under a UIStateProvider component'); - } - - return context; -} - -export function UIStateProvider({ children }: PropsWithChildren) { - const service = useLocalStorage('UI_STATE', {} as UIState); - - return {children}; -} diff --git a/app/portainer/hooks/useUIState.tsx b/app/portainer/hooks/useUIState.tsx new file mode 100644 index 000000000..6ff7b2994 --- /dev/null +++ b/app/portainer/hooks/useUIState.tsx @@ -0,0 +1,37 @@ +import create from 'zustand'; +import { persist } from 'zustand/middleware'; + +import { keyBuilder } from '@/portainer/hooks/useLocalStorage'; + +interface UIState { + dismissedInfoPanels: Record; + dismissInfoPanel(id: string): void; + + dismissedInfoHash: string; + dismissMotd(hash: string): void; + + dismissedUpdateVersion: string; + dismissUpdateVersion(version: string): void; +} + +export const useUIState = create()( + persist( + (set) => ({ + dismissedInfoPanels: {}, + dismissInfoPanel(id: string) { + set((state) => ({ + dismissedInfoPanels: { ...state.dismissedInfoPanels, [id]: true }, + })); + }, + dismissedInfoHash: '', + dismissMotd(hash: string) { + set({ dismissedInfoHash: hash }); + }, + dismissedUpdateVersion: '', + dismissUpdateVersion(version: string) { + set({ dismissedUpdateVersion: version }); + }, + }), + { name: keyBuilder('NEW_UI_STATE') } + ) +); diff --git a/app/portainer/hooks/useUser.tsx b/app/portainer/hooks/useUser.tsx index 4f61d9502..dccbef18d 100644 --- a/app/portainer/hooks/useUser.tsx +++ b/app/portainer/hooks/useUser.tsx @@ -23,6 +23,7 @@ interface State { } export const UserContext = createContext(null); +UserContext.displayName = 'UserContext'; export function useUser() { const context = useContext(UserContext); diff --git a/app/portainer/react/components/index.ts b/app/portainer/react/components/index.ts index 784e7d2fb..fca7dd7aa 100644 --- a/app/portainer/react/components/index.ts +++ b/app/portainer/react/components/index.ts @@ -1,10 +1,19 @@ import angular from 'angular'; -import { react2angular } from 'react2angular'; import { r2a } from '@/react-tools/react2angular'; +import { CreateAccessToken } from '@/react/portainer/account/CreateAccessTokenView'; +import { + DefaultRegistryAction, + DefaultRegistryDomain, + DefaultRegistryName, +} from '@/react/portainer/registries/ListView/DefaultRegistry'; import { Icon } from '@/react/components/Icon'; import { ReactQueryDevtoolsWrapper } from '@/react/components/ReactQueryDevtoolsWrapper'; import { AccessControlPanel } from '@/react/portainer/access-control'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; +import { withI18nSuspense } from '@/react-tools/withI18nSuspense'; import { PageHeader } from '@@/PageHeader'; import { TagSelector } from '@@/TagSelector'; @@ -26,22 +35,25 @@ export const componentsModule = angular .module('portainer.app.react.components', [customTemplatesModule]) .component( 'tagSelector', - r2a(TagSelector, ['allowCreate', 'onChange', 'value']) + r2a(withReactQuery(TagSelector), ['allowCreate', 'onChange', 'value']) ) .component( 'portainerTooltip', - react2angular(Tooltip, ['message', 'position', 'className']) + r2a(Tooltip, ['message', 'position', 'className']) ) .component('fileUploadField', fileUploadField) .component('porSwitchField', switchField) .component( 'passwordCheckHint', - r2a(PasswordCheckHint, ['forceChangePassword', 'passwordValid']) + r2a(withReactQuery(PasswordCheckHint), [ + 'forceChangePassword', + 'passwordValid', + ]) ) .component('rdLoading', r2a(Loading, [])) .component( 'tableColumnHeader', - react2angular(TableColumnHeaderAngular, [ + r2a(TableColumnHeaderAngular, [ 'colTitle', 'canSort', 'isSorted', @@ -51,13 +63,13 @@ export const componentsModule = angular .component('viewLoading', r2a(ViewLoading, ['message'])) .component( 'pageHeader', - r2a(PageHeader, [ - 'id', + r2a(withUIRouter(withReactQuery(withCurrentUser(PageHeader))), [ 'title', 'breadcrumbs', 'loading', 'onReload', 'reload', + 'id', ]) ) .component( @@ -75,7 +87,7 @@ export const componentsModule = angular ) .component( 'prIcon', - react2angular(Icon, ['className', 'feather', 'icon', 'mode', 'size']) + r2a(Icon, ['className', 'feather', 'icon', 'mode', 'size']) ) .component('reactQueryDevTools', r2a(ReactQueryDevtoolsWrapper, [])) .component( @@ -86,13 +98,10 @@ export const componentsModule = angular 'datatableSearchbar', r2a(SearchBar, ['data-cy', 'onChange', 'value', 'placeholder']) ) - .component( - 'boxSelectorBadgeIcon', - react2angular(BadgeIcon, ['featherIcon', 'icon']) - ) + .component('boxSelectorBadgeIcon', r2a(BadgeIcon, ['featherIcon', 'icon'])) .component( 'accessControlPanel', - r2a(AccessControlPanel, [ + r2a(withReactQuery(withCurrentUser(AccessControlPanel)), [ 'disableOwnershipChange', 'onUpdateSuccess', 'resourceControl', @@ -100,4 +109,23 @@ export const componentsModule = angular 'resourceType', 'environmentId', ]) + ) + .component( + 'defaultRegistryName', + r2a(withReactQuery(DefaultRegistryName), []) + ) + .component( + 'defaultRegistryAction', + r2a(withReactQuery(DefaultRegistryAction), []) + ) + .component( + 'defaultRegistryDomain', + r2a(withReactQuery(DefaultRegistryDomain), []) + ) + .component( + 'createAccessToken', + r2a(withI18nSuspense(withUIRouter(CreateAccessToken)), [ + 'onSubmit', + 'onError', + ]) ).name; diff --git a/app/portainer/react/views/index.ts b/app/portainer/react/views/index.ts index b92068e97..852012638 100644 --- a/app/portainer/react/views/index.ts +++ b/app/portainer/react/views/index.ts @@ -1,12 +1,10 @@ import angular from 'angular'; +import { HomeView } from '@/portainer/home'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; import { r2a } from '@/react-tools/react2angular'; -import { CreateAccessToken } from '@/react/portainer/account/CreateAccessTokenView'; -import { - DefaultRegistryAction, - DefaultRegistryDomain, - DefaultRegistryName, -} from '@/react/portainer/registries/ListView/DefaultRegistry'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; import { wizardModule } from './wizard'; import { teamsModule } from './teams'; @@ -18,10 +16,7 @@ export const viewsModule = angular teamsModule, updateSchedulesModule, ]) - .component('defaultRegistryName', r2a(DefaultRegistryName, [])) - .component('defaultRegistryAction', r2a(DefaultRegistryAction, [])) - .component('defaultRegistryDomain', r2a(DefaultRegistryDomain, [])) .component( - 'createAccessToken', - r2a(CreateAccessToken, ['onSubmit', 'onError']) + 'homeView', + r2a(withUIRouter(withReactQuery(withCurrentUser(HomeView))), []) ).name; diff --git a/app/portainer/react/views/sidebar.ts b/app/portainer/react/views/sidebar.ts index 52069b090..623e24446 100644 --- a/app/portainer/react/views/sidebar.ts +++ b/app/portainer/react/views/sidebar.ts @@ -3,8 +3,14 @@ import angular from 'angular'; import { AngularSidebarService } from '@/react/sidebar/useSidebarState'; import { Sidebar } from '@/react/sidebar/Sidebar'; import { r2a } from '@/react-tools/react2angular'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; export const sidebarModule = angular .module('portainer.app.sidebar', []) - .component('sidebar', r2a(Sidebar, [])) + .component( + 'sidebar', + r2a(withUIRouter(withReactQuery(withCurrentUser(Sidebar))), []) + ) .factory('SidebarService', AngularSidebarService).name; diff --git a/app/portainer/react/views/teams.ts b/app/portainer/react/views/teams.ts index 309e880b6..2fd163ab2 100644 --- a/app/portainer/react/views/teams.ts +++ b/app/portainer/react/views/teams.ts @@ -3,12 +3,21 @@ import { StateRegistry } from '@uirouter/angularjs'; import { ItemView, ListView } from '@/react/portainer/users/teams'; import { r2a } from '@/react-tools/react2angular'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; export const teamsModule = angular .module('portainer.app.teams', []) .config(config) - .component('teamView', r2a(ItemView, [])) - .component('teamsView', r2a(ListView, [])).name; + .component( + 'teamView', + r2a(withUIRouter(withReactQuery(withCurrentUser(ItemView))), []) + ) + .component( + 'teamsView', + r2a(withUIRouter(withReactQuery(withCurrentUser(ListView))), []) + ).name; /* @ngInject */ function config($stateRegistryProvider: StateRegistry) { diff --git a/app/portainer/react/views/update-schedules.ts b/app/portainer/react/views/update-schedules.ts index dd5a39dde..92618c49e 100644 --- a/app/portainer/react/views/update-schedules.ts +++ b/app/portainer/react/views/update-schedules.ts @@ -7,12 +7,24 @@ import { CreateView, ItemView, } from '@/react/portainer/environments/update-schedules'; +import { withUIRouter } from '@/react-tools/withUIRouter'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; export const updateSchedulesModule = angular .module('portainer.edge.updateSchedules', []) - .component('updateSchedulesListView', r2a(ListView, [])) - .component('updateSchedulesCreateView', r2a(CreateView, [])) - .component('updateSchedulesItemView', r2a(ItemView, [])) + .component( + 'updateSchedulesListView', + r2a(withUIRouter(withReactQuery(withCurrentUser(ListView))), []) + ) + .component( + 'updateSchedulesCreateView', + r2a(withUIRouter(withReactQuery(withCurrentUser(CreateView))), []) + ) + .component( + 'updateSchedulesItemView', + r2a(withUIRouter(withReactQuery(withCurrentUser(ItemView))), []) + ) .config(config).name; function config($stateRegistryProvider: StateRegistry) { diff --git a/app/portainer/react/views/wizard.ts b/app/portainer/react/views/wizard.ts index 81235da7f..efb8c057e 100644 --- a/app/portainer/react/views/wizard.ts +++ b/app/portainer/react/views/wizard.ts @@ -7,15 +7,30 @@ import { EnvironmentTypeSelectView, HomeView, } from '@/react/portainer/environments/wizard'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; export const wizardModule = angular .module('portainer.app.react.views.wizard', []) - .component('wizardEnvironmentCreationView', r2a(EnvironmentCreationView, [])) + .component( + 'wizardEnvironmentCreationView', + r2a( + withUIRouter(withReactQuery(withCurrentUser(EnvironmentCreationView))), + [] + ) + ) .component( 'wizardEnvironmentTypeSelectView', - r2a(EnvironmentTypeSelectView, []) + r2a( + withUIRouter(withReactQuery(withCurrentUser(EnvironmentTypeSelectView))), + [] + ) + ) + .component( + 'wizardMainView', + r2a(withUIRouter(withReactQuery(withCurrentUser(HomeView))), []) ) - .component('wizardMainView', r2a(HomeView, [])) .config(config).name; function config($stateRegistryProvider: StateRegistry) { diff --git a/app/portainer/settings/edge-compute/EdgeComputeSettingsView.tsx b/app/portainer/settings/edge-compute/EdgeComputeSettingsView.tsx index 31f83425e..991ca2e2e 100644 --- a/app/portainer/settings/edge-compute/EdgeComputeSettingsView.tsx +++ b/app/portainer/settings/edge-compute/EdgeComputeSettingsView.tsx @@ -1,4 +1,6 @@ +import { withCurrentUser } from '@/react-tools/withCurrentUser'; import { r2a } from '@/react-tools/react2angular'; +import { withReactQuery } from '@/react-tools/withReactQuery'; import { Settings } from '../types'; @@ -20,7 +22,7 @@ export function EdgeComputeSettingsView({ settings, onSubmit }: Props) { ); } -export const EdgeComputeSettingsViewAngular = r2a(EdgeComputeSettingsView, [ - 'settings', - 'onSubmit', -]); +export const EdgeComputeSettingsViewAngular = r2a( + withReactQuery(withCurrentUser(EdgeComputeSettingsView)), + ['settings', 'onSubmit'] +); diff --git a/app/portainer/settings/edge-compute/SettingsFDO/index.ts b/app/portainer/settings/edge-compute/SettingsFDO/index.ts index acceef361..5c8190a33 100644 --- a/app/portainer/settings/edge-compute/SettingsFDO/index.ts +++ b/app/portainer/settings/edge-compute/SettingsFDO/index.ts @@ -1,6 +1,11 @@ import { react2angular } from '@/react-tools/react2angular'; +import { withReactQuery } from '@/react-tools/withReactQuery'; +import { withUIRouter } from '@/react-tools/withUIRouter'; import { SettingsFDO } from './SettingsFDO'; -const SettingsFDOAngular = react2angular(SettingsFDO, ['settings', 'onSubmit']); +const SettingsFDOAngular = react2angular( + withUIRouter(withReactQuery(SettingsFDO)), + ['settings', 'onSubmit'] +); export { SettingsFDO, SettingsFDOAngular }; diff --git a/app/react-tools/RootProvider.tsx b/app/react-tools/RootProvider.tsx deleted file mode 100644 index 0876d05c7..000000000 --- a/app/react-tools/RootProvider.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { QueryClientProvider } from 'react-query'; -import { UIRouterContextComponent } from '@uirouter/react-hybrid'; -import { PropsWithChildren, StrictMode } from 'react'; - -import { UserProvider } from '@/portainer/hooks/useUser'; -import { UIStateProvider } from '@/portainer/hooks/UIStateProvider'; - -import { createQueryClient } from './react-query'; - -const queryClient = createQueryClient(); - -export function RootProvider({ children }: PropsWithChildren) { - return ( - - - - - {children} - - - - - ); -} diff --git a/app/react-tools/react-query.ts b/app/react-tools/react-query.ts index a20c4890a..26db442ed 100644 --- a/app/react-tools/react-query.ts +++ b/app/react-tools/react-query.ts @@ -93,3 +93,5 @@ function handleError(error: unknown, errorMeta?: unknown) { notifyError(title, error as Error, message); } } + +export const queryClient = createQueryClient(); diff --git a/app/react-tools/react2angular.tsx b/app/react-tools/react2angular.tsx index 3baf48cb1..76e7b3355 100644 --- a/app/react-tools/react2angular.tsx +++ b/app/react-tools/react2angular.tsx @@ -1,10 +1,8 @@ import ReactDOM from 'react-dom'; import { IComponentOptions, IController } from 'angular'; -import { Suspense } from 'react'; +import { StrictMode } from 'react'; import _ from 'lodash'; -import { RootProvider } from './RootProvider'; - function toProps( propNames: string[], controller: IController, @@ -54,20 +52,29 @@ export function react2angular[]>( $element: HTMLElement[], $q: ng.IQService ) { + let isDestroyed = false; const el = $element[0]; + this.$onChanges = () => { - const props = toProps(propNames, this, $q); - ReactDOM.render( - - + if (!isDestroyed) { + const props = toProps(propNames, this, $q); + ReactDOM.render( + {/* eslint-disable-next-line react/jsx-props-no-spreading */} - - , - el - ); + , + + el + ); + } + }; + + this.$onDestroy = () => { + if (!isDestroyed) { + isDestroyed = true; + ReactDOM.unmountComponentAtNode(el); + } }; - this.$onDestroy = () => ReactDOM.unmountComponentAtNode(el); } } diff --git a/app/react-tools/withCurrentUser.tsx b/app/react-tools/withCurrentUser.tsx new file mode 100644 index 000000000..3176c4487 --- /dev/null +++ b/app/react-tools/withCurrentUser.tsx @@ -0,0 +1,24 @@ +import { ComponentType } from 'react'; + +import { UserProvider } from '@/portainer/hooks/useUser'; + +export function withCurrentUser( + WrappedComponent: ComponentType +): ComponentType { + // Try to create a nice displayName for React Dev Tools. + const displayName = + WrappedComponent.displayName || WrappedComponent.name || 'Component'; + + function WrapperComponent(props: T) { + return ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + + + ); + } + + WrapperComponent.displayName = displayName; + + return WrapperComponent; +} diff --git a/app/react-tools/withI18nSuspense.tsx b/app/react-tools/withI18nSuspense.tsx new file mode 100644 index 000000000..747773e2b --- /dev/null +++ b/app/react-tools/withI18nSuspense.tsx @@ -0,0 +1,22 @@ +import { ComponentType, Suspense } from 'react'; + +export function withI18nSuspense( + WrappedComponent: ComponentType +): ComponentType { + // Try to create a nice displayName for React Dev Tools. + const displayName = + WrappedComponent.displayName || WrappedComponent.name || 'Component'; + + function WrapperComponent(props: T) { + return ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + + + ); + } + + WrapperComponent.displayName = displayName; + + return WrapperComponent; +} diff --git a/app/react-tools/withReactQuery.tsx b/app/react-tools/withReactQuery.tsx new file mode 100644 index 000000000..0502f47a2 --- /dev/null +++ b/app/react-tools/withReactQuery.tsx @@ -0,0 +1,26 @@ +import { ComponentType } from 'react'; +import { QueryClientProvider } from 'react-query'; + +import { queryClient as defaultQueryClient } from './react-query'; + +export function withReactQuery( + WrappedComponent: ComponentType, + queryClient = defaultQueryClient +): ComponentType { + // Try to create a nice displayName for React Dev Tools. + const displayName = + WrappedComponent.displayName || WrappedComponent.name || 'Component'; + + function WrapperComponent(props: T) { + return ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + + + ); + } + + WrapperComponent.displayName = displayName; + + return WrapperComponent; +} diff --git a/app/react-tools/withUIRouter.tsx b/app/react-tools/withUIRouter.tsx new file mode 100644 index 000000000..761b8b442 --- /dev/null +++ b/app/react-tools/withUIRouter.tsx @@ -0,0 +1,23 @@ +import { ComponentType } from 'react'; +import { UIRouterContextComponent } from '@uirouter/react-hybrid'; + +export function withUIRouter( + WrappedComponent: ComponentType +): ComponentType { + // Try to create a nice displayName for React Dev Tools. + const displayName = + WrappedComponent.displayName || WrappedComponent.name || 'Component'; + + function WrapperComponent(props: T) { + return ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + + + ); + } + + WrapperComponent.displayName = displayName; + + return WrapperComponent; +} diff --git a/app/react/components/PageHeader/HeaderContainer.tsx b/app/react/components/PageHeader/HeaderContainer.tsx index 395fda4db..c333e037a 100644 --- a/app/react/components/PageHeader/HeaderContainer.tsx +++ b/app/react/components/PageHeader/HeaderContainer.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx'; import styles from './HeaderContainer.module.css'; const Context = createContext(null); +Context.displayName = 'PageHeaderContext'; export function useHeaderContext() { const context = useContext(Context); diff --git a/app/react/components/Widget/Widget.tsx b/app/react/components/Widget/Widget.tsx index 33c6c34d6..a6265a713 100644 --- a/app/react/components/Widget/Widget.tsx +++ b/app/react/components/Widget/Widget.tsx @@ -1,6 +1,7 @@ import { createContext, PropsWithChildren, useContext } from 'react'; const Context = createContext(null); +Context.displayName = 'WidgetContext'; export function useWidgetContext() { const context = useContext(Context); diff --git a/app/react/components/datatables/RowContext.tsx b/app/react/components/datatables/RowContext.tsx index bba924cb6..f7371888f 100644 --- a/app/react/components/datatables/RowContext.tsx +++ b/app/react/components/datatables/RowContext.tsx @@ -2,6 +2,7 @@ import { createContext, PropsWithChildren, useContext } from 'react'; export function createRowContext() { const Context = createContext(null); + Context.displayName = 'RowContext'; return { RowProvider, useRowContext }; diff --git a/app/react/components/datatables/useTableSettings.tsx b/app/react/components/datatables/useTableSettings.tsx index 9cfabb573..db2d6076f 100644 --- a/app/react/components/datatables/useTableSettings.tsx +++ b/app/react/components/datatables/useTableSettings.tsx @@ -19,6 +19,7 @@ interface TableSettingsContextInterface { const TableSettingsContext = createContext > | null>(null); +TableSettingsContext.displayName = 'TableSettingsContext'; export function useTableSettings() { const Context = getContextType(); diff --git a/app/react/components/datatables/useZustandTableSettings.tsx b/app/react/components/datatables/useZustandTableSettings.tsx index 77077265e..c7a34d73d 100644 --- a/app/react/components/datatables/useZustandTableSettings.tsx +++ b/app/react/components/datatables/useZustandTableSettings.tsx @@ -7,6 +7,7 @@ interface TableSettingsContextInterface { const TableSettingsContext = createContext > | null>(null); +TableSettingsContext.displayName = 'TableSettingsContext'; export function useTableSettings() { const Context = getContextType(); diff --git a/app/react/components/form-components/InputGroup/InputGroup.tsx b/app/react/components/form-components/InputGroup/InputGroup.tsx index b3f115b2d..35c343de9 100644 --- a/app/react/components/form-components/InputGroup/InputGroup.tsx +++ b/app/react/components/form-components/InputGroup/InputGroup.tsx @@ -2,6 +2,7 @@ import clsx from 'clsx'; import { createContext, PropsWithChildren, useContext } from 'react'; const Context = createContext(null); +Context.displayName = 'InputGroupContext'; type Size = 'small' | 'large'; diff --git a/app/docker/react/views/gpu.tsx b/app/react/docker/containers/CreateView/Gpu.tsx similarity index 100% rename from app/docker/react/views/gpu.tsx rename to app/react/docker/containers/CreateView/Gpu.tsx diff --git a/app/react/sidebar/Footer/UpdateNotifications.tsx b/app/react/sidebar/Footer/UpdateNotifications.tsx index 50a2fffdd..8ba18380c 100644 --- a/app/react/sidebar/Footer/UpdateNotifications.tsx +++ b/app/react/sidebar/Footer/UpdateNotifications.tsx @@ -2,14 +2,14 @@ import { useQuery } from 'react-query'; import clsx from 'clsx'; import { getVersionStatus } from '@/portainer/services/api/status.service'; -import { useUIState } from '@/portainer/hooks/UIStateProvider'; +import { useUIState } from '@/portainer/hooks/useUIState'; import { Icon } from '@@/Icon'; import styles from './UpdateNotifications.module.css'; export function UpdateNotification() { - const [uiState, setUIState] = useUIState(); + const uiStateStore = useUIState(); const query = useUpdateNotification(); if (!query.data || !query.data.UpdateAvailable) { @@ -19,9 +19,9 @@ export function UpdateNotification() { const { LatestVersion } = query.data; if ( - uiState?.dismissedUpdateVersion?.length > 0 && + !!uiStateStore.dismissedUpdateVersion && LatestVersion?.length > 0 && - uiState?.dismissedUpdateVersion === LatestVersion + uiStateStore.dismissedUpdateVersion === LatestVersion ) { return null; } @@ -63,10 +63,7 @@ export function UpdateNotification() { ); function onDismiss(version: string) { - setUIState({ - ...uiState, - dismissedUpdateVersion: version, - }); + uiStateStore.dismissUpdateVersion(version); } } diff --git a/app/react/sidebar/useSidebarState.tsx b/app/react/sidebar/useSidebarState.tsx index 7502aa7ef..0859b1b87 100644 --- a/app/react/sidebar/useSidebarState.tsx +++ b/app/react/sidebar/useSidebarState.tsx @@ -24,6 +24,7 @@ interface State { } export const Context = createContext(null); +Context.displayName = 'SidebarContext'; export function useSidebarState() { const context = useContext(Context); diff --git a/package.json b/package.json index e80972294..fb86bfead 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,6 @@ "react-select": "^5.2.1", "react-table": "^7.7.0", "react-tooltip": "^4.2.21", - "react2angular": "^4.0.6", "sanitize-html": "^2.5.3", "semver-compare": "^1.0.0", "spinkit": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index 591d14e64..154f10d29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4397,10 +4397,10 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@types/angular@^1.6.39", "@types/angular@^1.8.3": - version "1.8.4" - resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.8.4.tgz#a2cc163e508389c51d4c4119ebff6b9395cec472" - integrity sha512-wPS/ncJWhyxJsndsW1B6Ta8D4mi97x1yItSu+rkLDytU3oRIh2CFAjMuJceYwFAh9+DIohndWM0QBA9OU2Hv0g== +"@types/angular@^1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.8.3.tgz#97602244b685113ce9dc27823471b67d28f5ba3e" + integrity sha512-vgc5Z+TD07DT7NEUjFm6XMp0kEbGXIa95XmOL5IiHXR9LdrJpcdDh3jl1nCuZbWyzFn5/1OqtMfomcnA1sUFXQ== "@types/aria-query@^4.2.0": version "4.2.2" @@ -4678,14 +4678,7 @@ "@types/interpret" "*" "@types/node" "*" -"@types/lodash.frompairs@^4.0.5": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@types/lodash.frompairs/-/lodash.frompairs-4.0.7.tgz#ee9b800b4dab37cd063dcc652a2bd86f7ff984b7" - integrity sha512-7UeH2+GF9yop4AqnPwae5/2TE+eY0WRDy0RRQtNGHjzIgdUhilRskMXvXqUcCSazvbkxasjqydXrIE1OB6bPKA== - dependencies: - "@types/lodash" "*" - -"@types/lodash@*", "@types/lodash@^4.14.167", "@types/lodash@^4.14.85": +"@types/lodash@^4.14.167": version "4.14.182" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== @@ -5694,11 +5687,6 @@ angular@1.8.2, angular@^1.3: resolved "https://registry.yarnpkg.com/angular/-/angular-1.8.2.tgz#5983bbb5a9fa63e213cb7749199e0d352de3a2f1" integrity sha512-IauMOej2xEe7/7Ennahkbb5qd/HFADiNuLSESz9Q27inmi32zB0lnAsFeLEWcox3Gd1F6YhNd1CP7/9IukJ0Gw== -angular@>=1.5, angular@>=1.5.0: - version "1.8.3" - resolved "https://registry.yarnpkg.com/angular/-/angular-1.8.3.tgz#851ad75d5163c105a7e329555ef70c90aa706894" - integrity sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw== - angularjs-scroll-glue@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/angularjs-scroll-glue/-/angularjs-scroll-glue-2.2.0.tgz#07d3399ac16ca874c63b6b5ee2ee30558b37e5d1" @@ -12858,11 +12846,6 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.frompairs@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.frompairs/-/lodash.frompairs-4.0.1.tgz#bc4e5207fa2757c136e573614e9664506b2b1bd2" - integrity sha512-dvqe2I+cO5MzXCMhUnfYFa9MD+/760yx2aTAN1lqEcEkf896TxgrX373igVdqSJj6tQd0jnSLE1UMuKufqqxFw== - lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -12893,7 +12876,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^3.10.0, lodash@^3.6.0, lodash@^4.0.0, lodash@^4.11.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.7.0, lodash@~2.4.1, lodash@~4.17.15, lodash@~4.17.19, lodash@~4.17.21: +lodash@^3.10.0, lodash@^3.6.0, lodash@^4.0.0, lodash@^4.11.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.3.0, lodash@^4.7.0, lodash@~2.4.1, lodash@~4.17.15, lodash@~4.17.19, lodash@~4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -13613,16 +13596,6 @@ ng-file-upload@~12.2.13: resolved "https://registry.yarnpkg.com/ng-file-upload/-/ng-file-upload-12.2.13.tgz#01800f3872e526f95310f8477e99e4f12d0d8d14" integrity sha1-AYAPOHLlJvlTEPhHfpnk8S0NjRQ= -ngcomponent@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ngcomponent/-/ngcomponent-4.1.0.tgz#793e379138f552ea0cd2c767ad0aa7057678e228" - integrity sha512-cGL3iVoqMWTpCfaIwgRKhdaGqiy2Z+CCG0cVfjlBvdqE8saj8xap9B4OTf+qwObxLVZmDTJPDgx3bN6Q/lZ7BQ== - dependencies: - "@types/angular" "^1.6.39" - "@types/lodash" "^4.14.85" - angular ">=1.5.0" - lodash "^4.17.4" - ngtemplate-loader@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ngtemplate-loader/-/ngtemplate-loader-2.1.0.tgz#fb71c614c509b114b3bb28a828cb608757531e96" @@ -15735,16 +15708,6 @@ react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" -react2angular@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/react2angular/-/react2angular-4.0.6.tgz#ec49ef834d101c9a320e25229fc5afa5b29edc4f" - integrity sha512-MDl2WRoTyu7Gyh4+FAIlmsM2mxIa/DjSz6G/d90L1tK8ZRubqVEayKF6IPyAruC5DMhGDVJ7tlAIcu/gMNDjXg== - dependencies: - "@types/lodash.frompairs" "^4.0.5" - angular ">=1.5" - lodash.frompairs "^4.0.1" - ngcomponent "^4.1.0" - react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"