diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f22d2..00061c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog -## v0.1.0 +## v0.1.2 + +Fix crash on unknown asset +Added Edit asset details +Added Delete asset +Added Copy receive address button + +## v0.1.1 * Initial Release diff --git a/package-lock.json b/package-lock.json index 365a62d..ff4c4c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "pollen-wallet", - "version": "0.1.1", + "version": "0.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index dbc6aa9..27f9e3a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pollen-wallet", "description": "IOTA Pollen Wallet", - "version": "0.1.1", + "version": "0.1.2", "author": "Martyn Janes ", "repository": { "type": "git", diff --git a/src/app/components/Wallet.tsx b/src/app/components/Wallet.tsx index 988c16a..f978f46 100644 --- a/src/app/components/Wallet.tsx +++ b/src/app/components/Wallet.tsx @@ -1,6 +1,8 @@ import classNames from "classnames"; import React, { Component, ReactNode } from "react"; import { ServiceFactory } from "../../factories/serviceFactory"; +import { ClipboardHelper } from "../../helpers/clipboardHelper"; +import { IWalletAsset } from "../../models/IWalletAsset"; import { IWalletService } from "../../models/services/IWalletService"; import Spinner from "./Spinner"; import { WalletProps } from "./WalletProps"; @@ -253,8 +255,14 @@ class Wallet extends Component {
-
+

Addresses

+ {this.state.newAssetName === undefined && ( + + )}
{(!this.state.addresses || this.state.addresses.length === 0) && ( @@ -294,6 +302,7 @@ class Wallet extends Component { onClick={() => this.setState({ newAssetName: "", newAssetSymbol: "", + newAssetColor: "", newAssetAmount: "100", errorNewAsset: "" })} @@ -322,7 +331,7 @@ class Wallet extends Component {
Symbol -
+
{ })} />
-
- Amount -
-
- this.setState({ - newAssetAmount: e.target.value - })} - /> -
+ {!this.state.newAssetColor && ( + +
+ Amount +
+
+ this.setState({ + newAssetAmount: e.target.value + })} + /> +
+
+ )}
+ + ))} @@ -438,7 +476,8 @@ class Wallet extends Component {
- )} + ) + } ); } @@ -505,17 +544,25 @@ class Wallet extends Component { async () => { if (this.state.newAssetName && this.state.newAssetSymbol) { try { - await this._walletService.createAsset( - this.state.newAssetName, - this.state.newAssetSymbol, - BigInt(parseInt(this.state.newAssetAmount, 10)) - ); + if (this.state.newAssetColor) { + await this._walletService.updateAsset( + this.state.newAssetColor, + this.state.newAssetName, + this.state.newAssetSymbol); + } else { + await this._walletService.createAsset( + this.state.newAssetName, + this.state.newAssetSymbol, + BigInt(parseInt(this.state.newAssetAmount, 10)) + ); + } this.setState({ isBusyNewAsset: false, newAssetAmount: "100", newAssetName: undefined, - newAssetSymbol: undefined + newAssetSymbol: undefined, + newAssetColor: undefined }); } catch (err) { this.setState({ @@ -528,6 +575,33 @@ class Wallet extends Component { ); } + /** + * Delete an asset. + * @param color The color of the asset to delete. + */ + private deleteAsset(color: string): void { + this.setState( + { + isBusyNewAsset: true, + errorNewAsset: "" + }, + async () => { + try { + await this._walletService.deleteAsset(color); + + this.setState({ + isBusyNewAsset: false + }); + } catch (err) { + this.setState({ + errorNewAsset: err.message, + isBusyNewAsset: false + }); + } + } + ); + } + /** * Send funds to address. */ @@ -562,6 +636,26 @@ class Wallet extends Component { } ); } + + /** + * Does the asset have a balance. + * @param asset The asset to check. + * @returns True if the asset has a balance. + */ + private assetHasBalance(asset: IWalletAsset): boolean { + if (this.state.balances) { + return this.state.balances.find(b => b.asset.color === asset.color) === undefined; + } + + return false; + } + + /** + * Copy the receive address to the clipboard. + */ + private copyReceiveAddress(): void { + ClipboardHelper.copy(this.state.receiveAddress); + } } export default Wallet; diff --git a/src/app/components/WalletState.ts b/src/app/components/WalletState.ts index 5eb8b73..4104233 100644 --- a/src/app/components/WalletState.ts +++ b/src/app/components/WalletState.ts @@ -64,6 +64,11 @@ export interface WalletState { */ newAssetSymbol?: string; + /** + * New wallet asset color. + */ + newAssetColor?: string; + /** * The amount of tokens to create. */ diff --git a/src/helpers/clipboardHelper.ts b/src/helpers/clipboardHelper.ts new file mode 100644 index 0000000..dfc757a --- /dev/null +++ b/src/helpers/clipboardHelper.ts @@ -0,0 +1,46 @@ +/** + * Helper methods for clipbaord. + */ +export class ClipboardHelper { + /** + * Copy the text to the clipboard. + * @param text The text to copy. + * @returns True id the text was copied. + */ + public static copy(text: string | undefined): boolean { + if (text !== undefined && text !== null) { + try { + const textArea = document.createElement("textarea"); + + // Prevent zooming on iOS + textArea.style.fontSize = "12pt"; + // Reset box model + textArea.style.border = "0"; + textArea.style.padding = "0"; + textArea.style.margin = "0"; + // Move element out of screen horizontally + textArea.style.position = "absolute"; + textArea.style.left = "-9999px"; + // Move element to the same position vertically + const yPosition = window.pageYOffset || document.documentElement.scrollTop; + textArea.style.top = `${yPosition}px`; + + textArea.setAttribute("readonly", ""); + textArea.value = text; + + document.body.append(textArea); + + textArea.select(); + document.execCommand("Copy"); + textArea.remove(); + + return true; + } catch { + // Not much we can do + return false; + } + } else { + return false; + } + } +} diff --git a/src/models/services/IWalletService.ts b/src/models/services/IWalletService.ts index f1b7138..59e191a 100644 --- a/src/models/services/IWalletService.ts +++ b/src/models/services/IWalletService.ts @@ -66,6 +66,20 @@ export interface IWalletService { */ createAsset(name: string, symbol: string, amount: bigint): Promise; + /** + * Update an asset. + * @param name The color of the asset to update. + * @param name The name for the updated asset. + * @param symbol The symbol for the updated asset. + */ + updateAsset(color: string, name: string, symbol: string): Promise; + + /** + * Delete an asset. + * @param name The color of the asset to delete. + */ + deleteAsset(color: string): Promise; + /** * Send funds to an address. * @param address The address to send the funds to. diff --git a/src/scss/buttons.scss b/src/scss/buttons.scss index 114c26c..a08ac62 100644 --- a/src/scss/buttons.scss +++ b/src/scss/buttons.scss @@ -30,14 +30,14 @@ button { } &.button--danger { - background-color: $danger; border-color: $danger; + background-color: $danger; color: $white; } &.button--secondary { - background-color: $white; border-color: $dark-green; + background-color: $white; color: $dark-green; } } diff --git a/src/scss/tables.scss b/src/scss/tables.scss index 85e8adf..df4bb2a 100644 --- a/src/scss/tables.scss +++ b/src/scss/tables.scss @@ -41,6 +41,7 @@ table { &.break { word-break: break-all; + // sass-lint:disable no-duplicate-properties word-break: break-word; } } diff --git a/src/services/walletService.ts b/src/services/walletService.ts index 218c8fe..30448d6 100644 --- a/src/services/walletService.ts +++ b/src/services/walletService.ts @@ -200,6 +200,53 @@ export class WalletService implements IWalletService { } } + /** + * Update an asset. + * @param name The color of the asset to update. + * @param name The name for the updated asset. + * @param symbol The symbol for the updated asset. + */ + public async updateAsset(color: string, name: string, symbol: string): Promise { + if (this._wallet) { + const asset = this._wallet.assets.find(a => a.color === color); + if (asset) { + asset.name = name; + asset.symbol = symbol; + } else { + this._wallet.assets.push({ + color, + name, + symbol, + precision: 0 + }); + } + + await this.save(); + } + } + + /** + * Delete an asset. + * @param name The color of the asset to delete. + */ + public async deleteAsset(color: string): Promise { + if (this._wallet) { + let hasBalance = false; + if (this._balances) { + hasBalance = this._balances.findIndex(b => b.asset.color === color) >= 0; + } + + if (!hasBalance) { + const assetIdx = this._wallet.assets.findIndex(a => a.color === color); + if (assetIdx >= 0) { + this._wallet.assets.splice(assetIdx, 1); + } + } + + await this.save(); + } + } + /** * Send funds to an address. * @param address The address to send the funds to. @@ -630,7 +677,7 @@ export class WalletService implements IWalletService { */ private async doUpdates(): Promise { this._unspentOutputs = await this.getUnspentOutputs(); - this.calculateAddressesAndBalances(); + await this.calculateAddressesAndBalances(); for (const id in this._subscribers) { this._subscribers[id](); @@ -644,20 +691,21 @@ export class WalletService implements IWalletService { private async initialiseWallet(): Promise { this._unspentOutputs = await this.getUnspentOutputs(); this._spentOutputTransactions = []; - this.calculateAddressesAndBalances(); + await this.calculateAddressesAndBalances(); } /** * Calculate balances from the address outputs. * @param addressOutputs The address outputs to calculate balance from. */ - private calculateAddressesAndBalances(): void { + private async calculateAddressesAndBalances(): Promise { if (this._wallet && this._unspentOutputs) { this._balances = []; this._addresses = []; const colorMap: { [id: string]: IWalletBalance } = {}; const addressMap: { [id: string]: IWalletAddress } = {}; const assetsMap: { [id: string]: IWalletAsset } = {}; + const addedAssets: IWalletAsset[] = []; for (let i = 0; i <= this._wallet.lastAddressIndex; i++) { const addr = Seed.generateAddress(Base58.decode(this._wallet.seed), BigInt(i)); @@ -685,6 +733,19 @@ export class WalletService implements IWalletService { for (const output of addressOutput.outputs) { for (const balance of output.balances) { if (!colorMap[balance.color]) { + if (!assetsMap[balance.color]) { + // We have received an asset we don't recognise + // so create a dummy entry, in the future this + // will be looked up from a central registry + const asset = { + color: balance.color, + name: "Unknown", + symbol: "", + precision: 0 + }; + addedAssets.push(asset); + assetsMap[balance.color] = asset; + } colorMap[balance.color] = { asset: assetsMap[balance.color], confirmed: BigInt(0), @@ -706,6 +767,11 @@ export class WalletService implements IWalletService { if (!lastUnspent) { this.newReceiveAddress(); } + + if (addedAssets.length > 0) { + this._wallet.assets = this._wallet.assets.concat(addedAssets); + await this.save(); + } } }