Kaynağa Gözat

MVP block explorer

Joystream Stats 4 yıl önce
ebeveyn
işleme
3a19437b40

+ 9 - 7
package.json

@@ -1,8 +1,9 @@
 {
-  "name": "socket-example",
+  "name": "jsstats",
   "version": "0.0.1",
   "private": true,
   "dependencies": {
+    "@joystream/types": "^0.14.0",
     "bootstrap": "^4.3.1",
     "chalk": "^4.1.0",
     "concurrently": "^4.1.1",
@@ -10,25 +11,26 @@
     "cors": "^2.8.5",
     "express": "^4.17.1",
     "express-session": "^1.16.2",
-    "moment": "^2.24.0",
+    "moment": "^2.29.1",
     "morgan": "^1.9.1",
     "multer": "^1.4.2",
     "nodemon": "^1.19.1",
     "passport": "^0.4.0",
     "passport-local": "^1.0.0",
-    "pg": "^7.12.0",
-    "react": "^16.8.6",
+    "pg": "^8.5.1",
+    "react": "^17.0.1",
     "react-bootstrap": "^1.0.0-beta.9",
-    "react-dom": "^16.8.6",
+    "react-dom": "^17.0.1",
     "react-feather": "^2.0.3",
     "react-router-dom": "^5.0.1",
-    "react-scripts": "3.1.1",
+    "react-scripts": "^4.0.2",
+    "react-transition-group": "^4.4.1",
     "react-typography": "^0.16.19",
     "sequelize": "^5.12.2",
     "socket.io": "^2.2.0"
   },
   "scripts": {
-    "start": "concurrently \"react-scripts start\" \"nodemon server\"",
+    "start": "concurrently \"PORT=3050 HOST=127.0.0.1 react-scripts start\" \"nodemon server\"",
     "build": "react-scripts build",
     "prod": "node server",
     "test": "react-scripts test --env=jsdom",

+ 14 - 0
server/db/models/block.js

@@ -0,0 +1,14 @@
+const Sequelize = require('sequelize')
+const db = require('../db')
+
+const Block = db.define('block', {
+  id: {
+    type: Sequelize.INTEGER,
+    primaryKey: true,
+  },
+  timestamp: Sequelize.DATE,
+  blocktime: Sequelize.INTEGER,
+  author: Sequelize.STRING,
+})
+
+module.exports = Block

+ 3 - 2
server/db/models/index.js

@@ -1,2 +1,3 @@
-//const Questions = require("./questions");
-//module.exports = { Questions };
+const Block = require("./block");
+
+module.exports = { Block };

+ 19 - 0
server/db/seed.js

@@ -0,0 +1,19 @@
+const db = require('../db')
+const { Block } = require('./models')
+
+async function runSeed() {
+  await db.sync({ force: true })
+  console.log('db synced!')
+  console.log('seeding...')
+  try {
+  } catch (err) {
+    console.error(`sequelize error:`, err)
+    process.exitCode = 1
+  } finally {
+    console.log('closing db connection')
+    await db.close()
+    console.log('db connection closed')
+  }
+}
+
+runSeed()

+ 1 - 1
server/index.js

@@ -17,7 +17,7 @@ const chalk = require("chalk");
 //const bot = require('../ircbot')
 
 const PORT = process.env.PORT || 3500;
-//const URL = ["http://localhost:3400"];
+//const URL = ["http://localhost:3050"];
 
 app.use(morgan("dev"));
 //app.use(cors({ credentials: true, origin: URL }))

+ 42 - 12
server/socket.js

@@ -1,12 +1,42 @@
-//const {} = require('../db/models')
-const chalk = require("chalk");
-
-module.exports = io => {
-  io.on("connection", socket => {
-    socket.emit("welcome", "Websockets are awesome!");
-    console.log(chalk.green(`[socket.io] Connection: ${socket.id}`));
-    socket.on("disconnect", async () => {
-      console.log(chalk.red(`[socket.io] Disconnect: ${socket.id}`));
-    });
-  });
-};
+const { Block } = require('./db/models')
+const chalk = require('chalk')
+
+const { types } = require('@joystream/types')
+const { ApiPromise, WsProvider } = require('@polkadot/api')
+const wsLocation = 'wss://rome-rpc-endpoint.joystream.org:9944'
+
+module.exports = (io) => {
+  const initializeSocket = async () => {
+    console.debug(`[Joystream] Connecting to ${wsLocation}`)
+    const provider = new WsProvider(wsLocation)
+    const api = await ApiPromise.create({ provider, types })
+    await api.isReady
+    console.debug(`[Joystream] Connected.`)
+
+    api.derive.chain.subscribeNewHeads(async (header) => {
+      const id = Number(header.number)
+      const exists = await Block.findByPk(id)
+      if (exists) return
+      const timestamp = (await api.query.timestamp.now()).toNumber()
+      const last = await Block.findByPk(id - 1)
+      const blocktime = last ? timestamp - last.timestamp : 6000
+      const block = await Block.create({
+        id,
+        timestamp,
+        blocktime,
+        author: header.author?.toString(),
+      })
+      console.log('[Joystream] block', block.id, block.blocktime, block.author)
+      io.emit('block', block)
+    })
+  }
+
+  io.on('connection', (socket) => {
+    socket.emit('welcome', 'Websockets are awesome!')
+    console.log(chalk.green(`[socket.io] Connection: ${socket.id}`))
+    socket.on('disconnect', async () => {
+      console.log(chalk.red(`[socket.io] Disconnect: ${socket.id}`))
+    })
+  })
+  initializeSocket()
+}

+ 29 - 39
src/App.js

@@ -1,60 +1,50 @@
-import React from "react";
-import { Layout, Loading } from "./components";
-import { withRouter } from "react-router-dom";
-import socket from "./socket";
+import React from 'react'
+import { Layout, Loading, Main } from './components'
+import { withRouter } from 'react-router-dom'
+import socket from './socket'
 
-const initialState = { loading: true };
+const initialState = { loading: true, blocks: [], timestamp: 0 }
 
 class App extends React.Component {
   initializeSocket() {
-    socket.on("connect", () => {
-      if (!socket.id) console.log("no websocket connection");
-      else console.log("my socketId:", socket.id);
-    });
-    socket.on("welcome", message => {
-      this.setState({ loading: false, message });
-    });
+    socket.on('connect', () => {
+      if (!socket.id) console.log('no websocket connection')
+      else console.log('my socketId:', socket.id)
+    })
+    socket.on('block', (block) => {
+      const blocks = this.state.blocks
+        .filter((b) => b.id !== block.id)
+        .concat(block)
+        .sort((a, b) => b.id - a.id)
+      this.setState({ loading: false, blocks, timestamp: block.timestamp })
+    })
   }
 
-  async fetchData() {
-    // initial axios requests go here
-    this.setState({ loading: false });
-  }
-
-  /** RENDER FUNCTIONS **/
-  renderLoading() {
-    return <Loading />;
-  }
   renderError() {
-    if (this.state.showModal === "Error") return;
-    this.setShowModal("Error");
+    if (this.state.showModal === 'Error') return
+    this.setShowModal('Error')
   }
   renderApp() {
-    const message = this.state.message || "Fresh and clean.";
-    return (
-      <div className="w-100 h-100 d-flex flex-grow-1 align-items-center justify-content-center">
-        <h1>{message}</h1>
-      </div>
-    );
+    return <Main {...this.state} />
   }
   render() {
-    if (this.state.loading) return this.renderLoading();
-    if (this.state.error) this.renderError();
-    if (this.state.component === "layout") return <Layout {...this.state} />;
-    return this.renderApp();
+    if (this.state.loading) return <Loading />
+    if (this.state.error) this.renderError()
+    if (this.state.component === 'layout') return <Layout {...this.state} />
+    return this.renderApp()
   }
 
   componentDidMount() {
-    this.initializeSocket();
-    //this.fetchData();
+    this.initializeSocket()
+    setInterval(() => this.setState({}), 100)
   }
   componentWillUnmount() {
-    console.log("unmounting...");
+    console.log('unmounting...')
   }
   constructor() {
-    super();
-    this.state = initialState;
+    super()
+    this.state = initialState
   }
 }
 
-export default withRouter(App);
+export default withRouter(App)

+ 111 - 0
src/components/Main/index.js

@@ -0,0 +1,111 @@
+import React from 'react'
+import moment from 'moment'
+import { CSSTransition, TransitionGroup } from 'react-transition-group'
+
+const Main = (props) => {
+  const { blocks, timestamp } = props
+  const timePassed = moment().diff(timestamp) / 1000
+
+  return (
+    <div className="w-100 h-100 d-flex flex-column overflow-hidden">
+      <div className="h-100 flex-grow-1 d-flex flex-row justify-content-between">
+        <div className="col-4 p-3 text-light" style={{ background: '#000' }}>
+          <div className="float-right mt-3">
+            <b>{timePassed.toFixed(1)} s</b>
+          </div>
+          <h3>recent blocks</h3>
+          <TransitionGroup>
+            {blocks.map(({ id, author, blocktime, timestamp }) => (
+              <CSSTransition
+                in={true}
+                classNames="block"
+                timeout={100}
+                key={id}
+                appear
+              >
+                <div
+                  key={id}
+                  className="block text-center text-dark bg-light m-2 p-2"
+                >
+                  <div className="float-left">
+                    <b>{id}</b>
+                  </div>
+                  <div className="float-right">
+                    {moment(timestamp).format('HH:mm:ss')}
+                  </div>
+                  {(blocktime / 1000).toFixed(3)} s
+                  <div>
+                    <small>{author}</small>
+                  </div>
+                </div>
+              </CSSTransition>
+            ))}
+          </TransitionGroup>
+        </div>
+        <div className="col-4 text-center">
+          <h3>council</h3>
+          <div className="d-flex flex-row justify-content-between mx-3">
+            <div className="d-flex flex-column">
+              <div>1</div>
+              <div>2</div>
+              <div>3</div>
+              <div>4</div>
+              <div>5</div>
+              <div>6</div>
+            </div>
+            <div className="d-flex flex-column">
+              <div>7</div>
+              <div>8</div>
+              <div>9</div>
+              <div>10</div>
+              <div>11</div>
+              <div>12</div>
+            </div>
+          </div>
+          <h3>Proposals</h3>
+          <div>200</div>
+          <div>199</div>
+          <div>198</div>
+          <div>197</div>
+          <div>196</div>
+          <a href="/">more</a>
+        </div>
+        <div className="col-4 text-center">
+          <h3>forum</h3>
+          <div>post1</div>
+          <div>post2</div>
+          <div>post3</div>
+          <div>post4</div>
+          <div>post5</div>
+        </div>
+      </div>
+      <div
+        className="position-fixed w-100 p-3 text-light"
+        style={{ bottom: '0px', height: '140px', background: '#000' }}
+      >
+        <small className="float-right p-2 text-right">
+          734 resources
+          <br />
+          68 categories
+          <br />
+          20 channels
+          <br />
+          1231 hours
+          <br />
+          12389 GB
+        </small>
+        <div className="d-flex flex-row justify-content-between overflow-auto">
+          {[10, `schism1`, 66, `schism2`, 15].map((i) => (
+            <img
+              src={`https://eu-central-1.linodeobjects.com/joystream/${i}.jpg`}
+              width="200"
+              alt={`${i}`}
+            />
+          ))}
+        </div>
+      </div>
+    </div>
+  )
+}
+
+export default Main

+ 1 - 0
src/components/index.js

@@ -1,2 +1,3 @@
 export { default as Layout } from "./Layout";
 export { default as Loading } from "./Loading";
+export { default as Main } from "./Main";

+ 23 - 0
src/main.css

@@ -40,3 +40,26 @@ a:active {
   color: #000;
   text-decoration: none;
 }
+
+
+:root {
+  --timeout: 500ms;
+  --list-item-max-height: 30px;
+}
+
+.block-enter, .block-appear {
+  top: var(--menu-starting-top);
+  width: var(--toggler-width);
+  max-height: var(--toggler-height);
+  color: var(--fade-from-color);
+  background-color: var(--toggler-bg-color);
+}
+
+.block-enter-active, .block-appear-active {
+  top: var(--menu-ending-top);
+  width: var(--menu-width);
+  max-height: var(--menu-max-height);
+  color: var(--fade-to-color);
+  background-color: var(--menu-bg-color);
+  transition: all var(--timeout);
+}