diff --git a/README.md b/README.md index 97dd24b..ce205d5 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ Image and discussion boards, forums and the like have many problems: - Centralized - What if it gets shut down? - What if the servers are down? - - What if a phisical network link breaks and the data is on the other side? - - What if the owners of the board get eaten by aliens? - Fragile - what if there's a DoS attack? @@ -14,13 +12,6 @@ Image and discussion boards, forums and the like have many problems: - what if the datacenter is flooded and data is lost? - Closed Down, limited in possibilities - - What if I want to write a custom client and there is no API? - - What if I want to change the user interface? - - What if I want to use it in a LAN with no Internet access? - - What if I want a name that someone else already has? - - What if I want to move my data to another service/subreddit/forum ? - - What if I want more control (think a private forum), or less control (think 4chan) ? - - What if I want ? This project was conceived to solve that. With the help of modern web technologies, the IPFS and IPNS protocols and some optional cache servers, we can solve these problems and create a true universal platform which can act as: @@ -35,17 +26,14 @@ and some optional cache servers, we can solve these problems and create a true u With security, control, reliability, rock solid stability, fully distributed architecture or, optionally, none of these! -## FAQ +## Get Started -See `FAQ.md` +The App is not ready yet, but you're welcome to take a look at the prototype, +even though it only has informational pages and very limited functionality. -### How does it work? +You can access it [here](http://ipfs.io/ipfs/QmSqHvZDxiZFEy1AY6BXmogSk6DZiVJRUU4HEm7DmXSqno) -See `PROTOCOL.md` - -## Demo / Prototype - -You can find a working build [here](http://ipfs.io/ipfs/QmSqHvZDxiZFEy1AY6BXmogSk6DZiVJRUU4HEm7DmXSqno). +### Prototype information You need a local instance of go-ipfs running for it to work. You also need to set CORS settings right or it won't work. However, in that case, it will complain to @@ -70,6 +58,14 @@ Ability to publish stuff in the browser won't be implemented until go-ipfs 0.4 is ready. It will maybe be ready before the new year. You will be able to publish your boards/profile/posts using a CLI though. +## FAQ + +See `FAQ.md` + +### How does it work? + +See `PROTOCOL.md` + ### How do I set up a development environment? See `HACKING.md` diff --git a/lib/boards-api.js b/lib/boards-api.js index c2c27e3..24cff0e 100644 --- a/lib/boards-api.js +++ b/lib/boards-api.js @@ -163,6 +163,7 @@ BoardsAPI.prototype.searchUsers = function(){ }) }) // Look for who has the correct version file, they probably have a profile + /* this.ipfs.dht.findprovs(this.version_hash, (err,res) => { if(err){ console.log('DHT FINDPROVS err',err) @@ -171,7 +172,7 @@ BoardsAPI.prototype.searchUsers = function(){ } else { console.log('DHT FINDPROVS string',res) } - }) + })*/ return this.ee } @@ -258,17 +259,20 @@ BoardsAPI.prototype.getBoardSettings = function(userID,board){ return this.ee } -BoardsAPI.prototype.downloadPost = function(hash,adminID,board,op){ +BoardsAPI.prototype.downloadPost = function(hash,adminID,board,op,done){ this.ipfs.cat(hash,(err2,r) => { if(err2){ this.ee.emit('error',err2) console.log('Could not download post',hash,'of',board+'@'+adminID) + if(done && done.apply) done(err2) } else { // It already returns a JSON? var post = r + post.hash = hash if(op) post.op = op // Inject op if(board) this.ee.emit('post in '+board+'@'+adminID,post,hash) this.ee.emit(hash,post,adminID,board) + if(done && done.apply) done(null,post) } }) return this.ee @@ -344,11 +348,13 @@ BoardsAPI.prototype.getCommentsFor = function(parent,board,adminID){ // Initialize API BoardsAPI.prototype.init = function(done){ + if(this.isInit) return this.ipfs.id( (err, res) => { if(err){ console.log('Error while getting OWN ID:',err) this.ee.emit('error',err) - done(err) + this.ee.emit('init',err) + if(done && done.apply) done(err) } else if(res.ID){ console.log('I am',res.ID) this.id = res.ID @@ -358,25 +364,30 @@ BoardsAPI.prototype.init = function(done){ if(err2){ this.ee.emit('error',err2) console.log('Error while calculating version hash:',err2) - done(err2) + this.ee.emit('init',err2) + if(done && done.apply) done(err2) } else { if(r && r.Hash) this.version_hash = r.Hash if(r && r[0] && r[0].Hash) this.version_hash = r[0].Hash console.log('Version hash is',this.version_hash) - done(null) + this.ipfs.version((err,res) => { + if(err){ + this.ee.emit('error',err) + this.ee.emit('init',err) + console.log('Error while getting ipfs version:',err) + if(done && done.apply) done(err) + } else { + this.ipfs_version = res.Version + console.log('IPFS Version is',res.Version) + this.ee.emit('init',undefined) + this.isInit = true + if(done && done.apply) done(null) + } + }) } }) } }) - this.ipfs.version((err,res) => { - if(err){ - this.ee.emit('error',err) - console.log('Error while getting ipfs version:',err) - } else { - this.ipfs_version = res.Version - console.log('IPFS Version is',res.Version) - } - }) } BoardsAPI.prototype.getEventEmitter = function(){ @@ -387,4 +398,8 @@ BoardsAPI.prototype.getUsers = function(){ return Object.keys(this.users) } +BoardsAPI.prototype.getMyID = function(){ + return this.id +} + module.exports = BoardsAPI diff --git a/webapp/app.jsx b/webapp/app.jsx index f087939..8f89b23 100644 --- a/webapp/app.jsx +++ b/webapp/app.jsx @@ -5,7 +5,6 @@ var Route = require('react-router').Route var IndexRoute = require('react-router').IndexRoute var Redirect = require('react-router').Redirect var Link = require('react-router').Link -var BoardsAPI = require('boards-api.js') // Load CSS require('normalize.css') @@ -14,17 +13,24 @@ require('style.css') require('raleway.css') require('font-awesome.min.css') -var opt, s = localStorage.getItem('ipfs-boards-settings') -try { - opt = JSON.parse(s) -} catch(e){ - // Do nothing -} -if(opt === null || opt === undefined) opt = { addr: 'localhost', port: 5001 } -var ipfs = require('ipfs-api')(opt.addr || 'localhost',opt.port || 5001) -var boards = new BoardsAPI(ipfs) +// Load Components -// Components +var opt = require('options.jsx').get() +var boardsWrapper = require('boardsapiwrapper.js') +var boards = new boardsWrapper() +var Icon = require('icon.jsx') +var GetIPFS = require('getipfs.jsx') + +// Load pages + +var Navbar = require('navbar.jsx')(boards) +var Users = require('users.jsx')(boards) +var Settings = require('settings.jsx')(boards) +var Profile = require('profile.jsx')(boards) +var Board = require('board.jsx')(boards) +var PostPage = require('postpage.jsx')(boards) + +// Define Main Components var Container = React.createClass({ render: function(){ @@ -38,32 +44,6 @@ var App = React.createClass({ } }) -var Navbar = React.createClass({ - extraButtons: function(){ - if(boards.id && boards.version){ - return - - - - } else { - return - } - }, - render: function(){ - return ( -
-
- {this.props.children ||

Boards

} -
- {this.extraButtons()} - - -
-
-
) - } -}) - // Static pages var Static = React.createClass({ @@ -85,29 +65,6 @@ var Homepage = React.createClass({ } }) -var GetIPFS = React.createClass({ - render: function(){ - return ( -
-

Connection to IPFS not available

-

Sorry, but at the moment an external application is needed to try the Prototype

-

You don't have an IPFS node running at {opt.addr}:{opt.port} or it is not reachable

-

The IPFS Boards prototype requires a full IPFS node running at localhost. - Please start one by following the - go-ipfs documentation.

-
Do you have a running node but the app won't work?
-

It's probably one of these issues:

-
    -
  • Your IPFS node doesn't allow requests from the domain you're running the app from (CORS issue). See here for the fix.
  • -
  • Your IPFS node is not listening for API requests at {opt.addr}:{opt.port}. Go to the Settings page, provide the correct address for the node, then save and reload the page.
  • -
  • Some other networking issue is preventing the App from talking to your node.
  • -
-

Still can't fix it? File a issue on GitHub, we'll be happy to help!

-
- ) - } -}) - var NotFound = React.createClass({ render: function(){ return (
@@ -117,51 +74,22 @@ var NotFound = React.createClass({ } }) -var NotImplemented = React.createClass({ - render: function(){ - return (
-

Not yet implemented

-

-

Sorry, working on it!

-
) - } -}) - // Start -var Users = require('users.jsx')(boards) -var Settings = require('settings.jsx')(boards) -var Profile = require('profile.jsx')(boards) -var Board = require('board.jsx')(boards) -var Icon = require('icon.jsx') - -boards.init(err => { - if(err){ - console.log('FATAL: IPFS NODE NOT AVAILABLE') - ReactDOM.render( - - - - - +ReactDOM.render( + + + + + + + + - - , document.getElementById('root')) - } else { - ReactDOM.render( - - - - - - - - - - - - - , document.getElementById('root') - ) - } -}) + + + + + + , document.getElementById('root') +) diff --git a/webapp/components/board.jsx b/webapp/components/board.jsx index a76b317..f7a769e 100644 --- a/webapp/components/board.jsx +++ b/webapp/components/board.jsx @@ -3,28 +3,58 @@ var Markdown = require('markdown.jsx') var Link = require('react-router').Link var Icon = require('icon.jsx') -module.exports = function(boards){ - var UserID = require('userID.jsx')(boards) - var PostList = require('postlist.jsx')(boards) +module.exports = function(boardsAPI){ + var UserID = require('userID.jsx')(boardsAPI) + var PostList = require('postlist.jsx')(boardsAPI) + var GetIPFS = require('getipfs.jsx')(boardsAPI) return React.createClass({ getInitialState: function(){ - return { name: this.props.params.boardname } + return { name: this.props.params.boardname, api: false } }, componentDidMount: function(){ - var ee = boards.getBoardSettings(this.props.params.userid,this.props.params.boardname) - ee.on('settings for '+this.props.params.boardname+'@'+this.props.params.userid, (res) => { - if(!this.isMounted()) return true - console.log('Found name:',res.fullname) - this.setState({ name: res.fullname.trim(), description: res.description }) + boardsAPI.use(boards => { + /* + When a component inside the component being rendered by the router also needs + access to the boards api, it appears unitialized and never initializes to it + for no apparent reason. Calling init twice (one automgically and one + when the root component mounts) works as a cheap, horrible workaround + */ + boards.init() + if(!this.isMounted()) return + var ee = boards.getEventEmitter() + ee.on('init',err => { + if(!err && this.isMounted()){ + this.setState({ api: true }) + this.init(boards) + } + }) + ee.on('settings for '+this.props.params.boardname+'@'+this.props.params.userid, (res) => { + if(!this.isMounted()) return true + console.log('Found name:',res.fullname) + this.setState({ name: res.fullname.trim(), description: res.description }) + }) + if(boards.isInit || this.state.api){ + this.setState({api: true}) + this.init(boards) + boards.getBoardSettings(this.props.params.userid,this.props.params.boardname) + } }) }, + init: function(boards){ + if(!this.state.init){ + boards.getBoardSettings(this.props.params.userid,this.props.params.boardname) + this.setState({ init: true }) + } + }, render: function(){ - return (
-

{this.state.name}

- -
- -
) + if(this.state.api){ + return (
+

{this.state.name}

+ +
+ +
) + } else return } }) } diff --git a/webapp/components/boardsapiwrapper.js b/webapp/components/boardsapiwrapper.js new file mode 100644 index 0000000..ce4b8b6 --- /dev/null +++ b/webapp/components/boardsapiwrapper.js @@ -0,0 +1,26 @@ +var BoardsAPI = function(){ + this.done = false + this.fa = [] + this.boards + require.ensure(['options.jsx','ipfs-api','boards-api.js'], _ => { + var opt = require('options.jsx').get() + var BoardsAPI = require('boards-api.js') + var ipfs = require('ipfs-api')(opt.addr || 'localhost',opt.port || 5001) + this.boards = new BoardsAPI(ipfs) + this.boards.init() + this.done = true + this.fa.forEach(fn => fn(this.boards)) + this.fa = undefined + }) +} + +BoardsAPI.prototype.use = function(f){ + if(!f || !f.apply || !f.call) return console.log('Non-function tried to use API:',f) + if(this.done){ + f(this.boards) + } else { + this.fa.push(f) + } +} + +module.exports = BoardsAPI diff --git a/webapp/components/getipfs.jsx b/webapp/components/getipfs.jsx new file mode 100644 index 0000000..aa659c2 --- /dev/null +++ b/webapp/components/getipfs.jsx @@ -0,0 +1,64 @@ +var React = require('react') +var Link = require('react-router').Link +var Icon = require('icon.jsx') + +module.exports = function(boardsAPI){ + return React.createClass({ + getInitialState: function(){ + return { connected: false, error: false, long: false } + }, + componentDidMount: function(){ + boardsAPI.use(boards => { + if(!this.isMounted()) return + if(boards.isInit){ + this.setState({ connected: true }) + } else { + setTimeout(_ => { + if(this.isMounted()) this.setState({ long: true }) + }, 5000) + boards.getEventEmitter().on('init', err => { + if(!this.isMounted()) return + if(err){ + this.setState({ error: true }) + } else { + this.setState({ connected: true }) + } + }) + } + }) + }, + render: function(){ + var opt = require('options.jsx').get() + if(this.state.error){ + return ( +
+

Connection to IPFS not available

+

Sorry, but at the moment an external application is needed to try the Prototype

+

You don't have an IPFS node running at {opt.addr}:{opt.port} or it is not reachable. + The IPFS Boards prototype requires a full IPFS node. Please start one by following the + go-ipfs documentation.

+
Do you have a running node but the app won't work?
+

It's probably one of these issues:

+
    +
  • Your node is not located at {opt.addr}:{opt.port}. Go to the Settings Page to configure the connection.
  • +
  • You edited your settings and saved them but didn't reload the page
  • +
  • Your IPFS node doesn't allow requests from the domain you're running the app from (CORS issue). See here for the fix.
  • +
  • Some other networking or browser security issue is preventing the App from talking to your node.
  • +
+

Still can't fix it? File a issue on GitHub, we'll be happy to help!

+
+ )} else if(this.state.connected){ + return
+

+
You're connected!
+
+ } else { + return
+ +

Connecting to IPFS

+ {this.state.long?(

It's taking long... there's probably something wrong

):

} +
+ } + } + }) +} diff --git a/webapp/components/markdown.jsx b/webapp/components/markdown.jsx index 3eec6cb..9b5eb4c 100644 --- a/webapp/components/markdown.jsx +++ b/webapp/components/markdown.jsx @@ -1,11 +1,23 @@ var React = require('react') -var MarkdownLib = require('react-markdown') module.exports = React.createClass({ + getInitialState: function(){ + return { lib: false } + }, + componentDidMount: function(){ + require.ensure(['react-markdown'],_ => { + if(this.isMounted()) this.setState({ MarkdownLib: require('react-markdown') }) + }) + }, renderIfApplicable: function(){ - if(this.props.source) - return - return

...

+ if(this.props.source){ + if(this.state.MarkdownLib){ + var MarkdownLib = this.state.MarkdownLib + return + } else { + return

{this.props.source}

+ } + } else return

...

}, render: function(){ return this.renderIfApplicable() diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx new file mode 100644 index 0000000..e8587a0 --- /dev/null +++ b/webapp/components/navbar.jsx @@ -0,0 +1,49 @@ +var React = require('react') +var Icon = require('icon.jsx') +var Link = require('react-router').Link + +module.exports = function(boardsAPI){ + return React.createClass({ + getInitialState: function(){ + return { api: false, loading: true } + }, + componentDidMount(){ + boardsAPI.use(boards => { + if(boards.isInit) this.setState({ api: true }) + boards.getEventEmitter().on('init',err => { + if(!this.isMounted()) return + if(err){ + this.setState({ loading: false, api: false }) + } else { + this.setState({ api: true }) + } + }) + }) + }, + extraButtons: function(){ + if(this.state.api){ + return + + + + } else if(this.state.loading){ + return + } else { + return + } + }, + render: function(){ + return ( +
+
+ {this.props.children ||

Boards

} +
+ {this.extraButtons()} + + +
+
+
) + } + }) +} diff --git a/webapp/components/options.jsx b/webapp/components/options.jsx new file mode 100644 index 0000000..45d3153 --- /dev/null +++ b/webapp/components/options.jsx @@ -0,0 +1,12 @@ +module.exports = { + get: function(){ + var opt, s = localStorage.getItem('ipfs-boards-settings') + try { + opt = JSON.parse(s) + } catch(e){ + // Do nothing + } + if(opt === null || opt === undefined) opt = { addr: 'localhost', port: 5001 } + return opt + } +} diff --git a/webapp/components/post.jsx b/webapp/components/post.jsx index bd352e1..0a63a06 100644 --- a/webapp/components/post.jsx +++ b/webapp/components/post.jsx @@ -1,18 +1,28 @@ var React = require('react') -var moment = require('moment') var Markdown = require('markdown.jsx') var Icon = require('icon.jsx') +var Link = require('react-router').Link -module.exports = function(boards){ - var UserID = require('userID.jsx')(boards) +module.exports = function(boardsAPI){ + var UserID = require('userID.jsx')(boardsAPI) return React.createClass({ getDate: function(){ if(this.props.post.date){ - return moment.unix(this.props.post.date).fromNow() + if(this.state.moment) + return this.state.moment.unix(this.props.post.date).fromNow() + else return '...' } else { return 'Unknown Date' } }, + getInitialState: function(){ + return { moment: false } + }, + componentDidMount: function(){ + require.ensure(['moment'],_ => { + if(this.isMounted()) this.setState({ moment: require('moment') }) + }) + }, render: function(){ return
@@ -21,7 +31,8 @@ module.exports = function(boards){
{this.getDate()} - Comments + + Comments
diff --git a/webapp/components/postlist.jsx b/webapp/components/postlist.jsx index ffd0b36..a2a6ca2 100644 --- a/webapp/components/postlist.jsx +++ b/webapp/components/postlist.jsx @@ -1,22 +1,21 @@ var React = require('react') -var moment = require('moment') var sortedIndex = require('lodash.sortedindex') -module.exports = function(boards){ - var Post = require('post.jsx')(boards) +module.exports = function(boardsAPI){ + var Post = require('post.jsx')(boardsAPI) return React.createClass({ getInitialState: function(){ - return { posts: [] } + return { posts: [], api: false } }, sortFn: function(a,b){ return (b.date || 0) - (a.date || 0) }, - componentDidMount: function(){ - console.log('Initial POSTS',this.state.posts.length) + init: function(boards){ + this.setState({ api: true }) boards.getPostsInBoard(this.props.admin,this.props.board) .on('post in '+this.props.board+'@'+this.props.admin,(post,hash) => { if(!this.isMounted()) return true - var now = moment().unix() + var now = (new Date()).getTime() var posts = this.state.posts if(post.date === undefined || post.date <= 0){ posts.push(post) @@ -29,6 +28,23 @@ module.exports = function(boards){ this.setState({ posts }) }) }, + componentDidMount: function(){ + boardsAPI.use(boards => { + if(boards.isInit) this.init(boards) + else boards.getEventEmitter().on('init',err => { + if(!err && this.isMounted()) this.init(boards) + }) + }) + }, + getPosts: function(){ + if(this.state.posts.length > 0 || this.state.api){ + return this.state.posts.map(post => { + return + }) + } else return
+ +
+ }, render: function(){ return (
diff --git a/webapp/components/postpage.jsx b/webapp/components/postpage.jsx new file mode 100644 index 0000000..b9531c0 --- /dev/null +++ b/webapp/components/postpage.jsx @@ -0,0 +1,26 @@ +var React = require('react') +var Post = require('post.jsx') + +module.exports = function(boards){ + var UserID = require('userID.jsx')(boards) + return React.createClass({ + getInitialState: function(){ + return { post: { title: '...', text: '...' }, api: boards.isInit } + }, + componentDidMount: function(){ + boards.getEventEmitter().on('init', _ => { if(this.isMounted()) this.setState({ api: true })}) + boards.downloadPost(this.props.id,this.props.admin,this.props.board,this.props.op,(err,post) => { + if(err){ + this.setState({ post: { title: 'Error', text: err.Message || err }}) + } else { + this.setState({ post }) + } + }) + }, + render: function(){ + if(this.state.api || boards.isInit) + return + else return + } + }) +} diff --git a/webapp/components/profile.jsx b/webapp/components/profile.jsx index 97fee32..36ee9df 100644 --- a/webapp/components/profile.jsx +++ b/webapp/components/profile.jsx @@ -3,32 +3,58 @@ var Markdown = require('markdown.jsx') var Link = require('react-router').Link var Icon = require('icon.jsx') -module.exports = function(boards){ +module.exports = function(boardsAPI){ + var GetIPFS = require('getipfs.jsx')(boardsAPI) return React.createClass({ getInitialState: function(){ - return { name: '...', boards: [] } + return { name: '...', boards: [], api: false } }, componentDidMount: function(){ - console.log('About to ask for profile for',this.props.params.userid) - var ee = boards.getEventEmitter() - ee.on('boards for '+this.props.params.userid,l => { - if(!this.isMounted()) return true - this.setState({ boards: l }) + boardsAPI.use(boards => { + if(boards.isInit){ + this.setState({ api: true, id: boards.id }) + this.init() + } + var ee = boards.getEventEmitter() + ee.on('init',err => { + if(!err && this.isMounted()){ + this.setState({ api: true, id: boards.id }) + this.init() + } + }) }) - boards.getProfile(this.props.params.userid,(err,res) => { - if(!this.isMounted()) return true - if(err){ - this.setState({ - name: , - description: err + }, + init: function(){ + if(this.state.init) return + boardsAPI.use(boards => { + var ee = boards.getEventEmitter() + if(boards.isInit || this.state.api){ + var uid = this.props.params.userid + if(uid === 'me') uid = boards.id + console.log('About to ask for profile for',uid) + ee.on('boards for '+uid,l => { + if(!this.isMounted()) return true + this.setState({ boards: l }) }) - } else { - this.setState({ name: res.name, description: res.description }) + boards.getProfile(uid,(err,res) => { + if(!this.isMounted()) return true + if(err){ + this.setState({ + name: , + description: err + }) + } else { + this.setState({ name: res.name, description: res.description }) + } + }) + this.setState({ init: true }) } }) }, linkToEditor: function(){ - if(this.props.params.userid === boards.id){ + var uid = this.props.params.userid + if(uid === 'me' && this.state.id) uid = this.state.id + if(uid === this.state.id){ return
This is your profile

@@ -37,18 +63,22 @@ module.exports = function(boards){ return '' }, render: function(){ - return (
- {this.linkToEditor()} -

{this.state.name}

- -
-
@{this.props.params.userid}
- {this.state.boards.map(n => { - return
- # {n.name} -
- })} -
) + if(this.state.api){ + var uid = this.props.params.userid + if(uid === 'me') uid = this.state.id + return (
+ {this.linkToEditor()} +

{this.state.name}

+ +
+
@{uid}
+ {this.state.boards.map(n => { + return
+ # {n.name} +
+ })} +
) + } else return } }) } diff --git a/webapp/components/settings.jsx b/webapp/components/settings.jsx index a752d1e..aadc8e2 100644 --- a/webapp/components/settings.jsx +++ b/webapp/components/settings.jsx @@ -1,12 +1,18 @@ var React = require('react') var Icon = require('icon.jsx') -module.exports = function(boards){ +module.exports = function(boardsAPI){ return React.createClass({ getDefaults: function(){ - return { addr: 'localhost', port: 5001 } + return { addr: 'localhost', port: 5001, api: false } }, getInitialState: function(){ + boardsAPI.use(boards => { + if(boards.isInit && this.isMounted()) this.setState({ api: true }) + boards.getEventEmitter().on('init', err => { + if(!err && this.isMounted()) this.setState({ api: true }) + }) + }) var s = localStorage.getItem('ipfs-boards-settings') var obj = this.getDefaults() try { @@ -24,7 +30,7 @@ module.exports = function(boards){ addr: this.state.addr, port: parseInt(this.state.port) })) - alert('Saved') + window.location.reload(false) } }, setDefaults: function(){ @@ -37,6 +43,14 @@ module.exports = function(boards){ this.setState({ port: event.target.value }) } }, + isOK: function(){ + if(this.state.api){ + return
+
It's OK
+

You're connected to IPFS

+
+ } + }, render: function(){ return (
@@ -54,6 +68,7 @@ module.exports = function(boards){
+ {this.isOK()}
diff --git a/webapp/components/userID.jsx b/webapp/components/userID.jsx index 725833a..d37e53c 100644 --- a/webapp/components/userID.jsx +++ b/webapp/components/userID.jsx @@ -2,15 +2,29 @@ var React = require('react') var Icon = require('icon.jsx') var Link = require('react-router').Link -module.exports = function(boards){ +module.exports = function(boardsAPI){ return React.createClass({ getInitialState: function(){ return { } }, componentDidMount: function(){ - if(this.props.id) boards.getProfile(this.props.id, (err,res) => { + boardsAPI.use(boards => { + if(boards.isInit){ + this.getProfile(boards) + } + boards.getEventEmitter().on('init',err => { + if(!err) this.getProfile(boards) + else console.log('ERR INIT',err) + }) + }) + }, + getProfile: function(boards){ + if(this.props.id === undefined) return + boards.getProfile(this.props.id, (err,res) => { if(!this.isMounted()) return true - if(!err) { + if(err){ + console.log('Error while resolving user badge:',err) + } else { this.setState({ name: res.name || 'Unknown Name' }) } }) @@ -23,15 +37,16 @@ module.exports = function(boards){ } }, render: function(){ - if(this.props.id) + if(this.props.id === undefined || this.props.id === 'undefined') + return
+ Unknown User +
+ else return (
{this.getContent()}{this.state.name || this.props.id}
) - else return
- Unknown User -
} }) } diff --git a/webapp/components/users.jsx b/webapp/components/users.jsx index e7e65f1..b8f080c 100644 --- a/webapp/components/users.jsx +++ b/webapp/components/users.jsx @@ -1,29 +1,55 @@ var React = require('react') var Icon = require('icon.jsx') -module.exports = function(boards){ - var UserID = require('userID.jsx')(boards) +module.exports = function(boardsAPI){ + var GetIPFS = require('getipfs.jsx')(boardsAPI) + var UserID = require('userID.jsx')(boardsAPI) return React.createClass({ getInitialState: function(){ - return { users: boards.getUsers() } + return { users: [], api: false } }, componentDidMount: function(){ - boards.searchUsers().on('user',(id) => { - if(id === undefined) console.log('found undefined user???') - if(this.isMounted() && this.state.users.indexOf(id) < 0) - this.setState({ users: this.state.users.concat(id) }) + boardsAPI.use(boards => { + boards.init() + if(boards.isInit){ + if(this.isMounted()){ + this.setState({ api: true }) + this.init(boards) + } + } + var ee = boards.getEventEmitter() + ee.on('init', e => { + if(!e && this.isMounted()){ + this.init(boards) + this.setState({ api: true }) + } + }) + ee.on('user',(id) => { + if(id === undefined || id === 'undefined') console.log('found undefined user???') + if(this.isMounted() && this.state.users.indexOf(id) < 0){ + this.setState({ users: this.state.users.concat(id) }) + } + }) }) }, + init: function(boards){ + if(this.isMounted() && !this.state.init){ + this.setState({ users: boards.getUsers(), init: true }) + boards.searchUsers() + } + }, render: function(){ - return
-

Users

-

Found {this.state.users.length} users

-
    - {this.state.users.map(user => { - return - })} -
-
+ if(this.state.api){ + return
+

Users

+

Found {this.state.users.length} users

+
    + {this.state.users.map(user => { + return + })} +
+
+ } else return } }) } diff --git a/webapp/style.css b/webapp/style.css index a431a5d..4615374 100644 --- a/webapp/style.css +++ b/webapp/style.css @@ -20,6 +20,11 @@ a { text-decoration: none; } +.center-block { + margin:auto; + display:block; +} + .navbar { position: fixed; width: 100%; @@ -111,6 +116,15 @@ a:hover { text-decoration: none; } +.itsok p, h5 { + display: inline-block; + margin-bottom: 1rem; +} + +.itsok h5 { + margin-right: 1rem; +} + @media (min-width: 400px) { /* larger than mobile */ .settings .buttons {