diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 68b85d6a84..4c1b6a1b19 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -30,8 +30,7 @@ type ElectronChannel = "seshatReply" | "setBadgeCount" | "update-downloaded" | - "userDownloadCompleted" | - "userDownloadOpen"; + "userDownload"; declare global { interface Window { @@ -50,6 +49,7 @@ declare global { interface Electron { on(channel: ElectronChannel, listener: (event: Event, ...args: any[]) => void): void; + removeListener(event: ElectronChannel, listener: (...args: any[]) => void): void; send(channel: ElectronChannel, ...args: any[]): void; } diff --git a/src/components/views/toasts/ElectronDownloadToast.tsx b/src/components/views/toasts/ElectronDownloadToast.tsx new file mode 100644 index 0000000000..ff6d2da4d6 --- /dev/null +++ b/src/components/views/toasts/ElectronDownloadToast.tsx @@ -0,0 +1,104 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, {useState} from "react"; +import GenericToast from "matrix-react-sdk/src/components/views/toasts/GenericToast"; +import { useEventEmitter } from "matrix-react-sdk/src/hooks/useEventEmitter"; +import ToastStore from "matrix-react-sdk/src/stores/ToastStore"; +import {_t} from "../../../vector/init"; + +const electron = window.electron; + +interface IProps { + downloadPath: string; + name: string; + totalBytes: number; + receivedBytes: number; + toastKey: string; +} + +const ElectronDownloadToast: React.FC = ({ + downloadPath, + name, + totalBytes, + receivedBytes: initialReceivedBytes, + toastKey, +}) => { + const [state, setState] = useState<"progressing" | "paused" | "cancelled" | "failed">("progressing"); + const [receivedBytes, setReceivedBytes] = useState(initialReceivedBytes); + + useEventEmitter(electron, "userDownload", (ev, {state, path, name, totalBytes, receivedBytes, terminal, begin}) => { + if (path !== downloadPath) return; + + setReceivedBytes(receivedBytes); + switch (state) { + case "progressing": + setState("progressing"); + break; + case "interrupted": + if (terminal) { + setState("failed"); + } else { + setState("paused"); + } + break; + case "cancelled": + setState("cancelled"); + break; + } + }); + + let acceptLabel: string; + let acceptFn; + // TODO decide if this should be cancel/dismiss + let rejectLabel = _t("Dismiss"); + let rejectFn = () => { + ToastStore.sharedInstance().dismissToast(toastKey); + }; + switch (state) { + case "progressing": + acceptLabel = _t("Pause"); + acceptFn = () => { + electron.send("userDownload", { + action: "pause", + path: downloadPath, + }); + }; + break; + case "paused": + acceptLabel = _t("Resume"); + acceptFn = () => { + electron.send("userDownload", { + action: "resume", + path: downloadPath, + }); + }; + break; + case "failed": + rejectLabel = null; + rejectFn = null; + } + + return ; +}; + +export default ElectronDownloadToast; diff --git a/src/vector/platform/ElectronPlatform.tsx b/src/vector/platform/ElectronPlatform.tsx index 74317e77d0..2d52a2fe4a 100644 --- a/src/vector/platform/ElectronPlatform.tsx +++ b/src/vector/platform/ElectronPlatform.tsx @@ -47,6 +47,7 @@ import {showToast as showUpdateToast} from "matrix-react-sdk/src/toasts/UpdateTo import {CheckUpdatesPayload} from "matrix-react-sdk/src/dispatcher/payloads/CheckUpdatesPayload"; import ToastStore from "matrix-react-sdk/src/stores/ToastStore"; import GenericExpiringToast from "matrix-react-sdk/src/components/views/toasts/GenericExpiringToast"; +import ElectronDownloadToast from "../../components/views/toasts/ElectronDownloadToast"; const electron = window.electron; const isMac = navigator.platform.toUpperCase().includes('MAC'); @@ -249,24 +250,46 @@ export default class ElectronPlatform extends VectorBasePlatform { dis.fire(Action.ViewUserSettings); }); - electron.on('userDownloadCompleted', (ev, {path, name}) => { - const onAccept = () => { - electron.send('userDownloadOpen', {path}); - }; - - ToastStore.sharedInstance().addOrReplaceToast({ - key: `DOWNLOAD_TOAST_${path}`, - title: _t("Download Completed"), - props: { - description: name, - acceptLabel: _t("Open"), - onAccept, - dismissLabel: _t("Dismiss"), - numSeconds: 10, - }, - component: GenericExpiringToast, - priority: 99, - }); + electron.on("userDownload", (ev, {state, path, name, totalBytes, receivedBytes, begin}) => { + if (begin) { + ToastStore.sharedInstance().addOrReplaceToast({ + key: `DOWNLOAD_TOAST_${path}`, + title: _t("Downloading..."), + props: { + downloadPath: path, + receivedBytes, + totalBytes, + name, + }, + component: ElectronDownloadToast, + priority: 99, + }); + } else if (state === "completed") { + ToastStore.sharedInstance().addOrReplaceToast({ + key: `DOWNLOAD_TOAST_${path}`, + title: _t("Download Completed"), + props: { + description: name, + acceptLabel: _t("Open"), + onAccept: () => { + electron.send("userDownload", { + action: "download", + path, + }); + }, + dismissLabel: _t("Dismiss"), + numSeconds: 10, + onDismiss: () => { + electron.send("userDownload", { + action: "done", + path, + }); + }, + }, + component: GenericExpiringToast, + priority: 99, + }); + } }); // register OS-specific shortcuts