mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-01 02:41:39 -05:00
Optimised bundle size and configuration in AdminX (#18227)
refs https://github.com/TryGhost/Product/issues/3832 --- ### <samp>🤖 Generated by Copilot at eaad533</samp> Refactored the code for modal components in the admin-x-settings app to improve performance, readability, type-safety, and compatibility. Used dynamic import, default import, `import.meta.glob`, and type aliases to simplify the logic of importing and rendering modal components. Applied the type alias `RoutingModalProps` to all modal components to ensure consistent and safe props.
This commit is contained in:
parent
65838394c3
commit
64d7c73b00
10 changed files with 72 additions and 82 deletions
|
@ -1,48 +1,7 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface UseDynamicSVGImportOptions {
|
||||
onCompleted?: (
|
||||
name: string,
|
||||
SvgIcon: React.FC<React.SVGProps<SVGSVGElement>> | undefined
|
||||
) => void;
|
||||
onError?: (err: Error) => void;
|
||||
}
|
||||
|
||||
function useDynamicSVGImport(
|
||||
name: string,
|
||||
options: UseDynamicSVGImportOptions = {}
|
||||
) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [SvgComponent, setSvgComponent] = useState<React.FC<React.SVGProps<SVGSVGElement>> | null | undefined>(null);
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
const {onCompleted, onError} = options;
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
const importIcon = async (): Promise<void> => {
|
||||
try {
|
||||
const SvgIcon: React.FC<React.SVGProps<SVGSVGElement>> = (
|
||||
await import(`../assets/icons/${name}.svg`)
|
||||
).ReactComponent;
|
||||
setSvgComponent(() => SvgIcon);
|
||||
onCompleted?.(name, SvgIcon);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
onError?.(err);
|
||||
setError(err);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
} finally {
|
||||
setLoading(() => false);
|
||||
}
|
||||
};
|
||||
importIcon();
|
||||
}, [name, onCompleted, onError]);
|
||||
|
||||
return {error, loading, SvgComponent};
|
||||
}
|
||||
const icons: Record<string, {ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>}> = import.meta.glob('../assets/icons/*.svg', {eager: true});
|
||||
|
||||
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number;
|
||||
|
||||
|
@ -69,7 +28,7 @@ interface IconProps {
|
|||
* - all strokes must be paths and _NOT_ outlined objects. Stroke width should be set to 1.5px
|
||||
*/
|
||||
const Icon: React.FC<IconProps> = ({name, size = 'md', colorClass = '', className = ''}) => {
|
||||
const {SvgComponent} = useDynamicSVGImport(name);
|
||||
const {ReactComponent: SvgComponent} = icons[`../assets/icons/${name}.svg`];
|
||||
|
||||
let styles = '';
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 57 KiB |
Binary file not shown.
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 89 KiB |
|
@ -1,6 +1,7 @@
|
|||
import NiceModal, {NiceModalHocProps} from '@ebay/nice-modal-react';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import React, {createContext, useCallback, useEffect, useState} from 'react';
|
||||
import {ScrollSectionProvider} from '../../hooks/useScrollSection';
|
||||
import type {ModalComponent, ModalName} from './routing/modals';
|
||||
|
||||
export type RouteParams = {[key: string]: string}
|
||||
|
||||
|
@ -32,32 +33,7 @@ export type RoutingModalProps = {
|
|||
params?: Record<string, string>
|
||||
}
|
||||
|
||||
const modals: {[key: string]: () => Promise<{default: React.FC<NiceModalHocProps & RoutingModalProps>}>} = {
|
||||
AddIntegrationModal: () => import('../settings/advanced/integrations/AddIntegrationModal'),
|
||||
AddNewsletterModal: () => import('../settings/email/newsletters/AddNewsletterModal'),
|
||||
AddRecommendationModal: () => import('../settings/site/recommendations/AddRecommendationModal'),
|
||||
AmpModal: () => import('../settings/advanced/integrations/AmpModal'),
|
||||
CustomIntegrationModal: () => import('../settings/advanced/integrations/CustomIntegrationModal'),
|
||||
DesignAndThemeModal: () => import('../settings/site/DesignAndThemeModal'),
|
||||
EditRecommendationModal: () => import('../settings/site/recommendations/EditRecommendationModal'),
|
||||
FirstpromoterModal: () => import('../settings/advanced/integrations/FirstPromoterModal'),
|
||||
HistoryModal: () => import('../settings/advanced/HistoryModal'),
|
||||
InviteUserModal: () => import('../settings/general/InviteUserModal'),
|
||||
NavigationModal: () => import('../settings/site/NavigationModal'),
|
||||
NewsletterDetailModal: () => import('../settings/email/newsletters/NewsletterDetailModal'),
|
||||
PinturaModal: () => import('../settings/advanced/integrations/PinturaModal'),
|
||||
PortalModal: () => import('../settings/membership/portal/PortalModal'),
|
||||
SlackModal: () => import('../settings/advanced/integrations/SlackModal'),
|
||||
StripeConnectModal: () => import('../settings/membership/stripe/StripeConnectModal'),
|
||||
TierDetailModal: () => import('../settings/membership/tiers/TierDetailModal'),
|
||||
UnsplashModal: () => import('../settings/advanced/integrations/UnsplashModal'),
|
||||
UserDetailModal: () => import('../settings/general/UserDetailModal'),
|
||||
ZapierModal: () => import('../settings/advanced/integrations/ZapierModal'),
|
||||
AnnouncementBarModal: () => import('../settings/site/AnnouncementBarModal'),
|
||||
EmbedSignupFormModal: () => import('../settings/membership/embedSignup/EmbedSignupFormModal')
|
||||
};
|
||||
|
||||
const modalPaths: {[key: string]: keyof typeof modals} = {
|
||||
const modalPaths: {[key: string]: ModalName} = {
|
||||
'design/edit/themes': 'DesignAndThemeModal',
|
||||
'design/edit': 'DesignAndThemeModal',
|
||||
'navigation/edit': 'NavigationModal',
|
||||
|
@ -118,8 +94,8 @@ const handleNavigation = (currentRoute: string | undefined) => {
|
|||
pathName,
|
||||
changingModal: modalName && modalName !== currentModalName,
|
||||
modal: (path && modalName) ?
|
||||
modals[modalName]().then(({default: component}) => {
|
||||
NiceModal.show(component, {pathName, params: matchRoute(pathName, path)});
|
||||
import('./routing/modals').then(({default: modals}) => {
|
||||
NiceModal.show(modals[modalName] as ModalComponent, {pathName, params: matchRoute(pathName, path)});
|
||||
}) :
|
||||
undefined
|
||||
};
|
||||
|
@ -147,7 +123,7 @@ const RoutingProvider: React.FC<RouteProviderProps> = ({externalNavigate, childr
|
|||
useEffect(() => {
|
||||
// Preload all the modals after initial render to avoid a delay when opening them
|
||||
setTimeout(() => {
|
||||
Object.values(modalPaths).forEach(modal => modals[modal]());
|
||||
import('./routing/modals');
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import type {NiceModalHocProps} from '@ebay/nice-modal-react';
|
||||
import type {RoutingModalProps} from '../RoutingProvider';
|
||||
|
||||
import AddIntegrationModal from '../../settings/advanced/integrations/AddIntegrationModal';
|
||||
import AddNewsletterModal from '../../settings/email/newsletters/AddNewsletterModal';
|
||||
import AddRecommendationModal from '../../settings/site/recommendations/AddRecommendationModal';
|
||||
import AmpModal from '../../settings/advanced/integrations/AmpModal';
|
||||
import AnnouncementBarModal from '../../settings/site/AnnouncementBarModal';
|
||||
import CustomIntegrationModal from '../../settings/advanced/integrations/CustomIntegrationModal';
|
||||
import DesignAndThemeModal from '../../settings/site/DesignAndThemeModal';
|
||||
import EditRecommendationModal from '../../settings/site/recommendations/EditRecommendationModal';
|
||||
import EmbedSignupFormModal from '../../settings/membership/embedSignup/EmbedSignupFormModal';
|
||||
import FirstpromoterModal from '../../settings/advanced/integrations/FirstPromoterModal';
|
||||
import HistoryModal from '../../settings/advanced/HistoryModal';
|
||||
import InviteUserModal from '../../settings/general/InviteUserModal';
|
||||
import NavigationModal from '../../settings/site/NavigationModal';
|
||||
import NewsletterDetailModal from '../../settings/email/newsletters/NewsletterDetailModal';
|
||||
import PinturaModal from '../../settings/advanced/integrations/PinturaModal';
|
||||
import PortalModal from '../../settings/membership/portal/PortalModal';
|
||||
import SlackModal from '../../settings/advanced/integrations/SlackModal';
|
||||
import StripeConnectModal from '../../settings/membership/stripe/StripeConnectModal';
|
||||
import TierDetailModal from '../../settings/membership/tiers/TierDetailModal';
|
||||
import UnsplashModal from '../../settings/advanced/integrations/UnsplashModal';
|
||||
import UserDetailModal from '../../settings/general/UserDetailModal';
|
||||
import ZapierModal from '../../settings/advanced/integrations/ZapierModal';
|
||||
|
||||
const modals = {
|
||||
AddIntegrationModal,
|
||||
AddNewsletterModal,
|
||||
AddRecommendationModal,
|
||||
AmpModal,
|
||||
CustomIntegrationModal,
|
||||
DesignAndThemeModal,
|
||||
EditRecommendationModal,
|
||||
FirstpromoterModal,
|
||||
HistoryModal,
|
||||
InviteUserModal,
|
||||
NavigationModal,
|
||||
NewsletterDetailModal,
|
||||
PinturaModal,
|
||||
PortalModal,
|
||||
SlackModal,
|
||||
StripeConnectModal,
|
||||
TierDetailModal,
|
||||
UnsplashModal,
|
||||
UserDetailModal,
|
||||
ZapierModal,
|
||||
AnnouncementBarModal,
|
||||
EmbedSignupFormModal
|
||||
} satisfies {[key: string]: ModalComponent};
|
||||
|
||||
export default modals;
|
||||
|
||||
export type ModalName = keyof typeof modals;
|
||||
export type ModalComponent = React.FC<NiceModalHocProps & RoutingModalProps>
|
|
@ -6,11 +6,10 @@ import React, {useEffect, useState} from 'react';
|
|||
import TextField from '../../../../admin-x-ds/global/form/TextField';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import {HostLimitError, useLimiter} from '../../../../hooks/useLimiter';
|
||||
import {RoutingModalProps} from '../../../providers/RoutingProvider';
|
||||
import {useCreateIntegration} from '../../../../api/integrations';
|
||||
|
||||
interface AddIntegrationModalProps {}
|
||||
|
||||
const AddIntegrationModal: React.FC<AddIntegrationModalProps> = () => {
|
||||
const AddIntegrationModal: React.FC<RoutingModalProps> = () => {
|
||||
const modal = useModal();
|
||||
const {updateRoute} = useRouting();
|
||||
const [name, setName] = useState('');
|
||||
|
|
|
@ -9,14 +9,13 @@ import Toggle from '../../../../admin-x-ds/global/form/Toggle';
|
|||
import useForm from '../../../../hooks/useForm';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import {HostLimitError, useLimiter} from '../../../../hooks/useLimiter';
|
||||
import {RoutingModalProps} from '../../../providers/RoutingProvider';
|
||||
import {showToast} from '../../../../admin-x-ds/global/Toast';
|
||||
import {toast} from 'react-hot-toast';
|
||||
import {useAddNewsletter} from '../../../../api/newsletters';
|
||||
import {useBrowseMembers} from '../../../../api/members';
|
||||
|
||||
interface AddNewsletterModalProps {}
|
||||
|
||||
const AddNewsletterModal: React.FC<AddNewsletterModalProps> = () => {
|
||||
const AddNewsletterModal: React.FC<RoutingModalProps> = () => {
|
||||
const modal = useModal();
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import URLTextField from '../../../../admin-x-ds/global/form/URLTextField';
|
|||
import useForm from '../../../../hooks/useForm';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import {EditOrAddRecommendation, useBrowseRecommendations} from '../../../../api/recommendations';
|
||||
import {RoutingModalProps} from '../../../providers/RoutingProvider';
|
||||
import {arePathsEqual, trimSearchAndHash} from '../../../../utils/url';
|
||||
import {showToast} from '../../../../admin-x-ds/global/Toast';
|
||||
import {toast} from 'react-hot-toast';
|
||||
|
@ -18,7 +19,7 @@ interface AddRecommendationModalProps {
|
|||
animate?: boolean
|
||||
}
|
||||
|
||||
const AddRecommendationModal: React.FC<AddRecommendationModalProps> = ({recommendation, animate}) => {
|
||||
const AddRecommendationModal: React.FC<RoutingModalProps & AddRecommendationModalProps> = ({recommendation, animate}) => {
|
||||
const modal = useModal();
|
||||
const {updateRoute} = useRouting();
|
||||
const {query: queryOembed} = useGetOembed();
|
||||
|
|
|
@ -15,7 +15,7 @@ interface AddRecommendationModalProps {
|
|||
|
||||
const AddRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({recommendation, animate}) => {
|
||||
const modal = useModal();
|
||||
const {updateRoute} = useRouting();
|
||||
const {updateRoute, route} = useRouting();
|
||||
const {mutateAsync: addRecommendation} = useAddRecommendation();
|
||||
|
||||
const {formState, updateForm, handleSave, saveState, errors} = useForm({
|
||||
|
@ -62,6 +62,7 @@ const AddRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({r
|
|||
// Switch modal without changing the route, but pass along any changes that were already made
|
||||
modal.remove();
|
||||
NiceModal.show(AddRecommendationModal, {
|
||||
pathName: route,
|
||||
animate: false,
|
||||
recommendation: {
|
||||
...formState
|
||||
|
|
Loading…
Add table
Reference in a new issue