Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: bulk CIDs import #2307

Open
wants to merge 49 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
16c9c92
Start bulk CID import
MattWong-ca Nov 30, 2024
8240607
Merge remote-tracking branch 'upstream/main' into feat/bulk-cids-import
MattWong-ca Nov 30, 2024
31a6cd0
Get functional MVP working with csv file
MattWong-ca Nov 30, 2024
35e392d
Get it working with txt file
MattWong-ca Dec 1, 2024
31ea252
WIP: bulk import modal UX
MattWong-ca Dec 6, 2024
a85432f
WIP, need to debug modal UX
MattWong-ca Dec 7, 2024
481e7a9
Modal UX working, need to clean up code
MattWong-ca Dec 7, 2024
c47e403
Remove comments
MattWong-ca Dec 7, 2024
2d2157d
Clean code
MattWong-ca Dec 7, 2024
fa54900
Add validation to file select
MattWong-ca Dec 8, 2024
274bc37
Use localization
MattWong-ca Dec 8, 2024
85a9328
Add correct type, remove progress tracking
MattWong-ca Dec 8, 2024
d343c88
Refactor code
MattWong-ca Dec 9, 2024
7b80a5d
Add storybook file
MattWong-ca Dec 9, 2024
88e7876
Remove comments
MattWong-ca Dec 9, 2024
8af56b7
Update Add menu test
MattWong-ca Dec 9, 2024
c3271fe
Merge remote-tracking branch 'upstream/main' into feat/bulk-cids-import
MattWong-ca Dec 9, 2024
8b56df3
Remove prop-types
MattWong-ca Dec 13, 2024
542ac1b
Use functional component
MattWong-ca Dec 13, 2024
90a6487
Use useTranslation
MattWong-ca Dec 13, 2024
45eaacb
Update to TS file
MattWong-ca Dec 13, 2024
fb30c30
Update import path
MattWong-ca Dec 13, 2024
0e00a85
fix(explore): browsing chunked files and inspecting via context menu …
lidel Nov 29, 2024
48b55fd
chore(ci): set cluster pin timeout to 30m
lidel Nov 29, 2024
7177f71
chore(ci): use repo in offline mode
lidel Nov 30, 2024
991f019
Get functional MVP working with csv file
MattWong-ca Nov 30, 2024
21b0aa1
Get it working with txt file
MattWong-ca Dec 1, 2024
febbc95
WIP: bulk import modal UX
MattWong-ca Dec 6, 2024
dbf6bda
WIP, need to debug modal UX
MattWong-ca Dec 7, 2024
7bd6081
Modal UX working, need to clean up code
MattWong-ca Dec 7, 2024
c701fb7
Remove comments
MattWong-ca Dec 7, 2024
108068a
Clean code
MattWong-ca Dec 7, 2024
6fa7da6
Add validation to file select
MattWong-ca Dec 8, 2024
5c82550
Use localization
MattWong-ca Dec 8, 2024
28e3725
Add correct type, remove progress tracking
MattWong-ca Dec 8, 2024
38ef8df
Refactor code
MattWong-ca Dec 9, 2024
0b4b84a
Add storybook file
MattWong-ca Dec 9, 2024
2752634
Remove comments
MattWong-ca Dec 9, 2024
44cd9f8
Update Add menu test
MattWong-ca Dec 9, 2024
2ea51b1
chore(release): 4.4.1 [skip ci]
semantic-release-bot Nov 30, 2024
ff3f8c1
chore: pull new translations (#2308)
github-actions[bot] Dec 4, 2024
d74bf52
Remove prop-types
MattWong-ca Dec 13, 2024
e9b4d85
Use functional component
MattWong-ca Dec 13, 2024
4d52a3c
Use useTranslation
MattWong-ca Dec 13, 2024
eafec89
Update to TS file
MattWong-ca Dec 13, 2024
fdef1f4
Update import path
MattWong-ca Dec 13, 2024
7f95020
Merge branch 'feat/bulk-cids-import' of https://github.com/MattWong-c…
MattWong-ca Dec 14, 2024
fb064df
Merge remote-tracking branch 'upstream/main' into feat/bulk-cids-import
MattWong-ca Dec 14, 2024
23f229a
Merge branch 'main' into feat/bulk-cids-import
SgtPooki Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions public/locales/en/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"openWithLocalAndPublicGateway": "Try opening it instead with your <1>local gateway</1> or <3>public gateway</3>.",
"cantBePreviewed": "Sorry, this file can’t be previewed",
"addByPath": "From IPFS",
"bulkImport": "Bulk import",
"newFolder": "New folder",
"generating": "Generating…",
"actions": {
Expand Down Expand Up @@ -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."
Expand Down
47 changes: 47 additions & 0 deletions src/bundles/files/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/bundles/files/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'} */
Expand Down
2 changes: 2 additions & 0 deletions src/bundles/files/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>
Expand Down
13 changes: 2 additions & 11 deletions src/components/modal/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => (
<div className={`flex justify-${justify} pa2 ${className}`} style={{ backgroundColor: '#f4f6f8' }} {...props}>
{ children }
</div>
Expand All @@ -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 }) => (
<div className={`ph4 pv3 tc ${className}`} {...props}>
{ Icon && (
<div className='center bg-snow br-100 flex justify-center items-center' style={{ width: '80px', height: '80px' }}>
Expand All @@ -40,10 +35,6 @@ ModalBody.propTypes = {
])
}

ModalBody.defaultProps = {
className: ''
}

export const Modal = ({ onCancel, children, className, ...props }) => {
return (
<div className={`${className} bg-white w-80 shadow-4 sans-serif relative`} style={{ maxWidth: '34em' }} {...props}>
Expand Down
13 changes: 11 additions & 2 deletions src/files/FilesPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ 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'

const FilesPage = ({
doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesAddPath, doUpdateHash,
doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesBulkCidImport, 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
Expand Down Expand Up @@ -72,6 +72,12 @@ const FilesPage = ({
doFilesWrite(raw, root)
}

const onBulkCidImport = (raw, root = '') => {
if (root === '') root = files.path

doFilesBulkCidImport(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 })
Expand Down Expand Up @@ -206,6 +212,7 @@ const FilesPage = ({
onAddFiles={onAddFiles}
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)} />
Expand All @@ -226,6 +233,7 @@ const FilesPage = ({
onShareLink={doFilesShareLink}
onRemove={doFilesDelete}
onAddByPath={onAddByPath}
onBulkCidImport={onBulkCidImport}
onPinningSet={doSetPinning}
onPublish={doPublishIpnsKey}
cliOptions={cliOptions}
Expand Down Expand Up @@ -277,6 +285,7 @@ export default connect(
'selectFilesSorting',
'selectToursEnabled',
'doFilesWrite',
'doFilesBulkCidImport',
'doFilesDownloadLink',
'doFilesDownloadCarLink',
'doFilesSizeGet',
Expand Down
12 changes: 11 additions & 1 deletion src/files/file-input/FileInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ class FileInput extends React.Component {
this.toggleDropdown()
}

onBulkCidImport = () => {
this.props.onBulkCidImport()
this.toggleDropdown()
}

onNewFolder = () => {
this.props.onNewFolder()
this.toggleDropdown()
Expand Down Expand Up @@ -92,6 +97,10 @@ class FileInput extends React.Component {
<NewFolderIcon className='fill-aqua w2 h2 mr1' />
{t('newFolder')}
</Option>
<Option onClick={this.onBulkCidImport} id='bulk-cid-import'>
<DocumentIcon className='fill-aqua w2 mr1' />
{t('bulkImport')}
</Option>
</DropdownMenu>
</Dropdown>

Expand Down Expand Up @@ -120,7 +129,8 @@ FileInput.propTypes = {
t: PropTypes.func.isRequired,
onAddFiles: PropTypes.func.isRequired,
onAddByPath: PropTypes.func.isRequired,
onNewFolder: PropTypes.func.isRequired
onNewFolder: PropTypes.func.isRequired,
onBulkCidImport: PropTypes.func.isRequired
}

export default connect(
Expand Down
1 change: 1 addition & 0 deletions src/files/header/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class Header extends React.Component {
onNewFolder={this.props.onNewFolder}
onAddFiles={this.props.onAddFiles}
onAddByPath={this.props.onAddByPath}
onBulkCidImport={this.props.onBulkCidImport}
onCliTutorMode={this.props.onCliTutorMode}
/>
: <div ref={el => { this.dotsWrapper = el }}>
Expand Down
18 changes: 18 additions & 0 deletions src/files/modals/Modals.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +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/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'
Expand All @@ -20,6 +21,7 @@ const SHARE = 'share'
const RENAME = 'rename'
const DELETE = 'delete'
const ADD_BY_PATH = 'add_by_path'
const BULK_CID_IMPORT = 'bulk_cid_import'
const CLI_TUTOR_MODE = 'cli_tutor_mode'
const PINNING = 'pinning'
const PUBLISH = 'publish'
Expand All @@ -30,6 +32,7 @@ export {
RENAME,
DELETE,
ADD_BY_PATH,
BULK_CID_IMPORT,
CLI_TUTOR_MODE,
PINNING,
PUBLISH
Expand Down Expand Up @@ -63,6 +66,11 @@ class Modals extends React.Component {
this.leave()
}

onBulkCidImport = (files, root) => {
this.props.onBulkCidImport(files, root)
this.leave()
}

makeDir = (path) => {
this.props.onMakeDir(join(this.props.root, path))
this.leave()
Expand Down Expand Up @@ -152,6 +160,9 @@ class Modals extends React.Component {
case ADD_BY_PATH:
this.setState({ readyToShow: true })
break
case BULK_CID_IMPORT:
this.setState({ readyToShow: true })
break
case CLI_TUTOR_MODE:
this.setState({ command: this.cliCommand(cliOptions, files, root) }, () => {
this.setState({ readyToShow: true })
Expand Down Expand Up @@ -254,6 +265,13 @@ class Modals extends React.Component {
onCancel={this.leave} />
</Overlay>

<Overlay show={show === BULK_CID_IMPORT && readyToShow} onLeave={this.leave}>
<BulkImportModal
className='outline-0'
onBulkCidImport={this.onBulkCidImport}
onCancel={this.leave} />
</Overlay>

<Overlay show={show === CLI_TUTOR_MODE && readyToShow} onLeave={this.leave}>
<CliTutorMode onLeave={this.leave} filesPage={true} command={command} t={t}/>
</Overlay>
Expand Down
21 changes: 21 additions & 0 deletions src/files/modals/bulk-import-modal/bulk-import-modal.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import { action } from '@storybook/addon-actions'
import i18n from '../../../i18n-decorator.js'
import BulkImportModal from './bulk-import-modal.tsx'

/**
* @type {import('@storybook/react').Meta}
*/
export default {
title: 'Files/Modals',
decorators: [i18n]
}

/**
* @type {import('@storybook/react').StoryObj}
*/
export const BulkImport = () => (
<div className="ma3">
<BulkImportModal onCancel={action('Cancel')} onBulkCidImport={action('Bulk CID Import')} />
</div>
)
Loading