Skip to content
Merged
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
7 changes: 6 additions & 1 deletion apps/studio/src/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
40 changes: 40 additions & 0 deletions apps/studio/src/modules/desks/lib/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 > {
Expand All @@ -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();
}
}
3 changes: 3 additions & 0 deletions apps/studio/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
8 changes: 8 additions & 0 deletions apps/ui/src/data/core/connectors/ipc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
2 changes: 2 additions & 0 deletions apps/ui/src/data/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 14 additions & 7 deletions apps/ui/src/data/queries/use-desk-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
},
} );
}
1 change: 1 addition & 0 deletions apps/ui/src/ui-desks/chats/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export function DeskChats( { siteId }: DeskChatsProps ) {
{ selectedSessionId ? (
<DeskSessionSurface
key={ selectedSessionId }
siteId={ siteId }
sessionId={ selectedSessionId }
onSwitchSession={ handleSwitchSession }
autoFocus={ autoFocusSessionId === selectedSessionId }
Expand Down
9 changes: 6 additions & 3 deletions apps/ui/src/ui-desks/chats/session-surface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SessionUIProvider } from '@/hooks/use-session-ui';
import styles from './style.module.css';

interface DeskSessionSurfaceProps {
siteId?: string;
sessionId: string;
onSwitchSession: ( sessionId: string ) => void;
autoFocus?: boolean;
Expand All @@ -43,6 +44,7 @@ function Frame( { composer, scrollRef, children }: FrameProps ) {
}

function DeskSessionSurfaceContent( {
siteId,
sessionId,
onSwitchSession,
autoFocus = false,
Expand All @@ -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,
Expand Down Expand Up @@ -141,7 +144,7 @@ function DeskSessionSurfaceContent( {
effectiveEnvironment={ effectiveEnvironment }
liveSite={ liveSite }
entries={ data.entries }
ownerSiteId={ ownerSite?.id }
ownerSiteId={ ownerSiteId }
onSwitchSession={ onSwitchSession }
autoFocus={ autoFocus }
/>
Expand Down
24 changes: 22 additions & 2 deletions apps/ui/src/ui-desks/chrome/create-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
} ),
} );
};

Expand All @@ -30,6 +39,17 @@ export function DeskCreateMenu() {
render={ <DeskHeaderIconButton icon={ plus } label={ __( 'Create new' ) } /> }
/>
<Menu.Popup side="bottom" align="start" className={ styles.popup }>
{ creatableWidgetDefinitions.map( ( definition ) => (
<Menu.Item
key={ definition.type }
disabled={ ! desk.canAddWidgets }
onClick={ () => desk.addWidget( definition.type ) }
>
{ definition.icon && <Icon icon={ definition.icon } /> }
<span>{ definition.labels.add() }</span>
</Menu.Item>
) ) }
{ creatableWidgetDefinitions.length > 0 && <Menu.Separator /> }
<Menu.Item onClick={ createChat }>
<Icon icon={ comment } />
<span>{ __( 'New chat' ) }</span>
Expand Down
8 changes: 6 additions & 2 deletions apps/ui/src/ui-desks/chrome/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ export function DeskHeader( { children }: DeskHeaderProps ) {
);
}

export function DeskChrome() {
interface DeskChromeProps {
siteId?: string;
}

export function DeskChrome( { siteId }: DeskChromeProps ) {
return (
<DeskHeader>
<DeskMenu />
<DeskMenu siteId={ siteId } />
<DeskChatsTrigger />
<DeskCreateMenu />
</DeskHeader>
Expand Down
14 changes: 7 additions & 7 deletions apps/ui/src/ui-desks/chrome/user-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 ?? [];
Expand All @@ -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 ? (
<button
type="button"
className={ styles.siteTrigger }
Expand Down Expand Up @@ -113,7 +113,7 @@ export function DeskMenu( { activeSiteId }: DeskMenuProps ) {
switcherSites.map( ( site ) => (
<Menu.Item
key={ site.id }
aria-current={ site.id === activeSiteId ? 'page' : undefined }
aria-current={ site.id === siteId ? 'page' : undefined }
onClick={ () => openSite( site.id ) }
>
<span title={ site.name }>{ site.name }</span>
Expand Down
44 changes: 44 additions & 0 deletions apps/ui/src/ui-desks/desk/canvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useCallback, useEffect } from 'react';
import { Tldraw, type Editor, type TldrawOptions } from 'tldraw';
import 'tldraw/tldraw.css';
import { deskShapeUtils } from '@/ui-desks/shapes/registry';
import { useDesk, useRegisterDeskEditor } from './provider';
import styles from './style.module.css';

const deskCanvasOptions = {
createTextOnCanvasDoubleClick: false,
} satisfies Partial< TldrawOptions >;

export function DeskCanvas() {
const { isLoading } = useDesk();
const registerEditor = useRegisterDeskEditor();

const handleMount = useCallback(
( nextEditor: Editor ) => {
registerEditor( nextEditor );
},
[ registerEditor ]
);

useEffect( () => {
return () => {
registerEditor( null );
};
}, [ registerEditor ] );

if ( isLoading ) {
return <div className={ styles.loading } />;
}

return (
<div className={ styles.canvas }>
<Tldraw
hideUi
autoFocus
options={ deskCanvasOptions }
shapeUtils={ deskShapeUtils }
onMount={ handleMount }
/>
</div>
);
}
37 changes: 37 additions & 0 deletions apps/ui/src/ui-desks/desk/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { __ } from '@wordpress/i18n';
import { DeskChats } from '../chats';
import { DeskChrome } from '../chrome';
import { DeskCanvas } from './canvas';
import { DeskProvider } from './provider';
import styles from './style.module.css';
import type { ReactNode } from 'react';

interface DeskProps {
siteId?: string;
}

export function Desk( { siteId }: DeskProps ) {
return (
<DeskProvider key={ siteId ?? 'user' } siteId={ siteId }>
<DeskShell siteId={ siteId }>
<DeskCanvas />
</DeskShell>
</DeskProvider>
);
}

function DeskShell( { siteId, children }: DeskProps & { children: ReactNode } ) {
return (
<>
<DeskChats siteId={ siteId } />
<main className={ styles.root } aria-label={ getDeskLabel( siteId ) } data-site-id={ siteId }>
<DeskChrome siteId={ siteId } />
{ children }
</main>
</>
);
}

function getDeskLabel( siteId?: string ) {
return siteId ? __( 'Site desk' ) : __( 'User desk' );
}
Loading
Loading