Merge pull request #2 from status-im/watermelon-data-persist

Add data persistence and syncing
This commit is contained in:
Barry G 2019-02-01 08:18:39 -05:00 committed by GitHub
commit 30736c7a9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1191 additions and 234 deletions

67
app/actions/lpEvents.js Normal file
View File

@ -0,0 +1,67 @@
import { Q } from '@nozbe/watermelondb'
import database from '../db'
import { getAllLPEvents, GIVER_ADDED, DELEGATE_ADDED, PROJECT_ADDED } from '../utils/events'
const lpCollection = database.collections.get('lp_events')
export const addEvent = async data => {
await database.action(async () => {
const res = await lpCollection.create(lpEvent => {
const { event, address, id, blockNumber } = data
lpEvent.eventId = id
lpEvent.address = address
lpEvent.event = event
lpEvent.blockNumber = blockNumber
})
return res
})
}
export const batchAddEvents = async events => {
const batch = events.map(e => {
return lpCollection.prepareCreate(lpEvent => {
const { event, address, id, blockNumber, returnValues } = e
lpEvent.eventId = id
lpEvent.address = address
lpEvent.event = event
lpEvent.blockNumber = blockNumber
lpEvent.returnValues = returnValues
})
})
return await database.action(async () => await database.batch(...batch))
}
export const getLatestProfileEvents = async eventIds => {
const events = await lpCollection.query(
Q.where(
'id',
Q.notIn(eventIds)
),
Q.where(
'event',
Q.oneOf([GIVER_ADDED, DELEGATE_ADDED, PROJECT_ADDED])
)
).fetch()
return events
}
export const getLpEventById = async id => {
const event = await lpCollection.query(
Q.where('event_id', id)
).fetch()
return event
}
export const getLastBlockStored = async () => {
const col = await lpCollection.query().fetch()
const blockNumber = col.length
? col.sort((a,b) => b.blockNumber - a.blockNumber)[0].blockNumber
: 0
return blockNumber
}
export const getAndAddLpEvents = async () => {
const lastBlock = await getLastBlockStored()
const events = await getAllLPEvents(lastBlock + 1)
batchAddEvents(events)
}

61
app/actions/pledges.js Normal file
View File

@ -0,0 +1,61 @@
import { Q } from '@nozbe/watermelondb'
import database from '../db'
import { getAllPledges } from '../utils/pledges'
import { getProfilesById } from './profiles'
const createPledge = (pledge, data, profiles) => {
const { id, owner, amount, token, commitTime, nDelegates, pledgeState, intendedProject } = data
const profile = profiles.find(p => p.idProfile == owner)
pledge.pledgeId = Number(id)
pledge.owner = Number(owner)
pledge.amount = amount
pledge.token = token
pledge.commitTime = Number(commitTime)
pledge.nDelegates = Number(nDelegates)
pledge.pledgeState = pledgeState
pledge.intendedProject = Number(intendedProject)
pledge.profile.set(profile)
}
const pledgesCollection = database.collections.get('pledges')
export const addPledge = async data => {
return await database.action(async () => {
const res = await pledgesCollection.create(pledge => createPledge(pledge, data))
return res
})
}
export const batchAddPledges = async (pledges, profiles = []) => {
const batch = pledges.map(data => {
return pledgesCollection.prepareCreate(pledge => createPledge(pledge, data, profiles))
})
console.log({batch})
return await database.action(async () => await database.batch(...batch))
}
const getLastPledge = pledges => {
const pledgeId = pledges.length
? pledges.sort((a,b) => b.pledgeId - a.pledgeId)[0].pledgeId
: 0
return pledgeId
}
export const getAndAddPledges = async () => {
const pledges = await getLocalPledges()
const pledgeId = getLastPledge(pledges)
const newPledges = await getAllPledges(pledgeId + 1)
const pledgeIds = newPledges.map(p => p.owner)
const profiles = await getProfilesById(pledgeIds)
batchAddPledges(newPledges, profiles)
}
export const getLocalPledges = async () => {
const events = await pledgesCollection.query().fetch()
return events
}
export const getPledgeById = async id => {
const event = await pledgesCollection.query(
Q.where('id_profile', id)
).fetch()
return event
}

72
app/actions/profiles.js Normal file
View File

@ -0,0 +1,72 @@
import { Q } from '@nozbe/watermelondb'
import database from '../db'
import { getLatestProfileEvents } from './lpEvents'
import { formatFundProfileEvent } from '../utils/events'
const profilesCollection = database.collections.get('profiles')
export const addProfile = async data => {
return await database.action(async () => {
const res = await profilesCollection.create(profile => {
const { id, addr, canceled, commitTime, type, name, url, idProfile } = data
profile.eventId = id
profile.addr = addr
profile.canceled = canceled
profile.commitTime = Number(commitTime)
profile.type = type
profile.name = name
profile.url = url
profile.idProfile = Number(idProfile)
})
return res
})
}
export const batchAddProfiles = async profiles => {
const batch = profiles.map(data => {
return profilesCollection.prepareCreate(profile => {
const { id, addr, canceled, commitTime, type, name, url, idProfile } = data
profile.eventId = id
profile.addr = addr
profile.canceled = canceled
profile.commitTime = Number(commitTime)
profile.type = type
profile.name = name
profile.url = url
profile.idProfile = Number(idProfile)
})
})
return await database.action(async () => await database.batch(...batch))
}
export const addFormattedProfiles = async () => {
const allProfiles = await getAllProfiles()
const allEventIds = allProfiles.map(p => p.eventId)
const events = await getLatestProfileEvents(allEventIds)
const formattedEvents = await Promise.all(
events.map(formatFundProfileEvent)
)
await batchAddProfiles(formattedEvents)
}
export const getAllProfiles = async () => {
const events = await profilesCollection.query().fetch()
return events
}
export const getProfileById = async id => {
const event = await profilesCollection.query(
Q.where('id_profile', id)
).fetch()
return event
}
export const getProfilesById = async ids => {
const event = await profilesCollection.query(
Q.where(
'id_profile',
Q.oneOf(ids)
)
).fetch()
return event
}

View File

@ -0,0 +1,52 @@
import { Q } from '@nozbe/watermelondb'
import database from '../db'
import { ALL_EVENTS, getAllVaultEvents } from '../utils/events'
const vaultCollection = database.collections.get('vault_events')
export const addEvent = async data => {
await database.action(async () => {
const res = await vaultCollection.create(lpEvent => {
const { event, address, id, blockNumber } = data
lpEvent.eventId = id
lpEvent.address = address
lpEvent.event = event
lpEvent.blockNumber = blockNumber
})
return res
})
}
export const batchAddEvents = async events => {
const batch = events.map(e => {
return vaultCollection.prepareCreate(lpEvent => {
const { event, address, id, blockNumber, returnValues } = e
lpEvent.eventId = id
lpEvent.address = address
lpEvent.event = event
lpEvent.blockNumber = blockNumber
lpEvent.returnValues = returnValues
})
})
return await database.action(async () => await database.batch(...batch))
}
export const getVaultEventById = async id => {
const event = await vaultCollection.query(
Q.where('event_id', id)
).fetch()
return event
}
export const getLastBlockStored = async () => {
const col = await vaultCollection.query().fetch()
const blockNumber = col.length
? col.sort((a,b) => b.blockNumber - a.blockNumber)[0].blockNumber
: 0
return blockNumber
}
export const getAndAddVaultEvents = async () => {
const lastBlock = await getLastBlockStored()
const events = await getAllVaultEvents(lastBlock + 1)
batchAddEvents(events)
}

View File

@ -1,12 +1,22 @@
import React from 'react'
import PropTypes from 'prop-types'
import { withDatabase } from '@nozbe/watermelondb/DatabaseProvider'
import withObservables from '@nozbe/with-observables'
import PledgeAllocationsChart from './dashboard/PledgeAllocationsChart'
import FundingSummary from './dashboard/FundingSummary'
const Dashboard = () => (
const Dashboard = ({ pledges }) => (
<div>
<FundingSummary title="Funding Summary" />
<PledgeAllocationsChart title="Pledge Allocations" />
<FundingSummary title="Funding Summary" pledges={pledges} />
<PledgeAllocationsChart title="Pledge Allocations" pledges={pledges} />
</div>
)
export default Dashboard
Dashboard.propTypes = {
pledges: PropTypes.array.isRequired
}
export default withDatabase(withObservables([], ({ database }) => ({
pledges: database.collections.get('pledges').query().observe()
}))(Dashboard))

View File

@ -1,6 +1,8 @@
import React, { Fragment, memo } from 'react'
import React, { Fragment } from 'react'
import MaterialTable from 'material-table'
import LiquidPledging from 'Embark/contracts/LiquidPledging'
import withObservables from '@nozbe/with-observables'
import { withDatabase } from '@nozbe/watermelondb/DatabaseProvider'
import { FundingContext } from '../context'
const { cancelProject } = LiquidPledging.methods
@ -8,11 +10,11 @@ const { cancelProject } = LiquidPledging.methods
const convertToHours = seconds => seconds / 60 / 60
const cancelText = canceled => canceled ? 'Yes' : 'No'
const formatField = field => ({
...field,
...field.getFields(),
commitTime: convertToHours(field.commitTime),
canceled: cancelText(field.canceled)
})
const FunderProfilesTable = ({ data, cancelFundProfile }) => (
const FunderProfilesTable = ({ profiles }) => (
<FundingContext.Consumer>
{({ account }) =>
<Fragment>
@ -26,20 +28,21 @@ const FunderProfilesTable = ({ data, cancelFundProfile }) => (
{ title: 'Type', field: 'type' },
{ title: 'Canceled', field: 'canceled' }
]}
data={data.map(formatField)}
data={profiles.map(formatField)}
title="Funding Profiles"
options={{ showEmptyDataSourceMessage: true }}
actions={[
rowData => ({
icon: 'cancel',
disabled: rowData.addr.toLowerCase() != account.toLowerCase(),
disabled: !account || rowData.addr.toLowerCase() != account.toLowerCase(),
tooltip: 'Cancel',
onClick: (event, rowData) => {
cancelProject(rowData.idProject || rowData.idProfile)
.send()
.then(res => {
.then(async res => {
console.log({res})
cancelFundProfile(rowData.idProfile)
const profile = profiles.find(p => p.idProfile == rowData.idProfile)
await profile.markAsCanceled()
})
}
})
@ -50,4 +53,6 @@ const FunderProfilesTable = ({ data, cancelFundProfile }) => (
</FundingContext.Consumer>
)
export default memo(FunderProfilesTable)
export default withDatabase(withObservables([], ({ database }) => ({
profiles: database.collections.get('profiles').query().observeWithColumns(['canceled']),
}))(FunderProfilesTable))

View File

@ -7,13 +7,15 @@ import AddFunder from './AddFunder'
import CreateFunding from './CreateFunding'
const FundsManagement = ({ open }) => {
const maxWidth = open ? `${window.visualViewport.width - 35}px` : '100vw'
const windowWidth = window.visualViewport.width
const maxWidth = open ? `${windowWidth * 0.80}px` : '100vw'
const WebkitTransition = 'all 0.25s ease-out 0s'
return (
<FundingContext.Consumer>
{({ allPledges, appendPledges, appendFundProfile, transferPledgeAmounts, fundProfiles, cancelFundProfile }) =>
<div style={{ maxWidth }}>
<PledgesTable data={allPledges} transferPledgeAmounts={transferPledgeAmounts} fundProfiles={fundProfiles} />
<FunderProfilesTable data={fundProfiles} cancelFundProfile={cancelFundProfile}/>
{({ appendPledges, appendFundProfile }) =>
<div style={{ maxWidth, WebkitTransition }}>
<PledgesTable />
<FunderProfilesTable />
<AddFunder appendFundProfile={appendFundProfile} />
<Divider variant="middle" />
<CreateFunding refreshTable={appendPledges} />

View File

@ -47,9 +47,6 @@ const styles = theme => ({
appBarBg: {
backgroundColor: '#111735'
},
childrenShift: {
width: `calc(100% - ${drawerWidth}px)`
},
menuButton: {
marginLeft: 12,
marginRight: 20,
@ -195,9 +192,7 @@ class PersistentDrawerLeft extends React.Component {
})}
>
<div className={classes.drawerHeader} />
<div className={classNames(classes.appBar, {
[classes.childrenShift]: open,
})}>
<div className={classNames(classes.appBar)}>
<Switch>
<Route path="/(|dashboard)" component={Dashboard} />
<Route path="/admin" component={ContractAdmin} />

View File

@ -1,5 +1,7 @@
import React, { Fragment, PureComponent } from 'react'
import React, { Fragment, Component } from 'react'
import MaterialTable from 'material-table'
import withObservables from '@nozbe/with-observables'
import { withDatabase } from '@nozbe/watermelondb/DatabaseProvider'
import { toEther } from '../utils/conversions'
import { getTokenLabel } from '../utils/currencies'
import TransferDialog from './TransferDialog'
@ -12,50 +14,75 @@ const pledgeStateMap = {
1: 'Paying',
2: 'Paid'
}
const convertToDatetime = (field, fundProfiles) => {
const { commitTime, owner } = field
const profile = fundProfiles[Number(owner) - 1]
const convertToDatetime = async field => {
const { commitTime } = field
const profile = await field.profile.fetch()
if (!profile || Number(commitTime) === 0) return 0
const time = Number(commitTime) + Number(profile.commitTime)
const date = new Date(time * 1000)
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
}
const formatField = (field, fundProfiles) => ({
...field,
commitTime: convertToDatetime(field, fundProfiles),
const formatField = async field => ({
...field.getFields(),
commitTime: await convertToDatetime(field),
amount: toEther(field.amount),
token: getTokenLabel(field.token),
intendedProject: projectText(field.intendedProject),
pledgeState: pledgeStateMap[field.pledgeState]
pledgeState: pledgeStateMap[field.pledgeState],
transferTo: field.transferTo,
pledge: field
})
class PledgesTable extends PureComponent {
class PledgesTable extends Component {
state = {
data: [],
row: false,
}
componentDidMount() {
this.setData()
}
componentDidUpdate() {
const { pledges } = this.props
const { data } = this.state
if (data.length) {
pledges.some((pledge, idx) => {
const current = data[idx]
if (current) {
if (toEther(pledge.amount) != current.amount || pledgeStateMap[pledge.pledgeState] != current.pledgeState) this.setData()
}
})
}
if (pledges.length && !data.length) this.setData()
}
setData = async () => {
const { pledges } = this.props
const data = await Promise.all(pledges.map(formatField))
this.setState({ data })
}
handleClickOpen = row => {
this.setState({ row });
this.setState({ row })
}
handleClose = () => {
this.setState({ row: false });
this.setState({ row: false })
}
clearRowData = () => this.setState({ rowData: null })
render() {
const { data, transferPledgeAmounts, fundProfiles } = this.props
const { row, rowData } = this.state
const { data, row, rowData } = this.state
return (
<Fragment>
<TransferDialog
row={row}
handleClose={this.handleClose}
transferPledgeAmounts={transferPledgeAmounts}
/>
<MaterialTable
columns={[
{ title: 'Pledge Id', field: 'id', type: 'numeric' },
{ title: 'Pledge Id', field: 'pledgeId', type: 'numeric' },
{ title: 'Owner', field: 'owner' },
{ title: 'Amount Funded', field: 'amount', type: 'numeric' },
{ title: 'Token', field: 'token' },
@ -64,7 +91,7 @@ class PledgesTable extends PureComponent {
{ title: 'Intended Project', field: 'intendedProject' },
{ title: 'Pledge State', field: 'pledgeState' },
]}
data={data.map((f) => formatField(f, fundProfiles))}
data={data}
title="Pledges"
options={{ showEmptyDataSourceMessage: true }}
actions={[
@ -91,4 +118,6 @@ class PledgesTable extends PureComponent {
}
}
export default PledgesTable
export default withDatabase(withObservables([], ({ database }) => ({
pledges: database.collections.get('pledges').query().observeWithColumns(['amount', 'pledge_state']),
}))(PledgesTable))

View File

@ -10,34 +10,42 @@ import DialogContentText from '@material-ui/core/DialogContentText'
import DialogTitle from '@material-ui/core/DialogTitle'
import { getTokenLabel } from '../utils/currencies'
import { toWei } from '../utils/conversions'
import { FundingContext } from '../context'
const { transfer } = LiquidPledging.methods
const TransferDialog = ({ row, handleClose, transferPledgeAmounts }) => (
const TransferDialog = ({ row, handleClose }) => (
<Formik
initialValues={{}}
onSubmit={async (values, { setSubmitting, resetForm, setStatus }) => {
const { id } = row
const { idSender, amount, idReceiver } = values
const args = [idSender, id, toWei(amount.toString()), idReceiver]
const toSend = transfer(...args);
const estimatedGas = await toSend.estimateGas();
onSubmit={async (values, { setSubmitting, resetForm, setStatus }) => {
const { pledgeId, pledge } = row
const { idSender, amount, idReceiver } = values
const args = [idSender, pledgeId, toWei(amount.toString()), idReceiver]
const toSend = transfer(...args)
const estimatedGas = await toSend.estimateGas()
toSend.send({gas: estimatedGas + 1000})
.then(res => {
console.log({res})
const { events: { Transfer: { returnValues } } } = res
transferPledgeAmounts(returnValues)
})
.catch(e => {
console.log({e})
})
.finally(() => {
handleClose()
resetForm()
})
}}
toSend
.send({gas: estimatedGas + 1000})
.then(async res => {
console.log({res})
const { events: { Transfer } } = res
if (Array.isArray(Transfer)) {
Transfer.forEach(async t => {
const { to, amount } = t.returnValues
await pledge.transferTo(to, amount)
})
} else {
const { to, amount } = Transfer.returnValues
await pledge.transferTo(to, amount)
}
})
.catch(e => {
console.log({e})
})
.finally(() => {
handleClose()
resetForm()
})
}}
>
{({
values,
@ -60,7 +68,7 @@ const TransferDialog = ({ row, handleClose, transferPledgeAmounts }) => (
<DialogTitle id="form-dialog-title">Transfer Funds</DialogTitle>
<DialogContent>
<DialogContentText>
{`Transfer ${values.amount || ''} ${values.amount ? getTokenLabel(row[6]) : ''} from Pledge ${row.id} ${values.idReceiver ? 'to Giver/Delegate/Project' : ''} ${values.idReceiver || ''}`}
{`Transfer ${values.amount || ''} ${values.amount ? getTokenLabel(row[6]) : ''} from Pledge ${row.pledgeId} ${values.idReceiver ? 'to Giver/Delegate/Project' : ''} ${values.idReceiver || ''}`}
</DialogContentText>
<TextField
autoFocus

View File

@ -1,11 +1,14 @@
import Cytoscape from 'cytoscape'
import dagre from 'cytoscape-dagre'
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import CytoscapeComponent from 'react-cytoscapejs'
import withObservables from '@nozbe/with-observables'
import { Q } from '@nozbe/watermelondb'
import { withDatabase } from '@nozbe/watermelondb/DatabaseProvider'
import { uniq, isNil } from 'ramda'
import { toEther } from '../utils/conversions'
import { getTokenLabel } from '../utils/currencies'
import { FundingContext } from '../context'
import { getAuthorizations } from '../selectors/vault'
@ -71,21 +74,27 @@ const createElements = (transfers, vaultEvents) => {
]
}
const TransfersGraph = () => {
const TransfersGraph = ({ transfers, vaultEvents }) => {
return (
<FundingContext.Consumer>
{({ transfers, vaultEvents }) =>
<Fragment>
<CytoscapeComponent
elements={createElements(transfers, vaultEvents)}
style={ { width: '800px', height: '100%', fontSize: '14px' } }
stylesheet={stylesheet}
layout={layout}
/>
</Fragment>
}
</FundingContext.Consumer>
<Fragment>
<CytoscapeComponent
elements={createElements(transfers, vaultEvents)}
style={ { width: '100vw', height: '100%', fontSize: '14px' } }
stylesheet={stylesheet}
layout={layout}
/>
</Fragment>
)
}
export default TransfersGraph
TransfersGraph.propTypes = {
transfers: PropTypes.array.isRequired,
vaultEvents: PropTypes.array.isRequired
}
export default withDatabase(withObservables([], ({ database }) => ({
transfers: database.collections.get('lp_events').query(
Q.where('event', 'Transfer')
).observe(),
vaultEvents : database.collections.get('vault_events').query().observe()
}))(TransfersGraph))

View File

@ -1,11 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
import withObservables from '@nozbe/with-observables'
import { Q } from '@nozbe/watermelondb'
import { withDatabase } from '@nozbe/watermelondb/DatabaseProvider'
import { withStyles } from '@material-ui/core/styles'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import Typography from '@material-ui/core/Typography'
import LinearProgress from '@material-ui/core/LinearProgress'
import { FundingContext } from '../../context'
import { getDepositWithdrawTotals, getPledgesWaitingCommit } from '../../selectors/pledging'
import { getTokenAddress } from '../../utils/currencies'
@ -47,77 +49,83 @@ const styles = {
const getNet = (deposits, withdraws) => Number(deposits) - Number(withdraws)
const getValue = (deposits, withdraws) => (getNet(deposits, withdraws) / Number(deposits)) * 100
function SimpleCard(props) {
const { classes, title } = props
const { classes, title, transfers, pledges, vaultEvents } = props
return (
<FundingContext.Consumer>
{({ allPledges, allLpEvents, vaultEvents }) =>
<Card className={classes.card}>
<CardContent>
<Typography variant="h5" className={classes.cardTitle}>
{title}
</Typography>
{!!allLpEvents &&
Object.entries(getDepositWithdrawTotals({ allLpEvents, allPledges, vaultEvents }))
.map(token => {
const [name, amounts] = token
const { deposits, withdraws } = amounts
const address = getTokenAddress(name)
const pledgesForCommit = getPledgesWaitingCommit({ allPledges }).filter(p => p.token == address)
return (
<Card key={name}>
<Typography variant="h5" className={classes.titleText}>
{name}
</Typography>
<CardContent className={classes.fundingSummaries}>
<Typography variant="h3">
{Number(deposits) - Number(withdraws || 0)}
</Typography>
<Typography variant="h6" key={name + 'total'} className={classes.pos} color="textSecondary">
Remaining In Pledges
</Typography>
<Typography variant="h3" >
{deposits}
</Typography>
<Typography variant="h6" key={name + 'withdraw'} className={classes.pos} color="textSecondary">
Funded
</Typography>
<Typography variant="h3">
{withdraws || 0}
</Typography>
<Typography variant="h6" key={name + 'deposit'} className={classes.pos} color="textSecondary">
Withdrawn
</Typography>
<Typography variant="h3">
{pledgesForCommit.length}
</Typography>
<Typography variant="h6" key={name + 'deposit'} className={classes.pos} color="textSecondary">
Pledges that can be vetoed / approved
</Typography>
</CardContent>
<LinearProgress
classes={{
colorPrimary: classes.linearColorPrimary,
barColorPrimary: classes.linearBarColorPrimary,
}}
color="primary"
variant="buffer"
value={getValue(deposits, withdraws)}
valueBuffer={100}
/>
</Card>
)
})}
</CardContent>
</Card>
}
</FundingContext.Consumer>
<Card className={classes.card}>
<CardContent>
<Typography variant="h5" className={classes.cardTitle}>
{title}
</Typography>
{!!transfers && !!pledges.length &&
Object.entries(getDepositWithdrawTotals({ transfers, pledges, vaultEvents }))
.map(token => {
const [name, amounts] = token
const { deposits, withdraws } = amounts
const address = getTokenAddress(name)
const pledgesForCommit = getPledgesWaitingCommit({ pledges }).filter(p => p.token == address)
return (
<Card key={name}>
<Typography variant="h5" className={classes.titleText}>
{name}
</Typography>
<CardContent className={classes.fundingSummaries}>
<Typography variant="h3">
{Number(deposits) - Number(withdraws || 0)}
</Typography>
<Typography variant="h6" key={name + 'total'} className={classes.pos} color="textSecondary">
Remaining In Pledges
</Typography>
<Typography variant="h3" >
{deposits}
</Typography>
<Typography variant="h6" key={name + 'withdraw'} className={classes.pos} color="textSecondary">
Funded
</Typography>
<Typography variant="h3">
{withdraws || 0}
</Typography>
<Typography variant="h6" key={name + 'deposit'} className={classes.pos} color="textSecondary">
Withdrawn
</Typography>
<Typography variant="h3">
{pledgesForCommit.length}
</Typography>
<Typography variant="h6" key={name + 'veto/approve'} className={classes.pos} color="textSecondary">
Pledges that can be vetoed / approved
</Typography>
</CardContent>
<LinearProgress
classes={{
colorPrimary: classes.linearColorPrimary,
barColorPrimary: classes.linearBarColorPrimary,
}}
color="primary"
variant="buffer"
value={getValue(deposits, withdraws)}
valueBuffer={100}
/>
</Card>
)
})}
</CardContent>
</Card>
)
}
SimpleCard.propTypes = {
classes: PropTypes.object.isRequired,
title: PropTypes.string
title: PropTypes.string,
pledges: PropTypes.array.isRequired,
transfers: PropTypes.array.isRequired,
vaultEvents: PropTypes.array.isRequired
}
export default withStyles(styles)(SimpleCard)
const styledCard = withStyles(styles)(SimpleCard)
export default withDatabase(withObservables([], ({ database }) => ({
transfers: database.collections.get('lp_events').query(
Q.where('event', 'Transfer')
).observe(),
vaultEvents : database.collections.get('vault_events').query().observe()
}))(styledCard))

View File

@ -5,7 +5,6 @@ import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import Typography from '@material-ui/core/Typography'
import { Doughnut } from 'react-chartjs-2'
import { FundingContext } from '../../context'
import { toEther } from '../../utils/conversions'
import { getTokenLabel } from '../../utils/currencies'
import { getColor } from '../../utils/colorSchemes'
@ -32,11 +31,11 @@ const pledgesChartData = pledges => {
const labels = []
const backgroundColor = []
pledges.forEach((pledge, idx) => {
const { id, amount, token } = pledge
const { pledgeId, amount, token } = pledge
const converted = toEther(amount)
data.push(converted)
labels.push(
`pledge ${id} - ${getTokenLabel(token)}`
`pledge ${pledgeId} - ${getTokenLabel(token)}`
)
backgroundColor.push(getColor('Dark2-8', idx))
})
@ -53,30 +52,27 @@ const pledgesChartData = pledges => {
}
function SimpleCard(props) {
const { classes, title } = props
const { classes, title, pledges } = props
return (
<FundingContext.Consumer>
{({ allPledges }) =>
<Card className={classes.card}>
<CardContent>
<Typography variant="h5" component="h2">
{title}
</Typography>
<Typography className={classes.pos} color="textSecondary">
How your funds are distributed among pledges
</Typography>
<Doughnut data={pledgesChartData(allPledges)} />
</CardContent>
</Card>
}
</FundingContext.Consumer>
<Card className={classes.card}>
<CardContent>
<Typography variant="h5" component="h2">
{title}
</Typography>
<Typography className={classes.pos} color="textSecondary">
How your funds are distributed among pledges
</Typography>
<Doughnut data={pledgesChartData(pledges)} />
</CardContent>
</Card>
)
}
SimpleCard.propTypes = {
classes: PropTypes.object.isRequired,
title: PropTypes.string
title: PropTypes.string,
pledges: PropTypes.array.isRequired
}
export default withStyles(styles)(SimpleCard)

View File

@ -65,7 +65,7 @@ class Withdraw extends PureComponent {
initialValues={{}}
onSubmit={async (values, { setSubmitting, resetForm, setStatus }) => {
const { amount } = values
const paymentId = isPaying ? authorizedPayments.find(r => r.ref === rowData.id)['idPayment'] : rowData.id
const paymentId = isPaying ? authorizedPayments.find(r => r.ref === rowData.id)['idPayment'] : rowData.pledgeId
const args = isPaying ? [paymentId] : [paymentId, toWei(amount)]
const sendFn = isPaying ? confirmPayment : withdraw
try {
@ -102,7 +102,7 @@ class Withdraw extends PureComponent {
<Card className={classes.card} elevation={0}>
<CardContent>
<Typography variant="h6" component="h2">
{`${isPaying ? 'Confirm' : ''} Withdraw${isPaying ? 'al' : ''} ${values.amount || ''} ${values.amount ? getTokenLabel(rowData[6]) : ''} from Pledge ${rowData.id}`}
{`${isPaying ? 'Confirm' : ''} Withdraw${isPaying ? 'al' : ''} ${values.amount || ''} ${values.amount ? getTokenLabel(rowData[6]) : ''} from Pledge ${rowData.pledgeId}`}
</Typography>
{!isPaying && <TextField
className={classes.amount}

View File

@ -1,16 +1,16 @@
import React from 'react'
import { HashRouter as Router, Route, Link, Switch } from 'react-router-dom'
import EmbarkJS from 'Embark/EmbarkJS';
import LPVault from 'Embark/contracts/LPVault';
import { HashRouter as Router } from 'react-router-dom'
import EmbarkJS from 'Embark/EmbarkJS'
import LiquidPledging from 'Embark/contracts/LiquidPledging'
import web3 from 'Embark/web3'
import { initVaultAndLP, vaultPledgingNeedsInit, standardTokenApproval, getLpAllowance } from './utils/initialize'
import { getAllLPEvents, getAllVaultEvents, getProfileEvents, formatFundProfileEvent, getAuthorizedPayments } from './utils/events'
import { getAllPledges, appendToExistingPledges, transferBetweenPledges } from './utils/pledges'
import { getAuthorizedPayments } from './utils/events'
import { FundingContext } from './context'
import { cancelProfile } from './utils/fundProfiles'
import MainCointainer from './components/MainCointainer'
import { getTransfersMemo } from './selectors/pledging'
import { getAndAddLpEvents } from './actions/lpEvents'
import { getAndAddVaultEvents } from './actions/vaultEvents'
import { addFormattedProfiles } from './actions/profiles'
import { getAndAddPledges } from './actions/pledges'
const { getNetworkType } = web3.eth.net
@ -18,12 +18,7 @@ class App extends React.Component {
state = {
loading: true,
lpAllowance: 0,
fundProfiles: [],
allPledges: [],
needsInit: true,
transfers: [],
allLpEvents: [],
vaultEvents: []
};
componentDidMount(){
@ -33,61 +28,38 @@ class App extends React.Component {
const isInitialized = await vaultPledgingNeedsInit()
if (!!isInitialized) {
if (environment === 'development') console.log('mock_time:', await LiquidPledging.mock_time.call())
const lpAllowance = await getLpAllowance()
const fundProfiles = await getProfileEvents()
const allPledges = await getAllPledges()
//TODO add block based sync
const authorizedPayments = await getAuthorizedPayments()
const account = await web3.eth.getCoinbase()
const allLpEvents = await getAllLPEvents()
const vaultEvents = await getAllVaultEvents()
const transfers = getTransfersMemo({ allLpEvents })
this.syncWithRemote()
this.setState({
account,
network,
environment,
lpAllowance,
fundProfiles,
allPledges,
authorizedPayments,
allLpEvents,
vaultEvents,
transfers,
needsInit: false,
loading: false
needsInit: false
})
}
})
})
}
appendFundProfile = async event => {
const formattedEvent = await formatFundProfileEvent(event)
this.setState((state) => {
const { fundProfiles } = state
return {
...state,
fundProfiles: [ ...fundProfiles, formattedEvent ]
}
})
}
appendPledges = () => {
const { allPledges } = this.state
appendToExistingPledges(allPledges, this.setState)
}
transferPledgeAmounts = tx => {
transferBetweenPledges(this.setState.bind(this), tx)
}
cancelFundProfile = id => {
this.setState((state) => cancelProfile(state, id))
async syncWithRemote() {
// not running in parallel due to possible metamask / infura limitation
await getAndAddLpEvents()
await getAndAddVaultEvents()
await getAndAddPledges()
await addFormattedProfiles()
this.setState({ loading: false })
}
render() {
const { account, needsInit, lpAllowance, loading, fundProfiles, allPledges, allLpEvents, authorizedPayments, transfers, vaultEvents } = this.state
const { appendFundProfile, appendPledges, transferPledgeAmounts, cancelFundProfile } = this
const fundingContext = { allPledges, allLpEvents, appendPledges, appendFundProfile, account, transferPledgeAmounts, authorizedPayments, cancelFundProfile, fundProfiles, needsInit, initVaultAndLP, standardTokenApproval, transfers, vaultEvents }
const { account, needsInit, lpAllowance, loading, authorizedPayments } = this.state
const { appendFundProfile, appendPledges, transferPledgeAmounts } = this
const fundingContext = { appendPledges, appendFundProfile, account, transferPledgeAmounts, authorizedPayments, needsInit, initVaultAndLP, standardTokenApproval }
return (
<FundingContext.Provider value={fundingContext}>
<Router>

27
app/db.js Normal file
View File

@ -0,0 +1,27 @@
import { Database } from '@nozbe/watermelondb'
import LokiJSAdapter from '@nozbe/watermelondb/adapters/lokijs'
import schema from './model/schema'
import LpEvent from './model/lpEvents'
import VaultEvent from './model/vaultEvent'
import Profile from './model/profile'
import Pledge from './model/pledge'
const dbName = 'LiquidFunding'
const adapter = new LokiJSAdapter({
dbName,
schema,
})
const database = new Database({
adapter,
modelClasses: [
LpEvent,
VaultEvent,
Profile,
Pledge
],
actionsEnabled: true,
})
export default database

View File

@ -1,8 +1,11 @@
import React from 'react';
import { render } from 'react-dom';
import DatabaseProvider from '@nozbe/watermelondb/DatabaseProvider';
import database from './db';
import App from './dapp';
render(
<App />,
document.getElementById('app')
<DatabaseProvider database={database}>
<App />
</DatabaseProvider>, document.getElementById('app')
);

29
app/model/lpEvents.js Normal file
View File

@ -0,0 +1,29 @@
import { Model } from '@nozbe/watermelondb'
import { action, field, json } from '@nozbe/watermelondb/decorators'
const sanitizeValues = json => json
export default class LpEvent extends Model {
static table = 'lp_events'
@field('address') address
@field('event_id') eventId
@field('event') event
@field('block_number') blockNumber
@json('return_values', sanitizeValues) returnValues
@action async addEvent(data) {
return await this.create(lpEvent => {
const { event, address, id, blockNumber } = data
lpEvent.eventId = id
lpEvent.address = address
lpEvent.event = event
lpEvent.blockNumber = blockNumber
})
}
}

36
app/model/pledge.js Normal file
View File

@ -0,0 +1,36 @@
import { action, field, relation } from '@nozbe/watermelondb/decorators'
import { Q } from '@nozbe/watermelondb'
import { LiquidModel } from '../utils/models'
export default class Pledge extends LiquidModel {
static table = 'pledges'
static associations = {
profiles: { type: 'belongs_to', key: 'profile_id' },
}
@field('pledge_id') pledgeId
@field('owner_id') owner
@field('amount') amount
@field('token') token
@field('commit_time') commitTime
@field('n_delegates') nDelegates
@field('intended_project') intendedProject
@field('pledge_state') pledgeState
@relation('profiles', 'profile_id') profile
@action async transferTo(to, amount) {
const toPledgeQuery = await this.collections.get('pledges').query(
Q.where('pledge_id', to)
).fetch()
const toPledge = toPledgeQuery[0]
await this.batch(
this.prepareUpdate(pledge => {
pledge.amount = (BigInt(pledge.amount) - BigInt(amount)).toString()
}),
toPledge.prepareUpdate(pledge => {
pledge.amount = (BigInt(pledge.amount) + BigInt(amount)).toString()
})
)
}
}

26
app/model/profile.js Normal file
View File

@ -0,0 +1,26 @@
import { action, field, children } from '@nozbe/watermelondb/decorators'
import { LiquidModel } from '../utils/models'
export default class Profile extends LiquidModel {
static table = 'profiles'
static associations = {
pledges: { type: 'has_many', foreignKey: 'profile_id' }
}
@field('addr') addr
@field('event_id') eventId
@field('canceled') canceled
@field('commit_time') commitTime
@field('type') type
@field('name') name
@field('url') url
@field('id_profile') idProfile
@children('pledges') pledges
@action async markAsCanceled() {
await this.update(profile => {
profile.canceled = true
})
}
}

54
app/model/schema.js Normal file
View File

@ -0,0 +1,54 @@
import { appSchema, tableSchema } from '@nozbe/watermelondb'
export default appSchema({
version: 1,
tables: [
tableSchema({
name: 'lp_events',
columns: [
{ name: 'event_id', type: 'string', isIndexed: true },
{ name: 'address', type: 'string' },
{ name: 'event', type: 'string', isIndexed: true },
{ name: 'block_number', type: 'number', isIndexed: true },
{ name : 'return_values', type: 'string', isOptional: true }
]
}),
tableSchema({
name: 'vault_events',
columns: [
{ name: 'event_id', type: 'string', isIndexed: true },
{ name: 'address', type: 'string' },
{ name: 'event', type: 'string', isIndexed: true },
{ name: 'block_number', type: 'number', isIndexed: true },
{ name : 'return_values', type: 'string', isOptional: true }
]
}),
tableSchema({
name: 'profiles',
columns: [
{ name: 'event_id', type: 'string' },
{ name: 'addr', type: 'string' },
{ name: 'canceled', type: 'boolean' },
{ name: 'commit_time', type: 'number' },
{ name: 'type', type: 'string' },
{ name: 'name', type: 'string' },
{ name: 'url', type: 'string' },
{ name: 'id_profile', type: 'number', isIndexed: true }
]
}),
tableSchema({
name: 'pledges',
columns: [
{ name: 'pledge_id', type: 'number', isIndexed: true },
{ name: 'owner_id', type: 'number', isIndexed: true },
{ name: 'amount', type: 'string' },
{ name: 'token', type: 'string' },
{ name: 'commit_time', type: 'number' },
{ name: 'n_delegates', type: 'number' },
{ name: 'intended_project', type: 'number' },
{ name: 'pledge_state', type: 'number' },
{ name: 'profile_id', type: 'string', isIndexed: true }
]
})
]
})

28
app/model/vaultEvent.js Normal file
View File

@ -0,0 +1,28 @@
import { Model } from '@nozbe/watermelondb'
import { action, field, json } from '@nozbe/watermelondb/decorators'
const sanitizeValues = json => json
export default class VaultEvent extends Model {
static table = 'vault_events'
@field('address') address
@field('event_id') eventId
@field('event') event
@field('block_number') blockNumber
@json('return_values', sanitizeValues) returnValues
@action async addEvent(data) {
return await this.create(lpEvent => {
const { event, address, id, blockNumber } = data
lpEvent.eventId = id
lpEvent.address = address
lpEvent.event = event
lpEvent.blockNumber = blockNumber
})
}
}

View File

@ -9,6 +9,7 @@ const getWithdraws = state => state.vaultEvents.filter(
event => event.event === 'AuthorizePayment'
)
export const getPledges = state => state.allPledges
const pluckPledges = state => state.pledges
export const getTransfersMemo = createSelector(
getTransfers,
@ -20,7 +21,7 @@ export const getDeposits = transfers => transfers.filter(
)
const getDepositsSelector = createSelector(
getTransfersMemo,
({ transfers }) => transfers,
getDeposits
)
@ -35,20 +36,28 @@ const pledgesWaitingCommit = pledges => {
}
export const getPledgesWaitingCommit = createSelector(
getPledges,
pluckPledges,
pledgesWaitingCommit
)
const formatAndSumDepositWithdraws = (deposits, pledges, withdraws) => {
const tokens = {}
let incomplete = false
deposits.forEach(deposit => {
const { amount, to } = deposit.returnValues
const { token } = pledges.find(p => Number(p.id) === Number(to))
const pledge = pledges.find(p => Number(p.pledgeId) === Number(to))
if (!pledge) {
incomplete = true
return
}
const { token } = pledge
const tokenName = getTokenLabel(token)
if (tokens[tokenName]) tokens[tokenName]['deposits'] = BigInt(tokens[tokenName]['deposits']) + BigInt(amount)
else tokens[tokenName] = { 'deposits': BigInt(amount) }
})
if (incomplete) return {}
withdraws
.filter(w => !isNaN(Number(w.returnValues.ref.slice(2))))
.forEach(withdraw => {
@ -70,7 +79,7 @@ const formatAndSumDepositWithdraws = (deposits, pledges, withdraws) => {
}
export const getDepositWithdrawTotals = createSelector(
getDepositsSelector,
getPledges,
pluckPledges,
getWithdraws,
formatAndSumDepositWithdraws
)

12
app/utils/db.js Normal file
View File

@ -0,0 +1,12 @@
export const fieldGenerator = self => (column, name) => {
Object.defineProperty(self, name || column, {
get() { return self._getRaw(column) },
set(value) { self._setRaw(column, value) },
enumerable: true,
configurable: true
})
}
export function initialize(target, name, descriptor) {
descriptor.initializer = true
}

View File

@ -1,11 +1,13 @@
import LiquidPledging from 'Embark/contracts/LiquidPledging'
import LPVault from 'Embark/contracts/LPVault'
import web3 from 'Embark/web3'
import { getLastBlockStored } from '../actions/lpEvents'
const AUTHORIZE_PAYMENT = 'AuthorizePayment'
const GIVER_ADDED = 'GiverAdded'
const DELEGATE_ADDED = 'DelegateAdded'
const PROJECT_ADDED = 'ProjectAdded'
export const GIVER_ADDED = 'GiverAdded'
export const DELEGATE_ADDED = 'DelegateAdded'
export const PROJECT_ADDED = 'ProjectAdded'
const ALL_EVENTS = 'allEvents'
const lookups = {
[GIVER_ADDED]: {
@ -31,10 +33,10 @@ const formatVaultEvent = async event => {
}
}
const getPastVaultEvents = async (event, raw = false) => {
const getPastVaultEvents = async (event, raw = false, fromBlock = 0) => {
const events = await LPVault.getPastEvents(event, {
addr: await web3.eth.getCoinbase(),
fromBlock: 0,
fromBlock,
toBlock: 'latest'
})
if (raw) return events
@ -47,12 +49,12 @@ const getPastVaultEvents = async (event, raw = false) => {
const { getPledgeAdmin } = LiquidPledging.methods
export const formatFundProfileEvent = async event => {
const lookup = lookups[event.event]
const { returnValues: { url, idProject } } = event
const { id, returnValues: { url } } = event
const idProfile = event.returnValues[lookup.id]
const { addr, commitTime, name, canceled } = await getPledgeAdmin(idProfile).call()
return {
id,
idProfile,
idProject,
url,
commitTime,
name,
@ -62,10 +64,10 @@ export const formatFundProfileEvent = async event => {
}
}
const getPastEvents = async (event, raw = false) => {
const getPastEvents = async (event, raw = false, fromBlock = 0) => {
const events = await LiquidPledging.getPastEvents(event, {
addr: await web3.eth.getCoinbase(),
fromBlock: 0,
fromBlock,
toBlock: 'latest'
})
if (raw) return events
@ -86,9 +88,13 @@ export const lpEventsSubscription = async () => {
export const getFunderProfiles = async () => await getPastEvents(GIVER_ADDED)
export const getDelegateProfiles = async () => await getPastEvents(DELEGATE_ADDED)
export const getProjectProfiles = async () => await getPastEvents(PROJECT_ADDED)
export const getAllLPEvents = async () => await getPastEvents(ALL_EVENTS, true)
export const getAllLPEvents = async fromBlock => await getPastEvents(
ALL_EVENTS,
true,
fromBlock
)
export const getAuthorizedPayments = async () => getPastVaultEvents(AUTHORIZE_PAYMENT)
export const getAllVaultEvents = async () => getPastVaultEvents(ALL_EVENTS,true)
export const getAllVaultEvents = async (fromBlock = 0) => getPastVaultEvents(ALL_EVENTS,true, fromBlock)
export const getProfileEvents = async () => {
const [ funderProfiles, delegateProfiles, projectProfiles]
= await Promise.all([getFunderProfiles(), getDelegateProfiles(), getProjectProfiles()])

25
app/utils/models.js Normal file
View File

@ -0,0 +1,25 @@
import { Model } from '@nozbe/watermelondb'
export function getFields(obj) {
const validTypes = new Set(['string', 'number', 'boolean'])
const newObj = {}
const proto = Object.getPrototypeOf(obj)
const names = Object.getOwnPropertyNames(proto)
names
.filter(name => validTypes.has(typeof obj[name]))
.forEach(name => { newObj[name] = obj[name] })
return newObj
}
export class LiquidModel extends Model {
getFields() {
const validTypes = new Set(['string', 'number', 'boolean'])
const newObj = {}
const proto = Object.getPrototypeOf(this)
const names = Object.getOwnPropertyNames(proto)
names
.filter(name => validTypes.has(typeof this[name]))
.forEach(name => { newObj[name] = this[name] })
return newObj
}
}

29
babel-loader-overrides.js Normal file
View File

@ -0,0 +1,29 @@
/**
* This source code was adapted from:
* https://github.com/facebook/create-react-app/blob/v2.0.4/packages/babel-preset-react-app/webpack-overrides.js
*
* Copyright (c) 2015-present, Facebook, Inc.
*
* The MIT license for this code may be found on GitHub:
* https://github.com/facebook/create-react-app/blob/v2.0.4/packages/babel-preset-react-app/LICENSE
*/
const crypto = require('crypto');
const macroCheck = new RegExp('[./]macro');
module.exports = function () {
return {
config(config, {source}) {
// don't cache babel macros
// https://github.com/babel/babel/issues/8497
if (macroCheck.test(source)) {
return Object.assign({}, config.options, {
caller: Object.assign({}, config.options.caller, {
macroInvalidationToken: crypto.randomBytes(32).toString('hex')
})
});
}
return config.options;
}
};
};

View File

@ -49,13 +49,16 @@
"mocha": "^3.5.0",
"random-bytes": "^1.0.0",
"solcpiler": "1.0.0-beta.8",
"web3": "1.0.0-beta.34"
"web3": "1.0.0-beta.34",
"worker-loader": "^2.0.0"
},
"homepage": "https://github.com/Giveth/liquidpledging#readme",
"dependencies": {
"@aragon/os": "3.1.9",
"@material-ui/core": "^3.6.0",
"@material-ui/icons": "^3.0.1",
"@nozbe/watermelondb": "^0.9.0",
"@nozbe/with-observables": "^1.0.2",
"async": "^2.4.0",
"chai": "^4.1.0",
"chart.js": "^2.7.3",
@ -66,6 +69,7 @@
"eslint": "^5.9.0",
"eth-contract-class": "^0.0.12",
"formik": "^1.3.2",
"lokijs": "^1.5.6",
"material-table": "^1.12.0",
"ramda": "^0.26.1",
"react": "^16.7.0",

289
webpack.config.js Normal file
View File

@ -0,0 +1,289 @@
/* global __dirname module process require */
const path = require('path');
const dappPath = process.env.DAPP_PATH;
const embarkPath = process.env.EMBARK_PATH;
const dappNodeModules = path.join(dappPath, 'node_modules');
const embarkNodeModules = path.join(embarkPath, 'node_modules');
let nodePathNodeModules;
if (process.env.NODE_PATH) {
nodePathNodeModules = process.env.NODE_PATH.split(path.delimiter);
} else {
nodePathNodeModules = [];
}
if (!nodePathNodeModules.includes(embarkNodeModules)) {
nodePathNodeModules.unshift(embarkNodeModules);
}
function requireFromEmbark(mod) {
return require(requireFromEmbark.resolve(mod));
}
requireFromEmbark.resolve = function (mod) {
return require.resolve(
mod,
{paths: [embarkNodeModules]}
);
};
const cloneDeep = requireFromEmbark('lodash.clonedeep');
const glob = requireFromEmbark('glob');
const HardSourceWebpackPlugin = requireFromEmbark('hard-source-webpack-plugin');
const embarkAliases = require(path.join(dappPath, '.embark/embark-aliases.json'));
const embarkAssets = require(path.join(dappPath, '.embark/embark-assets.json'));
const embarkJson = require(path.join(dappPath, 'embark.json'));
const embarkPipeline = require(path.join(dappPath, '.embark/embark-pipeline.json'));
const buildDir = path.join(dappPath, embarkJson.buildDir);
// it's important to `embark reset` if a pkg version is specified in
// embark.json and changed/removed later, otherwise pkg resolution may behave
// unexpectedly
let versions;
try {
versions = glob.sync(path.join(dappPath, '.embark/versions/*/*'));
} catch (e) {
versions = [];
}
const entry = Object.keys(embarkAssets)
.filter(key => key.match(/\.js$/))
.reduce((obj, key) => {
// webpack entry paths should start with './' if they're relative to the
// webpack context; embark.json "app" keys correspond to lists of .js
// source paths relative to the top-level dapp dir and may be missing the
// leading './'
obj[key] = embarkAssets[key]
.map(file => {
let file_path = file.path;
if (!file.path.match(/^\.\//)) {
file_path = './' + file_path;
}
return file_path;
});
return obj;
}, {});
function resolve(pkgName) {
if (Array.isArray(pkgName)) {
const _pkgName = pkgName[0];
pkgName[0] = requireFromEmbark.resolve(_pkgName);
return pkgName;
}
return requireFromEmbark.resolve(pkgName);
}
// base config
// -----------------------------------------------------------------------------
// order and options of babel plugins and presets adapted from babel-preset-react-app:
// see: https://github.com/facebook/create-react-app/tree/v2.0.4/packages/babel-preset-react-app
// + babel plugins run before babel presets.
// + babel plugin ordering is first to last.
// + babel preset ordering is reversed (last to first).
// see: https://babeljs.io/docs/en/plugins#plugin-ordering
const base = {
context: dappPath,
entry: entry,
module: {
rules: [
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=100000'
},
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components|\.embark[\\/]versions)/,
options: {
cacheDirectory: true,
cacheCompression: false,
customize: path.join(__dirname, 'babel-loader-overrides.js'),
plugins: [
[
'babel-plugin-module-resolver', {
alias: embarkAliases
}
],
'babel-plugin-macros',
'@babel/plugin-transform-destructuring',
[
'@babel/plugin-proposal-decorators', {
legacy: true
}
],
[
'@babel/plugin-proposal-class-properties', {
loose: true
}
],
[
'@babel/plugin-proposal-object-rest-spread', {
useBuiltIns: true
}
],
[
'@babel/plugin-transform-runtime', {
helpers: true,
regenerator: true
}
],
'@babel/plugin-syntax-dynamic-import'
].map(resolve),
presets: [
[
'@babel/preset-env', {
exclude: ['transform-typeof-symbol'],
modules: false,
targets: {
browsers: ['last 1 version', 'not dead', '> 0.2%']
}
}
],
[
'@babel/preset-react', {
useBuiltIns: true
}
]
].map(resolve)
}
},
{
test: /\.worker\.js$/,
use: { loader: 'worker-loader' }
}
]
},
output: {
filename: (chunkData) => chunkData.chunk.name,
// globalObject workaround for node-compatible UMD builds with webpack 4
// see: https://github.com/webpack/webpack/issues/6522#issuecomment-371120689
// see: https://github.com/webpack/webpack/issues/6522#issuecomment-418864518
globalObject: '(typeof self !== \'undefined\' ? self : this)',
libraryTarget: 'umd',
path: buildDir
},
plugins: [new HardSourceWebpackPlugin()],
// profiling and generating verbose stats increases build time; if stats
// are generated embark will write the output to:
// path.join(dappPath, '.embark/stats.[json,report]')
// to visualize the stats info in a browser run:
// npx webpack-bundle-analyzer .embark/stats.json <buildDir>
profile: true, stats: 'verbose',
resolve: {
alias: embarkAliases,
extensions: [
// webpack defaults
// see: https://webpack.js.org/configuration/resolve/#resolve-extensions
'.wasm', '.mjs', '.js', '.json',
// additional extensions
'.jsx'
],
modules: [
...versions,
'node_modules',
dappNodeModules,
...nodePathNodeModules
]
},
resolveLoader: {
modules: [
'node_modules',
dappNodeModules,
...nodePathNodeModules
]
}
};
const baseBabelLoader = base.module.rules[3];
// Flow
// -----------------------------------------------------------------------------
// should be false in configs that have isTypeScriptEnabled = true
// const isFlowEnabled = !embarkPipeline.typescript;
// if (isFlowEnabled) {
// // position @babel/plugin-transform-flow-strip-types per babel-preset-react-app
// baseBabelLoader.options.plugins.unshift(
// requireFromEmbark.resolve('@babel/plugin-transform-flow-strip-types')
// );
// }
// TypeScript
// -----------------------------------------------------------------------------
// should be false in configs that have isFlowEnabled = true
const isTypeScriptEnabled = !!embarkPipeline.typescript;
if (isTypeScriptEnabled) {
// position @babel/preset-typescript as the last preset (runs first)
// see: https://blogs.msdn.microsoft.com/typescript/2018/08/27/typescript-and-babel-7/
baseBabelLoader.options.presets.push(
requireFromEmbark.resolve('@babel/preset-typescript')
);
// additional extensions
baseBabelLoader.test = /\.(js|ts)x?$/;
base.resolve.extensions.push('.ts', '.tsx');
}
// if (isFlowEnabled && isTypeScriptEnabled) {
// throw new Error('isFlowEnabled and isTypeScriptEnabled cannot both be true');
// }
// development config
// -----------------------------------------------------------------------------
const development = cloneDeep(base);
// full source maps increase build time but are useful during dapp development
development.devtool = 'source-map';
development.mode = 'development';
// alternatively:
// development.mode = 'none';
development.name = 'development';
const devBabelLoader = development.module.rules[3];
devBabelLoader.options.compact = false;
// enable 'development' option for @babel/preset-react
const devPresetReact = devBabelLoader.options.presets[1];
const devPresetReactOptions = devPresetReact[1];
devPresetReactOptions.development = true;
// production config
// -----------------------------------------------------------------------------
const production = cloneDeep(base);
production.mode = 'production';
production.name = 'production';
const prodBabelLoader = production.module.rules[3];
// position babel-plugin-transform-react-remove-prop-types per babel-preset-react-app
prodBabelLoader.options.plugins.splice(prodBabelLoader.length - 1, 0, [
requireFromEmbark.resolve('babel-plugin-transform-react-remove-prop-types'),
{
removeImport: true
}
]);
// export a list of named configs
// -----------------------------------------------------------------------------
module.exports = [
development,
production
];

View File

@ -321,6 +321,23 @@
version "1.1.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
"@nozbe/watermelondb@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@nozbe/watermelondb/-/watermelondb-0.9.0.tgz#2864c11228a981dddf96d94c793082a90cd002f6"
integrity sha512-jAjmtATFPAD8gE0pqLVsCT5GVA/2H5poH+Rxy/pbukARrlty5nI38FUP7+yNYiSMkPp0tSCHDMiFfkMzRAjxzQ==
dependencies:
rambdax "^0.23.0"
rxjs "^6.2.2"
rxjs-compat "^6.3.2"
sql-escape-string "^1.1.0"
"@nozbe/with-observables@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@nozbe/with-observables/-/with-observables-1.0.2.tgz#9749b3b5d33a058f8aed92d75138a5778bead89b"
integrity sha512-p9WNGTUm0eKb28ylcMBayUgBbzGoZ2bVBxzqtS5SxRL3Hopwf1eSNF+fGbC8fX1YHxhQjma4nN4pkKAt+H6NLA==
dependencies:
rxjs "^6.2.2"
"@types/jss@^9.5.6":
version "9.5.7"
resolved "https://registry.yarnpkg.com/@types/jss/-/jss-9.5.7.tgz#fa57a6d0b38a3abef8a425e3eb6a53495cb9d5a0"
@ -409,6 +426,21 @@ aes-js@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a"
ajv-keywords@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=
ajv@^6.1.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96"
integrity sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^6.5.3, ajv@^6.6.1:
version "6.6.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61"
@ -1218,6 +1250,11 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
"bignumber.js@git+https://github.com/debris/bignumber.js#master":
version "2.0.7"
resolved "git+https://github.com/debris/bignumber.js#c7a38de919ed75e6fb6ba38051986e294b328df9"
@ -2551,6 +2588,11 @@ emoji-regex@^6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"
emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k=
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
@ -4523,6 +4565,13 @@ json5@^0.5.1:
version "0.5.1"
resolved "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
dependencies:
minimist "^1.2.0"
jsonfile@^2.1.0:
version "2.4.0"
resolved "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
@ -4836,6 +4885,15 @@ load-json-file@^4.0.0:
pify "^3.0.0"
strip-bom "^3.0.0"
loader-utils@^1.0.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
dependencies:
big.js "^5.2.2"
emojis-list "^2.0.0"
json5 "^1.0.1"
locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
@ -4934,6 +4992,11 @@ lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1,
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
lokijs@^1.5.6:
version "1.5.6"
resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.6.tgz#6de6b8c3ff7a972fd0104169f81e7ddc244c029f"
integrity sha512-xJoDXy8TASTjmXMKr4F8vvNUCu4dqlwY5gmn0g5BajGt1GM3goDCafNiGAh/sfrWgkfWu1J4OfsxWm8yrWweJA==
looper@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/looper/-/looper-2.0.0.tgz#66cd0c774af3d4fedac53794f742db56da8f09ec"
@ -5969,6 +6032,11 @@ quick-lru@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8"
rambdax@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/rambdax/-/rambdax-0.23.0.tgz#a4ffe1a6bbe0a85e164bd2e7187ac3453f60efc3"
integrity sha512-TquZA91VfG2he/iFExaMDfMHN/0B4U0H2LEoGwixYc7BmKMp6mj2gWfIpcjrUroPuxc6ExbXK9JvnKN5t5kgFQ==
ramda@^0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
@ -6513,7 +6581,12 @@ rx-lite@*, rx-lite@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
rxjs@^6.1.0:
rxjs-compat@^6.3.2:
version "6.3.3"
resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.3.3.tgz#2ab3b9ac0dac0c073749d55fef9c03ea1df2045c"
integrity sha512-caGN7ixiabHpOofginKEquuHk7GgaCrC7UpUQ9ZqGp80tMc68msadOeP/2AKy2R4YJsT1+TX5GZCtxO82qWkyA==
rxjs@^6.1.0, rxjs@^6.2.2:
version "6.3.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55"
integrity sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==
@ -6552,6 +6625,14 @@ scheduler@^0.12.0:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@^0.4.0:
version "0.4.7"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==
dependencies:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
scrypt.js@0.2.0, scrypt.js@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/scrypt.js/-/scrypt.js-0.2.0.tgz#af8d1465b71e9990110bedfc593b9479e03a8ada"
@ -6867,6 +6948,11 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
sql-escape-string@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/sql-escape-string/-/sql-escape-string-1.1.0.tgz#fe744b8514868c0eb4bfb9e4a989271d40f30eb9"
integrity sha1-/nRLhRSGjA60v7nkqYknHUDzDrk=
sshpk@^1.7.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629"
@ -8057,6 +8143,14 @@ wordwrap@~1.0.0:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
worker-loader@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac"
integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==
dependencies:
loader-utils "^1.0.0"
schema-utils "^0.4.0"
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"