diff --git a/README.md b/README.md index 8d91a9080..74c93e9f5 100644 --- a/README.md +++ b/README.md @@ -75,9 +75,9 @@ Click on the IPFS icon in the menubar to open the app. |:---:|:---:| | ![](https://ipfs.io/ipfs/QmPBMMG4HUB1U1kLfwWvr6zQhGMcuvWYtGEFeTqBjG2jxW) | ![](https://ipfs.io/ipfs/QmSL8L5kMevXWDZguBwiwDVsQX6qkRFt1yTCDdnA1LmyR9) | -| Pin Hashes | See connected Peers | +| | See connected Peers | |:---:|:---:| -| ![](https://ipfs.io/ipfs/QmTUS8rRufkXQp7ePRogMFLMxmf6YziYcb8HnDrVCvC2DF)| ![](https://ipfs.io/ipfs/QmW1SWU1aALf8sqSvEFGUtzK8oqX9BGNA6r4bzS8MPg94B) | +| | ![](https://ipfs.io/ipfs/QmW1SWU1aALf8sqSvEFGUtzK8oqX9BGNA6r4bzS8MPg94B) | ## Features @@ -85,10 +85,10 @@ Click on the IPFS icon in the menubar to open the app. |-----------------------|-------------------------|---------------------------------| | Peer ID | Add by drag'n'drop | Easy access to the WebUI | | Location | Add entire directories | Easy access to Node Settings | -| Addresses | Delete files | Pin hashes with labels | -| Public Key | Copy and share links | Optional launch on startup | -| Repository Size | | Auto add screenshots | -| Bandwidth Usage | | Download copied hash | +| Addresses | Delete files | Optional launch on startup | +| Public Key | Copy and share links | Auto add screenshots | +| Repository Size | | Download copied hash | +| Bandwidth Usage | | | Down/Up Speeds | | | Peers | | @@ -174,11 +174,6 @@ The components are classes exported with CamelCase names. The corresponding file + [**KeyCombo**](./src/js/components/view/key-combo.js) is a key combination. + [**Key**](./src/js/components/view/key.js) is a key. + [**MenuOption**](./src/js/components/view/menu-option.js) is a menu option to show within a menu bar. -+ [**PinnedHash**](./src/js/components/view/pinned-hash.js) is a pinned hash. - -#### [Statefull Components](./src/js/components/logic) - -+ [**NewPinnedHash**](./src/js/components/view/new-pinned-hash.js) is a new pinned hash form. ## Contribute diff --git a/package.json b/package.json index 78d475951..4a21f0c9f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "bl": "^2.0.1", "electron-compile": "^6.4.3", "electron-menubar": "^1.0.1", + "electron-prompt": "^1.1.0-1", "electron-squirrel-startup": "^1.0.0", "electron-store": "^2.0.0", "file-extension": "^4.0.5", diff --git a/src/components/IconButton.js b/src/components/IconButton.js index f402bdb93..633ced243 100644 --- a/src/components/IconButton.js +++ b/src/components/IconButton.js @@ -15,7 +15,7 @@ import Icon from './Icon' */ export default function IconButton (props) { return ( - ) @@ -25,7 +25,8 @@ IconButton.propTypes = { icon: PropTypes.string.isRequired, onClick: PropTypes.func.isRequired, active: PropTypes.bool, - color: PropTypes.string + color: PropTypes.string, + title: PropTypes.string } IconButton.defaultProps = { diff --git a/src/components/NewPinnedHash.js b/src/components/NewPinnedHash.js deleted file mode 100644 index d9dee7391..000000000 --- a/src/components/NewPinnedHash.js +++ /dev/null @@ -1,127 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' - -import IconButton from './IconButton' - -/** - * Is a New Pinned Hash form. - * - * @prop {Function} onSubmit - the on submit handler. - * @prop {Bool} hidden - should the box be hidden? - */ -export default class NewPinnedHash extends Component { - static propTypes = { - onSubmit: PropTypes.func.isRequired, - hidden: PropTypes.bool.isRequired - } - - state = { - tag: '', - hash: '' - } - - /** - * KeyUp event handler. - * @param {Event} event - * @returns {Void} - */ - keyUp = (event) => { - if (event.keyCode === 13) { - event.preventDefault() - this.submit() - } - } - - /** - * Tag change event handler. - * @param {Event} event - * @returns {Void} - */ - tagChange = (event) => { - this.setState({tag: event.target.value}) - } - - /** - * Hash change event handler. - * @param {Event} event - * @returns {Void} - */ - hashChange = (event) => { - this.setState({hash: event.target.value}) - } - - /** - * Resets the hash and tag. - * @returns {Void} - */ - reset = () => { - this.setState({ - hash: '', - tag: '' - }) - } - - /** - * Submits the hash and tag. - * @returns {Void} - */ - submit = () => { - const {hash, tag} = this.state - - if (hash) { - this.props.onSubmit(hash, tag) - this.reset() - } else { - this.hashInput.focus() - } - } - - componentDidUpdate (prevProps) { - if (!this.props.hidden && prevProps.hidden) { - this.tagInput.focus() - } - - if (this.props.hidden && !prevProps.hidden) { - this.reset() - } - } - - /** - * Render function. - * @returns {ReactElement} - */ - render () { - let className = 'block new-pinned' - if (this.props.hidden) { - className += ' hide' - } - - return ( -
-
-
- { this.tagInput = input }} - onChange={this.tagChange} - onKeyUp={this.keyUp} - value={this.state.tag} /> - { this.hashInput = input }} - onChange={this.hashChange} - onKeyUp={this.keyUp} - value={this.state.hash} - placeholder='Hash' /> -
-
- -
-
-
- ) - } -} diff --git a/src/components/PinnedHash.js b/src/components/PinnedHash.js deleted file mode 100644 index 61fb26d0f..000000000 --- a/src/components/PinnedHash.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import {ipcRenderer} from 'electron' - -import Button from './Button' - -/** - * Is a Pinned Hash. - * - * @param {Object} props - * - * @prop {String} tag - The hash tag - * @prop {String} hash - The hash - * @prop {Function} onChange - On tag change handler - * - * @return {ReactElement} - */ -export default function PinnedHash (props) { - return ( -
-
-
- -

{props.hash}

-
-
-
-
-
- ) -} - -PinnedHash.propTypes = { - tag: PropTypes.string.isRequired, - hash: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired -} diff --git a/src/controls/main/files.js b/src/controls/main/files.js index 0728fcda4..a8213bff7 100644 --- a/src/controls/main/files.js +++ b/src/controls/main/files.js @@ -83,6 +83,23 @@ function move (opts) { } } +function addByPath (opts) { + const { ipfs } = opts + + return async (_, hash, path) => { + if (!hash.startsWith('/ipfs') && !hash.startsWith('/ipns')) { + hash = `/ipfs/${hash}` + } + + try { + await ipfs().files.cp([hash, path]) + listAndSend(opts, basename(path)) + } catch (e) { + logger.error(e.stack) + } + } +} + export default function (opts) { const { menubar } = opts @@ -90,6 +107,7 @@ export default function (opts) { ipcMain.on('create-directory', createDirectory(opts)) ipcMain.on('remove-file', remove(opts)) ipcMain.on('move-file', move(opts)) + ipcMain.on('add-by-path', addByPath(opts)) ipcMain.on('drop-files', uploadFiles(opts)) menubar.tray.on('drop-files', uploadFiles(opts)) diff --git a/src/controls/main/pinned-files.js b/src/controls/main/pinned-files.js index 2880143c2..f199dceb2 100644 --- a/src/controls/main/pinned-files.js +++ b/src/controls/main/pinned-files.js @@ -1,146 +1,51 @@ -import {dialog, ipcMain} from 'electron' -import {validateIPFS} from '../utils' +import {dialog} from 'electron' import { logger } from '../../utils' -import bl from 'bl' const PATH = '/.pinset' -var pins = {} +// MIGRATE PINS +export default async function (opts) { + const { ipfs } = opts -function collect (stream) { - return new Promise((resolve, reject) => { - stream.pipe(bl((err, buf) => { - if (err) return reject(err) - resolve(buf) - })) - }) -} - -function makePinset (opts) { - const {ipfs} = opts - - return new Promise((resolve, reject) => { - ipfs().files.stat(PATH) - .then(resolve) - .catch(() => { - ipfs().files.write(PATH, Buffer.from('{}'), {create: true}) - .then(resolve) - .catch(reject) - }) - }) -} - -function writePinset (opts) { - const {ipfs, send} = opts - - return new Promise((resolve, reject) => { - const write = JSON.stringify(pins) - ipfs().files.rm(PATH, { recursive: true }) - .then(() => ipfs().files.write(PATH, Buffer.from(write), {create: true})) - .then(() => { - send('pinned', pins) - send('files-updated') - resolve() - }) - .catch(reject) - }) -} - -function pinset (opts) { - const {ipfs} = opts - - return () => { - pins = {} - - ipfs().pin.ls() - .then((pinset) => { - pinset.forEach((pin) => { - if (pin.type === 'indirect') { - return - } - - pins[pin.hash] = '' - }) + if (!ipfs()) { + return + } - return makePinset(opts) - }) - .then(() => ipfs().files.read(PATH)) - .then((res) => collect(res)) - .then((buf) => JSON.parse(buf.toString())) - .then((res) => { - for (const hash in res) { - if (hash in pins) { - pins[hash] = res[hash] - } - } + try { + await ipfs().files.stat(PATH) + } catch (e) { + if (!e.toString().includes('file does not exist')) { + logger.error(e) + } - return writePinset(opts) - }) - .catch(error => logger.error(error.stack)) + return } -} - -function pinHash (opts) { - const {ipfs, send} = opts - let pinning = 0 + const buf = await ipfs().files.read(PATH) + const pins = JSON.parse(buf.toString()) - const sendPinning = () => { send('pinning', pinning > 0) } - const inc = () => { pinning++; sendPinning() } - const dec = () => { pinning--; sendPinning() } + await ipfs().files.mkdir('/pinset_from_old_ipfs_desktop') - return (event, hash, tag) => { - if (!validateIPFS(hash)) { - dialog.showErrorBox( - 'Invalid Hash', - 'The hash you provided is invalid.' - ) - return + for (const pin of Object.keys(pins)) { + let src = pin + if (!src.startsWith('/ipfs') && !src.startsWith('/ipns')) { + src = `/ipfs/${src}` } - inc() - logger.info(`Pinning ${hash}`) - - ipfs().pin.add(hash) - .then(() => { - dec() - logger.info(`${hash} pinned`) - pins[hash] = tag - return writePinset(opts) - }) - .catch(e => { - dec() - logger.error(e.stack) - }) - } -} - -function unpinHash (opts) { - const {ipfs} = opts - - return (_, hash) => { - logger.info(`Unpinning ${hash}`) + let dst = pins[pin] + if (dst === '') { + dst = pin + } + dst = `/pinset_from_old_ipfs_desktop/${dst}` - ipfs().pin.rm(hash) - .then(() => { - logger.info(`${hash} unpinned`) - delete pins[hash] - return writePinset(opts) - }) - .catch(e => { logger.error(e.stack) }) + await ipfs().files.cp([src, dst]) } -} -function tagHash (opts) { - return (_, hash, tag) => { - pins[hash] = tag - writePinset(opts).catch(e => { logger.error(e.stack) }) - } -} + await ipfs().files.rm(PATH) -export default function (opts) { - ipcMain.on('request-pinned', pinset(opts)) - ipcMain.on('tag-hash', tagHash(opts)) - ipcMain.on('pin-hash', pinHash(opts)) - ipcMain.on('unpin-hash', unpinHash(opts)) + dialog.showMessageBox({ + type: 'info', + buttons: ['OK'], + message: 'Pinned assets were moved to /pinset_from_old_ipfs_desktop. Everything in Files tab is implicitly pinned. You can add new files from the local machine or via IPFS Path.' + }) } diff --git a/src/panes/Files.js b/src/panes/Files.js index 37edb6705..4d32a4002 100644 --- a/src/panes/Files.js +++ b/src/panes/Files.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import {ipcRenderer, clipboard} from 'electron' import {NativeTypes} from 'react-dnd-html5-backend' import {DropTarget} from 'react-dnd' +import {validateIPFS} from '../controls/utils' import Pane from '../components/Pane' import Header from '../components/Header' @@ -95,6 +96,38 @@ class Files extends Component { return } + addFromIPFS = async () => { + const prompt = require('electron-prompt') + + const hash = await prompt({ + title: 'Add by IPFS Path', + label: 'Enter the IPFS path to add:', + inputAttrs: {type: 'text', required: true} + }) + + if (hash === null) { + return + } + + if (!validateIPFS(hash)) { + // es-no-alert + window.alert('Invalid IPFS Path!') + return + } + + const path = await prompt({ + title: 'Add by IPFS Path', + label: 'How do you want to name it?', + inputAttrs: {type: 'text', required: true} + }) + + if (path === null) { + return + } + + ipcRenderer.send('add-by-path', hash, join(this.props.root, path)) + } + render () { const {connectDropTarget, isOver, canDrop} = this.props @@ -144,8 +177,9 @@ class Files extends Component {
- - + + +
diff --git a/src/panes/Pinned.js b/src/panes/Pinned.js deleted file mode 100644 index c0e880733..000000000 --- a/src/panes/Pinned.js +++ /dev/null @@ -1,99 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {ipcRenderer} from 'electron' - -import Pane from '../components/Pane' -import Header from '../components/Header' -import Footer from '../components/Footer' -import IconButton from '../components/IconButton' -import PinnedHash from '../components/PinnedHash' -import NewPinnedHash from '../components/NewPinnedHash' -import InputText from '../components/InputText' - -export default class Pinned extends Component { - static propTypes = { - files: PropTypes.object, - pinning: PropTypes.bool - } - - static defaultProps = { - files: {}, - pinning: false - } - - state = { - search: '', - showNew: false - } - - submitHash = (hash, tag) => { - ipcRenderer.send('pin-hash', hash, tag) - this.setState({ showNew: false }) - } - - toggleShowNew = () => { - this.setState({ showNew: !this.state.showNew }) - } - - onSearch = (text) => { - this.setState({ search: text }) - } - - static tagUpdater = (hash) => (event) => { - ipcRenderer.send('tag-hash', hash, event.target.value) - } - - render () { - let hashes = [] - - for (const hash of Object.keys(this.props.files)) { - const tag = this.props.files[hash] - const found = hash.toLowerCase().indexOf(this.state.search) > -1 || - tag.toLowerCase().indexOf(this.state.search) > -1 - - if (found) { - hashes.push(( - - )) - } - } - - if (hashes.length === 0 && !this.state.showNew) { - hashes = ( -

- You do not have any pinned hashes yet. Pin one by - clicking the button on the bottom left. -

- ) - } - - return ( -
- -
- -
-
- -
- - -
- -
-
- -
- ) - } -} diff --git a/src/screens/menubar.js b/src/screens/menubar.js index 76c8422d5..f65a72921 100644 --- a/src/screens/menubar.js +++ b/src/screens/menubar.js @@ -12,7 +12,6 @@ import Peers from '../panes/Peers' import Loader from '../panes/Loader' import Start from '../panes/Start' import Files from '../panes/Files' -import Pinned from '../panes/Pinned' import Info from '../panes/Info' import Settings from '../panes/Settings' @@ -33,11 +32,6 @@ const panes = [ title: 'Files', icon: 'files' }, - { - id: 'pinned', - title: 'Pin', - icon: 'pin' - }, { id: 'peers', title: 'Peers', @@ -60,8 +54,7 @@ class Menubar extends Component { root: '/', contents: [] }, - adding: false, - pinning: false + adding: false } listeners = {} @@ -86,16 +79,6 @@ class Menubar extends Component { return this.listeners[key] } - onPinned = (event, pinset) => { - this.setState({pinned: pinset}) - - setTimeout(() => { - if (this.state.route === 'pinned') { - ipcRenderer.send('request-pinned') - } - }, 5000) - } - _changeRoute = (route) => { if (route === this.state.route) return @@ -106,9 +89,6 @@ class Menubar extends Component { case 'peers': ipcRenderer.send('request-stats', ['peers']) break - case 'pinned': - ipcRenderer.send('request-pinned') - break default: ipcRenderer.send('request-stats', []) break @@ -118,7 +98,6 @@ class Menubar extends Component { } onRunning = () => { - ipcRenderer.send('request-pinned') ipcRenderer.send('request-files', this.state.files.root) } @@ -133,8 +112,6 @@ class Menubar extends Component { ipcRenderer.on('settings', this._onSomething('settings')) ipcRenderer.on('adding', this._onSomething('adding')) ipcRenderer.on('files', this._onSomething('files')) - ipcRenderer.on('pinning', this._onSomething('pinning')) - ipcRenderer.on('pinned', this.onPinned) ipcRenderer.on('files-updated', this.filesUpdated) ipcRenderer.send('request-state') @@ -149,8 +126,6 @@ class Menubar extends Component { ipcRenderer.removeListener('settings', this._onSomething('settings')) ipcRenderer.removeListener('adding', this._onSomething('adding')) ipcRenderer.removeListener('files', this._onSomething('files')) - ipcRenderer.removeListener('pinning', this._onSomething('pinning')) - ipcRenderer.removeListener('pinned', this.onPinned) ipcRenderer.removeListener('files-updated', this.filesUpdated) } @@ -185,12 +160,6 @@ class Menubar extends Component { bw={this.state.stats.bw} repo={this.state.stats.repo} /> ) - case 'pinned': - return ( - - ) default: return ( diff --git a/src/setup.js b/src/setup.js index 94a3583d8..8aa1ea278 100644 --- a/src/setup.js +++ b/src/setup.js @@ -12,7 +12,7 @@ function welcome ({ path }) { title: 'Welcome to IPFS', icon: logo('ice'), show: false, - // resizable: false, + resizable: false, width: 850, height: 450 }) @@ -24,7 +24,7 @@ function welcome ({ path }) { logger.info('Welcome window ready') }) - // window.setMenu(null) + window.setMenu(null) window.loadURL(`file://${__dirname}/views/welcome.html`) // Send the default path as soon as the window is ready. diff --git a/src/styles/app.less b/src/styles/app.less index 2bd9e771b..54a476803 100644 --- a/src/styles/app.less +++ b/src/styles/app.less @@ -15,7 +15,6 @@ @import "./components/Key.less"; @import "./components/Menu.less"; @import "./components/MenuOption.less"; -@import "./components/NewPinnedHash.less"; @import "./components/Pane.less"; @import "./components/PaneContainer.less"; diff --git a/src/styles/components/NewPinnedHash.less b/src/styles/components/NewPinnedHash.less deleted file mode 100644 index 4b5f64345..000000000 --- a/src/styles/components/NewPinnedHash.less +++ /dev/null @@ -1,42 +0,0 @@ -.block.new-pinned { - transition: .2s ease max-height, .2s ease transform; - overflow: hidden; - max-height: 5em; - - &, - &:hover, - &:active { - background: #2ecc71; - } - - input.info, - input.label { - color: white; - } - - &.hide { - transform: scaleY(0); - max-height: 0; - } - - button .icon, - ::-webkit-input-placeholder { - color: white; - } - - & > div { - display: flex; - align-items: center; - } - - & > div > div:first-child { - flex-grow: 1; - } - - button { - opacity: 1; - position: static; - transform: none; - top: initial; - } -} \ No newline at end of file