on master help sign in
feat: de-deup pointhome, add birthday and invite info to point cache
0ad03e1Matt Condon (shrugs)
src/components/Passport.js+13−1
@@ -1,12 +1,24 @@
1import React from 'react';
1import React from 'react';
2import * as ob from 'urbit-ob';
2import * as ob from 'urbit-ob';
3import { Grid, Flex } from 'indigo-react';
3import { Grid, Flex } from 'indigo-react';
4+import { times } from 'lodash';
4
5
5import Sigil from './Sigil';
6import Sigil from './Sigil';
7+import usePointBirthday from 'lib/usePointBirthday';
8+import { formatDots } from 'lib/dateFormat';
9+import { kLoadingCharacter } from 'lib/loadingCharacter';
10+
11+const kLoadingDate = [4, 2, 2]
12+ .map(t => times(t, () => kLoadingCharacter).join(''))
13+ .join('.');
6
14
7export default function Passport({ point }) {
15export default function Passport({ point }) {
16+ const birthday = usePointBirthday(point);
17+ const birthDate = birthday.matchWith({
18+ Nothing: () => kLoadingDate,
19+ Just: p => formatDots(p.value),
20+ });
8 const name = ob.patp(point);
21 const name = ob.patp(point);
9− const birthDate = '2019.1.27';
10
22
11 return (
23 return (
12 <Grid gap={16} className="bg-black r8 p7">
24 <Grid gap={16} className="bg-black r8 p7">
src/lib/dateFormat.jsnew file+8
@@ -0,0 +1,8 @@
1+// NB(shrugs): if we get any more complicated with dates, add `luxon`
2+export const formatDots = date => {
3+ const year = date.getFullYear();
4+ const month = `${date.getMonth() + 1}`.padStart(2, 0);
5+ const day = `${date.getDate()}`.padStart(2, 0);
6+
7+ return [year, month, day].join('.');
8+};
src/lib/loadingCharacter.js+3−1
@@ -1,5 +1,7 @@
1+export const kLoadingCharacter = '▓';
2+
1export default val =>
3export default val =>
2 val.matchWith({
4 val.matchWith({
3− Nothing: () => '▓',
5+ Nothing: () => kLoadingCharacter,
4 Just: p => p.value,
6 Just: p => p.value,
5 });
7 });
src/lib/routeNames.js−1
@@ -7,7 +7,6 @@ export const ROUTE_NAMES = {
7 INVITES_MANAGE: Symbol('INVITES_MANAGE'),
7 INVITES_MANAGE: Symbol('INVITES_MANAGE'),
8 //
8 //
9 LOGIN: Symbol('LOGIN'),
9 LOGIN: Symbol('LOGIN'),
10− POINT_HOME: Symbol('POINT_HOME'),
11 //
10 //
12 ACCEPT_TRANSFER: Symbol('ACCEPT_TRANSFER'),
11 ACCEPT_TRANSFER: Symbol('ACCEPT_TRANSFER'),
13 CANCEL_TRANSFER: Symbol('CANCEL_TRANSFER'),
12 CANCEL_TRANSFER: Symbol('CANCEL_TRANSFER'),
src/lib/router.js+2−6
@@ -6,7 +6,6 @@ import Landing from '../views/Landing';
6import InviteTicket from '../views/InviteTicket';
6import InviteTicket from '../views/InviteTicket';
7import InvitesManage from '../views/InvitesManage.js';
7import InvitesManage from '../views/InvitesManage.js';
8import Login from '../views/Login.js';
8import Login from '../views/Login.js';
9−import PointHome from '../views/PointHome.js';
10import AcceptTransfer from '../views/AcceptTransfer';
9import AcceptTransfer from '../views/AcceptTransfer';
11import CancelTransfer from '../views/CancelTransfer';
10import CancelTransfer from '../views/CancelTransfer';
12import CreateGalaxy from '../views/CreateGalaxy';
11import CreateGalaxy from '../views/CreateGalaxy';
@@ -45,7 +44,6 @@ export const ROUTES = {
45 [ROUTE_NAMES.INVITE]: Invite,
44 [ROUTE_NAMES.INVITE]: Invite,
46 [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
45 [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
47 [ROUTE_NAMES.LOGIN]: Login,
46 [ROUTE_NAMES.LOGIN]: Login,
48− [ROUTE_NAMES.POINT_HOME]: PointHome,
49 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
47 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
50 [ROUTE_NAMES.CANCEL_TRANSFER]: CancelTransfer,
48 [ROUTE_NAMES.CANCEL_TRANSFER]: CancelTransfer,
51 [ROUTE_NAMES.NETWORK]: Network,
49 [ROUTE_NAMES.NETWORK]: Network,
@@ -87,8 +85,8 @@ export const getRouteBreadcrumb = ({
87 return 'Manage invites';
85 return 'Manage invites';
88 case ROUTE_NAMES.LOGIN:
86 case ROUTE_NAMES.LOGIN:
89 return 'Login to Bridge';
87 return 'Login to Bridge';
90− case ROUTE_NAMES.POINT_HOME:
88+ case ROUTE_NAMES.POINT:
91− return 'Overview: ' + ob.patp(need.pointCursor({ pointCursor }));
89+ return `Overview: ${ob.patp(need.pointCursor(pointCursor))}`;
92 case ROUTE_NAMES.NETWORK:
90 case ROUTE_NAMES.NETWORK:
93 return renderNetworkType(networkType);
91 return renderNetworkType(networkType);
94 case ROUTE_NAMES.WALLET:
92 case ROUTE_NAMES.WALLET:
@@ -119,8 +117,6 @@ export const getRouteBreadcrumb = ({
119 return 'Points';
117 return 'Points';
120 case ROUTE_NAMES.VIEW_POINT:
118 case ROUTE_NAMES.VIEW_POINT:
121 return 'View';
119 return 'View';
122− case ROUTE_NAMES.POINT:
123− return ob.patp(need.pointCursor(pointCursor));
124 case ROUTE_NAMES.SET_TRANSFER_PROXY:
120 case ROUTE_NAMES.SET_TRANSFER_PROXY:
125 case ROUTE_NAMES.SET_MANAGEMENT_PROXY:
121 case ROUTE_NAMES.SET_MANAGEMENT_PROXY:
126 case ROUTE_NAMES.SET_SPAWN_PROXY:
122 case ROUTE_NAMES.SET_SPAWN_PROXY:
src/lib/useInvites.js+3−36
@@ -1,40 +1,7 @@
1−import { useCallback, useState } from 'react';
1+import { usePointCache } from 'store/pointCache';
2−import Maybe from 'folktale/maybe';
3−import * as azimuth from 'azimuth-js';
4
2
5−import { useNetwork } from 'store/network';
6−import useLifecycle from './useLifecycle';
7−
8−// fetch the invites available for a point
9export default function useInvites(point) {
3export default function useInvites(point) {
10− const { contracts } = useNetwork();
4+ const { getInvites } = usePointCache();
11−
12− const [availableInvites, _setAvailableInvites] = useState(Maybe.Nothing());
13−
14− const fetchInvites = useCallback(() => {
15− const _contracts = contracts.getOrElse(null);
16−
17− if (!contracts) {
18− return;
19− }
20−
21− (async () => {
22− const count = await azimuth.delegatedSending.getTotalUsableInvites(
23− _contracts,
24− point
25− );
26− _setAvailableInvites(Maybe.Just(count));
27− })();
28− }, [_setAvailableInvites, point, contracts]);
29−
30− // fetch invites on first render
31− useLifecycle(fetchInvites);
32
5
33− return {
6+ return getInvites(point);
34− availableInvites,
35− fetchInvites,
36− // TODO: look up sent/accepted
37− sentInvites: Maybe.Just(6),
38− acceptedInvites: Maybe.Just(5),
39− };
40}
7}
src/lib/usePointBirthday.jsnew file+7
@@ -0,0 +1,7 @@
1+import { usePointCache } from 'store/pointCache';
2+
3+export default function usePointBirthday(point) {
4+ const { getBirthday } = usePointCache();
5+
6+ return getBirthday(point);
7+}
src/store/pointCache.js+73−3
@@ -5,36 +5,106 @@ import React, {
5 useContext,
5 useContext,
6 useState,
6 useState,
7} from 'react';
7} from 'react';
8+import Maybe from 'folktale/maybe';
8import * as azimuth from 'azimuth-js';
9import * as azimuth from 'azimuth-js';
9
10
10import { useNetwork } from './network';
11import { useNetwork } from './network';
11
12
12export const PointCacheContext = createContext(null);
13export const PointCacheContext = createContext(null);
13
14
15+// the default value of a point's invites
16+const kEmptyInvites = {
17+ availableInvites: Maybe.Nothing(),
18+ sentInvites: Maybe.Nothing(),
19+ acceptedInvites: Maybe.Nothing(),
20+};
21+
14function _usePointCache() {
22function _usePointCache() {
15− const { contracts } = useNetwork();
23+ const { contracts, web3 } = useNetwork();
16 const [pointCache, _setPointCache] = useState({});
24 const [pointCache, _setPointCache] = useState({});
25+ const [birthdayCache, _setBirthdayCache] = useState({});
26+ const [invitesCache, _setInvitesCache] = useState({});
17
27
18 const addToPointCache = useCallback(
28 const addToPointCache = useCallback(
19 entry => _setPointCache(cache => ({ ...cache, ...entry })),
29 entry => _setPointCache(cache => ({ ...cache, ...entry })),
20 [_setPointCache]
30 [_setPointCache]
21 );
31 );
22
32
33+ const addToBirthdayCache = useCallback(
34+ entry => _setBirthdayCache(cache => ({ ...cache, ...entry })),
35+ [_setBirthdayCache]
36+ );
37+
38+ const addToInvitesCache = useCallback(
39+ entry => _setInvitesCache(cache => ({ ...cache, ...entry })),
40+ [_setInvitesCache]
41+ );
42+
43+ // TODO: refactor pointCache access to use accessor like bithday
44+ const getBirthday = useCallback(
45+ point => birthdayCache[point] || Maybe.Nothing(),
46+ [birthdayCache]
47+ );
48+ const getInvites = useCallback(
49+ point => invitesCache[point] || kEmptyInvites,
50+ [invitesCache]
51+ );
52+
23 const fetchPoint = useCallback(
53 const fetchPoint = useCallback(
24 async point => {
54 async point => {
25 const _contracts = contracts.getOrElse(null);
55 const _contracts = contracts.getOrElse(null);
56+ const _web3 = web3.getOrElse(null);
26− if (!_contracts) {
57+ if (!_contracts || !_web3) {
27 return;
58 return;
28 }
59 }
29
60
61+ // fetch point details
30 const details = await azimuth.azimuth.getPoint(_contracts, point);
62 const details = await azimuth.azimuth.getPoint(_contracts, point);
31 addToPointCache({ [point]: details });
63 addToPointCache({ [point]: details });
64+
65+ // fetch invites
66+ const count = await azimuth.delegatedSending.getTotalUsableInvites(
67+ _contracts,
68+ point
69+ );
70+ addToInvitesCache({
71+ [point]: {
72+ availableInvites: Maybe.Just(count),
73+ // TODO: look up sent/accepted on-chain
74+ sentInvites: Maybe.Just(6),
75+ acceptedInvites: Maybe.Just(5),
76+ },
77+ });
78+
79+ // fetch birthday (if not already known — will not change after being set)
80+ if (Maybe.Nothing.hasInstance(getBirthday(point))) {
81+ const birthBlock = await azimuth.azimuth.getActivationBlock(
82+ _contracts,
83+ point
84+ );
85+
86+ if (birthBlock > 0) {
87+ const block = await _web3.eth.getBlock(birthBlock);
88+ addToBirthdayCache({
89+ [point]: Maybe.Just(new Date(block.timestamp * 1000)),
90+ });
91+ }
92+ }
32 },
93 },
94+ [
95+ contracts,
96+ web3,
33− [contracts, addToPointCache]
97+ addToPointCache,
98+ addToBirthdayCache,
99+ getBirthday,
100+ addToInvitesCache,
101+ ]
34 );
102 );
35
103
36 return {
104 return {
37 pointCache,
105 pointCache,
106+ getBirthday,
107+ getInvites,
38 addToPointCache,
108 addToPointCache,
39 fetchPoint,
109 fetchPoint,
40 };
110 };
src/views/Login.js+5−4
@@ -71,14 +71,15 @@ class Login extends React.Component {
71 }
71 }
72 }
72 }
73
73
74+ // if we have a deduced point or one in the global context,
75+ // navigate to that specific point, otherwise navigate to list of points
74 if (Just.hasInstance(deduced)) {
76 if (Just.hasInstance(deduced)) {
75 this.props.setPointCursor(deduced);
77 this.props.setPointCursor(deduced);
76− this.props.history.popAndPush(ROUTE_NAMES.POINT_HOME);
78+ this.props.history.popAndPush(ROUTE_NAMES.POINT);
77 } else if (Just.hasInstance(this.props.pointCursor)) {
79 } else if (Just.hasInstance(this.props.pointCursor)) {
78− this.props.history.popAndPush(ROUTE_NAMES.POINT_HOME);
80+ this.props.history.popAndPush(ROUTE_NAMES.POINT);
79 } else {
81 } else {
80− //TODO to new overview (maybe should be merged into point home?)
81− this.props.history.popAndPush(ROUTE_NAMES.SHIPS);
82+ this.props.history.popAndPush(ROUTE_NAMES.POINTS);
82 }
83 }
83 }
84 }
84
85
src/views/Mnemonic.js+2
@@ -33,6 +33,8 @@ export default function Mnemonic() {
33 // when the properties change, re-derive wallet
33 // when the properties change, re-derive wallet
34 useEffect(() => {
34 useEffect(() => {
35 let mounted = true;
35 let mounted = true;
36+ // allow the browser to do something else while we derive this wallet
37+ // on the main thread by making this an async function
36 (async () => {
38 (async () => {
37 const wallet = walletFromMnemonic(mnemonic, walletHdPath, passphrase);
39 const wallet = walletFromMnemonic(mnemonic, walletHdPath, passphrase);
38 mounted && setWallet(wallet);
40 mounted && setWallet(wallet);
src/views/PointHome.jsdeleted−111
@@ -1,111 +0,0 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import React from 'react';
3−import * as azimuth from 'azimuth-js';
4−import * as need from '../lib/need';
5−
6−import { Row, Col, P } from '../components/old/Base';
7−
8−import { compose } from '../lib/lib';
9−
10−import { withNetwork } from '../store/network';
11−import { withPointCursor } from '../store/pointCursor';
12−import { withPointCache } from '../store/pointCache';
13−
14−class PointHome extends React.Component {
15− constructor(props) {
16− super(props);
17−
18− this.state = {
19− birthday: Nothing(),
20− spawned: [],
21− invites: Nothing(),
22− };
23− }
24−
25− componentDidMount() {
26− this.cachePoints();
27− this.updateBirthday();
28− }
29−
30− //TODO can probably re-use this to implement point switching
31− componentDidUpdate(prevProps) {
32− //
33− // NB (jtobin)
34− //
35− // When a link in the 'issued points' component is clicked, we don't
36− // actually leave the Point component, and componentDidMount will not fire
37− // again. But, we can force a call to cachePoints here.
38− //
39− const oldCursor = prevProps.pointCursor.getOrElse(true);
40− const newCursor = this.props.pointCursor.getOrElse(true);
41−
42− if (oldCursor !== newCursor) {
43− this.cachePoints();
44− }
45− }
46−
47− cachePoints() {
48− const { web3, contracts, pointCursor, addToPointCache } = this.props;
49−
50− web3.chain(_ =>
51− contracts.chain(ctrcs =>
52− pointCursor.chain(point => {
53− azimuth.azimuth
54− .getPoint(ctrcs, point)
55− .then(details => addToPointCache({ [point]: details }));
56− azimuth.delegatedSending
57− .getTotalUsableInvites(ctrcs, point)
58− .then(count => this.setState({ invites: Just(count) }));
59− this.updateSpawned(ctrcs, point);
60− })
61− )
62− );
63− }
64−
65− async updateBirthday() {
66− const birthBlock = await azimuth.azimuth.getActivationBlock(
67− need.contracts(this.props),
68− need.pointCursor(this.props)
69− );
70− if (birthBlock === 0) {
71− this.setState({ birthday: Nothing() });
72− } else {
73− const block = await need.web3(this.props).eth.getBlock(birthBlock);
74− //TODO add to point cache
75− this.setState({ birthday: Just(new Date(block.timestamp * 1000)) });
76− }
77− }
78−
79− updateSpawned = contracts => {
80− const { pointCursor } = this.props;
81−
82− pointCursor.matchWith({
83− Nothing: _ => {},
84− Just: point => {
85− azimuth.azimuth
86− .getSpawned(contracts, point.value)
87− .then(spawned => this.setState({ spawned }));
88− },
89− });
90− };
91−
92− render() {
93− return (
94− <Row>
95− <Col>
96− <P>Admin</P>
97−
98− <P>Boot Arvo</P>
99−
100− <P>Invite</P>
101− </Col>
102− </Row>
103− );
104− }
105−}
106−
107−export default compose(
108− withNetwork,
109− withPointCursor,
110− withPointCache
111−)(PointHome);