Note
For a more general Pioneer documentation see Dev Readme.
After clonning the project, to run a developpment instance of Pioneer simply run:
yarn start
Pioneer is built using React 17. The React development assumes:
- TypeScript 5.x – using
strict:true
- Function components & hooks
- styled components for CSS
- Apollo client - to interact with GraphQL
- @polkadot/api - to interact with the RPC node
- RxJS to subscribe to the Polkadot API
- CKEditor 5 as Markdown editor
- xstate state management for complex flows in modals
- Yup validation (partially)
- date-fns to interact with dates
- Lodash helper functions
- React libraries for: routing, pagination, breadcrumbs, dropzone, etc (see package.json)
The build scripts uses Webpack directly (no CRA) as it integrates better with custom Webpack extensions (build CKEditor, etc.).
As the Storybook uses Babel a shared Webpack configuration for both webpack and storybook was introduced.
To build the project in a development mode using Webpack dev server:
yarn start
To build a production ready version:
yarn build
The application is divided to 4 types of building blocks (located in the src
directory):
common
- commonly used components & utilities not tied to specific domain- A domain/use-case specific:
accounts
– domain of Polkadot's accounts handling, transferring tokens, balances, etcapi
– domain containing Joystream's API specific code: hooks, providers, utilities, etccouncil
– council governanceforum
- forum subsystemmemberships
– memberships managementworking-groups
– working group governanceproposals
– proposal systembounty
,financials
,overview
: are domains which are not available yet but coming soon.services
– Contains internationalization utilities.
app
– Anything related to the application- assets
- routing
- pages
- global providers
ProxyApi
– [A service calling the Joystream API from a web worker instead of on the main thread])(#ProxyApi)mocks
– Utilities and data used for both the deprecated Query-node mocks and the new Storybook mocks.
Some rules/hints:
- More general packages should not import anything from more specific packages. So code inside
common
cannot import fromaccounts
orapp
. - Domain specific code can import from other domains and
common
as you can display account select component when creating membership. - The app folder describes how the application is wired-up.
The common and domain specific code is split by functionality:
components
- Contains React componentshooks
- Contains react hooksmodals
- Contains specific modalsmodel
- Contains business logic like validation, helpers, etc.queries
- Contains domain specific queriestypes
- TypeScript typesproviders
- React context providers
Most of the Pioneer uses common React app coding patterns. Some however, requires additional explanation.
The modals can be created as:
- locally included component
- or by requesting to show a global modal using
useModal()
hook:
The global modals should be included in <GlobalModals />
component. After that can be instantiated from anywhere in
the app.
Some modal's flows require a complex state handling. For those the Pioneer 2 app uses transitions defined using xstate library.
To add a stepper add the proper meta
key to the state definition:
const state = {
id: 'state',
meta: { isStep: true, stepTitle: 'First Step' }
}
If the states are nested, they will be rendered by @/common/model/machines/getSteps()
helper as a nested steps.
Example machine that uses nested steps:
export const myMachine = createMachine<Context, Event, State>({
initial: 'setup',
states: {
setup: {
id: 'setup',
meta: { isStep: true, stepTitle: 'Setup' },
on: { NEXT: 'general' },
},
general: {
id: 'general',
initial: 'stakingAccount',
meta: { isStep: true, stepTitle: 'General parameters' },
states: {
title: {
meta: { isStep: true, stepTitle: 'Title' },
on: {
BACK: '#setup',
NEXT: 'description'
},
},
description: {
meta: { isStep: true, stepTitle: 'Description' },
on: {
BACK: 'title',
NEXT: 'end',
},
},
end: {
type: 'final',
},
},
onDone: 'transaction',
},
tranasction: {}
}
})
For any transaction we use a dedicated machine
The transaction machine should be used as invoked actor:
// machine.ts
const machine = createMachine({
states: {
transaction: {
invoke: {
id: 'transaction',
src: transactionMachine,
// Automatic transition to the next state
onDone: [
{
target: 'success',
// Save events to process responses & errors
actions: assign({ transactionEvents: (context, event) => event.data.events }),
// Transition guard
cond: isTransactionSuccess,
},
{
target: 'error',
actions: assign({ transactionEvents: (context, event) => event.data.events }),
cond: isTransactionError,
},
{
target: 'canceled',
cond: isTransactionCanceled,
},
],
},
},
success: { type: 'final' },
error: { type: 'final' },
canceled: { type: 'final' },
}
})
The transaction machine can be used either as a standalone machine or as a child of a bigger flow.
To access the archival state of the chain Pioneer fetch such information from the Query node. Read Pioneer architecture section for more details. It is a GraphQL server that allows a convenient API for querying the data.
The following tools are used to consume GraphQL data:
- Apollo Client for accessing GraphQL
To fetch the data from a GraphQL we use code generated by GraphQL Code Generator
To generate scripts run:
yarn run queries:generate
The queries are organized as below:
- The query-node schema is stored under @/common/api/schema.graphql
- GraphQL's queries are stored per every module, inside
@/module/queries/
folder - you only need to modify those. - The
graphq-codegen
will generate React hooks for Apollo Client (plugintypescript-react-apollo
) that will be exposed as@/module/queries
import.
For instance, to query for memberships
:
Create a @/memberships/queries/members.graphql
file:
query GetMembers {
memberships {
id
handle
}
}
Then run the yarn run queries:generate
script.
Use the generated hook in your code:
import { useGetMembersQuery } from '@/memberships/queries'
const { loading, data } = useGetMembersQuery()
Some GraphQL related tools use code generation to scaffold types and react hooks from GraphQL schemas and queries.
After updating any of the *.graphql
files run the yarn queries:generated
script.
To interact with the Joystream node a polkadot{.js} API instance as to be created. Because part of the instance creation is resource intensive it results in the App freezing on lower spec machines. In order to solve this issue Pioneer creates the API instance in a web worker and all interaction with the API are done via the web worker.
This is done seamlessly by using on the main thread a ProxyApi
which has the same type signature than the ApiRx
instance. The ProxyApi
forwards the calls to the ApiRx
created on the web worker and forward the returned data too.
This feature can be disabled by running Pioneer with DISABLE_PROXY_API=true
.
When a chain is run with in development Substrate creates well-known accounts for Alice
, Alice_Stash
, Bob
, Bob_Stash
, Charlie
, Dave
, Eve
and Ferdie
. Some of these accounts will are already assigned some token.
To add these to your wallet:
- Open extension and click plus sign
- Select "Import account from pre-existing seed"
- Copy this seed
bottom drive obey lake curtain smoke basket hold race lonely fit walk
as "existing mnemonic seed" - Open advanced and type the derivation path:
- For
Alice
,Bob
,Charlie
,Dave
,Eve
&Ferdie
use the name as path, e.g.//Eve
- For
Alice_Stash
andBob_Stash
use//stash
after name, e.g.://Bob//stash
Some networks expose JSON configuration describing their different endpoints. These configurations can be used to connect Pioneer to a network with an easy to share URL.
For example if Pioneer is running on localhost:8080
it can connect to the "Atlas dev" playground by going to http://localhost:8080/#/settings?network-config=https://atlas-dev.joystream.org/network/config.json.
A URL such as https://playground.test/config.json
can respond with a JSON:
{
"websocket_rpc": "wss://rpc-endpoint.com:9944",
"graphql_server": "https://joystream.app/server/graphql",
"graphql_server_websocket": "wss://joystream.app/server/graphql",
"member_faucet": "https://joystream.app/member-faucet/register"
}
By visiting pioneer with a query network-config
as below:
https://pioneer.joystream.app/#/settings?network-config=https://playground.test/config.json
This will save the endpoints locally under the "Auto-conf" network.
In case there is the network you wish to connect to has no JSON configuration (or the configuration is incomplete), custom endpoints can be set on the app too. In Settings -> Select Network
pick "Custom" there the network endpoints can be defined freely.
To use custom addresses add the .env
file in packages/ui
(example: packages/ui/.env.example
) and set
REACT_APP_MAINNET_NODE_SOCKET
examplewss://rpc.joystream.org:9944
REACT_APP_MAINNET_QUERY_NODE
examplehttps://query.joystream.org/graphql
REACT_APP_MAINNET_QUERY_NODE_SOCKET
examplewss://query.joystream.org/graphql
REACT_APP_MAINNET_MEMBERSHIP_FAUCET_URL
examplehttps://faucet.joystream.org/member-faucet/register
REACT_APP_MAINNET_BACKEND
examplehttps://api-7zai.onrender.com
Please remember to restart the Webpack process after each change.
All the variables are required to be configured for the network to be used.