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

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import {
__experimentalVStack as VStack,
__experimentalHStack as HStack,
} from '@wordpress/components';
import { useEffect } from 'react';
import { useLocalizationSupport } from '../hooks/use-localization-support';
import { useOnboarding } from '../hooks/use-onboarding';
import { useSidebarVisibility } from '../hooks/use-sidebar-visibility';
import { isWindows } from '../lib/app-globals';
import { cx } from '../lib/cx';
import { getIpcApi } from '../lib/get-ipc-api';
import MainSidebar from './main-sidebar';
import Onboarding from './onboarding';
import { SiteContentTabs } from './site-content-tabs';
Expand All @@ -19,6 +21,10 @@ export default function App() {
const { needsOnboarding } = useOnboarding();
const { isSidebarVisible, toggleSidebar } = useSidebarVisibility();

useEffect( () => {
getIpcApi().setupAppMenu( { needsOnboarding } );
}, [ needsOnboarding ] );

return (
<>
{ needsOnboarding ? (
Expand Down
54 changes: 18 additions & 36 deletions src/components/onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ import { FormEvent, useCallback, useEffect, useState } from 'react';
import { ACCEPTED_IMPORT_FILE_TYPES } from '../constants';
import { useAddSite } from '../hooks/use-add-site';
import { useDragAndDropFile } from '../hooks/use-drag-and-drop-file';
import { useImportExport } from '../hooks/use-import-export';
import { useIpcListener } from '../hooks/use-ipc-listener';
import { useOnboarding } from '../hooks/use-onboarding';
import { useSiteDetails } from '../hooks/use-site-details';
import { generateSiteName } from '../lib/generate-site-name';
import { getIpcApi } from '../lib/get-ipc-api';
import Button from './button';
Expand Down Expand Up @@ -39,7 +35,6 @@ const GradientBox = () => {

export default function Onboarding() {
const { __ } = useI18n();
const { needsOnboarding } = useOnboarding();
const {
setSiteName,
setProposedSitePath,
Expand All @@ -57,12 +52,6 @@ export default function Onboarding() {
fileForImport,
} = useAddSite();
const [ fileError, setFileError ] = useState( '' );
const { importState } = useImportExport();
const { data } = useSiteDetails();

const isAnySiteProcessing = data.some(
( site ) => site.isAddingSite || importState[ site.id ]?.isNewSite
);

const siteAddedMessage = sprintf(
// translators: %s is the site name.
Expand Down Expand Up @@ -101,21 +90,24 @@ export default function Onboarding() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [] );

const onAddSite = useCallback( async () => {
// Prompt the user to enable optimizations on Windows
try {
await getIpcApi().promptWindowsSpeedUpSites( { skipIfAlreadyPrompted: true } );
} catch ( error ) {
console.error( error );
}
const handleSubmit = useCallback(
async ( event: FormEvent ) => {
event.preventDefault();
try {
await getIpcApi().promptWindowsSpeedUpSites( { skipIfAlreadyPrompted: true } );
} catch ( error ) {
console.error( error );
}

try {
await handleAddSiteClick();
speak( siteAddedMessage );
} catch {
// No need to handle error here, it's already handled in handleAddSiteClick
}
}, [ handleAddSiteClick, siteAddedMessage ] );
try {
await handleAddSiteClick();
speak( siteAddedMessage );
} catch {
// No need to handle error here, it's already handled in handleAddSiteClick
}
},
[ handleAddSiteClick, siteAddedMessage ]
);

const handleImportFile = useCallback(
async ( file: File ) => {
Expand All @@ -125,13 +117,6 @@ export default function Onboarding() {
[ setFileForImport ]
);

useIpcListener( 'add-site', () => {
if ( isAnySiteProcessing || ! needsOnboarding ) {
return;
}
onAddSite();
} );
Comment thread
fluiddot marked this conversation as resolved.

return (
<div className="flex flex-row flex-grow" data-testid="onboarding">
<div className="w-1/2 bg-a8c-blueberry pb-[50px] pt-[46px] px-[50px] flex flex-col justify-between">
Expand All @@ -154,10 +139,7 @@ export default function Onboarding() {
onSelectPath={ handlePathSelectorClick }
error={ error }
doesPathContainWordPress={ doesPathContainWordPress }
onSubmit={ async ( event: FormEvent ) => {
event.preventDefault();
await onAddSite();
} }
onSubmit={ handleSubmit }
fileForImport={ fileForImport }
setFileForImport={ setFileForImport }
onFileSelected={ handleImportFile }
Expand Down
43 changes: 24 additions & 19 deletions src/hooks/use-i18n-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { I18nProvider } from '@wordpress/react-i18n';
import { createContext, useContext, useEffect, useMemo, useState, useCallback } from 'react';
import { getIpcApi } from '../lib/get-ipc-api';
import { SupportedLocale, getLocaleData } from '../lib/locale';
import { useOnboarding } from './use-onboarding';

interface I18nDataContext {
setLocale: ( localeKey: SupportedLocale ) => void;
Expand All @@ -19,26 +20,30 @@ export const I18nDataProvider = ( { children }: { children: React.ReactNode } )
const [ initialized, setInitialized ] = useState< boolean >( false );
const [ i18n, setI18n ] = useState< I18n | null >( null );
const [ locale, setLocale ] = useState< SupportedLocale >( 'en' );
const { needsOnboarding } = useOnboarding();

const initI18n = useCallback( async ( localeKey: SupportedLocale ) => {
const translations = getLocaleData( localeKey )?.messages;
const newI18n = createI18n( translations );
setI18n( newI18n );
// Update default I18n data to reflect language change when using
// I18n functions from `@wordpress/i18n` package.
// Note we need to update this in both the renderer and main processes.
if ( translations ) {
defaultI18n.setLocaleData( translations );
getIpcApi().setDefaultLocaleData( translations );
} else {
// In case we don't find translations, we reset the locale data to
// fallback to the default translations.
defaultI18n.resetLocaleData();
getIpcApi().resetDefaultLocaleData();
}
// App menu is reloaded to ensure the items show the translated strings.
getIpcApi().setupAppMenu();
}, [] );
const initI18n = useCallback(
async ( localeKey: SupportedLocale ) => {
const translations = getLocaleData( localeKey )?.messages;
const newI18n = createI18n( translations );
setI18n( newI18n );
// Update default I18n data to reflect language change when using
// I18n functions from `@wordpress/i18n` package.
// Note we need to update this in both the renderer and main processes.
if ( translations ) {
defaultI18n.setLocaleData( translations );
getIpcApi().setDefaultLocaleData( translations );
} else {
// In case we don't find translations, we reset the locale data to
// fallback to the default translations.
defaultI18n.resetLocaleData();
getIpcApi().resetDefaultLocaleData();
}
// App menu is reloaded to ensure the items show the translated strings.
getIpcApi().setupAppMenu( { needsOnboarding } );
Comment on lines +42 to +43

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wouldn't expect that refreshing the app menu requires passing the entire config parameters, like needsOnboarding. That said, I wonder if we could create a React context to manage the app menu from a global perspective instead.

The provider will use the hook useOnboarding to retrieve needsOnboarding, and set a side effect to update that app menu as we do in:

useEffect( () => {
getIpcApi().setupAppMenu( { needsOnboarding } );
}, [ needsOnboarding ] );

Additionally, the provider will expose a function to refresh the app menu that will be consumed here. We could also expose other functions in the future to make updates in the app bar in a reactive way.

With the above approach, we'll unlock updating the app menu from anywhere on the app. WDYT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe another alternative would be reading the number of sites directly on IPC?

Something like (I didn't try the code):

export async function setupAppMenu( _event: IpcMainInvokeEvent ) {
	const userData = await loadUserData();
	const needsOnboarding = userData.sites.length === 0;
	setupMenu( { needsOnboarding } );
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since we need to manipulate just one menu item for now, I am going to merge this as this will have an immediate positive effect on the user experience. Let's open a separate issue to create a React context and make this more adaptable for future implementations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I created the issue related to this here: https://github.com/Automattic/dotcom-forge/issues/9313

},
[ needsOnboarding ]
);

useEffect( () => {
if ( initialized ) {
Expand Down
4 changes: 2 additions & 2 deletions src/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,8 +717,8 @@ export async function showNotification(
new Notification( options ).show();
}

export function setupAppMenu( _event: IpcMainInvokeEvent ) {
setupMenu();
export function setupAppMenu( _event: IpcMainInvokeEvent, config: { needsOnboarding: boolean } ) {
setupMenu( config );
}

export function popupAppMenu( _event: IpcMainInvokeEvent ) {
Expand Down
10 changes: 7 additions & 3 deletions src/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import { promptWindowsSpeedUpSites } from './lib/windows-helpers';
import { withMainWindow } from './main-window';
import { isUpdateReadyToInstall, manualCheckForUpdates } from './updates';

export function setupMenu() {
export function setupMenu( config: { needsOnboarding: boolean } ) {
withMainWindow( ( mainWindow ) => {
if ( ! mainWindow && process.platform !== 'darwin' ) {
Menu.setApplicationMenu( null );
return;
}
const menu = getAppMenu( mainWindow );
const menu = getAppMenu( mainWindow, config );
if ( process.platform === 'darwin' ) {
Menu.setApplicationMenu( menu );
return;
Expand All @@ -44,7 +44,10 @@ export function popupMenu() {
} );
}

function getAppMenu( mainWindow: BrowserWindow | null ) {
function getAppMenu(
mainWindow: BrowserWindow | null,
{ needsOnboarding = false }: { needsOnboarding?: boolean } = {}
) {
const crashTestMenuItems: MenuItemConstructorOptions[] = [
{
label: __( 'Test Hard Crash (dev only)' ),
Expand Down Expand Up @@ -119,6 +122,7 @@ function getAppMenu( mainWindow: BrowserWindow | null ) {
window.webContents.send( 'add-site' );
} );
},
enabled: ! needsOnboarding,
},
...( process.platform === 'win32'
? []
Expand Down
3 changes: 2 additions & 1 deletion src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ const api: IpcApi = {
// Use .send instead of .invoke because logging is fire-and-forget
logRendererMessage: ( level: LogLevel, ...args: any[] ) =>
ipcRenderer.send( 'logRendererMessage', level, ...args ),
setupAppMenu: () => ipcRenderer.invoke( 'setupAppMenu' ),
setupAppMenu: ( config: { needsOnboarding: boolean } ) =>
ipcRenderer.invoke( 'setupAppMenu', config ),
popupAppMenu: () => ipcRenderer.invoke( 'popupAppMenu' ),
promptWindowsSpeedUpSites: ( ...args: Parameters< typeof promptWindowsSpeedUpSites > ) =>
ipcRenderer.invoke( 'promptWindowsSpeedUpSites', ...args ),
Expand Down