diff --git a/components/AppBar.js b/components/AppBar.js index ca669ed..2882a54 100644 --- a/components/AppBar.js +++ b/components/AppBar.js @@ -2,6 +2,7 @@ import React from 'react' import { makeStyles } from '@material-ui/core/styles' import { AppBar, Toolbar, Typography, IconButton } from '@material-ui/core' import ProfileIcon from '@material-ui/icons/AccountCircle' +import Status from './Status' const useStyles = makeStyles(theme => ({ title: { @@ -16,6 +17,7 @@ export default function BoardsAppBar() { IPFS Boards + ) diff --git a/components/Board.js b/components/Board.js new file mode 100644 index 0000000..cc5796e --- /dev/null +++ b/components/Board.js @@ -0,0 +1,53 @@ +import React from 'react' +import { Card, CardActions, CardHeader, Button, Avatar } from '@material-ui/core' +import { makeStyles } from '@material-ui/core/styles' +import AddIcon from '@material-ui/icons/Add' +import ViewIcon from '@material-ui/icons/Visibility' +import EmptyIcon from '@material-ui/icons/HourglassEmpty' +import Router from 'next/router' + +const useStyles = makeStyles(theme => ({ + card: { + margin: theme.spacing(2), + }, + buttonIcon: { + marginRight: theme.spacing(1) + }, + button: { + marginLeft: theme.spacing(2) + } +})) + +export default function Board({ boardId, posts }){ + const classes = useStyles() + return ( + + {posts.map(p => + + + + + )} + {posts.length === 0 && + } + title="No Posts Yet" + subheader="Why don't you break the ice?" + /> + } + + + ) +} \ No newline at end of file diff --git a/components/Status.js b/components/Status.js new file mode 100644 index 0000000..4c2dda0 --- /dev/null +++ b/components/Status.js @@ -0,0 +1,44 @@ +import React from 'react' +import { Button } from '@material-ui/core' +import { refreshInfo, getInfo } from './system' +import SSRIcon from '@material-ui/icons/SignalCellularNull' +import ConnectedIcon from '@material-ui/icons/SignalCellular1Bar' + +class Status extends React.PureComponent { + state = { info: getInfo() || {} } + + componentDidMount() { + this.refresh() + } + + componentWillUnmount() { + clearTimeout(this.state.timeout) + } + + async refresh() { + const info = await refreshInfo() + this.setState({ + info, timeout: setTimeout(this.refresh.bind(this), 3000) + }) + } + + render() { + const { info } = this.state + let Icon = SSRIcon + let statusText = 'Pre-Rendered' + if (!info.isServer) { + statusText = 'Offline' + if (info.ipfsReady) { + statusText = 'Starting DB' + Icon = ConnectedIcon + } + if (info.orbitDbReady) statusText = `${info.ipfsPeers.length} Peers` + } + + return + } +} + +export default Status \ No newline at end of file diff --git a/components/system.js b/components/system.js index 6e34c3e..4ca2a8f 100644 --- a/components/system.js +++ b/components/system.js @@ -6,6 +6,15 @@ export function getGlobalScope() { } } +export function isServer() { + try { + window.document + return false + } catch (error) { + return true + } +} + export function getGlobalData() { const scope = getGlobalScope() if (!scope.ipfsBoards) scope.ipfsBoards = {} @@ -15,26 +24,35 @@ export function getGlobalData() { export async function getIPFS() { const data = getGlobalData() if (data.ipfs) return data.ipfs - const IPFS = await import('ipfs') - data.ipfs = await IPFS.create() + if (!data.ipfsPromise) { + const IPFS = await import(/* webpackChunkName: "ipfs" */ 'ipfs') + data.ipfsPromise = IPFS.create() + } + data.ipfs = await data.ipfsPromise + delete data.ipfsPromise return data.ipfs } export async function getOrbitDB() { const data = getGlobalData() if (data.orbitDb) return data.orbitDb - const OrbitDB = await import('orbit-db').then(m => m.default) - const BoardStore = await import('orbit-db-discussion-board').then(m => m.default) - OrbitDB.addDatabaseType(BoardStore.type, BoardStore) - data.orbitDb = await OrbitDB.createInstance(await getIPFS()) - data.boards = {} + const ipfs = await getIPFS() + if (!data.orbitDbPromise) { + const OrbitDB = await import(/* webpackChunkName: "orbit-db" */ 'orbit-db').then(m => m.default) + const BoardStore = await import(/* webpackChunkName: "orbit-db-discussion-board" */ 'orbit-db-discussion-board').then(m => m.default) + OrbitDB.addDatabaseType(BoardStore.type, BoardStore) + data.orbitDbPromise = OrbitDB.createInstance(ipfs) + } + data.orbitDb = await data.orbitDbPromise + delete data.orbitDbPromise + if (!data.boards) data.boards = {} return data.orbitDb } export async function openBoard(id) { const data = getGlobalData() if (data.boards && data.boards[id]) return data.boards[id] - const BoardStore = await import('orbit-db-discussion-board').then(m => m.default) + const BoardStore = await import(/* webpackChunkName: "orbit-db-discussion-board" */ 'orbit-db-discussion-board').then(m => m.default) const options = { type: BoardStore.type, create: true, @@ -64,3 +82,37 @@ export function getLocalStorage() { } } } + +export async function getIPFSPeers() { + const data = getGlobalData() + return data.ipfs ? (await data.ipfs.swarm.peers()).map(x => x.peer._idB58String) : [] +} + +export async function getPubsubInfo() { + const data = getGlobalData() + if (!data.ipfs) return {} + const rooms = await data.ipfs.pubsub.ls() + const pubsubInfo = {} + for (const room of rooms) { + pubsubInfo[room] = await data.ipfs.pubsub.peers(room) + } + return pubsubInfo +} + +export function getInfo() { + const data = getGlobalData() + return data.info +} + +export async function refreshInfo() { + const data = getGlobalData() + data.info = { + isServer: isServer(), + ipfsReady: Boolean(data.ipfs), + orbitDbReady: Boolean(data.orbitDb), + openBoards: Object.keys(data.boards || {}), + ipfsPeers: await getIPFSPeers(), + pubsub: await getPubsubInfo() + } + return data.info +} \ No newline at end of file diff --git a/pages/b/[board]/index.js b/pages/b/[board]/index.js index f2bf794..6125756 100644 --- a/pages/b/[board]/index.js +++ b/pages/b/[board]/index.js @@ -1,11 +1,8 @@ import React from 'react' -import { Fab, Card, CardActions, CardHeader, Button } from '@material-ui/core' -import AddIcon from '@material-ui/icons/Add' -import ViewIcon from '@material-ui/icons/Visibility' -import { openBoard } from '../../../components/system' -import Router from 'next/router' +import { openBoard, refreshInfo } from '../../../components/system' +import Board from '../../../components/Board' -class Board extends React.PureComponent { +class BoardPage extends React.PureComponent { state = { posts: [] } componentDidMount() { @@ -14,41 +11,25 @@ class Board extends React.PureComponent { async refreshPosts() { const { boardId } = this.props - const board = await openBoard(boardId) - this.setState({ posts: board.posts }) + if (boardId) { + const board = await openBoard(boardId) + this.setState({ posts: board.posts }) + } else { + throw new Error('Missing boardId') + } } render() { const { boardId } = this.props const posts = this.state.posts || this.props.posts - return ( - - {posts.map(p => - - - - - )} - {posts.length === 0 && - - } - Router.push(`/b/${boardId}/p/new`)}> - - ) + return } } -Board.getInitialProps = async ({ query }) => { +BoardPage.getInitialProps = async ({ query }) => { + await refreshInfo() const board = await openBoard(query.board) return { posts: await board.posts, boardId: query.board } } -export default Board +export default BoardPage diff --git a/pages/b/[board]/p/new.js b/pages/b/[board]/p/new.js index bb4f0cd..c9d0208 100644 --- a/pages/b/[board]/p/new.js +++ b/pages/b/[board]/p/new.js @@ -1,21 +1,77 @@ import React, { useState } from 'react' -import { Card, CardContent, TextField, Button } from '@material-ui/core' +import { openBoard } from '../../../../components/system' +import Router from 'next/router' +import { makeStyles } from '@material-ui/core/styles' +import { Card, CardHeader, CardContent, TextField, Button, Avatar } from '@material-ui/core' import SendIcon from '@material-ui/icons/Send' +import AddIcon from '@material-ui/icons/Add' -export default function CreatePost() { +const useStyles = makeStyles(theme => ({ + card: { + maxWidth: 600, + margin: theme.spacing(2), + marginLeft: 'auto', + marginRight: 'auto' + }, + field: { + marginTop: theme.spacing(2), + marginBottom: theme.spacing(2), + }, + secondaryButton: { + marginLeft: theme.spacing(2) + }, + buttonIcon: { + marginLeft: theme.spacing(1) + } +})) + +async function createPost(boardId, postData) { + const board = await openBoard(boardId) + await board.addPost(postData) + Router.push(`/b/${boardId}`) +} + +export default function CreatePost({ boardId }) { + const classes = useStyles() const [title, setTitle] = useState('') - return + return + } + title="Post Something" + subheader={ + Your post will be published to the {boardId} board + } + /> setTitle(e.target.value)} autoFocus + fullWidth /> - + } + +CreatePost.getInitialProps = ({ query }) => { + return { boardId: query.board } +}