diff --git a/src/bundles/peer-bandwidth.js b/src/bundles/peer-bandwidth.js index c6625b863..af0c63093 100644 --- a/src/bundles/peer-bandwidth.js +++ b/src/bundles/peer-bandwidth.js @@ -2,7 +2,7 @@ import { createSelector } from 'redux-bundler' import last from 'it-last' // Depends on ipfsBundle, peersBundle, routesBundle -export default function (opts) { +const bundle = function (opts) { opts = opts || {} // Max number of peers to update at once opts.peerUpdateConcurrency = opts.peerUpdateConcurrency || 5 @@ -180,3 +180,5 @@ export default function (opts) { ) } } + +export default bundle diff --git a/src/bundles/pinning.js b/src/bundles/pinning.js index a4c7411db..b4715517a 100644 --- a/src/bundles/pinning.js +++ b/src/bundles/pinning.js @@ -32,6 +32,12 @@ const pinningBundle = { if (action.type === 'SET_REMOTE_PINS') { return { ...state, remotePins: action.payload } } + if (action.type === 'ADD_REMOTE_PIN') { + return { ...state, remotePins: [...state.remotePins, action.payload] } + } + if (action.type === 'REMOVE_REMOTE_PIN') { + return { ...state, remotePins: state.remotePins.filter(p => p.id !== action.payload.id) } + } if (action.type === 'SET_REMOTE_PINNING_SERVICES') { return { ...state, pinningServices: action.payload } } @@ -41,43 +47,43 @@ const pinningBundle = { return state }, - doFetchRemotePins: () => async ({ dispatch, store }) => { - // const pinningServices = store.selectPinningServices() - - // if (!pinningServices?.length) return - - // // TODO: unmock this (e.g. const pins = ipfs.pin.remote.ls ...) - // const response = [ - // { - // id: 'Pinata:UniqueIdOfPinRequest', - // status: 'queued', - // cid: 'QmQsUbcVx6Vu8vtL858FdxD3sVBE6m8uP3bjFoTzrGubmX', - // name: '26_remote.png', - // delegates: ['/dnsaddr/pin-service.example.com'] - // } - // ] - - // // TODO: get type of item? - - // const remotePins = response.map(item => ({ - // ...item, - // isRemotePin: true, - // type: item.type || 'unknown', - // size: Math.random() * 1000// TODO: files.stat in the future - // })) + doFetchRemotePins: () => async ({ dispatch, store, getIpfs }) => { + const pinningServices = store.selectPinningServices() - // // TODO: handle different status (queued = async fetch in batches to update ui?) + if (!pinningServices?.length) return - const remotePins = [] + const ipfs = getIpfs() - dispatch({ type: 'SET_REMOTE_PINS', payload: remotePins }) + if (!ipfs || store?.ipfs?.ipfs?.ready || !ipfs.pin.remote) return + + const pinsGenerator = pinningServices.map(async service => ({ + pins: await ipfs.pin.remote.ls({ + service: service.name + }), + serviceName: service.name + })) + + dispatch({ type: 'SET_REMOTE_PINS', payload: [] }) + + for await (const { pins, serviceName } of pinsGenerator) { + for await (const pin of pins) { + dispatch({ + type: 'ADD_REMOTE_PIN', + payload: { + ...pin, + id: `${serviceName}:${pin.cid}` + } + }) + } + } }, selectRemotePins: (state) => state.pinning.remotePins || [], - doSelectRemotePinsForFile: (file) => async ({ store }) => { + doSelectRemotePinsForFile: (file) => ({ store }) => { const pinningServicesNames = store.selectPinningServices().map(remote => remote.name) - const remotePinForFile = store.selectRemotePins().filter(pin => pin.cid === file.cid.string) + + const remotePinForFile = store.selectRemotePins().filter(pin => pin.cid.string === file.cid.string) const servicesBeingUsed = remotePinForFile.map(pin => pin.id.split(':')[0]).filter(pinId => pinningServicesNames.includes(pinId)) return servicesBeingUsed @@ -117,19 +123,42 @@ const pinningBundle = { } }), {}), - doSetPinning: (cid, services = []) => async ({ getIpfs, store }) => { + doSetPinning: (pin, services = []) => async ({ getIpfs, store, dispatch }) => { const ipfs = getIpfs() + const { cid, name } = pin const pinLocally = services.includes('local') try { pinLocally ? await ipfs.pin.add(cid) : await ipfs.pin.rm(cid) } catch (e) { console.error(e) - } finally { - await store.doPinsFetch() } - // TODO: handle rest of services + store.selectPinningServices().forEach(async service => { + const shouldPin = services.includes(service.name) + try { + if (shouldPin) { + dispatch({ + type: 'ADD_REMOTE_PIN', + payload: { + ...pin, + id: `${service.name}:${pin.cid}` + } + }) + await ipfs.pin.remote.add(cid, { service: service.name, name }) + } else { + dispatch({ + type: 'REMOVE_REMOTE_PIN', + payload: { id: `${service.name}:${pin.cid}` } + }) + await ipfs.pin.remote.rm({ cid: [cid], service: service.name }) + } + } catch (e) { + console.error(e) + } + }) + + await store.doPinsFetch() }, doAddPinningService: ({ apiEndpoint, nickname, secretApiKey }) => async ({ getIpfs }) => { const ipfs = getIpfs() diff --git a/src/components/pinning-manager/PinningManager.js b/src/components/pinning-manager/PinningManager.js index ff67ce142..c83c24d90 100644 --- a/src/components/pinning-manager/PinningManager.js +++ b/src/components/pinning-manager/PinningManager.js @@ -163,11 +163,11 @@ const OptionsCell = ({ doRemovePinningService, name, t }) => { setContextVisibility(false)} arrowAlign="right"> { visitServiceUrl && ( - setContextVisibility(false) }> - + + setContextVisibility(false) }> {t('visitService')} - - ) + + ) } {t('remove')} diff --git a/src/components/pinning-manager/fixtures/pinningServices.js b/src/components/pinning-manager/fixtures/pinningServices.js index 51a7ac16f..0e6531c9f 100644 --- a/src/components/pinning-manager/fixtures/pinningServices.js +++ b/src/components/pinning-manager/fixtures/pinningServices.js @@ -1,4 +1,4 @@ -export default [ +const services = [ { name: 'Pinata', icon: 'https://svgshare.com/i/M7y.svg', @@ -19,3 +19,5 @@ export default [ addedAt: new Date(1592491648691) } ] + +export default services diff --git a/src/constants/pinning.js b/src/constants/pinning.js index 54b363d3c..2b4bcb0d1 100644 --- a/src/constants/pinning.js +++ b/src/constants/pinning.js @@ -1,7 +1,9 @@ -export default [ +const pinningConstants = [ { name: 'Pinata', icon: 'https://ipfs.io/ipfs/QmVYXV4urQNDzZpddW4zZ9PGvcAbF38BnKWSgch3aNeViW?filename=pinata.svg', apiEndpoint: 'https://api.pinata.cloud/psa' } ] + +export default pinningConstants diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js index da0c4f559..090d02380 100644 --- a/src/files/FilesPage.js +++ b/src/files/FilesPage.js @@ -48,7 +48,7 @@ class FilesPage extends React.Component { this.props.doFilesFetch() this.props.doPinsFetch() this.props.doFilesSizeGet() - this.props.doFetchRemotePins() + this.props.doFetchPinningServices().then(() => this.props.doFetchRemotePins()) } componentDidUpdate (prev) { @@ -316,6 +316,7 @@ export default connect( 'selectFilesPathInfo', 'doUpdateHash', 'doPinsFetch', + 'doFetchPinningServices', 'doFetchRemotePins', 'doFilesFetch', 'doFilesMove', diff --git a/src/files/files-list/FilesList.js b/src/files/files-list/FilesList.js index 056aa062e..934da8113 100644 --- a/src/files/files-list/FilesList.js +++ b/src/files/files-list/FilesList.js @@ -23,7 +23,7 @@ const addFiles = async (filesPromise, onAddFiles) => { } const mergeRemotePinsIntoFiles = (files, remotePins) => { - const remotePinsCids = remotePins.map(c => c.cid) + const remotePinsCids = remotePins.map(c => c.cid.string) return files.map(f => remotePinsCids.includes(f.cid?.string) ? ({ ...f, @@ -32,7 +32,7 @@ const mergeRemotePinsIntoFiles = (files, remotePins) => { } export const FilesList = ({ - className, files, pins, remotePins, filesSorting, updateSorting, downloadProgress, filesIsFetching, filesPathInfo, showLoadingAnimation, + className, files, pins, remotePins, filesSorting, updateSorting, downloadProgress, filesIsFetching, filesPathInfo, showLoadingAnimation, availablePinningServices, onShare, onSetPinning, onInspect, onDownload, onDelete, onRename, onNavigate, onRemotePinClick, onAddFiles, onMove, handleContextMenuClick, t }) => { const [selected, setSelected] = useState([]) diff --git a/src/files/modals/pinning-modal/PinningModal.js b/src/files/modals/pinning-modal/PinningModal.js index 64bf16e62..db61547fb 100644 --- a/src/files/modals/pinning-modal/PinningModal.js +++ b/src/files/modals/pinning-modal/PinningModal.js @@ -17,12 +17,26 @@ const humanSize = (size) => { }) } -export const PinningModal = ({ t, tReady, onCancel, onPinningSet, file, availablePinningServices, doGetFileSizeThroughCid, doSelectRemotePinsForFile, className, ...props }) => { +const PinIcon = ({ icon, index }) => { + if (icon) { + return + } + + const colors = ['aqua', 'link', 'yellow', 'teal', 'red', 'green', 'navy', 'gray', 'charcoal'] + const color = colors[index % colors.length] + const glyphClass = `mr1 fill-${color} flex-shrink-0` + + return +} + +export const PinningModal = ({ t, tReady, onCancel, onPinningSet, file, pinningServices, doGetFileSizeThroughCid, doSelectRemotePinsForFile, doFetchPinningServices, className, ...props }) => { const remoteServices = useMemo(() => doSelectRemotePinsForFile(file), [doSelectRemotePinsForFile, file]) + const [selectedServices, setSelectedServices] = useState([...remoteServices, ...[file.pinned && 'local']]) const [size, setSize] = useState(null) useEffect(() => { + doFetchPinningServices() const fetchSize = async () => setSize(await doGetFileSizeThroughCid(file.cid)) fetchSize() // eslint-disable-next-line react-hooks/exhaustive-deps @@ -35,6 +49,7 @@ export const PinningModal = ({ t, tReady, onCancel, onPinningSet, file, availabl return setSelectedServices(selectedServices.filter(s => s !== key)) } + return ( @@ -44,10 +59,10 @@ export const PinningModal = ({ t, tReady, onCancel, onPinningSet, file, availabl

{ t('pinningModal.localNode') }

- { availablePinningServices.map(({ icon, name }) => ( + { pinningServices.map(({ icon, name }, index) => ( ))} @@ -62,7 +77,7 @@ export const PinningModal = ({ t, tReady, onCancel, onPinningSet, file, availabl - +
) @@ -81,8 +96,9 @@ PinningModal.defaultProps = { } export default connect( - 'selectAvailablePinningServices', + 'selectPinningServices', 'doSelectRemotePinsForFile', 'doGetFileSizeThroughCid', + 'doFetchPinningServices', withTranslation('files')(PinningModal) ) diff --git a/src/files/modals/pinning-modal/fixtures/pinningServices.js b/src/files/modals/pinning-modal/fixtures/pinningServices.js index d7052c06a..ba7742689 100644 --- a/src/files/modals/pinning-modal/fixtures/pinningServices.js +++ b/src/files/modals/pinning-modal/fixtures/pinningServices.js @@ -1,4 +1,4 @@ -export default [ +const services = [ { name: 'Pinata', icon: 'https://svgshare.com/i/M7y.svg' @@ -10,3 +10,5 @@ export default [ icon: 'https://www.eternum.io/static/images/icons/favicon-32x32.a2341c8ec160.png' } ] + +export default services