WIP commit on proposals: have it pulling all timelock accounts and storing them clientside. Now need to fix formatting and stand up an index page.

This commit is contained in:
Dummy Tester 123 2021-02-20 10:54:44 -06:00
parent 143a50ce69
commit 9b091622bd
8 changed files with 200 additions and 48 deletions

View File

@ -0,0 +1,74 @@
// credit https://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
export function toUTF8Array(str: string) {
let utf8 = [];
for (let i = 0; i < str.length; i++) {
let charcode = str.charCodeAt(i);
if (charcode < 0x80) utf8.push(charcode);
else if (charcode < 0x800) {
utf8.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f));
} else if (charcode < 0xd800 || charcode >= 0xe000) {
utf8.push(
0xe0 | (charcode >> 12),
0x80 | ((charcode >> 6) & 0x3f),
0x80 | (charcode & 0x3f),
);
}
// surrogate pair
else {
i++;
// UTF-16 encodes 0x10000-0x10FFFF by
// subtracting 0x10000 and splitting the
// 20 bits of 0x0-0xFFFFF into two halves
charcode =
0x10000 + (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
utf8.push(
0xf0 | (charcode >> 18),
0x80 | ((charcode >> 12) & 0x3f),
0x80 | ((charcode >> 6) & 0x3f),
0x80 | (charcode & 0x3f),
);
}
}
return utf8;
}
//courtesy https://gist.github.com/joni/3760795
export function fromUTF8Array(data: number[]) {
// array of bytes
let str = '',
i;
for (i = 0; i < data.length; i++) {
const value = data[i];
if (value < 0x80) {
str += String.fromCharCode(value);
} else if (value > 0xbf && value < 0xe0) {
str += String.fromCharCode(((value & 0x1f) << 6) | (data[i + 1] & 0x3f));
i += 1;
} else if (value > 0xdf && value < 0xf0) {
str += String.fromCharCode(
((value & 0x0f) << 12) |
((data[i + 1] & 0x3f) << 6) |
(data[i + 2] & 0x3f),
);
i += 2;
} else {
// surrogate pair
const charCode =
(((value & 0x07) << 18) |
((data[i + 1] & 0x3f) << 12) |
((data[i + 2] & 0x3f) << 6) |
(data[i + 3] & 0x3f)) -
0x010000;
str += String.fromCharCode(
(charCode >> 10) | 0xd800,
(charCode & 0x03ff) | 0xdc00,
);
i += 3;
}
}
return str;
}

View File

@ -18962,12 +18962,12 @@
}
},
"@umijs/route-utils": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/@umijs/route-utils/-/route-utils-1.0.34.tgz",
"integrity": "sha512-5TUZCm5uuS3NbzdaoRDAfAIy6LDu+OCNL8xd+Dhth3YVFvbYkIeIG9/xl/5x1F+sWU8X/Oujlo6rfR8kgdB0Wg==",
"version": "1.0.36",
"resolved": "https://registry.npmjs.org/@umijs/route-utils/-/route-utils-1.0.36.tgz",
"integrity": "sha512-e3TwjlqSmhZAQ3rIE++VJ3wliwZ4cGHZhvGtPS1QohCPkVILxA4oTJIyKgzJGUFEJcWbEB1CxiimLnPtZnBd/A==",
"requires": {
"@qixian.cs/path-to-regexp": "^6.1.0",
"hash.js": "^1.1.7",
"fast-deep-equal": "^3.1.3",
"lodash.isequal": "^4.5.0",
"memoize-one": "^5.1.1"
}
@ -21475,11 +21475,6 @@
}
}
},
"css-mediaquery": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz",
"integrity": "sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA="
},
"css-prefers-color-scheme": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz",
@ -24475,11 +24470,6 @@
"strip-url-auth": "^1.0.0"
}
},
"hyphenate-style-name": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -26319,14 +26309,6 @@
"object-visit": "^1.0.0"
}
},
"matchmediaquery": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.3.1.tgz",
"integrity": "sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ==",
"requires": {
"css-mediaquery": "^0.1.2"
}
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -29612,17 +29594,6 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-responsive": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-8.2.0.tgz",
"integrity": "sha512-iagCqVrw4QSjhxKp3I/YK6+ODkWY6G+YPElvdYKiUUbywwh9Ds0M7r26Fj2/7dWFFbOpcGnJE6uE7aMck8j5Qg==",
"requires": {
"hyphenate-style-name": "^1.0.0",
"matchmediaquery": "^0.3.0",
"prop-types": "^15.6.1",
"shallow-equal": "^1.1.0"
}
},
"react-router": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
@ -32135,12 +32106,9 @@
"integrity": "sha512-P/AgEKXphcN0L/8G5wLfbEz88mZQ9ayHS1OVESZaS2nxkN/msDWTGE8E1e9HXOWCZ9yoAfDrbVKAmTYpsYupFA=="
},
"use-media-antd-query": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/use-media-antd-query/-/use-media-antd-query-1.0.3.tgz",
"integrity": "sha512-r8UnYLYYKwQIEIkWQQYs/ohJ0pqNGel9nbWHj5VBCtXtixT0ozj+X9PBlskv2HEOPfBLfnJrazpLgIyziXCU8A==",
"requires": {
"react-responsive": "^8.0.1"
}
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/use-media-antd-query/-/use-media-antd-query-1.0.6.tgz",
"integrity": "sha512-uEP116w7LH2z4MatYM0UMKD0lI4awPtXh2WrbeUfG5mCS1UD9D7lZVz9QsRThTIButxNnx9LjPSBEBD8G3Z5BA=="
},
"use-url-search-params": {
"version": "2.3.13",

View File

@ -1,5 +1,5 @@
@import '~antd/es/style/themes/dark.less';
@import "~antd/dist/antd.dark.less";
@import '~antd/dist/antd.dark.less';
@primary-color: #ff00a8;
@popover-background: #1a2029;

View File

@ -31,7 +31,7 @@ export const AppLayout = React.memo((props: any) => {
[...Object.keys(paths)].find(key => location.pathname.startsWith(key)) ||
'';
const defaultKey = paths[current] || '1';
const theme = 'light';
const theme = 'dark';
return (
<div className="App">

View File

@ -0,0 +1,12 @@
import React, { useEffect, useState } from 'react';
import { contexts } from '@oyster/common';
import { Card } from 'antd';
import { useProposals } from '../../contexts/proposals';
export function Proposal() {
const context = useProposals();
const proposal =
context.proposals['svXV4BxjpqQHVSNPoM15XecVj2RJ431xHrpxXDFfmGX'];
console.log('please', proposal);
return <Card>{proposal?.info.state.name}</Card>;
}

View File

@ -0,0 +1,89 @@
import React, { useContext, useEffect, useState } from 'react';
import {
Connection,
KeyedAccountInfo,
PublicKeyAndAccount,
} from '@solana/web3.js';
import { useMemo } from 'react';
import { contexts, utils, ParsedAccount } from '@oyster/common';
import {
TimelockSet,
TimelockSetLayout,
TimelockSetParser,
} from '../models/timelock';
const { useConnectionConfig } = contexts.Connection;
const { cache } = contexts.Accounts;
export interface ProposalsContextState {
proposals: Record<string, ParsedAccount<TimelockSet>>;
}
export const ProposalsContext = React.createContext<ProposalsContextState | null>(
null,
);
export default function ProposalsProvider({ children = null as any }) {
const { endpoint } = useConnectionConfig();
const connection = useMemo(() => new Connection(endpoint, 'recent'), [
endpoint,
]);
const PROGRAM_IDS = utils.programIds();
const [proposals, setProposals] = useState({});
useEffect(() => {
const queryProposals = async () => {
const programAccounts = await connection.getProgramAccounts(
PROGRAM_IDS.timelock.programId,
);
return programAccounts;
};
Promise.all([queryProposals()]).then(
(all: PublicKeyAndAccount<Buffer>[][]) => {
const newProposals: Record<string, ParsedAccount<TimelockSet>> = {};
all[0].forEach(a => {
if (a.account.data.length === TimelockSetLayout.span) {
cache.add(a.pubkey, a.account, TimelockSetParser);
const cached = cache.get(a.pubkey) as ParsedAccount<TimelockSet>;
console.log('Got', a.pubkey.toBase58());
newProposals[a.pubkey.toBase58()] = cached;
}
});
setProposals(newProposals);
},
);
const subID = connection.onProgramAccountChange(
PROGRAM_IDS.timelock.programId,
async (info: KeyedAccountInfo) => {
if (info.accountInfo.data.length === TimelockSetLayout.span) {
cache.add(info.accountId, info.accountInfo, TimelockSetParser);
const cached = cache.get(
info.accountId,
) as ParsedAccount<TimelockSet>;
setProposals(proposals => ({
...proposals,
[info.accountId.toBase58()]: cached,
}));
}
},
'singleGossip',
);
return () => {
connection.removeProgramAccountChangeListener(subID);
};
}, [connection, PROGRAM_IDS.timelock.programAccountId]);
return (
<ProposalsContext.Provider value={{ proposals }}>
{children}
</ProposalsContext.Provider>
);
}
export const useProposals = () => {
const context = useContext(ProposalsContext);
return context as ProposalsContextState;
};

View File

@ -2,7 +2,7 @@ import { HashRouter, Route, Switch } from 'react-router-dom';
import React from 'react';
import { contexts } from '@oyster/common';
import { AppLayout } from './components/Layout';
import ProposalsProvider from './contexts/proposals';
import { DashboardView, HomeView } from './views';
const { WalletProvider } = contexts.Wallet;
const { ConnectionProvider } = contexts.Connection;
@ -15,12 +15,18 @@ export function Routes() {
<ConnectionProvider>
<WalletProvider>
<AccountsProvider>
<AppLayout>
<Switch>
<Route exact path="/" component={() => <HomeView />} />
<Route exact path="/dashboard" children={<DashboardView />} />
</Switch>
</AppLayout>
<ProposalsProvider>
<AppLayout>
<Switch>
<Route exact path="/" component={() => <HomeView />} />
<Route
exact
path="/dashboard"
children={<DashboardView />}
/>
</Switch>
</AppLayout>
</ProposalsProvider>
</AccountsProvider>
</WalletProvider>
</ConnectionProvider>

View File

@ -4,6 +4,8 @@ import { GUTTER } from '../../constants';
import { Button } from 'antd';
import { createProposal } from '../../actions/createProposal';
import { contexts } from '@oyster/common';
import { Proposal } from '../../components/Proposal';
import { ProposalsContext } from '../../contexts/proposals';
const { useWallet } = contexts.Wallet;
const { useConnection } = contexts.Connection;
export const HomeView = () => {
@ -15,6 +17,7 @@ export const HomeView = () => {
<Button onClick={() => createProposal(connection, wallet.wallet)}>
Click me
</Button>
<Proposal />
</Row>
</div>
);