diff --git a/apps/studio/src/ipc-handlers.ts b/apps/studio/src/ipc-handlers.ts
index f9f5c18a82..5343a0dad0 100644
--- a/apps/studio/src/ipc-handlers.ts
+++ b/apps/studio/src/ipc-handlers.ts
@@ -87,7 +87,7 @@ import { supportedEditorConfig, SupportedEditor } from 'src/modules/user-setting
import { getUserEditor, getUserTerminal } from 'src/modules/user-settings/lib/ipc-handlers';
import { winFindEditorPath } from 'src/modules/user-settings/lib/win-editor-path';
import { SiteServer, stopAllServers as triggerStopAllServers } from 'src/site-server';
-import { DEFAULT_SITE_PATH, getSiteThumbnailPath } from 'src/storage/paths';
+import { getSiteThumbnailPath, resolveDefaultSiteDirectory } from 'src/storage/paths';
import {
loadUserData,
lockAppdata,
@@ -137,6 +137,8 @@ export {
saveUserLocale,
saveUserTerminal,
showUserSettings,
+ getDefaultSiteDirectory,
+ saveDefaultSiteDirectory,
} from 'src/modules/user-settings/lib/ipc-handlers';
export async function getAgentInstructionsStatus(
@@ -608,9 +610,11 @@ export async function showSaveAsDialog( event: IpcMainInvokeEvent, options: Save
throw new Error( `No window found for sender of showSaveAsDialog message: ${ event.frameId }` );
}
+ const defaultSiteDirectory = await resolveDefaultSiteDirectory();
const defaultPath =
- options.defaultPath === nodePath.basename( options.defaultPath ?? '' )
- ? nodePath.join( DEFAULT_SITE_PATH, options.defaultPath )
+ typeof options.defaultPath === 'string' &&
+ options.defaultPath === nodePath.basename( options.defaultPath )
+ ? nodePath.join( defaultSiteDirectory, options.defaultPath )
: options.defaultPath;
const { canceled, filePath } = await dialog.showSaveDialog( parentWindow, {
defaultPath,
@@ -645,9 +649,10 @@ export async function showOpenFolderDialog(
};
}
+ const defaultSiteDirectory = await resolveDefaultSiteDirectory();
const { canceled, filePaths } = await dialog.showOpenDialog( parentWindow, {
title,
- defaultPath: defaultDialogPath !== '' ? defaultDialogPath : DEFAULT_SITE_PATH,
+ defaultPath: defaultDialogPath !== '' ? defaultDialogPath : defaultSiteDirectory,
properties: [
'openDirectory',
'createDirectory', // allow user to create new directories; macOS only
@@ -691,7 +696,8 @@ export async function copySite(
}
const sourceSite = sourceServer.details;
- const finalSitePath = nodePath.join( DEFAULT_SITE_PATH, sanitizeFolderName( siteName ) );
+ const defaultSiteDirectory = await resolveDefaultSiteDirectory();
+ const finalSitePath = nodePath.join( defaultSiteDirectory, sanitizeFolderName( siteName ) );
console.log( `Copying site '${ sourceSite.name }' to '${ siteName }'` );
@@ -889,7 +895,8 @@ export async function generateProposedSitePath(
_event: IpcMainInvokeEvent,
siteName: string
): Promise< FolderDialogResponse > {
- const path = nodePath.join( DEFAULT_SITE_PATH, sanitizeFolderName( siteName ) );
+ const defaultSiteDirectory = await resolveDefaultSiteDirectory();
+ const path = nodePath.join( defaultSiteDirectory, sanitizeFolderName( siteName ) );
try {
return {
@@ -924,9 +931,10 @@ export async function generateSiteNameFromList(
_event: IpcMainInvokeEvent,
usedSites: SiteDetails[]
): Promise< string > {
+ const defaultSiteDirectory = await resolveDefaultSiteDirectory();
return generateSiteName(
usedSites.map( ( s ) => s.name ),
- DEFAULT_SITE_PATH
+ defaultSiteDirectory
);
}
@@ -935,10 +943,11 @@ export async function generateNumberedNameFromList(
baseName: string,
usedSites: SiteDetails[]
): Promise< string > {
+ const defaultSiteDirectory = await resolveDefaultSiteDirectory();
return generateNumberedName(
baseName,
usedSites.map( ( s ) => s.name ),
- DEFAULT_SITE_PATH
+ defaultSiteDirectory
);
}
diff --git a/apps/studio/src/modules/user-settings/components/default-directory-picker.tsx b/apps/studio/src/modules/user-settings/components/default-directory-picker.tsx
new file mode 100644
index 0000000000..f6960cd13e
--- /dev/null
+++ b/apps/studio/src/modules/user-settings/components/default-directory-picker.tsx
@@ -0,0 +1,39 @@
+import { useI18n } from '@wordpress/react-i18n';
+import Button from 'src/components/button';
+import { SettingsFormField } from './settings-form-field';
+
+interface DefaultDirectoryPickerProps {
+ directory?: string;
+ isLoading: boolean;
+ isSelecting: boolean;
+ onPick: () => void;
+}
+
+export const DefaultDirectoryPicker = ( {
+ directory,
+ isLoading,
+ isSelecting,
+ onPick,
+}: DefaultDirectoryPickerProps ) => {
+ const { __ } = useI18n();
+
+ return (
+
+
+
+ { isLoading ? __( 'Loading...' ) : directory ?? '' }
+
+
+
+
+
+
+ );
+};
diff --git a/apps/studio/src/modules/user-settings/components/preferences-tab.tsx b/apps/studio/src/modules/user-settings/components/preferences-tab.tsx
index 9ff501bbfd..c328d11be6 100644
--- a/apps/studio/src/modules/user-settings/components/preferences-tab.tsx
+++ b/apps/studio/src/modules/user-settings/components/preferences-tab.tsx
@@ -1,10 +1,11 @@
import { SupportedLocale } from '@studio/common/lib/locale';
import { useI18n } from '@wordpress/react-i18n';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import Button from 'src/components/button';
import { useFeatureFlags } from 'src/hooks/use-feature-flags';
import { isWindowsStore } from 'src/lib/app-globals';
import { McpSettings } from 'src/modules/mcp/components/mcp-settings';
+import { getIpcApi } from 'src/lib/get-ipc-api';
import { EditorPicker } from 'src/modules/user-settings/components/editor-picker';
import { LanguagePicker } from 'src/modules/user-settings/components/language-picker';
import { StudioCliToggle } from 'src/modules/user-settings/components/studio-cli-toggle';
@@ -21,6 +22,7 @@ import {
useGetStudioCliIsInstalledQuery,
useSaveStudioCliIsInstalledMutation,
} from 'src/stores/installed-apps-api';
+import { DefaultDirectoryPicker } from './default-directory-picker';
export const PreferencesTab = ( { onClose }: { onClose: () => void } ) => {
const { __ } = useI18n();
@@ -40,6 +42,10 @@ export const PreferencesTab = ( { onClose }: { onClose: () => void } ) => {
const [ dirtyEditor, setDirtyEditor ] = useState< SupportedEditor | null >();
const [ dirtyTerminal, setDirtyTerminal ] = useState< SupportedTerminal >();
const [ dirtyIsCliInstalled, setDirtyIsCliInstalled ] = useState< boolean >();
+ const [ storedDefaultSiteDirectory, setStoredDefaultSiteDirectory ] = useState< string >();
+ const [ defaultSiteDirectory, setDefaultSiteDirectory ] = useState< string >();
+ const [ isLoadingDefaultSiteDirectory, setIsLoadingDefaultSiteDirectory ] = useState( true );
+ const [ isSelectingDefaultDirectory, setIsSelectingDefaultDirectory ] = useState( false );
const savePreferences = async () => {
if ( dirtyLocale ) {
@@ -54,6 +60,13 @@ export const PreferencesTab = ( { onClose }: { onClose: () => void } ) => {
if ( dirtyIsCliInstalled !== undefined ) {
await saveCliIsInstalled( dirtyIsCliInstalled );
}
+ const isDefaultDirectoryDirty =
+ storedDefaultSiteDirectory !== undefined &&
+ defaultSiteDirectory !== undefined &&
+ storedDefaultSiteDirectory !== defaultSiteDirectory;
+ if ( isDefaultDirectoryDirty && defaultSiteDirectory ) {
+ await getIpcApi().saveDefaultSiteDirectory( defaultSiteDirectory );
+ }
onClose();
};
@@ -67,8 +80,45 @@ export const PreferencesTab = ( { onClose }: { onClose: () => void } ) => {
[ dirtyEditor, editor ],
[ dirtyTerminal, terminal ],
[ dirtyIsCliInstalled, isCliInstalled ],
+ [ defaultSiteDirectory, storedDefaultSiteDirectory ],
].some( ( [ a, b ] ) => a !== undefined && a !== b );
+ useEffect( () => {
+ let isMounted = true;
+ void ( async () => {
+ try {
+ const directory = await getIpcApi().getDefaultSiteDirectory();
+ if ( ! isMounted ) {
+ return;
+ }
+ setStoredDefaultSiteDirectory( directory );
+ setDefaultSiteDirectory( directory );
+ } finally {
+ if ( isMounted ) {
+ setIsLoadingDefaultSiteDirectory( false );
+ }
+ }
+ } )();
+ return () => {
+ isMounted = false;
+ };
+ }, [] );
+
+ const handleChangeDefaultDirectory = async () => {
+ setIsSelectingDefaultDirectory( true );
+ try {
+ const response = await getIpcApi().showOpenFolderDialog(
+ __( 'Select default site directory' ),
+ defaultSiteDirectory ?? ''
+ );
+ if ( response?.path ) {
+ setDefaultSiteDirectory( response.path );
+ }
+ } finally {
+ setIsSelectingDefaultDirectory( false );
+ }
+ };
+
return (
<>
@@ -84,6 +134,12 @@ export const PreferencesTab = ( { onClose }: { onClose: () => void } ) => {
{ enableAgentSuite && }
>
) }
+