From 16c9c926804108dd6816a44766a9099910f94e0d Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Fri, 29 Nov 2024 20:51:12 -0500 Subject: [PATCH 01/44] Start bulk CID import --- src/bundles/files/actions.js | 1 + src/files/file-input/FileInput.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index d3f349a33..55b80cfa5 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -378,6 +378,7 @@ const actions = () => ({ * @param {string} src * @param {string} name */ + // HERE doFilesAddPath: (root, src, name = '') => perform(ACTIONS.ADD_BY_PATH, async (ipfs, { store }) => { ensureMFS(store) diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js index c83ddfbc1..ec9bfb6e6 100644 --- a/src/files/file-input/FileInput.js +++ b/src/files/file-input/FileInput.js @@ -92,6 +92,11 @@ class FileInput extends React.Component { {t('newFolder')} + From 31a6cd06a4a3259ffbec3f5e7c70fef08ea7055b Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sat, 30 Nov 2024 14:08:55 -0500 Subject: [PATCH 02/44] Get functional MVP working with csv file --- src/bundles/files/actions.js | 48 +++++++++++++++++++++++++++++++ src/files/FilesPage.js | 9 +++++- src/files/file-input/FileInput.js | 30 +++++++++++++++++-- src/files/header/Header.js | 1 + 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index 55b80cfa5..73c506429 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -399,6 +399,54 @@ const actions = () => ({ } }), + /** + * Reads a CSV file containing CIDs and adds each one to IPFS at the given root path. + * @param {FileStream[]} source - The CSV file containing CIDs + * @param {string} root - Destination directory in IPFS + */ + doFilesAddBulkCid: (source, root) => spawn(ACTIONS.ADD_BY_PATH, async function * (ipfs, { store }) { + ensureMFS(store) + + if (source.length !== 1) { + throw new Error('Please provide exactly one CSV file') + } + + // Read the CSV file content + const file = source[0] + const content = await new Response(file.content).text() + + // Split content into CIDs (assuming one CID per line, comma-separated) + const cids = content.split(/[\n,]/).map(cid => cid.trim()).filter(Boolean) + + /** @type {Array<{ path: string, cid: string }>} */ + const entries = [] + let progress = 0 + const totalCids = cids.length + + yield { entries, progress: 0 } + + for (const cid of cids) { + try { + const src = `/ipfs/${cid}` + const dst = realMfsPath(join(root || '/files', cid)) + + await ipfs.files.cp(src, dst) + + entries.push({ path: dst, cid }) + progress = (entries.length / totalCids) * 100 + + yield { entries, progress } + } catch (err) { + console.error(`Failed to add CID ${cid}:`, err) + // Continue with next CID even if one fails + } + } + + yield { entries, progress: 100 } + await store.doFilesFetch() + return entries + }), + /** * Creates a download link for the provided files. * @param {FileStat[]} files diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js index d9a837330..ccad9f994 100644 --- a/src/files/FilesPage.js +++ b/src/files/FilesPage.js @@ -21,7 +21,7 @@ import FileImportStatus from './file-import-status/FileImportStatus.js' import { useExplore } from 'ipld-explorer-components/providers' const FilesPage = ({ - doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesAddPath, doUpdateHash, + doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesAddBulkCid, doFilesAddPath, doUpdateHash, doFilesUpdateSorting, doFilesNavigateTo, doFilesMove, doSetCliOptions, doFetchRemotePins, remotePins, pendingPins, failedPins, ipfsProvider, ipfsConnected, doFilesMakeDir, doFilesShareLink, doFilesDelete, doSetPinning, onRemotePinClick, doPublishIpnsKey, files, filesPathInfo, pinningServices, toursEnabled, handleJoyrideCallback, isCliTutorModeEnabled, cliOptions, t @@ -72,6 +72,11 @@ const FilesPage = ({ doFilesWrite(raw, root) } + const onAddBulkCid = (raw, root = '') => { + if (root === '') root = files.path + doFilesAddBulkCid(raw, root) + } + const onAddByPath = (path, name) => doFilesAddPath(files.path, path, name) const onInspect = (cid) => doUpdateHash(`/explore/${cid}`) const showModal = (modal, files = null) => setModals({ show: modal, files }) @@ -204,6 +209,7 @@ const FilesPage = ({ files={files} onNavigate={doFilesNavigateTo} onAddFiles={onAddFiles} + onAddBulkCid={onAddBulkCid} onMove={doFilesMove} onAddByPath={(files) => showModal(ADD_BY_PATH, files)} onNewFolder={(files) => showModal(NEW_FOLDER, files)} @@ -277,6 +283,7 @@ export default connect( 'selectFilesSorting', 'selectToursEnabled', 'doFilesWrite', + 'doFilesAddBulkCid', 'doFilesDownloadLink', 'doFilesDownloadCarLink', 'doFilesSizeGet', diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js index ec9bfb6e6..6a1ba964f 100644 --- a/src/files/file-input/FileInput.js +++ b/src/files/file-input/FileInput.js @@ -40,11 +40,21 @@ class FileInput extends React.Component { return this.filesInput.click() } + onAddBulkCid = async () => { + this.toggleDropdown() + return this.bulkCidInput.click() + } + onInputChange = (input) => async () => { this.props.onAddFiles(normalizeFiles(input.files)) input.value = null } + onBulkCidInputChange = (input) => async () => { + this.props.onAddBulkCid(normalizeFiles(input.files)) + input.value = null + } + onAddByPath = () => { this.props.onAddByPath() this.toggleDropdown() @@ -92,8 +102,12 @@ class FileInput extends React.Component { {t('newFolder')} - @@ -116,6 +130,15 @@ class FileInput extends React.Component { webkitdirectory='true' ref={el => { this.folderInput = el }} onChange={this.onInputChange(this.folderInput)} /> + + { this.bulkCidInput = el }} + onChange={this.onBulkCidInputChange(this.bulkCidInput)} /> ) } @@ -125,7 +148,8 @@ FileInput.propTypes = { t: PropTypes.func.isRequired, onAddFiles: PropTypes.func.isRequired, onAddByPath: PropTypes.func.isRequired, - onNewFolder: PropTypes.func.isRequired + onNewFolder: PropTypes.func.isRequired, + onAddBulkCid: PropTypes.func.isRequired } export default connect( diff --git a/src/files/header/Header.js b/src/files/header/Header.js index ce9142109..a1df8f537 100644 --- a/src/files/header/Header.js +++ b/src/files/header/Header.js @@ -93,6 +93,7 @@ class Header extends React.Component { onNewFolder={this.props.onNewFolder} onAddFiles={this.props.onAddFiles} onAddByPath={this.props.onAddByPath} + onAddBulkCid={this.props.onAddBulkCid} onCliTutorMode={this.props.onCliTutorMode} /> :
{ this.dotsWrapper = el }}> From 35e392d9fc1d3003809901ebe7f0b2e6a8775cd7 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sat, 30 Nov 2024 20:03:56 -0500 Subject: [PATCH 03/44] Get it working with txt file --- src/bundles/files/actions.js | 27 ++++++++++++++++++++------- src/files/file-input/FileInput.js | 2 +- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index 73c506429..c8e8d5520 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -408,27 +408,40 @@ const actions = () => ({ ensureMFS(store) if (source.length !== 1) { - throw new Error('Please provide exactly one CSV file') + throw new Error('Please provide exactly one text file') } - // Read the CSV file content + // Read the text file content const file = source[0] const content = await new Response(file.content).text() - // Split content into CIDs (assuming one CID per line, comma-separated) - const cids = content.split(/[\n,]/).map(cid => cid.trim()).filter(Boolean) + const lines = content.split('\n').map(line => line.trim()).filter(Boolean) + // NOTE: need to add finally fetch later + 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 + } + }) /** @type {Array<{ path: string, cid: string }>} */ const entries = [] let progress = 0 - const totalCids = cids.length + const totalCids = cidObjects.length yield { entries, progress: 0 } - for (const cid of cids) { + for (const { cid, name } of cidObjects) { try { const src = `/ipfs/${cid}` - const dst = realMfsPath(join(root || '/files', cid)) + const dst = realMfsPath(join(root || '/files', name || cid)) await ipfs.files.cp(src, dst) diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js index 6a1ba964f..8092cc03f 100644 --- a/src/files/file-input/FileInput.js +++ b/src/files/file-input/FileInput.js @@ -136,7 +136,7 @@ class FileInput extends React.Component { type='file' className='dn' multiple - accept='.csv' + accept='.txt' ref={el => { this.bulkCidInput = el }} onChange={this.onBulkCidInputChange(this.bulkCidInput)} />
From 31ea2522a266087ee7496e0dcb6b626efde4adfd Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Fri, 6 Dec 2024 14:38:09 -0500 Subject: [PATCH 04/44] WIP: bulk import modal UX --- src/files/FilesPage.js | 9 +- src/files/file-input/FileInput.js | 14 +- src/files/header/Header.js | 2 +- src/files/modals/Modals.js | 18 ++ .../bulk-import-modal/BulkImportModal.js | 185 ++++++++++++++++++ 5 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 src/files/modals/bulk-import-modal/BulkImportModal.js diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js index ccad9f994..e36cf8406 100644 --- a/src/files/FilesPage.js +++ b/src/files/FilesPage.js @@ -15,7 +15,7 @@ import FilesList from './files-list/FilesList.js' import { getJoyrideLocales } from '../helpers/i8n.js' // Icons -import Modals, { DELETE, NEW_FOLDER, SHARE, RENAME, ADD_BY_PATH, CLI_TUTOR_MODE, PINNING, PUBLISH } from './modals/Modals.js' +import Modals, { DELETE, NEW_FOLDER, SHARE, RENAME, ADD_BY_PATH, BULK_CID_IMPORT, CLI_TUTOR_MODE, PINNING, PUBLISH } from './modals/Modals.js' import Header from './header/Header.js' import FileImportStatus from './file-import-status/FileImportStatus.js' import { useExplore } from 'ipld-explorer-components/providers' @@ -72,7 +72,7 @@ const FilesPage = ({ doFilesWrite(raw, root) } - const onAddBulkCid = (raw, root = '') => { + const onBulkCidImport = (raw, root = '') => { if (root === '') root = files.path doFilesAddBulkCid(raw, root) } @@ -209,9 +209,11 @@ const FilesPage = ({ files={files} onNavigate={doFilesNavigateTo} onAddFiles={onAddFiles} - onAddBulkCid={onAddBulkCid} + // onAddBulkCid={onAddBulkCid} + // onBulkCidImport={onBulkCidImport} onMove={doFilesMove} onAddByPath={(files) => showModal(ADD_BY_PATH, files)} + onBulkCidImport={(files) => showModal(BULK_CID_IMPORT, files)} onNewFolder={(files) => showModal(NEW_FOLDER, files)} onCliTutorMode={() => showModal(CLI_TUTOR_MODE)} handleContextMenu={(...args) => handleContextMenu(...args, true)} /> @@ -232,6 +234,7 @@ const FilesPage = ({ onShareLink={doFilesShareLink} onRemove={doFilesDelete} onAddByPath={onAddByPath} + onBulkCidImport={onBulkCidImport} onPinningSet={doSetPinning} onPublish={doPublishIpnsKey} cliOptions={cliOptions} diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js index 8092cc03f..7b6cf6650 100644 --- a/src/files/file-input/FileInput.js +++ b/src/files/file-input/FileInput.js @@ -51,7 +51,8 @@ class FileInput extends React.Component { } onBulkCidInputChange = (input) => async () => { - this.props.onAddBulkCid(normalizeFiles(input.files)) + console.log('onBulkCidInputChange', input) + this.props.onBulkCidImport(normalizeFiles(input.files)) input.value = null } @@ -60,6 +61,11 @@ class FileInput extends React.Component { this.toggleDropdown() } + onBulkCidImport = () => { + this.props.onBulkCidImport() + this.toggleDropdown() + } + onNewFolder = () => { this.props.onNewFolder() this.toggleDropdown() @@ -102,7 +108,9 @@ class FileInput extends React.Component { {t('newFolder')} - diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.js b/src/files/modals/bulk-import-modal/BulkImportModal.js index 25dc03172..edb94d797 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.js +++ b/src/files/modals/bulk-import-modal/BulkImportModal.js @@ -31,13 +31,13 @@ class BulkImportModal extends React.Component { for (const line of lines) { const [cid] = line.trim().split(' ') if (!isIPFS.cid(cid)) { - return { isValid: false, error: '*Invalid CID(s) found' } + return { isValid: false, error: this.props.t('bulkImportModal.invalidCids') } } } return { isValid: true } } catch (err) { - return { isValid: false, error: '*Failed to read file contents' } + return { isValid: false, error: this.props.t('bulkImportModal.failedToReadFile') } } } @@ -53,7 +53,7 @@ class BulkImportModal extends React.Component { } selectFile = async () => { - return this.bulkCidInputt.click() + return this.bulkCidInput.click() } onSubmit = async () => { @@ -76,9 +76,9 @@ class BulkImportModal extends React.Component { return ( - +
-

{'Upload a text file with a list of CIDs (names are optional).' + ' ' + 'Example:'}

+

{t('bulkImportModal.description')}

bafkreibm6jg3ux5qumhcn2b3flc3tyu6dmlb4xa7u5bf44yegnrjhc4yeq
QmawceGscqN4o8Y8Fv26UUmB454kn2bnkXV5tEQYc4jBd6 barrel.png
QmbvrHYWXAU1BuxMPNRtfeF4DS2oPmo5hat7ocqAkNPr74 pi equals.png
@@ -90,10 +90,7 @@ class BulkImportModal extends React.Component { onChange={this.onChange} // className='input-reset' // id='bulk-import' - ref={el => { - console.log('Setting ref:', el) - this.bulkCidInputt = el - }} + ref={el => { this.bulkCidInput = el }} /> {this.state.selectedFile && (

- Selected file: {this.state.selectedFile.name} + {`${t('bulkImportModal.selectedFile')}: ${this.state.selectedFile.name}`}

)} From 85a9328b01a8741b851419710ce00ab852218f92 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sun, 8 Dec 2024 15:45:06 -0500 Subject: [PATCH 11/44] Add correct type, remove progress tracking --- public/locales/en/files.json | 2 +- src/bundles/files/actions.js | 43 ++++++----------------------------- src/bundles/files/consts.js | 2 ++ src/bundles/files/protocol.ts | 2 ++ 4 files changed, 12 insertions(+), 37 deletions(-) diff --git a/public/locales/en/files.json b/public/locales/en/files.json index 2d849ebe3..20db42bf5 100644 --- a/public/locales/en/files.json +++ b/public/locales/en/files.json @@ -64,7 +64,7 @@ "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:", + "selectedFile": "Selected file", "invalidCids": "*Invalid CID(s) found", "failedToReadFile": "*Failed to read file contents" }, diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index 43f9a7d95..ab7a0d770 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -399,34 +399,19 @@ const actions = () => ({ }), /** - * Reads a CSV file containing CIDs and adds each one to IPFS at the given root path. - * @param {FileStream[]} source - The CSV file containing CIDs + * 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) => spawn(ACTIONS.ADD_BY_PATH, async function * (ipfs, { store }) { + doFilesBulkCidImport: (source, root) => perform(ACTIONS.BULK_CID_IMPORT, async function (ipfs, { store }) { ensureMFS(store) - // Ensure source is properly passed - if (!source) { - throw new Error('Source is required') + if (!source?.[0]?.content) { + console.error('Invalid file format provided to doFilesBulkCidImport') + return } - // If source is passed as first argument of spawn callback - const actualSource = Array.isArray(source) ? source : arguments[0] - console.log('Actual source:', actualSource) - - if (!Array.isArray(actualSource)) { - throw new Error('Source must be an array') - } - - const fileStream = actualSource[0] - console.log('fileStream:', fileStream) - - if (!fileStream || !fileStream.content) { - throw new Error('Invalid file format') - } - - const file = fileStream + const file = source[0] const content = await new Response(file.content).text() const lines = content.split('\n').map(line => line.trim()).filter(Boolean) @@ -445,33 +430,19 @@ const actions = () => ({ } }) - /** @type {Array<{ path: string, cid: string }>} */ - const entries = [] - let progress = 0 - const totalCids = cidObjects.length - - yield { entries, progress: 0 } - 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) - - entries.push({ path: dst, cid }) - progress = (entries.length / totalCids) * 100 - - yield { entries, progress } } catch (err) { console.error(`Failed to add CID ${cid}:`, err) // Continue with next CID even if one fails } } - yield { entries, progress: 100 } await store.doFilesFetch() - return entries }), /** 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> From d343c881ac89aa8b29c974a3adf57784f79ff840 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sun, 8 Dec 2024 22:03:16 -0500 Subject: [PATCH 12/44] Refactor code --- src/bundles/files/actions.js | 56 +++++++++---------- .../bulk-import-modal/BulkImportModal.js | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index ab7a0d770..7381ff92b 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -411,38 +411,38 @@ const actions = () => ({ return } - const file = source[0] - const content = await new Response(file.content).text() - - const lines = content.split('\n').map(line => line.trim()).filter(Boolean) - // NOTE: need to add finally fetch later - 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 - } - }) + 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)) + 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) - // Continue with next CID even if one fails + await ipfs.files.cp(src, dst) + } catch (err) { + console.error(`Failed to add CID ${cid}:`, err) + } } + } finally { + await store.doFilesFetch() } - - await store.doFilesFetch() }), /** diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.js b/src/files/modals/bulk-import-modal/BulkImportModal.js index edb94d797..3c3b663a5 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.js +++ b/src/files/modals/bulk-import-modal/BulkImportModal.js @@ -69,7 +69,7 @@ class BulkImportModal extends React.Component { render () { const { - t, tReady, onCancel, onSubmit, className, ...props + t, tReady, onCancel, className, onBulkCidImport, ...props } = this.props const codeClass = 'w-100 mb1 pa1 tl bg-snow f7 charcoal-muted truncate' From 7b80a5dfdd42e0031bbe2d43ebd8ca89b6733cdf Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sun, 8 Dec 2024 22:40:22 -0500 Subject: [PATCH 13/44] Add storybook file --- .../BulkImportModal.stories.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/files/modals/bulk-import-modal/BulkImportModal.stories.js diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.stories.js b/src/files/modals/bulk-import-modal/BulkImportModal.stories.js new file mode 100644 index 000000000..a13e141a7 --- /dev/null +++ b/src/files/modals/bulk-import-modal/BulkImportModal.stories.js @@ -0,0 +1,21 @@ +import React from 'react' +import { action } from '@storybook/addon-actions' +import i18n from '../../../i18n-decorator.js' +import BulkImportModal from './BulkImportModal.js' + +/** + * @type {import('@storybook/react').Meta} + */ +export default { + title: 'Files/Modals', + decorators: [i18n] +} + +/** + * @type {import('@storybook/react').StoryObj} + */ +export const BulkImport = () => ( +
+ +
+) From 88e787686f482b29595e2a25f7e218531ea5c188 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sun, 8 Dec 2024 23:24:28 -0500 Subject: [PATCH 14/44] Remove comments --- src/files/file-input/FileInput.js | 8 +------- src/files/modals/bulk-import-modal/BulkImportModal.js | 5 ++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js index f56e55c8f..cd41f0623 100644 --- a/src/files/file-input/FileInput.js +++ b/src/files/file-input/FileInput.js @@ -97,13 +97,7 @@ class FileInput extends React.Component { {t('newFolder')} - diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.js b/src/files/modals/bulk-import-modal/BulkImportModal.js index 3c3b663a5..0d872804b 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.js +++ b/src/files/modals/bulk-import-modal/BulkImportModal.js @@ -83,14 +83,13 @@ class BulkImportModal extends React.Component { { this.bulkCidInput = el }} + onChange={this.onChange} /> - - {this.state.selectedFile && ( -

- {`${t('bulkImportModal.selectedFile')}: ${this.state.selectedFile.name}`} -

- )} - - {this.state.validationError && ( -

- {this.state.validationError} -

- )} -
- - - - - -
- ) - } + const isDisabled = !selectedFile + const codeClass = 'w-100 mb1 pa1 tl bg-snow f7 charcoal-muted truncate' + + return ( + + +
+

{t('bulkImportModal.description')}

+ bafkreibm6jg3ux5qumhcn2b3flc3tyu6dmlb4xa7u5bf44yegnrjhc4yeq
QmawceGscqN4o8Y8Fv26UUmB454kn2bnkXV5tEQYc4jBd6 barrel.png
QmbvrHYWXAU1BuxMPNRtfeF4DS2oPmo5hat7ocqAkNPr74 pi equals.png
+
+ + + + + {selectedFile && ( +

+ {`${t('bulkImportModal.selectedFile')}: ${selectedFile.name}`} +

+ )} + + {validationError && ( +

+ {validationError} +

+ )} +
+ + + + + +
+ ) } export default withTranslation('files')(BulkImportModal) From 90a648770ca479e0b2e1737b776d4db6ab26e868 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Fri, 13 Dec 2024 14:58:49 -0500 Subject: [PATCH 18/44] Use useTranslation --- src/files/modals/bulk-import-modal/BulkImportModal.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.js b/src/files/modals/bulk-import-modal/BulkImportModal.js index 04dd375ac..86e5accbe 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.js +++ b/src/files/modals/bulk-import-modal/BulkImportModal.js @@ -1,15 +1,16 @@ import React, { useState, useRef } from 'react' import Button from '../../../components/button/button.tsx' import { Modal, ModalActions, ModalBody } from '../../../components/modal/Modal.js' -import { withTranslation } from 'react-i18next' +import { useTranslation } from 'react-i18next' import * as isIPFS from 'is-ipfs' import Icon from '../../../icons/StrokeDocument.js' import { normalizeFiles } from '../../../lib/files.js' -const BulkImportModal = ({ t, tReady, onCancel, className = '', onBulkCidImport, ...props }) => { +const BulkImportModal = ({ onCancel, className = '', onBulkCidImport, ...props }) => { const [selectedFile, setSelectedFile] = useState(null) const [validationError, setValidationError] = useState(null) const bulkCidInputRef = useRef(null) + const { t } = useTranslation('files') const validateFileContents = async (file) => { try { @@ -99,4 +100,4 @@ const BulkImportModal = ({ t, tReady, onCancel, className = '', onBulkCidImport, ) } -export default withTranslation('files')(BulkImportModal) +export default BulkImportModal From 45eaacb23a43ed96b726a33ce8b4d4c05290e317 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Fri, 13 Dec 2024 15:38:26 -0500 Subject: [PATCH 19/44] Update to TS file --- src/components/modal/Modal.js | 13 ++----------- src/files/modals/Modals.js | 2 +- ...odal.stories.js => bulk-import-modal.stories.js} | 2 +- .../{BulkImportModal.js => bulk-import-modal.tsx} | 11 ++++++----- 4 files changed, 10 insertions(+), 18 deletions(-) rename src/files/modals/bulk-import-modal/{BulkImportModal.stories.js => bulk-import-modal.stories.js} (89%) rename src/files/modals/bulk-import-modal/{BulkImportModal.js => bulk-import-modal.tsx} (91%) 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 }) => (
{ children }
@@ -13,12 +13,7 @@ ModalActions.propTypes = { className: PropTypes.string } -ModalActions.defaultProps = { - justify: 'between', - className: '' -} - -export const ModalBody = ({ className, Icon, title, children, ...props }) => ( +export const ModalBody = ({ className = '', Icon, title, children, ...props }) => (
{ Icon && (
@@ -40,10 +35,6 @@ ModalBody.propTypes = { ]) } -ModalBody.defaultProps = { - className: '' -} - export const Modal = ({ onCancel, children, className, ...props }) => { return (
diff --git a/src/files/modals/Modals.js b/src/files/modals/Modals.js index beb966aed..5ccebac50 100644 --- a/src/files/modals/Modals.js +++ b/src/files/modals/Modals.js @@ -10,7 +10,7 @@ import RenameModal from './rename-modal/RenameModal.js' import PinningModal from './pinning-modal/PinningModal.js' import RemoveModal from './remove-modal/RemoveModal.js' import AddByPathModal from './add-by-path-modal/AddByPathModal.js' -import BulkImportModal from './bulk-import-modal/BulkImportModal.js' +import BulkImportModal from './bulk-import-modal/bulk-import-modal.tsx' import PublishModal from './publish-modal/PublishModal.js' import CliTutorMode from '../../components/cli-tutor-mode/CliTutorMode.js' import { cliCommandList, cliCmdKeys } from '../../bundles/files/consts.js' diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.stories.js b/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js similarity index 89% rename from src/files/modals/bulk-import-modal/BulkImportModal.stories.js rename to src/files/modals/bulk-import-modal/bulk-import-modal.stories.js index a13e141a7..2d3d10eb4 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.stories.js +++ b/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js @@ -1,7 +1,7 @@ import React from 'react' import { action } from '@storybook/addon-actions' import i18n from '../../../i18n-decorator.js' -import BulkImportModal from './BulkImportModal.js' +import BulkImportModal from './bulk-import-modal.js' /** * @type {import('@storybook/react').Meta} diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.js b/src/files/modals/bulk-import-modal/bulk-import-modal.tsx similarity index 91% rename from src/files/modals/bulk-import-modal/BulkImportModal.js rename to src/files/modals/bulk-import-modal/bulk-import-modal.tsx index 86e5accbe..6702a32c3 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.js +++ b/src/files/modals/bulk-import-modal/bulk-import-modal.tsx @@ -7,9 +7,9 @@ import Icon from '../../../icons/StrokeDocument.js' import { normalizeFiles } from '../../../lib/files.js' const BulkImportModal = ({ onCancel, className = '', onBulkCidImport, ...props }) => { - const [selectedFile, setSelectedFile] = useState(null) - const [validationError, setValidationError] = useState(null) - const bulkCidInputRef = useRef(null) + const [selectedFile, setSelectedFile] = useState(null) + const [validationError, setValidationError] = useState(undefined) + const bulkCidInputRef = useRef(null) const { t } = useTranslation('files') const validateFileContents = async (file) => { @@ -40,7 +40,9 @@ const BulkImportModal = ({ onCancel, className = '', onBulkCidImport, ...props } } const selectFile = () => { - bulkCidInputRef.current.click() + if (bulkCidInputRef.current) { + bulkCidInputRef.current.click() + } } const onSubmit = async () => { @@ -74,7 +76,6 @@ const BulkImportModal = ({ onCancel, className = '', onBulkCidImport, ...props } onClick={selectFile} className='ma2 tc' bg='bg-teal' - type='button' > {t('bulkImportModal.select')} From fb30c308fcc998e2fb3392509d256d4394947d7e Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Fri, 13 Dec 2024 17:27:53 -0500 Subject: [PATCH 20/44] Update import path --- src/files/modals/bulk-import-modal/bulk-import-modal.stories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js b/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js index 2d3d10eb4..17d585019 100644 --- a/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js +++ b/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js @@ -1,7 +1,7 @@ import React from 'react' import { action } from '@storybook/addon-actions' import i18n from '../../../i18n-decorator.js' -import BulkImportModal from './bulk-import-modal.js' +import BulkImportModal from './bulk-import-modal.tsx' /** * @type {import('@storybook/react').Meta} From 0e00a85e83771a201080c66f353aa03e73d28815 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Fri, 29 Nov 2024 22:11:43 +0100 Subject: [PATCH 21/44] fix(explore): browsing chunked files and inspecting via context menu (#2305) * fix(explore): chunked files This includes latest ipld-explorer-components with fix from https://github.com/ipfs/ipld-explorer-components/issues/462 also bumped kubo and caniuse and non-breaking audit suggestions * fix(files): Inspect via context menu Closes #2306 --- package-lock.json | 82 +++++++++++++++++++++--------------------- package.json | 4 +-- src/files/FilesPage.js | 2 +- test/e2e/files.test.js | 33 +++++++++++++++++ 4 files changed, 77 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd68ec2c7..c28dea85e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "ipfs-css": "^1.4.0", "ipfs-geoip": "^9.1.0", "ipfs-provider": "^2.1.0", - "ipld-explorer-components": "^8.1.0", + "ipld-explorer-components": "^8.1.2", "is-ipfs": "^8.0.1", "istextorbinary": "^6.0.0", "it-all": "^1.0.5", @@ -135,7 +135,7 @@ "ipfsd-ctl": "^14.1.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "kubo": "^0.30.0", + "kubo": "^0.32.1", "npm-run-all": "^4.1.5", "nyc": "^15.1.0", "os-browserify": "^0.3.0", @@ -17891,6 +17891,16 @@ "@types/json-schema": "*" } }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/esm": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/esm/-/esm-3.2.2.tgz", @@ -27681,9 +27691,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001676", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz", - "integrity": "sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==", + "version": "1.0.30001684", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", + "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", "funding": [ { "type": "opencollective", @@ -27697,8 +27707,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/capital-case": { "version": "1.0.4", @@ -30034,9 +30043,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -41688,9 +41697,9 @@ } }, "node_modules/ipld-explorer-components": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ipld-explorer-components/-/ipld-explorer-components-8.1.0.tgz", - "integrity": "sha512-NtpIX1zFTol/IZUVlWQPc9RNGpBKZv4dxTMW3HUiEkshg134noA8cLYTA+ntaxQShlYZKcAZxZSZDIvwMQN1bQ==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/ipld-explorer-components/-/ipld-explorer-components-8.1.2.tgz", + "integrity": "sha512-dCUMTw/z28GGrrW3QkwD3inQ5Mg+BGJjGvfh9m/QSAibsHV3vBJuBb7Q1qp/BsvnktbCfLMgM8Km+K+F9E8tPQ==", "license": "Apache-2.0 OR MIT", "dependencies": { "@helia/block-brokers": "^4.0.0", @@ -47697,9 +47706,9 @@ } }, "node_modules/kubo": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/kubo/-/kubo-0.30.0.tgz", - "integrity": "sha512-wrHaNtlyn59SQmkIiXSP3VAwebqImNkCuhTDJ70lSDXr3Rd9av8s2YzYblSHShtiEC5ylC0pofwDNPP8+F2yiw==", + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/kubo/-/kubo-0.32.1.tgz", + "integrity": "sha512-vXTprOeWACNpU86ldCBSBMRgq4MahLIr/YBn6DBb0SvHHVe4ZD8Djma6ncfVmxzrWxEiIMq9XOxJcQ27UOZ6kw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -53521,9 +53530,9 @@ "license": "MIT" }, "node_modules/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "license": "MIT", "dependencies": { @@ -57889,9 +57898,9 @@ } }, "node_modules/patch-package/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "license": "MIT", "dependencies": { @@ -66805,9 +66814,9 @@ } }, "node_modules/sane/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "license": "MIT", "dependencies": { @@ -74118,18 +74127,18 @@ } }, "node_modules/webpack": { - "version": "5.95.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", - "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", + "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.5", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -74550,15 +74559,6 @@ "node": ">=0.4.0" } }, - "node_modules/webpack/node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", diff --git a/package.json b/package.json index 6a721e48b..d5195c0cf 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "ipfs-css": "^1.4.0", "ipfs-geoip": "^9.1.0", "ipfs-provider": "^2.1.0", - "ipld-explorer-components": "^8.1.0", + "ipld-explorer-components": "^8.1.2", "is-ipfs": "^8.0.1", "istextorbinary": "^6.0.0", "it-all": "^1.0.5", @@ -165,7 +165,7 @@ "ipfsd-ctl": "^14.1.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "kubo": "^0.30.0", + "kubo": "^0.32.1", "npm-run-all": "^4.1.5", "nyc": "^15.1.0", "os-browserify": "^0.3.0", diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js index 1692ec486..d9a837330 100644 --- a/src/files/FilesPage.js +++ b/src/files/FilesPage.js @@ -73,7 +73,7 @@ const FilesPage = ({ } const onAddByPath = (path, name) => doFilesAddPath(files.path, path, name) - const onInspect = (cid) => doUpdateHash(`/explore/ipfs/${cid}`) + const onInspect = (cid) => doUpdateHash(`/explore/${cid}`) const showModal = (modal, files = null) => setModals({ show: modal, files }) const hideModal = () => setModals({}) const handleContextMenu = (ev, clickType, file, pos) => { diff --git a/test/e2e/files.test.js b/test/e2e/files.test.js index b2566a550..9ea7c9fbc 100644 --- a/test/e2e/files.test.js +++ b/test/e2e/files.test.js @@ -62,4 +62,37 @@ test.describe('Files screen', () => { await page.waitForSelector(`text=${human(file.size)}`) } }) + + test('should have active Context menu that allows Inspection of the DAG', async ({ page }) => { + // dedicated test file to make this isolated from the rest + const testFilename = 'explorer-context-menu-test.txt' + const testCid = 'bafkqaddjnzzxazldoqwxizltoq' + + // first: create a test file + const button = 'button[id="import-button"]' + await page.waitForSelector(button, { state: 'visible' }) + await page.click(button) + await page.waitForSelector('#add-by-path', { state: 'visible' }) + page.click('button[id="add-by-path"]') + await page.waitForSelector('div[role="dialog"] input[name="name"]') + await page.fill('div[role="dialog"] input[name="path"]', `/ipfs/${testCid}`) + await page.fill('div[role="dialog"] input[name="name"]', testFilename) + await page.keyboard.press('Enter') + // expect file with matching filename to be added to the file list + await page.waitForSelector(`.File:has-text("${testFilename}")`) + + // open context menu + const cbutton = `button[aria-label="View more options for ${testFilename}"]` + await page.waitForSelector(cbutton, { state: 'visible' }) + await page.click(cbutton, { force: true }) + await page.waitForSelector('text=Inspect', { state: 'visible' }) + + // click on Inspect option + await page.waitForSelector('text=Inspect', { state: 'visible' }) + await page.locator('button:has-text("Inspect"):enabled').click({ force: true }) + + // confirm Explore screen was opened with correct CID + await page.waitForURL(`/#/explore/${testCid}`) + await page.waitForSelector('text="CID info"') + }) }) From 48b55fd6f1ab29bc3f477dee0912f09b109c8055 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Sat, 30 Nov 2024 00:59:39 +0100 Subject: [PATCH 22/44] chore(ci): set cluster pin timeout to 30m https://github.com/ipfs/ipfs-webui/actions/workflows/ci.yml?page=4&query=is%3Asuccess are usually under 10-20 minutes if something takes longer it will likely take ages and then fail, so better to fail faster, allowing user to retry release --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0fbc0616..bde6197ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,6 +135,7 @@ jobs: CLUSTER_USER: ${{ secrets.CLUSTER_USER }} CLUSTER_PASSWORD: ${{ secrets.CLUSTER_PASSWORD }} PIN_NAME: "ipfs-webui@${{ github.sha }}" + timeout-minutes: 15 - name: Fail job due to pinning failure # only fail if pinning failed for main commits From 7177f71b9277ec275a3359a6dc1ef646808be31e Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Sat, 30 Nov 2024 02:16:54 +0100 Subject: [PATCH 23/44] chore(ci): use repo in offline mode no need to start node and open outgoing connections github CI may be punishing us by throttling egress --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bde6197ae..e10177277 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,8 +70,7 @@ jobs: run: | # fix resolv - DNS provided by Github is unreliable for DNSLik/dnsaddr sudo sed -i -e 's/nameserver 127.0.0.*/nameserver 1.1.1.1/g' /etc/resolv.conf - - - uses: ipfs/start-ipfs-daemon-action@v1 + - run: ipfs init - name: Import build/ to IPFS id: ipfs run: | From 991f019318ef93c2d51f4a596667b1ddc3ed03f4 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sat, 30 Nov 2024 14:08:55 -0500 Subject: [PATCH 24/44] Get functional MVP working with csv file --- src/bundles/files/actions.js | 48 +++++++++++++++++++++++++++++++ src/files/FilesPage.js | 9 +++++- src/files/file-input/FileInput.js | 30 +++++++++++++++++-- src/files/header/Header.js | 1 + 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index 55b80cfa5..73c506429 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -399,6 +399,54 @@ const actions = () => ({ } }), + /** + * Reads a CSV file containing CIDs and adds each one to IPFS at the given root path. + * @param {FileStream[]} source - The CSV file containing CIDs + * @param {string} root - Destination directory in IPFS + */ + doFilesAddBulkCid: (source, root) => spawn(ACTIONS.ADD_BY_PATH, async function * (ipfs, { store }) { + ensureMFS(store) + + if (source.length !== 1) { + throw new Error('Please provide exactly one CSV file') + } + + // Read the CSV file content + const file = source[0] + const content = await new Response(file.content).text() + + // Split content into CIDs (assuming one CID per line, comma-separated) + const cids = content.split(/[\n,]/).map(cid => cid.trim()).filter(Boolean) + + /** @type {Array<{ path: string, cid: string }>} */ + const entries = [] + let progress = 0 + const totalCids = cids.length + + yield { entries, progress: 0 } + + for (const cid of cids) { + try { + const src = `/ipfs/${cid}` + const dst = realMfsPath(join(root || '/files', cid)) + + await ipfs.files.cp(src, dst) + + entries.push({ path: dst, cid }) + progress = (entries.length / totalCids) * 100 + + yield { entries, progress } + } catch (err) { + console.error(`Failed to add CID ${cid}:`, err) + // Continue with next CID even if one fails + } + } + + yield { entries, progress: 100 } + await store.doFilesFetch() + return entries + }), + /** * Creates a download link for the provided files. * @param {FileStat[]} files diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js index d9a837330..ccad9f994 100644 --- a/src/files/FilesPage.js +++ b/src/files/FilesPage.js @@ -21,7 +21,7 @@ import FileImportStatus from './file-import-status/FileImportStatus.js' import { useExplore } from 'ipld-explorer-components/providers' const FilesPage = ({ - doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesAddPath, doUpdateHash, + doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesAddBulkCid, doFilesAddPath, doUpdateHash, doFilesUpdateSorting, doFilesNavigateTo, doFilesMove, doSetCliOptions, doFetchRemotePins, remotePins, pendingPins, failedPins, ipfsProvider, ipfsConnected, doFilesMakeDir, doFilesShareLink, doFilesDelete, doSetPinning, onRemotePinClick, doPublishIpnsKey, files, filesPathInfo, pinningServices, toursEnabled, handleJoyrideCallback, isCliTutorModeEnabled, cliOptions, t @@ -72,6 +72,11 @@ const FilesPage = ({ doFilesWrite(raw, root) } + const onAddBulkCid = (raw, root = '') => { + if (root === '') root = files.path + doFilesAddBulkCid(raw, root) + } + const onAddByPath = (path, name) => doFilesAddPath(files.path, path, name) const onInspect = (cid) => doUpdateHash(`/explore/${cid}`) const showModal = (modal, files = null) => setModals({ show: modal, files }) @@ -204,6 +209,7 @@ const FilesPage = ({ files={files} onNavigate={doFilesNavigateTo} onAddFiles={onAddFiles} + onAddBulkCid={onAddBulkCid} onMove={doFilesMove} onAddByPath={(files) => showModal(ADD_BY_PATH, files)} onNewFolder={(files) => showModal(NEW_FOLDER, files)} @@ -277,6 +283,7 @@ export default connect( 'selectFilesSorting', 'selectToursEnabled', 'doFilesWrite', + 'doFilesAddBulkCid', 'doFilesDownloadLink', 'doFilesDownloadCarLink', 'doFilesSizeGet', diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js index ec9bfb6e6..6a1ba964f 100644 --- a/src/files/file-input/FileInput.js +++ b/src/files/file-input/FileInput.js @@ -40,11 +40,21 @@ class FileInput extends React.Component { return this.filesInput.click() } + onAddBulkCid = async () => { + this.toggleDropdown() + return this.bulkCidInput.click() + } + onInputChange = (input) => async () => { this.props.onAddFiles(normalizeFiles(input.files)) input.value = null } + onBulkCidInputChange = (input) => async () => { + this.props.onAddBulkCid(normalizeFiles(input.files)) + input.value = null + } + onAddByPath = () => { this.props.onAddByPath() this.toggleDropdown() @@ -92,8 +102,12 @@ class FileInput extends React.Component { {t('newFolder')} - @@ -116,6 +130,15 @@ class FileInput extends React.Component { webkitdirectory='true' ref={el => { this.folderInput = el }} onChange={this.onInputChange(this.folderInput)} /> + + { this.bulkCidInput = el }} + onChange={this.onBulkCidInputChange(this.bulkCidInput)} />
) } @@ -125,7 +148,8 @@ FileInput.propTypes = { t: PropTypes.func.isRequired, onAddFiles: PropTypes.func.isRequired, onAddByPath: PropTypes.func.isRequired, - onNewFolder: PropTypes.func.isRequired + onNewFolder: PropTypes.func.isRequired, + onAddBulkCid: PropTypes.func.isRequired } export default connect( diff --git a/src/files/header/Header.js b/src/files/header/Header.js index ce9142109..a1df8f537 100644 --- a/src/files/header/Header.js +++ b/src/files/header/Header.js @@ -93,6 +93,7 @@ class Header extends React.Component { onNewFolder={this.props.onNewFolder} onAddFiles={this.props.onAddFiles} onAddByPath={this.props.onAddByPath} + onAddBulkCid={this.props.onAddBulkCid} onCliTutorMode={this.props.onCliTutorMode} /> :
{ this.dotsWrapper = el }}> From 21b0aa10f56484eaa729540960951090b029a878 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sat, 30 Nov 2024 20:03:56 -0500 Subject: [PATCH 25/44] Get it working with txt file --- src/bundles/files/actions.js | 27 ++++++++++++++++++++------- src/files/file-input/FileInput.js | 2 +- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index 73c506429..c8e8d5520 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -408,27 +408,40 @@ const actions = () => ({ ensureMFS(store) if (source.length !== 1) { - throw new Error('Please provide exactly one CSV file') + throw new Error('Please provide exactly one text file') } - // Read the CSV file content + // Read the text file content const file = source[0] const content = await new Response(file.content).text() - // Split content into CIDs (assuming one CID per line, comma-separated) - const cids = content.split(/[\n,]/).map(cid => cid.trim()).filter(Boolean) + const lines = content.split('\n').map(line => line.trim()).filter(Boolean) + // NOTE: need to add finally fetch later + 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 + } + }) /** @type {Array<{ path: string, cid: string }>} */ const entries = [] let progress = 0 - const totalCids = cids.length + const totalCids = cidObjects.length yield { entries, progress: 0 } - for (const cid of cids) { + for (const { cid, name } of cidObjects) { try { const src = `/ipfs/${cid}` - const dst = realMfsPath(join(root || '/files', cid)) + const dst = realMfsPath(join(root || '/files', name || cid)) await ipfs.files.cp(src, dst) diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js index 6a1ba964f..8092cc03f 100644 --- a/src/files/file-input/FileInput.js +++ b/src/files/file-input/FileInput.js @@ -136,7 +136,7 @@ class FileInput extends React.Component { type='file' className='dn' multiple - accept='.csv' + accept='.txt' ref={el => { this.bulkCidInput = el }} onChange={this.onBulkCidInputChange(this.bulkCidInput)} />
From febbc95d336036a10d507fca3b7a65350935fc87 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Fri, 6 Dec 2024 14:38:09 -0500 Subject: [PATCH 26/44] WIP: bulk import modal UX --- src/files/FilesPage.js | 9 +- src/files/file-input/FileInput.js | 14 +- src/files/header/Header.js | 2 +- src/files/modals/Modals.js | 18 ++ .../bulk-import-modal/BulkImportModal.js | 185 ++++++++++++++++++ 5 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 src/files/modals/bulk-import-modal/BulkImportModal.js diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js index ccad9f994..e36cf8406 100644 --- a/src/files/FilesPage.js +++ b/src/files/FilesPage.js @@ -15,7 +15,7 @@ import FilesList from './files-list/FilesList.js' import { getJoyrideLocales } from '../helpers/i8n.js' // Icons -import Modals, { DELETE, NEW_FOLDER, SHARE, RENAME, ADD_BY_PATH, CLI_TUTOR_MODE, PINNING, PUBLISH } from './modals/Modals.js' +import Modals, { DELETE, NEW_FOLDER, SHARE, RENAME, ADD_BY_PATH, BULK_CID_IMPORT, CLI_TUTOR_MODE, PINNING, PUBLISH } from './modals/Modals.js' import Header from './header/Header.js' import FileImportStatus from './file-import-status/FileImportStatus.js' import { useExplore } from 'ipld-explorer-components/providers' @@ -72,7 +72,7 @@ const FilesPage = ({ doFilesWrite(raw, root) } - const onAddBulkCid = (raw, root = '') => { + const onBulkCidImport = (raw, root = '') => { if (root === '') root = files.path doFilesAddBulkCid(raw, root) } @@ -209,9 +209,11 @@ const FilesPage = ({ files={files} onNavigate={doFilesNavigateTo} onAddFiles={onAddFiles} - onAddBulkCid={onAddBulkCid} + // onAddBulkCid={onAddBulkCid} + // onBulkCidImport={onBulkCidImport} onMove={doFilesMove} onAddByPath={(files) => showModal(ADD_BY_PATH, files)} + onBulkCidImport={(files) => showModal(BULK_CID_IMPORT, files)} onNewFolder={(files) => showModal(NEW_FOLDER, files)} onCliTutorMode={() => showModal(CLI_TUTOR_MODE)} handleContextMenu={(...args) => handleContextMenu(...args, true)} /> @@ -232,6 +234,7 @@ const FilesPage = ({ onShareLink={doFilesShareLink} onRemove={doFilesDelete} onAddByPath={onAddByPath} + onBulkCidImport={onBulkCidImport} onPinningSet={doSetPinning} onPublish={doPublishIpnsKey} cliOptions={cliOptions} diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js index 8092cc03f..7b6cf6650 100644 --- a/src/files/file-input/FileInput.js +++ b/src/files/file-input/FileInput.js @@ -51,7 +51,8 @@ class FileInput extends React.Component { } onBulkCidInputChange = (input) => async () => { - this.props.onAddBulkCid(normalizeFiles(input.files)) + console.log('onBulkCidInputChange', input) + this.props.onBulkCidImport(normalizeFiles(input.files)) input.value = null } @@ -60,6 +61,11 @@ class FileInput extends React.Component { this.toggleDropdown() } + onBulkCidImport = () => { + this.props.onBulkCidImport() + this.toggleDropdown() + } + onNewFolder = () => { this.props.onNewFolder() this.toggleDropdown() @@ -102,7 +108,9 @@ class FileInput extends React.Component { {t('newFolder')} - diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.js b/src/files/modals/bulk-import-modal/BulkImportModal.js index 25dc03172..edb94d797 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.js +++ b/src/files/modals/bulk-import-modal/BulkImportModal.js @@ -31,13 +31,13 @@ class BulkImportModal extends React.Component { for (const line of lines) { const [cid] = line.trim().split(' ') if (!isIPFS.cid(cid)) { - return { isValid: false, error: '*Invalid CID(s) found' } + return { isValid: false, error: this.props.t('bulkImportModal.invalidCids') } } } return { isValid: true } } catch (err) { - return { isValid: false, error: '*Failed to read file contents' } + return { isValid: false, error: this.props.t('bulkImportModal.failedToReadFile') } } } @@ -53,7 +53,7 @@ class BulkImportModal extends React.Component { } selectFile = async () => { - return this.bulkCidInputt.click() + return this.bulkCidInput.click() } onSubmit = async () => { @@ -76,9 +76,9 @@ class BulkImportModal extends React.Component { return ( - +
-

{'Upload a text file with a list of CIDs (names are optional).' + ' ' + 'Example:'}

+

{t('bulkImportModal.description')}

bafkreibm6jg3ux5qumhcn2b3flc3tyu6dmlb4xa7u5bf44yegnrjhc4yeq
QmawceGscqN4o8Y8Fv26UUmB454kn2bnkXV5tEQYc4jBd6 barrel.png
QmbvrHYWXAU1BuxMPNRtfeF4DS2oPmo5hat7ocqAkNPr74 pi equals.png
@@ -90,10 +90,7 @@ class BulkImportModal extends React.Component { onChange={this.onChange} // className='input-reset' // id='bulk-import' - ref={el => { - console.log('Setting ref:', el) - this.bulkCidInputt = el - }} + ref={el => { this.bulkCidInput = el }} /> {this.state.selectedFile && (

- Selected file: {this.state.selectedFile.name} + {`${t('bulkImportModal.selectedFile')}: ${this.state.selectedFile.name}`}

)} From 28e3725785a88ce237ba265d783e410228cf9388 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sun, 8 Dec 2024 15:45:06 -0500 Subject: [PATCH 33/44] Add correct type, remove progress tracking --- public/locales/en/files.json | 2 +- src/bundles/files/actions.js | 43 ++++++----------------------------- src/bundles/files/consts.js | 2 ++ src/bundles/files/protocol.ts | 2 ++ 4 files changed, 12 insertions(+), 37 deletions(-) diff --git a/public/locales/en/files.json b/public/locales/en/files.json index 2d849ebe3..20db42bf5 100644 --- a/public/locales/en/files.json +++ b/public/locales/en/files.json @@ -64,7 +64,7 @@ "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:", + "selectedFile": "Selected file", "invalidCids": "*Invalid CID(s) found", "failedToReadFile": "*Failed to read file contents" }, diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index 43f9a7d95..ab7a0d770 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -399,34 +399,19 @@ const actions = () => ({ }), /** - * Reads a CSV file containing CIDs and adds each one to IPFS at the given root path. - * @param {FileStream[]} source - The CSV file containing CIDs + * 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) => spawn(ACTIONS.ADD_BY_PATH, async function * (ipfs, { store }) { + doFilesBulkCidImport: (source, root) => perform(ACTIONS.BULK_CID_IMPORT, async function (ipfs, { store }) { ensureMFS(store) - // Ensure source is properly passed - if (!source) { - throw new Error('Source is required') + if (!source?.[0]?.content) { + console.error('Invalid file format provided to doFilesBulkCidImport') + return } - // If source is passed as first argument of spawn callback - const actualSource = Array.isArray(source) ? source : arguments[0] - console.log('Actual source:', actualSource) - - if (!Array.isArray(actualSource)) { - throw new Error('Source must be an array') - } - - const fileStream = actualSource[0] - console.log('fileStream:', fileStream) - - if (!fileStream || !fileStream.content) { - throw new Error('Invalid file format') - } - - const file = fileStream + const file = source[0] const content = await new Response(file.content).text() const lines = content.split('\n').map(line => line.trim()).filter(Boolean) @@ -445,33 +430,19 @@ const actions = () => ({ } }) - /** @type {Array<{ path: string, cid: string }>} */ - const entries = [] - let progress = 0 - const totalCids = cidObjects.length - - yield { entries, progress: 0 } - 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) - - entries.push({ path: dst, cid }) - progress = (entries.length / totalCids) * 100 - - yield { entries, progress } } catch (err) { console.error(`Failed to add CID ${cid}:`, err) // Continue with next CID even if one fails } } - yield { entries, progress: 100 } await store.doFilesFetch() - return entries }), /** 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> From 38ef8dfd159fafce3d023df01435095722e7ce4c Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sun, 8 Dec 2024 22:03:16 -0500 Subject: [PATCH 34/44] Refactor code --- src/bundles/files/actions.js | 56 +++++++++---------- .../bulk-import-modal/BulkImportModal.js | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index ab7a0d770..7381ff92b 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -411,38 +411,38 @@ const actions = () => ({ return } - const file = source[0] - const content = await new Response(file.content).text() - - const lines = content.split('\n').map(line => line.trim()).filter(Boolean) - // NOTE: need to add finally fetch later - 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 - } - }) + 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)) + 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) - // Continue with next CID even if one fails + await ipfs.files.cp(src, dst) + } catch (err) { + console.error(`Failed to add CID ${cid}:`, err) + } } + } finally { + await store.doFilesFetch() } - - await store.doFilesFetch() }), /** diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.js b/src/files/modals/bulk-import-modal/BulkImportModal.js index edb94d797..3c3b663a5 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.js +++ b/src/files/modals/bulk-import-modal/BulkImportModal.js @@ -69,7 +69,7 @@ class BulkImportModal extends React.Component { render () { const { - t, tReady, onCancel, onSubmit, className, ...props + t, tReady, onCancel, className, onBulkCidImport, ...props } = this.props const codeClass = 'w-100 mb1 pa1 tl bg-snow f7 charcoal-muted truncate' From 0b4b84a869f85f861a922ec0f03119ea6f564b70 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sun, 8 Dec 2024 22:40:22 -0500 Subject: [PATCH 35/44] Add storybook file --- .../BulkImportModal.stories.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/files/modals/bulk-import-modal/BulkImportModal.stories.js diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.stories.js b/src/files/modals/bulk-import-modal/BulkImportModal.stories.js new file mode 100644 index 000000000..a13e141a7 --- /dev/null +++ b/src/files/modals/bulk-import-modal/BulkImportModal.stories.js @@ -0,0 +1,21 @@ +import React from 'react' +import { action } from '@storybook/addon-actions' +import i18n from '../../../i18n-decorator.js' +import BulkImportModal from './BulkImportModal.js' + +/** + * @type {import('@storybook/react').Meta} + */ +export default { + title: 'Files/Modals', + decorators: [i18n] +} + +/** + * @type {import('@storybook/react').StoryObj} + */ +export const BulkImport = () => ( +
+ +
+) From 275263428bfa0615e86ab48611e0849ace6f00c7 Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Sun, 8 Dec 2024 23:24:28 -0500 Subject: [PATCH 36/44] Remove comments --- src/files/file-input/FileInput.js | 8 +------- src/files/modals/bulk-import-modal/BulkImportModal.js | 5 ++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js index f56e55c8f..cd41f0623 100644 --- a/src/files/file-input/FileInput.js +++ b/src/files/file-input/FileInput.js @@ -97,13 +97,7 @@ class FileInput extends React.Component { {t('newFolder')} - diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.js b/src/files/modals/bulk-import-modal/BulkImportModal.js index 3c3b663a5..0d872804b 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.js +++ b/src/files/modals/bulk-import-modal/BulkImportModal.js @@ -83,14 +83,13 @@ class BulkImportModal extends React.Component {
{ this.bulkCidInput = el }} + onChange={this.onChange} /> - - {this.state.selectedFile && ( -

- {`${t('bulkImportModal.selectedFile')}: ${this.state.selectedFile.name}`} -

- )} - - {this.state.validationError && ( -

- {this.state.validationError} -

- )} - - - - - - - - ) - } + const isDisabled = !selectedFile + const codeClass = 'w-100 mb1 pa1 tl bg-snow f7 charcoal-muted truncate' + + return ( + + +
+

{t('bulkImportModal.description')}

+ bafkreibm6jg3ux5qumhcn2b3flc3tyu6dmlb4xa7u5bf44yegnrjhc4yeq
QmawceGscqN4o8Y8Fv26UUmB454kn2bnkXV5tEQYc4jBd6 barrel.png
QmbvrHYWXAU1BuxMPNRtfeF4DS2oPmo5hat7ocqAkNPr74 pi equals.png
+
+ + + + + {selectedFile && ( +

+ {`${t('bulkImportModal.selectedFile')}: ${selectedFile.name}`} +

+ )} + + {validationError && ( +

+ {validationError} +

+ )} +
+ + + + + +
+ ) } export default withTranslation('files')(BulkImportModal) From 4d52a3c8a6bc6edb7db13b225ad8d986dcd9106d Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Fri, 13 Dec 2024 14:58:49 -0500 Subject: [PATCH 42/44] Use useTranslation --- src/files/modals/bulk-import-modal/BulkImportModal.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.js b/src/files/modals/bulk-import-modal/BulkImportModal.js index 04dd375ac..86e5accbe 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.js +++ b/src/files/modals/bulk-import-modal/BulkImportModal.js @@ -1,15 +1,16 @@ import React, { useState, useRef } from 'react' import Button from '../../../components/button/button.tsx' import { Modal, ModalActions, ModalBody } from '../../../components/modal/Modal.js' -import { withTranslation } from 'react-i18next' +import { useTranslation } from 'react-i18next' import * as isIPFS from 'is-ipfs' import Icon from '../../../icons/StrokeDocument.js' import { normalizeFiles } from '../../../lib/files.js' -const BulkImportModal = ({ t, tReady, onCancel, className = '', onBulkCidImport, ...props }) => { +const BulkImportModal = ({ onCancel, className = '', onBulkCidImport, ...props }) => { const [selectedFile, setSelectedFile] = useState(null) const [validationError, setValidationError] = useState(null) const bulkCidInputRef = useRef(null) + const { t } = useTranslation('files') const validateFileContents = async (file) => { try { @@ -99,4 +100,4 @@ const BulkImportModal = ({ t, tReady, onCancel, className = '', onBulkCidImport, ) } -export default withTranslation('files')(BulkImportModal) +export default BulkImportModal From eafec89f332322dccab0c033f8536060a21cee7e Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Fri, 13 Dec 2024 15:38:26 -0500 Subject: [PATCH 43/44] Update to TS file --- src/components/modal/Modal.js | 13 ++----------- src/files/modals/Modals.js | 2 +- ...odal.stories.js => bulk-import-modal.stories.js} | 2 +- .../{BulkImportModal.js => bulk-import-modal.tsx} | 11 ++++++----- 4 files changed, 10 insertions(+), 18 deletions(-) rename src/files/modals/bulk-import-modal/{BulkImportModal.stories.js => bulk-import-modal.stories.js} (89%) rename src/files/modals/bulk-import-modal/{BulkImportModal.js => bulk-import-modal.tsx} (91%) 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 }) => (
{ children }
@@ -13,12 +13,7 @@ ModalActions.propTypes = { className: PropTypes.string } -ModalActions.defaultProps = { - justify: 'between', - className: '' -} - -export const ModalBody = ({ className, Icon, title, children, ...props }) => ( +export const ModalBody = ({ className = '', Icon, title, children, ...props }) => (
{ Icon && (
@@ -40,10 +35,6 @@ ModalBody.propTypes = { ]) } -ModalBody.defaultProps = { - className: '' -} - export const Modal = ({ onCancel, children, className, ...props }) => { return (
diff --git a/src/files/modals/Modals.js b/src/files/modals/Modals.js index beb966aed..5ccebac50 100644 --- a/src/files/modals/Modals.js +++ b/src/files/modals/Modals.js @@ -10,7 +10,7 @@ import RenameModal from './rename-modal/RenameModal.js' import PinningModal from './pinning-modal/PinningModal.js' import RemoveModal from './remove-modal/RemoveModal.js' import AddByPathModal from './add-by-path-modal/AddByPathModal.js' -import BulkImportModal from './bulk-import-modal/BulkImportModal.js' +import BulkImportModal from './bulk-import-modal/bulk-import-modal.tsx' import PublishModal from './publish-modal/PublishModal.js' import CliTutorMode from '../../components/cli-tutor-mode/CliTutorMode.js' import { cliCommandList, cliCmdKeys } from '../../bundles/files/consts.js' diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.stories.js b/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js similarity index 89% rename from src/files/modals/bulk-import-modal/BulkImportModal.stories.js rename to src/files/modals/bulk-import-modal/bulk-import-modal.stories.js index a13e141a7..2d3d10eb4 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.stories.js +++ b/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js @@ -1,7 +1,7 @@ import React from 'react' import { action } from '@storybook/addon-actions' import i18n from '../../../i18n-decorator.js' -import BulkImportModal from './BulkImportModal.js' +import BulkImportModal from './bulk-import-modal.js' /** * @type {import('@storybook/react').Meta} diff --git a/src/files/modals/bulk-import-modal/BulkImportModal.js b/src/files/modals/bulk-import-modal/bulk-import-modal.tsx similarity index 91% rename from src/files/modals/bulk-import-modal/BulkImportModal.js rename to src/files/modals/bulk-import-modal/bulk-import-modal.tsx index 86e5accbe..6702a32c3 100644 --- a/src/files/modals/bulk-import-modal/BulkImportModal.js +++ b/src/files/modals/bulk-import-modal/bulk-import-modal.tsx @@ -7,9 +7,9 @@ import Icon from '../../../icons/StrokeDocument.js' import { normalizeFiles } from '../../../lib/files.js' const BulkImportModal = ({ onCancel, className = '', onBulkCidImport, ...props }) => { - const [selectedFile, setSelectedFile] = useState(null) - const [validationError, setValidationError] = useState(null) - const bulkCidInputRef = useRef(null) + const [selectedFile, setSelectedFile] = useState(null) + const [validationError, setValidationError] = useState(undefined) + const bulkCidInputRef = useRef(null) const { t } = useTranslation('files') const validateFileContents = async (file) => { @@ -40,7 +40,9 @@ const BulkImportModal = ({ onCancel, className = '', onBulkCidImport, ...props } } const selectFile = () => { - bulkCidInputRef.current.click() + if (bulkCidInputRef.current) { + bulkCidInputRef.current.click() + } } const onSubmit = async () => { @@ -74,7 +76,6 @@ const BulkImportModal = ({ onCancel, className = '', onBulkCidImport, ...props } onClick={selectFile} className='ma2 tc' bg='bg-teal' - type='button' > {t('bulkImportModal.select')} From fdef1f415807d19e375e56a33b53dd533396512d Mon Sep 17 00:00:00 2001 From: MattWong-ca Date: Fri, 13 Dec 2024 17:27:53 -0500 Subject: [PATCH 44/44] Update import path --- src/files/modals/bulk-import-modal/bulk-import-modal.stories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js b/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js index 2d3d10eb4..17d585019 100644 --- a/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js +++ b/src/files/modals/bulk-import-modal/bulk-import-modal.stories.js @@ -1,7 +1,7 @@ import React from 'react' import { action } from '@storybook/addon-actions' import i18n from '../../../i18n-decorator.js' -import BulkImportModal from './bulk-import-modal.js' +import BulkImportModal from './bulk-import-modal.tsx' /** * @type {import('@storybook/react').Meta}