diff --git a/src/components/App.css b/src/components/App.css
index 23dfe3ba..776bf2d5 100644
--- a/src/components/App.css
+++ b/src/components/App.css
@@ -50,7 +50,7 @@ code {
code p{
margin-bottom: 0.5rem;
}
-
+
.favourite-on .on,
.favourite-off .off {
display: block !important;
@@ -71,4 +71,12 @@ code p{
.favourite-off:hover .off {
display: none !important;
}
-}
\ No newline at end of file
+}
+
+pre.pre-wrap {
+ white-space: pre-wrap; /* css-3 */
+ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
+ white-space: -pre-wrap; /* Opera 4-6 */
+ white-space: -o-pre-wrap; /* Opera 7 */
+ word-wrap: break-word; /* Internet Explorer 5.5+ */
+ }
diff --git a/src/components/ProposalDetails.js b/src/components/ProposalDetails.js
index f622890b..5e2939da 100644
--- a/src/components/ProposalDetails.js
+++ b/src/components/ProposalDetails.js
@@ -5,10 +5,13 @@ import remarkGfm from 'remark-gfm'
import {
Table,
+ Tab,
+ Nav
} from 'react-bootstrap'
import Coins from './Coins';
import ProposalProgress from './ProposalProgress';
+import ProposalMessages from './ProposalMessages';
import VoteForm from './VoteForm';
import AlertMessage from './AlertMessage';
import Vote from '../utils/Vote.mjs';
@@ -156,26 +159,47 @@ function ProposalDetails(props) {
tally={tally}
height={25} />
-
+
+
+
+
+
+
+
+
+
+
+
>
)
}
diff --git a/src/components/ProposalMessages.js b/src/components/ProposalMessages.js
new file mode 100644
index 00000000..b7ac9511
--- /dev/null
+++ b/src/components/ProposalMessages.js
@@ -0,0 +1,101 @@
+import React from 'react';
+import {
+ Table,
+} from 'react-bootstrap'
+import moment from 'moment'
+import _ from 'lodash'
+import Moment from 'react-moment';
+import Coins from './Coins';
+
+function ProposalMessages(props) {
+ const { proposal, network } = props
+
+ const { content, messages } = proposal
+
+ let contentMessages = []
+
+ if(messages){
+ messages.forEach(message => {
+ if(message['@type'] === '/cosmos.gov.v1.MsgExecLegacyContent'){
+ contentMessages.push(message.content)
+ }else{
+ contentMessages.push(message)
+ }
+ })
+ }else{
+ contentMessages.push(content)
+ }
+
+ function messageData(message){
+ const data = _.omit(message, 'title', 'name', '@type', 'description')
+ switch (message['@type']) {
+ case '/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal':
+ return {
+ name: message.plan.name,
+ height: message.plan.height,
+ estimated_time: () => {
+ return (
+
+ {moment().add(network.timeToBlock(message.plan.height), 'seconds')}
+
+ )
+ }
+ }
+ case '/cosmos.distribution.v1beta1.CommunityPoolSpendProposal':
+ return {
+ ...data,
+ amount: () => {
+ return data.amount.map(coin => {
+ return
+ })
+ }
+ }
+ default:
+ return _.mapValues(data, value => {
+ if(typeof value === 'object' && value !== null){
+ return {JSON.stringify(value, undefined, 2)}
+ }else if(typeof value == "boolean"){
+ return value ? 'true' : 'false'
+ }else{
+ return value
+ }
+ })
+ }
+ }
+
+ function renderMessage(message){
+ const data = messageData(message)
+ return (
+ <>
+ {message.title || message.name} {message['@type']}
+
+
+ {Object.entries(data).map(([key, value]) => {
+ return (
+
+ {_.startCase(key)} |
+ {typeof value === 'function' ? value() : value} |
+
+ )
+ })}
+
+
+ >
+ )
+ }
+
+ return (
+ <>
+ {contentMessages.map((message, index) => {
+ return (
+
+ {index > 0 &&
}
+ {renderMessage(message)}
+
+ )
+ })}
+ >
+ )
+}
+
+export default ProposalMessages
diff --git a/src/components/Voting.js b/src/components/Voting.js
index 2a1d70e4..43f68258 100644
--- a/src/components/Voting.js
+++ b/src/components/Voting.js
@@ -8,7 +8,7 @@ import { useParams, useNavigate } from "react-router-dom";
import AlertMessage from './AlertMessage';
import Proposals from './Proposals';
-import { executeSync } from '../utils/Helpers.mjs';
+import { executeSync, mapSync } from '../utils/Helpers.mjs';
import ProposalModal from './ProposalModal';
import Proposal from '../utils/Proposal.mjs';
import Vote from '../utils/Vote.mjs';
@@ -31,7 +31,7 @@ function Voting(props) {
const params = useParams();
const voteGrants = (wallet?.grants || []).filter(grant => {
- return grant.authorization['@type'] === '/cosmos.authz.v1beta1.GenericAuthorization' &&
+ return grant.authorization['@type'] === '/cosmos.authz.v1beta1.GenericAuthorization' &&
grant.authorization.msg === '/cosmos.gov.v1beta1.MsgVote'
})
@@ -84,7 +84,12 @@ function Voting(props) {
try {
let newProposals = await props.queryClient.getProposals()
- newProposals = newProposals.map(el => Proposal(el))
+ newProposals = await mapSync(newProposals.map(el => {
+ return async () => {
+ return await Proposal(el)
+ }
+ }))
+
setError()
setProposals(sortProposals(newProposals))
setTallies(newProposals.reduce((sum, proposal) => {
@@ -94,7 +99,7 @@ function Voting(props) {
return sum
}, {}))
} catch (error) {
- if (!proposals || clearExisting) setProposals([])
+ if (clearExisting) setProposals([])
setError(`Failed to load proposals: ${error.message}`);
}
}
@@ -211,4 +216,4 @@ function Voting(props) {
);
}
-export default Voting;
\ No newline at end of file
+export default Voting;
diff --git a/src/utils/Network.mjs b/src/utils/Network.mjs
index 859dc684..49727e6e 100644
--- a/src/utils/Network.mjs
+++ b/src/utils/Network.mjs
@@ -183,6 +183,17 @@ class Network {
this.authzAminoSupport && 'full authz ledger',
])
}
+
+ timeToBlock(height){
+ const params = this.chain.params
+ const currentHeight = params.current_block_height
+ const blockTime = params.actual_block_time
+ return (height - currentHeight) * blockTime
+ }
+
+ assetForDenom(denom){
+ return this.assets.find(el => el.base.denom === denom)
+ }
}
export default Network;
diff --git a/src/utils/Proposal.mjs b/src/utils/Proposal.mjs
index d6cf222d..628a725b 100644
--- a/src/utils/Proposal.mjs
+++ b/src/utils/Proposal.mjs
@@ -1,3 +1,5 @@
+import axios from 'axios'
+
export const PROPOSAL_STATUSES = {
'': 'All',
'PROPOSAL_STATUS_DEPOSIT_PERIOD': 'Deposit Period',
@@ -13,18 +15,32 @@ export const PROPOSAL_SCAM_URLS = [
'cosmos-network.io'
]
-const Proposal = (data) => {
- let { proposal_id, content, messages, metadata } = data
+const Proposal = async (data) => {
+ let { proposal_id, content, messages, metadata, title, summary: description } = data
if(!proposal_id && data.id) proposal_id = data.id
- let title, description, typeHuman
+ let typeHuman
if(metadata){
try {
metadata = JSON.parse(metadata)
- title = metadata.title
- description = metadata.summary
- } catch (e) {
- console.log(e)
+ title = title || metadata.title
+ description = description || metadata.summary
+ } catch {
+ try {
+ let ipfsUrl
+ if(metadata.startsWith('ipfs://')){
+ ipfsUrl = metadata.replace("ipfs://", "https://ipfs.io/ipfs/")
+ }else if(metadata.startsWith('https://')){
+ ipfsUrl = metadata
+ }else{
+ ipfsUrl = `https://ipfs.io/ipfs/${metadata}`
+ }
+ metadata = await axios.get(ipfsUrl).then(res => res.data)
+ title = metadata.title
+ description = metadata.summary || metadata.description
+ } catch (e) {
+ console.log(e)
+ }
}
}
@@ -43,7 +59,6 @@ const Proposal = (data) => {
title = title || typeHuman
description = description || metadata
-
const statusHuman = PROPOSAL_STATUSES[data.status]
const isDeposit = data.status === 'PROPOSAL_STATUS_DEPOSIT_PERIOD'
@@ -59,7 +74,6 @@ const Proposal = (data) => {
description,
content,
metadata,
- messages,
isDeposit,
isVoting,
isSpam