Browse Source

re-introduce storage roles app

Paul M Fox 5 years ago
parent
commit
ab3b6ff5bc

+ 3 - 0
packages/apps-routing/src/index.ts

@@ -13,6 +13,7 @@ import media from './joy-media';
 import members from './joy-members';
 import proposals from './joy-proposals';
 import roles from './joy-roles';
+import storageRoles from './joy-storage';
 import pages from './joy-pages';
 
 import template from './123code';
@@ -41,6 +42,7 @@ const routes: Routes = appSettings.isBasicMode
     explorer,
     staking,
     roles,
+    storageRoles,
     transfer,
     null,
     media,
@@ -63,6 +65,7 @@ const routes: Routes = appSettings.isBasicMode
     explorer,
     staking,
     roles,
+    storageRoles,
     transfer,
     null,
     media,

+ 2 - 3
packages/apps-routing/src/joy-roles.ts

@@ -6,15 +6,14 @@ export default ([
   {
     Component: Roles,
     display: {
-      needsAccounts: true,
       needsApi: [
         'query.actors.actorAccountIds'
       ]
     },
     i18n: {
-      defaultValue: 'Roles'
+      defaultValue: 'Working groups'
     },
-    icon: 'server',
+    icon: 'users',
     name: 'roles'
   }
 ] as Routes);

+ 20 - 0
packages/apps-routing/src/joy-storage.ts

@@ -0,0 +1,20 @@
+import { Routes } from './types';
+
+import Storage from '@polkadot/joy-storage/index';
+
+export default ([
+  {
+    Component: Storage,
+    display: {
+      needsAccounts: true,
+      needsApi: [
+        'query.actors.actorAccountIds'
+      ]
+    },
+    i18n: {
+      defaultValue: 'Storage'
+    },
+    icon: 'database',
+    name: 'storage'
+  }
+] as Routes);

+ 2 - 6
packages/joy-roles/src/tabs/WorkingGroup.controller.tsx

@@ -1,5 +1,4 @@
 import React from 'react';
-import { Container } from 'semantic-ui-react';
 
 import { Controller, View } from '@polkadot/joy-utils/index'
 
@@ -8,7 +7,7 @@ import { ITransport } from '../transport'
 import {
   ContentCurators,
   WorkingGroupMembership,
-  StorageAndDistribution, StorageAndDistributionMembership,
+  StorageAndDistributionMembership,
 } from './WorkingGroup'
 
 type State = {
@@ -40,9 +39,6 @@ export class WorkingGroupsController extends Controller<State, ITransport> {
 
 export const WorkingGroupsView = View<WorkingGroupsController, State>(
   (state) => (
-    <Container>
-      <ContentCurators {...state.contentCurators!} />
-      <StorageAndDistribution {...state.storageProviders!} />
-    </Container>
+    <ContentCurators {...state.contentCurators!} />
   )
 )

+ 1 - 0
packages/joy-storage/README.md

@@ -0,0 +1 @@
+# @polkadot/joy-actors

+ 15 - 0
packages/joy-storage/package.json

@@ -0,0 +1,15 @@
+{
+  "name": "@polkadot/joy-storage",
+  "version": "0.0.1",
+  "description": "Staked roles module for Joystream node",
+  "main": "index.js",
+  "scripts": {},
+  "author": "Joystream contributors",
+  "maintainers": [],
+  "dependencies": {
+    "@babel/runtime": "^7.6.0",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-query": "^0.36.0-beta.65",
+    "@polkadot/joy-utils": "^0.1.1"
+  }
+}

+ 87 - 0
packages/joy-storage/src/ActorsList/index.tsx

@@ -0,0 +1,87 @@
+import React from 'react'
+import { BareProps } from '@polkadot/react-components/types';
+import { ComponentProps } from '../props';
+import { withCalls } from '@polkadot/react-api/index';
+import { Table } from 'semantic-ui-react';
+import { Option } from '@polkadot/types';
+import { AccountId } from '@polkadot/types/interfaces';
+import AddressMini from '@polkadot/react-components/AddressMiniJoy';
+import { Actor } from '@joystream/types/roles';
+import { MemberId } from '@joystream/types/members';
+import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
+import { queryMembershipToProp } from '@polkadot/joy-members/utils';
+import TxButton from '@polkadot/joy-utils/TxButton';
+
+type MemberIdProps = {
+  memberIdByAccountId?: Option<MemberId>,
+};
+
+type Props = BareProps & ComponentProps & MyAccountProps & MemberIdProps;
+
+class ActorsList extends React.PureComponent<Props> {
+  render() {
+    const { actorAccountIds, memberIdByAccountId } = this.props;
+
+    return (
+      <Table>
+        <Table.Header>
+          <Table.Row>
+            <Table.HeaderCell>Member Id</Table.HeaderCell>
+            <Table.HeaderCell>Role</Table.HeaderCell>
+            <Table.HeaderCell>Actor Account</Table.HeaderCell>
+            <Table.HeaderCell></Table.HeaderCell>
+          </Table.Row>
+        </Table.Header>
+        <Table.Body>{actorAccountIds.map((account: string) =>
+          <ActorDisplay key={account} account={account} memberIdByAccountId={memberIdByAccountId} />
+        )}
+        </Table.Body>
+      </Table>
+    )
+  }
+}
+
+
+type ActorProps = BareProps & MemberIdProps & {
+  account: string,
+  actor?: Option<Actor>
+}
+
+class ActorInner extends React.PureComponent<ActorProps> {
+  render() {
+    const { actor: actorOpt, memberIdByAccountId: memberIdOpt } = this.props;
+
+    if (!actorOpt || actorOpt.isNone || !memberIdOpt) return null;
+
+    const actor = actorOpt.unwrap();
+
+    const memberId = memberIdOpt.isSome ? memberIdOpt.unwrap() : undefined;
+    const memberIsActor = memberId && (memberId.toString() == actor.member_id.toString());
+
+    return (
+      <Table.Row>
+        <Table.Cell>{actor.member_id.toString()}</Table.Cell>
+        <Table.Cell>{actor.role.toString()}</Table.Cell>
+        <Table.Cell>
+          <AddressMini value={actor.account} isShort={false} isPadded={false} withBalance={true} /></Table.Cell>
+        {memberIsActor ? <Table.Cell>{this.renderUnstakeTxButton(actor.account)}</Table.Cell> : null}
+      </Table.Row>
+    )
+  }
+
+  private renderUnstakeTxButton(account: AccountId) {
+    return <TxButton tx={'actors.unstake'} params={[account]} label={'Unstake'}
+      type='submit' size='large' isDisabled={false} />
+  }
+}
+
+const ActorDisplay = withCalls<ActorProps>(
+  ['query.actors.actorByAccountId', { propName: 'actor', paramName: 'account' }]
+)(ActorInner)
+
+
+const ActionableActorsList = withMyAccount(withCalls<Props>(
+  queryMembershipToProp('memberIdByAccountId', 'myAddress')
+)(ActorsList));
+
+export default ActionableActorsList;

+ 97 - 0
packages/joy-storage/src/AvailableRoles/index.tsx

@@ -0,0 +1,97 @@
+import React from 'react'
+import { BareProps } from '@polkadot/react-components/types';
+import { ComponentProps } from '../props';
+import { Role, RoleParameters } from '@joystream/types/roles';
+import { Option } from '@polkadot/types';
+import { AccountId } from '@polkadot/types/interfaces';
+import { withCalls } from '@polkadot/react-api/index';
+import { Table } from 'semantic-ui-react';
+import Section from '@polkadot/joy-utils/Section';
+import { formatBalance } from '@polkadot/util';
+
+import BN from 'bn.js';
+
+type Props = BareProps & ComponentProps;
+
+export default class AvailableRoles extends React.PureComponent<Props> {
+  render() {
+    return (
+      <div>{this.props.roles.map((role) =>
+        <div key={role.toString()}><RoleDisplay role={role} /></div>)
+      }</div>
+    )
+  }
+}
+
+
+type RoleProps = BareProps & {
+  role: Role,
+  roleParams?: Option<RoleParameters>,
+  actorAccountIds?: Array<AccountId>
+}
+
+class RoleDisplayInner extends React.PureComponent<RoleProps> {
+  render() {
+    const { role, roleParams, actorAccountIds } = this.props;
+    if (!roleParams || roleParams.isNone || !actorAccountIds) return <em>Loading...</em>;
+
+    const params = roleParams.unwrap();
+
+    return (
+      <Section title={role.toString()}>
+        <Parameters role={role} params={params} active={actorAccountIds.length}></Parameters>
+      </Section>
+    )
+  }
+}
+
+const RoleDisplay = withCalls<RoleProps>(
+  ['query.actors.parameters', { propName: 'roleParams', paramName: 'role' }],
+  ['query.actors.accountIdsByRole', { propName: 'actorAccountIds', paramName: 'role' }],
+)(RoleDisplayInner)
+
+
+type ParamProps = BareProps & {
+  role: Role,
+  params: RoleParameters,
+  active: number,
+}
+
+const Parameters = function Parameters(props: ParamProps) {
+  const { params, role, active } = props;
+
+  const minStake = formatBalance(new BN(params.min_stake));
+  const maxActors = (new BN(params.max_actors)).toString();
+  const reward = formatBalance(new BN(params.reward));
+  const rewardPeriod = (new BN(params.reward_period)).toString();
+  const unbondingPeriod = (new BN(params.unbonding_period)).toString();
+
+  return (
+    <Table>
+      <Table.Header>
+        <Table.Row>
+          <Table.HeaderCell>Role Id</Table.HeaderCell>
+          <Table.HeaderCell>{role.toNumber()}</Table.HeaderCell>
+        </Table.Row>
+      </Table.Header>
+      <Table.Body>
+        <Table.Row>
+          <Table.Cell>Minimum Stake</Table.Cell>
+          <Table.Cell>{minStake}</Table.Cell>
+        </Table.Row>
+        <Table.Row>
+          <Table.Cell>Actors</Table.Cell>
+          <Table.Cell>{active}/{maxActors}</Table.Cell>
+        </Table.Row>
+        <Table.Row>
+          <Table.Cell>Reward</Table.Cell>
+          <Table.Cell>{reward}, every {rewardPeriod} blocks</Table.Cell>
+        </Table.Row>
+        <Table.Row>
+          <Table.Cell>Unbonding Period</Table.Cell>
+          <Table.Cell>{unbondingPeriod} blocks</Table.Cell>
+        </Table.Row>
+      </Table.Body>
+    </Table>
+  )
+};

+ 98 - 0
packages/joy-storage/src/MyRequests/index.tsx

@@ -0,0 +1,98 @@
+import React from 'react';
+import { Table } from 'semantic-ui-react';
+import { BareProps, CallProps } from '@polkadot/react-api/types';
+import { MyAccountProps, withOnlyMembers } from '@polkadot/joy-utils/MyAccount';
+import { withCalls, withMulti } from '@polkadot/react-api/index';
+import { Request, Role, RoleParameters } from '@joystream/types/roles';
+import { Option } from '@polkadot/types';
+import { AccountId, Balance } from '@polkadot/types/interfaces';
+import TxButton from '@polkadot/joy-utils/TxButton';
+import BN from 'bn.js';
+import AddressMini from '@polkadot/react-components/AddressMiniJoy';
+import { ComponentProps } from '../props';
+
+type Props = BareProps & ComponentProps & MyAccountProps & {
+  requests: Array<Request>
+};
+
+class ActionList extends React.PureComponent<Props> {
+  render() {
+    const { myMemberId, requests } = this.props;
+    if (!myMemberId) {
+      return <em>Loading...</em>;
+    }
+
+    // filter requests for member
+    const filteredRequests = requests.filter((request) => request[1].toString() === myMemberId.toString());
+
+    if (filteredRequests.length) {
+      return this.renderActions(filteredRequests);
+    } else {
+      return <div>No requests for member id: {myMemberId.toString()}</div>;
+    }
+  }
+
+  private renderActions(requests: Array<Request>) {
+    return (
+      <Table>
+        <Table.Body>
+          {
+            requests.map(([account, _, role]: Request) => {
+              return <ActionDisplay account={account} role={role} key={account.toString()}></ActionDisplay>;
+            })
+          }
+        </Table.Body>
+      </Table>
+    );
+  }
+}
+
+type ActionProps = BareProps & CallProps & {
+  account: AccountId,
+  role: Role,
+  balance?: Balance,
+  roleParams?: Option<RoleParameters>
+};
+
+class Action extends React.PureComponent<ActionProps> {
+  render() {
+    const { account, role, balance, roleParams } = this.props;
+
+    if (!balance || !roleParams || roleParams.isNone) return null;
+
+    const params = roleParams.unwrap();
+
+    const minStake = new BN(params.min_stake);
+    const canStake = balance.gte(minStake);
+
+    return (
+      <Table.Row>
+        <Table.Cell>
+          <AddressMini value={account} isShort={false} isPadded={false} withBalance={true} />
+        </Table.Cell>
+        <Table.Cell>{role.toString()}</Table.Cell>
+        <Table.Cell>{canStake ? null : `(minimum required balance: ${minStake.toString()})`}</Table.Cell>
+        <Table.Cell>
+          <TxButton
+            type='submit'
+            size='large'
+            label={'Stake'}
+            isDisabled={!canStake}
+            params={[role, account]}
+            tx={'actors.stake'}
+          />
+        </Table.Cell>
+      </Table.Row>
+    );
+  }
+}
+
+const ActionDisplay = withCalls<ActionProps>(
+  ['query.balances.freeBalance', { propName: 'balance', paramName: 'account' }],
+  ['query.actors.parameters', { propName: 'roleParams', paramName: 'role' }]
+)(Action);
+
+export default withMulti(
+  ActionList,
+  withOnlyMembers
+);

+ 0 - 0
packages/joy-storage/src/index.css


+ 133 - 0
packages/joy-storage/src/index.tsx

@@ -0,0 +1,133 @@
+import { AppProps, I18nProps } from '@polkadot/react-components/types';
+import { ApiProps } from '@polkadot/react-api/types';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+import { ComponentProps } from './props';
+import { Request, Role } from '@joystream/types/roles';
+
+import React from 'react';
+import { Route, Switch } from 'react-router';
+import { AccountId } from '@polkadot/types/interfaces';
+import Tabs, { TabItem } from '@polkadot/react-components/Tabs';
+import accountObservable from '@polkadot/ui-keyring/observable/accounts';
+import { withCalls, withMulti, withObservable } from '@polkadot/react-api/index';
+
+import ActorsList from './ActorsList';
+import MyRequests from './MyRequests';
+import AvailableRoles from './AvailableRoles';
+
+import './index.css';
+
+import translate from './translate';
+
+type Props = AppProps & ApiProps & I18nProps & {
+  requests?: Array<Request>,
+  actorAccountIds?: Array<AccountId>,
+  roles?: Array<Role>,
+  allAccounts?: SubjectInfo,
+};
+
+type State = {
+  tabs: Array<TabItem>,
+  actorAccountIds: Array<string>,
+  requests: Array<Request>,
+  roles: Array<Role>,
+};
+
+class App extends React.PureComponent<Props, State> {
+  state: State;
+
+  constructor(props: Props) {
+    super(props);
+
+    const { t } = props;
+
+    this.state = {
+      actorAccountIds: [],
+      requests: [],
+      roles: [],
+      tabs: [
+        {
+          isRoot: true,
+          name: 'actors',
+          text: t('Actors')
+        },
+        {
+          name: 'roles',
+          text: t('Available Roles')
+        },
+        {
+          name: 'requests',
+          text: t('My Requests')
+        },
+      ],
+    };
+  }
+
+  static getDerivedStateFromProps({ actorAccountIds, requests, roles }: Props): State {
+    return {
+      actorAccountIds: (actorAccountIds || []).map((accountId) =>
+        accountId.toString()
+      ),
+      requests: (requests || []).map((request) =>
+        request
+      ),
+      roles: (roles || []).map((role) =>
+        role
+      ),
+    } as State;
+  }
+
+  render() {
+    const { allAccounts } = this.props;
+    const { tabs } = this.state;
+    const { basePath } = this.props;
+    const hasAccounts = allAccounts && Object.keys(allAccounts).length;
+    const filteredTabs = hasAccounts
+      ? tabs
+      : tabs.filter(({ name }) =>
+        !['requests'].includes(name)
+      );
+
+    return (
+      <main className='actors--App'>
+        <header>
+          <Tabs
+            basePath={basePath}
+            items={filteredTabs}
+          />
+        </header>
+        <Switch>
+          <Route path={`${basePath}/requests`} render={this.renderComponent(MyRequests)} />
+          <Route path={`${basePath}/roles`} render={this.renderComponent(AvailableRoles)} />
+          <Route render={this.renderComponent(ActorsList)} />
+        </Switch>
+      </main>
+    );
+  }
+
+  private renderComponent(Component: React.ComponentType<ComponentProps>) {
+    return (): React.ReactNode => {
+      const { actorAccountIds, requests, roles } = this.state;
+
+      return (
+        <Component
+          actorAccountIds={actorAccountIds}
+          requests={requests}
+          roles={roles}
+        />
+      );
+    };
+  }
+
+}
+
+export default withMulti(
+  App,
+  translate,
+  withObservable(accountObservable.subject, { propName: 'allAccounts' }),
+  withCalls<Props>(
+    ['query.actors.actorAccountIds', { propName: 'actorAccountIds' }],
+    ['query.actors.roleEntryRequests', { propName: 'requests' }],
+    ['query.actors.availableRoles', { propName: 'roles' }],
+  )
+);

+ 7 - 0
packages/joy-storage/src/props.ts

@@ -0,0 +1,7 @@
+import { Request, Role } from '@joystream/types/roles';
+
+export type ComponentProps = {
+  actorAccountIds: Array<string>,
+  requests: Array<Request>
+  roles: Array<Role>,
+};

+ 3 - 0
packages/joy-storage/src/translate.ts

@@ -0,0 +1,3 @@
+import { withTranslation } from 'react-i18next';
+
+export default withTranslation(['actors', 'ui']);

+ 2 - 0
tsconfig.json

@@ -28,6 +28,8 @@
       "@polkadot/joy-proposals/*": [ "packages/joy-proposals/src/*" ],
       "@polkadot/joy-roles/": [ "packages/joy-roles/src/" ],
       "@polkadot/joy-roles/*": [ "packages/joy-roles/src/*" ],
+      "@polkadot/joy-storage/": [ "packages/joy-storage/src/" ],
+      "@polkadot/joy-storage/*": [ "packages/joy-storage/src/*" ],
       "@polkadot/joy-settings/": [ "packages/joy-settings/src/" ],
       "@polkadot/joy-settings/*": [ "packages/joy-settings/src/*" ],
       "@polkadot/joy-utils/": [ "packages/joy-utils/src/" ],