initial commit 🏄‍♀️

This commit is contained in:
Matthias Kretschmann 2019-10-16 17:02:04 +02:00
commit f356f3ba29
Signed by: m
GPG Key ID: 606EEEF3C479A91F
21 changed files with 638 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules
npm-debug.log
.DS_Store
dist
package-lock.json

6
.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"tabWidth": 2
}

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# IPFS
> 🏄‍♀️ Ocean Protocol's public IPFS Node, setup to be a public gateway, and to provide some access to its HTTP API for everyone.
> [ipfs.oceanprotocol.com](https://ipfs.oceanprotocol.com)

24
config.js Normal file
View File

@ -0,0 +1,24 @@
export const title = 'Ocean Protocol 💖 IPFS'
export const description = `Ocean Protocol's public IPFS Node, setup to be a public gateway, and to provide some access to its HTTP API for everyone.<br /><a href="https://blog.oceanprotocol.com/ocean-and-ipfs-sitting-in-the-merkle-tree-43c623c356d7">Learn More →</a>`
export const ipfsGateway = 'https://ipfs.oceanprotocol.com'
export const ipfsNodeUri = 'https://ipfs.oceanprotocol.com:443'
export const links = [
{
name: 'Homepage',
url: 'https://oceanprotocol.com'
},
{
name: 'Blog',
url: 'https://blog.oceanprotocol.com'
},
{
name: 'Twitter',
url: 'https://twitter.com/oceanprotocol'
},
{
name: 'GitHub',
url: 'https://github.com/oceanprotocol'
}
]

53
package.json Normal file
View File

@ -0,0 +1,53 @@
{
"name": "@oceanprotocol/ipfs",
"version": "1.0.0",
"description": "Ocean Protocol's Public IPFS Node.",
"scripts": {
"start": "webpack-dev-server",
"build": "NODE_ENV=production webpack"
},
"author": "Matthias Kretschmann <matthias@oceanprotocol.com>",
"license": "Apache-2.0",
"dependencies": {
"@oceanprotocol/art": "^2.2.0",
"@oceanprotocol/typographies": "^0.1.0",
"ipfs-http-client": "^38.2.0",
"react-dropzone": "^10.1.10"
},
"devDependencies": {
"@babel/core": "^7.6.4",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/polyfill": "^7.6.0",
"@babel/preset-env": "^7.6.3",
"@babel/preset-react": "^7.6.3",
"@svgr/webpack": "^4.3.3",
"babel-loader": "^8.0.6",
"copy-webpack-plugin": "^5.0.4",
"css-loader": "^3.2.0",
"eslint": "^6.5.1",
"eslint-config-oceanprotocol": "^1.5.0",
"eslint-config-prettier": "^6.4.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-react": "^7.16.0",
"mini-css-extract-plugin": "^0.8.0",
"prettier": "^1.18.2",
"react": "^16.10.2",
"react-dom": "^16.10.2",
"style-loader": "^1.0.0",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2"
},
"eslintConfig": {
"extends": [
"oceanprotocol",
"oceanprotocol/react",
"prettier/react",
"prettier/standard",
"plugin:prettier/recommended"
],
"plugins": [
"prettier"
]
}
}

46
src/App.css Normal file
View File

@ -0,0 +1,46 @@
@import 'styles/_variables.css';
.app {
padding: var(--spacer);
background: var(--brand-black);
height: 100%;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
@media screen and (min-width: 640px) {
.app {
height: auto;
min-height: calc(100vh - (var(--page-frame) * 2) - (var(--spacer) * 2));
}
}
.header {
width: 100%;
margin-top: var(--spacer);
}
.appTitle {
margin-top: var(--spacer);
font-size: var(--font-size-h2);
}
@media screen and (min-width: 640px) {
.appTitle {
font-size: var(--font-size-h1);
}
}
.appDescription {
font-size: var(--font-size-large);
color: var(--brand-grey-lighter);
max-width: 40rem;
margin-left: auto;
margin-right: auto;
}
.logo {
width: 80px;
height: 80px;
}

25
src/App.jsx Normal file
View File

@ -0,0 +1,25 @@
import '@babel/polyfill'
import React from 'react'
import Add from './components/Add'
import Logo from '@oceanprotocol/art/logo/logo-white.svg'
import './styles/global.css'
import './App.css'
import { title, description } from '../config'
import Footer from './components/Footer'
export default function App() {
return (
<div className="app">
<header className="header">
<Logo className="logo" />
<h1 className="appTitle">{title}</h1>
<p
className="appDescription"
dangerouslySetInnerHTML={{ __html: description }}
/>
</header>
<Add />
<Footer />
</div>
)
}

4
src/components/Add.css Normal file
View File

@ -0,0 +1,4 @@
.add {
max-width: 40rem;
width: 100%;
}

38
src/components/Add.jsx Normal file
View File

@ -0,0 +1,38 @@
import React, { useState } from 'react'
import { saveToIpfs } from '../ipfs'
import { ipfsGateway } from '../../config'
import Dropzone from './Dropzone'
import './Add.css'
import Spinner from './Spinner'
export default function Add() {
const [fileHash, setFileHash] = useState(null)
const [loading, setLoading] = useState(false)
const handleCaptureFile = async files => {
setLoading(true)
const cid = await saveToIpfs(files)
setFileHash(cid)
setLoading(false)
}
return (
<div className="add">
{loading ? (
<Spinner />
) : fileHash ? (
<div>
<a
target="_blank"
rel="noopener noreferrer"
href={`${ipfsGateway}/ipfs/${fileHash}`}
>
{fileHash}
</a>
</div>
) : (
<Dropzone multiple={false} handleOnDrop={handleCaptureFile} />
)}
</div>
)
}

View File

@ -0,0 +1,31 @@
@import '../styles/_variables.css';
.dropzone {
border: 0.2rem dashed var(--brand-grey-light);
border-radius: calc(var(--border-radius) * 2);
padding: calc(var(--spacer) * 2);
transition: 0.2s ease-out;
cursor: pointer;
}
.dropzone:hover,
.dropzone:focus,
.dropzone:active {
border-color: var(--brand-grey-lighter);
outline: 0;
}
.dropzone.dragover {
border-color: var(--brand-pink);
}
.dropzone.disabled {
opacity: 0.5;
pointer-events: none;
}
.dropzone p {
text-align: center;
margin-bottom: 0;
color: var(--brand-grey-light);
}

View File

@ -0,0 +1,45 @@
import React, { useCallback } from 'react'
import PropTypes from 'prop-types'
import { useDropzone } from 'react-dropzone'
import './Dropzone.css'
Dropzone.propTypes = {
handleOnDrop: PropTypes.func.isRequired,
disabled: PropTypes.bool,
multiple: PropTypes.bool
}
export default function Dropzone({ handleOnDrop, disabled, multiple }) {
const onDrop = useCallback(acceptedFiles => handleOnDrop(acceptedFiles), [
handleOnDrop
])
const {
getRootProps,
getInputProps,
isDragActive,
isDragReject
} = useDropzone({ onDrop })
return (
<div
{...getRootProps({
className: isDragActive
? 'dragover'
: disabled
? 'disabled'
: 'dropzone'
})}
>
<input {...getInputProps({ multiple })} />
<p>
{isDragActive && !isDragReject
? `Drop it like it's hot!`
: multiple
? `Drag 'n' drop some files here, or click to select files`
: `Drag 'n' drop a file here, or click to select a file`}
{}
</p>
</div>
)
}

31
src/components/Footer.css Normal file
View File

@ -0,0 +1,31 @@
@import '../styles/_variables.css';
.footer {
width: 100%;
font-size: var(--font-size-mini);
padding: var(--spacer) 0;
padding-bottom: 0;
display: flex;
justify-content: space-between;
align-self: flex-end;
text-align: left;
margin-top: var(--spacer);
}
.footer,
.footer a {
color: var(--brand-grey-light);
}
.footer > div {
flex: 1 1 48%;
}
.footer > div:last-child {
text-align: right;
}
.footer > div:last-child a {
display: inline-block;
margin-left: var(--spacer);
}

22
src/components/Footer.jsx Normal file
View File

@ -0,0 +1,22 @@
import React from 'react'
import { links } from '../../config'
import './Footer.css'
export default function Footer() {
return (
<footer className="footer">
<div>
© <span id="year">{Date.now()}</span>{' '}
<a href="https://oceanprotocol.com">Ocean Protocol Foundation Ltd.</a>
All Rights Reserved
</div>
<div>
{links.map(link => (
<a key={link.name} href={link.url}>
{link.name}
</a>
))}
</div>
</footer>
)
}

View File

@ -0,0 +1,36 @@
@import '../styles/_variables.css';
.spinner {
position: relative;
text-align: center;
margin-top: calc(var(--spacer) * var(--line-height));
margin-bottom: calc(var(--spacer) / 2);
line-height: 1.3;
}
.spinner:before {
content: '';
box-sizing: border-box;
position: absolute;
top: 0;
left: 50%;
width: 20px;
height: 20px;
margin-top: -20px;
margin-left: -10px;
border-radius: 50%;
border: 2px solid var(--brand-purple);
border-top-color: var(--brand-violet);
animation: spinner 0.6s linear infinite;
}
.spinnerMessage {
color: var(--brand-grey-light);
padding-top: calc(var(--spacer) / 4);
}
@keyframes spinner {
to {
transform: rotate(360deg);
}
}

View File

@ -0,0 +1,6 @@
import React from 'react'
import './Spinner.css'
export default function Spinner() {
return <div className="spinner" />
}

13
src/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Ocean Protocol 💖 IPFS</title>
</head>
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
</body>
</html>

5
src/index.js Normal file
View File

@ -0,0 +1,5 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

35
src/ipfs.js Normal file
View File

@ -0,0 +1,35 @@
import ipfsClient from 'ipfs-http-client'
import { ipfsNodeUri } from '../config'
export async function saveToIpfs(files) {
const { hostname, port, protocol } = new URL(ipfsNodeUri)
const ipfsConfig = {
protocol: protocol.replace(':', ''),
host: hostname,
port: port || '443'
}
const ipfs = ipfsClient(ipfsConfig)
const file = [...files][0]
let ipfsId
const fileDetails = {
path: file.name,
content: file
}
const options = {
wrapWithDirectory: true,
progress: prog => console.log(`received: ${prog}`)
}
try {
const response = await ipfs.add(fileDetails, options)
// CID of wrapping directory is returned last
ipfsId = `${response[response.length - 1].hash}/${fileDetails.path}`
return ipfsId
} catch (error) {
console.error(error.message)
}
}

51
src/styles/_variables.css Normal file
View File

@ -0,0 +1,51 @@
:root {
--brand-white: #fff;
--brand-black: #141414;
--brand-pink: #ff4092;
--brand-purple: #7b1173;
--brand-violet: #e000cf;
--brand-blue: #11597b;
--brand-grey: #41474e;
--brand-grey-light: #8b98a9;
--brand-grey-dark: #303030;
--brand-grey-lighter: #e2e2e2;
--green: #5fb359;
--red: #d80606;
--orange: #b35f36;
--yellow: #eac146;
--font-family-base: 'Sharp Sans', -apple-system, BlinkMacSystemFont,
'Segoe UI', Helvetica, Arial, sans-serif;
--font-family-title: 'Sharp Sans Display', -apple-system, BlinkMacSystemFont,
'Segoe UI', Helvetica, Arial, sans-serif;
--font-family-monospace: 'Fira Code', 'Fira Mono', Menlo, Monaco, Consolas,
'Courier New', monospace;
--font-size-root: 16px;
--font-size-base: 1rem;
--font-size-large: 1.2rem;
--font-size-small: 0.85rem;
--font-size-mini: 0.65rem;
--font-size-text: $font-size-base;
--font-size-label: $font-size-base;
--font-size-title: 1.4rem;
--font-size-h1: 3rem;
--font-size-h2: 1.7rem;
--font-size-h3: 1.4rem;
--font-size-h4: $font-size-large;
--font-size-h5: 1.1rem;
--font-weight-base: 500;
--font-weight-bold: 600;
--line-height: 1.6;
--spacer: 2rem;
--page-frame: 0.75rem;
--break-point--small: 640px;
--break-point--medium: 860px;
--break-point--large: 1140px;
--break-point--huge: 1400px;
--border-radius: 0.2rem;
}

90
src/styles/global.css Normal file
View File

@ -0,0 +1,90 @@
@import '../../node_modules/@oceanprotocol/typographies/css/ocean-typo.css';
@import '_variables.css';
html,
body,
#root {
height: 100%;
max-height: calc(100vh - (var(--page-frame) * 2));
}
html {
height: 100%;
font-size: var(--font-size-root);
}
body {
color: var(--brand-grey-light);
font-size: var(--font-size-base);
font-family: var(--font-family-base);
font-weight: var(--font-weight-base);
line-height: var(--line-height);
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: relative;
margin: 0;
text-align: center;
}
@media screen and (min-width: 640px) {
body {
padding: var(--page-frame);
}
}
a {
text-decoration: none;
color: var(--brand-pink);
transition: 0.2s ease-out;
}
p {
margin: 0;
margin-bottom: calc(var(--spacer) / var(--line-height));
}
h1,
h2,
h3,
h4,
h5 {
font-family: var(--font-family-title);
color: var(--brand-white);
line-height: 1.2;
font-weight: var(--font-weight-bold);
}
h1 {
font-size: var(--font-size-h1);
}
h2 {
font-size: var(--font-size-h2);
}
h3 {
font-size: var(--font-size-h3);
}
h4 {
font-size: var(--font-size-h4);
}
h5 {
font-size: var(--font-size-h5);
}
figure,
img,
svg,
video,
audio,
embed,
canvas,
picture {
max-width: 100%;
height: auto;
margin: 0 auto;
display: block;
}

68
webpack.config.js Normal file
View File

@ -0,0 +1,68 @@
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const defaultInclude = [path.resolve(__dirname, 'src')]
const isDevelopment = process.env.NODE_ENV !== 'production'
module.exports = {
mode: isDevelopment ? 'development' : 'production',
entry: path.resolve(__dirname, 'src', 'index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
include: defaultInclude,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-proposal-class-properties']
}
}
]
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
include: defaultInclude
},
{
test: /\.(jpe?g|png|gif)$/,
use: ['file-loader?name=img/[name]__[hash:base64:5].[ext]'],
include: defaultInclude
},
{
test: /\.svg$/,
use: [
{
loader: '@svgr/webpack',
options: {
icon: true
}
}
]
}
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
},
plugins: [
new MiniCssExtractPlugin({
filename: isDevelopment ? '[name].css' : '[name].[hash].css',
chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css'
}),
new CopyPlugin([{ from: './src/index.html', to: './', flatten: true }])
]
}