Skip to content
Draft
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
21 changes: 16 additions & 5 deletions src/components/tree-view.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { CheckboxControl, Icon, Spinner } from '@wordpress/components';
import { file, moreHorizontal, page, plugins, brush } from '@wordpress/icons';
import { file, moreHorizontal, page, plugins, brush, cautionFilled } from '@wordpress/icons';
import { useI18n } from '@wordpress/react-i18n';
import React from 'react';
import { RightArrowIcon } from 'src/components/icons/right-arrow';
import { cx } from 'src/lib/cx';

type TreeNodeType = 'folder' | 'file' | 'plugin' | 'theme' | 'more';
type TreeNodeType = 'folder' | 'file' | 'plugin' | 'theme' | 'more' | 'error';
export type TreeNode = {
id: string;
name: string;
Expand All @@ -27,6 +27,7 @@ const TREE_NODE_ICONS: Record< TreeNodeType, React.JSX.Element > = {
plugin: plugins,
theme: brush,
more: moreHorizontal,
error: cautionFilled,
};

const updateNode = ( node: TreeNode, partialNode: Partial< TreeNode > ): TreeNode => {
Expand Down Expand Up @@ -85,6 +86,7 @@ const TreeItem = ( {
siblingsLength,
disabled,
renderAfterChildren,
renderEmptyContent,
}: {
node: TreeNode;
onPatchNode: ( id: string, patchNode: Partial< TreeNode > ) => void;
Expand All @@ -95,6 +97,7 @@ const TreeItem = ( {
isLast?: boolean;
disabled?: boolean;
renderAfterChildren?: ( nodeId: string ) => React.ReactNode;
renderEmptyContent?: ( nodeId: string ) => React.ReactNode;
} ) => {
const { __ } = useI18n();
const isFirstLevel = level === 1;
Expand Down Expand Up @@ -164,9 +167,13 @@ const TreeItem = ( {
className={ cx( 'ps-6', isFirstLevel && 'border border-gray-300 rounded-sm py-2' ) }
>
{ node.children.length === 0 ? (
<div className="text-gray-500 italic" aria-label={ __( 'Empty folder' ) }>
{ __( 'Empty' ) }
</div>
renderEmptyContent && renderEmptyContent( node.id ) ? (
renderEmptyContent( node.id )
) : (
<div className="text-gray-500 italic" aria-label={ __( 'Empty folder' ) }>
{ __( 'Empty' ) }
</div>
)
) : (
node.children.map( ( child, idx ) => (
<TreeItem
Expand All @@ -178,6 +185,7 @@ const TreeItem = ( {
index={ idx }
siblingsLength={ node.children?.length }
renderAfterChildren={ renderAfterChildren }
renderEmptyContent={ renderEmptyContent }
/>
) )
) }
Expand All @@ -194,6 +202,7 @@ export type TreeViewProps = {
onExpand?: ( node: TreeNode ) => Promise< void >;
disabled?: boolean;
renderAfterChildren?: ( nodeId: string ) => React.ReactNode;
renderEmptyContent?: ( nodeId: string ) => React.ReactNode;
};

export const TreeView = ( {
Expand All @@ -202,6 +211,7 @@ export const TreeView = ( {
onExpand,
disabled,
renderAfterChildren,
renderEmptyContent,
}: TreeViewProps ) => {
const handlePatchNode = ( id: string, partialNode: Partial< TreeNode > ) => {
setTree( ( prev: TreeNode[] ) => updateNodeById( prev, id, partialNode ) );
Expand All @@ -221,6 +231,7 @@ export const TreeView = ( {
isLast={ index === tree.length - 1 }
disabled={ disabled }
renderAfterChildren={ renderAfterChildren }
renderEmptyContent={ renderEmptyContent }
/>
) ) }
</div>
Expand Down
74 changes: 59 additions & 15 deletions src/modules/sync/components/sync-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { SelectControl, Notice, __experimentalHeading as Heading } from '@wordpress/components';
import {
SelectControl,
Notice,
__experimentalHeading as Heading,
Icon,
} from '@wordpress/components';
import { createInterpolateElement } from '@wordpress/element';
import { sprintf, __ } from '@wordpress/i18n';
import { cautionFilled } from '@wordpress/icons';
import { useI18n } from '@wordpress/react-i18n';
import { format } from 'date-fns';
import { useState, useEffect, useCallback } from 'react';
Expand Down Expand Up @@ -51,9 +57,12 @@ const useDynamicTreeState = (
} = useLatestRewindId( remoteSiteId, {
skip: type === 'push',
} );
const { fetchChildren } = useRemoteFileTree();
const { fetchChildren: fetchLocalChildren, isLoading: isLoadingLocalFileTree } =
useLocalFileTree();
const { fetchChildren, error: remoteFileTreeError } = useRemoteFileTree();
const {
fetchChildren: fetchLocalChildren,
isLoading: isLoadingLocalFileTree,
error: localFileTreeError,
} = useLocalFileTree();

// If the site was just created and if there is no rewind_id yet,
// then all options are pre-checked to allow only a full sync
Expand Down Expand Up @@ -92,15 +101,11 @@ const useDynamicTreeState = (
if ( type === 'push' ) {
let isCancelled = false;
const loadLocalTree = async () => {
try {
const localTree = await fetchLocalChildren( localSiteId, 'wp-content' );
if ( ! isCancelled ) {
setTreeState( ( treeState ) =>
updateNodeById( treeState, 'wp-content', { children: localTree } )
);
}
} catch ( error ) {
console.error( 'Failed to load local file tree:', error );
const localTree = await fetchLocalChildren( localSiteId, 'wp-content' );
if ( ! isCancelled ) {
setTreeState( ( treeState ) =>
updateNodeById( treeState, 'wp-content', { children: localTree } )
);
}
};
void loadLocalTree();
Expand All @@ -118,13 +123,22 @@ const useDynamicTreeState = (
localSiteId,
] );

// Handle file tree errors by clearing children to show custom error message
useEffect( () => {
if ( ( type === 'push' && localFileTreeError ) || ( type === 'pull' && remoteFileTreeError ) ) {
setTreeState( ( treeState ) => updateNodeById( treeState, 'wp-content', { children: [] } ) );
}
}, [ type, localFileTreeError, remoteFileTreeError, setTreeState ] );

return {
rewindId,
fetchChildren,
fetchLocalChildren,
isLoadingRewindId,
isErrorRewindId,
isLoadingLocalFileTree,
localFileTreeError,
remoteFileTreeError,
};
};

Expand Down Expand Up @@ -155,8 +169,15 @@ export function SyncDialog( {
formattedOverAmount,
} = useSelectedItemsPushSize( localSite.id, treeState, type );

const { fetchChildren, rewindId, isLoadingRewindId, isErrorRewindId, isLoadingLocalFileTree } =
useDynamicTreeState( type, localSite.id, remoteSite.id, setTreeState );
const {
fetchChildren,
rewindId,
isLoadingRewindId,
isErrorRewindId,
isLoadingLocalFileTree,
localFileTreeError,
remoteFileTreeError,
} = useDynamicTreeState( type, localSite.id, remoteSite.id, setTreeState );

const [ wpVersion ] = useGetWpVersion( localSite );
const { data: wpVersions = [] } = useGetWordPressVersions( {
Expand Down Expand Up @@ -355,6 +376,29 @@ export function SyncDialog( {
}
return null;
} }
renderEmptyContent={ ( nodeId ) => {
if ( nodeId === 'wp-content' && type === 'push' && localFileTreeError ) {
return (
<div className="text-red-600 italic flex items-center gap-1.5">
<Icon icon={ cautionFilled } size={ 20 } className="fill-red-600" />
{ __(
'Error retrieving files and directories. Please close and reopen this dialog to try again.'
) }
</div>
);
}
if ( nodeId === 'wp-content' && type === 'pull' && remoteFileTreeError ) {
return (
<div className="text-red-600 italic flex items-center gap-1.5">
<Icon icon={ cautionFilled } size={ 20 } className="fill-red-600" />
{ __(
'Error retrieving remote files and directories. Please close and reopen this dialog to try again.'
) }
</div>
);
}
return null;
} }
/>
</>
) }
Expand Down
Loading