Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
80 changes: 80 additions & 0 deletions src/components/progress-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,83 @@ export function ProgressBarWithAutoIncrement( {

return <ProgressBar value={ animatedValue } maxValue={ maxValue } />;
}

type TwoColorProgressBarProps = {
value: number;
maxValue: number;
normalColorClass?: string;
overLimitColorClass?: string;
trackColorClass?: string;
showLabels?: boolean;
valueLabel?: string;
limitLabel?: string;
overLimitLabel?: string;
};

export function TwoColorProgressBar( {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to achieve this with ProgressBar but that component is too simple so I opted for this custom simple implementation.

value,
maxValue,
normalColorClass = 'bg-a8c-blue-50',
overLimitColorClass = 'bg-a8c-red-50',
trackColorClass = 'bg-a8c-gray-5',
showLabels = false,
valueLabel,
limitLabel,
overLimitLabel,
}: TwoColorProgressBarProps ) {
const isOverLimit = value > maxValue;
const percentage = Math.min( ( value / maxValue ) * 100, 100 );

return (
<div>
{ showLabels && ( valueLabel || limitLabel || overLimitLabel ) && (
<div className="flex justify-between items-center text-xs mb-2">
<div className="text-a8c-gray-90 font-medium uppercase">{ valueLabel }</div>
<div>
{ isOverLimit && overLimitLabel ? (
<span className="text-a8c-gray-700 text-xs">{ overLimitLabel }</span>
) : (
limitLabel && <span className="text-a8c-gray-700 text-xs">{ limitLabel }</span>
) }
</div>
</div>
) }
<div
className={ cx(
'relative w-full h-[1.5px] rounded-full overflow-hidden',
trackColorClass
) }
>
{ isOverLimit ? (
<>
<div
className={ cx(
'absolute left-0 top-0 h-full transition-all duration-300',
normalColorClass
) }
style={ { width: `${ ( maxValue / value ) * 100 }%` } }
/>
<div
className={ cx(
'absolute top-0 h-full transition-all duration-300',
overLimitColorClass
) }
style={ {
left: `${ ( maxValue / value ) * 100 }%`,
width: `${ ( ( value - maxValue ) / value ) * 100 }%`,
} }
/>
</>
) : (
<div
className={ cx(
'absolute left-0 top-0 h-full rounded-full transition-all duration-300',
normalColorClass
) }
style={ { width: `${ percentage }%` } }
/>
) }
</div>
</div>
);
}
41 changes: 34 additions & 7 deletions src/modules/sync/components/sync-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ArrowIcon } from 'src/components/arrow-icon';
import Button from 'src/components/button';
import { RightArrowIcon } from 'src/components/icons/right-arrow';
import Modal from 'src/components/modal';
import { TwoColorProgressBar } from 'src/components/progress-bar';
import { Tooltip } from 'src/components/tooltip';
import { TreeView, TreeNode, updateNodeById } from 'src/components/tree-view';
import { SYNC_PUSH_SIZE_LIMIT_GB } from 'src/constants';
Expand Down Expand Up @@ -140,11 +141,15 @@ export function SyncDialog( {
const [ showAllFiles, setShowAllFiles ] = useState( false );
const [ treeState, setTreeState ] = useState< TreeNode[] >( defaultTree );
const isSubmitDisabled = treeState.every( ( node ) => ! node.checked && ! node.indeterminate );
const { isPushSelectionOverLimit, isLoading: isSizeCheckLoading } = useSelectedItemsPushSize(
localSite.id,
treeState,
type
);
const {
isPushSelectionOverLimit,
isLoading: isSizeCheckLoading,
totalSize,
limitBytes,
formattedSize,
formattedLimit,
formattedOverAmount,
} = useSelectedItemsPushSize( localSite.id, treeState, type );

const { fetchChildren, rewindId, isLoadingRewindId, isErrorRewindId, isLoadingLocalFileTree } =
useDynamicTreeState( type, localSite.id, remoteSite.id, setTreeState );
Expand Down Expand Up @@ -218,13 +223,23 @@ export function SyncDialog( {
onRequestClose();
};

const getBottomPadding = () => {
if ( type === 'pull' ) {
return 'pb-[70px]'; // Original padding for pull
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I left these comments for the ease of reading the function. They could be removed if they seem self-explanatory

}
if ( isPushSelectionOverLimit ) {
return 'pb-[200px]'; // Progress bar + warning notice
}
return 'pb-[110px]'; // Just progress bar
};

return (
<Modal
className="w-3/5 min-w-[550px] max-h-[84vh] [&>div]:!p-0"
onRequestClose={ onRequestClose }
title={ syncTexts.title }
>
<div className={ isPushSelectionOverLimit ? 'pb-[140px]' : 'pb-[70px]' }>
<div className={ getBottomPadding() }>
<div className="px-8 pb-6 pt-1">{ syncTexts.description }</div>
<div className="px-8">
<span className="sr-only">
Expand Down Expand Up @@ -321,7 +336,19 @@ export function SyncDialog( {
</div>
</Tooltip>

<div className="px-8 py-4 absolute left-0 right-0 bottom-0 bg-white z-10">
<div className="px-8 py-4 absolute left-0 right-0 bottom-0 bg-white z-10 border-t border-a8c-gray-5">
{ type === 'push' && (
<div className="mb-4">
<TwoColorProgressBar
value={ totalSize }
maxValue={ limitBytes }
showLabels
valueLabel={ formattedSize }
limitLabel={ formattedLimit }
overLimitLabel={ sprintf( __( '%s over' ), formattedOverAmount ) }
/>
</div>
) }
{ type === 'push' && isPushSelectionOverLimit && (
<Notice status="warning" isDismissible={ false } className="mb-4">
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I left the notice as warning even though the designs had it in blue. I tried the info version and this is what it looked like:

Image

To me it looked a bit too pale to be well noticed but we can change it if that's the preference.

<p data-testid="push-selection-over-limit-notice">
Expand Down
33 changes: 29 additions & 4 deletions src/modules/sync/hooks/use-selected-items-push-size.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import { useState, useEffect, useCallback } from 'react';
import { TreeNode } from 'src/components/tree-view';
import { SYNC_PUSH_SIZE_LIMIT_BYTES } from 'src/constants';
import { SYNC_PUSH_SIZE_LIMIT_BYTES, SYNC_PUSH_SIZE_LIMIT_GB } from 'src/constants';
import { getIpcApi } from 'src/lib/get-ipc-api';

const formatFileSize = ( bytes: number ) => {
if ( bytes === 0 ) return '0 B';
const k = 1024;
const sizes = [ 'B', 'KB', 'MB', 'GB' ];
const i = Math.floor( Math.log( bytes ) / Math.log( k ) );
return Math.round( ( bytes / Math.pow( k, i ) ) * 100 ) / 100 + ' ' + sizes[ i ];
};

export function useSelectedItemsPushSize(
siteId: string,
treeState: TreeNode[],
type: 'push' | 'pull'
) {
const [ isPushSelectionOverLimit, setIsPushSelectionOverLimit ] = useState( false );
const [ isLoading, setIsLoading ] = useState( false );
const [ totalSize, setTotalSize ] = useState( 0 );

const checkSelectedItemsSize = useCallback( async () => {
if ( ! siteId || ! treeState.length || type !== 'push' ) {
setIsPushSelectionOverLimit( false );
setTotalSize( 0 );
return;
}

Expand All @@ -24,6 +34,7 @@ export function useSelectedItemsPushSize(

if ( isEverythingSelected ) {
const size = await getIpcApi().getDirectorySize( siteId, [ 'wp-content' ] );
setTotalSize( size );
setIsPushSelectionOverLimit( size > SYNC_PUSH_SIZE_LIMIT_BYTES );
return;
}
Expand Down Expand Up @@ -78,14 +89,17 @@ export function useSelectedItemsPushSize(

if ( sizePromises.length > 0 ) {
const sizes = await Promise.all( sizePromises );
const totalSize = sizes.reduce( ( sum, size ) => sum + size, 0 );
setIsPushSelectionOverLimit( totalSize > SYNC_PUSH_SIZE_LIMIT_BYTES );
const calculatedSize = sizes.reduce( ( sum, size ) => sum + size, 0 );
setTotalSize( calculatedSize );
setIsPushSelectionOverLimit( calculatedSize > SYNC_PUSH_SIZE_LIMIT_BYTES );
} else {
setTotalSize( 0 );
setIsPushSelectionOverLimit( false );
}
} catch ( error ) {
console.error( 'Error checking selected items size:', error );
setIsPushSelectionOverLimit( false );
setTotalSize( 0 );
} finally {
setIsLoading( false );
}
Expand All @@ -96,5 +110,16 @@ export function useSelectedItemsPushSize(
void checkSelectedItemsSize();
}, [ checkSelectedItemsSize ] );

return { isPushSelectionOverLimit, isLoading };
const limitBytes = SYNC_PUSH_SIZE_LIMIT_GB * 1024 * 1024 * 1024;
const overAmount = totalSize > limitBytes ? totalSize - limitBytes : 0;

return {
isPushSelectionOverLimit,
isLoading,
totalSize,
limitBytes,
formattedSize: formatFileSize( totalSize ),
formattedLimit: formatFileSize( limitBytes ),
formattedOverAmount: formatFileSize( overAmount ),
};
}