Browse Source

Merge pull request #721 from Joystream/kdembler_sideproject

Kdembler sideproject
Mokhtar Naamani 4 years ago
parent
commit
bd32f07dc2

+ 6 - 0
pioneer/packages/app-accounts/src/Overview.tsx

@@ -7,6 +7,8 @@ import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 import { ComponentProps } from './types';
 
 import React, { useState } from 'react';
+import { Link, useLocation } from 'react-router-dom';
+import { Button as SUIButton } from 'semantic-ui-react';
 import keyring from '@polkadot/ui-keyring';
 import accountObservable from '@polkadot/ui-keyring/observable/accounts';
 import { getLedger, isLedger, withMulti, withObservable } from '@polkadot/react-api';
@@ -35,6 +37,7 @@ async function queryLedger (): Promise<void> {
 }
 
 function Overview ({ accounts, onStatusChange, t }: Props): React.ReactElement<Props> {
+  const { pathname } = useLocation();
   const [isCreateOpen, setIsCreateOpen] = useState(false);
   const [isImportOpen, setIsImportOpen] = useState(false);
   const emptyScreen = !(isCreateOpen || isImportOpen) && accounts && (Object.keys(accounts).length === 0);
@@ -44,6 +47,9 @@ function Overview ({ accounts, onStatusChange, t }: Props): React.ReactElement<P
 
   return (
     <CardGrid
+      topButtons={
+        !emptyScreen && <SUIButton as={Link} to={`${pathname}/vanity`}>Generate a vanity address</SUIButton>
+      }
       buttons={
         <Button.Group>
           <Button

+ 21 - 12
pioneer/packages/app-accounts/src/Vanity/index.tsx

@@ -18,6 +18,7 @@ import matchRegex from '../vanitygen/regex';
 import generatorSort from '../vanitygen/sort';
 import Match from './Match';
 import translate from './translate';
+import Section from '@polkadot/joy-utils/Section';
 
 interface Props extends ComponentProps, I18nProps {}
 
@@ -42,6 +43,10 @@ const BOOL_OPTIONS = [
   { text: 'Yes', value: true }
 ];
 
+const SectionContentWrapper = styled.div`
+  margin-left: -2rem;
+`;
+
 class VanityApp extends TxComponent<Props, State> {
   private results: GeneratorResult[] = [];
 
@@ -72,18 +77,22 @@ class VanityApp extends TxComponent<Props, State> {
 
     return (
       <div className={className}>
-        {this.renderOptions()}
-        {this.renderButtons()}
-        {this.renderStats()}
-        {this.renderMatches()}
-        {createSeed && (
-          <CreateModal
-            onClose={this.closeCreate}
-            onStatusChange={onStatusChange}
-            seed={createSeed}
-            type={type}
-          />
-        )}
+        <Section title="Generate a vanity address">
+          <SectionContentWrapper>
+            {this.renderOptions()}
+            {this.renderButtons()}
+            {this.renderStats()}
+            {this.renderMatches()}
+            {createSeed && (
+              <CreateModal
+                onClose={this.closeCreate}
+                onStatusChange={onStatusChange}
+                seed={createSeed}
+                type={type}
+              />
+            )}
+          </SectionContentWrapper>
+        </Section>
       </div>
     );
   }

+ 0 - 4
pioneer/packages/app-accounts/src/index.tsx

@@ -62,10 +62,6 @@ function AccountsApp ({ allAccounts = {}, basePath, location, onStatusChange, t
               name: 'overview',
               text: t('My accounts')
             },
-            {
-              name: 'vanity',
-              text: t('Vanity address')
-            },
             {
               name: 'memo',
               text: t('My memo')

+ 15 - 8
pioneer/packages/app-address-book/src/Overview.tsx

@@ -7,7 +7,9 @@ import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 import { ComponentProps } from './types';
 
 import React, { useState } from 'react';
+import { Link, useLocation } from 'react-router-dom';
 import { Button, CardGrid } from '@polkadot/react-components';
+import { Button as SUIButton, Icon } from 'semantic-ui-react';
 import addressObservable from '@polkadot/ui-keyring/observable/addresses';
 import { withMulti, withObservable } from '@polkadot/react-api';
 
@@ -20,6 +22,7 @@ interface Props extends ComponentProps, I18nProps {
 }
 
 function Overview ({ addresses, onStatusChange, t }: Props): React.ReactElement<Props> {
+  const { pathname } = useLocation();
   const [isCreateOpen, setIsCreateOpen] = useState(false);
   const emptyScreen = !isCreateOpen && (!addresses || Object.keys(addresses).length === 0);
 
@@ -27,15 +30,19 @@ function Overview ({ addresses, onStatusChange, t }: Props): React.ReactElement<
 
   return (
     <CardGrid
+      topButtons={
+        <SUIButton as={Link} to={`${pathname}/memo`}>
+          <Icon name="search" />
+          View memo
+        </SUIButton>
+      }
       buttons={
-        <Button.Group>
-          <Button
-            icon='add'
-            isPrimary
-            label={t('Add contact')}
-            onClick={_toggleCreate}
-          />
-        </Button.Group>
+        <Button
+          icon='add'
+          isPrimary
+          label={t('Add contact')}
+          onClick={_toggleCreate}
+        />
       }
       isEmpty={emptyScreen}
       emptyText={t('No contacts found.')}

+ 30 - 19
pioneer/packages/app-address-book/src/index.tsx

@@ -8,8 +8,10 @@ import { ComponentProps } from './types';
 
 import React from 'react';
 import { Route, Switch } from 'react-router';
+import { Link } from 'react-router-dom';
+import styled from 'styled-components';
+import { Breadcrumb } from 'semantic-ui-react';
 import { HelpOverlay } from '@polkadot/react-components';
-import Tabs from '@polkadot/react-components/Tabs';
 
 import basicMd from './md/basic.md';
 import Overview from './Overview';
@@ -21,7 +23,16 @@ interface Props extends AppProps, I18nProps {
   location: any;
 }
 
-function AddressBookApp ({ basePath, onStatusChange, t }: Props): React.ReactElement<Props> {
+const StyledHeader = styled.header`
+  text-align: left;
+
+  .ui.breadcrumb {
+    padding: 1.4rem 0 0 .4rem;
+    font-size: 1.4rem;
+  }
+`;
+
+function AddressBookApp ({ basePath, onStatusChange }: Props): React.ReactElement<Props> {
   const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
     // eslint-disable-next-line react/display-name
     return (): React.ReactNode =>
@@ -32,27 +43,27 @@ function AddressBookApp ({ basePath, onStatusChange, t }: Props): React.ReactEle
       />;
   };
 
+  const viewMemoPath = `${basePath}/memo/:accountId?`;
+
   return (
     <main className='address-book--App'>
       <HelpOverlay md={basicMd} />
-      <header>
-        <Tabs
-          basePath={basePath}
-          items={[
-            {
-              isRoot: true,
-              name: 'overview',
-              text: t('My contacts')
-            },
-            {
-              name: 'memo',
-              text: t('View memo')
-            }
-          ]}
-        />
-      </header>
+      <StyledHeader>
+        <Breadcrumb>
+          <Switch>
+            <Route path={viewMemoPath}>
+              <Breadcrumb.Section link as={Link} to={basePath}>Contacts</Breadcrumb.Section>
+              <Breadcrumb.Divider icon="right angle" />
+              <Breadcrumb.Section active>View memo</Breadcrumb.Section>
+            </Route>
+            <Route>
+              <Breadcrumb.Section active>Contacts</Breadcrumb.Section>
+            </Route>
+          </Switch>
+        </Breadcrumb>
+      </StyledHeader>
       <Switch>
-        <Route path={`${basePath}/memo/:accountId?`} component={MemoByAccount} />
+        <Route path={viewMemoPath} component={MemoByAccount} />
         <Route render={_renderComponent(Overview)} />
       </Switch>
     </main>

+ 10 - 5
pioneer/packages/joy-election/src/SealedVotes.tsx

@@ -1,4 +1,6 @@
 import React from 'react';
+import { Link } from 'react-router-dom';
+import { Button } from 'semantic-ui-react';
 
 import { I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
@@ -42,11 +44,14 @@ class Comp extends React.PureComponent<Props> {
           ? <em>No votes by the current account found on the current browser.</em>
           : this.renderVotes(myVotes)
       }</Section>
-      <Section title={`Other votes (${otherVotes.length})`}>{
-        !otherVotes.length
-          ? <em>No votes submitted by other accounts yet.</em>
-          : this.renderVotes(otherVotes)
-      }</Section>
+      <Section title={`Other votes (${otherVotes.length})`}>
+        <Button primary as={Link} to="reveals">Reveal a vote</Button>
+        {
+          !otherVotes.length
+            ? <em>No votes submitted by other accounts yet.</em>
+            : this.renderVotes(otherVotes)
+        }
+      </Section>
     </>;
   }
 }

+ 0 - 4
pioneer/packages/joy-election/src/index.tsx

@@ -51,10 +51,6 @@ class App extends React.PureComponent<Props, State> {
       {
         name: 'votes',
         text: t('Votes') + ` (${commitments.length})`
-      },
-      {
-        name: 'reveals',
-        text: t('Reveal a vote')
       }
     ];
   }

+ 18 - 15
pioneer/packages/joy-media/src/channels/EditChannel.tsx

@@ -17,6 +17,7 @@ import { TxCallback } from '@polkadot/react-components/Status/types';
 import { SubmittableResult } from '@polkadot/api';
 import { ChannelValidationConstraints } from '../transport';
 import { JoyError } from '@polkadot/joy-utils/JoyStatus';
+import Section from '@polkadot/joy-utils/Section';
 
 export type OuterProps = {
   history?: History;
@@ -176,21 +177,23 @@ const InnerForm = (props: MediaFormProps<OuterProps, FormValues>) => {
       {avatar && <img src={avatar} onError={onImageError} />}
     </div>
 
-    <Form className='ui form JoyForm EditMetaForm'>
-
-      {formFields()}
-
-      <LabelledField style={{ marginTop: '1rem' }} {...props}>
-        {renderMainButton()}
-        <Button
-          type='button'
-          size='large'
-          disabled={!dirty || isSubmitting}
-          onClick={() => resetForm()}
-          content='Reset form'
-        />
-      </LabelledField>
-    </Form>
+    <Section title={isNew ? 'Create a channel' : 'Edit a channel'}>
+      <Form className='ui form JoyForm EditMetaForm'>
+
+        {formFields()}
+
+        <LabelledField style={{ marginTop: '1rem' }} {...props}>
+          {renderMainButton()}
+          <Button
+            type='button'
+            size='large'
+            disabled={!dirty || isSubmitting}
+            onClick={() => resetForm()}
+            content='Reset form'
+          />
+        </LabelledField>
+      </Form>
+    </Section>
   </div>;
 };
 

+ 1 - 1
pioneer/packages/joy-media/src/index.css

@@ -124,7 +124,7 @@
     }
   }
 
-  .EditMetaForm {
+  .JoySection {
     width: 100%;
     max-width: 600px;
   }

+ 0 - 4
pioneer/packages/joy-media/src/index.tsx

@@ -40,10 +40,6 @@ function App (props: Props) {
     !myAddress ? undefined : {
       name: `account/${myAddress}/channels`,
       text: t('My channels')
-    },
-    {
-      name: 'channels/new',
-      text: t('New channel')
     }
     // !myAddress ? undefined : {
     //   name: `account/${myAddress}/videos`,

+ 1 - 1
pioneer/packages/joy-members/src/index.tsx

@@ -37,7 +37,7 @@ class App extends React.PureComponent<Props> {
       },
       {
         name: 'edit',
-        text: iAmMember ? t('Edit my profile') : t('Register')
+        text: iAmMember ? 'My profile' : t('Register')
       },
       {
         name: 'dashboard',

+ 69 - 13
pioneer/packages/joy-proposals/src/Proposal/ProposalPreviewList.tsx

@@ -1,5 +1,7 @@
 import React, { useState } from 'react';
-import { Card, Container, Menu } from 'semantic-ui-react';
+import { Button, Card, Container, Icon } from 'semantic-ui-react';
+import styled from 'styled-components';
+import { Link, useLocation } from 'react-router-dom';
 
 import ProposalPreview from './ProposalPreview';
 import { ParsedProposal } from '@polkadot/joy-utils/types/proposals';
@@ -7,6 +9,7 @@ import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks';
 import { PromiseComponent } from '@polkadot/joy-utils/react/components';
 import { withCalls } from '@polkadot/react-api';
 import { BlockNumber } from '@polkadot/types/interfaces';
+import { Dropdown } from '@polkadot/react-components';
 
 const filters = ['All', 'Active', 'Canceled', 'Approved', 'Rejected', 'Slashed', 'Expired'] as const;
 
@@ -50,31 +53,84 @@ type ProposalPreviewListProps = {
   bestNumber?: BlockNumber;
 };
 
+const FilterContainer = styled.div`
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  margin-bottom: 1.75rem;
+`;
+const FilterOption = styled.span`
+  display: inline-flex;
+  align-items: center;
+`;
+const ProposalFilterCountBadge = styled.span`
+  background-color: rgba(0, 0, 0, .3);
+  color: #fff;
+
+  border-radius: 10px;
+  height: 19px;
+  min-width: 19px;
+  padding: 0 4px;
+
+  font-size: .8rem;
+  font-weight: 500;
+  line-height: 1;
+
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  margin-left: 6px;
+`;
+const StyledDropdown = styled(Dropdown)`
+  .dropdown {
+    width: 200px;
+  }
+`;
+
 function ProposalPreviewList ({ bestNumber }: ProposalPreviewListProps) {
+  const { pathname } = useLocation();
   const transport = useTransport();
   const [proposals, error, loading] = usePromise<ParsedProposal[]>(() => transport.proposals.proposals(), []);
   const [activeFilter, setActiveFilter] = useState<ProposalFilter>('All');
 
   const proposalsMap = mapFromProposals(proposals);
   const filteredProposals = proposalsMap.get(activeFilter) as ParsedProposal[];
+  const sortedProposals = filteredProposals.sort((p1, p2) => p2.id.cmp(p1.id));
+
+  const filterOptions = filters.map(filter => ({
+    text: (
+      <FilterOption>
+        {filter}
+        <ProposalFilterCountBadge>{(proposalsMap.get(filter) as ParsedProposal[]).length}</ProposalFilterCountBadge>
+      </FilterOption>
+    ),
+    value: filter
+  }));
+
+  const _onChangePrefix = (f: ProposalFilter) => setActiveFilter(f);
 
   return (
     <Container className="Proposal" fluid>
+      <FilterContainer>
+        <Button primary as={Link} to={`${pathname}/new`}>
+          <Icon name="add" />
+          New proposal
+        </Button>
+        {!loading && (
+          <StyledDropdown
+            label="Proposal state"
+            options={filterOptions}
+            value={activeFilter}
+            onChange={_onChangePrefix}
+          />
+        )}
+      </FilterContainer>
       <PromiseComponent error={ error } loading={ loading } message="Fetching proposals...">
-        <Menu tabular className="list-menu">
-          {filters.map((filter, idx) => (
-            <Menu.Item
-              key={`${filter} - ${idx}`}
-              name={`${filter.toLowerCase()} - ${(proposalsMap.get(filter) as ParsedProposal[]).length}`}
-              active={activeFilter === filter}
-              onClick={() => setActiveFilter(filter)}
-            />
-          ))}
-        </Menu>
         {
-          filteredProposals.length ? (
+          sortedProposals.length ? (
             <Card.Group>
-              {filteredProposals.map((prop: ParsedProposal, idx: number) => (
+              {sortedProposals.map((prop: ParsedProposal, idx: number) => (
                 <ProposalPreview key={`${prop.title}-${idx}`} proposal={prop} bestNumber={bestNumber} />
               ))}
             </Card.Group>

+ 27 - 17
pioneer/packages/joy-proposals/src/index.tsx

@@ -1,8 +1,10 @@
 import React from 'react';
 import { Route, Switch } from 'react-router';
+import { Link } from 'react-router-dom';
+import styled from 'styled-components';
+import { Breadcrumb } from 'semantic-ui-react';
 
 import { AppProps, I18nProps } from '@polkadot/react-components/types';
-import Tabs, { TabItem } from '@polkadot/react-components/Tabs';
 import { TransportProvider } from '@polkadot/joy-utils/react/context';
 import { ProposalPreviewList, ProposalFromId, ChooseProposalType } from './Proposal';
 
@@ -24,27 +26,35 @@ import {
 
 interface Props extends AppProps, I18nProps {}
 
-function App (props: Props): React.ReactElement<Props> {
-  const { t, basePath } = props;
+const StyledHeader = styled.header`
+  text-align: left;
+
+  .ui.breadcrumb {
+    padding: 1.4rem 0 0 .4rem;
+    font-size: 1.4rem;
+  }
+`;
 
-  const tabs: TabItem[] = [
-    {
-      isRoot: true,
-      name: 'proposals',
-      text: t('Proposals')
-    },
-    {
-      name: 'new',
-      text: t('New Proposal')
-    }
-  ];
+function App (props: Props): React.ReactElement<Props> {
+  const { basePath } = props;
 
   return (
     <TransportProvider>
       <main className="proposal--App">
-        <header>
-          <Tabs basePath={basePath} items={tabs} />
-        </header>
+        <StyledHeader>
+          <Breadcrumb>
+            <Switch>
+              <Route path={`${basePath}/new`}>
+                <Breadcrumb.Section link as={Link} to={basePath}>Proposals</Breadcrumb.Section>
+                <Breadcrumb.Divider icon="right angle" />
+                <Breadcrumb.Section active>New proposal</Breadcrumb.Section>
+              </Route>
+              <Route>
+                <Breadcrumb.Section active>Proposals</Breadcrumb.Section>
+              </Route>
+            </Switch>
+          </Breadcrumb>
+        </StyledHeader>
         <Switch>
           <Route exact path={`${basePath}/new`} component={ChooseProposalType} />
           <Route exact path={`${basePath}/new/text`} component={SignalForm} />

+ 1 - 1
pioneer/packages/react-components/src/CardGrid.tsx

@@ -15,7 +15,7 @@ class CardGrid extends Collection<Props, State> {
 
     return {
       ...state,
-      showHeader: !state.isEmpty || !!props.headerText
+      showFullHeader: !state.isEmpty
     };
   }
 

+ 21 - 8
pioneer/packages/react-components/src/Collection.tsx

@@ -9,6 +9,7 @@ import React from 'react';
 export interface CollectionProps extends I18nProps {
   banner?: React.ReactNode;
   buttons?: React.ReactNode;
+  topButtons?: React.ReactNode;
   children: React.ReactNode;
   className?: string;
   headerText?: React.ReactNode;
@@ -19,7 +20,7 @@ export interface CollectionProps extends I18nProps {
 
 export interface CollectionState {
   isEmpty: boolean;
-  showHeader?: boolean;
+  showFullHeader?: boolean;
 }
 
 export const collectionStyles = `
@@ -33,6 +34,12 @@ export const collectionStyles = `
       margin: 0;
       text-transform: lowercase;
     }
+
+    .ui--Collection-buttons {
+      flex: 1;
+      display: flex;
+      justify-content: space-between;
+    }
   }
 
   .ui--Collection-lowercase {
@@ -61,11 +68,11 @@ export default class Collection<P extends CollectionProps, S extends CollectionS
 
   public render (): React.ReactNode {
     const { banner, className } = this.props;
-    const { isEmpty, showHeader } = this.state;
+    const { isEmpty } = this.state;
 
     return (
       <div className={className}>
-        {showHeader && this.renderHeader()}
+        {this.renderHeader()}
         {banner}
         {isEmpty
           ? this.renderEmpty()
@@ -76,18 +83,24 @@ export default class Collection<P extends CollectionProps, S extends CollectionS
   }
 
   protected renderHeader (): React.ReactNode {
-    const { buttons, headerText } = this.props;
+    const { buttons, topButtons, headerText } = this.props;
+    const { showFullHeader } = this.state;
 
-    if (!headerText && !buttons) {
+    if (!headerText && !buttons && !topButtons) {
       return null;
     }
 
     return (
       <div className='ui--Collection-header'>
-        <h1>{headerText}</h1>
-        {buttons && (
+        {headerText && <h1>{headerText}</h1>}
+        {(buttons || topButtons) && (
           <div className='ui--Collection-buttons'>
-            {buttons}
+            <div>
+              {topButtons}
+            </div>
+            <div>
+              {showFullHeader && buttons}
+            </div>
           </div>
         )}
       </div>