lodash#zipObject JavaScript Examples
The following examples show how to use
lodash#zipObject.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: createDataLoaders.js From rate-repository-api with MIT License | 6 votes |
createModelLoader = Model =>
new DataLoader(
async ids => {
const idColumns = isArray(Model.idColumn)
? Model.idColumn
: [Model.idColumn];
const camelCasedIdColumns = idColumns.map(id => camelCase(id));
const results = await Model.query().findByIds(ids);
return ids.map(
id =>
find(
results,
zipObject(camelCasedIdColumns, isArray(id) ? id : [id]),
) || null,
);
},
{
cacheKeyFn: jsonCacheKeyFn,
},
)
Example #2
Source File: data-generator-utils.js From ThreatMapper with Apache License 2.0 | 6 votes |
function handleUpdated(updatedNodes, prevNodes) {
const modifiedNodesIndex = zipObject((updatedNodes || []).map(n => [n.id, n]));
return prevNodes.toIndexedSeq().toJS().map(n => (
Object.assign({}, mergeMetrics(n), modifiedNodesIndex[n.id])
));
}
Example #3
Source File: LedgerDerivationContent.js From origin-dollar with MIT License | 4 votes |
LedgerDerivationContent = ({}) => {
const [displayError, setDisplayError] = useState(null)
const [ledgerPath, setLedgerPath] = useState({})
const [addresses, setAddresses] = useState({})
const [addressBalances, setAddressBalances] = useState({})
const [addressStableBalances, setAddressStableBalances] = useState({})
const [pathTotals, setPathTotals] = useState({})
const [ready, setReady] = useState(false)
const [preloaded, setPreloaded] = useState(false)
const [activePath, setActivePath] = useState()
const [next, setNext] = useState({})
const [nextLoading, setNextLoading] = useState({})
const contractData = useStoreState(ContractStore, (s) => {
if (s.coinInfoList) {
return [
s.coinInfoList.usdt,
s.coinInfoList.dai,
s.coinInfoList.usdc,
s.coinInfoList.ousd,
]
}
return []
})
const errorMessageMap = (error) => {
if (!error || !error.message) {
return 'Unknown error'
}
if (
error.message.includes('Ledger device: UNKNOWN_ERROR') ||
error.message.includes(
'Failed to sign with Ledger device: U2F DEVICE_INELIGIBLE'
)
) {
return fbt(
'Unlock your Ledger wallet and open the Ethereum application',
'Unlock ledger'
)
} else if (error.message.includes('MULTIPLE_OPEN_CONNECTIONS_DISALLOWED')) {
return fbt(
'Unexpected error occurred. Please refresh page and try again.',
'Unexpected login error'
)
}
return error.message
}
const options = [
{
display: `Ledger Live`,
path: LEDGER_LIVE_BASE_PATH,
},
{
display: `Legacy`,
path: LEDGER_LEGACY_BASE_PATH,
},
{
display: `Ethereum`,
path: LEDGER_OTHER,
},
]
const loadBalances = async (path) => {
if (!(ledgerConnector.provider && addresses[path])) {
return
}
const [balances, stableBalances] = await Promise.all([
Promise.all(
addresses[path].map((a) =>
ledgerConnector
.getBalance(a)
.then((r) => (Number(r) / 10 ** 18).toFixed(2))
)
),
Promise.all(
addresses[path].map((a) => {
return Promise.all(
contractData.map((c) =>
c.contract.balanceOf(a).then((r) => Number(r) / 10 ** c.decimals)
)
)
})
),
])
const ethTotal = balances
.map((balance) => Number(balance))
.reduce((a, b) => a + b, 0)
const stableTotals = stableBalances.map((balance) => {
return balance.reduce((a, b) => a + b, 0).toFixed(2)
})
setAddressBalances({
...addressBalances,
[path]: zipObject(addresses[path], balances),
})
setAddressStableBalances({
...addressStableBalances,
[path]: zipObject(addresses[path], stableTotals),
})
setPathTotals({ ...pathTotals, [path]: ethTotal })
// preload addresses for each path
if (!ledgerPath[LEDGER_LIVE_BASE_PATH]) {
setLedgerPath({ ...ledgerPath, [LEDGER_LIVE_BASE_PATH]: true })
onSelectDerivationPath(LEDGER_LIVE_BASE_PATH)
} else if (!ledgerPath[LEDGER_LEGACY_BASE_PATH]) {
setLedgerPath({ ...ledgerPath, [LEDGER_LEGACY_BASE_PATH]: true })
onSelectDerivationPath(LEDGER_LEGACY_BASE_PATH)
} else if (!ledgerPath[LEDGER_OTHER]) {
setLedgerPath({ ...ledgerPath, [LEDGER_OTHER]: true })
onSelectDerivationPath(LEDGER_OTHER)
} else if (!activePath) {
// autoselect first path with non-zero ETH balance
if (pathTotals[LEDGER_LIVE_BASE_PATH] > 0) {
setActivePath(LEDGER_LIVE_BASE_PATH)
} else if (pathTotals[LEDGER_LEGACY_BASE_PATH] > 0) {
setActivePath(LEDGER_LEGACY_BASE_PATH)
} else if (ethTotal > 0) {
setActivePath(LEDGER_OTHER)
} else setActivePath(LEDGER_LIVE_BASE_PATH)
}
setPreloaded(
ledgerPath[LEDGER_LIVE_BASE_PATH] &&
ledgerPath[LEDGER_LEGACY_BASE_PATH] &&
ledgerPath[LEDGER_OTHER]
)
// indicators for scrolling to next address page within path
setNext({
[activePath]: nextLoading[activePath] ? !next[activePath] : false,
})
setNextLoading({ [activePath]: false })
}
useEffect(() => {
if (ready) loadBalances(LEDGER_LIVE_BASE_PATH)
}, [addresses[LEDGER_LIVE_BASE_PATH]])
useEffect(() => {
if (ready) loadBalances(LEDGER_LEGACY_BASE_PATH)
}, [addresses[LEDGER_LEGACY_BASE_PATH]])
useEffect(() => {
if (ready) loadBalances(LEDGER_OTHER)
}, [addresses[LEDGER_OTHER]])
const loadAddresses = async (path, next) => {
if (!ledgerConnector.provider) {
return
}
setLedgerPath({ ...ledgerPath, [path]: true })
if (next) {
setAddresses({
...addresses,
[path]: (await ledgerConnector.getAccounts(10)).slice(5),
})
} else {
setAddresses({
...addresses,
[path]: await ledgerConnector.getAccounts(5),
})
}
}
useEffect(() => {
onSelectDerivationPath(LEDGER_LIVE_BASE_PATH)
setReady(true)
}, [])
const onSelectDerivationPath = async (path, next) => {
try {
await ledgerConnector.activate()
await ledgerConnector.setPath(path)
setDisplayError(null)
} catch (error) {
setDisplayError(errorMessageMap(error))
return
}
loadAddresses(path, next)
}
return (
<>
<div
onClick={(e) => {
e.stopPropagation()
}}
className={`ledger-derivation-content d-flex flex-column`}
>
<h2>
{fbt(
'Select a Ledger derivation path',
'Select a Ledger derivation path'
)}
</h2>
<div className={`paths d-flex flex-row`}>
{options.map((option) => {
return (
<button
key={option.path}
className={
'text-center ' + (activePath === option.path && 'active')
}
onClick={() => {
if (!nextLoading[option.path]) {
setActivePath(option.path)
if (next[activePath]) {
// reset path to first address page in the background after clicking different path
setNextLoading({ [activePath]: true })
onSelectDerivationPath(activePath)
}
}
}}
>
{option.display}
<br />
<span className="button-path">{`m/${option.path}`}</span>
</button>
)
})}
</div>
{displayError && (
<div className="error d-flex align-items-center justify-content-center">
{displayError}
</div>
)}
<div className="d-flex flex-column align-items-center justify-content-center">
{activePath && preloaded ? (
<>
{nextLoading[activePath] && (
<img
className="waiting-icon rotating mx-auto"
src={assetRootPath('/images/spinner-green-small.png')}
/>
)}
{!nextLoading[activePath] && (
<LedgerAccountContent
addresses={addresses[activePath]}
addressBalances={addressBalances[activePath]}
addressStableBalances={addressStableBalances[activePath]}
activePath={activePath}
/>
)}
{!next[activePath] && !nextLoading[activePath] && (
<button
className="button-arrow"
onClick={() => {
setNextLoading({ [activePath]: true })
onSelectDerivationPath(activePath, true)
}}
>
<img
className="arrow-icon"
src={assetRootPath('/images/arrow-down.png')}
/>
</button>
)}
{next[activePath] && !nextLoading[activePath] && (
<button
className="button-arrow"
onClick={() => {
setNextLoading({ [activePath]: true })
onSelectDerivationPath(activePath)
}}
>
<img
className="arrow-icon"
src={assetRootPath('/images/arrow-up.png')}
/>
</button>
)}
</>
) : (
!displayError && (
<img
className="waiting-icon rotating mx-auto"
src={assetRootPath('/images/spinner-green-small.png')}
/>
)
)}
</div>
</div>
<style jsx>{`
.ledger-derivation-content {
padding: 26px 22px 20px 22px;
max-width: 500px;
min-width: 500px;
box-shadow: 0 0 14px 0 rgba(24, 49, 64, 0.1);
background-color: white;
border-radius: 10px;
align-items: center;
justify-content: center;
}
.ledger-derivation-content h2 {
padding-left: 12px;
padding-right: 12px;
font-size: 18px;
font-weight: bold;
text-align: center;
line-height: normal;
margin-bottom: 14px;
}
.ledger-derivation-content .paths {
width: 100%;
justify-content: center;
}
.ledger-derivation-content button {
width: 100%;
height: 55px;
border-radius: 50px;
border: solid 1px #1a82ff;
background-color: white;
font-size: 18px;
font-weight: bold;
text-align: center;
color: #1a82ff;
padding: 5px 10px;
margin: 10px 5px 20px 5px;
line-height: 22px;
}
.ledger-derivation-content .button-path {
font-size: 14px;
color: #a0a0a0;
}
.active {
background-color: #c0e0ff !important;
}
.error {
margin-top: 20px;
padding: 5px 8px;
font-size: 14px;
line-height: 1.36;
text-align: center;
color: #ed2a28;
border-radius: 5px;
border: solid 1px #ed2a28;
min-height: 50px;
width: 100%;
}
.button-arrow {
width: 70px !important;
height: 35px !important;
padding: 0 !important;
margin: 10px 0 0 0 !important;
}
.arrow-icon {
width: 25px;
height: 25px;
}
.waiting-icon {
width: 25px;
height: 25px;
}
.rotating {
-webkit-animation: spin 2s linear infinite;
-moz-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
@-moz-keyframes spin {
100% {
-moz-transform: rotate(360deg);
}
}
@-webkit-keyframes spin {
100% {
-webkit-transform: rotate(360deg);
}
}
@keyframes spin {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
`}</style>
</>
)
}
Example #4
Source File: BalanceHeader.js From origin-dollar with MIT License | 4 votes |
BalanceHeader = ({
storeTransaction,
storeTransactionError,
rpcProvider,
isMobile,
}) => {
const { connector, account } = useWeb3React()
const DEFAULT_SELECTED_APY = 365
const apyOptions = useStoreState(ContractStore, (s) =>
apyDayOptions.map((d) => {
return s.apy[`apy${d}`] || 0
})
)
const daysToApy = zipObject(apyDayOptions, apyOptions)
const [apyDays, setApyDays] = useState(
process.browser && localStorage.getItem('last_user_selected_apy') !== null
? localStorage.getItem('last_user_selected_apy')
: DEFAULT_SELECTED_APY
)
const vault = useStoreState(ContractStore, (s) => _get(s, 'contracts.vault'))
const ousdContract = useStoreState(ContractStore, (s) =>
_get(s, 'contracts.ousd')
)
const ousdBalance = useStoreState(AccountStore, (s) => s.balances['ousd'])
const lifetimeYield = useStoreState(AccountStore, (s) => s.lifetimeYield)
const ousdBalanceLoaded = typeof ousdBalance === 'string'
const animatedOusdBalance = useStoreState(
AnimatedOusdStore,
(s) => s.animatedOusdBalance
)
const mintAnimationLimit = 0.5
const walletConnected = useStoreState(ContractStore, (s) => s.walletConnected)
const [balanceEmphasised, setBalanceEmphasised] = useState(false)
const prevOusdBalance = usePrevious(ousdBalance)
const { animatedExpectedIncrease } = useExpectedYield()
const normalOusdAnimation = (from, to) => {
setBalanceEmphasised(true)
return animateValue({
from: parseFloat(from) || 0,
to: parseFloat(to),
callbackValue: (val) => {
AnimatedOusdStore.update((s) => {
s.animatedOusdBalance = val
})
},
onCompleteCallback: () => {
setBalanceEmphasised(false)
},
// non even duration number so more of the decimals in ousdBalance animate
duration: 1985,
id: 'header-balance-ousd-animation',
stepTime: 30,
})
}
useEffect(() => {
if (ousdBalanceLoaded) {
const ousdBalanceNum = parseFloat(ousdBalance)
const prevOusdBalanceNum = parseFloat(prevOusdBalance)
// user must have minted the OUSD
if (
!isNaN(parseFloat(ousdBalanceNum)) &&
!isNaN(parseFloat(prevOusdBalanceNum)) &&
Math.abs(ousdBalanceNum - prevOusdBalanceNum) > mintAnimationLimit
) {
normalOusdAnimation(prevOusdBalance, ousdBalance)
} else if (
!isNaN(parseFloat(ousdBalanceNum)) &&
ousdBalanceNum > mintAnimationLimit
) {
normalOusdAnimation(0, ousdBalance)
} else {
normalOusdAnimation(prevOusdBalance, 0)
}
}
}, [ousdBalance])
/*
* Type: number or percentage
*/
const Statistic = ({
dropdown,
title,
value,
type,
titleLink,
marginBottom = false,
}) => {
return (
<>
<div
className={`d-flex holder flex-row flex-md-column align-items-end align-items-md-start justify-content-start ${
marginBottom ? 'margin-bottom' : ''
}`}
>
<div className={`value ${type}`}>{value}</div>
<div className="flex-row">
<span className="dropdown">{dropdown}</span>
{titleLink && (
<a
className={`title link ${type}`}
href={adjustLinkHref(titleLink)}
rel="noopener noreferrer"
target="blank"
>
{title}
</a>
)}
{!titleLink && <div className="title">{title}</div>}
</div>
</div>
<style jsx>{`
.dropdown {
display: inline-block;
}
.title {
color: #8293a4;
font-size: 14px;
display: inline;
}
.title.link {
cursor: pointer;
text-decoration: underline;
}
.value {
color: white;
font-size: 28px;
}
.value.percentage::after {
content: '%';
padding-left: 2px;
}
@media (max-width: 767px) {
.dropdown {
padding-bottom: 10px;
}
.title {
width: 55%;
text-align: left;
margin-bottom: 3px;
}
.title.percentage {
margin-bottom: 10px;
}
.holder {
width: 100%;
}
.value.percentage {
font-size: 32px;
}
.value {
color: white;
font-size: 20px;
width: 45%;
text-align: left;
}
.margin-bottom {
margin-bottom: 20px;
}
}
`}</style>
</>
)
}
const displayedBalance = formatCurrency(animatedOusdBalance || 0, 2)
useEffect(() => {
localStorage.setItem('last_user_selected_apy', apyDays)
}, [apyDays])
const ApySelect = () => {
const [open, setOpen] = useState(false)
return (
<>
<Dropdown
content={
<div className="dropdown-menu d-flex flex-column">
{apyDayOptions.map((days) => {
return (
<div
key={days}
className="dropdown-item justify-content-start align-items-center"
onClick={() => {
setApyDays(days)
setOpen(false)
}}
>
{`${days}d`}
</div>
)
})}
</div>
}
open={open}
onClose={() => setOpen(false)}
>
<div
className="apy-select d-flex flex-row align-items-center"
onClick={() => setOpen(!open)}
>
{`${apyDays}d`}
<span className="downcaret">
<DownCaret color="black" size="26" />
</span>
</div>
</Dropdown>
<style jsx>{`
.apy-select {
background-color: white;
font-size: 16px;
font-weight: 500;
color: black;
width: 68px;
height: 25px;
padding: 0 22px 2px 8px;
margin-right: 8px;
border-radius: 20px;
cursor: pointer;
}
.apy-select:hover {
background-color: #f2f3f5;
}
.dropdown-menu {
margin-right: 200px;
background-color: white;
font-size: 16px;
color: black;
min-width: 90px;
top: 100%;
left: 0;
padding: 5px;
}
.dropdown-item {
background-color: white;
color: black;
padding: 3px 5px 3px 10px;
line-height: 20px;
cursor: pointer;
}
.dropdown-item:hover {
background-color: #f2f3f5;
}
.downcaret {
position: absolute;
left: 42px;
}
`}</style>
</>
)
}
return (
<>
<div className="balance-header d-flex flex-column justify-content-start">
<div className="d-flex flex-column flex-md-row balance-holder justify-content-start w-100">
<div className="apy-container d-flex justify-content-center">
<div
className={`contents d-flex align-items-center justify-content-center box box-black ${
isMobile ? 'w-50' : ''
}`}
>
<Statistic
dropdown={<ApySelect />}
title={fbt('Trailing APY', 'Trailing APY')}
titleLink="https://analytics.ousd.com/apy"
value={
typeof daysToApy[apyDays] === 'number'
? formatCurrency(daysToApy[apyDays] * 100, 2)
: '--.--'
}
type={
typeof daysToApy[apyDays] === 'number' ? 'percentage' : ''
}
/>
</div>
</div>
<div className="d-flex flex-column flex-md-row align-items-center justify-content-between box box-narrow w-100">
<Statistic
title={fbt('Balance', 'OUSD Balance')}
value={
!isNaN(parseFloat(displayedBalance)) && ousdBalanceLoaded
? displayedBalance
: '--.--'
}
type={'number'}
marginBottom={true}
/>
<Statistic
title={fbt('Pending yield', 'Pending yield')}
value={
walletConnected
? formatCurrency(animatedExpectedIncrease, 2)
: '--.--'
}
type={'number'}
marginBottom={true}
/>
<Statistic
title={fbt(
'Lifetime earnings',
'Lifetime OUSD balance header earnings'
)}
titleLink={
account
? `${
process.env.ANALYTICS_ENDPOINT
}/address/${account.toLowerCase()}`
: false
}
value={
walletConnected && lifetimeYield
? formatCurrency(lifetimeYield, 2)
: '--.--'
}
type={'number'}
/>
</div>
</div>
</div>
<style jsx>{`
.balance-header {
margin-bottom: 19px;
}
.balance-header .inaccurate-balance {
border: 2px solid #ed2a28;
border-radius: 5px;
color: #ed2a28;
margin-bottom: 40px;
padding: 15px;
}
.balance-header .inaccurate-balance a {
text-decoration: underline;
}
.balance-header .light-grey-label {
font-size: 14px;
font-weight: bold;
color: #8293a4;
}
.balance-header .detail {
font-size: 12px;
color: #8293a4;
}
.balance-header a:hover {
color: white;
}
.balance-header .ousd-value {
font-size: 14px;
color: white;
text-align: left;
text-overflow: ellipsis;
transition: font-size 0.2s cubic-bezier(0.5, -0.5, 0.5, 1.5),
color 0.2s cubic-bezier(0.5, -0.5, 0.5, 1.5);
position: relative;
margin-left: 11px;
}
.balance-header .ousd-value.big {
color: #00d592;
}
.balance-header .apy-container {
height: 100%;
}
.balance-header .apy-container .contents {
z-index: 2;
}
.balance-header .apy-container .apy-percentage {
font-size: 14px;
color: #ffffff;
font-weight: bold;
margin-left: 8px;
}
.balance-header .apy-container .apy-percentage::after {
content: '%';
font-weight: bold;
padding-left: 2px;
}
.balance-header .expected-increase {
font-size: 12px;
color: #8293a4;
}
.balance-header .expected-increase p {
margin: auto;
}
.balance-header .expected-increase .dropdown {
justify-content: center !important;
}
.balance-header .expected-increase .dropdown .disclaimer-tooltip {
display: flex !important;
}
.balance-header .expected-increase .collect {
color: #1a82ff;
cursor: pointer;
}
.box {
padding: 30px;
min-width: 210px;
min-height: 118px;
border-radius: 10px;
box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
border: solid 1px black;
color: white;
}
.box-narrow {
padding: 30px 50px;
}
.box.box-black {
background-color: black;
margin-right: 10px;
min-width: 230px;
}
@media (max-width: 767px) {
.balance-header {
align-items: center;
text-align: center;
padding: 0px 20px;
min-height: 80px;
}
.apy-container {
margin-bottom: 10px;
}
.balance-header .gradient-border {
width: 100px;
height: 100px;
margin-right: 20px;
padding-right: 20px;
}
.box {
padding: 20px;
min-width: auto;
min-height: 90px;
}
.box.box-black {
min-width: 100%;
margin-right: 0px;
}
.balance-header .ousd-value.mio-club {
font-size: 20px;
}
.balance-header .ousd-value .grey {
color: #8293a4;
}
.balance-header .ousd-value-holder {
white-space: nowrap;
}
.balance-header .apy-container .apy-label {
font-family: Lato;
font-size: 11px;
font-weight: bold;
text-align: center;
color: #8293a4;
}
.balance-header .apy-container .apy-percentage {
font-family: Lato;
font-weight: normal;
}
.balance-header .ousd-value::after {
content: '';
}
.balance-header .light-grey-label {
font-family: Lato;
font-size: 11px;
font-weight: bold;
color: #8293a4;
margin-bottom: -2px;
}
.balance-holder {
width: 100%;
}
}
`}</style>
</>
)
}
Example #5
Source File: BalanceHeaderWrapped.js From origin-dollar with MIT License | 4 votes |
BalanceHeaderWrapped = ({
storeTransaction,
storeTransactionError,
rpcProvider,
isMobile,
}) => {
const DEFAULT_SELECTED_APY = 365
const apyOptions = useStoreState(ContractStore, (s) =>
apyDayOptions.map((d) => {
return s.apy[`apy${d}`] || 0
})
)
const daysToApy = zipObject(apyDayOptions, apyOptions)
const [apyDays, setApyDays] = useState(
process.browser && localStorage.getItem('last_user_selected_apy') !== null
? localStorage.getItem('last_user_selected_apy')
: DEFAULT_SELECTED_APY
)
const walletConnected = useStoreState(ContractStore, (s) => s.walletConnected)
const { animatedExpectedIncrease } = useExpectedYield(true)
const wousdBalance = useStoreState(AccountStore, (s) => s.balances['wousd'])
const wousdBalanceLoaded = typeof wousdBalance === 'string'
const wousdValue = useStoreState(AccountStore, (s) => s.wousdValue)
/*
* Type: number or percentage
*/
const Statistic = ({
dropdown,
title,
value,
type,
titleLink,
marginBottom = false,
}) => {
return (
<>
<div
className={`d-flex holder flex-row flex-md-column align-items-end align-items-md-start justify-content-start ${
marginBottom ? 'margin-bottom' : ''
}`}
>
<div className={`value ${type}`}>{value}</div>
<div className="flex-row">
<span className="dropdown">{dropdown}</span>
{titleLink && (
<a
className={`title link ${type}`}
href={adjustLinkHref(titleLink)}
rel="noopener noreferrer"
target="blank"
>
{title}
</a>
)}
{!titleLink && <div className="title">{title}</div>}
</div>
</div>
<style jsx>{`
.dropdown {
display: inline-block;
}
.title {
color: #8293a4;
font-size: 14px;
display: inline;
}
.title.link {
cursor: pointer;
text-decoration: underline;
}
.value {
color: white;
font-size: 28px;
}
.value.percentage::after {
content: '%';
padding-left: 2px;
}
@media (max-width: 767px) {
.title {
width: 55%;
text-align: left;
margin-bottom: 3px;
}
.title.percentage {
margin-bottom: 10px;
}
.holder {
width: 100%;
}
.value.percentage {
font-size: 32px;
}
.value {
color: white;
font-size: 20px;
width: 45%;
text-align: left;
}
.margin-bottom {
margin-bottom: 20px;
}
}
`}</style>
</>
)
}
const displayedWousdBalance = formatCurrency(wousdBalance || 0, 2)
useEffect(() => {
localStorage.setItem('last_user_selected_apy', apyDays)
}, [apyDays])
const ApySelect = () => {
const [open, setOpen] = useState(false)
return (
<>
<Dropdown
content={
<div className="dropdown-menu d-flex flex-column">
{apyDayOptions.map((days) => {
return (
<div
key={days}
className="dropdown-item justify-content-start align-items-center"
onClick={() => {
setApyDays(days)
setOpen(false)
}}
>
{`${days}d`}
</div>
)
})}
</div>
}
open={open}
onClose={() => setOpen(false)}
>
<div
className="apy-select d-flex flex-row align-items-center"
onClick={() => setOpen(!open)}
>
{`${apyDays}d`}
<span className="downcaret">
<DownCaret color="black" size="26" />
</span>
</div>
</Dropdown>
<style jsx>{`
.apy-select {
background-color: white;
font-size: 16px;
font-weight: 500;
color: black;
width: 68px;
height: 25px;
padding: 0 22px 2px 8px;
margin-right: 8px;
border-radius: 20px;
cursor: pointer;
}
.apy-select:hover {
background-color: #f2f3f5;
}
.dropdown-menu {
margin-right: 200px;
background-color: white;
font-size: 16px;
color: black;
min-width: 90px;
top: 100%;
left: 0;
padding: 5px;
}
.dropdown-item {
background-color: white;
color: black;
padding: 3px 5px 3px 10px;
line-height: 20px;
cursor: pointer;
}
.dropdown-item:hover {
background-color: #f2f3f5;
}
.downcaret {
position: absolute;
left: 42px;
}
`}</style>
</>
)
}
return (
<>
<div className="balance-header d-flex flex-column justify-content-start">
<div className="d-flex flex-column flex-md-row balance-holder justify-content-start w-100">
<div className="apy-container d-flex justify-content-center">
<div
className={`contents d-flex align-items-center justify-content-center box box-black ${
isMobile ? 'w-50' : ''
}`}
>
<Statistic
dropdown={<ApySelect />}
title={fbt('Trailing APY', 'Trailing APY')}
titleLink="https://analytics.ousd.com/apy"
value={
typeof daysToApy[apyDays] === 'number'
? formatCurrency(daysToApy[apyDays] * 100, 2)
: '--.--'
}
type={
typeof daysToApy[apyDays] === 'number' ? 'percentage' : ''
}
/>
</div>
</div>
<div className="d-flex flex-column flex-md-row align-items-center justify-content-between box box-narrow w-100">
<Statistic
title={fbt('wOUSD Balance', 'wOUSD Balance')}
value={
!isNaN(parseFloat(displayedWousdBalance)) && wousdBalanceLoaded
? displayedWousdBalance
: '--.--'
}
type={'number'}
marginBottom={true}
/>
<Statistic
title={fbt('Current Value (OUSD)', 'Current Value (OUSD)')}
value={
walletConnected && !isNaN(wousdValue)
? formatCurrency(wousdValue, 2)
: '--.--'
}
type={'number'}
marginBottom={true}
/>
<Statistic
title={fbt('Pending yield (OUSD)', 'Pending yield (OUSD)')}
value={
walletConnected
? formatCurrency(animatedExpectedIncrease, 2)
: '--.--'
}
type={'number'}
/>
</div>
</div>
</div>
<style jsx>{`
.balance-header {
margin-bottom: 19px;
}
.balance-header .inaccurate-balance {
border: 2px solid #ed2a28;
border-radius: 5px;
color: #ed2a28;
margin-bottom: 40px;
padding: 15px;
}
.balance-header .inaccurate-balance a {
text-decoration: underline;
}
.balance-header .light-grey-label {
font-size: 14px;
font-weight: bold;
color: #8293a4;
}
.balance-header .detail {
font-size: 12px;
color: #8293a4;
}
.balance-header a:hover {
color: white;
}
.balance-header .ousd-value {
font-size: 14px;
color: white;
text-align: left;
text-overflow: ellipsis;
transition: font-size 0.2s cubic-bezier(0.5, -0.5, 0.5, 1.5),
color 0.2s cubic-bezier(0.5, -0.5, 0.5, 1.5);
position: relative;
margin-left: 11px;
}
.balance-header .ousd-value.big {
color: #00d592;
}
.balance-header .apy-container {
height: 100%;
}
.balance-header .apy-container .contents {
z-index: 2;
}
.balance-header .apy-container .apy-percentage {
font-size: 14px;
color: #ffffff;
font-weight: bold;
margin-left: 8px;
}
.balance-header .apy-container .apy-percentage::after {
content: '%';
font-weight: bold;
padding-left: 2px;
}
.balance-header .expected-increase {
font-size: 12px;
color: #8293a4;
}
.balance-header .expected-increase p {
margin: auto;
}
.balance-header .expected-increase .dropdown {
justify-content: center !important;
}
.balance-header .expected-increase .dropdown .disclaimer-tooltip {
display: flex !important;
}
.balance-header .expected-increase .collect {
color: #1a82ff;
cursor: pointer;
}
.box {
padding: 30px;
min-width: 210px;
min-height: 118px;
border-radius: 10px;
box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
border: solid 1px black;
color: white;
}
.box-narrow {
padding: 30px 50px;
}
.box.box-black {
background-color: black;
margin-right: 10px;
min-width: 230px;
}
@media (max-width: 767px) {
.balance-header {
align-items: center;
text-align: center;
padding: 0px 20px;
min-height: 80px;
}
.apy-container {
margin-bottom: 10px;
}
.balance-header .gradient-border {
width: 100px;
height: 100px;
margin-right: 20px;
padding-right: 20px;
}
.box {
padding: 20px;
min-width: auto;
min-height: 90px;
}
.box.box-black {
min-width: 100%;
margin-right: 0px;
}
.balance-header .ousd-value.mio-club {
font-size: 20px;
}
.balance-header .ousd-value .grey {
color: #8293a4;
}
.balance-header .ousd-value-holder {
white-space: nowrap;
}
.balance-header .apy-container .apy-label {
font-family: Lato;
font-size: 11px;
font-weight: bold;
text-align: center;
color: #8293a4;
}
.balance-header .apy-container .apy-percentage {
font-family: Lato;
font-weight: normal;
}
.balance-header .ousd-value::after {
content: '';
}
.balance-header .light-grey-label {
font-family: Lato;
font-size: 11px;
font-weight: bold;
color: #8293a4;
margin-bottom: -2px;
}
.balance-holder {
width: 100%;
}
}
`}</style>
</>
)
}
Example #6
Source File: Canvas.js From hivemind with Apache License 2.0 | 4 votes |
Canvas = ({ data, timestamp, events }) => {
const { cyWrapper, poppers } = useContext(GlobalContext)
const [output, setOutput] = useState(null)
const [els, setEls] = useState([])
const prevEls = usePrevious(els)
useEffect(() => {
if (cyWrapper.cy && prevEls !== els) {
const commonEls = intersectionBy(prevEls, els, 'data.id')
const celMap = zipObject(map(commonEls, 'data.id'), commonEls)
cyWrapper.cy
.elements()
.filter((el) => celMap[el.id()])
.forEach((el) => {
el.removeData('summary content audio lastUpdatedBy')
el.data(celMap[el.id()].data)
})
}
}, [cyWrapper.cy, els, prevEls])
useEffect(() => {
if (get(data, 'ok') && typeof window !== 'undefined') {
setEls(CytoscapeComponent.normalizeElements(data.data.elements))
}
}, [data])
useEffect(() => {
function initCy(cyInternal) {
cyWrapper.cy = cyInternal
cyInternal.nodes().forEach((node) => {
node.scratch('style', node.style())
})
}
const nodes = els.filter((el) => !el.data.id.startsWith('links'))
const fit = shouldFit(nodes)
const options = getOptions(fit)
setOutput(
<CytoscapeComponent
cy={initCy}
style={{ width: '100%', height: '100%' }}
stylesheet={style}
layout={options}
elements={els}
/>
)
}, [cyWrapper, els])
useEffect(() => {
function configurePlugins(access) {
function buildMenu() {
const { viewApi } = cyWrapper
return function (node) {
const menu = []
view(menu, poppers)
if (!node.data('isRoot')) {
hide(menu, viewApi)
}
if (node.scratch('showReveal')) {
reveal(menu, viewApi)
}
if (access && ['admin', 'write'].includes(access.access)) {
add(menu, poppers)
if (!node.data('isRoot')) {
del(menu, poppers)
}
edit(menu, poppers)
}
return menu
}
}
const { cy } = cyWrapper
const minRadius = Math.min(cy.width(), cy.height()) / 8
const viewOpts = {
highlightStyles: [
{
node: { 'border-color': '#0b9bcd', 'border-width': 3 },
edge: {
'line-color': '#0b9bcd',
'source-arrow-color': '#0b9bcd',
'target-arrow-color': '#0b9bcd',
width: 3,
},
},
{
node: { 'border-color': '#04f06a', 'border-width': 3 },
edge: {
'line-color': '#04f06a',
'source-arrow-color': '#04f06a',
'target-arrow-color': '#04f06a',
width: 3,
},
},
],
selectStyles: {
node: {
'border-color': 'white',
'border-width': 3,
'background-color': 'lightgrey',
},
edge: {
'line-color': 'white',
'source-arrow-color': 'white',
'target-arrow-color': 'white',
width: 3,
},
},
setVisibilityOnHide: false, // whether to set visibility on hide/show
setDisplayOnHide: true, // whether to set display on hide/show
zoomAnimationDuration: 500, //default duration for zoom animation speed
neighbor: function (node) {
return node.successors()
},
neighborSelectTime: 500,
}
cyWrapper.viewApi = cy.viewUtilities(viewOpts)
const cxtMenu = {
menuRadius: minRadius + 50, // the radius of the circular menu in pixels
selector: 'node', // elements matching this Cytoscape.js selector will trigger cxtmenus
commands: buildMenu(), // function( ele ){ return [
// /*...*/ ] }, // a function
// that returns
// commands or a promise of commands
fillColor: 'rgba(0, 0, 0, 0.75)', // the background colour of the menu
activeFillColor: 'rgba(100, 100, 100, 0.5)', // the colour used to indicate the selected
// command
activePadding: 10, // additional size in pixels for the active command
indicatorSize: 16, // the size in pixels of the pointer to the active command
separatorWidth: 3, // the empty spacing in pixels between successive commands
spotlightPadding: 4, // extra spacing in pixels between the element and the spotlight
minSpotlightRadius: minRadius - 40, // the minimum radius in pixels of the spotlight
maxSpotlightRadius: minRadius - 20, // the maximum radius in pixels of the spotlight
openMenuEvents: 'tap', // space-separated cytoscape events that will open the menu; only
// `cxttapstart` and/or `taphold` work here
itemColor: 'white', // the colour of text in the command's content
itemTextShadowColor: 'transparent', // the text shadow colour of the command's content
// zIndex: 9999, // the z-index of the ui div
atMouse: false, // draw menu at mouse position
}
cyWrapper.menu = cy.cxtmenu(cxtMenu)
}
function setHandlers() {
const { viewApi, cy } = cyWrapper
cy.on(
'boxend',
throttle(() => defer(() => viewApi.zoomToSelected(cy.$(':selected')))),
1000
)
cy.on('mouseover', 'node', () => {
document.getElementById('cy').style.cursor = 'pointer'
})
cy.on('mouseout', 'node', () => {
document.getElementById('cy').style.cursor = 'default'
})
cy.on('mouseover', 'edge', (e) => {
e.target.style({
width: 4,
'line-color': '#007bff',
'target-arrow-color': '#007bff',
})
})
cy.on('unselect mouseout', 'edge', (e) => {
const edge = e.target
if (!edge.selected()) {
edge.style({
width: 2,
'line-color': '#ccc',
'target-arrow-color': '#ccc',
})
}
})
cy.on('add', 'node', (e) => {
const node = e.target
node.scratch('style', node.style())
})
cy.on(
'add data remove',
'node',
throttle(() => {
if (timestamp) {
const idx = findIndex(events.data, { lctime: timestamp })
const event = events.data[idx]
const { viewApi } = cyWrapper
viewApi.removeHighlights(cy.elements())
if (event && event.event !== 'deleted') {
const nid = event.nids[0]
const node = cy.$id(nid)
viewApi.highlight(node)
}
}
}, 100)
)
cy.on('mouseover', 'node', (e) => {
e.target.style('background-color', '#007bff')
})
cy.on('unselect mouseout', 'node', (e) => {
const node = e.target
viewApi.removeHighlights(node)
if (!node.selected()) {
node.style(
'background-color',
node.scratch('style')['background-color']
)
}
})
}
if (cyWrapper.cy && get(data, 'ok') && get(events, 'ok')) {
configurePlugins(data.data.access)
setHandlers()
}
return () => {
if (cyWrapper.menu) {
cyWrapper.menu.destroy()
}
}
}, [data, events, cyWrapper.menu, cyWrapper, timestamp, poppers, els])
return (
<div
className={`border border-${
timestamp ? 'secondary' : 'danger'
} rounded w-100`}
id="cy-container"
>
<div className="m-1" id="cy">
{output}
</div>
</div>
)
}
Example #7
Source File: index.js From datapass with GNU Affero General Public License v3.0 | 4 votes |
DonneesSection = ({
DonneesDescription,
availableScopes = [],
AvailableScopesDescription,
accessModes,
enableFileSubmissionForScopeSelection = false,
}) => {
const {
disabled,
onChange,
enrollment: {
scopes = {},
data_recipients = '',
data_retention_period = '',
data_retention_comment = '',
additional_content = {},
documents = [],
documents_attributes = [],
},
} = useContext(FormContext);
useEffect(() => {
if (!isEmpty(availableScopes) && isEmpty(scopes)) {
onChange({
target: {
name: 'scopes',
value: zipObject(
availableScopes.map(({ value }) => value),
availableScopes.map(
({ required, checkedByDefault }) =>
!!required || !!checkedByDefault
)
),
},
});
}
});
const groupTitleScopesGroup = groupBy(
availableScopes,
(e) => e.groupTitle || 'default'
);
// {'a': true, 'b': false, 'c': true} becomes ['a', 'c']
const scopesAsArray = chain(scopes)
.omitBy((e) => !e)
.keys()
.value();
const availableScopesAsArray = availableScopes.map(({ value }) => value);
const outdatedScopes = difference(scopesAsArray, availableScopesAsArray);
const [isFileInputExpanded, setFileInputExpanded] = useState(
enableFileSubmissionForScopeSelection &&
!isEmpty(
documents.filter(
({ type }) => type === 'Document::ExpressionBesoinSpecifique'
)
)
);
useEffect(() => {
const hasDocument = !isEmpty(
documents.filter(
({ type }) => type === 'Document::ExpressionBesoinSpecifique'
)
);
if (
enableFileSubmissionForScopeSelection &&
!isFileInputExpanded &&
hasDocument
) {
setFileInputExpanded(true);
}
}, [enableFileSubmissionForScopeSelection, isFileInputExpanded, documents]);
return (
<ScrollablePanel scrollableId={SECTION_ID}>
<h2>Les données nécessaires</h2>
{DonneesDescription && (
<ExpandableQuote title="Comment choisir les données ?">
<DonneesDescription />
</ExpandableQuote>
)}
{AvailableScopesDescription && <AvailableScopesDescription />}
{!isEmpty(availableScopes) && (
<>
<h3>À quelles données souhaitez-vous avoir accès ?</h3>
{Object.keys(groupTitleScopesGroup).map((group) => (
<Scopes
key={group}
title={group === 'default' ? null : group}
scopes={groupTitleScopesGroup[group]}
selectedScopes={scopes}
disabledApplication={disabled}
handleChange={onChange}
/>
))}
{disabled && !isEmpty(outdatedScopes) && (
<Scopes
title="Les données suivantes ont été sélectionnées mais ne sont plus disponibles :"
scopes={outdatedScopes.map((value) => ({ value, label: value }))}
selectedScopes={zipObject(
outdatedScopes,
Array(outdatedScopes.length).fill(true)
)}
disabledApplication
handleChange={() => null}
/>
)}
</>
)}
{enableFileSubmissionForScopeSelection && (
<>
<ExpandableQuote title="J’ai une expression de besoin spécifique ?">
<p>
Les partenaires ayant convenu avec la DGFiP un périmètre de
données particulier peuvent rattacher leur expression de besoin
listant l’ensemble des données strictement nécessaires à leur cas
d’usage. Si vous n'avez pas encore contacté la DGFiP, vous pouvez
les joindre à l'adresse{' '}
<Link
inline
href="mailto:[email protected]?subject=Expression%20de%20besoin%20spécifique"
>
[email protected]
</Link>
</p>
<CheckboxInput
label="J’ai une expression de besoin spécifique"
value={isFileInputExpanded}
onChange={() => setFileInputExpanded(!isFileInputExpanded)}
disabled={disabled}
/>
</ExpandableQuote>
{isFileInputExpanded && (
<>
<FileInput
label="Joindre l’expression de besoin"
meta={
'Attention : seule l’expression de besoin en données ayant déjà été partagée avec la DGFiP peut être rattachée à votre habilitation.'
}
mimeTypes="*"
disabled={disabled}
uploadedDocuments={documents}
documentsToUpload={documents_attributes}
documentType={'Document::ExpressionBesoinSpecifique'}
onChange={onChange}
/>
</>
)}
</>
)}
{!isEmpty(accessModes) && (
<>
<h3>Comment souhaitez-vous y accéder ?</h3>
{accessModes.map(({ id, label }) => (
<CheckboxInput
key={id}
label={label}
name={`additional_content.${id}`}
value={additional_content[id] || false}
disabled={disabled}
onChange={onChange}
/>
))}
</>
)}
<h3>Comment seront traitées ces données personnelles ?</h3>
<TextInput
label="Destinataires des données"
placeholder={
'« agents instructeurs des demandes d’aides », « usagers des ' +
'services publics de la ville », etc.'
}
meta={
<Link
inline
newTab
href="https://www.cnil.fr/fr/definition/destinataire"
aria-label="Voir la définition CNIL du destinataire des données"
>
Plus d’infos
</Link>
}
name="data_recipients"
value={data_recipients}
disabled={disabled}
onChange={onChange}
required
/>
<NumberInput
label="Durée de conservation des données en mois"
meta={
<Link
inline
newTab
href="https://www.cnil.fr/fr/les-durees-de-conservation-des-donnees"
aria-label="Voir l’explication CNIL sur les durées de conservation des données"
>
Plus d’infos
</Link>
}
name="data_retention_period"
value={data_retention_period}
disabled={disabled}
onChange={onChange}
required
/>
{data_retention_period > 36 && (
<>
<Alert type="warning" title="Attention">
Cette durée excède la durée communément constatée (36 mois).
</Alert>
<TextInput
label="Veuillez justifier cette durée dans le champ ci-après :"
name="data_retention_comment"
value={data_retention_comment}
disabled={disabled}
onChange={onChange}
/>
</>
)}
</ScrollablePanel>
);
}