From 6266f9bcab3a1e22694da77837b5b5888bf760a7 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Wed, 18 Aug 2021 11:55:36 +0200 Subject: [PATCH] feat: download as CAR via the context menu License: MIT Signed-off-by: Henrique Dias --- public/locales/en/app.json | 3 ++- src/bundles/files/actions.js | 11 +++++++++- src/bundles/files/consts.js | 9 +++++++-- src/files/FilesPage.js | 16 ++++++++++++++- src/files/context-menu/ContextMenu.js | 11 +++++++++- src/files/context-menu/ContextMenu.stories.js | 1 + src/lib/files.js | 20 +++++++++++++++++++ 7 files changed, 65 insertions(+), 6 deletions(-) diff --git a/public/locales/en/app.json b/public/locales/en/app.json index 82ba213de..0c0a8fc7e 100644 --- a/public/locales/en/app.json +++ b/public/locales/en/app.json @@ -27,7 +27,8 @@ "setPinning": "Set pinning", "submit": "Submit", "unpin": "Unpin", - "unselectAll": "Unselect all" + "unselectAll": "Unselect all", + "exportDag": "Download as CAR" }, "cliModal": { "description": "Paste the following into your terminal to do this task in IPFS via the command line. Remember that you'll need to replace placeholders with your specific parameters." diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index 650ee715d..d83a9202d 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -1,7 +1,7 @@ /* eslint-disable require-yield */ import { join, dirname, basename } from 'path' -import { getDownloadLink, getShareableLink } from '../../lib/files' +import { getDownloadLink, getShareableLink, getCarLink } from '../../lib/files' import countDirs from '../../lib/count-dirs' import memoize from 'p-memoize' import all from 'it-all' @@ -423,6 +423,15 @@ const actions = () => ({ return await getDownloadLink(files, gatewayUrl, apiUrl, ipfs) }), + /** + * Creates a download link for the DAG CAR. + * @param {FileStat[]} files + */ + doFilesDownloadCarLink: (files) => perform(ACTIONS.DOWNLOAD_LINK, async (ipfs, { store }) => { + const gatewayUrl = store.selectGatewayUrl() + return await getCarLink(files, gatewayUrl, ipfs) + }), + /** * Generates sharable link for the provided files. * @param {FileStat[]} files diff --git a/src/bundles/files/consts.js b/src/bundles/files/consts.js index 0bb4818d2..3107755fa 100644 --- a/src/bundles/files/consts.js +++ b/src/bundles/files/consts.js @@ -78,7 +78,8 @@ export const cliCmdKeys = { ADD_DIRECTORY: 'addNewDirectory', CREATE_NEW_DIRECTORY: 'createNewDirectory', FROM_IPFS: 'fromIpfs', - ADD_NEW_PEER: 'addNewPeer' + ADD_NEW_PEER: 'addNewPeer', + DOWNLOAD_CAR_COMMAND: 'downloadCarCommand' } export const cliCmdPrefixes = { @@ -124,5 +125,9 @@ export const cliCommandList = { * @param {string} path */ [cliCmdKeys.FROM_IPFS]: (path) => `ipfs files cp /ipfs/ "${path}/"`, - [cliCmdKeys.ADD_NEW_PEER]: () => 'ipfs swarm connect ' + [cliCmdKeys.ADD_NEW_PEER]: () => 'ipfs swarm connect ', + /** + * @param {string} cid + */ + [cliCmdKeys.DOWNLOAD_CAR_COMMAND]: (cid) => `ipfs dag export ${cid}` } diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js index a728fea53..91e93f631 100644 --- a/src/files/FilesPage.js +++ b/src/files/FilesPage.js @@ -21,7 +21,7 @@ import Header from './header/Header' import FileImportStatus from './file-import-status/FileImportStatus' const FilesPage = ({ - doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesWrite, doFilesAddPath, doUpdateHash, + doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesAddPath, doUpdateHash, doFilesUpdateSorting, doFilesNavigateTo, doFilesMove, doSetCliOptions, doFetchRemotePins, remotePins, doExploreUserProvidedPath, ipfsProvider, ipfsConnected, doFilesMakeDir, doFilesShareLink, doFilesDelete, doSetPinning, onRemotePinClick, files, filesPathInfo, pinningServices, toursEnabled, handleJoyrideCallback, isCliTutorModeEnabled, cliOptions, t @@ -67,6 +67,18 @@ const FilesPage = ({ const { abort } = await downloadFile(url, filename, updater, method) setDownloadAbort(() => abort) } + + const onDownloadCar = async (files) => { + if (downloadProgress !== null) { + return downloadAbort() + } + + const url = await doFilesDownloadCarLink(files) + const link = document.createElement('a') + link.href = url + link.click() + } + const onAddFiles = (raw, root = '') => { if (root === '') root = files.path @@ -202,6 +214,7 @@ const FilesPage = ({ onRename={() => showModal(RENAME, [contextMenu.file])} onInspect={() => onInspect(contextMenu.file.cid)} onDownload={() => onDownload([contextMenu.file])} + onDownloadCar={() => onDownloadCar([contextMenu.file])} onPinning={() => showModal(PINNING, [contextMenu.file])} isCliTutorModeEnabled={isCliTutorModeEnabled} onCliTutorMode={() => showModal(CLI_TUTOR_MODE, [contextMenu.file])} @@ -274,6 +287,7 @@ export default connect( 'selectToursEnabled', 'doFilesWrite', 'doFilesDownloadLink', + 'doFilesDownloadCarLink', 'doExploreUserProvidedPath', 'doFilesSizeGet', 'selectIsCliTutorModeEnabled', diff --git a/src/files/context-menu/ContextMenu.js b/src/files/context-menu/ContextMenu.js index abd97cb7b..73944c7c5 100644 --- a/src/files/context-menu/ContextMenu.js +++ b/src/files/context-menu/ContextMenu.js @@ -9,6 +9,7 @@ import StrokePencil from '../../icons/StrokePencil' import StrokeIpld from '../../icons/StrokeIpld' import StrokeTrash from '../../icons/StrokeTrash' import StrokeDownload from '../../icons/StrokeDownload' +import StrokeData from '../../icons/StrokeData' import StrokePin from '../../icons/StrokePin' import { cliCmdKeys } from '../../bundles/files/consts' @@ -43,7 +44,7 @@ class ContextMenu extends React.Component { render () { const { - t, onRename, onRemove, onDownload, onInspect, onShare, + t, onRename, onRemove, onDownload, onInspect, onShare, onDownloadCar, translateX, translateY, className, isMfs, isUnknown, isCliTutorModeEnabled } = this.props return ( @@ -87,6 +88,13 @@ class ContextMenu extends React.Component { {t('app:actions.download')} } + { !isUnknown && onDownloadCar && + + } { !isUnknown && isMfs && onRename &&