Update Download Progress
This commit is contained in:
parent
eecb5bac8b
commit
a6cac05a66
7 changed files with 189 additions and 39 deletions
BIN
static/img/dark_loading.gif
Normal file
BIN
static/img/dark_loading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
BIN
static/img/white_loading.gif
Normal file
BIN
static/img/white_loading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
32
ui/component/downloadProgress/index.js
Executable file → Normal file
32
ui/component/downloadProgress/index.js
Executable file → Normal file
|
@ -1,36 +1,48 @@
|
|||
import { connect } from 'react-redux';
|
||||
import DownloadProgress from './view';
|
||||
import { doSetPlayingUri, doStopDownload, doUpdateDownloadingStatus } from 'redux/actions/content';
|
||||
import { selectFileInfosByOutpoint } from 'lbry-redux';
|
||||
import { doSetPlayingUri, doStopDownload, doContinueDownloading, doPurchaseUriWrapper } from 'redux/actions/content';
|
||||
import { selectFileInfosByOutpoint, SETTINGS } from 'lbry-redux';
|
||||
import { selectPrimaryUri, selectPlayingUri } from 'redux/selectors/content';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
|
||||
const select = (state) => {
|
||||
const byOutpoint = selectFileInfosByOutpoint(state);
|
||||
const runningByOutpoint = [];
|
||||
const primaryUri = selectPrimaryUri(state);
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const uri = playingUri ? playingUri.uri : null;
|
||||
let primaryOutpoint = null;
|
||||
let playingOutpoint = null;
|
||||
|
||||
for (const key in byOutpoint) {
|
||||
const item = byOutpoint[key];
|
||||
|
||||
if (item && primaryUri && primaryUri.includes(`/${item.claim_name}`)) primaryOutpoint = item.outpoint;
|
||||
if (item && uri && uri.includes(`/${item.claim_name}`)) playingOutpoint = item.outpoint;
|
||||
|
||||
if (item && item.status === 'running') {
|
||||
if (
|
||||
(!primaryUri || !primaryUri.includes(`/${item.claim_name}`)) &&
|
||||
(!uri || !uri.includes(`/${item.claim_name}`))
|
||||
) {
|
||||
runningByOutpoint.push(item);
|
||||
}
|
||||
runningByOutpoint.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
downloadList: runningByOutpoint,
|
||||
byOutpoint: selectFileInfosByOutpoint(state),
|
||||
primary: {
|
||||
uri: primaryUri,
|
||||
outpoint: primaryOutpoint,
|
||||
},
|
||||
playing: {
|
||||
uri,
|
||||
outpoint: playingOutpoint,
|
||||
},
|
||||
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
pause: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||
updateDownloadingStatus: (outpoint) => dispatch(doUpdateDownloadingStatus(outpoint)),
|
||||
doContinueDownloading: (outpoint, force) => dispatch(doContinueDownloading(outpoint, force)),
|
||||
stopDownload: (outpoint) => dispatch(doStopDownload(outpoint)),
|
||||
download: (uri) => dispatch(doPurchaseUriWrapper(uri, false, true)),
|
||||
});
|
||||
export default connect(select, perform)(DownloadProgress);
|
||||
|
|
138
ui/component/downloadProgress/view.jsx
Executable file → Normal file
138
ui/component/downloadProgress/view.jsx
Executable file → Normal file
|
@ -5,40 +5,108 @@ import Button from 'component/button';
|
|||
import * as ICONS from 'constants/icons';
|
||||
import { buildURI } from 'lbry-redux';
|
||||
import { formatBytes } from 'util/format-bytes';
|
||||
import { areEqual, removeItem } from 'util/array';
|
||||
import loadingIcon from '../../../static/img/white_loading.gif';
|
||||
import darkLoadingIcon from '../../../static/img/dark_loading.gif';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
|
||||
type Props = {
|
||||
downloadList: any[],
|
||||
byOutpoint: any,
|
||||
primary: any,
|
||||
playing: any,
|
||||
currentTheme: string,
|
||||
stopDownload: (outpoint: string) => void,
|
||||
updateDownloadingStatus: (outpoint: string) => void,
|
||||
doContinueDownloading: (outpoint: string, force: boolean) => void,
|
||||
download: (uri: string) => void,
|
||||
};
|
||||
|
||||
function DownloadProgress({ downloadList, stopDownload, updateDownloadingStatus }: Props) {
|
||||
function DownloadProgress({ byOutpoint, primary, playing, currentTheme, stopDownload, doContinueDownloading }: Props) {
|
||||
const [isShow, setIsShow] = usePersistedState('download-progress', true);
|
||||
const [downloading, setDownloading] = usePersistedState('download-progress-downloading', []);
|
||||
const [cancelHash] = useState({});
|
||||
const [checkDownloadingHash] = useState({});
|
||||
const [initDownloadingHash] = useState({});
|
||||
const [prevPlaying, setPrevPlaying] = useState({});
|
||||
const [prevPrimary, setPrevPrimary] = useState({});
|
||||
|
||||
const handleCancel = (hash, value) => {
|
||||
cancelHash[hash] = value;
|
||||
};
|
||||
|
||||
if (downloadList.length === 0) return null;
|
||||
useEffect(() => {}, []);
|
||||
|
||||
downloadList.map((item) => {
|
||||
if (item && !checkDownloadingHash[item.outpoint]) {
|
||||
updateDownloadingStatus(item.outpoint);
|
||||
checkDownloadingHash[item.outpoint] = true;
|
||||
const handleStopDownload = (outpoint) => {
|
||||
const updated = [...downloading];
|
||||
removeItem(updated, outpoint);
|
||||
setDownloading(updated);
|
||||
stopDownload(outpoint);
|
||||
};
|
||||
|
||||
const runningByOutpoint = {};
|
||||
const updateDownloading = [...downloading];
|
||||
|
||||
for (const key in byOutpoint) {
|
||||
const item = byOutpoint[key];
|
||||
if (item && item.status === 'running') runningByOutpoint[item.outpoint] = item;
|
||||
}
|
||||
|
||||
Object.keys(runningByOutpoint)
|
||||
.filter((outpoint) => downloading.indexOf(outpoint) === -1)
|
||||
.map((outpoint) => {
|
||||
if (primary.outpoint !== outpoint && playing.outpoint !== outpoint) {
|
||||
updateDownloading.push(outpoint);
|
||||
}
|
||||
});
|
||||
|
||||
downloading
|
||||
.filter((outpoint) => (byOutpoint[outpoint] && byOutpoint[outpoint].status !== 'running') || !byOutpoint[outpoint])
|
||||
.map((outpoint) => {
|
||||
removeItem(updateDownloading, outpoint);
|
||||
});
|
||||
if (!areEqual(downloading, updateDownloading)) setDownloading(updateDownloading);
|
||||
|
||||
if (updateDownloading.length === 0) return null;
|
||||
|
||||
if (playing.outpoint !== prevPlaying.outpoint) {
|
||||
if (downloading.includes(prevPlaying.outpoint)) {
|
||||
setTimeout(() => {
|
||||
doContinueDownloading(prevPlaying.outpoint, true);
|
||||
}, 1000);
|
||||
}
|
||||
setPrevPlaying(playing);
|
||||
}
|
||||
|
||||
if (primary.outpoint !== prevPrimary.outpoint) {
|
||||
if (downloading.includes(prevPrimary.outpoint)) {
|
||||
setTimeout(() => {
|
||||
doContinueDownloading(prevPrimary.outpoint, true);
|
||||
}, 1000);
|
||||
}
|
||||
setPrevPrimary(primary);
|
||||
}
|
||||
|
||||
updateDownloading.map((outpoint) => {
|
||||
if (!initDownloadingHash[outpoint]) {
|
||||
initDownloadingHash[outpoint] = true;
|
||||
doContinueDownloading(outpoint, false);
|
||||
}
|
||||
});
|
||||
|
||||
if (!isShow) {
|
||||
return (
|
||||
<Button
|
||||
iconSize={40}
|
||||
icon={ICONS.DOWNLOAD}
|
||||
className="download-progress__toggle-button"
|
||||
onClick={() => setIsShow(true)}
|
||||
/>
|
||||
<>
|
||||
<Button
|
||||
iconSize={40}
|
||||
icon={ICONS.DOWNLOAD}
|
||||
className="download-progress__toggle-button"
|
||||
onClick={() => setIsShow(true)}
|
||||
>
|
||||
<div className="download-progress__current-downloading">
|
||||
<span className="notification__bubble">
|
||||
<span className="notification__count">{updateDownloading.length}</span>
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -48,11 +116,16 @@ function DownloadProgress({ downloadList, stopDownload, updateDownloadingStatus
|
|||
<div />
|
||||
</Button>
|
||||
|
||||
{downloadList.map((item, index) => {
|
||||
{updateDownloading.map((outpoint, index) => {
|
||||
const item = runningByOutpoint[outpoint];
|
||||
let releaseTime = '';
|
||||
let isPlaying = false;
|
||||
if (item.metadata && item.metadata.release_time) {
|
||||
releaseTime = new Date(parseInt(item.metadata.release_time) * 1000).toISOString().split('T')[0];
|
||||
}
|
||||
if (outpoint === primary.outpoint || outpoint === playing.outpoint) {
|
||||
isPlaying = true;
|
||||
}
|
||||
return (
|
||||
<div key={item.outpoint}>
|
||||
{index !== 0 && <hr className="download-progress__divider" />}
|
||||
|
@ -64,12 +137,14 @@ function DownloadProgress({ downloadList, stopDownload, updateDownloadingStatus
|
|||
totalBytes={item.total_bytes}
|
||||
addedOn={item.added_on}
|
||||
directory={item.download_directory}
|
||||
stopDownload={stopDownload}
|
||||
stopDownload={handleStopDownload}
|
||||
outpoint={item.outpoint}
|
||||
isCancel={cancelHash[item.outpoint]}
|
||||
claimID={item.claim_id}
|
||||
playing={isPlaying}
|
||||
claimName={item.claim_name}
|
||||
handleCancel={handleCancel}
|
||||
currentTheme={currentTheme}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -90,6 +165,8 @@ type DownloadProgressItemProps = {
|
|||
isCancel: boolean,
|
||||
claimID: string,
|
||||
claimName: string,
|
||||
playing: boolean,
|
||||
currentTheme: string,
|
||||
stopDownload: (outpoint: string) => void,
|
||||
handleCancel: (hash: string, value: boolean) => void,
|
||||
};
|
||||
|
@ -106,6 +183,8 @@ function DownloadProgressItem({
|
|||
isCancel,
|
||||
claimID,
|
||||
claimName,
|
||||
playing,
|
||||
currentTheme,
|
||||
stopDownload,
|
||||
handleCancel,
|
||||
}: DownloadProgressItemProps) {
|
||||
|
@ -152,7 +231,6 @@ function DownloadProgressItem({
|
|||
const openDownloadFolder = () => {
|
||||
shell.openPath(directory);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="download-progress__state-container">
|
||||
<div className="download-progress__state-bar">
|
||||
|
@ -161,14 +239,22 @@ function DownloadProgressItem({
|
|||
className="download-progress__state-filename"
|
||||
navigate={buildURI({ claimName, claimID })}
|
||||
/>
|
||||
<div
|
||||
className="download-progress__close-button"
|
||||
onClick={() => {
|
||||
handleCancel(outpoint, true);
|
||||
}}
|
||||
>
|
||||
×
|
||||
</div>
|
||||
{playing ? (
|
||||
currentTheme === 'light' ? (
|
||||
<img src={loadingIcon} className="download-progress__playing-button" />
|
||||
) : (
|
||||
<img src={darkLoadingIcon} className="download-progress__playing-button" />
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
className="download-progress__close-button"
|
||||
onClick={() => {
|
||||
handleCancel(outpoint, true);
|
||||
}}
|
||||
>
|
||||
×
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="download-progress__state-bar">
|
||||
<a className="download-progress__state-filename-link" onClick={openDownloadFolder}>
|
||||
|
|
|
@ -54,6 +54,7 @@ export function doUpdateLoadStatus(uri: any, outpoint: string) {
|
|||
setNextStatusUpdate();
|
||||
} else if (fileInfo.completed) {
|
||||
// TODO this isn't going to get called if they reload the client before
|
||||
|
||||
// the download finished
|
||||
dispatch({
|
||||
type: ACTIONS.DOWNLOADING_COMPLETED,
|
||||
|
@ -99,9 +100,9 @@ export function doUpdateLoadStatus(uri: any, outpoint: string) {
|
|||
// @endif
|
||||
}
|
||||
|
||||
export function doUpdateDownloadingStatus(outpoint: string) {
|
||||
export function doContinueDownloading(outpoint: string, force: boolean) {
|
||||
return (dispatch: Dispatch) => {
|
||||
if (!timeOutHash[outpoint]) {
|
||||
if (!timeOutHash[outpoint] || force) {
|
||||
dispatch(doUpdateLoadStatus(null, outpoint));
|
||||
}
|
||||
};
|
||||
|
@ -113,6 +114,7 @@ export function doStopDownload(outpoint: string) {
|
|||
clearInterval(timeOutHash[outpoint]);
|
||||
timeOutHash[outpoint] = undefined;
|
||||
}
|
||||
|
||||
dispatch(doDeleteFile(outpoint, false, false, null));
|
||||
};
|
||||
}
|
||||
|
|
|
@ -89,6 +89,12 @@
|
|||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.download-progress__playing-button {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
.download-progress__count-time {
|
||||
font-size: 11px;
|
||||
letter-spacing: -0.6px;
|
||||
|
@ -148,7 +154,7 @@
|
|||
bottom: 10px;
|
||||
right: 10px;
|
||||
border: none;
|
||||
background: var(--color-white);
|
||||
background: var(--color-header-background);
|
||||
color: var(--color-gray-6);
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
@ -163,3 +169,29 @@
|
|||
margin-right: 10px;
|
||||
font-size: 25px;
|
||||
}
|
||||
.download-progress__current-downloading {
|
||||
position: fixed;
|
||||
bottom: 25px;
|
||||
right: 15px;
|
||||
border: none;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
animation-name: downloadcount;
|
||||
animation-duration: 1.3s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
@keyframes downloadcount {
|
||||
0% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
|
18
ui/util/array.js
Normal file
18
ui/util/array.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
export function areEqual(first, second) {
|
||||
if (first.length !== second.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < first.length; i++) {
|
||||
if (!second.includes(first[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function removeItem(array, item) {
|
||||
const index = array.indexOf(item);
|
||||
if (index > -1) {
|
||||
array.splice(index, 1);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue