diff --git a/src/components/edit-absolute-url.tsx b/src/components/edit-absolute-url.tsx
new file mode 100644
index 000000000..f6ce254ca
--- /dev/null
+++ b/src/components/edit-absolute-url.tsx
@@ -0,0 +1,89 @@
+import { useI18n } from '@wordpress/react-i18n';
+import { FormEvent, useCallback, useState } from 'react';
+import { useSiteDetails } from '../hooks/use-site-details';
+import Button from './button';
+import Modal from './modal';
+import TextControlComponent from './text-control';
+
+export default function EditAbsoluteUrl() {
+ const { __ } = useI18n();
+ const { updateSite, selectedSite, stopServer, startServer } = useSiteDetails();
+ const [ isEditingSite, setIsEditingSite ] = useState( false );
+ const [ showEditAbsoluteUrlModal, setShowEditAbsoluteUrlModal ] = useState( false );
+ const [ localUrl, setLocalUrl ] = useState(
+ selectedSite?.absoluteUrl || `http://localhost:${ selectedSite?.port }`
+ );
+
+ const onLocalUrlEdit = useCallback(
+ async ( event: FormEvent ) => {
+ event.preventDefault();
+ if ( ! selectedSite ) {
+ return;
+ }
+
+ setIsEditingSite( true );
+ await updateSite( {
+ ...selectedSite,
+ absoluteUrl: localUrl,
+ } );
+ if ( selectedSite.running ) {
+ await stopServer( selectedSite.id );
+ await startServer( selectedSite.id );
+ }
+ setIsEditingSite( false );
+ setShowEditAbsoluteUrlModal( false );
+ },
+ [ selectedSite, localUrl, updateSite, stopServer, startServer ]
+ );
+
+ return (
+ <>
+ { showEditAbsoluteUrlModal && (
+
setShowEditAbsoluteUrlModal( false ) }
+ >
+
+
+ ) }
+
+ >
+ );
+}
diff --git a/src/ipc-types.d.ts b/src/ipc-types.d.ts
index a67a68a03..e445a243d 100644
--- a/src/ipc-types.d.ts
+++ b/src/ipc-types.d.ts
@@ -17,6 +17,7 @@ interface StoppedSiteDetails {
id: string;
name: string;
path: string;
+ absoluteUrl?: string;
port?: number;
phpVersion: string;
adminPassword?: string;
diff --git a/src/site-server.ts b/src/site-server.ts
index 102b650e8..7476d23a3 100644
--- a/src/site-server.ts
+++ b/src/site-server.ts
@@ -88,8 +88,7 @@ export class SiteServer {
siteTitle: this.details.name,
php: this.details.phpVersion,
} );
- const absoluteUrl = `http://localhost:${ port }`;
- options.absoluteUrl = absoluteUrl;
+ options.absoluteUrl = this.details.absoluteUrl || `http://localhost:${ port }`;
options.siteLanguage = await getPreferredSiteLanguage( options.wordPressVersion );
if ( options.mode !== WPNowMode.WORDPRESS ) {
@@ -124,6 +123,7 @@ export class SiteServer {
name: site.name,
path: site.path,
phpVersion: site.phpVersion,
+ absoluteUrl: site.absoluteUrl,
};
}
diff --git a/src/storage/user-data.ts b/src/storage/user-data.ts
index bf62df612..22414571e 100644
--- a/src/storage/user-data.ts
+++ b/src/storage/user-data.ts
@@ -104,29 +104,35 @@ export async function saveUserData( data: UserData ): Promise< void > {
function toDiskFormat( { sites, ...rest }: UserData ): PersistedUserData {
return {
version: 1,
- sites: sites.map( ( { id, path, adminPassword, port, phpVersion, name, themeDetails } ) => {
- // No object spreading allowed. TypeScript's structural typing is too permissive and
- // will permit us to persist properties that aren't in the type definition.
- // Add each property explicitly instead.
- const persistedSiteDetails: PersistedUserData[ 'sites' ][ number ] = {
- id,
- name,
- path,
- adminPassword,
- port,
- phpVersion,
- themeDetails: {
- name: themeDetails?.name || '',
- path: themeDetails?.path || '',
- slug: themeDetails?.slug || '',
- isBlockTheme: themeDetails?.isBlockTheme || false,
- supportsWidgets: themeDetails?.supportsWidgets || false,
- supportsMenus: themeDetails?.supportsMenus || false,
- },
- };
+ sites: sites.map(
+ ( { id, path, adminPassword, port, phpVersion, name, themeDetails, absoluteUrl } ) => {
+ // No object spreading allowed. TypeScript's structural typing is too permissive and
+ // will permit us to persist properties that aren't in the type definition.
+ // Add each property explicitly instead.
+ const persistedSiteDetails: PersistedUserData[ 'sites' ][ number ] = {
+ id,
+ name,
+ path,
+ adminPassword,
+ port,
+ phpVersion,
+ themeDetails: {
+ name: themeDetails?.name || '',
+ path: themeDetails?.path || '',
+ slug: themeDetails?.slug || '',
+ isBlockTheme: themeDetails?.isBlockTheme || false,
+ supportsWidgets: themeDetails?.supportsWidgets || false,
+ supportsMenus: themeDetails?.supportsMenus || false,
+ },
+ };
- return persistedSiteDetails;
- } ),
+ if ( absoluteUrl ) {
+ persistedSiteDetails.absoluteUrl = absoluteUrl;
+ }
+
+ return persistedSiteDetails;
+ }
+ ),
...rest,
};
}