Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/types/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export enum StatsMetric {
REMOTE_BLUEPRINT = 'remote-blueprint',
FILE_BLUEPRINT = 'file-blueprint',
NO_BLUEPRINT = 'no-blueprint',
SITE_COPIED = 'site-copied',
}

export type AggregateInterval = 'daily' | 'weekly' | 'monthly';
4 changes: 3 additions & 1 deletion src/components/content-tab-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DropdownMenu, MenuGroup, Button } from '@wordpress/components';
import { moreVertical } from '@wordpress/icons';
import { useI18n } from '@wordpress/react-i18n';
import { PropsWithChildren } from 'react';
import CopySite from 'src/components/copy-site';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move CopySite and DeleteSite to src/modules/site-settings.
AFAIK these aren't being used anywhere else and src/components should be used for generic, reusable components.

import { CopyTextButton } from 'src/components/copy-text-button';
import DeleteSite from 'src/components/delete-site';
import { useGetWpVersion } from 'src/hooks/use-get-wp-version';
Expand Down Expand Up @@ -64,7 +65,8 @@ export function ContentTabSettings( { selectedSite }: ContentTabSettingsProps )
className="flex items-center"
>
{ ( { onClose }: { onClose: () => void } ) => (
<MenuGroup>
<MenuGroup className="max-w-[160px]">
<CopySite onClose={ onClose } />
<DeleteSite onClose={ onClose } />
</MenuGroup>
) }
Expand Down
33 changes: 33 additions & 0 deletions src/components/copy-site.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { MenuItem } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useI18n } from '@wordpress/react-i18n';
import { useSiteDetails } from 'src/hooks/use-site-details';
import { getIpcApi } from 'src/lib/get-ipc-api';

type CopySiteProps = {
onClose: () => void;
};

const CopySite = ( { onClose }: CopySiteProps ) => {
const { __ } = useI18n();
const { selectedSite } = useSiteDetails();

const isCopyDisabled = ! selectedSite;

return (
<MenuItem
aria-disabled={ isCopyDisabled }
onClick={ () => {
if ( isCopyDisabled || ! selectedSite ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isCopyDisabled is defined as ! selectedSite (line 15), so this condition checks the same thing twice

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR, but I noticed the DeleteSite component has a similar issue.
It would be a nice time to address it in my opinion.

return;
}
onClose();
getIpcApi().triggerAddSiteCopy( selectedSite.id );
} }
disabled={ isCopyDisabled }
>
{ __( 'Copy site' ) }
</MenuItem>
);
};
export default CopySite;
4 changes: 4 additions & 0 deletions src/components/site-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ interface SiteFormErrorProps {
interface SiteFormProps {
className?: string;
children?: React.ReactNode;
beforeAdvancedSettings?: React.ReactNode;
siteName: string;
setSiteName: ( name: string ) => void;
sitePath?: string;
Expand Down Expand Up @@ -250,6 +251,7 @@ function FormImportComponent( {
export const SiteForm = ( {
className,
children,
beforeAdvancedSettings,
siteName,
setSiteName,
phpVersion,
Expand Down Expand Up @@ -369,6 +371,8 @@ export const SiteForm = ( {
</>
) }

{ beforeAdvancedSettings }

{ onSelectPath && (
<>
<div className="flex flex-row items-center mb-1">
Expand Down
3 changes: 3 additions & 0 deletions src/components/site-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ export default function SiteMenu( { className }: SiteMenuProps ) {
setSelectedTab( 'settings' );
setIsEditModalOpen( true );
break;
case 'copy-site':
ipcApi.triggerAddSiteCopy( site.id );
break;
case 'delete':
await handleDeleteSite( site.id, site.name );
break;
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export const IPC_VOID_HANDLERS = < const >[
'showItemInFolder',
'showNotification',
'authenticate',
'triggerAddSiteCopy',
];

// What's New
Expand Down
105 changes: 105 additions & 0 deletions src/hooks/use-site-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ interface SiteDetailsContext {
blueprint?: Blueprint,
callback?: ( site: SiteDetails ) => Promise< void >
) => Promise< SiteDetails | void >;
copySite: (
sourceId: string,
config: Omit< CopySiteConfig, 'siteId' >
) => Promise< SiteDetails | void >;
startServer: ( id: string ) => Promise< void >;
stopServer: ( id: string ) => Promise< void >;
stopAllRunningSites: () => Promise< void >;
Expand All @@ -55,6 +59,7 @@ const defaultContext: SiteDetailsContext = {
siteCreationMessages: {},
setSelectedSiteId: () => undefined,
createSite: async () => undefined,
copySite: async () => undefined,
startServer: async () => undefined,
stopServer: async () => undefined,
stopAllRunningSites: async () => undefined,
Expand Down Expand Up @@ -186,6 +191,15 @@ export function SiteDetailsProvider( { children }: SiteDetailsProviderProps ) {
}
} );

useIpcListener( 'copySiteProgress', ( _, { siteId, message } ) => {
if ( siteId && message ) {
setSiteCreationMessages( ( prev ) => ( {
...prev,
[ siteId ]: message,
} ) );
}
} );

const toggleLoadingServerForSite = useCallback( ( siteId: string ) => {
setLoadingServer( ( currentLoading ) => ( {
...currentLoading,
Expand Down Expand Up @@ -334,6 +348,95 @@ export function SiteDetailsProvider( { children }: SiteDetailsProviderProps ) {
[ selectedTab, setSelectedSiteId, setSelectedTab ]
);

const copySite = useCallback(
async (
sourceId: string,
config: Omit< CopySiteConfig, 'siteId' >
): Promise< SiteDetails | void > => {
const tempSiteId = crypto.randomUUID();
setAddingSiteIds( ( prev ) => [ ...prev, tempSiteId ] );
setData( ( prevData ) =>
sortSites( [
...prevData,
{
id: tempSiteId,
name: config.newName,
path: config.newPath,
port: -1,
running: false,
isAddingSite: true,
phpVersion: '',
},
] )
);
setSelectedSiteId( tempSiteId );

let newSite: SiteDetails;
try {
newSite = await getIpcApi().copySite( sourceId, {
...config,
siteId: tempSiteId,
} );
if ( ! newSite ) {
setTimeout( () => {
setAddingSiteIds( ( prev ) => prev.filter( ( id ) => id !== tempSiteId ) );
setData( ( prevData ) =>
sortSites( prevData.filter( ( site ) => site.id !== tempSiteId ) )
);
}, 2000 );
return;
}

setAddingSiteIds( ( prev ) => {
prev.push( newSite.id );
return prev;
} );
Comment on lines +390 to +393
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
setAddingSiteIds( ( prev ) => {
prev.push( newSite.id );
return prev;
} );
setAddingSiteIds( ( prev ) => [ ...prev, newSite.id ] );


setSelectedSiteId( ( prevSelectedSiteId ) => {
if ( prevSelectedSiteId === tempSiteId ) {
if ( selectedTab !== 'overview' ) {
setSelectedTab( 'overview' );
}
return newSite.id;
}
return prevSelectedSiteId;
} );

setData( ( prevData ) =>
prevData.map( ( site ) => ( site.id === tempSiteId ? newSite : site ) )
);

setSiteCreationMessages( ( prev ) => {
const { [ newSite.id ]: _, ...rest } = prev;
return rest;
} );

return newSite;
} catch ( error ) {
getIpcApi().showErrorMessageBox( {
title: __( 'Failed to copy site' ),
message: __(
'An error occurred while copying the site. Please try again. If this problem persists, please contact support.'
),
error: simplifyErrorForDisplay( error ),
showOpenLogs: true,
} );

setTimeout( () => {
setAddingSiteIds( ( prev ) => prev.filter( ( id ) => id !== tempSiteId ) );
setData( ( prevData ) =>
sortSites( prevData.filter( ( site ) => site.id !== tempSiteId ) )
);
}, 2000 );
} finally {
setAddingSiteIds( ( prev ) =>
prev.filter( ( id ) => id !== tempSiteId && id !== newSite?.id )
);
}
},
[ selectedTab, setSelectedSiteId, setSelectedTab ]
);

const updateSite = useCallback( async ( site: SiteDetails ) => {
await getIpcApi().updateSite( site );
const updatedSites = await getIpcApi().getSiteDetails();
Expand Down Expand Up @@ -511,6 +614,7 @@ export function SiteDetailsProvider( { children }: SiteDetailsProviderProps ) {
data,
setSelectedSiteId,
createSite,
copySite,
updateSite,
startServer,
stopServer,
Expand All @@ -530,6 +634,7 @@ export function SiteDetailsProvider( { children }: SiteDetailsProviderProps ) {
data,
setSelectedSiteId,
createSite,
copySite,
updateSite,
startServer,
stopServer,
Expand Down
Loading