Split tips into an own tab (#3487)

This commit is contained in:
Jaco Greeff 2020-08-23 15:22:31 +02:00 committed by GitHub
parent 9b1821203e
commit 6079e30579
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 149 additions and 88 deletions

View File

@ -3,36 +3,24 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { DeriveTreasuryProposals } from '@polkadot/api-derive/types';
import { BareProps as Props } from '@polkadot/react-components/types';
import React, { useEffect, useState } from 'react';
import React from 'react';
import { Button } from '@polkadot/react-components';
import { useApi, useCall, useIncrement, useMembers, useIsMountedRef } from '@polkadot/react-hooks';
import { useApi, useCall } from '@polkadot/react-hooks';
import ProposalCreate from './ProposalCreate';
import Proposals from './Proposals';
import Summary from './Summary';
import TipCreate from './TipCreate';
import Tips from './Tips';
function Overview ({ className }: Props): React.ReactElement<Props> {
interface Props {
className?: string;
isMember: boolean;
members: string[];
}
function Overview ({ className, isMember, members }: Props): React.ReactElement<Props> {
const { api } = useApi();
const info = useCall<DeriveTreasuryProposals>(api.derive.treasury.proposals);
const { isMember, members } = useMembers();
const mountedRef = useIsMountedRef();
const [tipHashTrigger, triggerTipHashes] = useIncrement();
const [tipHashes, setTipHashes] = useState<string[] | null>(null);
useEffect((): void => {
if (tipHashTrigger && mountedRef.current) {
api.query.treasury.tips.keys().then((keys) =>
mountedRef.current && setTipHashes(
keys.map((key) => key.args[0].toHex())
)
).catch(console.error);
}
}, [api, tipHashTrigger, mountedRef]);
return (
<div className={className}>
@ -41,10 +29,6 @@ function Overview ({ className }: Props): React.ReactElement<Props> {
proposalCount={info?.proposals.length}
/>
<Button.Group>
<TipCreate
members={members}
refresh={triggerTipHashes}
/>
<ProposalCreate />
</Button.Group>
<Proposals
@ -58,11 +42,6 @@ function Overview ({ className }: Props): React.ReactElement<Props> {
members={members}
proposals={info?.approvals}
/>
<Tips
hashes={tipHashes}
isMember={isMember}
members={members}
/>
</div>
);
}

View File

@ -5,7 +5,7 @@
import { AccountId, Balance, BlockNumber, OpenTip, OpenTipTo225 } from '@polkadot/types/interfaces';
import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { AddressSmall, AddressMini, Expander, Icon, LinkExternal, TxButton } from '@polkadot/react-components';
import { useAccounts } from '@polkadot/react-hooks';
@ -40,44 +40,48 @@ function isCurrentTip (tip: OpenTip | OpenTipTo225): tip is OpenTip {
return !!(tip as OpenTip)?.findersFee;
}
function extractTipState (tip: OpenTip | OpenTipTo225, hash: string, allAccounts: string[]): TipState {
const closesAt = tip.closes.unwrapOr(null);
let finder: AccountId | null = null;
let deposit: Balance | null = null;
if (isCurrentTip(tip)) {
finder = tip.finder;
deposit = tip.deposit;
} else if (tip.finder.isSome) {
const finderInfo = tip.finder.unwrap();
finder = finderInfo[0];
deposit = finderInfo[1];
}
const values = tip.tips.map(([, value]) => value).sort((a, b) => a.cmp(b));
const midIndex = Math.floor(values.length / 2);
const median = values.length
? values.length % 2
? values[midIndex]
: values[midIndex - 1].add(values[midIndex]).divn(2)
: BN_ZERO;
return {
closesAt,
deposit,
finder,
isFinder: !!finder && allAccounts.includes(finder.toString()),
isTipped: !!values.length,
isTipper: tip.tips.some(([address]) => allAccounts.includes(address.toString())),
median
};
}
function Tip ({ bestNumber, className = '', hash, isMember, members, tip }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { allAccounts } = useAccounts();
const [{ closesAt, finder, isFinder, isTipped, isTipper, median }, setTipState] = useState<TipState>({ closesAt: null, deposit: null, finder: null, isFinder: false, isTipped: false, isTipper: false, median: BN_ZERO });
useEffect((): void => {
const closesAt = tip.closes.unwrapOr(null);
let finder: AccountId | null = null;
let deposit: Balance | null = null;
if (isCurrentTip(tip)) {
finder = tip.finder;
deposit = tip.deposit;
} else if (tip.finder.isSome) {
const finderInfo = tip.finder.unwrap();
finder = finderInfo[0];
deposit = finderInfo[1];
}
const values = tip.tips.map(([, value]) => value).sort((a, b) => a.cmp(b));
const midIndex = Math.floor(values.length / 2);
const median = values.length
? values.length % 2
? values[midIndex]
: values[midIndex - 1].add(values[midIndex]).divn(2)
: BN_ZERO;
setTipState({
closesAt,
deposit,
finder,
isFinder: !!finder && allAccounts.includes(finder.toString()),
isTipped: !!values.length,
isTipper: tip.tips.some(([address]) => allAccounts.includes(address.toString())),
median
});
}, [allAccounts, hash, tip]);
const { closesAt, finder, isFinder, isTipped, isTipper, median } = useMemo(
() => extractTipState(tip, hash, allAccounts),
[allAccounts, hash, tip]
);
const { reason, tips, who } = tip;

View File

@ -4,7 +4,7 @@
import { BlockNumber, OpenTip, OpenTipTo225 } from '@polkadot/types/interfaces';
import React, { useEffect, useRef, useState } from 'react';
import React, { useMemo, useRef } from 'react';
import { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { Option } from '@polkadot/types';
@ -21,29 +21,35 @@ interface Props {
type Tip = [string, OpenTip | OpenTipTo225];
function extractTips (optTips?: Option<OpenTip>[], hashes?: string[] | null): Tip[] | undefined {
if (!hashes || !optTips) {
return undefined;
}
return optTips
.map((opt, index): [string, OpenTip | null] => [hashes[index], opt.unwrapOr(null)])
.filter((val): val is [string, OpenTip] => !!val[1])
.sort((a, b) =>
a[1].closes.isNone
? b[1].closes.isNone
? 0
: -1
: b[1].closes.isSome
? b[1].closes.unwrap().cmp(a[1].closes.unwrap())
: 1
);
}
function Tips ({ className = '', hashes, isMember, members }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const bestNumber = useCall<BlockNumber>(api.derive.chain.bestNumber);
const [tips, setTips] = useState<Tip[] | undefined>();
const optTips = useCall<Option<OpenTip>[]>(hashes && api.query.treasury.tips.multi, [hashes]);
useEffect((): void => {
hashes && optTips && setTips(
optTips
.map((opt, index): [string, OpenTip | null] => [hashes[index], opt.unwrapOr(null)])
.filter((val): val is [string, OpenTip] => !!val[1])
.sort((a, b) =>
a[1].closes.isNone
? b[1].closes.isNone
? 0
: -1
: b[1].closes.isSome
? b[1].closes.unwrap().cmp(a[1].closes.unwrap())
: 1
)
);
}, [hashes, optTips]);
const tips = useMemo(
() => extractTips(optTips, hashes),
[hashes, optTips]
);
const headerRef = useRef([
[t('tips'), 'start'],

View File

@ -0,0 +1,37 @@
// Copyright 2017-2020 @polkadot/app-treasury authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import { Button } from '@polkadot/react-components';
import TipCreate from './TipCreate';
import Tips from './Tips';
interface Props {
className?: string;
hashes?: string[] | null;
isMember: boolean;
members: string[];
trigger: () => void;
}
function TipsEntry ({ className, hashes, isMember, members, trigger }: Props): React.ReactElement<Props> {
return (
<div className={className}>
<Button.Group>
<TipCreate
members={members}
refresh={trigger}
/>
</Button.Group>
<Tips
hashes={hashes}
isMember={isMember}
members={members}
/>
</div>
);
}
export default React.memo(TipsEntry);

View File

@ -2,12 +2,14 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React, { useRef } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { Route, Switch } from 'react-router';
import { HelpOverlay, Tabs } from '@polkadot/react-components';
import { useApi, useIncrement, useIsMountedRef, useMembers } from '@polkadot/react-hooks';
import basicMd from './md/basic.md';
import Overview from './Overview';
import Tips from './Tips';
import { useTranslation } from './translate';
@ -19,14 +21,34 @@ interface Props {
function TreasuryApp ({ basePath }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const mountedRef = useIsMountedRef();
const [tipHashTrigger, triggerTipHashes] = useIncrement();
const { isMember, members } = useMembers();
const [tipHashes, setTipHashes] = useState<string[] | null>(null);
const itemsRef = useRef([
useEffect((): void => {
if (tipHashTrigger && mountedRef.current) {
api.query.treasury.tips.keys().then((keys) =>
mountedRef.current && setTipHashes(
keys.map((key) => key.args[0].toHex())
)
).catch(console.error);
}
}, [api, tipHashTrigger, mountedRef]);
const items = useMemo(() => [
{
isRoot: true,
name: 'overview',
text: t<string>('Treasury overview')
},
{
count: tipHashes?.length,
name: 'tips',
text: t<string>('Tips')
}
]);
], [t, tipHashes]);
return (
<main className='treasury--App'>
@ -34,11 +56,24 @@ function TreasuryApp ({ basePath }: Props): React.ReactElement<Props> {
<header>
<Tabs
basePath={basePath}
items={itemsRef.current}
items={items}
/>
</header>
<Switch>
<Route component={Overview} />
<Route path={`${basePath}/tips`}>
<Tips
hashes={tipHashes}
isMember={isMember}
members={members}
trigger={triggerTipHashes}
/>
</Route>
<Route>
<Overview
isMember={isMember}
members={members}
/>
</Route>
</Switch>
</main>
);