mirror of
https://github.com/oceanprotocol/ipfs
synced 2024-11-24 19:10:16 +01:00
initial commit 🏄♀️
This commit is contained in:
commit
f356f3ba29
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
package-lock.json
|
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
4
README.md
Normal file
4
README.md
Normal 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
24
config.js
Normal 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
53
package.json
Normal 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
46
src/App.css
Normal 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
25
src/App.jsx
Normal 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
4
src/components/Add.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.add {
|
||||||
|
max-width: 40rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
38
src/components/Add.jsx
Normal file
38
src/components/Add.jsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
31
src/components/Dropzone.css
Normal file
31
src/components/Dropzone.css
Normal 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);
|
||||||
|
}
|
45
src/components/Dropzone.jsx
Normal file
45
src/components/Dropzone.jsx
Normal 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
31
src/components/Footer.css
Normal 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
22
src/components/Footer.jsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
36
src/components/Spinner.css
Normal file
36
src/components/Spinner.css
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
6
src/components/Spinner.jsx
Normal file
6
src/components/Spinner.jsx
Normal 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
13
src/index.html
Normal 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
5
src/index.js
Normal 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
35
src/ipfs.js
Normal 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
51
src/styles/_variables.css
Normal 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
90
src/styles/global.css
Normal 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
68
webpack.config.js
Normal 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 }])
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user