diff --git a/package-lock.json b/package-lock.json index 53219e8d0..8d69cc459 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1995,6 +1995,11 @@ "glob-to-regexp": "^0.3.0" } }, + "@multiformats/base-x": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", + "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==" + }, "@nodelib/fs.stat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", @@ -14453,6 +14458,17 @@ "requires": { "uint8arrays": "1.0.0", "varint": "^5.0.0" + }, + "dependencies": { + "uint8arrays": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-1.0.0.tgz", + "integrity": "sha512-14tqEVujDREW7YwonSZZwLvo7aFDfX7b6ubvM/U7XvZol+CC/LbhaX/550VlWmhddAL9Wou1sxp0Of3tGqXigg==", + "requires": { + "multibase": "^3.0.0", + "web-encoding": "^1.0.2" + } + } } }, "multihashes": { @@ -16448,9 +16464,9 @@ } }, "it-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/it-concat/-/it-concat-1.0.0.tgz", - "integrity": "sha512-mLhiCB3tW4NTYTg7bMlyYX2c782KsAacthHMR3y5kjJn9JhNFb02NcH70KZuNrSXFSTq8k6m8MiYaQWRjrDxAA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/it-concat/-/it-concat-1.0.1.tgz", + "integrity": "sha512-ca7tnIqSpPycty9K+x08OwFj9kLSrsXgENn7ry2mNXlFlUkgEZe1/xvBjwnUlUEHvnITMj4Mq7ozPm1VaOm8FQ==", "requires": { "bl": "^4.0.0" } @@ -27086,20 +27102,20 @@ "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==" }, "uint8arrays": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-1.0.0.tgz", - "integrity": "sha512-14tqEVujDREW7YwonSZZwLvo7aFDfX7b6ubvM/U7XvZol+CC/LbhaX/550VlWmhddAL9Wou1sxp0Of3tGqXigg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-1.1.0.tgz", + "integrity": "sha512-cLdlZ6jnFczsKf5IH1gPHTtcHtPGho5r4CvctohmQjw8K7Q3gFdfIGHxSTdTaCKrL4w09SsPRJTqRS0drYeszA==", "requires": { "multibase": "^3.0.0", "web-encoding": "^1.0.2" }, "dependencies": { "multibase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-3.0.0.tgz", - "integrity": "sha512-fuB+zfRbF5zWV4L+CPM0dgA0gX7DHG/IMyzwhVi2RxbRVWn41Wk7SkKW8cxYDGOg6TVh7XgyoesjOAYrB1HBAA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-3.0.1.tgz", + "integrity": "sha512-MRU5WpnSg81/vYO977MweoeUAxBdXl7+F5Af2Es+X6Vcgfk/g/EjIqXTgm3kb+xO3m1Kzr+aIV14oRX7nv5Z9w==", "requires": { - "base-x": "^3.0.8", + "@multiformats/base-x": "^4.0.1", "web-encoding": "^1.0.2" } } diff --git a/package.json b/package.json index c40fb99e9..fb66a4eae 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "simplify-number": "^1.0.0", "tachyons": "^4.12.0", "topojson": "^3.0.2", + "uint8arrays": "^1.1.0", "video-extensions": "^1.1.0", "window-or-global": "^1.0.1", "window.ipfs-fallback": "^1.2.1" diff --git a/public/locales/en/files.json b/public/locales/en/files.json index e5f143fd0..2450b4fe1 100644 --- a/public/locales/en/files.json +++ b/public/locales/en/files.json @@ -85,6 +85,7 @@ "paragraph3": "Pro tip: drag and drop a file from any other page of the Web UI to add them to the root of your MFS." } }, + "loadMore": "Load more", "previousFolder": "Go back to previous folder", "fileLabel": "Select {type} {name} with size: {size}", "hashUnavailable": "hash unavailable", diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js index 9ef03c431..dc6a4dc1a 100644 --- a/src/files/FilesPage.js +++ b/src/files/FilesPage.js @@ -163,7 +163,7 @@ class FilesPage extends React.Component { if (files.type === 'file') { return ( - + this.onDownload([files])} /> ) } diff --git a/src/files/file-preview/FilePreview.js b/src/files/file-preview/FilePreview.js index 0979beb50..aeee0b4ed 100644 --- a/src/files/file-preview/FilePreview.js +++ b/src/files/file-preview/FilePreview.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' import { connect } from 'redux-bundler-react' @@ -9,6 +9,8 @@ import ComponentLoader from '../../loader/ComponentLoader.js' import './FilePreview.css' import CID from 'cids' import { useDrag } from 'react-dnd' +import fromUint8ArrayToString from 'uint8arrays/to-string' +import Button from '../../components/button/Button' const Preview = (props) => { const { name, size, cid, path } = props @@ -18,20 +20,43 @@ const Preview = (props) => { const type = typeFromExt(name) - return
+ // Hack: Allows for text selection if it's a text file (bypass useDrag) + const dummyRef = useRef() + + return
} -const PreviewItem = ({ t, name, cid, size, type, availableGatewayUrl: gatewayUrl, read }) => { +const PreviewItem = ({ t, name, cid, size, type, availableGatewayUrl: gatewayUrl, read, onDownload }) => { const [content, setContent] = useState(null) + const [hasMoreContent, setHasMoreContent] = useState(false) + const [buffer, setBuffer] = useState(null) - const loadContent = async () => { - const buf = await read() - setContent(buf.toString('utf-8')) - } + const loadContent = useCallback(async () => { + const readBuffer = buffer || await read() + if (!buffer) { + setBuffer(readBuffer) + } + + const { value, done } = await readBuffer.next() + const previousContent = content || '' + + const currentContent = previousContent + fromUint8ArrayToString(value) + + setContent(currentContent) + + const hasMore = !done && new TextEncoder().encode(currentContent).length < size - const src = `${gatewayUrl}/ipfs/${cid}` + setHasMoreContent(hasMore) + }, [buffer, content, read, size]) + + useEffect(() => { + loadContent() + }, // eslint-disable-next-line react-hooks/exhaustive-deps + []) + + const src = `${gatewayUrl}/ipfs/${cid}?filename=${encodeURIComponent(name)}` const className = 'mw-100 mt3 bg-snow-muted pa2 br2 border-box' switch (type) { @@ -75,19 +100,27 @@ const PreviewItem = ({ t, name, cid, size, type, availableGatewayUrl: gatewayUrl } if (!content) { - loadContent() return } if (isBinary(content)) { + loadContent() return cantPreview } - return ( + return <>
           {content}
         
- ) + { hasMoreContent &&
+ + +
} + } } }