on master help sign in
refactor: Points into new format, refactor loadingCharacter into <Blinky>
commitb1b27357ed81e701d2d78bcb361a3ea78c5cded9 authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateTue 25 Jun 2019, 10:04 (7 years ago) parent9b85bb1
20 files changed+578−425
src/components/Blinky.js+22 src/components/Passport.js+8−6 src/indigo-react/components/Input.js+3−3 src/lib/loadingCharacter.js−7 src/lib/useArray.js−1 src/lib/useInterval.js+22 src/lib/useIsEclipticOwner.js+20 src/lib/usePreviousValue.js+16 src/lib/useSyncPoint.js−12 src/lib/useSyncPoints.js+24 src/store/lib/useBirthdaysStore.js+52 src/store/lib/useControlledPointsStore.js+71 src/store/lib/useDetailsStore.js+49 src/store/lib/useEclipticOwnerStore.js+26 src/store/lib/useInvitesStore.js+76 src/store/lib/usePointStore.js+38 src/store/pointCache.js+5−164 src/views/Invite.js+7−7 src/views/Point.js+6−6 src/views/Points.js+133−219
src/components/Blinky.jsnew file+22
@@ -0,0 +1,22 @@
1+import React, { useState } from 'react';
2+
3+import useInterval from 'lib/useInterval';
4+
5+export const kLoadingCharacter = '▓';
6+export const kInterstitialCharacter = '░';
7+
8+export const matchBlinky = obj =>
9+ obj.matchWith({
10+ Nothing: () => <Blinky />,
11+ Just: p => p.value,
12+ });
13+
14+export default function Blinky({
15+ a = kLoadingCharacter,
16+ b = kInterstitialCharacter,
17+}) {
18+ const [value, setValue] = useState(true);
19+ useInterval(() => setValue(val => !val), 1000);
20+
21+ return value ? a : b;
22+}
src/components/Passport.js+8−6
@@ -3,19 +3,21 @@ import * as ob from 'urbit-ob';
33import { Grid, Flex } from 'indigo-react';
44import { times } from 'lodash';
55
6−import Sigil from './Sigil';
76import usePointBirthday from 'lib/usePointBirthday';
87import { formatDots } from 'lib/dateFormat';
9−import { kLoadingCharacter } from 'lib/loadingCharacter';
108
11−const kLoadingDate = [4, 2, 2]
12− .map(t => times(t, () => kLoadingCharacter).join(''))
13− .join('.');
9+import Sigil from './Sigil';
10+import Blinky, { kLoadingCharacter, kInterstitialCharacter } from './Blinky';
11+
12+const buildDate = char =>
13+ [4, 2, 2].map(t => times(t, () => char).join('')).join('.');
14+const kDateA = buildDate(kLoadingCharacter);
15+const kDateB = buildDate(kInterstitialCharacter);
1416
1517export default function Passport({ point }) {
1618 const birthday = usePointBirthday(point);
1719 const birthDate = birthday.matchWith({
18− Nothing: () => kLoadingDate,
20+ Nothing: () => <Blinky a={kDateA} b={kDateB} />,
1921 Just: p => formatDots(p.value),
2022 });
2123 const name = ob.patp(point);
src/indigo-react/components/Input.js+3−3
@@ -41,10 +41,10 @@ export default function Input({
4141 // state that we already know
4242 // this also happens to prevent render loops when rendering a map of inputs
4343
44− // notify parent of enter keypress iff not disabled
44+ // notify parent of enter keypress iff not disabled and passing
4545 const onKeyPress = useCallback(
46− e => !disabled && e.key === 'Enter' && onEnter && onEnter(),
47− [disabled] // eslint-disable-line react-hooks/exhaustive-deps
46+ e => !disabled && pass && e.key === 'Enter' && onEnter && onEnter(),
47+ [disabled, pass] // eslint-disable-line react-hooks/exhaustive-deps
4848 );
4949
5050 // notify parent of value only when passing
src/lib/loadingCharacter.jsdeleted−7
@@ -1,7 +0,0 @@
1−export const kLoadingCharacter = '▓';
2−
3−export default val =>
4− val.matchWith({
5− Nothing: () => kLoadingCharacter,
6− Just: p => p.value,
7− });
src/lib/useArray.js−1
@@ -11,7 +11,6 @@ export default function useArray(initialItems = [], itemBuilder) {
1111 const removeAt = i =>
1212 _setItems(items => {
1313 const newItems = [...items.slice(0, i), ...items.slice(i + 1)];
14− console.log(items.length, newItems.length);
1514 return newItems;
1615 });
1716
src/lib/useInterval.jsnew file+22
@@ -0,0 +1,22 @@
1+import { useEffect, useRef } from 'react';
2+
3+// via: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
4+export default function useInterval(callback, delay) {
5+ const savedCallback = useRef();
6+
7+ // Remember the latest callback.
8+ useEffect(() => {
9+ savedCallback.current = callback;
10+ }, [callback]);
11+
12+ // Set up the interval.
13+ useEffect(() => {
14+ function tick() {
15+ savedCallback.current();
16+ }
17+ if (delay !== null) {
18+ let id = setInterval(tick, delay);
19+ return () => clearInterval(id);
20+ }
21+ }, [delay]);
22+}
src/lib/useIsEclipticOwner.jsnew file+20
@@ -0,0 +1,20 @@
1+import { useWallet } from 'store/wallet';
2+import { usePointCache } from 'store/pointCache';
3+
4+import { eqAddr } from 'lib/wallet';
5+
6+export default function useIsEclipticOwner() {
7+ const { wallet } = useWallet();
8+ const { eclipticOwner } = usePointCache();
9+
10+ return wallet.matchWith({
11+ Nothing: () => false,
12+ Just: p => {
13+ if (!eclipticOwner) {
14+ return false;
15+ }
16+
17+ return eqAddr(p.value.address, eclipticOwner);
18+ },
19+ });
20+}
src/lib/usePreviousValue.jsnew file+16
@@ -0,0 +1,16 @@
1+import { useRef } from 'react';
2+
3+export default function usePreviousValue(value) {
4+ const prev = useRef(null);
5+ const curr = useRef(null);
6+
7+ if (prev.current !== curr.current) {
8+ prev.current = curr.current;
9+ }
10+
11+ if (curr.current !== value) {
12+ curr.current = value;
13+ }
14+
15+ return prev.current;
16+}
src/lib/useSyncPoint.jsdeleted−12
@@ -1,12 +0,0 @@
1−import { useEffect } from 'react';
2−
3−import { usePointCache } from 'store/pointCache';
4−
5−// sync a specified point to the cache
6−export default function useSyncPoint(point) {
7− const { syncOwnedPoint } = usePointCache();
8−
9− useEffect(() => {
10− syncOwnedPoint(point);
11− }, [syncOwnedPoint, point]);
12−}
src/lib/useSyncPoints.jsnew file+24
@@ -0,0 +1,24 @@
1+import { useEffect } from 'react';
2+import { isEqual } from 'lodash';
3+
4+import { usePointCache } from 'store/pointCache';
5+import usePreviousValue from 'lib/usePreviousValue';
6+
7+// sync a known point to the cache (i.e display values)
8+export function useSyncKnownPoints(points = []) {
9+ //
10+}
11+
12+// sync an owned point to the cache
13+export function useSyncOwnedPoints(points = []) {
14+ const { syncOwnedPoint } = usePointCache();
15+ const prevPoints = usePreviousValue(points);
16+
17+ useEffect(() => {
18+ if (!isEqual(prevPoints, points)) {
19+ // if the actual set of points changes, sync the whole array
20+ // TODO: we should get clever about caching
21+ points.forEach(point => syncOwnedPoint(point));
22+ }
23+ }, [syncOwnedPoint, prevPoints, points]);
24+}
src/store/lib/useBirthdaysStore.jsnew file+52
@@ -0,0 +1,52 @@
1+import { useCallback, useState } from 'react';
2+import Maybe from 'folktale/maybe';
3+import * as azimuth from 'azimuth-js';
4+
5+import { useNetwork } from '../network';
6+
7+export default function useBirthdayStore() {
8+ const { contracts, web3 } = useNetwork();
9+ const [birthdayCache, _setBirthdayCache] = useState({});
10+
11+ const addToBirthdayCache = useCallback(
12+ entry =>
13+ _setBirthdayCache(cache => ({
14+ ...cache,
15+ ...entry,
16+ })),
17+ [_setBirthdayCache]
18+ );
19+
20+ const getBirthday = useCallback(
21+ point => birthdayCache[point] || Maybe.Nothing(),
22+ [birthdayCache]
23+ );
24+
25+ const syncBirthday = useCallback(
26+ async point => {
27+ const _contracts = contracts.getOrElse(null);
28+ const _web3 = web3.getOrElse(null);
29+ if (!_contracts || !web3) {
30+ return;
31+ }
32+
33+ // fetch birthday if not already known—will not change after being set
34+ if (Maybe.Nothing.hasInstance(getBirthday(point))) {
35+ const birthBlock = await azimuth.azimuth.getActivationBlock(
36+ _contracts,
37+ point
38+ );
39+
40+ if (birthBlock > 0) {
41+ const block = await _web3.eth.getBlock(birthBlock);
42+ addToBirthdayCache({
43+ [point]: Maybe.Just(new Date(block.timestamp * 1000)),
44+ });
45+ }
46+ }
47+ },
48+ [contracts, web3, addToBirthdayCache, getBirthday]
49+ );
50+
51+ return { getBirthday, syncBirthday };
52+}
src/store/lib/useControlledPointsStore.jsnew file+71
@@ -0,0 +1,71 @@
1+import { useState, useCallback } from 'react';
2+import Maybe from 'folktale/maybe';
3+import * as azimuth from 'azimuth-js';
4+
5+import { useNetwork } from '../network';
6+import { useWallet } from 'store/wallet';
7+
8+const kEmptyControlledPoints = {
9+ ownedPoints: Maybe.Nothing(),
10+ incomingPoints: Maybe.Nothing(),
11+ managingPoints: Maybe.Nothing(),
12+ votingPoints: Maybe.Nothing(),
13+ spawningPoints: Maybe.Nothing(),
14+};
15+
16+export default function useControlledPointsStore() {
17+ const { contracts } = useNetwork();
18+ const { wallet } = useWallet();
19+ const [controlledPointsCache, _setControlledPointsCache] = useState(
20+ kEmptyControlledPoints
21+ );
22+
23+ const getControlledPoints = useCallback(
24+ () => controlledPointsCache || kEmptyControlledPoints,
25+ [controlledPointsCache]
26+ );
27+
28+ const syncControlledPoints = useCallback(async () => {
29+ const _contracts = contracts.getOrElse(null);
30+ const _wallet = wallet.getOrElse(null);
31+ if (!_contracts || !_wallet) {
32+ return;
33+ }
34+
35+ const address = _wallet.address;
36+
37+ const ownedPoints = await azimuth.azimuth.getOwnedPoints(
38+ _contracts,
39+ address
40+ );
41+ const incomingPoints = await azimuth.azimuth.getTransferringFor(
42+ _contracts,
43+ address
44+ );
45+ const managingPoints = await azimuth.azimuth.getManagerFor(
46+ _contracts,
47+ address
48+ );
49+ const votingPoints = await azimuth.azimuth.getVotingFor(
50+ _contracts,
51+ address
52+ );
53+ const spawningPoints = await azimuth.azimuth.getSpawningFor(
54+ _contracts,
55+ address
56+ );
57+
58+ _setControlledPointsCache({
59+ ownedPoints: Maybe.Just(ownedPoints),
60+ incomingPoints: Maybe.Just(incomingPoints),
61+ managingPoints: Maybe.Just(managingPoints),
62+ votingPoints: Maybe.Just(votingPoints),
63+ spawningPoints: Maybe.Just(spawningPoints),
64+ });
65+ }, [contracts, wallet, _setControlledPointsCache]);
66+
67+ return {
68+ getControlledPoints,
69+ syncControlledPoints,
70+ };
71+}
src/store/lib/useDetailsStore.jsnew file+49
@@ -0,0 +1,49 @@
1+import { useState, useCallback } from 'react';
2+import Maybe from 'folktale/maybe';
3+import * as azimuth from 'azimuth-js';
4+
5+import { useNetwork } from '../network';
6+
7+export default function useDetailsStore() {
8+ const { contracts } = useNetwork();
9+ const [detailsCache, _setDetailsCache] = useState({});
10+
11+ const addToDetails = useCallback(
12+ entry =>
13+ _setDetailsCache(cache => ({
14+ ...cache,
15+ ...entry,
16+ })),
17+ [_setDetailsCache]
18+ );
19+
20+ // TODO: refactor detailsCache access to use accessor like bithday
21+ const getDetails = useCallback(
22+ point => detailsCache[point] || Maybe.Nothing(),
23+ [detailsCache]
24+ );
25+
26+ const syncDetails = useCallback(
27+ async point => {
28+ const _contracts = contracts.getOrElse(null);
29+ if (!_contracts) {
30+ return;
31+ }
32+
33+ // fetch point details
34+ const details = await azimuth.azimuth.getPoint(_contracts, point);
35+ addToDetails({
36+ [point]: details,
37+ });
38+ },
39+ [contracts, addToDetails]
40+ );
41+
42+ return {
43+ // TODO: refactor accessors to use getDetails instead of
44+ // touching the cache directly
45+ pointCache: detailsCache,
46+ getDetails,
47+ syncDetails,
48+ };
49+}
src/store/lib/useEclipticOwnerStore.jsnew file+26
@@ -0,0 +1,26 @@
1+import { useCallback, useState } from 'react';
2+import * as azimuth from 'azimuth-js';
3+
4+import { useNetwork } from 'store/network';
5+
6+export default function useEclipticOwnerStore() {
7+ const { contracts } = useNetwork();
8+ const [eclipticOwner, _setEclipticOwner] = useState(null);
9+
10+ const syncEclipticOwner = useCallback(async () => {
11+ // owner is generally immutable
12+ if (eclipticOwner) {
13+ return;
14+ }
15+
16+ const _contracts = contracts.getOrElse(null);
17+ if (!_contracts) {
18+ return;
19+ }
20+
21+ const owner = await azimuth.ecliptic.owner(_contracts);
22+ _setEclipticOwner(owner);
23+ }, [contracts, eclipticOwner]);
24+
25+ return { eclipticOwner, syncEclipticOwner };
26+}
src/store/lib/useInvitesStore.jsnew file+76
@@ -0,0 +1,76 @@
1+import { useCallback, useState } from 'react';
2+import Maybe from 'folktale/maybe';
3+import * as azimuth from 'azimuth-js';
4+
5+import { useNetwork } from '../network';
6+
7+// the default value of a point's invites
8+const kEmptyInvites = {
9+ availableInvites: Maybe.Nothing(),
10+ sentInvites: Maybe.Nothing(),
11+ acceptedInvites: Maybe.Nothing(),
12+};
13+
14+export default function useInvitesStore() {
15+ const { contracts } = useNetwork();
16+ const [invitesCache, _setInvitesCache] = useState({});
17+
18+ const addToInvitesCache = useCallback(
19+ entry =>
20+ _setInvitesCache(cache => ({
21+ ...cache,
22+ ...entry,
23+ })),
24+ [_setInvitesCache]
25+ );
26+
27+ const getInvites = useCallback(
28+ point => invitesCache[point] || kEmptyInvites,
29+ [invitesCache]
30+ );
31+
32+ const syncInvites = useCallback(
33+ async point => {
34+ const _contracts = contracts.getOrElse(null);
35+ if (!_contracts) {
36+ return;
37+ }
38+
39+ const availableInvites = await azimuth.delegatedSending.getTotalUsableInvites(
40+ _contracts,
41+ point
42+ );
43+
44+ const invitedPoints = await azimuth.delegatedSending.getInvited(
45+ _contracts,
46+ point
47+ );
48+ const invitedPointDetails = await Promise.all(
49+ invitedPoints.map(async invitedPoint => {
50+ console.log('invitedPoint', invitedPoint, typeof invitedPoint);
51+ const active = await azimuth.azimuth.isActive(
52+ _contracts,
53+ invitedPoint
54+ );
55+ return {
56+ point: Number(invitedPoint),
57+ active,
58+ };
59+ })
60+ );
61+ const sentInvites = invitedPointDetails.length;
62+ const acceptedInvites = invitedPointDetails.filter(i => i.active).length;
63+
64+ addToInvitesCache({
65+ [point]: {
66+ availableInvites: Maybe.Just(availableInvites),
67+ sentInvites: Maybe.Just(sentInvites),
68+ acceptedInvites: Maybe.Just(acceptedInvites),
69+ },
70+ });
71+ },
72+ [contracts, addToInvitesCache]
73+ );
74+
75+ return { getInvites, syncInvites };
76+}
src/store/lib/usePointStore.jsnew file+38
@@ -0,0 +1,38 @@
1+import { useCallback } from 'react';
2+
3+import useDetailsStore from './useDetailsStore';
4+import useBirthdaysStore from './useBirthdaysStore';
5+import useInvitesStore from './useInvitesStore';
6+import useControlledPointsStore from './useControlledPointsStore';
7+
8+export default function usePointStore() {
9+ const { syncDetails, ...details } = useDetailsStore();
10+ const { syncBirthday, ...birthdays } = useBirthdaysStore();
11+ const { syncInvites, ...invites } = useInvitesStore();
12+ const {
13+ syncControlledPoints,
14+ ...controlledPoints
15+ } = useControlledPointsStore();
16+
17+ // sync all of the on-chain info required to display a foreign point
18+ const syncForeignPoint = useCallback(
19+ async point => Promise.all([syncDetails(point), syncBirthday(point)]),
20+ [syncDetails, syncBirthday]
21+ );
22+
23+ // sync all of the on-chain info required for a point that the user owns
24+ const syncOwnedPoint = useCallback(
25+ async point => Promise.all([syncForeignPoint(point), syncInvites(point)]),
26+ [syncForeignPoint, syncInvites]
27+ );
28+
29+ return {
30+ ...details,
31+ ...birthdays,
32+ ...invites,
33+ ...controlledPoints,
34+ syncControlledPoints,
35+ syncOwnedPoint,
36+ syncForeignPoint,
37+ };
38+}
src/store/pointCache.js+5−164
@@ -1,173 +1,14 @@
1−import React, {
2− createContext,
3− forwardRef,
4− useCallback,
5− useContext,
6− useState,
7−} from 'react';
8−import Maybe from 'folktale/maybe';
9−import * as azimuth from 'azimuth-js';
1+import React, { createContext, forwardRef, useContext } from 'react';
102
11−import { useNetwork } from './network';
3+import usePointStore from './lib/usePointStore';
124
135export const PointCacheContext = createContext(null);
146
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−
22−// TODO(shrugs): this hook is too big -> refactor into individual hooks for
23−// details, birthday, invites, etc
24−function _usePointCache() {
25− const { contracts, web3 } = useNetwork();
26− const [pointCache, _setPointCache] = useState({});
27− const [birthdayCache, _setBirthdayCache] = useState({});
28− const [invitesCache, _setInvitesCache] = useState({});
29−
30− const addToPointCache = useCallback(
31− entry => _setPointCache(cache => ({ ...cache, ...entry })),
32− [_setPointCache]
33− );
34−
35− const addToBirthdayCache = useCallback(
36− entry => _setBirthdayCache(cache => ({ ...cache, ...entry })),
37− [_setBirthdayCache]
38− );
39−
40− const addToInvitesCache = useCallback(
41− entry => _setInvitesCache(cache => ({ ...cache, ...entry })),
42− [_setInvitesCache]
43− );
44−
45− // TODO: refactor pointCache access to use accessor like bithday
46− const getDetails = useCallback(
47− point => pointCache[point] || Maybe.Nothing(),
48− [pointCache]
49− );
50− const getBirthday = useCallback(
51− point => birthdayCache[point] || Maybe.Nothing(),
52− [birthdayCache]
53− );
54− const getInvites = useCallback(
55− point => invitesCache[point] || kEmptyInvites,
56− [invitesCache]
57− );
58−
59− const syncDetails = useCallback(
60− async point => {
61− const _contracts = contracts.getOrElse(null);
62− if (!_contracts) {
63− return;
64− }
65−
66− // fetch point details
67− const details = await azimuth.azimuth.getPoint(_contracts, point);
68− addToPointCache({
69− [point]: details,
70− });
71− },
72− [contracts, addToPointCache]
73− );
74−
75− const syncBirthday = useCallback(
76− async point => {
77− const _contracts = contracts.getOrElse(null);
78− const _web3 = web3.getOrElse(null);
79− if (!_contracts || !web3) {
80− return;
81− }
82−
83− // fetch birthday if not already known—will not change after being set
84− if (Maybe.Nothing.hasInstance(getBirthday(point))) {
85− const birthBlock = await azimuth.azimuth.getActivationBlock(
86− _contracts,
87− point
88− );
89−
90− if (birthBlock > 0) {
91− const block = await _web3.eth.getBlock(birthBlock);
92− addToBirthdayCache({
93− [point]: Maybe.Just(new Date(block.timestamp * 1000)),
94− });
95− }
96− }
97− },
98− [contracts, web3, addToBirthdayCache, getBirthday]
99− );
100−
101− const syncInvites = useCallback(
102− async point => {
103− const _contracts = contracts.getOrElse(null);
104− if (!_contracts) {
105− return;
106− }
107−
108− const availableInvites = await azimuth.delegatedSending.getTotalUsableInvites(
109− _contracts,
110− point
111− );
112−
113− const invitedPoints = await azimuth.delegatedSending.getInvited(
114− _contracts,
115− point
116− );
117− const invitedPointDetails = await Promise.all(
118− invitedPoints.map(async invitedPoint => {
119− console.log('invitedPoint', invitedPoint, typeof invitedPoint);
120− const active = await azimuth.azimuth.isActive(
121− _contracts,
122− invitedPoint
123− );
124− return {
125− point: Number(invitedPoint),
126− active,
127− };
128− })
129− );
130− const sentInvites = invitedPointDetails.length;
131− const acceptedInvites = invitedPointDetails.filter(i => i.active).length;
132−
133− addToInvitesCache({
134− [point]: {
135− availableInvites: Maybe.Just(availableInvites),
136− sentInvites: Maybe.Just(sentInvites),
137− acceptedInvites: Maybe.Just(acceptedInvites),
138− },
139− });
140− },
141− [contracts, addToInvitesCache]
142− );
143−
144− // sync all of the on-chain info required for a point that the user owns
145− const syncOwnedPoint = useCallback(
146− async point => {
147− await Promise.all([
148− syncDetails(point),
149− syncInvites(point),
150− syncBirthday(point),
151− ]);
152− },
153− [syncDetails, syncInvites, syncBirthday]
154− );
155−
156− return {
157− pointCache,
158− getDetails,
159− getBirthday,
160− getInvites,
161− addToPointCache,
162− syncOwnedPoint,
163− };
164−}
165−
1667export function PointCacheProvider({ children }) {
167− const pointCache = _usePointCache();
8+ const cache = usePointStore();
1689
16910 return (
170− <PointCacheContext.Provider value={pointCache}>
11+ <PointCacheContext.Provider value={cache}>
17112 {children}
17213 </PointCacheContext.Provider>
17314 );
@@ -182,6 +23,6 @@ export function usePointCache() {
18223export const withPointCache = Component =>
18324 forwardRef((props, ref) => (
18425 <PointCacheContext.Consumer>
185− {pointCache => <Component ref={ref} {...pointCache} {...props} />}
26+ {cache => <Component ref={ref} {...cache} {...props} />}
18627 </PointCacheContext.Consumer>
18728 ));
src/views/Invite.js+7−7
@@ -7,17 +7,17 @@ import { useHistory } from 'store/history';
77
88import View from 'components/View';
99import Crumbs from 'components/Crumbs';
10+import Highlighted from 'components/Highlighted';
11+import { matchBlinky } from 'components/Blinky';
1012
1113import useInvites from 'lib/useInvites';
12−import useSyncPoint from 'lib/useSyncPoint';
14+import { useSyncOwnedPoints } from 'lib/useSyncPoints';
1315import useCurrentPointName from 'lib/useCurrentPointName';
1416import useRouter from 'lib/useRouter';
15−import loadingCharacter from 'lib/loadingCharacter';
1617import { LocalRouterProvider } from 'lib/LocalRouter';
1718
1819import InviteUrl from './Invite/InviteUrl';
1920import InviteEmail from './Invite/InviteEmail';
20−import Highlighted from 'components/Highlighted';
2121
2222const kInviteNames = {
2323 URL: 'URL',
@@ -41,15 +41,15 @@ export default function Invite() {
4141 const name = useCurrentPointName();
4242
4343 // sync the current point
44− useSyncPoint(point);
44+ useSyncOwnedPoints([point]);
4545
4646 // get the invites for the current point
4747 const { availableInvites, sentInvites, acceptedInvites } = useInvites(point);
4848
4949 // number or loading character
50− const availableInvitesText = loadingCharacter(availableInvites);
51− const sentInvitesText = loadingCharacter(sentInvites);
52− const acceptedInvitesText = loadingCharacter(acceptedInvites);
50+ const availableInvitesText = matchBlinky(availableInvites);
51+ const sentInvitesText = matchBlinky(sentInvites);
52+ const acceptedInvitesText = matchBlinky(acceptedInvites);
5353
5454 return (
5555 <LocalRouterProvider value={router}>
src/views/Point.js+6−6
@@ -3,18 +3,18 @@ import * as need from 'lib/need';
33import { Grid } from 'indigo-react';
44
55import { usePointCursor } from 'store/pointCursor';
6+import { useHistory } from 'store/history';
67
78import View from 'components/View';
89import Passport from 'components/Passport';
910import { ForwardButton } from 'components/Buttons';
11+import FooterButton from 'components/FooterButton';
12+import { matchBlinky } from 'components/Blinky';
1013
1114import useInvites from 'lib/useInvites';
12−import useSyncPoint from 'lib/useSyncPoint';
15+import { useSyncOwnedPoints } from 'lib/useSyncPoints';
1316import { ROUTE_NAMES } from 'lib/routeNames';
1417
15−import { useHistory } from 'store/history';
16−import FooterButton from 'components/FooterButton';
17−import loadingCharacter from 'lib/loadingCharacter';
1818import Actions from './Point/Actions';
1919
2020export default function Point() {
@@ -26,14 +26,14 @@ export default function Point() {
2626
2727 // fetch the invites for the current cursor
2828 const { availableInvites } = useInvites(point);
29− const availableInvitesText = loadingCharacter(availableInvites);
29+ const availableInvitesText = matchBlinky(availableInvites);
3030
3131 const goInvite = useCallback(() => history.push(ROUTE_NAMES.INVITE), [
3232 history,
3333 ]);
3434
3535 // sync the current cursor
36− useSyncPoint(point);
36+ useSyncOwnedPoints([point]);
3737
3838 return (
3939 <View>
src/views/Points.js+133−219
@@ -1,245 +1,159 @@
11import React from 'react';
2−import { H1, H2, P } from '../components/old/Base';
3−import { Button } from '../components/old/Base';
4−import * as azimuth from 'azimuth-js';
5−
6−import PointList from '../components/old/PointList';
7−import { NETWORK_TYPES } from '../lib/network';
8−import { ROUTE_NAMES } from '../lib/routeNames';
9−import { withHistory } from '../store/history';
10−import { ETH_ZERO_ADDR, eqAddr } from '../lib/wallet';
11−import { withNetwork } from '../store/network';
12−import { compose } from '../lib/lib';
13−import { withWallet } from '../store/wallet';
14−import { withPointCache } from '../store/pointCache';
2+import Maybe from 'folktale/maybe';
3+import { H1, H2, P, Grid } from 'indigo-react';
4+
5+import { ROUTE_NAMES } from 'lib/routeNames';
6+import { ETH_ZERO_ADDR, eqAddr } from 'lib/wallet';
7+import useIsEclipticOwner from 'lib/useIsEclipticOwner';
8+import { useSyncKnownPoints } from 'lib/useSyncPoints';
9+
10+import { useHistory } from 'store/history';
11+import { useWallet } from 'store/wallet';
12+import { usePointCache } from 'store/pointCache';
13+
14+import PointList from 'components/old/PointList';
1515import View from 'components/View';
16−import { Grid } from 'indigo-react';
17−
18−const hasTransferProxy = (cache, point) =>
19− point in cache ? !eqAddr(cache[point].transferProxy, ETH_ZERO_ADDR) : false;
20−
21−class Points extends React.Component {
22− constructor(props) {
23− super(props);
24−
25− const { networkType } = props;
26− const loading = networkType !== NETWORK_TYPES.OFFLINE;
27−
28− this.state = {
29− points: [],
30− incoming: [],
31− managing: [],
32− voting: [],
33− spawning: [],
34− eclipticOwner: false,
35− loading: loading,
36− };
37− }
38−
39− componentDidMount() {
40− const { web3, wallet, contracts, addToPointCache } = this.props;
41−
42− web3.chain(_ =>
43− contracts.chain(ctrcs =>
44− wallet.chain(async wal => {
45− const addr = wal.address;
46−
47− const points = await azimuth.azimuth.getOwnedPoints(ctrcs, addr);
48−
49− const incoming = await azimuth.azimuth.getTransferringFor(
50− ctrcs,
51− addr
52− );
53−
54− const managing = await azimuth.azimuth.getManagerFor(ctrcs, addr);
55−
56− const voting = await azimuth.azimuth.getVotingFor(ctrcs, addr);
57−
58− const spawning = await azimuth.azimuth.getSpawningFor(ctrcs, addr);
59−
60− const owner = await azimuth.ecliptic.owner(ctrcs);
61−
62− const eclipticOwner = eqAddr(addr, owner);
63−
64− this.setState({
65− points,
66− incoming,
67− managing,
68− voting,
69− spawning,
70− eclipticOwner,
71− loading: false,
72− });
73−
74− this.cachePoints(ctrcs, addToPointCache, points);
75− })
76− )
77− );
78− }
79−
80− cachePoints(contracts, addToPointCache, points) {
81− points.forEach(point => {
82− azimuth.azimuth
83− .getPoint(contracts, point)
84− .then(details => addToPointCache({ [point]: details }));
85− });
86− }
87−
88− render() {
89− const { history, pointCache } = this.props;
90−
91− const {
92− points,
93− incoming,
94− managing,
95− voting,
96− spawning,
97− loading,
98− eclipticOwner,
99− } = this.state;
100−
101− const lookupPointButton = (
16+import { ForwardButton } from 'components/Buttons';
17+import useLifecycle from 'lib/useLifecycle';
18+
19+const matchArray = obj =>
20+ obj.matchWith({ Nothing: () => [], Just: p => p.value });
21+
22+const hasTransferProxy = (details, point) =>
23+ !eqAddr(details.transferProxy, ETH_ZERO_ADDR);
24+
25+export default function Points() {
26+ const { wallet } = useWallet();
27+ const history = useHistory();
28+ const {
29+ getControlledPoints,
30+ syncControlledPoints,
31+ getDetails,
32+ } = usePointCache();
33+ const {
34+ ownedPoints,
35+ incomingPoints,
36+ managingPoints,
37+ votingPoints,
38+ spawningPoints,
39+ } = getControlledPoints(wallet);
40+ const isEclipticOwner = useIsEclipticOwner();
41+
42+ const owned = matchArray(ownedPoints);
43+ const incoming = matchArray(incomingPoints);
44+ const managing = matchArray(managingPoints);
45+ const voting = matchArray(votingPoints);
46+ const spawning = matchArray(spawningPoints);
47+
48+ // sync controlled points on mount
49+ useLifecycle(() => {
50+ syncControlledPoints();
51+ });
52+
53+ // sync display details for known points
54+ useSyncKnownPoints([
55+ ...owned,
56+ ...incoming,
57+ ...managing,
58+ ...voting,
59+ ...spawning,
60+ ]);
61+
62+ const loading = Maybe.Nothing.hasInstance(ownedPoints);
63+
64+ const outgoing = ownedPoints.filter(point =>
65+ getDetails(point).matchWith({
66+ Nothing: () => false,
67+ Just: p => hasTransferProxy(p.value),
68+ })
69+ );
70+
71+ return (
72+ <View>
73+ <H1>Points</H1>
74+
75+ <P>
76+ A point is an identity on the Ethereum blockchain. Points declare keys
77+ for ships on the Arvo network.
78+ </P>
79+
10280 <Grid>
10381 <Grid.Item full>
104− <Button
105− prop-type={'link'}
106− prop-size={'md'}
82+ <ForwardButton
83+ detail="View a point on Azimuth"
10784 onClick={() => history.push(ROUTE_NAMES.VIEW_POINT)}>
108− {'View a point →'}
109− </Button>
110− <P>{'View a point on Azimuth.'}</P>
85+ View a point
86+ </ForwardButton>
11187 </Grid.Item>
11288 </Grid>
113− );
11489
115− const createGalaxyButton =
116− eclipticOwner === false ? (
117− <div />
118− ) : (
90+ {isEclipticOwner && (
11991 <Grid>
12092 <Grid.Item full>
121− <Button
122− prop-type={'link'}
123− prop-size={'md'}
93+ <ForwardButton
94+ detail="You have the authority to create a new Galaxy."
12495 onClick={() => history.push(ROUTE_NAMES.CREATE_GALAXY)}>
125− {'Create a galaxy →'}
126− </Button>
127− <P>
128− {
129− 'You have the authority to create a new Galaxy and you can do so here.'
130− }
131− </P>
96+ Create a galaxy
97+ </ForwardButton>
13298 </Grid.Item>
13399 </Grid>
134− );
100+ )}
135101
136− const outgoing = points.filter(point =>
137− hasTransferProxy(pointCache, point)
138− );
139−
140− const outgoingPoints =
141− outgoing.length !== 0 ? (
142− <React.Fragment>
143− <H2>{'Outgoing Transfers'}</H2>
102+ {incoming.length > 0 && (
103+ <>
104+ <H2>Incoming Transfers</H2>
144105 <P>
145− {`You own these points until the recipient accepts the incoming
146− transfer. You may cancel the transfer until accepted.`}
106+ You do not own these points until you accept the incoming transfer.
107+ You may reject any incoming transfers.
147108 </P>
148− <PointList routeHandler={history.push} points={outgoing} />
149− </React.Fragment>
150− ) : (
151− <div />
152− );
153−
154− const incomingPoints =
155− incoming.length !== 0 ? (
156− <React.Fragment>
157− <H2>{'Incoming Transfers'}</H2>
109+ <PointList routeHandler={history.push} points={incoming} />
110+ </>
111+ )}
112+
113+ {outgoing.length > 0 && (
114+ <>
115+ <H2>Outgoing Transfers</H2>
158116 <P>
159− {`You do not own these points until you accept the incoming transfer.
160− You may reject any incoming transfers.`}
117+ You own these points until the recipient accepts the incoming
118+ transfer. You may cancel the transfer until accepted.
161119 </P>
162− <PointList routeHandler={history.push} points={incoming} />
163− </React.Fragment>
164− ) : (
165− <div />
166− );
167−
168− const managingPoints =
169− managing.length !== 0 ? (
170− <React.Fragment>
171− <H2>{'You Are a Management Proxy For'}</H2>
120+ <PointList routeHandler={history.push} points={outgoing} />
121+ </>
122+ )}
123+
124+ <H2>Your Points</H2>
125+
126+ <PointList routeHandler={history.push} points={owned} loading={loading} />
127+
128+ {managing.length > 0 && (
129+ <>
130+ <H2>You Are a Management Proxy For</H2>
172131 <P>
173− {`You can configure or set network keys and conduct sponsorship
174− related operations for these points.`}
132+ You can configure or set network keys and conduct sponsorship
133+ related operations for these points.
175134 </P>
176135 <PointList routeHandler={history.push} points={managing} />
177− </React.Fragment>
178− ) : (
179− <div />
180− );
181−
182− const votingPoints =
183− voting.length !== 0 ? (
184− <React.Fragment>
185− <H2>{'You Are a Voting Proxy For'}</H2>
136+ </>
137+ )}
138+
139+ {voting.length > 0 && (
140+ <>
141+ <H2>You Are a Voting Proxy For</H2>
186142 <P>
187− {`Since you are part of the Galactic Senate, you can cast votes on
188− new Azimuth proposals on behalf of these points.`}
143+ Since you are part of the Galactic Senate, you can cast votes on new
144+ Azimuth proposals on behalf of these points.
189145 </P>
190146 <PointList routeHandler={history.push} points={voting} />
191− </React.Fragment>
192− ) : (
193− <div />
194− );
195−
196− const spawningPoints =
197− spawning.length !== 0 ? (
198− <React.Fragment>
199− <H2>{'You Are a Spawn Proxy For'}</H2>
200− <P>{`You can create new child ships under these points.`}</P>
201− <PointList routeHandler={history.push} points={spawning} />
202− </React.Fragment>
203− ) : (
204− <div />
205− );
206−
207− return (
208− <View>
209− <H1>{'Points'}</H1>
210−
211− <P>{`A point is an identity on the Ethereum blockchain.
212− Points declare keys for ships on the Arvo network.`}</P>
213−
214− {lookupPointButton}
215−
216− {createGalaxyButton}
217−
218− {incomingPoints}
219−
220− {outgoingPoints}
147+ </>
148+ )}
221149
222− <H2>{'Your Points'}</H2>
223−
224− <PointList
225− routeHandler={history.push}
226− points={points}
227− loading={loading}
228− />
229−
230− {managingPoints}
231−
232− {votingPoints}
233−
234− {spawningPoints}
235− </View>
236− );
237− }
150+ {spawning.length > 0 && (
151+ <>
152+ <H2>You Are a Spawn Proxy For</H2>
153+ <P>You can create new child points under these points.</P>
154+ <PointList routeHandler={history.push} points={spawning} />
155+ </>
156+ )}
157+ </View>
158+ );
238159}
239−
240−export default compose(
241− withNetwork,
242− withHistory,
243− withWallet,
244− withPointCache
245−)(Points);