From 0214422e6b4beed2953b3ba84129f96f1dc292f9 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 24 Jun 2017 15:57:37 +0700 Subject: [PATCH] Add a badge to the dock for downloaded files --- ui/js/actions/content.js | 13 +++++++ ui/js/constants/action_types.js | 1 + ui/js/main.js | 21 +++++++++++ ui/js/reducers/app.js | 22 ++++++++++++ ui/js/selectors/app.js | 5 +++ ui/js/selectors/file_info.js | 63 +++++++++++++++++++++++++++++++++ ui/js/util/setBadge.js | 12 +++++++ ui/js/util/setProgressBar.js | 9 +++++ 8 files changed, 146 insertions(+) create mode 100644 ui/js/util/setBadge.js create mode 100644 ui/js/util/setProgressBar.js diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 652f1ca22..5e23791c4 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -11,6 +11,10 @@ import { selectResolvingUris } from "selectors/content"; import { selectCostInfoForUri } from "selectors/cost_info"; import { doOpenModal } from "actions/app"; import { doClaimEligiblePurchaseRewards } from "actions/rewards"; +import { selectBadgeNumber } from "selectors/app"; +import { selectTotalDownloadProgress } from "selectors/file_info"; +import setBadge from "util/setBadge"; +import setProgressBar from "util/setProgressBar"; import batchActions from "util/batchActions"; export function doResolveUri(uri) { @@ -121,6 +125,11 @@ export function doUpdateLoadStatus(uri, outpoint) { fileInfo, }, }); + + const badgeNumber = selectBadgeNumber(getState()); + setBadge(badgeNumber === 0 ? "" : `${badgeNumber}`); + const totalProgress = selectTotalDownloadProgress(getState()); + setProgressBar(totalProgress); } else { // ready to play const { total_bytes, written_bytes } = fileInfo; @@ -135,6 +144,10 @@ export function doUpdateLoadStatus(uri, outpoint) { progress, }, }); + + const totalProgress = selectTotalDownloadProgress(getState()); + setProgressBar(totalProgress); + setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)); }, 250); diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index e5432b1fb..b40bc9d1d 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -4,6 +4,7 @@ export const CLOSE_MODAL = "CLOSE_MODAL"; export const HISTORY_BACK = "HISTORY_BACK"; export const SHOW_SNACKBAR = "SHOW_SNACKBAR"; export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK"; +export const WINDOW_FOCUSED = "WINDOW_FOCUSED"; export const DAEMON_READY = "DAEMON_READY"; diff --git a/ui/js/main.js b/ui/js/main.js index 331eccc11..94df53b32 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -9,6 +9,8 @@ import SplashScreen from "component/splash.js"; import AuthOverlay from "component/authOverlay"; import { doChangePath, doNavigate, doDaemonReady } from "actions/app"; import { toQueryString } from "util/query_params"; +import { selectBadgeNumber } from "selectors/app"; +import * as types from "constants/action_types"; const env = ENV; const { remote, ipcRenderer, shell } = require("electron"); @@ -71,6 +73,25 @@ document.addEventListener("click", event => { } }); +const application = remote.app; +const dock = application.dock; +const win = remote.BrowserWindow.getFocusedWindow(); + +// Clear the badge when the window is focused +win.on("focus", () => { + if (!dock) return; + + app.store.dispatch({ type: types.WINDOW_FOCUSED }); + dock.setBadge(""); +}); + +const updateProgress = () => { + const state = app.store.getState(); + const progress = selectTotalDownloadProgress(state); + + win.setProgressBar(progress || -1); +}; + const initialState = app.store.getState(); // import whyDidYouUpdate from "why-did-you-update"; diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index 2a638ede4..5e44f4ac1 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -7,6 +7,10 @@ const currentPath = () => { else return "/discover"; }; +const { remote } = require("electron"); +const application = remote.app; +const win = remote.BrowserWindow.getFocusedWindow(); + const reducers = {}; const defaultState = { isLoaded: false, @@ -16,6 +20,7 @@ const defaultState = { daemonReady: false, obscureNsfw: !lbry.getClientSetting("showNsfw"), hasSignature: false, + badgeNumber: 0, }; reducers[types.DAEMON_READY] = function(state, action) { @@ -120,6 +125,23 @@ reducers[types.REMOVE_SNACKBAR_SNACK] = function(state, action) { }); }; +reducers[types.DOWNLOADING_COMPLETED] = function(state, action) { + const badgeNumber = state.badgeNumber; + + // Don't update the badge number if the window is focused + if (win.isFocused()) return Object.assign({}, state); + + return Object.assign({}, state, { + badgeNumber: badgeNumber + 1, + }); +}; + +reducers[types.WINDOW_FOCUSED] = function(state, action) { + return Object.assign({}, state, { + badgeNumber: 0, + }); +}; + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index 0945833c1..086500ff2 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -190,3 +190,8 @@ export const selectSnackBarSnacks = createSelector( selectSnackBar, snackBar => snackBar.snacks || [] ); + +export const selectBadgeNumber = createSelector( + _selectState, + state => state.badgeNumber +); diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index fac5cd29e..23d62e230 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -108,3 +108,66 @@ export const selectFileInfosPublished = createSelector( return [...fileInfos, ...pendingFileInfos]; } ); + +// export const selectFileInfoForUri = (state, props) => { +// const claims = selectClaimsByUri(state), +// claim = claims[props.uri], +// fileInfos = selectAllFileInfos(state), +// outpoint = claim ? `${claim.txid}:${claim.nout}` : undefined; + +// return outpoint && fileInfos ? fileInfos[outpoint] : undefined; +// }; + +export const selectFileInfosByUri = createSelector( + selectClaimsByUri, + selectAllFileInfos, + (claimsByUri, byOutpoint) => { + const fileInfos = {}; + const uris = Object.keys(claimsByUri); + + uris.forEach(uri => { + const claim = claimsByUri[uri]; + if (claim) { + const outpoint = `${claim.txid}:${claim.nout}`; + const fileInfo = byOutpoint[outpoint]; + + if (fileInfo) fileInfos[uri] = fileInfo; + } + }); + + return fileInfos; + } +); + +export const selectDownloadingFileInfos = createSelector( + selectUrisDownloading, + selectFileInfosByUri, + (urisDownloading, byUri) => { + const uris = Object.keys(urisDownloading); + const fileInfos = []; + + uris.forEach(uri => { + const fileInfo = byUri[uri]; + + if (fileInfo) fileInfos.push(fileInfo); + }); + + return fileInfos; + } +); + +export const selectTotalDownloadProgress = createSelector( + selectDownloadingFileInfos, + fileInfos => { + const progress = []; + + fileInfos.forEach(fileInfo => { + progress.push(fileInfo.written_bytes / fileInfo.total_bytes * 100); + }); + + const totalProgress = progress.reduce((a, b) => a + b, 0); + + if (fileInfos.length > 0) return totalProgress / fileInfos.length / 100.0; + else return -1; + } +); diff --git a/ui/js/util/setBadge.js b/ui/js/util/setBadge.js new file mode 100644 index 000000000..5d564c434 --- /dev/null +++ b/ui/js/util/setBadge.js @@ -0,0 +1,12 @@ +const { remote } = require("electron"); +const application = remote.app; +const dock = application.dock; +const win = remote.BrowserWindow.getFocusedWindow(); +const setBadge = text => { + if (!dock) return; + if (win.isFocused()) return; + + dock.setBadge(text); +}; + +export default setBadge; diff --git a/ui/js/util/setProgressBar.js b/ui/js/util/setProgressBar.js new file mode 100644 index 000000000..304a54f17 --- /dev/null +++ b/ui/js/util/setProgressBar.js @@ -0,0 +1,9 @@ +const { remote } = require("electron"); +const application = remote.app; +const win = remote.BrowserWindow.getFocusedWindow(); + +const setProgressBar = progress => { + win.setProgressBar(progress); +}; + +export default setProgressBar;