Browse Source

improved Loading

Joystream Stats 4 years ago
parent
commit
8271725bd2

+ 111 - 58
src/App.tsx

@@ -8,10 +8,7 @@ import { domain, wsLocation } from "./config";
 // types
 import { Api, Block, IState } from "./types";
 import { types } from "@joystream/types";
-//import { Observable } from "@polkadot/types/types";
 import { Seat } from "@joystream/types/augment/all/types";
-//import { Vec } from "@polkadot/types/codec";
-//import { Codec } from "@polkadot/types/types";
 import { ApiPromise, WsProvider } from "@polkadot/api";
 import { AccountId, Header } from "@polkadot/types/interfaces";
 
@@ -19,6 +16,7 @@ interface IProps {}
 
 const initialState = {
   blocks: [],
+  now: 0,
   block: 0,
   loading: true,
   nominators: [],
@@ -39,55 +37,19 @@ class App extends React.Component<IProps, IState> {
     const provider = new WsProvider(wsLocation);
     const api = await ApiPromise.create({ provider, types });
     await api.isReady;
-    this.setState({ loading: false });
 
     let blocks: Block[] = [];
     let lastBlock: Block = { id: 0, timestamp: 0, duration: 6 };
 
-    let channels = [];
-    channels[0] = await get.currentChannelId(api);
-
-    let posts = [];
-    posts[0] = await get.currentPostId(api);
-    let categories = [];
-    categories[0] = await get.currentCategoryId(api);
-    let threads = [];
-    threads[0] = await get.currentThreadId(api);
-
-    let { proposals } = this.state;
-    const proposalCount = await get.proposalCount(api);
-    for (let i = proposalCount; i > 0; i--) {
-      this.fetchProposal(api, i);
-    }
-
-    this.setState({ channels, proposalCount, posts, categories, threads });
-
-    // TODO typeof activeCouncil
-    // Type 'Codec' is missing the following properties from type 'Observable<Vec<Seat>>': _isScalar,
-    // Type 'Codec' is missing the following properties from type 'Observable<any>': _isScalar, source
-    const council: any = await api.query.council.activeCouncil();
-    console.log(`council`, council);
-    // Property 'map' does not exist on type 'Codec'.  TS2339
-    council.map((seat: Seat) => this.fetchHandle(api, seat.member));
-
-    // count nominators and validators
-    const validatorEntries = await api.query.session.validators();
-    const nominatorEntries = await api.query.staking.nominators.entries();
-
-    const validators = await validatorEntries.map((v) => {
-      this.fetchHandle(api, v.toJSON());
-      return String(v);
-    });
-    console.log(`validators`, validators);
-
-    const nominators = nominatorEntries.map((n) => {
-      const name = n[0].toHuman();
-      this.fetchHandle(api, `${name}`);
-      return `${name}`;
-    });
-    console.log(`nominators`, nominatorEntries);
-
-    this.setState({ council, nominators, validators, loading: false });
+    // let channels = [];
+    // channels[0] = await get.currentChannelId(api);
+    // let posts = [];
+    // posts[0] = await get.currentPostId(api);
+    // let categories = [];
+    // categories[0] = await get.currentCategoryId(api);
+    // let threads = [];
+    // threads[0] = await get.currentThreadId(api);
+    // this.setState({ channels, posts, categories, threads });
 
     api.rpc.chain.subscribeNewHeads(
       async (header: Header): Promise<void> => {
@@ -98,29 +60,119 @@ class App extends React.Component<IProps, IState> {
         const duration = timestamp - lastBlock.timestamp;
         const block: Block = { id, timestamp, duration };
         blocks = blocks.concat(block);
-        this.setState({ blocks, block: id });
+        this.setState({ now: timestamp, blocks, block: id, loading: false });
+
+        const proposalCount = await get.proposalCount(api);
+        if (proposalCount > this.state.proposalCount)
+          this.fetchProposal(api, proposalCount);
 
-        channels[1] = await get.currentChannelId(api);
-        proposals.current = await get.proposalCount(api);
-        categories[1] = await get.currentCategoryId(api);
-        posts[1] = await get.currentPostId(api);
-        threads[1] = await get.currentThreadId(api);
+        // channels[1] = await get.currentChannelId(api);
+        // categories[1] = await get.currentCategoryId(api);
+        // posts[1] = await get.currentPostId(api);
+        // threads[1] = await get.currentThreadId(api);
         lastBlock = block;
       }
     );
+
+    this.fetchCouncil(api);
+    this.fetchProposals(api);
+    this.fetchValidators(api);
+    this.fetchNominators(api);
+  }
+
+  async fetchCouncil(api: Api) {
+    const council: any = await api.query.council.activeCouncil();
+    this.save(`council`, council);
+    council.map((seat: Seat) => this.fetchHandle(api, seat.member));
   }
 
+  async fetchProposals(api: Api) {
+    const proposalCount = await get.proposalCount(api);
+    for (let i = proposalCount; i > 0; i--) {
+      this.fetchProposal(api, i);
+    }
+  }
   async fetchProposal(api: Api, id: number) {
-    const proposal = await get.proposalDetail(api, id);
     let { proposals } = this.state;
+    if (proposals.find((p) => p && p.id === id)) return;
+
+    const proposal = await get.proposalDetail(api, id);
+    if (!proposal) return;
     proposals[id] = proposal;
-    this.setState({ proposals });
+    this.save("proposals", proposals);
+  }
+
+  async fetchNominators(api: Api) {
+    const nominatorEntries = await api.query.staking.nominators.entries();
+    const nominators = nominatorEntries.map((n: any) => {
+      const name = n[0].toHuman();
+      this.fetchHandle(api, `${name}`);
+      return `${name}`;
+    });
+    this.save("nominators", nominators);
+  }
+  async fetchValidators(api: Api) {
+    const validatorEntries = await api.query.session.validators();
+    const validators = await validatorEntries.map((v: any) => {
+      this.fetchHandle(api, v.toJSON());
+      return String(v);
+    });
+    this.save("validators", validators);
   }
   async fetchHandle(api: Api, id: AccountId | string) {
-    const handle = await get.memberHandleByAccount(api, id);
     let { handles } = this.state;
+    if (handles[String(id)]) return;
+
+    const handle = await get.memberHandleByAccount(api, id);
     handles[String(id)] = handle;
-    this.setState({ handles });
+    this.save("handles", handles);
+  }
+
+  loadCouncil() {
+    const council = this.load("council");
+    if (council) this.setState({ council });
+  }
+  loadProposals() {
+    const proposals = this.load("proposals");
+    if (proposals) this.setState({ proposals });
+  }
+  loadValidators() {
+    const validators = this.load("validators");
+    if (validators) this.setState({ validators });
+  }
+  loadNominators() {
+    const nominators = this.load("nominators");
+    if (nominators) this.setState({ nominators });
+  }
+  loadHandles() {
+    const handles = this.load("handles");
+    if (handles) this.setState({ handles });
+  }
+  async loadData() {
+    await this.loadCouncil();
+    await this.loadProposals();
+    await this.loadValidators();
+    await this.loadNominators();
+    await this.loadHandles();
+    this.setState({ loading: false });
+  }
+
+  load(key: string) {
+    try {
+      const data = localStorage.getItem(key);
+      if (data) return JSON.parse(data);
+    } catch (e) {
+      console.log(`Failed to load ${key}`, e);
+    }
+  }
+  save(key: string, data: any) {
+    try {
+      localStorage.setItem(key, JSON.stringify(data));
+    } catch (e) {
+      console.log(`Failed to save ${key}`, e);
+    } finally {
+      this.setState({ [key]: data });
+    }
   }
 
   render() {
@@ -129,6 +181,7 @@ class App extends React.Component<IProps, IState> {
   }
 
   componentDidMount() {
+    this.loadData();
     this.initializeSocket();
   }
   componentWillUnmount() {

+ 26 - 15
src/components/Dashboard/Nominators.tsx

@@ -1,28 +1,39 @@
 import React from "react";
 import User from "../User";
 import { Handles } from "../../types";
+import Loading from "../Loading";
+
+const Nominators = (props: { nominators: string[]; handles: Handles }) => {
+  const { nominators, handles } = props;
 
-const Nominators = (props: { nominators: string[] ,handles:Handles}) => {
-  const { nominators,handles } = props;
-  //if (!nominators) return ""
-  
   const half = Math.floor(nominators.length / 2) + 1;
 
   return (
     <div className="box">
       <h3>Nominators</h3>
-      <div className="d-flex flex-row">
-        <div className="mx-1">
-          {nominators.slice(0, half).map((nominator: string) => (
-            <User key={nominator} id={nominator} handle={handles[nominator]} />
-          ))}
-        </div>
-        <div className="mx-1">
-          {nominators.slice(half).map((nominator: string) => (
-            <User key={nominator} id={nominator} handle={handles[nominator]}/>
-          ))}
+
+      {(nominators.length && (
+        <div className="d-flex flex-row">
+          <div className="mx-1">
+            {nominators.slice(0, half).map((nominator: string) => (
+              <User
+                key={nominator}
+                id={nominator}
+                handle={handles[nominator]}
+              />
+            ))}
+          </div>
+          <div className="mx-1">
+            {nominators.slice(half).map((nominator: string) => (
+              <User
+                key={nominator}
+                id={nominator}
+                handle={handles[nominator]}
+              />
+            ))}
+          </div>
         </div>
-      </div>
+      )) || <Loading />}
     </div>
   );
 };

+ 32 - 17
src/components/Dashboard/Validators.tsx

@@ -1,33 +1,48 @@
 import React from "react";
 import User from "../User";
 import { Handles } from "../../types";
+import Loading from "../Loading";
 
 const Validators = (props: { validators: string[]; handles: Handles }) => {
   const { validators, handles } = props;
-  //if (!validators) return "";
 
   const third = Math.floor(validators.length / 3) + 1;
 
   return (
     <div className="box">
       <h3>Validators</h3>
-      <div className="d-flex flex-row">
-        <div className="mx-1">
-          {validators.slice(0, third).map((validator: string) => (
-            <User key={validator} id={validator} handle={handles[validator]} />
-          ))}
-        </div>
-        <div className="mx-1">
-          {validators.slice(third, third * 2).map((validator: string) => (
-            <User key={validator} id={validator} handle={handles[validator]} />
-          ))}
-        </div>
-        <div className="mx-1">
-          {validators.slice(third * 2).map((validator: string) => (
-            <User key={validator} id={validator} handle={handles[validator]} />
-          ))}
+
+      {(validators.length && (
+        <div className="d-flex flex-row">
+          <div className="mx-1">
+            {validators.slice(0, third).map((validator: string) => (
+              <User
+                key={validator}
+                id={validator}
+                handle={handles[validator]}
+              />
+            ))}
+          </div>
+          <div className="mx-1">
+            {validators.slice(third, third * 2).map((validator: string) => (
+              <User
+                key={validator}
+                id={validator}
+                handle={handles[validator]}
+              />
+            ))}
+          </div>
+          <div className="mx-1">
+            {validators.slice(third * 2).map((validator: string) => (
+              <User
+                key={validator}
+                id={validator}
+                handle={handles[validator]}
+              />
+            ))}
+          </div>
         </div>
-      </div>
+      )) || <Loading />}
     </div>
   );
 };

+ 2 - 1
src/components/Dashboard/index.tsx

@@ -3,6 +3,7 @@ import { ActiveProposals, Council } from "..";
 import Nominators from "./Nominators";
 import Validators from "./Validators";
 import { IState } from "../../types";
+import Loading from "../Loading";
 
 const Dashboard = (props: IState) => {
   return (
@@ -14,7 +15,7 @@ const Dashboard = (props: IState) => {
       </div>
       <div className="box mt-3">
         <h3>latest block</h3>
-        {props.block}
+        {props.block ? props.block : <Loading />}
       </div>
       <div className="box">
         <h3>Active Proposals</h3>

+ 1 - 1
src/components/Loading.tsx

@@ -3,7 +3,7 @@ import { Spinner } from "react-bootstrap";
 
 const Loading = () => {
   return (
-    <div className="h-100 bg-dark d-flex flex-column flex-grow-1 align-items-center justify-content-center">
+    <div className="h-100 d-flex flex-column flex-grow-1 align-items-center justify-content-center">
       <Spinner
         animation="grow"
         variant="light"

+ 3 - 1
src/components/Proposals/Active.tsx

@@ -1,14 +1,16 @@
 import React from "react";
 import Proposal from "./ProposalOverlay";
 import { ProposalDetail } from "../../types";
+import Loading from "../Loading";
 
 const ActiveProposals = (props: {
   block: number;
   proposals: ProposalDetail[];
 }) => {
   const { block, proposals } = props;
-  const active = proposals.filter((p) => p.stage === "Active");
+  const active = proposals.filter((p) => p && p.stage === "Active");
 
+  if (!proposals.length) return <Loading />;
   if (!active.length) return <div className="box">No active proposals.</div>;
 
   return (

+ 2 - 2
src/components/Proposals/ProposalOverlay.tsx

@@ -31,8 +31,8 @@ const ProposalOverlay = (props: {
           </div>
 
           <div className="my-2 p-1 bg-light  text-secondary text-left">
-            {props.message.split(/\n/).map((line: string) => (
-              <div>{htmr(line)}</div>
+            {props.message.split(/\n/).map((line: string, i: number) => (
+              <div key={i}>{htmr(line)}</div>
             ))}
           </div>
 

+ 1 - 0
src/types.ts

@@ -10,6 +10,7 @@ export interface Api {
 }
 
 export interface IState {
+  now: number;
   block: number;
   blocks: Block[];
   nominators: string[];