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: add pinning to files page #1678

Merged
merged 15 commits into from
Nov 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3,996 changes: 3,636 additions & 360 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions public/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"save": "Save",
"saving": "Saving…",
"selectAll": "Select all",
"setPinning": "Set pinning",
"submit": "Submit",
"unpin": "Unpin",
"unselectAll": "Unselect all"
Expand Down Expand Up @@ -68,6 +69,7 @@
"peers": "Peers",
"pinNoun": "Pin",
"pins": "Pins",
"pinStatus": "Pin Status",
lidel marked this conversation as resolved.
Show resolved Hide resolved
"publicKey": "Public key",
"rateIn": "Rate in",
"rateOut": "Rate out",
Expand Down
5 changes: 5 additions & 0 deletions public/locales/en/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
"descriptionFolder": "{count, plural, one {Are you sure you want to delete this folder? This action is permanent and cannot be reversed.} other {Are you sure you want to delete these {count} folders? This action is permanent and cannot be reversed.}}",
"descriptionFile": "{count, plural, one {Are you sure you want to delete this file? This action is permanent and cannot be reversed.} other {Are you sure you want to delete these {count} files? This action is permanent and cannot be reversed.}}"
},
"pinningModal": {
"title": "Select the services with which you wish to pin these files.",
"footer": "Need to add or configure a pinning service? Go to <1>Settings</1>.",
"localNode": "Local node"
},
"addByPathModal": {
"title": "Import from IPFS",
"description": "Insert an IPFS path (CID) to import.",
Expand Down
10 changes: 0 additions & 10 deletions public/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,6 @@
"DISABLED": "Disabled",
"PINS_ONLY": "Pins only"
},
"localPinning": "Local Pinning",
"service": "Service",
"files": "Files",
"bandwidthUsed": "Bandwidth Used",
"autoUpload": "Auto Upload",
"autoUploadKeys": {
"ALL_FILES": "All files",
"DISABLED": "Disabled",
"PINS_ONLY": "Pins only"
},
"fetchingSettings": "Fetching settings...",
"configApiNotAvailable": "The IPFS config API is not available. Please disable the \"IPFS Companion\" Web Extension and try again.",
"ipfsDaemonOffline": "The IPFS daemon is offline. Please turn it on and try again.",
Expand Down
4 changes: 2 additions & 2 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ export class App extends Component {

return connectDropTarget(
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div className='sans-serif h-100' onClick={getNavHelper(this.props.doUpdateUrl)}>
<div className='sans-serif h-100 relative' onClick={getNavHelper(this.props.doUpdateUrl)}>
{/* Tinted overlay that appears when dragging and dropping an item */}
{ canDrop && isOver && <div className='w-100 h-100 top-0 left-0 absolute' style={{ background: 'rgba(99, 202, 210, 0.2)' }} /> }
{ canDrop && isOver && <div className='h-100 top-0 right-0 fixed appOverlay' style={{ background: 'rgba(99, 202, 210, 0.2)' }} /> }
<div className='flex flex-row-reverse-l flex-column-reverse justify-end justify-start-l' style={{ minHeight: '100vh' }}>
<div className='flex-auto-l'>
<div className='flex items-center ph3 ph4-l' style={{ WebkitAppRegion: 'drag', height: 75, background: '#F0F6FA', paddingTop: '20px', paddingBottom: '15px' }}>
Expand Down
29 changes: 18 additions & 11 deletions src/bundles/files/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,21 +462,28 @@ const actions = () => ({
doFilesDismissErrors: () => send({ type: ACTIONS.DISMISS_ERRORS }),

/**
* @param {string} path
*/
doFilesNavigateTo: (path) =>
* @param {Object} fileArgs
* @param {string} fileArgs.path
* @param {string|CID} fileArgs.cid
*/
doFilesNavigateTo: ({ path, cid }) =>
/**
* @param {Context} context
*/
async ({ store }) => {
const link = path.split('/').map(p => encodeURIComponent(p)).join('/')
const files = store.selectFiles()
const url = store.selectFilesPathInfo()

if (files && files.path === link && url) {
await store.doFilesFetch()
} else {
await store.doUpdateHash(link)
try {
const link = path.split('/').map(p => encodeURIComponent(p)).join('/')
const files = store.selectFiles()
const url = store.selectFilesPathInfo()

if (files && files.path === link && url) {
await store.doFilesFetch()
} else {
await store.doUpdateHash(link)
}
} catch (e) {
console.error(e)
store.doUpdateHash(`/ipfs/${cid}`)
}
},

Expand Down
3 changes: 2 additions & 1 deletion src/bundles/files/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ export default () => {
pageContent: {
...pageContent,
content
}
},
sorting: action.payload
}
} else {
return state
Expand Down
59 changes: 58 additions & 1 deletion src/bundles/pinning.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,63 @@
// @ts-check
/**
* TODO: This might change, current version from: https://github.com/ipfs/go-ipfs/blob/petar/pincli/core/commands/remotepin.go#L53
* @typedef {Object} RemotePin
* @property {string} id
* @property {string} name
* @property {('queued'|'pinning'|'pinned'|'failed')} status
* @property {string} cid
* @property {Array<string>} [delegates] e.g. ["/dnsaddr/pin-service.example.com"]
*/
export default {
name: 'pinning',
reducer: (state = {}) => state,
reducer: (state = {
remotePins: []
}, action) => {
if (action.type === 'SET_REMOTE_PINS') {
return { ...state, remotePins: action.payload }
}
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
}))

// TODO: handle different status (queued = async fetch in batches to update ui?)

dispatch({ type: 'SET_REMOTE_PINS', payload: remotePins })
},

selectRemotePins: (state) => state.pinning.remotePins || [],

doSelectRemotePinsForFile: (file) => ({ store }) => {
const pinningServicesNames = store.selectPinningServices().map(remote => remote.name)
const remotePinForFile = store.selectRemotePins().filter(pin => pin.cid === file.cid.string)
const servicesBeingUsed = remotePinForFile.map(pin => pin.id.split(':')[0]).filter(pinId => pinningServicesNames.includes(pinId))

return servicesBeingUsed
},

// selectPinningServices: state => state.pinning
// TODO: unmock this
Expand Down
2 changes: 1 addition & 1 deletion src/components/cli-tutor-mode/CliTutorMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const CliTutorialModal = ({ command, t, onLeave, className, downloadConfi

return (
<Modal {...props} className={className} onCancel={onLeave} style={{ maxWidth: '40em' }}>
<ModalBody icon={StrokeCode}>
<ModalBody Icon={StrokeCode}>
<p className='charcoal w-80 center' style={{ lineHeight: '1.3' }}>
{t('app:cliModal.description')}
</p>
Expand Down
6 changes: 3 additions & 3 deletions src/components/text-input-modal/TextInputModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class TextInputModal extends React.Component {
onChange: PropTypes.func,
onInputChange: PropTypes.func,
title: PropTypes.string.isRequired,
icon: PropTypes.func.isRequired,
Icon: PropTypes.func.isRequired,
description: PropTypes.node,
submitText: PropTypes.string,
validate: PropTypes.func,
Expand Down Expand Up @@ -98,7 +98,7 @@ class TextInputModal extends React.Component {
mustBeDifferent,
onSubmit,
className,
icon,
Icon,
submitText,
validate,
defaultValue,
Expand All @@ -111,7 +111,7 @@ class TextInputModal extends React.Component {

return (
<Modal {...props} className={className} onCancel={onCancel}>
<ModalBody title={title} icon={icon}>
<ModalBody title={title} Icon={Icon}>
{ description && typeof description === 'object' && description }

{ description && typeof description === 'string' &&
Expand Down
20 changes: 11 additions & 9 deletions src/files/FilesPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import FilesList from './files-list/FilesList'
import { getJoyrideLocales } from '../helpers/i8n'

// Icons
import Modals, { DELETE, NEW_FOLDER, SHARE, RENAME, ADD_BY_PATH, CLI_TUTOR_MODE } from './modals/Modals'
import Modals, { DELETE, NEW_FOLDER, SHARE, RENAME, ADD_BY_PATH, CLI_TUTOR_MODE, PINNING } from './modals/Modals'
import Header from './header/Header'
import FileImportStatus from './file-import-status/FileImportStatus'

Expand Down Expand Up @@ -48,6 +48,7 @@ class FilesPage extends React.Component {
this.props.doFilesFetch()
this.props.doPinsFetch()
this.props.doFilesSizeGet()
this.props.doFetchRemotePins()
}

componentDidUpdate (prev) {
Expand Down Expand Up @@ -141,7 +142,7 @@ class FilesPage extends React.Component {
}

get mainView () {
const { t, files, doExploreUserProvidedPath } = this.props
const { t, files, remotePins, doExploreUserProvidedPath } = this.props

if (!files) {
return (<div/>)
Expand Down Expand Up @@ -170,15 +171,17 @@ class FilesPage extends React.Component {
return (
<FilesList
key={window.encodeURIComponent(files.path)}
root={files.path}
updateSorting={this.props.doFilesUpdateSorting}
files={files.content}
remotePins={remotePins}
upperDir={files.upper}
downloadProgress={this.state.downloadProgress}
onShare={(files) => this.showModal(SHARE, files)}
onRename={(files) => this.showModal(RENAME, files)}
onDelete={(files) => this.showModal(DELETE, files)}
onSetPinning={(files) => this.showModal(PINNING, files)}
onInspect={this.onInspect}
onRemotePinClick={this.onRemotePinClick}
onDownload={this.onDownload}
onAddFiles={this.onAddFiles}
onNavigate={this.props.doFilesNavigateTo}
Expand Down Expand Up @@ -234,8 +237,7 @@ class FilesPage extends React.Component {
onRename={() => this.showModal(RENAME, [contextMenu.file])}
onInspect={() => this.onInspect(contextMenu.file.cid)}
onDownload={() => this.onDownload([contextMenu.file])}
onPin={() => this.props.doFilesPin(contextMenu.file.cid)}
onUnpin={() => this.props.doFilesUnpin(contextMenu.file.cid)}
onPinning={() => this.showModal(PINNING, [contextMenu.file])}
isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={() => this.showModal(CLI_TUTOR_MODE, [contextMenu.file])}
doSetCliOptions={doSetCliOptions}
Expand Down Expand Up @@ -265,6 +267,7 @@ class FilesPage extends React.Component {
onShareLink={this.props.doFilesShareLink}
onDelete={this.props.doFilesDelete}
onAddByPath={this.onAddByPath}
onPinningSet={this.props.doSetPinning}
cliOptions={cliOptions}
{ ...this.state.modals } />

Expand Down Expand Up @@ -300,8 +303,6 @@ FilesPage.propTypes = {
doFilesDelete: PropTypes.func.isRequired,
doFilesAddPath: PropTypes.func.isRequired,
doFilesNavigateTo: PropTypes.func.isRequired,
doFilesPin: PropTypes.func.isRequired,
doFilesUnpin: PropTypes.func.isRequired,
doFilesUpdateSorting: PropTypes.func.isRequired,
doFilesWrite: PropTypes.func.isRequired,
doFilesDownloadLink: PropTypes.func.isRequired
Expand All @@ -311,18 +312,18 @@ export default connect(
'selectIpfsProvider',
'selectIpfsConnected',
'selectFiles',
'selectRemotePins',
'selectFilesPathInfo',
'doUpdateHash',
'doPinsFetch',
'doFetchRemotePins',
'doFilesFetch',
'doFilesMove',
'doFilesMakeDir',
'doFilesShareLink',
'doFilesDelete',
'doFilesAddPath',
'doFilesNavigateTo',
'doFilesPin',
'doFilesUnpin',
'doFilesUpdateSorting',
'selectFilesSorting',
'selectToursEnabled',
Expand All @@ -335,5 +336,6 @@ export default connect(
'doOpenCliTutorModal',
'doSetCliOptions',
'selectCliOptions',
'doSetPinning',
withTour(withTranslation('files')(FilesPage))
)
2 changes: 1 addition & 1 deletion src/files/breadcrumbs/Breadcrumbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const DropableBreadcrumb = ({ index, link, immutable, onAddFiles, onMove, onClic
index === 0 && (immutable ? 'bg-charcoal-muted white' : 'bg-navy white'),
immutable && (link.last || index === 0) && 'no-events',
link.last && 'b', isOver && 'dragging')}
onClick={() => onClick(link.path)} onContextMenu={(ev) => index !== 0 && handleOnContextMenuHandle(ev)}>
onClick={() => onClick({ path: link.path })} onContextMenu={(ev) => index !== 0 && handleOnContextMenuHandle(ev)}>
{link.name}
</button>
</span>
Expand Down
6 changes: 3 additions & 3 deletions src/files/context-menu/ContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class ContextMenu extends React.Component {
render () {
const {
t, onRename, onDelete, onDownload, onInspect, onShare,
translateX, translateY, className, isMfs, isUnknown, pinned, isCliTutorModeEnabled
translateX, translateY, className, isMfs, isUnknown, isCliTutorModeEnabled
} = this.props
return (
<Dropdown className={className}>
Expand Down Expand Up @@ -75,10 +75,10 @@ class ContextMenu extends React.Component {
{t('app:actions.inspect')}
</Option>
}
<Option onClick={this.wrap(pinned ? 'onUnpin' : 'onPin')} isCliTutorModeEnabled={isCliTutorModeEnabled}
<Option onClick={this.wrap('onPinning')} isCliTutorModeEnabled={isCliTutorModeEnabled}
onCliTutorMode={this.wrap('onCliTutorMode', cliCmdKeys.PIN_OBJECT)}>
<StrokePin className='w2 mr2 fill-aqua' />
{ pinned ? t('app:actions.unpin') : t('app:actions.pinVerb') }
{ t('app:actions.setPinning') }
</Option>
{ !isUnknown && onDownload &&
<Option onClick={this.wrap('onDownload')} isCliTutorModeEnabled={isCliTutorModeEnabled}
Expand Down
2 changes: 1 addition & 1 deletion src/files/explore-form/FilesExploreForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class FilesExploreForm extends React.Component {
path = `/ipfs/${path}`
}

this.props.onBrowse(path)
this.props.onBrowse({ path })
this.setState({ path: '' })
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/files/file-import-status/FileImportStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import GlyphCancel from '../../icons/GlyphCancel'
import GlyphSmallCancel from '../../icons/GlyphSmallCancel'

const File = (job, t) => {
const pathsByFolder = job.message.entries.reduce((prev, currentEntry) => {
const pathsByFolder = job.message?.entries?.reduce((prev, currentEntry) => {
const isFolder = currentEntry.path.includes('/')
if (!isFolder) {
return [...prev, currentEntry]
}

const baseFolder = currentEntry.path.split('/')[0]

const alreadyExistentBaseFolder = prev.find(previousPath => previousPath.path.startsWith(`${baseFolder}/`))
const alreadyExistentBaseFolder = prev.find(previousPath => previousPath.path.startsWith(baseFolder + '/'))

if (alreadyExistentBaseFolder) {
alreadyExistentBaseFolder.count = alreadyExistentBaseFolder.count + 1
Expand All @@ -33,7 +33,7 @@ const File = (job, t) => {
return [...prev, { ...currentEntry, name: baseFolder, count: 1 }]
}, [])

return pathsByFolder.map(({ count, name, path, size, progress }) => (
return pathsByFolder?.map(({ count, name, path, size, progress }) => (
<li className="flex w-100 bb b--light-gray items-center f6 charcoal" key={ path || name }>
{ count ? <FolderIcon className='fileImportStatusIcon fill-aqua pa1'/> : <DocumentIcon className='fileImportStatusIcon fill-aqua pa1'/> }
<span className="fileImportStatusName truncate">{ name || path }</span>
Expand Down
Loading