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
193 changes: 169 additions & 24 deletions src/elements/content-uploader/ContentUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import DroppableContent from './DroppableContent';
import Footer from './Footer';
import UploadsManager from './UploadsManager';
import { getUploadItemKey, mapToModernizedUploadItems } from './utils/mapToModernizedUploadItem';
import './ModernizedUploadsManagerPanel.scss';
import API from '../../api';
import Browser from '../../utils/Browser';
import Internationalize from '../common/Internationalize';
Expand Down Expand Up @@ -116,18 +117,22 @@ export interface ContentUploaderProps {
onToggle?: (isExpanded: boolean) => void;
}

type ModernizedPanelState = 'hidden' | 'shown' | 'dismissing';

type State = {
errorCode?: string;
isCancelAllModalOpen: boolean;
isUploadsManagerExpanded: boolean;
itemIds: Object;
items: UploadItem[];
modernizedPanelState: ModernizedPanelState;
view: View;
};

const CHUNKED_UPLOAD_MIN_SIZE_BYTES = 104857600; // 100MB
const FILE_LIMIT_DEFAULT = 100; // Upload at most 100 files at once by default
const HIDE_UPLOAD_MANAGER_DELAY_MS_DEFAULT = 8000;
const SLIDE_OUT_ANIMATION_NAME = 'bcu-modernized-slideOut';
const EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD = 5;
const UPLOAD_CONCURRENCY = 6;

Expand All @@ -146,6 +151,12 @@ class ContentUploader extends Component<ContentUploaderProps, State> {

isAutoExpanded: boolean = false;

modernizedDismissTimer: ReturnType<typeof setTimeout> | null = null;

isPanelHovered: boolean = false;

isPanelFocused: boolean = false;

itemsRef: React.MutableRefObject<UploadItem[]>;

itemIdsRef: React.MutableRefObject<Object>;
Expand Down Expand Up @@ -199,6 +210,7 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
itemIds: {},
isCancelAllModalOpen: false,
isUploadsManagerExpanded: false,
modernizedPanelState: 'hidden',
};
this.id = uniqueid('bcu_');

Expand Down Expand Up @@ -235,6 +247,7 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
*/
componentWillUnmount() {
this.cancel();
this.clearModernizedDismissTimer();
}

/**
Expand All @@ -243,18 +256,42 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
* @return {void}
*/
componentDidUpdate() {
const { files, dataTransferItems, useUploadsManager } = this.props;
const { files, dataTransferItems, useUploadsManager, enableModernizedUploads } = this.props;
const { items, modernizedPanelState } = this.state;

const hasFiles = Array.isArray(files) && files.length > 0;
const hasItems = Array.isArray(dataTransferItems) && dataTransferItems.length > 0;
const hasUploads = hasFiles || hasItems;

if (!useUploadsManager || !hasUploads) {
if (useUploadsManager && hasUploads) {
// TODO: this gets called unnecessarily (upon each render regardless of the queue not changing)
this.addFilesWithOptionsToUploadQueueAndStartUpload(files, dataTransferItems);
}

if (!enableModernizedUploads) {
return;
}

// TODO: this gets called unnecessarily (upon each render regardless of the queue not changing)
this.addFilesWithOptionsToUploadQueueAndStartUpload(files, dataTransferItems);
const hasItemsInUploadQueue = items.length > 0;
const isUploadsBatchSuccessfullyComplete = items.every(
item => item.status === STATUS_COMPLETE || item.status === STATUS_CANCELED,
);

if (hasItemsInUploadQueue && modernizedPanelState === 'hidden') {
this.showModernizedPanel();
}

if (!isUploadsBatchSuccessfullyComplete) {
this.clearModernizedDismissTimer();

if (modernizedPanelState === 'dismissing') {
this.showModernizedPanel();
}
} else if (modernizedPanelState === 'shown') {
if (this.modernizedDismissTimer === null) {
this.startModernizedDismissTimer();
}
}
}

/**
Expand Down Expand Up @@ -1360,6 +1397,11 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
* @return {void}
*/
checkClearUploadItems = () => {
const { enableModernizedUploads } = this.props;
if (enableModernizedUploads) {
return;
}

this.resetItemsTimeout = setTimeout(
this.resetUploadsManagerItemsWhenUploadsComplete,
HIDE_UPLOAD_MANAGER_DELAY_MS_DEFAULT,
Expand Down Expand Up @@ -1435,13 +1477,16 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
* @return {void}
*/
resetUploadsManagerItemsWhenUploadsComplete = (): void => {
const { onCancel, useUploadsManager } = this.props;
const { onCancel, useUploadsManager, enableModernizedUploads } = this.props;
const { view } = this.state;
const isUploadsManagerExpanded = this.getIsExpanded();

// Do not reset items when upload manger is expanded or there're uploads in progress
if (
(isUploadsManagerExpanded && useUploadsManager && !!this.itemsRef.current.length) ||
(isUploadsManagerExpanded &&
useUploadsManager &&
!!this.itemsRef.current.length &&
!enableModernizedUploads) ||
view === VIEW_UPLOAD_IN_PROGRESS
) {
return;
Expand All @@ -1458,6 +1503,92 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
});
};

clearModernizedDismissTimer = (): void => {
if (this.modernizedDismissTimer !== null) {
clearTimeout(this.modernizedDismissTimer);
this.modernizedDismissTimer = null;
}
};

showModernizedPanel = (): void => {
this.setState({ modernizedPanelState: 'shown' });
};

startModernizedDismissTimer = (): void => {
this.clearModernizedDismissTimer();

const { modernizedPanelState, items } = this.state;
const isUploadsBatchSuccessfullyComplete = items.every(
item => item.status === STATUS_COMPLETE || item.status === STATUS_CANCELED,
);

if (
!isUploadsBatchSuccessfullyComplete ||
modernizedPanelState !== 'shown' ||
this.isPanelHovered ||
this.isPanelFocused
) {
return;
}

this.modernizedDismissTimer = setTimeout(() => {
this.setState({ modernizedPanelState: 'dismissing' });
this.modernizedDismissTimer = null;
}, HIDE_UPLOAD_MANAGER_DELAY_MS_DEFAULT);
};

finalizeModernizedDismiss = (): void => {
this.clearModernizedDismissTimer();
this.resetUploadsManagerItemsWhenUploadsComplete();
this.setState({ modernizedPanelState: 'hidden' });
};

handleModernizedMouseEnter = (): void => {
this.isPanelHovered = true;
this.clearModernizedDismissTimer();

if (this.state.modernizedPanelState === 'dismissing') {
this.showModernizedPanel();
}
};

handleModernizedMouseLeave = (): void => {
this.isPanelHovered = false;
this.startModernizedDismissTimer();
};

handleModernizedFocus = (event): void => {
// Ensures that we don't block uploads closing timer because of implicit focus (e.g. on button click)
if (!(event.target as HTMLElement).matches(':focus-visible')) {
return;
}
this.isPanelFocused = true;
this.clearModernizedDismissTimer();

if (this.state.modernizedPanelState === 'dismissing') {
this.showModernizedPanel();
}
};

handleModernizedBlur = (event: React.FocusEvent<HTMLDivElement>): void => {
const relatedTarget = event.relatedTarget as Node | null;

if (relatedTarget && event.currentTarget.contains(relatedTarget)) {
return;
}

this.isPanelFocused = false;
this.startModernizedDismissTimer();
};

handleModernizedAnimationEnd = (event: React.AnimationEvent<HTMLDivElement>): void => {
if (event.animationName !== SLIDE_OUT_ANIMATION_NAME) {
return;
}

this.finalizeModernizedDismiss();
};

/**
* Adds file to the upload queue and starts upload immediately
*
Expand Down Expand Up @@ -1499,7 +1630,7 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
theme,
useUploadsManager,
}: ContentUploaderProps = this.props;
const { view, items, errorCode, isCancelAllModalOpen }: State = this.state;
const { view, items, errorCode, isCancelAllModalOpen, modernizedPanelState }: State = this.state;
const isUploadsManagerExpanded = this.getIsExpanded();
const isEmpty = items.length === 0;
const isVisible = !isEmpty || !!isDraggingItemsToUploadsManager;
Expand All @@ -1518,23 +1649,37 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
return (
<div ref={measureRef} className={styleClassName} id={this.id}>
<ThemingStyles selector={`#${this.id}`} theme={theme} />
<UploadsManagerBP
items={mapToModernizedUploadItems(items, rootFolderId)}
isExpanded={isUploadsManagerExpanded}
onToggle={this.toggleUploadsManager}
onItemCancel={this.handleUploadsManagerItemCancel}
onItemRetry={this.handleUploadsManagerItemRetry}
onItemRemove={this.handleUploadsManagerItemRemove}
onItemShare={onItemShare}
onItemOpen={onItemOpen}
onCancelAll={this.handleCancelAllClick}
onRetryAll={this.handleUploadsManagerRetryAll}
/>
<CancelAllUploadsModal
isOpen={isCancelAllModalOpen}
onConfirm={this.handleCancelAllConfirm}
onDismiss={this.handleCancelAllDismiss}
/>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div
className={classNames('bcu-modernized-panel', {
visible: modernizedPanelState === 'shown',
dismissing: modernizedPanelState === 'dismissing',
hidden: modernizedPanelState === 'hidden',
})}
onAnimationEnd={this.handleModernizedAnimationEnd}
onBlur={this.handleModernizedBlur}
onFocus={this.handleModernizedFocus}
onMouseEnter={this.handleModernizedMouseEnter}
onMouseLeave={this.handleModernizedMouseLeave}
>
<UploadsManagerBP
items={mapToModernizedUploadItems(items, rootFolderId)}
isExpanded={isUploadsManagerExpanded}
onToggle={this.toggleUploadsManager}
onItemCancel={this.handleUploadsManagerItemCancel}
onItemRetry={this.handleUploadsManagerItemRetry}
onItemRemove={this.handleUploadsManagerItemRemove}
onItemShare={onItemShare}
onItemOpen={onItemOpen}
onCancelAll={this.handleCancelAllClick}
onRetryAll={this.handleUploadsManagerRetryAll}
/>
<CancelAllUploadsModal
isOpen={isCancelAllModalOpen}
onConfirm={this.handleCancelAllConfirm}
onDismiss={this.handleCancelAllDismiss}
/>
</div>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@keyframes bcu-modernized-slideIn {
from {
opacity: 0;
transform: translateY(20px);
}

to {
opacity: 1;
transform: translateY(0);
}
}

@keyframes bcu-modernized-slideOut {
from {
opacity: 1;
transform: translateY(0);
}

to {
opacity: 0;
transform: translateY(20px);
}
}

.bcu-modernized-panel {
&.visible {
animation: bcu-modernized-slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) both;
}

&.dismissing {
animation: bcu-modernized-slideOut 0.2s cubic-bezier(0.4, 0, 1, 1) both;
}

&.hidden {
display: none;
}

@media (prefers-reduced-motion: reduce) {
&.visible {
animation: none;
}

&.dismissing {
animation: bcu-modernized-slideOut 0.001ms both;
Comment thread
jfox-box marked this conversation as resolved.
}
}
}
Loading
Loading