-
Notifications
You must be signed in to change notification settings - Fork 51
Add site context menu #1796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add site context menu #1796
Changes from 8 commits
7d019e8
c6806d9
c6032c9
20cd029
b75bd36
7d1a529
7cdaf01
7a413f1
481d396
6f35694
d1fa0db
7101550
6dac93c
846868f
ab5ac3f
1e927f4
8c7eae1
bb8cf40
6625612
e38617a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import * as Sentry from '@sentry/electron/renderer'; | ||
| import { speak } from '@wordpress/a11y'; | ||
| import { Spinner } from '@wordpress/components'; | ||
| import { __, sprintf } from '@wordpress/i18n'; | ||
|
|
@@ -6,8 +7,12 @@ import { Tooltip } from 'src/components/tooltip'; | |
| import { useSyncSites } from 'src/hooks/sync-sites'; | ||
| import { useImportExport } from 'src/hooks/use-import-export'; | ||
| import { useSiteDetails } from 'src/hooks/use-site-details'; | ||
| import { isMac } from 'src/lib/app-globals'; | ||
| import { isMac, isWindows } from 'src/lib/app-globals'; | ||
| import { cx } from 'src/lib/cx'; | ||
| import { getIpcApi } from 'src/lib/get-ipc-api'; | ||
| import { supportedEditorConfig } from 'src/modules/user-settings/lib/editor'; | ||
| import { getTerminalName } from 'src/modules/user-settings/lib/terminal'; | ||
| import { useGetUserEditorQuery, useGetUserTerminalQuery } from 'src/stores/installed-apps-api'; | ||
|
|
||
| interface SiteMenuProps { | ||
| className?: string; | ||
|
|
@@ -108,10 +113,13 @@ function ButtonToRun( { running, id, name }: Pick< SiteDetails, 'running' | 'id' | |
| ); | ||
| } | ||
| function SiteItem( { site }: { site: SiteDetails } ) { | ||
| const { selectedSite, setSelectedSiteId } = useSiteDetails(); | ||
| const { selectedSite, setSelectedSiteId, startServer, stopServer, deleteSite, loadingServer } = | ||
| useSiteDetails(); | ||
| const isSelected = site === selectedSite; | ||
| const { isSiteImporting, isSiteExporting } = useImportExport(); | ||
| const { isSiteIdPulling } = useSyncSites(); | ||
| const { data: editor } = useGetUserEditorQuery(); | ||
| const { data: terminal } = useGetUserTerminalQuery(); | ||
| const isImporting = isSiteImporting( site.id ); | ||
| const isExporting = isSiteExporting( site.id ); | ||
| const isPulling = isSiteIdPulling( site.id ); | ||
|
|
@@ -128,13 +136,136 @@ function SiteItem( { site }: { site: SiteDetails } ) { | |
| tooltipText = __( 'Loading' ); | ||
| } | ||
|
|
||
| // Handle context menu | ||
| const handleContextMenu = ( e: React.MouseEvent ) => { | ||
| e.preventDefault(); | ||
| const ipcApi = getIpcApi(); | ||
| const isLoading = loadingServer[ site.id ] || false; | ||
| const isAddingSite = site.isAddingSite || false; | ||
|
|
||
| // Get labels for the menu items | ||
| const finderLabel = isWindows() ? __( 'File Explorer' ) : __( 'Finder' ); | ||
| const editorLabel = | ||
| editor && supportedEditorConfig[ editor ] ? supportedEditorConfig[ editor ].label : null; | ||
| const terminalLabel = getTerminalName( terminal ); | ||
|
|
||
| ipcApi.showSiteContextMenu( | ||
| site.id, | ||
| site.name, | ||
| site.path, | ||
sejas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| site.running, | ||
| isLoading, | ||
| isAddingSite, | ||
| finderLabel, | ||
| editorLabel, | ||
| terminalLabel | ||
| ); | ||
| }; | ||
|
|
||
| // Listen for context menu actions | ||
| useEffect( () => { | ||
| const unsubscribe = window.ipcListener.subscribe( | ||
| 'site-context-menu-action', | ||
| async ( _, data: { action: string; siteId: string } ) => { | ||
| if ( data.siteId === site.id ) { | ||
| const ipcApi = getIpcApi(); | ||
| switch ( data.action ) { | ||
| case 'start': | ||
| void startServer( site.id ); | ||
| break; | ||
| case 'stop': | ||
| void stopServer( site.id ); | ||
| break; | ||
| case 'open-site': | ||
| ipcApi.openSiteURL( site.id ); | ||
| break; | ||
| case 'open-admin': | ||
| ipcApi.openSiteURL( site.id, '/wp-admin/' ); | ||
| break; | ||
| case 'open-finder': | ||
| ipcApi.openLocalPath( site.path ); | ||
| break; | ||
| case 'open-editor': | ||
| if ( editor ) { | ||
| void ipcApi.openAppAtPath( editor, site.path ); | ||
| } | ||
| break; | ||
| case 'open-terminal': | ||
| void ( async () => { | ||
| try { | ||
| await ipcApi.openTerminalAtPath( site.path ); | ||
| } catch ( error ) { | ||
| Sentry.captureException( error ); | ||
| alert( __( 'Could not open the terminal.' ) ); | ||
| } | ||
| } )(); | ||
| break; | ||
| case 'edit-site': | ||
| // Trigger edit site modal by sending a custom event | ||
| window.dispatchEvent( | ||
| new CustomEvent( 'edit-site-request', { | ||
| detail: { siteId: site.id }, | ||
| } ) | ||
| ); | ||
| break; | ||
| case 'delete': { | ||
| // Handle delete with confirmation dialog | ||
| const DELETE_BUTTON_INDEX = 0; | ||
| const CANCEL_BUTTON_INDEX = 1; | ||
| const MAX_LENGTH_SITE_TITLE = 35; | ||
|
|
||
| const trimmedSiteTitle = | ||
| site.name.length > MAX_LENGTH_SITE_TITLE | ||
| ? `${ site.name.substring( 0, MAX_LENGTH_SITE_TITLE - 3 ) }…` | ||
| : site.name; | ||
|
|
||
| const { response, checkboxChecked } = await ipcApi.showMessageBox( { | ||
|
||
| type: 'warning', | ||
| message: sprintf( __( 'Delete %s' ), trimmedSiteTitle ), | ||
| detail: __( | ||
| "The site's database will be lost. Including all posts, pages, comments, and media." | ||
| ), | ||
| buttons: [ __( 'Delete site' ), __( 'Cancel' ) ], | ||
| cancelId: CANCEL_BUTTON_INDEX, | ||
| checkboxLabel: __( 'Delete site files from my computer' ), | ||
| checkboxChecked: true, | ||
| } ); | ||
|
|
||
| if ( response === DELETE_BUTTON_INDEX ) { | ||
| try { | ||
| await deleteSite( site.id, checkboxChecked ); | ||
| } catch ( error ) { | ||
| ipcApi.showErrorMessageBox( { | ||
| title: __( 'Deletion failed' ), | ||
| message: sprintf( | ||
| __( "We couldn't delete the site '%s'. Please try again" ), | ||
| trimmedSiteTitle | ||
| ), | ||
| error, | ||
| } ); | ||
| Sentry.captureException( error ); | ||
| } | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ); | ||
|
|
||
| return () => { | ||
| unsubscribe(); | ||
| }; | ||
| }, [ site.id, site.name, site.path, startServer, stopServer, deleteSite, editor ] ); | ||
|
|
||
| return ( | ||
| <li | ||
| className={ cx( | ||
| 'flex flex-row min-w-[168px] h-8 hover:bg-[#ffffff0C] rounded transition-all ms-1', | ||
| isMac() ? 'me-5' : 'me-4', | ||
| isSelected && 'bg-[#ffffff19] hover:bg-[#ffffff19]' | ||
| ) } | ||
| onContextMenu={ handleContextMenu } | ||
| > | ||
| <button | ||
| className="p-2 text-xs rounded-tl rounded-bl whitespace-nowrap overflow-hidden text-ellipsis w-full text-left rtl:text-right focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-a8c-blue-50" | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.