Add currency selector to settings sidebar

Resolves #11
This commit is contained in:
Mononaut 2022-01-31 12:47:01 -06:00
parent 9f9ad26427
commit 89a7ef5af6
17 changed files with 379 additions and 147 deletions

View File

@ -12,7 +12,7 @@ This repo hosts the code behind [Bitfeed](https://bits.monospace.live), a live v
- [Umbrel](https://getumbrel.com) (coming soon)
- [Citadel](https://runcitadel.space/)
- [Docker](https://github.com/bitfeed-project/bitfeed/blob/master/DOCKER.md)
- [Build from source](https://github.com/bitfeed-project/bitfeed/blob/master/README.md#installing-and-building-bitfeed)
- [Build from source](#installing-and-building-bitfeed)
## Installing and Building Bitfeed

View File

@ -1,12 +1,12 @@
{
"name": "bitfeed-client",
"version": "2.1.0",
"version": "2.1.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitfeed-client",
"version": "2.1.0",
"version": "2.1.3",
"dependencies": {
"@babel/core": "^7.16.5",
"@babel/preset-env": "^7.16.5",
@ -31,7 +31,8 @@
"sass": "^1.49.0",
"sirv-cli": "^1.0.14",
"svelte": "^3.44.3",
"svelte-preprocess": "^4.10.1"
"svelte-preprocess": "^4.10.1",
"svelte-select": "^4.4.7"
}
},
"node_modules/@babel/code-frame": {
@ -4063,6 +4064,11 @@
}
}
},
"node_modules/svelte-select": {
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/svelte-select/-/svelte-select-4.4.7.tgz",
"integrity": "sha512-fIf9Z8rPI6F8naHZ9wjXT0Pv5gLyhdHAFkHFJnCfVVfELE8e82uOoF0xEVQP6Kir+b4Q5yOvNAzZ61WbSU6A0A=="
},
"node_modules/through2": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
@ -7205,6 +7211,11 @@
"strip-indent": "^3.0.0"
}
},
"svelte-select": {
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/svelte-select/-/svelte-select-4.4.7.tgz",
"integrity": "sha512-fIf9Z8rPI6F8naHZ9wjXT0Pv5gLyhdHAFkHFJnCfVVfELE8e82uOoF0xEVQP6Kir+b4Q5yOvNAzZ61WbSU6A0A=="
},
"through2": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "bitfeed-client",
"version": "2.1.2",
"version": "2.1.3",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
@ -30,6 +30,7 @@
"sass": "^1.49.0",
"sirv-cli": "^1.0.14",
"svelte": "^3.44.3",
"svelte-preprocess": "^4.10.1"
"svelte-preprocess": "^4.10.1",
"svelte-select": "^4.4.7"
}
}

View File

@ -6,7 +6,7 @@
import Icon from '../components/Icon.svelte'
import closeIcon from '../assets/icon/cil-x-circle.svg'
import { shortBtcFormat, longBtcFormat, timeFormat, integerFormat } from '../utils/format.js'
import { exchangeRates, localCurrency } from '../stores.js'
import { exchangeRates, settings } from '../stores.js'
import { formatCurrency } from '../utils/fx.js'
const dispatch = createEventDispatcher()
@ -21,10 +21,10 @@
$: {
if (block && block.value) {
const rate = $exchangeRates[$localCurrency]
const rate = $exchangeRates[$settings.currency]
let local
if (rate && rate.last) {
local = formatCurrency($localCurrency, (block.value/100000000) * rate.last, { compact: true })
local = formatCurrency($settings.currency, (block.value/100000000) * rate.last, { compact: true })
} else {
local = null
}

View File

@ -5,7 +5,7 @@ import { onMount } from 'svelte'
import Overlay from '../components/Overlay.svelte'
import TierCard from './sponsor/TierCard.svelte'
import SatoshiSlider from './sponsor/SatoshiSlider.svelte'
import Pill from '../components/Pill.svelte'
import Pill from './util/Pill.svelte'
import Icon from '../components/Icon.svelte'
import boltIcon from '../assets/icon/cil-bolt-filled.svg'
import chainIcon from '../assets/icon/cil-link.svg'

View File

@ -2,25 +2,49 @@
import config from '../config.js'
import analytics from '../utils/analytics.js'
import SidebarMenuItem from '../components/SidebarMenuItem.svelte'
import { settings, nativeAntialias, exchangeRates, localCurrency, haveMessages } from '../stores.js'
import { settings, nativeAntialias, exchangeRates, haveMessages } from '../stores.js'
import { currencies } from '../utils/fx.js'
function toggle(setting) {
$settings[setting] = !$settings[setting]
analytics.trackEvent('settings', setting, $settings[setting] ? 'on' : 'off')
if (settingConfig[setting] != null && settingConfig[setting].valueType === 'bool') {
onChange(setting, !$settings[setting])
}
}
function onChange(setting, value) {
$settings[setting] = value
analytics.trackEvent('settings', setting, $settings[setting])
}
const currencyOptions = Object.keys(currencies).map(code => {
return {
value: code,
label: `${currencies[code].char} ${currencies[code].name}`,
tags: [code, currencies[code].name, ...currencies[code].countries]
}
})
let settingConfig = {
showNetworkStatus: {
label: 'Network Status'
label: 'Network Status',
valueType: 'bool'
},
darkMode: {
label: 'Dark Mode'
label: 'Dark Mode',
valueType: 'bool'
},
currency: {
label: 'Fiat Currency',
type: 'dropdown',
valueType: 'string',
options: currencyOptions
},
vbytes: {
label: 'Size by',
type: 'pill',
falseLabel: 'value',
trueLabel: 'vbytes'
trueLabel: 'vbytes',
valueType: 'bool'
},
}
$: {
@ -28,21 +52,24 @@ $: {
settingConfig.fancyGraphics = false
} else {
settingConfig.fancyGraphics = {
label: 'Fancy Graphics'
label: 'Fancy Graphics',
valueType: 'bool'
}
}
if (config.messagesEnabled && $haveMessages) {
settingConfig.showMessages = {
label: 'Message Bar'
label: 'Message Bar',
valueType: 'bool'
}
}
}
$: {
const rate = $exchangeRates[$localCurrency]
const rate = $exchangeRates[$settings.currency]
if (rate && rate.last) {
settingConfig.showFX = {
label: '₿ Price'
label: '₿ Price',
valueType: 'bool'
}
} else {
settingConfig.showFX = false
@ -58,6 +85,6 @@ function getSettings(setting) {
{#each Object.keys($settings) as setting (setting) }
{#if settingConfig[setting]}
<SidebarMenuItem {...getSettings(setting)} active={$settings[setting]} on:click={() => { toggle(setting) }} />
<SidebarMenuItem {...getSettings(setting)} value={$settings[setting]} on:click={() => { toggle(setting) }} on:input={(e) => { onChange(setting, e.detail)}} />
{/if}
{/each}

View File

@ -49,54 +49,22 @@ function showBlock () {
top: 20%;
left: 100%;
display: flex;
flex-direction: column;
flex-direction: column-reverse;
justify-content: flex-start;
align-items: flex-start;
}
</style>
<div class="sidebar">
<SidebarTab open={$sidebarToggle === 'settings'} on:click={() => {settings('settings')}} tooltip="Settings">
<span slot="tab" title="Settings">
<Icon icon={cogIcon} color="var(--bold-a)" />
</span>
<div slot="content">
<Settings />
</div>
</SidebarTab>
<SidebarTab open={$sidebarToggle === 'legend'} on:click={() => {settings('legend')}} tooltip="Key">
<span slot="tab">
<Icon icon={infoIcon} color="var(--bold-a)" />
</span>
<div slot="content">
<MempoolLegend />
</div>
</SidebarTab>
<SidebarTab open={$sidebarToggle === 'contact'} on:click={() => {settings('contact')}} tooltip="Contact">
<span slot="tab">
<Icon icon={atIcon} color="var(--bold-a)" />
</span>
<div slot="content">
<ContactTab />
</div>
</SidebarTab>
<SidebarTab on:click={() => openOverlay('about')} tooltip="About">
<span slot="tab">
<Icon icon={questionIcon} color="var(--bold-a)" />
</span>
</SidebarTab>
{#if $haveSupporters }
<SidebarTab on:click={() => openOverlay('supporters')} tooltip="Supporters">
<!-- displayed in reverse order, to preserve proper z-index layering -->
{#if blockHidden }
<SidebarTab on:click={() => showBlock()} tooltip="Show Latest Block">
<span slot="tab">
<Icon icon={peopleIcon} color="var(--bold-a)" />
</span>
</SidebarTab>
{/if}
{#if config.donationsEnabled }
<SidebarTab on:click={() => openOverlay('donation')} tooltip="Donate">
<span slot="tab">
<Icon icon={giftIcon} color="var(--bold-a)" />
<Icon icon={gridIcon} color="var(--bold-a)" />
</span>
<div slot="content">
<MempoolLegend />
</div>
</SidebarTab>
{/if}
{#if config.dev && config.debug}
@ -109,14 +77,47 @@ function showBlock () {
</div>
</SidebarTab>
{/if}
{#if blockHidden }
<SidebarTab on:click={() => showBlock()} tooltip="Show Latest Block">
{#if config.donationsEnabled }
<SidebarTab on:click={() => openOverlay('donation')} tooltip="Donate">
<span slot="tab">
<Icon icon={gridIcon} color="var(--bold-a)" />
<Icon icon={giftIcon} color="var(--bold-a)" />
</span>
<div slot="content">
<MempoolLegend />
</div>
</SidebarTab>
{/if}
{#if $haveSupporters }
<SidebarTab on:click={() => openOverlay('supporters')} tooltip="Supporters">
<span slot="tab">
<Icon icon={peopleIcon} color="var(--bold-a)" />
</span>
</SidebarTab>
{/if}
<SidebarTab on:click={() => openOverlay('about')} tooltip="About">
<span slot="tab">
<Icon icon={questionIcon} color="var(--bold-a)" />
</span>
</SidebarTab>
<SidebarTab open={$sidebarToggle === 'contact'} on:click={() => {settings('contact')}} tooltip="Contact">
<span slot="tab">
<Icon icon={atIcon} color="var(--bold-a)" />
</span>
<div slot="content">
<ContactTab />
</div>
</SidebarTab>
<SidebarTab open={$sidebarToggle === 'legend'} on:click={() => {settings('legend')}} tooltip="Key">
<span slot="tab">
<Icon icon={infoIcon} color="var(--bold-a)" />
</span>
<div slot="content">
<MempoolLegend />
</div>
</SidebarTab>
<SidebarTab open={$sidebarToggle === 'settings'} on:click={() => {settings('settings')}} tooltip="Settings">
<span slot="tab" title="Settings">
<Icon icon={cogIcon} color="var(--bold-a)" />
</span>
<div slot="content">
<Settings />
</div>
</SidebarTab>
</div>

View File

@ -1,12 +1,32 @@
<script>
import Toggle from '../components/Toggle.svelte'
import Pill from '../components/Pill.svelte'
import Toggle from './util/Toggle.svelte'
import Pill from './util/Pill.svelte'
import Select from 'svelte-select'
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
export let active = false
export let value = false
export let type = 'toggle'
export let label = ''
export let falseLabel
export let trueLabel
export let options
let selectedOption
$: {
if (options && value) {
selectedOption = options.find(option => option.value === value)
}
}
function filterSelectItems (label, filterText, option) {
return [label, ...option.tags].join(' | ').toLowerCase().includes(filterText.toLowerCase())
}
function onSelect (e) {
selectedOption = e.detail
dispatch('input', selectedOption.value )
}
</script>
<style type="text/scss">
@ -31,15 +51,33 @@ export let trueLabel
.label {
margin-right: 0.5em;
}
.select {
width: 240px;
flex-shrink: 0;
color: var(--palette-a);
--inputColor: var(--palette-a);
--itemColor: var(--palette-a);
--itemHoverColor: var(--palette-a);
--borderFocusColor: var(--palette-good);
:global(.selectContainer) {
border-width: 3px;
}
}
}
</style>
<div class="sidebar-menu-item" class:active={active} on:click>
<div class="sidebar-menu-item" class:active={value} on:click>
{#if type === 'pill'}
<span class="label">{ label }</span>
<Pill active={active} left={falseLabel} right={trueLabel} />
<Pill active={value} left={falseLabel} right={trueLabel} />
{:else if type === 'dropdown'}
<div class="select">
<Select items={ options } value={selectedOption} isSearchable={true} isClearable={false} itemFilter={filterSelectItems} placeholder={label} on:select={onSelect} showIndicator={true} />
</div>
{:else}
<span class="label">{ label }</span>
<Toggle active={active} />
<Toggle active={value} />
{/if}
</div>

View File

@ -4,6 +4,7 @@ import { fly } from 'svelte/transition'
export let open = false
export let tooltip = null
let entered = false
let contentElement
let contentSlotElement
@ -20,6 +21,19 @@ async function updateContentHeight (isOpen) {
$: updateContentHeight(open)
$: {
if (open) setTimeout(afterEnter, 400)
else beforeExit()
}
function afterEnter () {
entered = true
}
function beforeExit () {
entered = false
}
</script>
<style type="text/scss">
@ -73,6 +87,12 @@ $: updateContentHeight(open)
&.open {
transform: translateX(-100%);
&.active {
.sidebar-content {
overflow: visible;
}
}
}
}
@ -95,7 +115,11 @@ $: updateContentHeight(open)
}
</style>
<div class="sidebar-tab" class:open={open} transition:fly={{ x: 30, duration: 1000 }}>
<div
class="sidebar-tab"
class:open={open}
class:active={entered}
>
<button class="tab-button" on:click title={tooltip}>
<slot name="tab">
??

View File

@ -1,6 +1,6 @@
<script>
import { longBtcFormat, integerFormat } from '../utils/format.js'
import { exchangeRates, localCurrency } from '../stores.js'
import { exchangeRates, settings } from '../stores.js'
import { formatCurrency } from '../utils/fx.js'
export let tx
@ -19,10 +19,10 @@ let formattedLocalValue
$: {
if (tx && tx.value) {
const rate = $exchangeRates[$localCurrency]
const rate = $exchangeRates[$settings.currency]
let local
if (rate && rate.last) {
formattedLocalValue = formatCurrency($localCurrency, (tx.value/100000000) * rate.last, { compact: true })
formattedLocalValue = formatCurrency($settings.currency, (tx.value/100000000) * rate.last, { compact: true })
} else {
formattedLocalValue = null
}

View File

@ -13,7 +13,7 @@
import SupportersOverlay from '../components/SupportersOverlay.svelte'
import Alerts from '../components/alert/Alerts.svelte'
import { integerFormat } from '../utils/format.js'
import { exchangeRates, localCurrency, lastBlockId, haveSupporters } from '../stores.js'
import { exchangeRates, lastBlockId, haveSupporters, sidebarToggle } from '../stores.js'
import { formatCurrency } from '../utils/fx.js'
import config from '../config.js'
@ -133,9 +133,9 @@
const fxColor = 'good'
let fxLabel = ''
$: {
const rate = $exchangeRates[$localCurrency]
const rate = $exchangeRates[$settings.currency]
if (rate && rate.last)
fxLabel = formatCurrency($localCurrency, rate.last)
fxLabel = formatCurrency($settings.currency, rate.last)
}
const debounce = v => {
@ -293,6 +293,7 @@
.stat-counter, .fx-ticker {
margin-top: 5px;
white-space: nowrap;
cursor: pointer;
&.bad {
color: var(--palette-bad);
@ -419,7 +420,7 @@
<div class="status">
<div class="row">
{#if $settings.showFX && fxLabel }
<span class="fx-ticker {fxColor}">{ fxLabel }</span>
<span class="fx-ticker {fxColor}" on:click={() => { $sidebarToggle = 'settings'}}>{ fxLabel }</span>
{/if}
</div>
<div class="row">

View File

@ -33,6 +33,7 @@ export let rightDisabled = false
flex-grow: 1;
padding: .5em 1em;
background: var(--palette-e);
text-align: center;
&:first-child {
border-top-left-radius: .5em;

View File

@ -1,5 +1,5 @@
function getInjectedEnv (key, fallback) {
if (window.injected && window.injected[key] != null) {
if (window.injected && window.injected[key] != null && window.injected[key] != "") {
return window.injected[key]
}
return fallback

View File

@ -1,7 +1,7 @@
import { writable, derived } from 'svelte/store'
import { makePollStore } from './utils/pollStore.js'
import { symbols } from './utils/fx.js'
import LocaleCurrency from 'locale-currency'
import { currencies } from './utils/fx.js'
import config from './config.js'
function createCounter () {
@ -26,7 +26,13 @@ function createCachedDict (namespace, defaultValues) {
// load from local storage
Object.keys(initial).forEach(field => {
const val = localStorage.getItem(`${namespace}-${field}`)
if (val != null) initial[field] = JSON.parse(val)
if (val != null) {
try {
initial[field] = JSON.parse(val)
} catch (e) {
initial[field] = val
}
}
})
const { subscribe, set, update } = writable(initial)
@ -89,9 +95,13 @@ export const blockAreaSize = writable(0)
export const settingsOpen = writable(false)
let localeCurrencyCode = LocaleCurrency.getCurrency(navigator.language)
if (!currencies[localeCurrencyCode]) localeCurrencyCode = 'USD'
export const settings = createCachedDict('settings', {
darkMode: true,
showNetworkStatus: true,
currency: localeCurrencyCode,
showFX: true,
vbytes: false,
fancyGraphics: true,
@ -111,8 +121,3 @@ export const nativeAntialias = writable(false)
const newVisitor = !localStorage.getItem('seen-welcome-msg')
// export const overlay = writable(newVisitor ? 'about' : null)
export const overlay = writable(null)
let currencyCode = LocaleCurrency.getCurrency(navigator.language)
console.log('LOCALE: ', navigator.language, currencyCode)
if (!symbols[currencyCode]) currencyCode = 'USD'
export const localCurrency = writable(currencyCode)

View File

@ -1,119 +1,242 @@
export const symbols = {
export const currencies = {
AUD: {
char: "AU$",
pre: true
char: 'AU$',
pre: true,
name: 'Australian Dollar',
countries: [ 'Australia' ],
dp: 2,
code: 'AUD'
},
BRL: {
char: "R$",
pre: true
char: 'R$',
pre: true,
name: 'Brazilian Real',
countries: [ 'Brazil' ],
dp: 2,
code: 'BRL'
},
CAD: {
char: "CA$",
pre: true
char: 'CA$',
pre: true,
name: 'Canadian Dollar',
countries: [ 'Canada' ],
dp: 2,
code: 'CAD'
},
CHF: {
char: "fr.",
pre: false
char: 'fr.',
pre: false,
name: 'Swiss Franc',
countries: [ 'Switzerland' ],
dp: 2,
code: 'CHF'
},
CLP: {
char: "CLP$",
pre: true
char: 'CLP$',
pre: true,
name: 'Chilean Peso',
countries: [ 'Chile' ],
dp: 2,
code: 'CLP'
},
CNY: {
char: "¥",
pre: true
char: '¥',
pre: true,
name: 'Chinese Yuan',
countries: [ 'China' ],
dp: 2,
code: 'CNY'
},
CZK: {
char: "Kč",
pre: false
char: 'Kč',
pre: false,
name: 'Czech Koruna',
countries: [ 'Czechia' ],
dp: 2,
code: 'CZK'
},
DKK: {
char: "kr.",
pre: false
char: 'kr.',
pre: false,
name: 'Danish Krone',
countries: [ 'Denmark' ],
dp: 2,
code: 'DKK'
},
EUR: {
char: "€",
pre: true
char: '€',
pre: true,
name: 'Euro',
countries: [
'European Union', 'Austria',
'Belgium', 'Cyprus',
'Estonia', 'Finland',
'France', 'Germany',
'Greece', 'Ireland',
'Italy', 'Latvia',
'Lithuania', 'Luxembourg',
'Malta', 'Netherlands',
'Portugal', 'Slovakia',
'Slovenia', 'Spain',
'Andorra', 'Monaco',
'San Marino', 'Vatican City',
'Kosovo', 'Montenegro'
],
dp: 2,
code: 'EUR'
},
GBP: {
char: "£",
pre: true
char: '£',
pre: true,
name: 'Pound Sterling',
countries: [ 'United Kingdom' ],
dp: 2,
code: 'GBP'
},
HKD: {
char: "HK$",
pre: true
char: 'HK$',
pre: true,
name: 'Hong Kong Dollar',
countries: [ 'Hong Kong' ],
dp: 2,
code: 'HKD'
},
HRK: {
char: "kn",
pre: false
char: 'kn',
pre: false,
name: 'Croatian Kuna',
countries: [ 'Croatia' ],
dp: 2,
code: 'HRK'
},
HUF: {
char: "Ft",
pre: false
char: 'Ft',
pre: false,
name: 'Hungarian Forint',
countries: [ 'Hungary' ],
dp: 2,
code: 'HUF'
},
INR: {
char: "₹",
pre: true
char: '₹',
pre: true,
name: 'Indian Rupee',
countries: [ 'India', 'Bhutan' ],
dp: 2,
code: 'INR'
},
ISK: {
char: "kr",
pre: false
char: 'kr',
pre: false,
name: 'Icelandic Króna',
countries: [ 'Iceland' ],
dp: 2,
code: 'ISK'
},
JPY: {
char: "¥",
pre: true
char: '¥',
pre: true,
name: 'Japanese Yen',
countries: [ 'Japan' ],
dp: 2,
code: 'JPY'
},
KRW: {
char: "₩",
pre: true
char: '₩',
pre: true,
name: 'Korean Won',
countries: [ 'South Korea' ],
dp: 2,
code: 'KRW'
},
NZD: {
char: "NZ$",
pre: true
char: 'NZ$',
pre: true,
name: 'New Zealand Dollar',
countries: [ 'New Zealand' ],
dp: 2,
code: 'NZD'
},
PLN: {
char: "zł",
pre: false
char: 'zł',
pre: false,
name: 'Polish Złoty',
countries: [ 'Poland' ],
dp: 2,
code: 'PLN'
},
RON: {
char: "L",
pre: false
char: 'L',
pre: false,
name: 'Romanian Leu',
countries: [ 'Romania' ],
dp: 2,
code: 'RON'
},
RUB: {
char: "₽",
pre: false
char: '₽',
pre: false,
name: 'Russian Ruble',
countries: [ 'Russia' ],
dp: 2,
code: 'RUB'
},
SEK: {
char: "kr",
pre: false
char: 'kr',
pre: false,
name: 'Swedish Krona',
countries: [ 'Sweden' ],
dp: 2,
code: 'SEK'
},
SGD: {
char: "S$",
pre: true
char: 'S$',
pre: true,
name: 'Singapore Dollar',
countries: [ 'Singapore' ],
dp: 2,
code: 'SGD'
},
THB: {
char: "฿",
pre: true
char: '฿',
pre: true,
name: 'Thai Baht',
countries: [ 'Thailand' ],
dp: 2,
code: 'THB'
},
TRY: {
char: "₺",
pre: true
char: '₺',
pre: true,
name: 'Turkish Lira',
countries: [ 'Turkey' ],
dp: 2,
code: 'TRY'
},
TWD: {
char: "圓",
pre: true
char: '圓',
pre: true,
name: 'New Taiwan Dollar',
countries: [ 'Taiwan' ],
dp: 2,
code: 'TWD'
},
USD: {
char: "$",
pre: true
char: '$',
pre: true,
name: 'US Dollar',
countries: [ 'United States', 'USA' ],
dp: 2,
code: 'USD'
}
}
export function formatCurrency (code, amount, params) {
// Support for Intl.NumberFormat not quite there yet for all currencies/locales
// const format = new Intl.NumberFormat(currency.locale, {currency: currency.code, currencyDisplay: "symbol", style: "currency"})
// return format.format(amount)
const currency = symbols[code] || symbols.USD
const currency = currencies[code] || currencies.USD
let parts = [currency.char]
if (params && params.compact) {
let compacted = amount
@ -129,7 +252,7 @@ export function formatCurrency (code, amount, params) {
const amountPart = amount < 1000 ? amount.toPrecision(precision) : Math.round(amount)
parts.push(`${amountPart}${['','K','M','B','T'][suffixIndex]}`)
} else {
parts.push(amount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }))
parts.push(amount.toLocaleString(undefined, { minimumFractionDigits: currency.dp, maximumFractionDigits: currency.dp }))
}
if (!currency.pre) parts = parts.reverse()
return parts.join(' ')

View File

@ -4,7 +4,7 @@ defmodule BitcoinStream.MixProject do
def project do
[
app: :bitcoin_stream,
version: "2.1.2",
version: "2.1.3",
elixir: "~> 1.10",
start_permanent: Mix.env() == :prod,
deps: deps(),