diff --git a/static/app-strings.json b/static/app-strings.json index 46815230d..e79a29143 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -1307,6 +1307,8 @@ "Uploading...": "Uploading...", "Creating...": "Creating...", "Stopped. Duplicate session detected.": "Stopped. Duplicate session detected.", + "File being uploaded in another tab or window.": "File being uploaded in another tab or window.", + "There are pending uploads.": "There are pending uploads.", "Use a URL": "Use a URL", "Edit Cover Image": "Edit Cover Image", "Cover Image": "Cover Image", diff --git a/ui/component/app/view.jsx b/ui/component/app/view.jsx index 3d3f065c1..0ebd85cfe 100644 --- a/ui/component/app/view.jsx +++ b/ui/component/app/view.jsx @@ -2,6 +2,7 @@ import * as PAGES from 'constants/pages'; import React, { useEffect, useRef, useState, useLayoutEffect } from 'react'; import { lazyImport } from 'util/lazyImport'; +import { tusUnlockAndNotify, tusHandleTabUpdates } from 'util/tus'; import classnames from 'classnames'; import analytics from 'analytics'; import { setSearchUserId } from 'redux/actions/search'; @@ -231,12 +232,29 @@ function App(props: Props) { useEffect(() => { if (!uploadCount) return; + + const handleUnload = (event) => tusUnlockAndNotify(); const handleBeforeUnload = (event) => { event.preventDefault(); - event.returnValue = 'magic'; // without setting this to something it doesn't work + event.returnValue = __('There are pending uploads.'); // without setting this to something it doesn't work }; + + window.addEventListener('unload', handleUnload); window.addEventListener('beforeunload', handleBeforeUnload); - return () => window.removeEventListener('beforeunload', handleBeforeUnload); + + return () => { + window.removeEventListener('unload', handleUnload); + window.removeEventListener('beforeunload', handleBeforeUnload); + }; + }, [uploadCount]); + + useEffect(() => { + if (!uploadCount) return; + + const onStorageUpdate = (e) => tusHandleTabUpdates(e.key); + window.addEventListener('storage', onStorageUpdate); + + return () => window.removeEventListener('storage', onStorageUpdate); }, [uploadCount]); // allows user to pause miniplayer using the spacebar without the page scrolling down diff --git a/ui/component/webUploadList/internal/web-upload-item.jsx b/ui/component/webUploadList/internal/web-upload-item.jsx index cdf861559..29a4e32e8 100644 --- a/ui/component/webUploadList/internal/web-upload-item.jsx +++ b/ui/component/webUploadList/internal/web-upload-item.jsx @@ -5,11 +5,12 @@ import Button from 'component/button'; import FileThumbnail from 'component/fileThumbnail'; import * as MODALS from 'constants/modal_types'; import { serializeFileObj } from 'util/file'; +import { tusIsSessionLocked } from 'util/tus'; type Props = { uploadItem: FileUploadItem, doPublishResume: (any) => void, - doUpdateUploadRemove: (any) => void, + doUpdateUploadRemove: (string, any) => void, doOpenModal: (string, {}) => void, }; @@ -18,6 +19,7 @@ export default function WebUploadItem(props: Props) { const { params, file, fileFingerprint, progress, status, resumable, uploader } = uploadItem; const [showFileSelector, setShowFileSelector] = useState(false); + const locked = tusIsSessionLocked(params.guid); function handleFileChange(newFile: WebFile, clearName = true) { if (serializeFileObj(newFile) === fileFingerprint) { @@ -25,7 +27,7 @@ export default function WebUploadItem(props: Props) { doPublishResume({ ...params, file_path: newFile }); if (!params.guid) { // Can remove this if-block after January 2022. - doUpdateUploadRemove(params); + doUpdateUploadRemove('', params); } } else { doOpenModal(MODALS.CONFIRM, { @@ -52,13 +54,19 @@ export default function WebUploadItem(props: Props) { uploader.abort(); // XHR } } - doUpdateUploadRemove(params); + + // The second parameter (params) can be removed after January 2022. + doUpdateUploadRemove(params.guid, params); closeModal(); }, }); } function resolveProgressStr() { + if (locked) { + return __('File being uploaded in another tab or window.'); + } + if (!uploader) { return __('Stopped.'); } @@ -85,7 +93,7 @@ export default function WebUploadItem(props: Props) { } function getRetryButton() { - if (!resumable) { + if (!resumable || locked) { return null; } @@ -113,7 +121,9 @@ export default function WebUploadItem(props: Props) { } function getCancelButton() { - return