diff --git a/apps/studio/src/ipc-handlers.ts b/apps/studio/src/ipc-handlers.ts index f29669881a..f2c347f8ec 100644 --- a/apps/studio/src/ipc-handlers.ts +++ b/apps/studio/src/ipc-handlers.ts @@ -186,7 +186,12 @@ export { getDefaultSiteDirectory, saveDefaultSiteDirectory }; export { importSite, exportSite } from 'src/modules/import-export/lib/ipc-handlers'; -export { getUserDeskConfig, saveUserDeskConfig } from 'src/modules/desks/lib/ipc-handlers'; +export { + getSiteDeskConfig, + getUserDeskConfig, + saveSiteDeskConfig, + saveUserDeskConfig, +} from 'src/modules/desks/lib/ipc-handlers'; export { studioCodeSendMessage, diff --git a/apps/studio/src/modules/desks/lib/ipc-handlers.ts b/apps/studio/src/modules/desks/lib/ipc-handlers.ts index a799f297df..11963a1e96 100644 --- a/apps/studio/src/modules/desks/lib/ipc-handlers.ts +++ b/apps/studio/src/modules/desks/lib/ipc-handlers.ts @@ -60,6 +60,12 @@ function assertDeskConfig( value: unknown ): asserts value is DeskConfig { } } +function assertSiteId( siteId: unknown ): asserts siteId is string { + if ( typeof siteId !== 'string' || ! siteId ) { + throw new Error( 'Invalid site desk config: expected site id.' ); + } +} + export async function getUserDeskConfig( _event: IpcMainInvokeEvent ): Promise< DeskConfig | undefined > { @@ -86,3 +92,37 @@ export async function saveUserDeskConfig( await unlockAppdata(); } } + +export async function getSiteDeskConfig( + _event: IpcMainInvokeEvent, + siteId: string +): Promise< DeskConfig | undefined > { + assertSiteId( siteId ); + const userData = await loadUserData(); + return userData.desks?.sites?.[ siteId ]; +} + +export async function saveSiteDeskConfig( + _event: IpcMainInvokeEvent, + siteId: string, + config: DeskConfig +): Promise< void > { + assertSiteId( siteId ); + assertDeskConfig( config ); + await lockAppdata(); + try { + const userData = await loadUserData(); + await saveUserData( { + ...userData, + desks: { + ...userData.desks, + sites: { + ...userData.desks?.sites, + [ siteId ]: config, + }, + }, + } ); + } finally { + await unlockAppdata(); + } +} diff --git a/apps/studio/src/preload.ts b/apps/studio/src/preload.ts index dd32066385..3ab35ecc5e 100644 --- a/apps/studio/src/preload.ts +++ b/apps/studio/src/preload.ts @@ -223,6 +223,9 @@ const api: IpcApi = { ipcRendererInvoke( 'setSessionEnvironment', sessionId, environment ), getUserDeskConfig: () => ipcRendererInvoke( 'getUserDeskConfig' ), saveUserDeskConfig: ( config ) => ipcRendererInvoke( 'saveUserDeskConfig', config ), + getSiteDeskConfig: ( siteId ) => ipcRendererInvoke( 'getSiteDeskConfig', siteId ), + saveSiteDeskConfig: ( siteId, config ) => + ipcRendererInvoke( 'saveSiteDeskConfig', siteId, config ), }; contextBridge.exposeInMainWorld( 'ipcApi', api ); diff --git a/apps/ui/src/data/core/connectors/ipc/index.ts b/apps/ui/src/data/core/connectors/ipc/index.ts index 807d0df5cf..1137c6caaf 100644 --- a/apps/ui/src/data/core/connectors/ipc/index.ts +++ b/apps/ui/src/data/core/connectors/ipc/index.ts @@ -606,6 +606,14 @@ export function createIpcConnector(): Connector { await ipcApi.saveUserDeskConfig( config ); }, + async getSiteDeskConfig( siteId ): Promise< DeskConfig | undefined > { + return ( await ipcApi.getSiteDeskConfig( siteId ) ) as DeskConfig | undefined; + }, + + async saveSiteDeskConfig( siteId, config ): Promise< void > { + await ipcApi.saveSiteDeskConfig( siteId, config ); + }, + async openSiteFolder( siteId ): Promise< void > { const sitePath = await resolveSiteFolder( siteId ); ipcApi.openLocalPath( sitePath ); diff --git a/apps/ui/src/data/core/types.ts b/apps/ui/src/data/core/types.ts index 5ab56b0a96..2203e544b8 100644 --- a/apps/ui/src/data/core/types.ts +++ b/apps/ui/src/data/core/types.ts @@ -235,6 +235,8 @@ export interface Connector { // Desks getUserDeskConfig(): Promise< DeskConfig | undefined >; saveUserDeskConfig( config: DeskConfig ): Promise< void >; + getSiteDeskConfig( siteId: string ): Promise< DeskConfig | undefined >; + saveSiteDeskConfig( siteId: string, config: DeskConfig ): Promise< void >; // Open the given site's folder in the system file manager, preferred // editor, or preferred terminal. When no editor/terminal preference is diff --git a/apps/ui/src/data/queries/use-desk-config.ts b/apps/ui/src/data/queries/use-desk-config.ts index ddcbe9466f..0a87e39999 100644 --- a/apps/ui/src/data/queries/use-desk-config.ts +++ b/apps/ui/src/data/queries/use-desk-config.ts @@ -2,24 +2,31 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useConnector } from '@/data/core'; import type { DeskConfig } from '@/data/core'; -export const USER_DESK_CONFIG_QUERY_KEY = [ 'desk-config', 'user' ] as const; +const userDeskConfigQueryKey = [ 'desk-config', 'user' ] as const; +const siteDeskConfigQueryKey = ( siteId: string ) => [ 'desk-config', 'site', siteId ] as const; +const deskConfigQueryKey = ( siteId?: string ) => + siteId ? siteDeskConfigQueryKey( siteId ) : userDeskConfigQueryKey; -export function useUserDeskConfig() { +export function useDeskConfig( siteId?: string ) { const connector = useConnector(); return useQuery( { - queryKey: USER_DESK_CONFIG_QUERY_KEY, - queryFn: () => connector.getUserDeskConfig(), + queryKey: deskConfigQueryKey( siteId ), + queryFn: () => + siteId ? connector.getSiteDeskConfig( siteId ) : connector.getUserDeskConfig(), } ); } -export function useSaveUserDeskConfig() { +export function useSaveDeskConfig( siteId?: string ) { const connector = useConnector(); const queryClient = useQueryClient(); return useMutation( { mutationFn: ( config: DeskConfig ) => - connector.saveUserDeskConfig( config ).then( () => config ), + ( siteId + ? connector.saveSiteDeskConfig( siteId, config ) + : connector.saveUserDeskConfig( config ) + ).then( () => config ), onSuccess: ( config ) => { - queryClient.setQueryData( USER_DESK_CONFIG_QUERY_KEY, config ); + queryClient.setQueryData( deskConfigQueryKey( siteId ), config ); }, } ); } diff --git a/apps/ui/src/ui-desks/chats/index.tsx b/apps/ui/src/ui-desks/chats/index.tsx index 95ffbcf29e..f81f4c6846 100644 --- a/apps/ui/src/ui-desks/chats/index.tsx +++ b/apps/ui/src/ui-desks/chats/index.tsx @@ -172,6 +172,7 @@ export function DeskChats( { siteId }: DeskChatsProps ) { { selectedSessionId ? ( void; autoFocus?: boolean; @@ -43,6 +44,7 @@ function Frame( { composer, scrollRef, children }: FrameProps ) { } function DeskSessionSurfaceContent( { + siteId, sessionId, onSwitchSession, autoFocus = false, @@ -53,9 +55,10 @@ function DeskSessionSurfaceContent( { const ownerSite = ownerSitePath ? sites?.find( ( candidate ) => candidate.path === ownerSitePath ) : undefined; - const { data: connectedSites } = useConnectedWpcomSites( ownerSite?.id ); + const ownerSiteId = ownerSite?.id ?? siteId; + const { data: connectedSites } = useConnectedWpcomSites( ownerSiteId ); const liveSite = pickLiveSite( connectedSites ); - const effectiveEnvironment = useSessionEffectiveEnvironment( data?.summary, ownerSite?.id ); + const effectiveEnvironment = useSessionEffectiveEnvironment( data?.summary, ownerSiteId ); const { isRunning, hasActiveRun, @@ -141,7 +144,7 @@ function DeskSessionSurfaceContent( { effectiveEnvironment={ effectiveEnvironment } liveSite={ liveSite } entries={ data.entries } - ownerSiteId={ ownerSite?.id } + ownerSiteId={ ownerSiteId } onSwitchSession={ onSwitchSession } autoFocus={ autoFocus } /> diff --git a/apps/ui/src/ui-desks/chrome/create-menu.tsx b/apps/ui/src/ui-desks/chrome/create-menu.tsx index 85c7fc8984..7b343aac9a 100644 --- a/apps/ui/src/ui-desks/chrome/create-menu.tsx +++ b/apps/ui/src/ui-desks/chrome/create-menu.tsx @@ -3,16 +3,25 @@ import { __ } from '@wordpress/i18n'; import { comment, download, globe, plus } from '@wordpress/icons'; import { Icon } from '@wordpress/ui'; import * as Menu from '@/components/menu'; +import { useDesk } from '@/ui-desks/desk/provider'; +import { getCreatableWidgetDefinitions } from '@/ui-desks/widgets/registry'; import { DeskHeaderIconButton } from './header-button'; import styles from './style.module.css'; +import type { DeskChatsSearch } from '../chats/search'; export function DeskCreateMenu() { const navigate = useNavigate(); + const desk = useDesk(); + const creatableWidgetDefinitions = getCreatableWidgetDefinitions(); const createChat = () => { void navigate( { - to: '/', - search: { chats: true, newChat: Date.now() }, + to: '.', + search: ( previous: DeskChatsSearch ) => ( { + ...previous, + chats: true, + newChat: Date.now(), + } ), } ); }; @@ -30,6 +39,17 @@ export function DeskCreateMenu() { render={ } /> + { creatableWidgetDefinitions.map( ( definition ) => ( + desk.addWidget( definition.type ) } + > + { definition.icon && } + { definition.labels.add() } + + ) ) } + { creatableWidgetDefinitions.length > 0 && } { __( 'New chat' ) } diff --git a/apps/ui/src/ui-desks/chrome/index.tsx b/apps/ui/src/ui-desks/chrome/index.tsx index 888918290d..0fdabab671 100644 --- a/apps/ui/src/ui-desks/chrome/index.tsx +++ b/apps/ui/src/ui-desks/chrome/index.tsx @@ -22,10 +22,14 @@ export function DeskHeader( { children }: DeskHeaderProps ) { ); } -export function DeskChrome() { +interface DeskChromeProps { + siteId?: string; +} + +export function DeskChrome( { siteId }: DeskChromeProps ) { return ( - + diff --git a/apps/ui/src/ui-desks/chrome/user-menu.tsx b/apps/ui/src/ui-desks/chrome/user-menu.tsx index 8f4b4a69bb..885e36b09f 100644 --- a/apps/ui/src/ui-desks/chrome/user-menu.tsx +++ b/apps/ui/src/ui-desks/chrome/user-menu.tsx @@ -17,14 +17,14 @@ import type { SiteDetails } from '@/data/core'; const WPCOM_PROFILE_URL = 'https://wordpress.com/me'; interface DeskMenuProps { - activeSiteId?: string; + siteId?: string; } function getSiteIconSeed( site: SiteDetails ) { return `${ site.id }:${ site.name }:${ site.path }`; } -export function DeskMenu( { activeSiteId }: DeskMenuProps ) { +export function DeskMenu( { siteId }: DeskMenuProps ) { const navigate = useNavigate(); const connector = useConnector(); const { data: user } = useAuthUser(); @@ -36,9 +36,9 @@ export function DeskMenu( { activeSiteId }: DeskMenuProps ) { const savedScheme = preferences?.colorScheme; const themeIsDark = savedScheme === 'dark' || ( savedScheme !== 'light' && effectiveScheme === 'dark' ); - const activeSite = sites?.find( ( candidate ) => candidate.id === activeSiteId ); + const activeSite = sites?.find( ( candidate ) => candidate.id === siteId ); const activeSiteName = activeSite?.name ?? __( 'Site' ); - const activeSiteIconSeed = activeSite ? getSiteIconSeed( activeSite ) : activeSiteId; + const activeSiteIconSeed = activeSite ? getSiteIconSeed( activeSite ) : siteId; const switcherSites = activeSite ? [ activeSite, ...( sites ?? [] ).filter( ( candidate ) => candidate.id !== activeSite.id ) ] : sites ?? []; @@ -52,13 +52,13 @@ export function DeskMenu( { activeSiteId }: DeskMenuProps ) { }; const openSite = ( nextSiteId: string ) => { - if ( nextSiteId === activeSiteId ) { + if ( nextSiteId === siteId ) { return; } void navigate( { to: '/sites/$siteId', params: { siteId: nextSiteId } } ); }; - const trigger = activeSiteId ? ( + const trigger = siteId ? (