diff --git a/public/locales/en/files.json b/public/locales/en/files.json index 0614e005d..20db42bf5 100644 --- a/public/locales/en/files.json +++ b/public/locales/en/files.json @@ -14,6 +14,7 @@ "openWithLocalAndPublicGateway": "Try opening it instead with your <1>local gateway1> or <3>public gateway3>.", "cantBePreviewed": "Sorry, this file can’t be previewed", "addByPath": "From IPFS", + "bulkImport": "Bulk import", "newFolder": "New folder", "generating": "Generating…", "actions": { @@ -59,6 +60,14 @@ "namePlaceholder": "Name (optional)", "examples": "Examples:" }, + "bulkImportModal": { + "title": "Bulk import with text file", + "description": "Upload a text file with a list of CIDs (names are optional). Example:", + "select": "Select file", + "selectedFile": "Selected file", + "invalidCids": "*Invalid CID(s) found", + "failedToReadFile": "*Failed to read file contents" + }, "newFolderModal": { "title": "New folder", "description": "Insert the name of the folder you want to create." diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index d3f349a33..7381ff92b 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -398,6 +398,53 @@ const actions = () => ({ } }), + /** + * Reads a text file containing CIDs and adds each one to IPFS at the given root path. + * @param {FileStream[]} source - The text file containing CIDs + * @param {string} root - Destination directory in IPFS + */ + doFilesBulkCidImport: (source, root) => perform(ACTIONS.BULK_CID_IMPORT, async function (ipfs, { store }) { + ensureMFS(store) + + if (!source?.[0]?.content) { + console.error('Invalid file format provided to doFilesBulkCidImport') + return + } + + try { + const file = source[0] + const content = await new Response(file.content).text() + const lines = content.split('\n').map(line => line.trim()).filter(Boolean) + + const cidObjects = lines.map((line) => { + let actualCid = line + let name = line + const cidParts = line.split(' ') + if (cidParts.length > 1) { + actualCid = cidParts[0] + name = cidParts.slice(1).join(' ') + } + return { + name, + cid: actualCid + } + }) + + for (const { cid, name } of cidObjects) { + try { + const src = `/ipfs/${cid}` + const dst = realMfsPath(join(root || '/files', name || cid)) + + await ipfs.files.cp(src, dst) + } catch (err) { + console.error(`Failed to add CID ${cid}:`, err) + } + } + } finally { + await store.doFilesFetch() + } + }), + /** * Creates a download link for the provided files. * @param {FileStat[]} files diff --git a/src/bundles/files/consts.js b/src/bundles/files/consts.js index b5fdac0dc..67383694a 100644 --- a/src/bundles/files/consts.js +++ b/src/bundles/files/consts.js @@ -23,6 +23,8 @@ export const ACTIONS = { SHARE_LINK: ('FILES_SHARE_LINK'), /** @type {'FILES_ADDBYPATH'} */ ADD_BY_PATH: ('FILES_ADDBYPATH'), + /** @type {'FILES_BULK_CID_IMPORT'} */ + BULK_CID_IMPORT: ('FILES_BULK_CID_IMPORT'), /** @type {'FILES_PIN_ADD'} */ PIN_ADD: ('FILES_PIN_ADD'), /** @type {'FILES_PIN_REMOVE'} */ diff --git a/src/bundles/files/protocol.ts b/src/bundles/files/protocol.ts index c6ca742e2..60076a255 100644 --- a/src/bundles/files/protocol.ts +++ b/src/bundles/files/protocol.ts @@ -63,6 +63,7 @@ export type Message = | Move | Write | AddByPath + | BulkCidImport | DownloadLink | Perform<'FILES_SHARE_LINK', Error, string, void> | Perform<'FILES_COPY', Error, void, void> @@ -76,6 +77,7 @@ export type MakeDir = Perform<'FILES_MAKEDIR', Error, void, void> export type WriteProgress = { paths: string[], progress: number } export type Write = Spawn<'FILES_WRITE', WriteProgress, Error, void, void> export type AddByPath = Perform<'FILES_ADDBYPATH', Error, void, void> +export type BulkCidImport = Perform<'FILES_BULK_CID_IMPORT', Error, void, void> export type Move = Perform<'FILES_MOVE', Error, void, void> export type Delete = Perform<'FILES_DELETE', Error, void, void> export type DownloadLink = Perform<'FILES_DOWNLOADLINK', Error, FileDownload, void> diff --git a/src/components/modal/Modal.js b/src/components/modal/Modal.js index 778d30ae2..05695bb3d 100644 --- a/src/components/modal/Modal.js +++ b/src/components/modal/Modal.js @@ -2,7 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import CancelIcon from '../../icons/GlyphSmallCancel.js' -export const ModalActions = ({ justify, className, children, ...props }) => ( +export const ModalActions = ({ justify = 'between', className = '', children, ...props }) => (
{t('bulkImportModal.description')}
+bafkreibm6jg3ux5qumhcn2b3flc3tyu6dmlb4xa7u5bf44yegnrjhc4yeq
QmawceGscqN4o8Y8Fv26UUmB454kn2bnkXV5tEQYc4jBd6 barrel.png
QmbvrHYWXAU1BuxMPNRtfeF4DS2oPmo5hat7ocqAkNPr74 pi equals.png
+ + {`${t('bulkImportModal.selectedFile')}: ${selectedFile.name}`} +
+ )} + + {validationError && ( ++ {validationError} +
+ )} +