Added i18n support.

Refactor receive page.
This commit is contained in:
Mikunj 2019-04-08 13:50:54 +10:00
parent 734444a8e4
commit 6ee9827ae1
45 changed files with 1209 additions and 1286 deletions

View File

@ -258,9 +258,20 @@ export class Backend {
})
if (filename) {
let base64Data = params.img.replace(/^data:image\/png;base64,/, "")
let binaryData = new Buffer(base64Data, "base64").toString("binary")
let binaryData = Buffer.from(base64Data, "base64").toString("binary")
fs.writeFile(filename, binaryData, "binary", (err) => {
if (err) { this.send("show_notification", { type: "negative", message: "Error saving " + params.type, timeout: 2000 }) } else { this.send("show_notification", { message: params.type + " saved to " + filename, timeout: 2000 }) }
if (err) {
this.send("show_notification", {
type: "negative",
i18n: ["notification.errors.errorSavingItem", { item: params.type }],
timeout: 2000
})
} else {
this.send("show_notification", {
i18n: ["notification.positive.itemSaved", { item: params.type, filename }],
timeout: 2000
})
}
})
}
break
@ -326,11 +337,11 @@ export class Backend {
// Check to see if data and wallet directories exist
const dirs_to_check = [{
path: data_dir,
error: "Data storge path not found"
error: "notification.errors.dataPathNotFound"
},
{
path: wallet_data_dir,
error: "Wallet data storge path not found"
error: "notification.errors.walletPathNotFound"
}]
for (const dir of dirs_to_check) {
@ -338,7 +349,7 @@ export class Backend {
if (!fs.existsSync(dir.path)) {
this.send("show_notification", {
type: "negative",
message: `Error: ${dir.error}`,
i18n: dir.error,
timeout: 2000
})
@ -390,13 +401,13 @@ export class Backend {
this.send("show_notification", {
type: "warning",
textColor: "black",
message: "Warning: Could not access remote node, switching to local only",
i18n: "notification.warnings.usingLocalNode",
timeout: 2000
})
} else {
this.send("show_notification", {
type: "negative",
message: "Error: Could not access remote node, please try another remote node",
i18n: "notification.errors.cannotAccessRemoteNode",
timeout: 2000
})
@ -414,7 +425,7 @@ export class Backend {
if (data.net_type && data.net_type !== net_type) {
this.send("show_notification", {
type: "negative",
message: "Error: Remote node is using a different nettype",
i18n: "notification.errors.differentNetType",
timeout: 2000
})
@ -481,9 +492,17 @@ export class Backend {
// eslint-disable-next-line
}).catch(error => {
if (this.config_data.daemons[net_type].type == "remote") {
this.send("show_notification", { type: "negative", message: "Remote daemon cannot be reached", timeout: 3000 })
this.send("show_notification", {
type: "negative",
i18n: "notification.errors.remoteCannotBeReached",
timeout: 3000
})
} else {
this.send("show_notification", { type: "negative", message: error.message, timeout: 3000 })
this.send("show_notification", {
type: "negative",
message: error.message,
timeout: 3000
})
}
this.send("set_app_data", {
status: {

View File

@ -215,12 +215,19 @@ export class Daemon {
this.sendRPC("set_bans", params).then((data) => {
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
this.sendGateway("show_notification", { type: "negative", message: "Error banning peer", timeout: 2000 })
this.sendGateway("show_notification", {
type: "negative",
i18n: "notification.errors.banningPeer",
timeout: 2000
})
return
}
let end_time = new Date(Date.now() + seconds * 1000).toLocaleString()
this.sendGateway("show_notification", { message: "Banned " + host + " until " + end_time, timeout: 2000 })
this.sendGateway("show_notification", {
i18n: ["notification.positive.bannedPeer", { host, time: end_time }],
timeout: 2000
})
// Send updated peer and ban list
this.heartbeatSlowAction()

View File

@ -373,7 +373,11 @@ export class WalletRPC {
this.sendGateway("reset_wallet_error")
this.backend.daemon.timestampToHeight(timestamp).then((height) => {
if (height === false) { this.sendGateway("set_wallet_error", { status: { code: -1, message: "Invalid restore date" } }) } else { this.restoreWallet(filename, password, seed, "height", height) }
if (height === false) {
this.sendGateway("set_wallet_error", { status: { code: -1, i18n: "notification.errors.invalidRestoreDate" } })
} else {
this.restoreWallet(filename, password, seed, "height", height)
}
})
return
}
@ -414,7 +418,11 @@ export class WalletRPC {
timestamp = timestamp - (timestamp % 86400000) - 86400000
this.backend.daemon.timestampToHeight(timestamp).then((height) => {
if (height === false) { this.sendGateway("set_wallet_error", { status: { code: -1, message: "Invalid restore date" } }) } else { this.restoreViewWallet(filename, password, address, viewkey, "height", height) }
if (height === false) {
this.sendGateway("set_wallet_error", { status: { code: -1, i18n: "notification.errors.invalidRestoreDate" } })
} else {
this.restoreViewWallet(filename, password, address, viewkey, "height", height)
}
})
return
}
@ -458,12 +466,12 @@ export class WalletRPC {
}
if (!fs.existsSync(import_path)) {
this.sendGateway("set_wallet_error", { status: { code: -1, message: "Invalid wallet path" } })
this.sendGateway("set_wallet_error", { status: { code: -1, i18n: "notification.errors.invalidWalletPath" } })
} else {
let destination = path.join(this.wallet_dir, filename)
if (fs.existsSync(destination) || fs.existsSync(destination + ".keys")) {
this.sendGateway("set_wallet_error", { status: { code: -1, message: "Wallet with name already exists" } })
this.sendGateway("set_wallet_error", { status: { code: -1, i18n: "notification.errors.walletAlreadyExists" } })
return
}
@ -474,7 +482,7 @@ export class WalletRPC {
fs.copySync(import_path + ".keys", destination + ".keys", fs.constants.COPYFILE_EXCL)
}
} catch (e) {
this.sendGateway("set_wallet_error", { status: { code: -1, message: "Failed to copy wallet" } })
this.sendGateway("set_wallet_error", { status: { code: -1, i18n: "notification.errors.copyWalletFail" } })
return
}
@ -497,7 +505,7 @@ export class WalletRPC {
this.finalizeNewWallet(filename)
}).catch(() => {
this.sendGateway("set_wallet_error", { status: { code: -1, message: "An unknown error occured" } })
this.sendGateway("set_wallet_error", { status: { code: -1, i18n: "notification.errors.unknownError" } })
})
}
}
@ -706,7 +714,7 @@ export class WalletRPC {
this.sendGateway("set_wallet_data", wallet)
} else {
this.closeWallet().then(() => {
this.sendGateway("set_wallet_error", { status: { code: -1, message: "Failed to open wallet. Please try again." } })
this.sendGateway("set_wallet_error", { status: { code: -1, i18n: "notification.errors.failedWalletOpen" } })
})
}
}
@ -719,7 +727,7 @@ export class WalletRPC {
this.sendGateway("set_snode_status", {
stake: {
code: -1,
message: "Internal error",
i18n: "notification.errors.internalError",
sending: false
}
})
@ -729,7 +737,7 @@ export class WalletRPC {
this.sendGateway("set_snode_status", {
stake: {
code: -1,
message: "Invalid password",
i18n: "notification.errors.invalidPassword",
sending: false
}
})
@ -758,7 +766,7 @@ export class WalletRPC {
this.sendGateway("set_snode_status", {
stake: {
code: 0,
message: "Successfully staked",
i18n: "notification.positive.stakeSuccess",
sending: false
}
})
@ -772,7 +780,7 @@ export class WalletRPC {
this.sendGateway("set_snode_status", {
registration: {
code: -1,
message: "Internal error",
i18n: "notification.errors.internalError",
sending: false
}
})
@ -783,7 +791,7 @@ export class WalletRPC {
this.sendGateway("set_snode_status", {
registration: {
code: -1,
message: "Invalid password",
i18n: "notification.errors.invalidPassword",
sending: false
}
})
@ -808,7 +816,7 @@ export class WalletRPC {
this.sendGateway("set_snode_status", {
registration: {
code: 0,
message: "Successfully registered service node",
i18n: "notification.positive.registerServiceNodeSuccess",
sending: false
}
})
@ -817,11 +825,12 @@ export class WalletRPC {
}
unlockStake (password, service_node_key, confirmed = false) {
const sendError = (message) => {
const sendError = (message, i18n = true) => {
const key = i18n ? "i18n" : "message"
this.sendGateway("set_snode_status", {
unlock: {
code: -1,
message,
[key]: message,
sending: false
}
})
@ -830,12 +839,12 @@ export class WalletRPC {
// Unlock code 0 means success, 1 means can unlock, -1 means error
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
if (err) {
sendError("Internal error")
sendError("notification.errors.internalError")
return
}
if (!this.isValidPasswordHash(password_hash)) {
sendError("Invalid password")
sendError("notification.errors.invalidPassword")
return
}
@ -845,12 +854,12 @@ export class WalletRPC {
}).then(data => {
if (data.hasOwnProperty("error")) {
const error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1)
sendError(error)
sendError(error, false)
return null
}
if (!data.hasOwnProperty("result")) {
sendError("Failed to unlock service node")
sendError("notification.errors.failedServiceNodeUnlock")
return null
}
@ -891,7 +900,7 @@ export class WalletRPC {
if (err) {
this.sendGateway("set_tx_status", {
code: -1,
message: "Internal error",
i18n: "notification.errors.internalError",
sending: false
})
return
@ -899,7 +908,7 @@ export class WalletRPC {
if (!this.isValidPasswordHash(password_hash)) {
this.sendGateway("set_tx_status", {
code: -1,
message: "Invalid password",
i18n: "notification.errors.invalidPassword",
sending: false
})
return
@ -938,7 +947,7 @@ export class WalletRPC {
this.sendGateway("set_tx_status", {
code: 0,
message: "Transaction successfully sent",
i18n: "notification.positive.sendSuccess",
sending: false
})
@ -968,7 +977,7 @@ export class WalletRPC {
if (err) {
this.sendGateway("set_wallet_data", {
secret: {
mnemonic: "Internal error",
mnemonic: "notification.errors.internalError",
spend_key: -1,
view_key: -1
}
@ -978,7 +987,7 @@ export class WalletRPC {
if (!this.isValidPasswordHash(password_hash)) {
this.sendGateway("set_wallet_data", {
secret: {
mnemonic: "Invalid password",
mnemonic: "notification.errors.invalidPassword",
spend_key: -1,
view_key: -1
}
@ -1225,11 +1234,11 @@ export class WalletRPC {
exportKeyImages (password, filename = null) {
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
if (err) {
this.sendGateway("show_notification", { type: "negative", message: "Internal error", timeout: 2000 })
this.sendGateway("show_notification", { type: "negative", i18n: "notification.errors.internalError", timeout: 2000 })
return
}
if (!this.isValidPasswordHash(password_hash)) {
this.sendGateway("show_notification", { type: "negative", message: "Invalid password", timeout: 2000 })
this.sendGateway("show_notification", { type: "negative", i18n: "notification.errors.invalidPassword", timeout: 2000 })
return
}
@ -1239,7 +1248,7 @@ export class WalletRPC {
filename = path.join(filename, "key_image_export")
}
const onError = () => this.sendGateway("show_notification", { type: "negative", message: "Error exporting key images", timeout: 2000 })
const onError = () => this.sendGateway("show_notification", { type: "negative", i18n: "notification.errors.keyImages.exporting", timeout: 2000 })
this.sendRPC("export_key_images").then((data) => {
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
@ -1249,9 +1258,9 @@ export class WalletRPC {
if (data.result.signed_key_images) {
fs.outputJSONSync(filename, data.result.signed_key_images)
this.sendGateway("show_notification", { message: "Key images exported to " + filename, timeout: 2000 })
this.sendGateway("show_notification", { i18n: ["notification.positive.keyImages.exported", { filename }], timeout: 2000 })
} else {
this.sendGateway("show_notification", { type: "warning", textColor: "black", message: "No key images found to export", timeout: 2000 })
this.sendGateway("show_notification", { type: "warning", textColor: "black", i18n: "notification.warnings.noKeyImageExport", timeout: 2000 })
}
}).catch(onError)
})
@ -1260,28 +1269,28 @@ export class WalletRPC {
importKeyImages (password, filename = null) {
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
if (err) {
this.sendGateway("show_notification", { type: "negative", message: "Internal error", timeout: 2000 })
this.sendGateway("show_notification", { type: "negative", i18n: "notification.errors.internalError", timeout: 2000 })
return
}
if (!this.isValidPasswordHash(password_hash)) {
this.sendGateway("show_notification", { type: "negative", message: "Invalid password", timeout: 2000 })
this.sendGateway("show_notification", { type: "negative", i18n: "notification.errors.invalidPassword", timeout: 2000 })
return
}
if (filename == null) { filename = path.join(this.wallet_data_dir, "images", this.wallet_state.name, "key_image_export") }
const onError = (message) => this.sendGateway("show_notification", { type: "negative", message, timeout: 2000 })
const onError = (i18n) => this.sendGateway("show_notification", { type: "negative", i18n, timeout: 2000 })
fs.readJSON(filename).then(signed_key_images => {
this.sendRPC("import_key_images", { signed_key_images }).then((data) => {
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
onError("Error importing key images")
onError("notification.errors.keyImages.importing")
return
}
this.sendGateway("show_notification", { message: "Key images imported", timeout: 2000 })
this.sendGateway("show_notification", { i18n: "notification.positive.keyImages.imported", timeout: 2000 })
})
}).catch(() => onError("Error reading key images"))
}).catch(() => onError("notification.errors.keyImages.reading"))
})
}
@ -1438,24 +1447,24 @@ export class WalletRPC {
changeWalletPassword (old_password, new_password) {
crypto.pbkdf2(old_password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
if (err) {
this.sendGateway("show_notification", { type: "negative", message: "Internal error", timeout: 2000 })
this.sendGateway("show_notification", { type: "negative", i18n: "notification.errors.internalError", timeout: 2000 })
return
}
if (!this.isValidPasswordHash(password_hash)) {
this.sendGateway("show_notification", { type: "negative", message: "Invalid old password", timeout: 2000 })
this.sendGateway("show_notification", { type: "negative", i18n: "notification.errors.invalidOldPassword", timeout: 2000 })
return
}
this.sendRPC("change_wallet_password", { old_password, new_password }).then((data) => {
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
this.sendGateway("show_notification", { type: "negative", message: "Error changing password", timeout: 2000 })
this.sendGateway("show_notification", { type: "negative", i18n: "notification.errors.changingPassword", timeout: 2000 })
return
}
// store hash of the password so we can check against it later when requesting private keys, or for sending txs
this.wallet_state.password_hash = crypto.pbkdf2Sync(new_password, this.auth[2], 1000, 64, "sha512").toString("hex")
this.sendGateway("show_notification", { message: "Password updated", timeout: 2000 })
this.sendGateway("show_notification", { i18n: "notification.positive.passwordUpdated", timeout: 2000 })
})
})
}
@ -1463,11 +1472,11 @@ export class WalletRPC {
deleteWallet (password) {
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
if (err) {
this.sendGateway("show_notification", { type: "negative", message: "Internal error", timeout: 2000 })
this.sendGateway("show_notification", { type: "negative", i18n: "notification.errors.internalError", timeout: 2000 })
return
}
if (!this.isValidPasswordHash(password_hash)) {
this.sendGateway("show_notification", { type: "negative", message: "Invalid password", timeout: 2000 })
this.sendGateway("show_notification", { type: "negative", i18n: "notification.errors.invalidPassword", timeout: 2000 })
return
}

View File

@ -5,18 +5,18 @@
<q-toolbar slot="header" color="dark" inverted>
<q-btn flat round dense icon="reply" @click="close()" />
<q-toolbar-title v-if="mode=='new'">
Add address book entry
{{ $t("strings.addAddressBookEntry") }}
</q-toolbar-title>
<q-toolbar-title v-else-if="mode=='edit'">
Edit address book entry
{{ $t("strings.editAddressBookEntry") }}
</q-toolbar-title>
<q-btn v-if="mode=='edit'" flat no-ripple @click="cancelEdit()" label="Cancel" />
<q-btn class="q-ml-sm" color="primary" @click="save()" label="Save" />
<q-btn v-if="mode=='edit'" flat no-ripple @click="cancelEdit()" :label="$t('buttons.cancel')" />
<q-btn class="q-ml-sm" color="primary" @click="save()" :label="$t('buttons.save')" />
</q-toolbar>
<div class="address-book-modal q-mx-md">
<LokiField label="Address" :error="$v.newEntry.address.$error">
<LokiField :label="$t('fieldLabels.address')" :error="$v.newEntry.address.$error">
<q-input
v-model="newEntry.address"
:placeholder="address_placeholder"
@ -32,26 +32,26 @@
dark
/>
</LokiField>
<LokiField label="Name">
<LokiField :label="$t('fieldLabels.name')">
<q-input
v-model="newEntry.name"
:dark="theme=='dark'"
hide-underline
/>
</LokiField>
<LokiField label="Payment ID" :error="$v.newEntry.payment_id.$error" optional>
<LokiField :label="$t('fieldLabels.paymentId')" :error="$v.newEntry.payment_id.$error" optional>
<q-input
v-model="newEntry.payment_id"
placeholder="16 or 64 hexadecimal characters"
:placeholder="$t('placeholders.hexCharacters', { count: '16 or 64' })"
@blur="$v.newEntry.payment_id.$touch"
:dark="theme=='dark'"
hide-underline
/>
</LokiField>
<LokiField label="Notes" optional>
<LokiField :label="$t('fieldLabels.notes')" optional>
<q-input
v-model="newEntry.description"
placeholder="Additional notes"
:placeholder="$t('placeholders.additionalNotes')"
type="textarea"
:dark="theme=='dark'"
hide-underline
@ -59,7 +59,7 @@
</LokiField>
<q-field v-if="mode=='edit'">
<q-btn class="float-right" color="red" @click="deleteEntry()" label="Delete" />
<q-btn class="float-right" color="red" @click="deleteEntry()" :label="$t('buttons.delete')" />
</q-field>
</div>
</q-modal-layout>
@ -68,17 +68,17 @@
<q-toolbar slot="header" color="dark" inverted>
<q-btn flat round dense icon="reply" @click="close()" />
<q-toolbar-title>
Address book details
{{ $t('strings.addressBookDetails') }}
</q-toolbar-title>
<q-btn class="q-mr-sm"
flat no-ripple
:disable="!is_ready"
@click="edit()" label="Edit" />
@click="edit()" :label="$t('buttons.edit')" />
<q-btn
color="primary"
:disabled="view_only"
@click="sendToAddress"
label="Send coins" />
:label="$t('buttons.sendCoins')" />
</q-toolbar>
<div class="layout-padding">
@ -87,16 +87,14 @@
<AddressHeader :address="entry.address"
:title="entry.name"
:payment_id="entry.payment_id"
:extra="entry.description ? 'Notes: '+entry.description : ''"
:extra="entry.description ? $t('strings.notes')+': '+entry.description : ''"
/>
<div class="q-mt-lg">
<div class="non-selectable">
<q-icon name="history" size="24px" />
<span class="vertical-middle q-ml-xs">Recent transactions with this address</span>
<span class="vertical-middle q-ml-xs">{{ $t('strings.recentTransactionsWithAddress') }}</span>
</div>
<TxList type="all_in" :limit="5" :to-outgoing-address="entry.address" :key="entry.address"/>
@ -174,7 +172,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Address not valid"
message: this.$t("notification.errors.invalidAddress")
})
return
}
@ -183,7 +181,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Payment id not valid"
message: this.$t("notification.errors.invalidPaymentId")
})
return
}

View File

@ -10,10 +10,10 @@
icon="reply"
/>
<q-toolbar-title>
Address details
{{ $t("titles.addressDetails") }}
</q-toolbar-title>
<q-btn flat @click="isQRCodeVisible = true" label="Show QR Code" />
<q-btn class="q-ml-sm" color="primary" @click="copyAddress()" label="Copy address" />
<q-btn flat @click="isQRCodeVisible = true" :label="$t('buttons.showQRCode')" />
<q-btn class="q-ml-sm" color="primary" @click="copyAddress()" :label="$t('buttons.copyAddress')" />
</q-toolbar>
<div class="layout-padding">
@ -21,8 +21,8 @@
<template v-if="address != null">
<AddressHeader :address="address.address"
:title="address.address_index == 0 ? 'Primary address' : 'Sub-address (Index '+address.address_index+')'"
:extra="'You have '+(address.used?'used':'not used')+' this address'"
:title="addressHeaderInfo.title"
:extra="addressHeaderInfo.extra"
:showCopy="false"
/>
@ -32,21 +32,21 @@
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Balance</span></div>
<div class="text"><span>{{ $t("strings.lokiBalance") }}</span></div>
<div class="value"><span><FormatLoki :amount="address.balance" /></span></div>
</div>
</div>
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Unlocked balance</span></div>
<div class="text"><span>{{ $t("strings.lokiUnlockedBalance") }}</span></div>
<div class="value"><span><FormatLoki :amount="address.unlocked_balance" /></span></div>
</div>
</div>
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Number of unspent outputs</span></div>
<div class="text"><span>{{ $t("strings.numberOfUnspentOutputs") }}</span></div>
<div class="value"><span>{{ address.num_unspent_outputs }}</span></div>
</div>
</div>
@ -58,21 +58,21 @@
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Balance</span></div>
<div class="text"><span>{{ $t("strings.lokiBalance") }}</span></div>
<div class="value"><span>N/A</span></div>
</div>
</div>
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Unlocked balance</span></div>
<div class="text"><span>{{ $t("strings.lokiUnlockedBalance") }}</span></div>
<div class="value"><span>N/A</span></div>
</div>
</div>
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Number of unspent outputs</span></div>
<div class="text"><span>{{ $t("strings.numberOfUnspentOutputs") }}</span></div>
<div class="value"><span>N/A</span></div>
</div>
</div>
@ -84,7 +84,7 @@
<div class="non-selectable">
<q-icon name="history" size="24px" />
<span class="vertical-middle q-ml-xs">Recent incoming transactions to this address</span>
<span class="vertical-middle q-ml-xs">{{ $t("strings.recentIncomingTransactionsToAddress") }}</span>
</div>
<div style="margin: 0 -16px;">
@ -108,10 +108,10 @@
<q-context-menu>
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay @click.native="copyQR()">
<q-item-main label="Copy QR code" />
<q-item-main :label="$t('menuItems.copyQR')" />
</q-item>
<q-item v-close-overlay @click.native="saveQR()">
<q-item-main label="Save QR code to file" />
<q-item-main :label="$t('menuItems.saveQR')" />
</q-item>
</q-list>
</q-context-menu>
@ -120,7 +120,7 @@
<q-btn
color="primary"
@click="isQRCodeVisible = false"
label="Close"
:label="$t('buttons.close')"
/>
</q-modal>
</template>
@ -138,6 +138,21 @@ import TxList from "components/tx_list"
export default {
name: "AddressDetails",
computed: mapState({
addressHeaderInfo (state) {
if (!this.address) return null
let title = this.$t('strings.addresses.primaryAddress')
if (this.address.address_index !== 0) {
title = this.$t('strings.addresses.subAddress') + ' (Index ' + this.address.address_index + ')'
}
const extra = this.address.used ? this.$t('strings.userUsedAddress') : this.$t('strings.userNotUsedAddress')
return {
title,
extra
}
}
}),
data () {
return {
@ -154,7 +169,7 @@ export default {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Copied QR code to clipboard"
message: this.$t("notification.positive.qrCopied")
})
},
saveQR() {
@ -166,7 +181,7 @@ export default {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
message: this.$t("notification.positive.addressCopied")
})
}
},

View File

@ -1,10 +1,10 @@
<template>
<q-item class="address-header">
<q-item-main class="self-start">
<q-item-tile sublabel class="title">{{ title }}</q-item-tile>
<q-item-tile sublabel class="title non-selectable">{{ title }}</q-item-tile>
<q-item-tile class="break-all" label>{{ address }}</q-item-tile>
<q-item-tile v-if="payment_id" sublabel>Payment id: {{ payment_id }}</q-item-tile>
<q-item-tile v-if="extra" sublabel class="extra">{{ extra }}</q-item-tile>
<q-item-tile v-if="payment_id" sublabel>{{ $t("fieldLabels.paymentId") }}: {{ payment_id }}</q-item-tile>
<q-item-tile v-if="extra" sublabel class="extra non-selectable">{{ extra }}</q-item-tile>
</q-item-main>
<q-item-side v-if="showCopy">
<q-btn
@ -14,7 +14,7 @@
ref="copy"
@click="copyAddress">
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
Copy address
{{ $t("menuItems.copyAddress") }}
</q-tooltip>
</q-btn>
@ -23,8 +23,8 @@
<q-context-menu>
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay
@click.native="copyAddress(address, $event)">
<q-item-main label="Copy address" />
@click.native="copyAddress($event)">
<q-item-main :label="$t('menuItems.copyAddress')" />
</q-item>
</q-list>
</q-context-menu>
@ -64,34 +64,33 @@ export default {
return {}
},
methods: {
copyAddress () {
this.$refs.copy.$el.blur()
copyAddress (event) {
if (event) {
event.stopPropagation()
}
if (this.$refs.copy) {
this.$refs.copy.$el.blur()
}
clipboard.writeText(this.address)
if(this.payment_id) {
this.$q.dialog({
title: "Copy address",
message: "There is a payment id associated with this address.\nBe sure to copy the payment id separately.",
title: this.$t("dialog.copyAddress.title"),
message: this.$t("dialog.copyAddress.message"),
ok: {
label: "OK"
label: this.$t(`dialog.${key}.ok`)
},
}).then(() => {
}).catch(() => null).then(() => {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
})
}).catch(() => {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
message: this.$t("notification.positive.addressCopied")
})
})
} else {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
message: this.$t("notification.positive.addressCopied")
})
}
},

View File

@ -2,8 +2,8 @@
<q-layout-footer class="status-footer">
<div class="status-line row items-center">
<div class="status row items-center">
<span>Status:</span>
<span class="status-text" :class="[status]">{{ status | upperCase }}</span>
<span>{{ $t("footer.status") }}:</span>
<span class="status-text" :class="[status]">{{ $t(`footer.${status}`) }}</span>
</div>
<div class="row">
<template v-if="config_daemon.type !== 'remote'">
@ -87,11 +87,6 @@ export default {
}
}),
filters: {
upperCase: function (status) {
return status.toUpperCase();
}
},
data () {
return {
}

View File

@ -2,7 +2,7 @@
<div class="loki-field" :class="{disable, 'disable-hover': disableHover}">
<div class="label row items-center" v-if="label" :disabled="disable">
{{ label }}
<span v-if="optional" class="optional">(Optional)</span>
<span v-if="optional" class="optional">({{ $t("fieldLabels.optional") }})</span>
</div>
<div class="content row items-center" :class="{error}">
<slot></slot>
@ -57,7 +57,6 @@ export default {
margin: 6px 0;
font-weight: bold;
font-size: 12px;
text-transform: uppercase;
// Disable text selection
-webkit-user-select: none;

View File

@ -5,22 +5,22 @@
<q-list separator link>
<q-item v-close-overlay @click.native="switchWallet" v-if="!disableSwitchWallet">
<q-item-main>
<q-item-tile label>Switch Wallet</q-item-tile>
<q-item-tile label>{{ $t("menuItems.switchWallet") }}</q-item-tile>
</q-item-main>
</q-item>
<q-item v-close-overlay @click.native="openSettings">
<q-item-main>
<q-item-tile label>Settings</q-item-tile>
<q-item-tile label>{{ $t("menuItems.settings") }}</q-item-tile>
</q-item-main>
</q-item>
<q-item v-close-overlay @click.native="showAbout(true)">
<q-item-main>
<q-item-tile label>About</q-item-tile>
<q-item-tile label>{{ $t("menuItems.about") }}</q-item-tile>
</q-item-main>
</q-item>
<q-item v-close-overlay @click.native="exit">
<q-item-main>
<q-item-tile label>Exit Loki GUI Wallet</q-item-tile>
<q-item-tile label>{{ $t("menuItems.exit") }}</q-item-tile>
</q-item-main>
</q-item>
</q-list>
@ -86,7 +86,7 @@ export default {
},
computed: mapState({
theme: state => state.gateway.app.config.appearance.theme,
isRPCSyncing: state => state.gateway.wallet.isRPCSyncing,
isRPCSyncing: state => state.gateway.wallet.isRPCSyncing
}),
methods: {
openExternal (url) {
@ -104,20 +104,20 @@ export default {
switchWallet () {
// If the rpc is syncing then we want to tell the user to restart
if (this.isRPCSyncing) {
this.$gateway.confirmClose("The wallet RPC is currently syncing. If you wish to switch wallets then you must restart the application. You will lose your syncing progress and have to rescan the blockchain again.", true)
this.$gateway.confirmClose(this.$t("dialog.switchWallet.restartMessage"), true)
return
}
// Allow switching normally because rpc won't be blocked
this.$q.dialog({
title: "Switch wallet",
message: "Are you sure you want to close the current wallet?",
title: this.$t("dialog.switchWallet.title"),
message: this.$t("dialog.switchWallet.closeMessage"),
ok: {
label: "OK"
label: this.$t("dialog.buttons.ok")
},
cancel: {
flat: true,
label: "CANCEL",
label: this.$t("dialog.buttons.cancel"),
color: this.theme=="dark"?"white":"dark"
}
}).then(() => {
@ -132,7 +132,7 @@ export default {
})
},
exit () {
this.$gateway.confirmClose("Are you sure you want to exit?")
this.$gateway.confirmClose(this.$t("dialog.exit.message"))
}
},
components: {

View File

@ -0,0 +1,114 @@
<template>
<q-list class="loki-list-item" no-border @click.native="details(address)">
<q-item>
<q-item-main>
<q-item-tile class="ellipsis" label>{{ address.address }}</q-item-tile>
<q-item-tile v-if="sublabel" sublabel class="non-selectable">{{ sublabel }}</q-item-tile>
</q-item-main>
<q-item-side>
<q-btn
flat
style="width:25px;"
size="md"
@click="showQR(address.address, $event)"
>
<img src="statics/qr-code.svg" height="20" />
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
{{ $t("menuItems.showQRCode") }}
</q-tooltip>
</q-btn>
<q-btn
flat
style="width:25px;"
size="md" icon="file_copy"
@click="copyAddress(address.address, $event)"
>
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
{{ $t("menuItems.copyAddress") }}
</q-tooltip>
</q-btn>
</q-item-side>
</q-item>
<template v-if="shouldShowInfo">
<q-item-separator />
<q-item class="info">
<q-item-main class="flex justify-between">
<div class="column">
<span>{{ $t("strings.lokiBalance") }}</span>
<span class="value">{{address.balance | currency}}</span>
</div>
<div class="column">
<span>{{ $t("strings.lokiUnlockedBalance") }}</span>
<span class="value">{{ address.unlocked_balance | currency }}</span>
</div>
<div class="column">
<span>{{ $t("strings.unspentOutputs") }}</span>
<span class="value">{{ address.num_unspent_outputs | toString }}</span>
</div>
</q-item-main>
</q-item>
</template>
<q-context-menu>
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay
@click.native="details(address)">
<q-item-main :label="$t('menuItems.showDetails')" />
</q-item>
<q-item v-close-overlay
@click.native="copyAddress(address.address, $event)">
<q-item-main :label="$t('menuItems.copyAddress')" />
</q-item>
</q-list>
</q-context-menu>
</q-list>
</template>
<script>
import { mapState } from "vuex"
const { clipboard, nativeImage } = require("electron")
export default {
name: "ReceiveItem",
props: {
address: {
required: true
},
sublabel: {
type: String,
required: false,
},
shouldShowInfo: {
type: Boolean,
required: false,
default: true
},
showQR: {
type: Function,
required: true
},
copyAddress: {
type: Function,
required: true
},
details: {
type: Function,
required: true
}
},
filters: {
toString: function (value) {
if (typeof value !== "number") return "N/A";
return String(value);
},
currency: function (value) {
if (typeof value !== "number") return "N/A";
const amount = value / 1e9
return amount.toLocaleString()
}
},
}
</script>
<style>
</style>

View File

@ -1,10 +1,11 @@
<template>
<div class="service-node-registration">
<div class="q-pa-md">
<div class="description q-mb-lg">
Enter the <b>register_service_node</b> command produced by the daemon that is registering to become a Service Node using the "<b>prepare_registration</b>" command
</div>
<LokiField label="Service Node Command" :error="$v.registration_string.$error" :disabled="registration_status.sending">
<i18n path="strings.serviceNodeRegistrationDescription" tag="div" class="description q-mb-lg">
<b place="registerCommand">register_service_node</b>
<b place="prepareCommand">prepare_registration</b>
</i18n>
<LokiField :label="$t('fieldLabels.serviceNodeCommand')" :error="$v.registration_string.$error" :disabled="registration_status.sending">
<q-input
v-model="registration_string"
type="textarea"
@ -16,7 +17,7 @@
/>
</LokiField>
<q-field class="q-pt-sm">
<q-btn color="primary" @click="register()" label="Register service node" :disabled="registration_status.sending"/>
<q-btn color="primary" @click="register()" :label="$t('buttons.registerServiceNode')" :disabled="registration_status.sending"/>
</q-field>
</div>
@ -81,16 +82,16 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Please enter the service node registration command"
message: this.$t("notification.errors.invalidServiceNodeCommand")
})
return
}
this.showPasswordConfirmation({
title: "Register service node",
noPasswordMessage: "Do you want to register the service node?",
title: this.$t("dialog.registerServiceNode.title"),
noPasswordMessage: this.$t("dialog.registerServiceNode.message"),
ok: {
label: "REGISTER"
label: this.$t("dialog.registerServiceNode.ok")
},
}).then(password => {
this.$store.commit("gateway/set_snode_status", {

View File

@ -1,28 +1,28 @@
<template>
<div class="service-node-staking">
<div class="q-pa-md">
<LokiField label="Service Node Key" :error="$v.service_node.key.$error">
<LokiField :label="$t('fieldLabels.serviceNodeKey')" :error="$v.service_node.key.$error">
<q-input v-model="service_node.key"
:dark="theme=='dark'"
@blur="$v.service_node.key.$touch"
placeholder="64 hexadecimal characters"
:placeholder="$t('placeholders.hexCharacters', { count: 64 })"
hide-underline
/>
</LokiField>
<div class="q-mt-md col">
<LokiField label="Award Recepient's Address" :error="$v.service_node.award_address.$error">
<LokiField :label="$t('fieldLabels.awardRecepientAddress')" :error="$v.service_node.award_address.$error">
<q-input v-model="service_node.award_address"
:dark="theme=='dark'"
@blur="$v.service_node.award_address.$touch"
placeholder="64 hexadecimal characters"
:placeholder="address_placeholder"
hide-underline
/>
</LokiField>
<div class="address-type" :class="[addressType]">( {{ addressType | addressTypeString }} )</div>
</div>
<LokiField label="Amount" class="q-mt-md" :error="$v.service_node.amount.$error">
<LokiField :label="$t('fieldLabels.amount')" class="q-mt-md" :error="$v.service_node.amount.$error">
<q-input v-model="service_node.amount"
:dark="theme=='dark'"
type="number"
@ -32,7 +32,9 @@
@blur="$v.service_node.amount.$touch"
hide-underline
/>
<q-btn color="secondary" @click="service_node.amount = unlocked_balance / 1e9" :text-color="theme=='dark'?'white':'dark'">All</q-btn>
<q-btn color="secondary" @click="service_node.amount = unlocked_balance / 1e9" :text-color="theme=='dark'?'white':'dark'">
{{ $t("buttons.all") }}
</q-btn>
</LokiField>
@ -40,7 +42,7 @@
<q-field class="q-pt-sm">
<q-btn
:disable="!is_able_to_send"
color="primary" @click="stake()" label="Stake" />
color="primary" @click="stake()" :label="$t('buttons.stake')" />
</q-field>
</div>
@ -59,6 +61,7 @@ import { required, decimal } from "vuelidate/lib/validators"
import { payment_id, service_node_key, greater_than_zero, address } from "src/validators/common"
import LokiField from "components/loki_field"
import WalletPassword from "src/mixins/wallet_password"
import { i18n } from "plugins/i18n"
export default {
name: "ServiceNodeStaking",
@ -74,7 +77,11 @@ export default {
is_able_to_send (state) {
return this.$store.getters["gateway/isAbleToSend"]
},
address_placeholder (state) {
const wallet = state.gateway.wallet.info;
const prefix = (wallet && wallet.address && wallet.address[0]) || "L";
return `${prefix}..`;
},
addressType (state) {
const address = this.service_node.award_address;
const inArray = (array) => array.map(o => o.address).includes(address);
@ -89,7 +96,7 @@ export default {
} else {
return "not-ours"
}
}
},
}),
data () {
return {
@ -104,13 +111,13 @@ export default {
addressTypeString: function (value) {
switch (value) {
case "primary":
return "Your primary address"
return i18n.t("strings.addresses.yourPrimaryAddress")
case "used":
return "Your used address"
return i18n.t("strings.addresses.yourUsedAddress")
case "ununsed":
return "Your unused address"
return i18n.t("strings.addresses.yourUnusedAddress")
default:
return "Not your address!"
return i18n.t("strings.addresses.notYourAddress")
}
}
},
@ -187,7 +194,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Service node key not valid"
message: this.$t("notification.errors.invalidServiceNodeKey")
})
return
}
@ -196,7 +203,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Address not valid"
message: this.$t("notification.errors.invalidAddress")
})
return
}
@ -205,37 +212,37 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Amount cannot be negative"
message: this.$t("notification.errors.negativeAmount")
})
return
} else if(this.service_node.amount == 0) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Amount must be greater than zero"
message: this.$t("notification.errors.zeroAmount")
})
return
} else if(this.service_node.amount > this.unlocked_balance / 1e9) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Not enough unlocked balance"
message: this.$t("notification.errors.notEnoughBalance")
})
return
} else if (this.$v.service_node.amount.$error) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Amount not valid"
message: this.$t("notification.errors.invalidAmount")
})
return
}
this.showPasswordConfirmation({
title: "Stake",
noPasswordMessage: "Do you want to stake?",
title: this.$t("dialog.stake.title"),
noPasswordMessage: this.$t("dialog.stake.message"),
ok: {
label: "STAKE"
label: this.$t("dialog.stake.ok")
},
}).then(password => {
this.$store.commit("gateway/set_snode_status", {

View File

@ -1,18 +1,18 @@
<template>
<div class="service-node-unlock">
<div class="q-pa-md">
<LokiField label="Service Node Key" :error="$v.node_key.$error" :disabled="unlock_status.sending">
<LokiField :label="$t('fieldLabels.serviceNodeKey')" :error="$v.node_key.$error" :disabled="unlock_status.sending">
<q-input
v-model="node_key"
:dark="theme=='dark'"
@blur="$v.node_key.$touch"
placeholder="64 hexadecimal characters"
:placeholder="$t('placeholders.hexCharacters', { count: 64 })"
:disabled="unlock_status.sending"
hide-underline
/>
</LokiField>
<q-field class="q-pt-sm">
<q-btn color="primary" @click="unlock()" label="Unlock service node" :disabled="unlock_status.sending"/>
<q-btn color="primary" @click="unlock()" :label="$t('buttons.unlockServiceNode')" :disabled="unlock_status.sending"/>
</q-field>
</div>
@ -64,14 +64,14 @@ export default {
case 1:
// Tell the user to confirm
this.$q.dialog({
title: "Confirm",
title: this.$t("dialog.unlockServiceNode.confirmTitle"),
message: this.unlock_status.message,
ok: {
label: "UNLOCK"
label: this.$t("dialog.unlockServiceNode.ok")
},
cancel: {
flat: true,
label: "CANCEL",
label: this.$t("dialog.buttons.cancel"),
color: this.theme=="dark"?"white":"dark"
}
}).then(() => {
@ -101,7 +101,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Service node key not valid"
message: this.$t("notification.errors.invalidServiceNodeKey")
})
return
}
@ -110,10 +110,10 @@ export default {
this.key = this.node_key
this.showPasswordConfirmation({
title: "Unlock service node",
noPasswordMessage: "Do you want to unlock the service node?",
title: this.$t("dialog.unlockServiceNode.title"),
noPasswordMessage: this.$t("dialog.unlockServiceNode.message"),
ok: {
label: "UNLOCK"
label: this.$t("dialog.unlockServiceNode.ok")
},
}).then(password => {
this.password = password

View File

@ -4,7 +4,7 @@
<q-toolbar slot="header" color="dark" inverted>
<q-btn flat round dense @click="isVisible = false" icon="reply" />
<q-toolbar-title shrink>
Settings
{{ $t("titles.settings.title") }}
</q-toolbar-title>
<div class="row col justify-center q-pr-xl">
@ -17,7 +17,7 @@
/>
</div>
<q-btn color="primary" @click="save" label="Save" />
<q-btn color="primary" @click="save" :label="$t('buttons.save')" />
</q-toolbar>
<div v-if="page=='general'">
@ -28,22 +28,22 @@
<div v-if="page=='peers'">
<q-list :dark="theme=='dark'" no-border>
<q-list-header>Peer list</q-list-header>
<q-list-header>{{ $t("strings.peerList") }}</q-list-header>
<q-item link v-for="(entry, index) in daemon.connections" @click.native="showPeerDetails(entry)">
<q-item-main>
<q-item-tile label>{{ entry.address }}</q-item-tile>
<q-item-tile sublabel>Height: {{ entry.height }}</q-item-tile>
<q-item-tile sublabel>{{ $t("strings.blockHeight") }}: {{ entry.height }}</q-item-tile>
</q-item-main>
</q-item>
<template v-if="daemon.bans.length">
<q-list-header>Banned peers (bans will cleared if wallet is restarted)</q-list-header>
<q-list-header>{{ $t("strings.bannedPeers.title") }}</q-list-header>
<q-item v-for="(entry, index) in daemon.bans">
<q-item-main>
<q-item-tile label>{{ entry.host }}</q-item-tile>
<q-item-tile sublabel>Banned until {{ new Date(Date.now() + entry.seconds * 1000).toLocaleString() }}</q-item-tile>
<q-item-tile sublabel>{{ $t("strings.bannedPeers.bannedUntil", { time: new Date(Date.now() + entry.seconds * 1000).toLocaleString() }) }}</q-item-tile>
</q-item-main>
</q-item>
@ -71,10 +71,10 @@ export default {
tabs: function(state) {
const { app, daemons } = state.gateway.app.config;
let tabs = [
{label: 'General', value: 'general', icon: 'settings'},
{label: this.$t("titles.settings.tabs.general"), value: 'general', icon: 'settings'},
]
if(daemons[app.net_type].type != 'remote') {
tabs.push({label: 'Peers', value: 'peers', icon: 'cloud_queue'})
tabs.push({label: this.$t("titles.settings.tabs.peers"), value: 'peers', icon: 'cloud_queue'})
}
return tabs
}
@ -99,10 +99,10 @@ export default {
},
showPeerDetails (entry) {
this.$q.dialog({
title: "Peer details",
title: this.$t("dialog.banPeer.peerDetailsTitle"),
message: JSON.stringify(entry, null, 2),
ok: {
label: "Ban peer",
label: this.$t("dialog.banPeer.ok"),
color: "negative",
},
cancel: {
@ -113,19 +113,19 @@ export default {
}).then(() => {
this.$q.dialog({
title: "Ban peer",
message: "Enter length to ban peer in seconds.\nDefault 3600 = 1 hour.",
title: this.$t("dialog.banPeer.title"),
message: this.$t("dialog.banPeer.message"),
prompt: {
model: "",
type: "number"
},
ok: {
label: "Ban peer",
label: this.$t("dialog.banPeer.ok"),
color: "negative"
},
cancel: {
flat: true,
label: "CANCEL",
label: this.$t("dialog.buttons.cancel"),
color: this.theme=="dark"?"white":"dark"
}
}).then(seconds => {

View File

@ -1,24 +1,24 @@
<template>
<div class="settings-general">
<div class="row justify-between q-mb-md">
<div><q-radio v-model="config_daemon.type" val="remote" label="Remote Daemon Only" /></div>
<div><q-radio v-model="config_daemon.type" val="local_remote" label="Local + Remote Daemon" /></div>
<div><q-radio v-model="config_daemon.type" val="local" label="Local Daemon Only" /></div>
<div><q-radio v-model="config_daemon.type" val="remote" :label="$t('strings.daemon.remote.title')" /></div>
<div><q-radio v-model="config_daemon.type" val="local_remote" :label="$t('strings.daemon.localRemote.title')" /></div>
<div><q-radio v-model="config_daemon.type" val="local" :label="$t('strings.daemon.local.title')" /></div>
</div>
<p v-if="config_daemon.type == 'local_remote'">
Get started quickly with this default option. Wallet will download the full blockchain, but use a remote node while syncing.
{{ $t("strings.daemon.localRemote.description") }}
</p>
<p v-if="config_daemon.type == 'local'">
Full security, wallet will download the full blockchain. You will not be able to transact until sync is completed.
{{ $t("strings.daemon.local.description") }}
</p>
<p v-if="is_remote">
Less security, wallet will connect to a remote node to make all transactions.
{{ $t("strings.daemon.remote.description") }}
</p>
<template v-if="config_daemon.type != 'remote'">
<div class="row pl-sm">
<LokiField class="col-8" label="Local Daemon IP" disable>
<LokiField class="col-8" :label="$t('fieldLabels.localDaemonIP')" disable>
<q-input
v-model="config_daemon.rpc_bind_ip"
:placeholder="daemon_defaults.rpc_bind_ip"
@ -27,7 +27,7 @@
hide-underline
/>
</LokiField>
<LokiField class="col-4" label="Local Daemon Port (RPC)">
<LokiField class="col-4" :label="$t('fieldLabels.localDaemonPort') + '(RPC)'">
<q-input
v-model="config_daemon.rpc_bind_port"
:placeholder="toString(daemon_defaults.rpc_bind_port)"
@ -45,7 +45,7 @@
<template v-if="config_daemon.type != 'local'">
<div class="row q-mt-md pl-sm">
<LokiField class="col-8" label="Remote Node Host">
<LokiField class="col-8" :label="$t('fieldLabels.remoteNodeHost')">
<q-input
v-model="config_daemon.remote_host"
:placeholder="daemon_defaults.remote_host"
@ -63,7 +63,7 @@
</q-list>
</q-btn-dropdown>
</LokiField>
<LokiField class="col-4" label="Remote Node Port">
<LokiField class="col-4" :label="$t('fieldLabels.remoteNodePort')">
<q-input
v-model="config_daemon.remote_port"
:placeholder="toString(daemon_defaults.remote_port)"
@ -81,63 +81,63 @@
</template>
<div class="col q-mt-md pt-sm">
<LokiField label="Data Storage Path" disable-hover>
<LokiField :label="$t('fieldLabels.dataStoragePath')" disable-hover>
<q-input v-model="config.app.data_dir" disable :dark="theme=='dark'" hide-underline/>
<input type="file" webkitdirectory directory id="dataPath" v-on:change="setDataPath" ref="fileInputData" hidden />
<q-btn color="secondary" v-on:click="selectPath('data')" :text-color="theme=='dark'?'white':'dark'">Select Location</q-btn>
<q-btn color="secondary" v-on:click="selectPath('data')" :text-color="theme=='dark'?'white':'dark'">{{ $t("buttons.selectLocation") }}</q-btn>
</LokiField>
<LokiField label="Wallet Storage Path" disable-hover>
<LokiField :label="$t('fieldLabels.walletStoragePath')" disable-hover>
<q-input v-model="config.app.wallet_data_dir" disable :dark="theme=='dark'" hide-underline/>
<input type="file" webkitdirectory directory id="walletPath" v-on:change="setWalletDataPath" ref="fileInputWallet" hidden />
<q-btn color="secondary" v-on:click="selectPath('wallet')" :text-color="theme=='dark'?'white':'dark'">Select Location</q-btn>
<q-btn color="secondary" v-on:click="selectPath('wallet')" :text-color="theme=='dark'?'white':'dark'">{{ $t("buttons.selectLocation") }}</q-btn>
</LokiField>
</div>
<q-collapsible label="Advanced Options" header-class="q-mt-sm non-selectable row reverse advanced-options-label">
<q-collapsible :label="$t('strings.advancedOptions')" header-class="q-mt-sm non-selectable row reverse advanced-options-label">
<div class="row pl-sm q-mt-sm">
<LokiField class="col-6" label="Daemon Log Level" :disable="is_remote">
<LokiField class="col-6" :label="$t('fieldLabels.daemonLogLevel')" :disable="is_remote">
<q-input v-model="config_daemon.log_level" :placeholder="toString(daemon_defaults.log_level)" :disable="is_remote" :dark="theme=='dark'"
type="number" :decimals="0" :step="1" min="0" max="4" hide-underline />
</LokiField>
<LokiField class="col-6" label="Wallet Log Level">
<LokiField class="col-6" :label="$t('fieldLabels.walletLogLevel')">
<q-input v-model="config.wallet.log_level" :placeholder="toString(defaults.wallet.log_level)" :dark="theme=='dark'"
type="number" :decimals="0" :step="1" min="0" max="4" hide-underline />
</LokiField>
</div>
<div class="row pl-sm q-mt-md">
<LokiField class="col-3" label="Max Incoming Peers" :disable="is_remote">
<LokiField class="col-3" :label="$t('fieldLabels.maxIncomingPeers')" :disable="is_remote">
<q-input v-model="config_daemon.in_peers" :placeholder="toString(daemon_defaults.in_peers)" :disable="is_remote" :dark="theme=='dark'"
type="number" :decimals="0" :step="1" min="-1" max="65535" hide-underline />
</LokiField>
<LokiField class="col-3" label="Max Outgoing Peers" :disable="is_remote">
<LokiField class="col-3" :label="$t('fieldLabels.maxOutgoingPeers')" :disable="is_remote">
<q-input v-model="config_daemon.out_peers" :placeholder="toString(daemon_defaults.out_peers)" :disable="is_remote" :dark="theme=='dark'"
type="number" :decimals="0" :step="1" min="-1" max="65535" hide-underline />
</LokiField>
<LokiField class="col-3" label="Limit Upload Rate" :disable="is_remote">
<LokiField class="col-3" :label="$t('fieldLabels.limitUploadRate')" :disable="is_remote">
<q-input v-model="config_daemon.limit_rate_up" :placeholder="toString(daemon_defaults.limit_rate_up)" :disable="is_remote" :dark="theme=='dark'"
type="number" suffix="Kb/s" :decimals="0" :step="1" min="-1" max="65535" hide-underline />
</LokiField>
<LokiField class="col-3" label="Limit Download Rate" :disable="is_remote">
<LokiField class="col-3" :label="$t('fieldLabels.limitDownloadRate')" :disable="is_remote">
<q-input v-model="config_daemon.limit_rate_down" :placeholder="toString(daemon_defaults.limit_rate_down)" :disable="is_remote" :dark="theme=='dark'"
type="number" suffix="Kb/s" :decimals="0" :step="1" min="-1" max="65535" hide-underline />
</LokiField>
</div>
<div class="row pl-sm q-mt-md">
<LokiField class="col-3" label="Daemon P2P Port" :disable="is_remote">
<LokiField class="col-3" :label="$t('fieldLabels.daemonP2pPort')" :disable="is_remote">
<q-input v-model="config_daemon.p2p_bind_port" :placeholder="toString(daemon_defaults.p2p_bind_port)" :disable="is_remote" :dark="theme=='dark'"
float- type="number" :decimals="0" :step="1" min="1024" max="65535" hide-underline />
</LokiField>
<LokiField class="col-3" label="Daemon ZMQ Port" :disable="is_remote">
<LokiField class="col-3" :label="$t('fieldLabels.daemonZMQPort')" :disable="is_remote">
<q-input v-model="config_daemon.zmq_rpc_bind_port" :placeholder="toString(daemon_defaults.zmq_rpc_bind_port)" :disable="is_remote" :dark="theme=='dark'"
float- type="number" :decimals="0" :step="1" min="1024" max="65535" hide-underline />
</LokiField>
<LokiField class="col-3" label="Internal Wallet Port">
<LokiField class="col-3" :label="$t('fieldLabels.internalWalletPort')">
<q-input v-model="config.app.ws_bind_port" :placeholder="toString(defaults.app.ws_bind_port)" :dark="theme=='dark'"
float- type="number" :decimals="0" :step="1" min="1024" max="65535" hide-underline />
</LokiField>
<LokiField class="col-3" label="Wallet RPC Port" :disable="is_remote">
<LokiField class="col-3" :label="$t('fieldLabels.walletRPCPort')" :disable="is_remote">
<q-input v-model="config.wallet.rpc_bind_port" :placeholder="toString(defaults.wallet.rpc_bind_port)" :disable="is_remote" :dark="theme=='dark'"
float- type="number" :decimals="0" :step="1" min="1024" max="65535" hide-underline />
</LokiField>

View File

@ -10,10 +10,10 @@
icon="reply"
/>
<q-toolbar-title>
Transaction details
{{ $t("titles.transactionDetails") }}
</q-toolbar-title>
<q-btn flat class="q-mr-sm" @click="showTxDetails" label="Show tx details" />
<q-btn color="primary" @click="openExplorer" label="View on explorer" />
<q-btn flat class="q-mr-sm" @click="showTxDetails" :label="$t('buttons.showTxDetails')" />
<q-btn color="primary" @click="openExplorer" :label="$t('buttons.viewOnExplorer')" />
</q-toolbar>
<div class="layout-padding">
@ -24,19 +24,19 @@
</div>
<div :class="'tx-'+tx.type" v-if="tx.type=='in'">
Incoming transaction
{{ $t("strings.transactions.description", { type: $t("strings.transactions.types.incoming") }) }}
</div>
<div :class="'tx-'+tx.type" v-else-if="tx.type=='out'">
Outgoing transaction
{{ $t("strings.transactions.description", { type: $t("strings.transactions.types.outgoing") }) }}
</div>
<div :class="'tx-'+tx.type" v-else-if="tx.type=='pool'">
Pending incoming transaction
{{ $t("strings.transactions.description", { type: $t("strings.transactions.types.pendingIncoming") }) }}
</div>
<div :class="'tx-'+tx.type" v-else-if="tx.type=='pending'">
Pending outgoing transaction
{{ $t("strings.transactions.description", { type: $t("strings.transactions.types.pendingOutgoing") }) }}
</div>
<div :class="'tx-'+tx.type" v-else-if="tx.type=='failed'">
Failed transaction
{{ $t("strings.transactions.description", { type: $t("strings.transactions.types.failed") }) }}
</div>
</div>
@ -45,28 +45,35 @@
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Amount</span></div>
<div class="text"><span>{{ $t("strings.transactions.amount") }}</span></div>
<div class="value"><span><FormatLoki :amount="tx.amount" /></span></div>
</div>
</div>
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Fee <template v-if="tx.type=='in'||tx.type=='pool'">(paid by sender)</template></span></div>
<div class="text">
<span>
{{ $t("strings.transactions.fee") }}
<template v-if="tx.type=='in'||tx.type=='pool'">
({{ $t("strings.transactions.paidBySender") }})
</template>
</span>
</div>
<div class="value"><span><FormatLoki :amount="tx.fee" /></span></div>
</div>
</div>
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Height</span></div>
<div class="text"><span>{{ $t("strings.blockHeight") }}</span></div>
<div class="value"><span>{{ tx.height }}</span></div>
</div>
</div>
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Timestamp</span></div>
<div class="text"><span>{{ $t("strings.transactions.timestamp") }}</span></div>
<div class="value"><span>{{ formatDate(tx.timestamp*1000) }}</span></div>
</div>
</div>
@ -74,19 +81,21 @@
</div>
<h6 class="q-mt-xs q-mb-none text-weight-light">Transaction id</h6>
<h6 class="q-mt-xs q-mb-none text-weight-light">{{ $t("strings.transactionID") }}</h6>
<p class="monospace break-all">{{ tx.txid }}</p>
<h6 class="q-mt-xs q-mb-none text-weight-light">Payment id</h6>
<h6 class="q-mt-xs q-mb-none text-weight-light">{{ $t("strings.paymentID") }}</h6>
<p class="monospace break-all">{{ tx.payment_id ? tx.payment_id : 'N/A' }}</p>
<div v-if="tx.type=='in' || tx.type=='pool'">
<q-list no-border>
<q-list-header class="q-px-none">Incoming transaction sent to:</q-list-header>
<q-list-header class="q-px-none">
{{ $t("strings.transactions.sentTo", { type: $t("strings.transactions.types.incoming") }) }}:
</q-list-header>
<q-item class="q-px-none">
<q-item-main>
<q-item-tile label>{{ in_tx_address_used.address_index_text }}</q-item-tile>
<q-item-tile label class="non-selectable">{{ in_tx_address_used.address_index_text }}</q-item-tile>
<q-item-tile class="monospace ellipsis" sublabel>{{ in_tx_address_used.address }}</q-item-tile>
</q-item-main>
@ -94,7 +103,7 @@
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay
@click.native="copyAddress(in_tx_address_used.address, $event)">
<q-item-main label="Copy address" />
<q-item-main :label="$t('menuItems.copyAddress')" />
</q-item>
</q-list>
</q-context-menu>
@ -105,7 +114,9 @@
<div v-else-if="tx.type=='out' || tx.type=='pending'">
<q-list no-border>
<q-list-header class="q-px-none">Outgoing transaction sent to:</q-list-header>
<q-list-header class="q-px-none">
{{ $t("strings.transactions.sentTo", { type: $t("strings.transactions.types.outgoing") }) }}:
</q-list-header>
<template v-if="out_destinations">
<q-item class="q-px-none" v-for="destination in out_destinations">
<q-item-main>
@ -117,7 +128,7 @@
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay
@click.native="copyAddress(destination.address, $event)">
<q-item-main label="Copy address" />
<q-item-main :label="$t('menuItems.copyAddress')" />
</q-item>
</q-list>
</q-context-menu>
@ -127,7 +138,7 @@
<template v-else>
<q-item class="q-px-none">
<q-item-main>
<q-item-tile label>Destination unknown</q-item-tile>
<q-item-tile label>{{ $t('strings.destinationUnknown') }}</q-item-tile>
</q-item-main>
</q-item>
</template>
@ -136,7 +147,7 @@
<q-field class="q-mt-md">
<q-input
v-model="txNotes" float-label="Transaction notes"
v-model="txNotes" :float-label="$t('fieldLabels.transactionNotes')"
:dark="theme=='dark'"
type="textarea" rows="2" />
</q-field>
@ -145,7 +156,7 @@
<q-btn
:disable="!is_ready"
:text-color="theme=='dark'?'white':'dark'"
@click="saveTxNotes" label="Save tx notes" />
@click="saveTxNotes" :label="$t('buttons.saveTxNotes')" />
</q-field>
</div>
@ -173,9 +184,9 @@ export default {
if(used_addresses[i].address_index == this.tx.subaddr_index.minor) {
let address_index_text = ""
if(used_addresses[i].address_index === 0) {
address_index_text = "Primary address"
address_index_text = this.$t("strings.addresses.primaryAddress")
} else {
address_index_text = "Sub-address (Index: "+used_addresses[i].address_index+")"
address_index_text = this.$t("strings.addresses.subAddress") + " (Index: " + used_addresses[i].address_index + ")"
}
return {
address: used_addresses[i].address,
@ -234,10 +245,10 @@ export default {
methods: {
showTxDetails () {
this.$q.dialog({
title: "Transaction details",
title: this.$t("dialogs.transactionDetails.title"),
message: JSON.stringify(this.tx, null, 2),
ok: {
label: "close",
label: this.$t("dialogs.transactionDetails.ok"),
color: "primary",
},
}).then(() => {
@ -252,7 +263,7 @@ export default {
this.$q.notify({
timeout: 1000,
type: "positive",
message: "Transaction notes saved"
message: this.$t("notification.positive.transactionNotesSaved")
})
this.$gateway.send("wallet", "save_tx_notes", {txid: this.tx.txid, note: this.txNotes})
},
@ -271,7 +282,7 @@ export default {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
message: this.$t("notification.positive.addressCopied")
})
}
},

View File

@ -2,7 +2,7 @@
<div class="tx-list">
<template v-if="tx_list_paged.length === 0">
<p class="q-pa-md q-mb-none">No transactions found</p>
<p class="q-pa-md q-mb-none">{{ $t("strings.noTransactionsFound") }}</p>
</template>
<template v-else>
@ -30,17 +30,17 @@
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay
@click.native="details(tx)">
<q-item-main label="Show details" />
<q-item-main :label="$t('menuItems.showDetails')" />
</q-item>
<q-item v-close-overlay
@click.native="copyTxid(tx.txid, $event)">
<q-item-main label="Copy transaction id" />
<q-item-main :label="$t('menuItems.copyTransactionId')" />
</q-item>
<q-item v-close-overlay
@click.native="openExplorer(tx.txid)">
<q-item-main label="View on explorer" />
<q-item-main :label="$t('menuItems.viewOnExplorer')" />
</q-item>
</q-list>
</q-context-menu>
@ -64,6 +64,7 @@ import Identicon from "components/identicon"
import TxTypeIcon from "components/tx_type_icon"
import TxDetails from "components/tx_details"
import FormatLoki from "components/format_loki"
import { i18n } from "plugins/i18n"
export default {
name: "TxList",
@ -161,22 +162,22 @@ export default {
typeToString: function (value) {
switch (value) {
case "in":
return "Received"
return i18n.t("strings.transactions.received")
case "out":
return "Sent"
return i18n.t("strings.transactions.sent")
case "failed":
return "Failed"
return i18n.t("strings.transactions.types.failed")
case "pending":
case "pool":
return "Pending"
return i18n.t("strings.transactions.types.pending")
case "miner":
return "Miner"
return i18n.t("strings.transactions.types.miner")
case "snode":
return "Service Node"
return i18n.t("strings.transactions.types.serviceNode")
case "gov":
return "Governance"
return i18n.t("strings.transactions.types.governance")
case "stake":
return "Stake"
return i18n.t("strings.transactions.types.stake")
default:
return "-"
}
@ -251,11 +252,11 @@ export default {
let height = tx.height;
let confirms = Math.max(0, this.wallet_height - height);
if(height == 0)
return "Pending"
return this.$t("strings.transactions.types.pending")
if(confirms < Math.max(10, tx.unlock_time - height))
return `Height: ${height} (${confirms} confirm${confirms==1?'':'s'})`
return this.$t("strings.blockHeight") + `: ${height} (${confirms} confirm${confirms==1?'':'s'})`
else
return `Height: ${height} (confirmed)`
return this.$t("strings.blockHeight") + `: ${height} (confirmed)`
},
copyTxid (txid, event) {
event.stopPropagation()
@ -269,7 +270,7 @@ export default {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Txid copied to clipboard"
message: this.$t("notification.positive.copied", { item: "Txid" })
})
},
openExplorer (txid) {

View File

@ -1,40 +1,20 @@
<template>
<div class="tx-icon" v-if="type=='in'">
<q-icon name="call_received" size="40px" class="main-icon" />
<q-tooltip v-if="tooltip" anchor="center right" self="center left" :offset="[10, 10]">
Incoming transaction
</q-tooltip>
</div>
<div class="tx-icon" v-else-if="type=='out'">
<q-icon name="call_made" size="40px" class="main-icon" />
<q-tooltip v-if="tooltip" anchor="center right" self="center left" :offset="[10, 10]">
Outgoing transaction
</q-tooltip>
</div>
<div class="tx-icon" v-else-if="type=='pool'">
<q-icon name="call_received" size="40px" class="main-icon" />
<q-icon name="access_time" size="14px" class="sub-icon" />
<q-tooltip v-if="tooltip" anchor="center right" self="center left" :offset="[10, 10]">
Pending incoming transaction
</q-tooltip>
</div>
<div class="tx-icon" v-else-if="type=='pending'">
<q-icon name="call_made" size="40px" class="main-icon" />
<q-icon name="access_time" size="14px" class="sub-icon" />
<q-tooltip v-if="tooltip" anchor="center right" self="center left" :offset="[10, 10]">
Pending outgoing transaction
</q-tooltip>
</div>
<div class="tx-icon" v-else-if="type=='failed'">
<q-icon name="close" size="40px" class="main-icon" color="red" />
<q-tooltip v-if="tooltip" anchor="center right" self="center left" :offset="[10, 10]">
Failed transaction
</q-tooltip>
</div>
</template>

View File

@ -8,11 +8,11 @@
<div class="row justify-center">
<div class="funds column items-center">
<div class="balance">
<div class="text"><span>Balance</span></div>
<div class="text"><span>{{ $t("strings.lokiBalance") }}</span></div>
<div class="value"><span><FormatLoki :amount="info.balance" /></span></div>
</div>
<div class="row unlocked">
<span>Unlocked: <FormatLoki :amount="info.unlocked_balance" /></span>
<span>{{ $t("strings.lokiUnlockedShort") }}: <FormatLoki :amount="info.unlocked_balance" /></span>
</div>
</div>
</div>
@ -24,7 +24,7 @@
ref="copy"
@click="copyAddress">
<q-tooltip anchor="center right" self="center left" :offset="[5, 10]">
Copy address
{{ $t("menuItems.copyAddress") }}
</q-tooltip>
</q-btn>
</div>
@ -50,7 +50,7 @@ export default {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
message: this.$t("notification.positive.addressCopied")
})
},
},

View File

@ -1,36 +1,36 @@
<template>
<div class="wallet-settings">
<q-btn icon-right="more_vert" label="Settings" size="md" flat>
<q-btn icon-right="more_vert" :label="$t('buttons.settings')" size="md" flat>
<q-popover anchor="bottom right" self="top right">
<q-list separator link>
<q-item :disabled="!is_ready"
v-close-overlay @click.native="getPrivateKeys()">
<q-item-main>
<q-item-tile label>Show Private Keys</q-item-tile>
<q-item-tile label>{{ $t("menuItems.showPrivateKeys") }}</q-item-tile>
</q-item-main>
</q-item>
<q-item :disabled="!is_ready"
v-close-overlay @click.native="showModal('change_password')">
<q-item-main>
<q-item-tile label>Change Password</q-item-tile>
<q-item-tile label>{{ $t("menuItems.changePassword") }}</q-item-tile>
</q-item-main>
</q-item>
<q-item :disabled="!is_ready"
v-close-overlay @click.native="showModal('rescan')">
<q-item-main>
<q-item-tile label>Rescan Wallet</q-item-tile>
<q-item-tile label>{{ $t("menuItems.rescanWallet") }}</q-item-tile>
</q-item-main>
</q-item>
<q-item :disabled="!is_ready"
v-close-overlay @click.native="showModal('key_image')">
<q-item-main>
<q-item-tile label>Manage Key Images</q-item-tile>
<q-item-tile label>{{ $t("menuItems.manageKeyImages") }}</q-item-tile>
</q-item-main>
</q-item>
<q-item :disabled="!is_ready"
v-close-overlay @click.native="deleteWallet()">
<q-item-main>
<q-item-tile label>Delete Wallet</q-item-tile>
<q-item-tile label>{{ $t("menuItems.deleteWallet") }}</q-item-tile>
</q-item-main>
</q-item>
</q-list>
@ -39,11 +39,11 @@
<!-- Modals -->
<q-modal minimized class="private-key-modal" v-model="modals.private_keys.visible" @hide="closePrivateKeys()">
<div class="modal-header">Show private keys</div>
<div class="modal-header">{{ $t("titles.privateKeys") }}</div>
<div class="q-ma-lg">
<template v-if="secret.mnemonic">
<h6 class="q-mb-xs q-mt-lg">Seed words</h6>
<h6 class="q-mb-xs q-mt-lg">{{ $t("strings.seedWords") }}</h6>
<div class="row">
<div class="col">
{{ secret.mnemonic }}
@ -55,7 +55,7 @@
size="sm" icon="file_copy"
@click="copyPrivateKey('mnemonic', $event)">
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
Copy seed words
{{ $t("menuItems.copySeedWords") }}
</q-tooltip>
</q-btn>
</div>
@ -63,7 +63,7 @@
</template>
<template v-if="secret.view_key != secret.spend_key">
<h6 class="q-mb-xs">View key</h6>
<h6 class="q-mb-xs">{{ $t("strings.viewKey") }}</h6>
<div class="row">
<div class="col" style="word-break:break-all;">
{{ secret.view_key }}
@ -75,7 +75,7 @@
size="sm" icon="file_copy"
@click="copyPrivateKey('view_key', $event)">
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
Copy view key
{{ $t("menuItems.copyViewKey") }}
</q-tooltip>
</q-btn>
</div>
@ -83,7 +83,7 @@
</template>
<template v-if="!/^0*$/.test(secret.spend_key)">
<h6 class="q-mb-xs">Spend key</h6>
<h6 class="q-mb-xs">{{ $t("strings.spendKey") }}</h6>
<div class="row">
<div class="col" style="word-break:break-all;">
{{ secret.spend_key }}
@ -95,7 +95,7 @@
size="sm" icon="file_copy"
@click="copyPrivateKey('spend_key', $event)">
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
Copy spend key
{{ $t("menuItems.copySpendKey") }}
</q-tooltip>
</q-btn>
</div>
@ -106,7 +106,7 @@
<q-btn
color="primary"
@click="hideModal('private_keys')"
label="Close"
:label="$t('buttons.close')"
/>
</div>
</div>
@ -114,56 +114,59 @@
<q-modal minimized v-model="modals.rescan.visible">
<div class="modal-header">Rescan wallet</div>
<div class="modal-header">{{ $t("titles.rescanWallet") }}</div>
<div class="q-ma-lg">
<p>Select full rescan or rescan of spent outputs only.</p>
<p>{{ $t("strings.rescanModalDescription") }}</p>
<div class="q-mt-lg">
<q-radio v-model="modals.rescan.type" val="full" label="Rescan full blockchain" />
<q-radio v-model="modals.rescan.type" val="full" :label="$t('fieldLabels.rescanFullBlockchain')" />
</div>
<div class="q-mt-sm">
<q-radio v-model="modals.rescan.type" val="spent" label="Rescan spent outputs" />
<q-radio v-model="modals.rescan.type" val="spent" :label="$t('fieldLabels.rescanSpentOutputs')" />
</div>
<div class="q-mt-xl text-right">
<q-btn
flat class="q-mr-sm"
@click="hideModal('rescan')"
label="Close"
:label="$t('buttons.close')"
/>
<q-btn
color="primary"
@click="rescanWallet()"
label="Rescan"
:label="$t('buttons.rescan')"
/>
</div>
</div>
</q-modal>
<q-modal class="key-image-modal" minimized v-model="modals.key_image.visible">
<div class="modal-header">{{modals.key_image.type}} key images</div>
<div class="modal-header">
<!-- Export/Import key images -->
{{ $t("dialog.keyImages.title", { type: $t(`dialog.keyImages.${modals.key_image.type.toLowerCase()}`) }) }}
</div>
<div class="q-ma-lg">
<div class="row q-mb-md">
<div class="q-mr-xl">
<q-radio v-model="modals.key_image.type" val="Export" label="Export" />
<q-radio v-model="modals.key_image.type" val="Export" :label="$t('dialog.keyImages.export')" />
</div>
<div>
<q-radio v-model="modals.key_image.type" val="Import" label="Import" />
<q-radio v-model="modals.key_image.type" val="Import" :label="$t('dialog.keyImages.import')" />
</div>
</div>
<template v-if="modals.key_image.type == 'Export'">
<LokiField class="q-mt-lg" label="Key image export directory" disable-hover>
<LokiField class="q-mt-lg" :label="$t('fieldLabels.keyImages.exportDirectory')" disable-hover>
<q-input v-model="modals.key_image.export_path" disable hide-underline />
<input type="file" webkitdirectory directory id="keyImageExportPath" v-on:change="setKeyImageExportPath" ref="keyImageExportSelect" hidden />
<q-btn color="secondary" v-on:click="selectKeyImageExportPath">Browse</q-btn>
<q-btn color="secondary" v-on:click="selectKeyImageExportPath">{{ $t("buttons.browse") }}</q-btn>
</LokiField>
</template>
<template v-if="modals.key_image.type == 'Import'">
<LokiField class="q-mt-lg" label="Key image import file" disable-hover>
<LokiField class="q-mt-lg" :label="$t('fieldLabels.keyImages.importFile')" disable-hover>
<q-input v-model="modals.key_image.import_path" disable hide-underline />
<input type="file" id="keyImageImportPath" v-on:change="setKeyImageImportPath" ref="keyImageImportSelect" hidden />
<q-btn color="secondary" v-on:click="selectKeyImageImportPath">Browse</q-btn>
<q-btn color="secondary" v-on:click="selectKeyImageImportPath">{{ $t("buttons.browse") }}</q-btn>
</LokiField>
</template>
@ -171,42 +174,42 @@
<q-btn
flat class="q-mr-sm"
@click="hideModal('key_image')"
label="Close"
:label="$t('buttons.close')"
/>
<q-btn
color="primary"
@click="doKeyImages()"
:label="modals.key_image.type"
:label="$t('buttons.' + modals.key_image.type.toLowerCase())"
/>
</div>
</div>
</q-modal>
<q-modal minimized v-model="modals.change_password.visible" @hide="clearChangePassword()">
<div class="modal-header">Change password</div>
<div class="modal-header">{{ $t("titles.changePassword") }}</div>
<div class="q-ma-lg">
<q-field>
<q-input v-model="modals.change_password.old_password" type="password" float-label="Old Password" :dark="theme=='dark'" />
<q-input v-model="modals.change_password.old_password" type="password" :float-label="$t('fieldLabels.oldPassword')" :dark="theme=='dark'" />
</q-field>
<q-field>
<q-input v-model="modals.change_password.new_password" type="password" float-label="New Password" :dark="theme=='dark'" />
<q-input v-model="modals.change_password.new_password" type="password" :float-label="$t('fieldLabels.newPassword')" :dark="theme=='dark'" />
</q-field>
<q-field>
<q-input v-model="modals.change_password.new_password_confirm" type="password" float-label="Confirm New Password" :dark="theme=='dark'" />
<q-input v-model="modals.change_password.new_password_confirm" type="password" :float-label="$t('fieldLabels.confirmNewPassword')" :dark="theme=='dark'" />
</q-field>
<div class="q-mt-xl text-right">
<q-btn
flat class="q-mr-sm"
@click="hideModal('change_password')"
label="Close"
:label="$t('buttons.close')"
/>
<q-btn
color="primary"
@click="doChangePassword()"
label="Change"
:label="$t('buttons.change')"
/>
</div>
</div>
@ -229,6 +232,9 @@ export default {
wallet_data_dir: state => state.gateway.app.config.app.wallet_data_dir,
is_ready (state) {
return this.$store.getters["gateway/isReady"]
},
locale (state) {
return this.$q.i18n.getLocale()
}
}),
data () {
@ -272,7 +278,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: this.secret.mnemonic
message: this.$t(this.secret.mnemonic)
})
this.$store.commit("gateway/set_wallet_data", {
secret: {
@ -311,41 +317,42 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Error copying private key",
message: this.$t("notification.errors.copyingPrivateKeys"),
})
return
}
clipboard.writeText(this.secret[type])
let type_human = type.substring(0,1).toUpperCase()+type.substring(1).replace("_"," ")
let type_key = "seedWords"
if (type === "spend_key") {
type_key = "spendKey"
} else if (type === "view_key") {
type_key = "viewKey"
}
const type_title = this.$t("dialog.copyPrivateKeys." + type_key)
this.$q.dialog({
title: "Copy "+type_human,
message: "Be careful who you send your private keys to as they control your funds.",
title: this.$t("dialog.copyPrivateKeys.title", { type: type_title }) ,
message: this.$t("dialog.copyPrivateKeys.message"),
ok: {
label: "OK"
label: this.$t("dialog.buttons.ok")
},
}).then(() => {
}).catch(() => null).then(() => {
this.$q.notify({
type: "positive",
timeout: 1000,
message: type_human+" copied to clipboard"
})
}).catch(() => {
this.$q.notify({
type: "positive",
timeout: 1000,
message: type_human+" copied to clipboard"
message: this.$t("notification.positive.copied", { item: this.$t("strings." + type_key) })
})
})
},
getPrivateKeys () {
if(!this.is_ready) return
this.showPasswordConfirmation({
title: "Show private keys",
noPasswordMessage: "Do you want to view your private keys?",
title: this.$t("dialog.showPrivateKeys.title"),
noPasswordMessage: this.$t("dialog.showPrivateKeys.message"),
ok: {
label: "SHOW"
label: this.$t("dialog.showPrivateKeys.ok")
},
}).then(password => {
this.$gateway.send("wallet", "get_private_keys", {password})
@ -368,14 +375,14 @@ export default {
this.hideModal("rescan")
if(this.modals.rescan.type == "full") {
this.$q.dialog({
title: "Rescan wallet",
message: "Warning: Some information about previous transactions\nsuch as the recipient's address will be lost.",
title: this.$t("dialog.rescan.title"),
message: this.$t("dialog.rescan.message"),
ok: {
label: "RESCAN"
label: this.$t("dialog.rescan.ok")
},
cancel: {
flat: true,
label: "CANCEL",
label: this.$t("dialog.buttons.cancel"),
color: this.theme=="dark"?"white":"dark"
}
}).then(password => {
@ -401,11 +408,13 @@ export default {
doKeyImages () {
this.hideModal("key_image")
const type = this.$t(`dialog.keyImages.${this.modals.key_image.type.toLowerCase()}`)
this.showPasswordConfirmation({
title: this.modals.key_image.type + " key images",
noPasswordMessage: `Do you want to ${this.modals.key_image.type.toLowerCase()} key images?`,
title: this.$t("dialog.keyImages.title", { type }),
noPasswordMessage: this.$t("dialog.keyImages.message", { type: type.toLocaleLowerCase(this.locale) }),
ok: {
label: this.modals.key_image.type
label: type.toLocaleUpperCase(this.locale)
},
}).then(password => {
if(this.modals.key_image.type == "Export")
@ -426,13 +435,13 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "New password must be different"
message: this.$t("notification.errors.newPasswordSame")
})
} else if(new_password != new_password_confirm) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "New passwords do not match"
message: this.$t("notification.errors.newPasswordNoMatch")
})
} else {
this.hideModal("change_password")
@ -448,15 +457,15 @@ export default {
deleteWallet () {
if(!this.is_ready) return
this.$q.dialog({
title: "Delete wallet",
message: "Are you absolutely sure you want to delete your wallet?\nMake sure you have your private keys backed up.\nTHIS PROCESS IS NOT REVERSIBLE!",
title: this.$t("dialog.deleteWallet.title"),
message: this.$t("dialog.deleteWallet.message"),
ok: {
label: "DELETE",
label: this.$t("dialog.deleteWallet.ok"),
color: "red"
},
cancel: {
flat: true,
label: "CANCEL",
label: this.$t("dialog.buttons.cancel"),
color: this.theme=="dark"?"white":"dark"
}
}).then(() => {
@ -464,19 +473,19 @@ export default {
}).then(hasPassword => {
if (!hasPassword) return ""
return this.$q.dialog({
title: "Delete wallet",
message: "Enter wallet password to continue.",
title: this.$t("dialog.deleteWallet.title"),
message: this.$t("dialog.password.message"),
prompt: {
model: "",
type: "password"
},
ok: {
label: "DELETE",
label: this.$t("dialog.deleteWallet.ok"),
color: "red"
},
cancel: {
flat: true,
label: "CANCEL",
label: this.$t("dialog.buttons.cancel"),
color: this.theme=="dark"?"white":"dark"
}
})

View File

@ -71,6 +71,9 @@ footer,
}
.q-btn {
text-transform: none;
}
.layout-padding {
padding: 15px 16px !important;
@ -181,10 +184,9 @@ footer,
display: inline-block;
.text {
font-size: 13px;
font-size: 14px;
margin-top: 8px;
color: #cecece;
text-transform: uppercase;
}
.value {
@ -237,7 +239,6 @@ footer,
.status-text {
font-weight: bold;
text-transform: uppercase;
}
.ready {

View File

@ -2,6 +2,7 @@ import { ipcRenderer } from "electron"
import { Notify, Dialog, Loading, LocalStorage } from "quasar"
import { EventEmitter } from "events"
import { SCEE } from "./SCEE-Node"
import { i18n } from "src/plugins/i18n"
export class Gateway extends EventEmitter {
constructor (app, router) {
@ -59,15 +60,18 @@ export class Gateway extends EventEmitter {
return
}
this.closeDialog = true
const key = restart ? "restart" : "exit"
Dialog.create({
title: restart ? "Restart" : "Exit",
title: i18n.t(`dialog.${key}.title`),
message: msg,
ok: {
label: restart ? "RESTART" : "EXIT"
label: i18n.t(`dialog.${key}.ok`)
},
cancel: {
flat: true,
label: "CANCEL",
label: i18n.t("dialog.buttons.cancel"),
color: this.app.store.state.gateway.app.config.appearance.theme == "dark" ? "white" : "dark"
}
}).then(() => {
@ -149,7 +153,17 @@ export class Gateway extends EventEmitter {
timeout: 1000,
message: ""
}
Notify.create(Object.assign(notification, decrypted_data.data))
const { data } = decrypted_data
if (data.i18n) {
if (typeof data.i18n === "string") {
notification.message = i18n.t(data.i18n)
} else if (Array.isArray(data.i18n)) {
notification.message = i18n.t(...data.i18n)
}
}
Notify.create(Object.assign(notification, data))
break
case "show_loading":

427
src/i18n/en-us.js Normal file
View File

@ -0,0 +1,427 @@
export default {
buttons: {
// All button text is uppercased in the gui
all: "ALL",
back: "BACK",
browse: "BROWSE",
cancel: "CANCEL",
change: "CHANGE",
close: "CLOSE",
contacts: "CONTACTS",
copyAddress: "COPY ADDRESS",
createWallet: "CREATE WALLET",
delete: "DELETE",
edit: "EDIT",
export: "EXPORT",
import: "IMPORT",
importWallet: "IMPORT WALLET | IMPORT WALLETS",
next: "NEXT",
openWallet: "OPEN WALLET",
receive: "RECEIVE",
registerServiceNode: "REGISTER SERVICE NODE",
rescan: "RESCAN",
restoreWallet: "RESTORE WALLET",
save: "SAVE",
saveTxNotes: "SAVE TX NOTES",
selectLocation: "SELECT LOCATION",
selectWalletFile: "SELECT WALLET FILE",
send: "SEND",
sendCoins: "SEND COINS",
serviceNode: "SERVICE NODE",
settings: "SETTINGS",
showQRCode: "SHOW QR CODE",
showTxDetails: "SHOW TX DETAILS",
stake: "STAKE",
unlockServiceNode: "UNLOCK SERVICE NODE",
viewOnExplorer: "VIEW ON EXPLORER"
},
dialog: {
// Generic buttons
buttons: {
ok: "OK",
cancel: "CANCEL",
open: "OPEN"
},
// Dialogs
banPeer: {
title: "Ban peer",
peerDetailsTitle: "Peer details",
message: "Enter length to ban peer in seconds.\nDefault 3600 = 1 hour.",
ok: "Ban peer"
},
copyAddress: {
title: "Copy address",
message: "There is a payment id associated with this address.\nBe sure to copy the payment id separately."
},
copyPrivateKeys: {
// Copy {seedWords/viewKey/spendKey}
title: "Copy {type}",
message: "Be careful who you send your private keys to as they control your funds.",
seedWords: "Seed Words",
viewKey: "View Key",
spendKey: "Spend Key"
},
deleteWallet: {
title: "Delete wallet",
message: "Are you absolutely sure you want to delete your wallet?\nMake sure you have your private keys backed up.\nTHIS PROCESS IS NOT REVERSIBLE!",
ok: "DELETE"
},
exit: {
title: "Exit",
message: "Are you sure you want to exit?",
ok: "EXIT"
},
keyImages: {
title: "{type} key images",
message: "Do you want to {type} key images?",
export: "Export",
import: "Import"
},
noPassword: {
title: "No password set",
message: "Are you sure you want to create a wallet with no password?",
ok: "YES"
},
password: {
title: "Password",
message: "Enter wallet password to continue."
},
registerServiceNode: {
title: "Register service node",
message: "Do you want to register the service node?",
ok: "REGISTER"
},
rescan: {
title: "Rescan wallet",
message: "Warning: Some information about previous transactions\nsuch as the recipient's address will be lost.",
ok: "RESCAN"
},
restart: {
title: "Restart",
message: "Changes require restart. Would you like to restart now?",
ok: "RESTART"
},
showPrivateKeys: {
title: "Show private keys",
message: "Do you want to view your private keys?",
ok: "SHOW"
},
stake: {
title: "Stake",
message: "Do you want to stake?",
ok: "STAKE"
},
switchWallet: {
title: "Switch wallet",
closeMessage: "Are you sure you want to close the current wallet?",
restartMessage: "The wallet RPC is currently syncing. \nIf you wish to switch wallets then you must restart the application. \nYou will lose your syncing progress and have to rescan the blockchain again."
},
transactionDetails: {
title: "Transaction details",
ok: "CLOSE"
},
transfer: {
title: "Transfer",
message: "Do you want to send the transaction?",
ok: "SEND"
},
unlockConfirm: {
title: "Confirm unlock",
ok: "UNLOCK"
},
unlockServiceNode: {
title: "Unlock service node",
confirmTitle: "Confirm unlock",
message: "Do you want to unlock the service node?",
ok: "UNLOCK"
}
},
fieldLabels: {
// Field labels are also all uppercased
address: "ADDRESS",
amount: "AMOUNT",
awardRecepientAddress: "AWARD RECEPIENT ADDRESS",
confirmPassword: "CONFIRM PASSWORD",
daemonLogLevel: "DAEMON LOG LEVEL",
daemonP2pPort: "DAEMON P2P PORT",
daemonZMQPort: "DAEMON ZMQ PORT",
dataStoragePath: "DATA STORAGE PATH",
filterTransactionType: "FILTER BY TRANSACTION TYPE",
filterTxId: "FILTER BY TXID",
internalWalletPort: "INTERNAL WALLET PORT",
keyImages: {
exportDirectory: "KEY IMAGE EXPORT DIRECTORY",
importFile: "KEY IMAGE IMPORT FILE"
},
limitDownloadRate: "LIMIT DOWNLOAD RATE",
limitUploadRate: "LIMIT UPLOAD RATE",
localDaemonIP: "LOCAL DAEMON IP",
localDaemonPort: "LOCAL DAEMON PORT",
maxIncomingPeers: "MAX INCOMING PEERS",
maxOutgoingPeers: "MAX OUTGOING PEERS",
mnemonicSeed: "MNEMONIC SEED",
name: "NAME",
newWalletName: "NEW WALLET NAME",
notes: "NOTES",
optional: "OPTIONAL",
password: "PASSWORD",
paymentId: "PAYMENT ID",
priority: "PRIORITY",
remoteNodeHost: "REMOTE NODE HOST",
remoteNodePort: "REMOTE NODE PORT",
restoreFromBlockHeight: "RESTORE FROM BLOCK HEIGHT",
restoreFromDate: "RESTORE FROM DATE",
seedLanguage: "SEED LANGUAGE",
serviceNodeCommand: "SERVICE NODE COMMAND",
serviceNodeKey: "SERVICE NODE KEY",
walletFile: "WALLET FILE",
walletLogLevel: "WALLET LOG LEVEL",
walletName: "WALLET NAME",
walletRPCPort: "WALLET RPC PORT",
walletStoragePath: "WALLET STORAGE PATH",
// These are specific labels which do not get uppercased
confirmNewPassword: "Confirm New Password",
newPassword: "New Password",
oldPassword: "Old Password",
rescanFullBlockchain: "Rescan full blockchain",
rescanSpentOutputs: "Rescan spent outputs",
transactionNotes: "Transaction Notes"
},
footer: {
ready: "READY",
scanning: "SCANNING",
status: "Status",
syncing: "SYNCING"
},
menuItems: {
about: "About",
changePassword: "Change Password",
copyAddress: "Copy address",
copyQR: "Copy QR code",
copySeedWords: "Copy seed words",
copySpendKey: "Copy spend key",
copyTransactionId: "Copy transaction ID",
copyViewKey: "Copy view key",
createNewWallet: "Create new wallet",
deleteWallet: "Delete Wallet",
exit: "Exit Loki GUI Wallet",
importOldGUIWallet: "Import wallets from old GUI",
manageKeyImages: "Manage Key Images",
openWallet: "Open wallet",
rescanWallet: "Rescan Wallet",
restoreWalletFile: "Restore wallet from file",
restoreWalletSeed: "Restore wallet from seed",
saveQR: "Save QR code to file",
sendToThisAddress: "Send to this address",
settings: "Settings",
showDetails: "Show details",
showPrivateKeys: "Show Private Keys",
showQRCode: "Show QR Code",
switchWallet: "Switch Wallet",
viewOnExplorer: "View on explorer"
},
notification: {
positive: {
addressCopied: "Address copied to clipboard",
bannedPeer: "Banned {host} until {time}",
copied: "{item} copied to clipboard",
itemSaved: "{item} saved to {filename}",
keyImages: {
exported: "Key images exported to {filename}",
imported: "Key images imported"
},
passwordUpdated: "Password updated",
qrCopied: "QR code copied to clipboard",
registerServiceNodeSuccess: "Successfully registered service node",
sendSuccess: "Transaction successfully sent",
stakeSuccess: "Successfully staked",
transactionNotesSaved: "Transaction notes saved"
},
errors: {
banningPeer: "Error banning peer",
cannotAccessRemoteNode: "Could not access remote node, please try another remote node",
changingPassword: "Error changing password",
copyWalletFail: "Failed to copy wallet",
copyingPrivateKeys: "Error copying private keys",
dataPathNotFound: "Data storage path not found",
differentNetType: "Remote node is using a different nettype",
enterSeedWords: "Enter seed words",
enterWalletName: "Enter a wallet name",
errorSavingItem: "Error saving {item}",
failedServiceNodeUnlock: "Failed to unlock service node",
failedWalletImport: "Failed to import wallet",
failedWalletOpen: "Failed to open wallet. Please try again.",
internalError: "Internal error",
invalidAddress: "Address not valid",
invalidAmount: "Amount not valid",
invalidOldPassword: "Invalid old password",
invalidPassword: "Invalid password",
invalidPaymentId: "Payment id not valid",
invalidPrivateViewKey: "Invalid private viewkey",
invalidPublicAddress: "Invalid public address",
invalidRestoreDate: "Invalid restore date",
invalidRestoreHeight: "Invalid restore height",
invalidSeedLength: "Invalid seed word length",
invalidServiceNodeCommand: "Please enter the service node registration command",
invalidServiceNodeKey: "Service node key not valid",
invalidWalletPath: "Invalid wallet path",
keyImages: {
exporting: "Error exporting key images",
reading: "Error reading key images",
importing: "Error importing key images"
},
negativeAmount: "Amount cannot be negative",
newPasswordNoMatch: "New passwords do not match",
newPasswordSame: "New password must be different",
notEnoughBalance: "Not enough unlocked balance",
passwordNoMatch: "Passwords do not match",
remoteCannotBeReached: "Remote daemon cannot be reached",
unknownError: "An unknown error occurred",
walletAlreadyExists: "Wallet with name already exists",
walletPathNotFound: "Wallet data storage path not found",
zeroAmount: "Amount must be greater than zero"
},
warnings: {
noKeyImageExport: "No key images found to export",
usingLocalNode: "Could not access remote node, switching to local only",
usingRemoteNode: "lokid not found, using remote node"
}
},
placeholders: {
additionalNotes: "Additional notes",
addressBookName: "Name that belongs to this address",
enterAnId: "Enter an ID",
hexCharacters: "{count} hexadecimal characters",
mnemonicSeed: "25 (or 24) word mnemonic seed",
selectAFile: "Please select a file",
transactionNotes: "Additional notes to attach to the transaction",
walletName: "A name for your wallet",
walletPassword: "An optional password for the wallet"
},
strings: {
addAddressBookEntry: "Add address book entry",
addressBookDetails: "Address book details",
addressBookIsEmpty: "Address book is empty",
addresses: {
myPrimaryAddress: "My primary address",
myUnusedAddresses: "My unused addresses",
myUsedAddresses: "My used addresses",
notYourAddress: "Not your address!",
primaryAddress: "Primary address",
subAddress: "Sub-address",
yourPrimaryAddress: "Your primary address",
yourUnusedAddress: "Your unused address",
yourUsedAddress: "Your used address"
},
advancedOptions: "Advanced Options",
bannedPeers: {
title: "Banned peers (bans will cleared if wallet is restarted)",
bannedUntil: "Banned until {time}"
},
blockHeight: "Height",
closing: "Closing",
connectingToBackend: "Connecting to backend",
daemon: {
local: {
title: "Local Daemon Only",
description: "Full security, wallet will download the full blockchain. You will not be able to transact until sync is completed."
},
localRemote: {
title: "Local + Remote Daemon",
description: "Get started quickly with this default option. Wallet will download the full blockchain, but use a remote node while syncing."
},
remote: {
title: "Remote Daemon Only",
description: "Less security, wallet will connect to a remote node to make all transactions."
}
},
destinationUnknown: "Destination Unknown",
editAddressBookEntry: "Edit address book entry",
loadingSettings: "Loading settings",
lokiBalance: "Balance",
lokiUnlockedBalance: "Unlocked balance",
lokiUnlockedShort: "Unlocked",
noTransactionsFound: "No transactions found",
notes: "Notes",
numberOfUnspentOutputs: "Number of unspent outputs",
paymentID: "Payment ID",
peerList: "Peer list",
readingWalletList: "Reading wallet list",
recentIncomingTransactionsToAddress: "Recent incoming transactions to this address",
recentTransactionsWithAddress: "Recent transactions with this address",
rescanModalDescription: "Select full rescan or rescan of spent outputs only.",
saveSeedWarning: "Please copy and save these in a secure location!",
saveToAddressBook: "Save to address book",
seedWords: "Seed words",
serviceNodeRegistrationDescription: "Enter the {registerCommand} command produced by the daemon that is registering to become a Service Node using the \"{prepareCommand}\" command",
spendKey: "Spend key",
startingDaemon: "Starting daemon",
startingWallet: "Starting wallet",
switchToDateSelect: "Switch to date select",
switchToHeightSelect: "Switch to height select",
transactionID: "Transaction ID",
transactions: {
amount: "Amount",
description: "{type} transaction",
fee: "Fee",
paidBySender: "paid by sender",
received: "Received",
sent: "Sent",
sentTo: "{type} transaction sent to",
timestamp: "Timestamp",
types: {
all: "All",
incoming: "Incoming",
outgoing: "Outgoing",
pending: "Pending",
pendingIncoming: "Pending incoming",
pendingOutgoing: "Pending outgoing",
miner: "Miner",
serviceNode: "Service Node",
governance: "Governance",
stake: "Stake",
failed: "Failed"
}
},
unspentOutputs: "Unspent outputs",
userNotUsedAddress: "You have not used this address",
userUsedAddress: "You have used this address",
viewKey: "View key",
viewOnlyMode: "View only mode. Please load full wallet in order to send coins."
},
titles: {
addressBook: "Address book",
addressDetails: "Address details",
changePassword: "Change password",
configure: "Configure",
privateKeys: "Private keys",
rescanWallet: "Rescan wallet",
serviceNode: {
registration: "REGISTRATION",
staking: "STAKING",
unlock: "UNLOCK"
},
settings: {
title: "Settings",
tabs: {
general: "General",
peers: "Peers"
}
},
transactionDetails: "Transaction details",
transactions: "Transactions",
wallet: {
createNew: "Create new wallet",
createdOrRestored: "Wallet created/restored",
importFromFile: "Import wallet from file",
importFromLegacyGUI: "Import wallet from legacy GUI",
importFromOldGUI: "Import wallet from old GUI",
restoreFromSeed: "Restore wallet from seed",
restoreViewOnly: "Restore view-only wallet"
},
welcome: "Welcome",
yourWallets: "Your Wallets"
}
}

View File

@ -1,7 +0,0 @@
// This is just an example,
// so you can safely delete all default props below
export default {
failed: "Action failed",
success: "Action was successful"
}

View File

@ -50,19 +50,19 @@ export default {
page_title () {
switch(this.$route.name) {
case "wallet-create":
return "Create new wallet"
return this.$t("titles.wallet.createNew")
case "wallet-restore":
return "Restore wallet from seed"
return this.$t("titles.wallet.restoreFromSeed")
case "wallet-import":
return "Import wallet from file"
return this.$t("titles.wallet.importFromFile")
case "wallet-import-view-only":
return "Restore view-only wallet"
return this.$t("titles.wallet.restoreViewOnly")
case "wallet-import-legacy":
return "Import wallet from legacy gui"
return this.$t("titles.wallet.importFromLegacyGUI")
case "wallet-import-old-gui":
return "Import wallets from old GUI"
return this.$t("titles.wallet.importFromOldGUI")
case "wallet-created":
return "Wallet created/restored"
return this.$t("titles.wallet.createdOrRestored")
default:
case "wallet-select":

View File

@ -25,7 +25,7 @@
<router-link to="/wallet/send">
<q-btn
class="large-btn"
label="Send"
:label="$t('buttons.send')"
size="md"
icon-right="arrow_right_alt"
align="left"
@ -34,7 +34,7 @@
<router-link to="/wallet/receive">
<q-btn
class="large-btn"
label="Receive"
:label="$t('buttons.receive')"
size="md"
icon-right="save_alt"
align="left"
@ -43,7 +43,7 @@
<router-link to="/wallet/servicenode">
<q-btn
class="large-btn"
label="Service node"
:label="$t('buttons.serviceNode')"
size="md"
icon-right="router"
align="left"
@ -123,9 +123,10 @@ export default {
}
.large-btn {
width: 160px;
min-width: 160px;
.q-btn-inner > *:last-child {
margin-left: auto;
padding-left: 8px;
}
}

View File

@ -22,11 +22,11 @@ export default {
return this.$q.dialog({
cancel: {
flat: true,
label: "CANCEL",
label: this.$t("dialog.buttons.cancel"),
color: this.theme === "dark" ? "white" : "dark"
},
...other,
message: hasPassword ? "Enter wallet password to continue." : noPasswordMessage,
message: hasPassword ? this.$t("dialog.password.message") : noPasswordMessage,
prompt: hasPassword ? {
model: "",
type: "password"

View File

@ -54,21 +54,21 @@ export default {
this.$router.replace({ path: "wallet-select" });
break;
case 1:
this.message = "Connecting to backend"
this.message = this.$t("strings.connectingToBackend")
this.$refs.backend.className = "pulse"
this.$refs.settings.className = "grey"
this.$refs.daemon.className = "grey"
this.$refs.wallet.className = "grey"
break;
case 2:
this.message = "Loading settings"
this.message = this.$t("strings.loadingSettings")
this.$refs.backend.className = "solid"
this.$refs.settings.className = "pulse"
this.$refs.daemon.className = "grey"
this.$refs.wallet.className = "grey"
break;
case 3:
this.message = "Starting daemon"
this.message = this.$t("strings.startingDaemon")
this.$refs.backend.className = "solid"
this.$refs.settings.className = "solid"
this.$refs.daemon.className = "pulse"
@ -81,18 +81,18 @@ export default {
this.$q.notify({
type: "warning",
timeout: 2000,
message: "Warning: lokid not found, using remote node"
message: "Warning: " + this.$t("notification.warnings.usingRemoteNode")
})
break;
case 6:
this.message = "Starting wallet"
this.message = this.$t("strings.startingWallet")
this.$refs.backend.className = "solid"
this.$refs.settings.className = "solid"
this.$refs.daemon.className = "solid"
this.$refs.wallet.className = "pulse"
break;
case 7:
this.message = "Reading wallet list"
this.message = this.$t("strings.readingWalletList")
this.$refs.backend.className = "solid"
this.$refs.settings.className = "solid"
this.$refs.daemon.className = "solid"

View File

@ -9,7 +9,7 @@
</div>
<div class="message">
Closing...
{{ $t("strings.closing") }}...
</div>
</div>
</div>

View File

@ -3,7 +3,7 @@
<q-stepper class="no-shadow" ref="stepper" :color="theme == 'dark' ? 'light' : 'dark'" dark @step="onStep">
<q-step default title="Welcome" class="first-step">
<q-step default :title="$t('titles.welcome')" class="first-step">
<div class="welcome-container">
<img src="statics/loki.svg" height="100" class="q-mb-md">
@ -25,7 +25,7 @@
</q-step>
<q-step title="Configure">
<q-step :title="$t('titles.configure')">
<SettingsGeneral randomise_remote ref="settingsGeneral" />
</q-step>
</q-stepper>
@ -36,7 +36,7 @@
<q-btn
flat
@click="clickPrev()"
label="Back"
:label="$t('buttons.back')"
/>
</div>
<div>
@ -44,7 +44,7 @@
class="q-ml-sm"
color="primary"
@click="clickNext()"
label="Next"
:label="$t('buttons.next')"
/>
</div>
</div>

View File

@ -1,17 +1,17 @@
<template>
<q-page class="create-wallet">
<div class="fields q-mx-md q-mt-md">
<LokiField label="Wallet name" :error="$v.wallet.name.$error">
<LokiField :label="$t('fieldLabels.walletName')" :error="$v.wallet.name.$error">
<q-input
v-model="wallet.name"
@blur="$v.wallet.name.$touch"
:dark="theme=='dark'"
placeholder="A name for your wallet"
:placeholder="$t('placeholders.walletName')"
hide-underline
/>
</LokiField>
<LokiField label="Seed Language">
<LokiField :label="$t('fieldLabels.seedLanguage')">
<q-select
v-model="wallet.language"
:options="languageOptions"
@ -20,17 +20,17 @@
/>
</LokiField>
<LokiField label="Password" optional>
<LokiField :label="$t('fieldLabels.password')" optional>
<q-input
v-model="wallet.password"
type="password"
:dark="theme=='dark'"
placeholder="An optional password for the wallet"
:placeholder="$t('placeholders.walletPassword')"
hide-underline
/>
</LokiField>
<LokiField label="Confirm Password">
<LokiField :label="$t('fieldLabels.confirmPassword')">
<q-input
v-model="wallet.password_confirm"
type="password"
@ -40,7 +40,7 @@
</LokiField>
<q-field>
<q-btn color="primary" @click="create" label="Create wallet" />
<q-btn color="primary" @click="create" :label="$t('buttons.createWallet')" />
</q-field>
</div>
@ -120,7 +120,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Enter a wallet name"
message: this.$t("notification.errors.enterWalletName")
})
return
}
@ -128,7 +128,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Passwords do not match"
message: this.$t("notification.errors.passwordNoMatch")
})
return
}
@ -137,14 +137,14 @@ export default {
let passwordPromise = Promise.resolve();
if (!this.wallet.password) {
passwordPromise = this.$q.dialog({
title: "No password set",
message: "Are you sure you want to create a wallet with no password?",
title: this.$t("dialog.noPassword.title"),
message: this.$t("dialog.noPassword.message"),
ok: {
label: "YES",
label: this.$t("dialog.noPassword.ok"),
},
cancel: {
flat: true,
label: "CANCEL",
label: this.$t("dialog.buttons.cancel"),
color: this.theme === "dark" ? "white" : "dark"
},
})

View File

@ -10,9 +10,9 @@
<q-btn
color="primary" style="width:25px;"
size="sm" icon="file_copy"
@click="copyPrivateKey('view_key', $event)">
@click="copyAddress">
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
Copy view key
{{ $t("menuItems.copyAddress") }}
</q-tooltip>
</q-btn>
</div>
@ -21,12 +21,12 @@
<template v-if="secret.mnemonic">
<div class="seed-box col">
<h6 class="q-mb-xs q-mt-lg">Seed words</h6>
<h6 class="q-mb-xs q-mt-lg">{{ $t("strings.seedWords") }}</h6>
<div class="seed q-my-lg">
{{ secret.mnemonic }}
</div>
<div class="q-my-md warning">
Please copy and save these in a secure location!
{{ $t("strings.saveSeedWarning") }}
</div>
<div>
<q-btn
@ -42,7 +42,7 @@
<q-collapsible label="Advanced" header-class="q-mt-sm non-selectable row reverse advanced-options-label">
<template v-if="secret.view_key != secret.spend_key">
<h6 class="q-mb-xs title">View key</h6>
<h6 class="q-mb-xs title">{{ $t("strings.viewKey") }}</h6>
<div class="row">
<div class="col" style="word-break:break-all;">
{{ secret.view_key }}
@ -53,7 +53,7 @@
size="sm" icon="file_copy"
@click="copyPrivateKey('view_key', $event)">
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
Copy view key
{{ $t("menuItems.copyViewKey") }}
</q-tooltip>
</q-btn>
</div>
@ -61,7 +61,7 @@
</template>
<template v-if="!/^0*$/.test(secret.spend_key)">
<h6 class="q-mb-xs title">Spend key</h6>
<h6 class="q-mb-xs title">{{ $t("strings.spendKey") }}</h6>
<div class="row">
<div class="col" style="word-break:break-all;">
{{ secret.spend_key }}
@ -72,7 +72,7 @@
size="sm" icon="file_copy"
@click="copyPrivateKey('spend_key', $event)">
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
Copy spend key
{{ $t("menuItems.copySpendKey") }}
</q-tooltip>
</q-btn>
</div>
@ -80,7 +80,7 @@
</template>
</q-collapsible>
<q-btn class="q-mt-lg" color="primary" @click="open" label="Open wallet" />
<q-btn class="q-mt-lg" color="primary" @click="open" :label="$t('buttons.openWallet')" />
</q-page>
</template>
@ -123,34 +123,43 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Error copying private key",
message: this.$t("notification.errors.copyingPrivateKeys"),
})
return
}
clipboard.writeText(this.secret[type])
let type_human = type.substring(0,1).toUpperCase()+type.substring(1).replace("_"," ")
let type_key = "seedWords"
if (type === "spend_key") {
type_key = "spendKey"
} else if (type === "view_key") {
type_key = "viewKey"
}
const type_title = this.$t("dialog.copyPrivateKeys." + type_key)
this.$q.dialog({
title: "Copy "+type_human,
message: "Be careful who you send your private keys to as they control your funds.",
title: this.$t("dialog.copyPrivateKeys.title", { type: type_title }) ,
message: this.$t("dialog.copyPrivateKeys.message"),
ok: {
label: "OK"
label: this.$t("dialog.buttons.ok")
},
}).then(() => {
}).catch(() => null).then(() => {
this.$q.notify({
type: "positive",
timeout: 1000,
message: type_human+" copied to clipboard"
})
}).catch(() => {
this.$q.notify({
type: "positive",
timeout: 1000,
message: type_human+" copied to clipboard"
message: this.$t("notification.positive.copied", { item: this.$t("strings." + type_key) })
})
})
},
copyAddress() {
clipboard.writeText(this.info.address)
this.$q.notify({
type: "positive",
timeout: 1000,
message: this.$t("notification.positive.addressCopied")
})
}
},
components: {
AddressHeader,
@ -182,7 +191,6 @@ export default {
.seed {
font-size: 24px;
text-transform: uppercase;
font-weight: 600;
}

View File

@ -106,7 +106,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Enter a wallet name"
message: this.$t("notification.errors.enterWalletName")
})
return
}
@ -114,7 +114,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Passwords do not match"
message: this.$t("notification.errors.passwordNoMatch")
})
return
}

View File

@ -16,7 +16,7 @@
</q-list>
<q-field>
<q-btn color="primary" @click="import_wallets" label="Import wallets" :disable="selectedWallets.length === 0"/>
<q-btn color="primary" @click="import_wallets" :label="$tc('buttons.importWallet', 2)" :disable="selectedWallets.length === 0"/>
</q-field>
</div>
</q-page>
@ -76,7 +76,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 3000,
message: `Failed to import wallet: ${wallet}`
message: this.$t("notification.errors.failedWalletImport") + `: ${wallet}`
})
})
}

View File

@ -161,7 +161,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Enter a wallet name"
message: this.$t("notification.errors.enterWalletName")
})
return
}
@ -169,7 +169,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Invalid public address"
message: this.$t("notification.errors.invalidPublicAddress")
})
return
}
@ -178,7 +178,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Invalid private viewkey"
message: this.$t("notification.errors.invalidPrivateViewKey")
})
return
}
@ -187,7 +187,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Invalid restore height"
message: this.$t("notification.errors.invalidRestoreHeight")
})
return
}
@ -195,7 +195,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Passwords do not match"
message: this.$t("notification.errors.passwordNoMatch")
})
return
}

View File

@ -2,32 +2,49 @@
<q-page>
<div class="q-mx-md import-wallet">
<LokiField label="New wallet name" :error="$v.wallet.name.$error">
<LokiField :label="$t('fieldLabels.newWalletName')" :error="$v.wallet.name.$error">
<q-input
v-model="wallet.name"
placeholder="A name for your wallet"
:placeholder="$t('placeholders.walletName')"
@blur="$v.wallet.name.$touch"
:dark="theme=='dark'"
hide-underline
/>
</LokiField>
<LokiField label="Wallet file" disable-hover>
<q-input v-model="wallet.path" placeholder="Please select a file" disable :dark="theme=='dark'" hide-underline/>
<LokiField :label="$t('fieldLabels.walletFile')" disable-hover>
<q-input
v-model="wallet.path"
:placeholder="$t('placeholders.selectAFile')"
disable
:dark="theme=='dark'"
hide-underline
/>
<input type="file" id="walletPath" v-on:change="setWalletPath" ref="fileInput" hidden />
<q-btn color="secondary" v-on:click="selectFile" :text-color="theme=='dark'?'white':'dark'">Select wallet file</q-btn>
<q-btn
color="secondary"
:label="$t('buttons.selectWalletFile')"
v-on:click="selectFile"
:text-color="theme=='dark'?'white':'dark'"
/>
</LokiField>
<LokiField label="Password">
<q-input v-model="wallet.password" placeholder="An optional password for the wallet" type="password" :dark="theme=='dark'" hide-underline />
<LokiField :label="$t('fieldLabels.password')">
<q-input
v-model="wallet.password"
:placeholder="$t('placeholders.walletPassword')"
type="password"
:dark="theme=='dark'"
hide-underline
/>
</LokiField>
<LokiField label="Confirm Password">
<LokiField :label="$t('fieldLabels.confirmPassword')">
<q-input v-model="wallet.password_confirm" type="password" :dark="theme=='dark'" hide-underline />
</LokiField>
<q-field>
<q-btn color="primary" @click="import_wallet" label="Import wallet" />
<q-btn color="primary" @click="import_wallet" :label="$tc('buttons.importWallet', 1)" />
</q-field>
</div>
@ -97,7 +114,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Enter a wallet name"
message: this.$t("notification.errors.enterWalletName")
})
return
}
@ -105,7 +122,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Passwords do not match"
message: this.$t("notification.errors.passwordNoMatch")
})
return
}

View File

@ -3,7 +3,7 @@
<q-list class="wallet-list" link no-border :dark="theme=='dark'">
<template v-if="wallets.list.length">
<div class="header row justify-between items-center">
<div class="header-title">Your wallets</div>
<div class="header-title">{{ $t("titles.yourWallets") }}</div>
<q-btn class="add" icon="add" size="md" color="primary" v-if="wallets.list.length">
<q-popover class="header-popover">
<q-list separator link>
@ -31,12 +31,12 @@
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay
@click.native="openWallet(wallet)">
<q-item-main label="Open wallet" />
<q-item-main :label="$t('menuItems.openWallet')" />
</q-item>
<q-item v-close-overlay
@click.native="copyAddress(wallet.address, $event)">
<q-item-main label="Copy address" />
<q-item-main :label="$t('menuItems.copyAddress')" />
</q-item>
</q-list>
</q-context-menu>
@ -71,22 +71,22 @@ export default {
// </q-item>
const actions = [
{
name: "Create new wallet",
name: this.$t("titles.wallet.createNew"),
handler: this.createNewWallet,
},
{
name: "Restore wallet from seed",
name: this.$t("titles.wallet.restoreFromSeed"),
handler: this.restoreWallet,
},
{
name: "Import wallet from file",
name: this.$t("titles.wallet.importFromFile"),
handler: this.importWallet,
}
];
if (this.wallets.directories.length > 0) {
actions.push( {
name: "Import wallets from old GUI",
name: this.$t("titles.wallet.importFromOldGUI"),
handler: this.importOldGuiWallets,
})
}
@ -101,18 +101,18 @@ export default {
openWallet(wallet) {
if(wallet.password_protected !== false) {
this.$q.dialog({
title: "Password",
message: "Enter wallet password to continue.",
title: this.$t("dialog.password.title"),
message: this.$t("dialog.password.message"),
prompt: {
model: "",
type: "password"
},
ok: {
label: "OPEN"
label: this.$t("dialog.buttons.open")
},
cancel: {
flat: true,
label: "CANCEL",
label: this.$t("dialog.buttons.cancel"),
color: this.theme=="dark"?"white":"dark"
}
}).then(password => {
@ -160,7 +160,7 @@ export default {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
message: this.$t("notification.positive.addressCopied")
})
}
},

View File

@ -1,20 +1,20 @@
<template>
<q-page>
<div class="q-mx-md">
<LokiField class="q-mt-md" label="Wallet name" :error="$v.wallet.name.$error">
<LokiField class="q-mt-md" :label="$t('fieldLabels.walletName')" :error="$v.wallet.name.$error">
<q-input
v-model="wallet.name"
placeholder="A name for your wallet"
:placeholder="$t('placeholders.walletName')"
@blur="$v.wallet.name.$touch"
:dark="theme=='dark'"
hide-underline
/>
</LokiField>
<LokiField class="q-mt-md" label="Mnemonic seed" :error="$v.wallet.seed.$error">
<LokiField class="q-mt-md" :label="$t('fieldLabels.mnemonicSeed')" :error="$v.wallet.seed.$error">
<q-input
v-model="wallet.seed"
placeholder="25 (or 24) word mnemonic seed"
:placeholder="$t('placeholders.mnemonicSeed')"
type="textarea"
@blur="$v.wallet.seed.$touch"
:dark="theme=='dark'"
@ -24,14 +24,14 @@
<div class="row items-end q-mt-md">
<div class="col">
<LokiField v-if="wallet.refresh_type=='date'" label="Restore from date">
<LokiField v-if="wallet.refresh_type=='date'" :label="$t('fieldLabels.restoreFromDate')">
<q-datetime v-model="wallet.refresh_start_date" type="date"
modal :min="1492486495000" :max="Date.now()"
modal :min="1525305600000" :max="Date.now()"
:dark="theme=='dark'"
hide-underline
/>
</LokiField>
<LokiField v-else-if="wallet.refresh_type=='height'" label="Restore from block height" :error="$v.wallet.refresh_start_height.$error">
<LokiField v-else-if="wallet.refresh_type=='height'" :label="$t('fieldLabels.restoreFromBlockHeight')" :error="$v.wallet.refresh_start_height.$error">
<q-input v-model="wallet.refresh_start_height" type="number"
min="0"
@blur="$v.wallet.refresh_start_height.$touch"
@ -43,34 +43,38 @@
<div class="col-auto q-ml-sm">
<template v-if="wallet.refresh_type=='date'">
<q-btn @click="wallet.refresh_type='height'" class="float-right" :text-color="theme=='dark'?'white':'dark'" flat>
<div style="width: 80px;" class="text-center">
<div style="min-width: 80px; height: 38px;" class="text-center flex column items-center justify-center">
<q-icon class="block" name="clear_all" />
<div style="font-size:10px">Switch to<br/>height select</div>
<div style="font-size:10px">
{{ $t("strings.switchToHeightSelect") }}
</div>
</div>
</q-btn>
</template>
<template v-else-if="wallet.refresh_type=='height'">
<q-btn @click="wallet.refresh_type='date'" class="float-right" :text-color="theme=='dark'?'white':'dark'" flat>
<div style="width: 80px;" class="text-center">
<div style="min-width: 80px; height: 38px;" class="text-center flex column items-center justify-center">
<q-icon class="block" name="today" />
<div style="font-size:10px">Switch to<br/>date select</div>
<div style="font-size:10px">
{{ $t("strings.switchToDateSelect") }}
</div>
</div>
</q-btn>
</template>
</div>
</div>
<LokiField class="q-mt-md" label="Password">
<LokiField class="q-mt-md" :label="$t('fieldLabels.password')">
<q-input
v-model="wallet.password"
placeholder="An optional password for the wallet"
:placeholder="$t('placeholders.walletPassword')"
type="password"
:dark="theme=='dark'"
hide-underline
/>
</LokiField>
<LokiField class="q-mt-md" label="Confirm Password">
<LokiField class="q-mt-md" :label="$t('fieldLabels.confirmPassword')">
<q-input
v-model="wallet.password_confirm"
type="password"
@ -80,7 +84,7 @@
</LokiField>
<q-field>
<q-btn color="primary" @click="restore_wallet" label="Restore wallet" />
<q-btn color="primary" @click="restore_wallet" :label="$t('buttons.restoreWallet')" />
</q-field>
</div>
@ -148,7 +152,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Enter a wallet name"
message: this.$t("notification.errors.enterWalletName")
})
return
}
@ -156,7 +160,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Enter seed words"
message: this.$t("notification.errors.enterSeedWords")
})
return
}
@ -170,7 +174,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Invalid seed word length"
message: this.$t("notification.errors.invalidSeedLength")
})
return
}
@ -179,7 +183,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Invalid restore height"
message: this.$t("notification.errors.invalidRestoreHeight")
})
return
}
@ -187,7 +191,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Passwords do not match"
message: this.$t("notification.errors.passwordNoMatch")
})
return
}

View File

@ -2,22 +2,22 @@
<q-page class="address-book">
<div class="header row q-pt-md q-pb-xs q-mx-md q-mb-none items-center non-selectable">
Address book
{{ $t("titles.addressBook") }}
</div>
<template v-if="address_book_combined.length">
<q-list link no-border :dark="theme=='dark'" class="loki-list">
<q-item class="loki-list-item" v-for="(entry, index) in address_book_combined" @click.native="details(entry)" :key="entry.address">
<q-item class="loki-list-item" v-for="(entry, index) in address_book_combined" @click.native="details(entry)" :key="`${entry.address}-${entry.name}`">
<q-item-main>
<q-item-tile class="ellipsis" label>{{ entry.address }}</q-item-tile>
<q-item-tile sublabel>{{ entry.name }}</q-item-tile>
<q-item-tile sublabel class="non-selectable">{{ entry.name }}</q-item-tile>
</q-item-main>
<q-item-side>
<q-icon size="24px" :name="entry.starred ? 'star' : 'star_border'" />
<q-btn
color="secondary"
style="margin-left: 10px;"
label="Send"
:label="$t('buttons.send')"
:disabled="view_only"
@click="sendToAddress(entry, $event)"
/>
@ -27,17 +27,17 @@
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay
@click.native="details(entry)">
<q-item-main label="Show details" />
<q-item-main :label="$t('menuItems.showDetails')" />
</q-item>
<q-item v-close-overlay
@click.native="sendToAddress(entry, $event)">
<q-item-main label="Send to this address" />
<q-item-main :label="$t('menuItems.sendToThisAddress')" />
</q-item>
<q-item v-close-overlay
@click.native="copyAddress(entry, $event)">
<q-item-main label="Copy address" />
<q-item-main :label="$t('menuItems.copyAddress')" />
</q-item>
</q-list>
</q-context-menu>
@ -46,7 +46,7 @@
</q-list>
</template>
<template v-else>
<p class="q-ma-md">Address book is empty</p>
<p class="q-ma-md">{{ $t("strings.addressBookIsEmpty") }}</p>
</template>
<q-page-sticky position="bottom-right" :offset="[18, 18]">
@ -117,29 +117,23 @@ export default {
clipboard.writeText(entry.address)
if(entry.payment_id) {
this.$q.dialog({
title: "Copy address",
message: "There is a payment id associated with this address.\nBe sure to copy the payment id separately.",
title: this.$t("dialog.copyAddress.title"),
message: this.$t("dialog.copyAddress.message"),
ok: {
label: "OK"
label: this.$t("dialog.buttons.ok")
},
}).then(password => {
}).catch(() => null).then(password => {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
})
}).catch(() => {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
message: this.$t("notification.positive.addressCopied")
})
})
} else {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
message: this.$t("notification.positive.addressCopied")
})
}
}

View File

@ -2,182 +2,43 @@
<q-page class="receive">
<q-list link no-border :dark="theme=='dark'" class="loki-list">
<q-list-header>My primary address</q-list-header>
<q-list class="loki-list-item primary-address" no-border v-for="address in address_list.primary" :key="address.address" @click.native="details(address)">
<q-item>
<q-item-main>
<q-item-tile class="ellipsis" label>{{ address.address }}</q-item-tile>
<q-item-tile sublabel>Primary address</q-item-tile>
</q-item-main>
<q-item-side>
<q-btn
flat
style="width:25px;"
size="md"
@click="showQR(address.address, $event)"
>
<img src="statics/qr-code.svg" height="20" />
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
Show QR code
</q-tooltip>
</q-btn>
<q-btn
flat
style="width:25px;"
size="md" icon="file_copy"
@click="copyAddress(address.address, $event)"
>
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
Copy address
</q-tooltip>
</q-btn>
</q-item-side>
</q-item>
<q-item-separator />
<q-item class="info">
<q-item-main class="flex justify-between">
<div class="column">
<span>Balance</span>
<span class="value">{{address.balance | currency}}</span>
</div>
<div class="column">
<span>Unlocked balance</span>
<span class="value">{{ address.unlocked_balance | currency }}</span>
</div>
<div class="column">
<span>Unspent outputs</span>
<span class="value">{{ address.num_unspent_outputs | toString }}</span>
</div>
</q-item-main>
</q-item>
<q-context-menu>
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay
@click.native="details(address)">
<q-item-main label="Show details" />
</q-item>
<q-item v-close-overlay
@click.native="copyAddress(address.address, $event)">
<q-item-main label="Copy address" />
</q-item>
</q-list>
</q-context-menu>
</q-list>
<q-list-header>{{ $t("strings.addresses.myPrimaryAddress") }}</q-list-header>
<receive-item
class="primary-address"
v-for="address in address_list.primary"
:key="address.address"
:address="address"
:sublabel="$t('strings.addresses.primaryAddress')"
:showQR="showQR"
:copyAddress="copyAddress"
:details="details"
/>
<template v-if="address_list.used.length">
<q-list-header>My used addresses</q-list-header>
<q-list class="loki-list-item" no-border v-for="address in address_list.used" @click.native="details(address)" :key="address.address">
<q-item>
<q-item-main>
<q-item-tile class="ellipsis" label>{{ address.address }}</q-item-tile>
<q-item-tile sublabel>Sub-address (Index {{ address.address_index }})</q-item-tile>
</q-item-main>
<q-item-side>
<q-btn
flat
style="width:25px;"
size="md"
@click="showQR(address.address, $event)"
>
<img src="statics/qr-code-grey.svg" height="20" />
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
Show QR code
</q-tooltip>
</q-btn>
<q-btn
flat
style="width:25px;"
size="md" icon="file_copy"
@click="copyAddress(address.address, $event)">
<q-tooltip anchor="bottom right" self="top right" :offset="[5, 10]">
Copy address
</q-tooltip>
</q-btn>
</q-item-side>
</q-item>
<q-item-separator />
<q-item class="info">
<q-item-main class="flex justify-between">
<div class="column">
<span>Balance</span>
<span class="value">{{ address.balance | currency }}</span>
</div>
<div class="column">
<span>Unlocked balance</span>
<span class="value">{{ address.unlocked_balance | currency }}</span>
</div>
<div class="column">
<span>Unspent outputs</span>
<span class="value">{{ address.num_unspent_outputs | toString }}</span>
</div>
</q-item-main>
</q-item>
<q-context-menu>
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay
@click.native="details(address)">
<q-item-main label="Show details" />
</q-item>
<q-item v-close-overlay
@click.native="copyAddress(address.address, $event)">
<q-item-main label="Copy address" />
</q-item>
</q-list>
</q-context-menu>
</q-list>
<q-list-header>{{ $t("strings.addresses.myUsedAddresses") }}</q-list-header>
<receive-item
v-for="address in address_list.used"
:key="address.address"
:address="address"
:sublabel="`${$t('strings.addresses.subAddress')} (Index ${address.address_index})`"
:showQR="showQR"
:copyAddress="copyAddress"
:details="details"
/>
</template>
<template v-if="address_list.unused.length">
<q-list-header>My unused addresses</q-list-header>
<q-list class="loki-list-item" no-border v-for="address in address_list.unused" @click.native="details(address)" :key="address.address">
<q-item>
<q-item-main>
<q-item-tile class="ellipsis" label>{{ address.address }}</q-item-tile>
<q-item-tile sublabel>Sub-address (Index {{ address.address_index }})</q-item-tile>
</q-item-main>
<q-item-side>
<q-btn
flat
style="width:25px;"
size="md"
@click="showQR(address.address, $event)"
>
<img src="statics/qr-code-grey.svg" height="20" />
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
Show QR code
</q-tooltip>
</q-btn>
<q-btn
flat
style="width:25px;"
size="md" icon="file_copy"
@click="copyAddress(address.address, $event)">
<q-tooltip anchor="bottom right" self="top right" :offset="[5, 10]">
Copy address
</q-tooltip>
</q-btn>
</q-item-side>
</q-item>
<q-context-menu>
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay
@click.native="details(address)">
<q-item-main label="Show details" />
</q-item>
<q-item v-close-overlay
@click.native="copyAddress(address.address, $event)">
<q-item-main label="Copy address" />
</q-item>
</q-list>
</q-context-menu>
</q-list>
<q-list-header>{{ $t("strings.addresses.myUnusedAddresses") }}</q-list-header>
<receive-item
v-for="address in address_list.unused"
:key="address.address"
:address="address"
:sublabel="`${$t('strings.addresses.subAddress')} (Index ${address.address_index})`"
:showQR="showQR"
:copyAddress="copyAddress"
:details="details"
:shouldShowInfo="false"
/>
</template>
</q-list>
@ -193,10 +54,10 @@
<q-context-menu>
<q-list link separator style="min-width: 150px; max-height: 300px;">
<q-item v-close-overlay @click.native="copyQR()">
<q-item-main label="Copy QR code" />
<q-item-main :label="$t('menuItems.copyQR')" />
</q-item>
<q-item v-close-overlay @click.native="saveQR()">
<q-item-main label="Save QR code to file" />
<q-item-main :label="$t('menuItems.saveQR')" />
</q-item>
</q-list>
</q-context-menu>
@ -205,7 +66,7 @@
<q-btn
color="primary"
@click="QR.visible = false"
label="Close"
:label="$t('buttons.close')"
/>
</q-modal>
</template>
@ -217,8 +78,8 @@
const { clipboard, nativeImage } = require("electron")
import { mapState } from "vuex"
import QrcodeVue from "qrcode.vue";
import Identicon from "components/identicon"
import AddressDetails from "components/address_details"
import ReceiveItem from "components/receive_item"
export default {
computed: mapState({
@ -247,6 +108,7 @@ export default {
},
methods: {
details (address) {
console.log(address)
this.$refs.addressDetails.address = address;
this.$refs.addressDetails.isVisible = true;
},
@ -262,7 +124,7 @@ export default {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Copied QR code to clipboard"
message: this.$t("notification.positive.qrCopied")
})
},
saveQR () {
@ -281,14 +143,14 @@ export default {
this.$q.notify({
type: "positive",
timeout: 1000,
message: "Address copied to clipboard"
message: this.$t("notification.positive.addressCopied")
})
}
},
components: {
Identicon,
AddressDetails,
QrcodeVue
QrcodeVue,
ReceiveItem
}
}
</script>

View File

@ -2,19 +2,16 @@
<q-page class="send">
<template v-if="view_only">
<div class="q-pa-md">
View-only mode. Please load full wallet in order to send coins.
{{ $t("strings.viewOnlyMode") }}
</div>
</template>
<template v-else>
<div class="q-pa-md">
<div class="row gutter-md">
<!-- Amount -->
<div class="col-6">
<LokiField label="Amount" :error="$v.newTx.amount.$error">
<LokiField :label="$t('fieldLabels.amount')" :error="$v.newTx.amount.$error">
<q-input v-model="newTx.amount"
:dark="theme=='dark'"
type="number"
@ -24,13 +21,15 @@
@blur="$v.newTx.amount.$touch"
hide-underline
/>
<q-btn color="secondary" @click="newTx.amount = unlocked_balance / 1e9" :text-color="theme=='dark'?'white':'dark'">All</q-btn>
<q-btn color="secondary" @click="newTx.amount = unlocked_balance / 1e9" :text-color="theme=='dark'?'white':'dark'">
{{ $t("buttons.all") }}
</q-btn>
</LokiField>
</div>
<!-- Priority -->
<div class="col-6">
<LokiField label="Priority">
<LokiField :label="$t('fieldLabels.priority')">
<q-select :dark="theme=='dark'"
v-model="newTx.priority"
:options="priorityOptions"
@ -42,24 +41,26 @@
<!-- Address -->
<div class="col q-mt-sm">
<LokiField label="Address" :error="$v.newTx.address.$error">
<LokiField :label="$t('fieldLabels.address')" :error="$v.newTx.address.$error">
<q-input v-model="newTx.address"
:dark="theme=='dark'"
@blur="$v.newTx.address.$touch"
:placeholder="address_placeholder"
hide-underline
/>
<q-btn color="secondary" :text-color="theme=='dark'?'white':'dark'" to="addressbook">Contacts</q-btn>
<q-btn color="secondary" :text-color="theme=='dark'?'white':'dark'" to="addressbook">
{{ $t("buttons.contacts") }}
</q-btn>
</LokiField>
</div>
<!-- Payment ID -->
<div class="col q-mt-sm">
<LokiField label="Payment id" :error="$v.newTx.payment_id.$error" optional>
<LokiField :label="$t('fieldLabels.paymentId')" :error="$v.newTx.payment_id.$error" optional>
<q-input v-model="newTx.payment_id"
:dark="theme=='dark'"
@blur="$v.newTx.payment_id.$touch"
placeholder="16 or 64 hexadecimal characters"
:placeholder="$t('placeholders.hexCharacters', { count: '16 or 64' })"
hide-underline
/>
</LokiField>
@ -67,11 +68,11 @@
<!-- Notes -->
<div class="col q-mt-sm">
<LokiField label="Notes" optional>
<LokiField :label="$t('fieldLabels.notes')" optional>
<q-input v-model="newTx.note"
type="textarea"
:dark="theme=='dark'"
placeholder="Additional notes to attach to the transaction"
:placeholder="$t('placeholders.transactionNotes')"
hide-underline
/>
</LokiField>
@ -79,23 +80,23 @@
<!-- Save to address book -->
<q-field>
<q-checkbox v-model="newTx.address_book.save" label="Save to address book" :dark="theme=='dark'" />
<q-checkbox v-model="newTx.address_book.save" :label="$t('strings.saveToAddressBook')" :dark="theme=='dark'" />
</q-field>
<div v-if="newTx.address_book.save">
<LokiField label="Name" optional>
<LokiField :label="$t('fieldLabels.name')" optional>
<q-input v-model="newTx.address_book.name"
:dark="theme=='dark'"
placeholder="Name that belongs to this address"
:placeholder="$t('placeholders.addressBookName')"
hide-underline
/>
</LokiField>
<LokiField class="q-mt-sm" label="Notes" optional>
<LokiField class="q-mt-sm" :label="$t('fieldLabels.notes')" optional>
<q-input v-model="newTx.address_book.description"
type="textarea"
rows="2"
:dark="theme=='dark'"
placeholder="Additional notes"
:placeholder="$t('placeholders.additionalNotes')"
hide-underline
/>
</LokiField>
@ -105,7 +106,7 @@
<q-btn
class="send-btn"
:disable="!is_able_to_send"
color="primary" @click="send()" label="Send" />
color="primary" @click="send()" :label="$t('buttons.send')" />
</q-field>
</div>
@ -252,28 +253,28 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Amount cannot be negative"
message: this.$t("notification.errors.negativeAmount")
})
return
} else if(this.newTx.amount == 0) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Amount must be greater than zero"
message: this.$t("notification.errors.zeroAmount")
})
return
} else if(this.newTx.amount > this.unlocked_balance / 1e9) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Not enough unlocked balance"
message: this.$t("notification.errors.notEnoughBalance")
})
return
} else if (this.$v.newTx.amount.$error) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Amount not valid"
message: this.$t("notification.errors.invalidAmount")
})
return
}
@ -283,7 +284,7 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Address not valid"
message: this.$t("notification.errors.invalidAddress")
})
return
}
@ -292,16 +293,16 @@ export default {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Payment id not valid"
message: this.$t("notification.errors.invalidPaymentId")
})
return
}
this.showPasswordConfirmation({
title: "Transfer",
noPasswordMessage: "Do you want to send the transaction?",
title: this.$t("dialog.transfer.title"),
noPasswordMessage: this.$t("dialog.transfer.message"),
ok: {
label: "SEND"
label: this.$t("dialog.transfer.ok")
},
}).then(password => {
this.$store.commit("gateway/set_tx_status", {

View File

@ -6,9 +6,9 @@
toggle-color="primary"
color="secondary"
:options="[
{label: 'Staking', value: 'staking'},
{label: 'Registration', value: 'registration'},
{label: 'Unlock', value: 'unlock'}
{label: $t('titles.serviceNode.staking'), value: 'staking'},
{label: $t('titles.serviceNode.registration'), value: 'registration'},
{label: $t('titles.serviceNode.unlock'), value: 'unlock'}
]"
/>
</div>

View File

@ -1,21 +1,20 @@
<template>
<q-page>
<div class="row q-pt-sm q-mx-md q-mb-sm items-end non-selectable">
<div class="col-5">
Transactions
{{ $t("titles.transactions") }}
</div>
<LokiField class="col-5 q-px-sm" label="Filter by txid">
<LokiField class="col-5 q-px-sm" :label="$t('fieldLabels.filterTxId')">
<q-input v-model="tx_txid"
:dark="theme=='dark'"
placeholder="Enter an ID"
:placeholder="$t('placeholders.enterAnId')"
hide-underline
/>
</LokiField>
<LokiField class="col-2" label="Filter by transaction type">
<LokiField class="col-2" :label="$t('fieldLabels.filterTransactionType')">
<q-select :dark="theme=='dark'"
v-model="tx_type"
:options="tx_type_options"
@ -38,15 +37,15 @@ export default {
tx_type: "all",
tx_txid: "",
tx_type_options: [
{label: "All", value: "all"},
{label: "Incoming", value: "in"},
{label: "Outgoing", value: "out"},
{label: "Pending", value: "all_pending"},
{label: "Miner", value: "miner"},
{label: "Service Node", value: "snode"},
{label: "Governance", value: "gov"},
{label: "Stake", value: "stake"},
{label: "Failed", value: "failed"},
{label: this.$t("strings.transactions.types.all"), value: "all"},
{label: this.$t("strings.transactions.types.incoming"), value: "in"},
{label: this.$t("strings.transactions.types.outgoing"), value: "out"},
{label: this.$t("strings.transactions.types.pending"), value: "all_pending"},
{label: this.$t("strings.transactions.types.miner"), value: "miner"},
{label: this.$t("strings.transactions.types.serviceNode"), value: "snode"},
{label: this.$t("strings.transactions.types.governance"), value: "gov"},
{label: this.$t("strings.transactions.types.stake"), value: "stake"},
{label: this.$t("strings.transactions.types.failed"), value: "failed"},
]
}
@ -55,12 +54,10 @@ export default {
theme: state => state.gateway.app.config.appearance.theme,
tx_list: state => state.gateway.wallet.transactions.tx_list
}),
components: {
TxList,
LokiField
}
}
</script>

View File

@ -1,566 +0,0 @@
/**
This is an unused class in LOKI
*/
<template>
<q-page padding>
<div class="row">
<div class="infoBoxBalance">
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Balance</span></div>
<div class="value"><span><FormatLoki :amount="info.balance" /></span></div>
</div>
</div>
</div>
<div>
<div class="infoBox">
<div class="infoBoxContent">
<div class="text"><span>Unlocked balance</span></div>
<div class="value"><span><FormatLoki :amount="info.unlocked_balance" /></span></div>
</div>
</div>
</div>
<div class="col text-right q-mr-sm">
<div class="infoBox q-pt-md">
<q-btn icon-right="more_vert" label="Wallet actions" size="md" flat>
<q-popover anchor="bottom right" self="top right">
<q-list separator link>
<q-item :disabled="!is_ready"
v-close-overlay @click.native="getPrivateKeys()">
<q-item-main>
<q-item-tile label>Show Private Keys</q-item-tile>
</q-item-main>
</q-item>
<q-item :disabled="!is_ready"
v-close-overlay @click.native="showModal('change_password')">
<q-item-main>
<q-item-tile label>Change Password</q-item-tile>
</q-item-main>
</q-item>
<q-item :disabled="!is_ready"
v-close-overlay @click.native="showModal('rescan')">
<q-item-main>
<q-item-tile label>Rescan Wallet</q-item-tile>
</q-item-main>
</q-item>
<q-item :disabled="!is_ready"
v-close-overlay @click.native="showModal('key_image')">
<q-item-main>
<q-item-tile label>Manage Key Images</q-item-tile>
</q-item-main>
</q-item>
<q-item :disabled="!is_ready"
v-close-overlay @click.native="deleteWallet()">
<q-item-main>
<q-item-tile label>Delete Wallet</q-item-tile>
</q-item-main>
</q-item>
</q-list>
</q-popover>
</q-btn>
</div>
</div>
</div>
<h6 class="q-my-none">Recent transactions:</h6>
<div style="margin: 0 -16px;">
<TxList :limit="5" />
</div>
<q-modal minimized v-model="modals.private_keys.visible" @hide="closePrivateKeys()">
<div class="modal-header">Show private keys</div>
<div class="q-ma-lg">
<template v-if="secret.mnemonic">
<h6 class="q-mb-xs q-mt-lg">Seed words</h6>
<div class="row">
<div class="col">
{{ secret.mnemonic }}
</div>
<div class="col-auto">
<q-btn
color="primary" style="width:25px;"
size="sm" icon="file_copy"
@click="copyPrivateKey('mnemonic', $event)">
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
Copy seed words
</q-tooltip>
</q-btn>
</div>
</div>
</template>
<template v-if="secret.view_key != secret.spend_key">
<h6 class="q-mb-xs">View key</h6>
<div class="row">
<div class="col" style="word-break:break-all;">
{{ secret.view_key }}
</div>
<div class="col-auto">
<q-btn
color="primary" style="width:25px;"
size="sm" icon="file_copy"
@click="copyPrivateKey('view_key', $event)">
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
Copy view key
</q-tooltip>
</q-btn>
</div>
</div>
</template>
<template v-if="!/^0*$/.test(secret.spend_key)">
<h6 class="q-mb-xs">Spend key</h6>
<div class="row">
<div class="col" style="word-break:break-all;">
{{ secret.spend_key }}
</div>
<div class="col-auto">
<q-btn
color="primary" style="width:25px;"
size="sm" icon="file_copy"
@click="copyPrivateKey('spend_key', $event)">
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
Copy spend key
</q-tooltip>
</q-btn>
</div>
</div>
</template>
<div class="q-mt-lg">
<q-btn
color="primary"
@click="hideModal('private_keys')"
label="Close"
/>
</div>
</div>
</q-modal>
<q-modal minimized v-model="modals.rescan.visible">
<div class="modal-header">Rescan wallet</div>
<div class="q-ma-lg">
<p>Select full rescan or rescan of spent outputs only.</p>
<div class="q-mt-lg">
<q-radio v-model="modals.rescan.type" val="full" label="Rescan full blockchain" />
</div>
<div class="q-mt-sm">
<q-radio v-model="modals.rescan.type" val="spent" label="Rescan spent outputs" />
</div>
<div class="q-mt-xl text-right">
<q-btn
flat class="q-mr-sm"
@click="hideModal('rescan')"
label="Close"
/>
<q-btn
color="primary"
@click="rescanWallet()"
label="Rescan"
/>
</div>
</div>
</q-modal>
<q-modal minimized v-model="modals.key_image.visible">
<div class="modal-header">{{modals.key_image.type}} key images</div>
<div class="q-ma-lg">
<div class="row q-mb-md">
<div class="q-mr-xl">
<q-radio v-model="modals.key_image.type" val="Export" label="Export" />
</div>
<div>
<q-radio v-model="modals.key_image.type" val="Import" label="Import" />
</div>
</div>
<template v-if="modals.key_image.type == 'Export'">
<q-field style="width:450px">
<div class="row gutter-sm">
<div class="col-9">
<q-input v-model="modals.key_image.export_path" stack-label="Key image export directory" disable />
<input type="file" webkitdirectory directory id="keyImageExportPath" v-on:change="setKeyImageExportPath" ref="keyImageExportSelect" hidden />
</div>
<div class="col-3">
<q-btn class="float-right" v-on:click="selectKeyImageExportPath">Browse</q-btn>
</div>
</div>
</q-field>
</template>
<template v-if="modals.key_image.type == 'Import'">
<q-field style="width:450px">
<div class="row gutter-sm">
<div class="col-9">
<q-input v-model="modals.key_image.import_path" stack-label="Key image import file" disable />
<input type="file" id="keyImageImportPath" v-on:change="setKeyImageImportPath" ref="keyImageImportSelect" hidden />
</div>
<div class="col-3">
<q-btn class="float-right" v-on:click="selectKeyImageImportPath">Browse</q-btn>
</div>
</div>
</q-field>
</template>
<div class="q-mt-xl text-right">
<q-btn
flat class="q-mr-sm"
@click="hideModal('key_image')"
label="Close"
/>
<q-btn
color="primary"
@click="doKeyImages()"
:label="modals.key_image.type"
/>
</div>
</div>
</q-modal>
<q-modal minimized v-model="modals.change_password.visible" @hide="clearChangePassword()">
<div class="modal-header">Change password</div>
<div class="q-ma-lg">
<q-field>
<q-input v-model="modals.change_password.old_password" type="password" float-label="Old Password" :dark="theme=='dark'" />
</q-field>
<q-field>
<q-input v-model="modals.change_password.new_password" type="password" float-label="New Password" :dark="theme=='dark'" />
</q-field>
<q-field>
<q-input v-model="modals.change_password.new_password_confirm" type="password" float-label="Confirm New Password" :dark="theme=='dark'" />
</q-field>
<div class="q-mt-xl text-right">
<q-btn
flat class="q-mr-sm"
@click="hideModal('change_password')"
label="Close"
/>
<q-btn
color="primary"
@click="doChangePassword()"
label="Change"
/>
</div>
</div>
</q-modal>
</q-page>
</template>
<script>
const { clipboard } = require("electron")
import { mapState } from "vuex"
import AddressHeader from "components/address_header"
import FormatLoki from "components/format_loki"
import TxList from "components/tx_list"
export default {
computed: mapState({
theme: state => state.gateway.app.config.appearance.theme,
info: state => state.gateway.wallet.info,
secret: state => state.gateway.wallet.secret,
data_dir: state => state.gateway.app.config.app.data_dir,
is_ready (state) {
return this.$store.getters["gateway/isReady"]
}
}),
data () {
return {
modals: {
private_keys: {
visible: false,
},
rescan: {
visible: false,
type: "full",
},
key_image: {
visible: false,
type: "Export",
export_path: "",
import_path: "",
},
change_password: {
visible: false,
old_password: "",
new_password: "",
new_password_confirm: "",
},
}
}
},
mounted () {
const path = require("path")
this.modals.key_image.export_path = path.join(this.data_dir, "gui")
this.modals.key_image.import_path = path.join(this.data_dir, "gui", "key_image_export")
},
watch: {
secret: {
handler(val, old) {
if(val.view_key == old.view_key) return
switch(this.secret.view_key) {
case "":
break
case -1:
this.$q.notify({
type: "negative",
timeout: 1000,
message: this.secret.mnemonic
})
this.$store.commit("gateway/set_wallet_data", {
secret: {
mnemonic: "",
spend_key: "",
view_key: ""
}
})
break
default:
this.showModal("private_keys")
break
}
},
deep: true
}
},
methods: {
showModal (which) {
if(!this.is_ready) return
this.modals[which].visible = true
},
hideModal (which) {
this.modals[which].visible = false
},
copyPrivateKey (type, event) {
event.stopPropagation()
for(let i = 0; i < event.path.length; i++) {
if(event.path[i].tagName == "BUTTON") {
event.path[i].blur()
break
}
}
if(this.secret[type] == null) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "Error copying private key",
})
return
}
clipboard.writeText(this.secret[type])
let type_human = type.substring(0,1).toUpperCase()+type.substring(1).replace("_"," ")
this.$q.dialog({
title: "Copy "+type_human,
message: "Be careful who you send your private keys to as they control your funds.",
ok: {
label: "OK"
},
}).then(() => {
this.$q.notify({
type: "positive",
timeout: 1000,
message: type_human+" copied to clipboard"
})
}).catch(() => {
this.$q.notify({
type: "positive",
timeout: 1000,
message: type_human+" copied to clipboard"
})
})
},
getPrivateKeys () {
if(!this.is_ready) return
this.$q.dialog({
title: "Show private keys",
message: "Enter wallet password to continue.",
prompt: {
model: "",
type: "password"
},
ok: {
label: "SHOW"
},
cancel: {
flat: true,
label: "CANCEL",
color: this.theme=="dark"?"white":"dark"
}
}).then(password => {
this.$gateway.send("wallet", "get_private_keys", {password})
}).catch(() => {
})
},
closePrivateKeys () {
this.hideModal("private_keys")
setTimeout(() => {
this.$store.commit("gateway/set_wallet_data", {
secret: {
mnemonic: "",
spend_key: "",
view_key: ""
}
})
}, 500)
},
rescanWallet () {
this.hideModal("rescan")
if(this.modals.rescan.type == "full") {
this.$q.dialog({
title: "Rescan wallet",
message: "Warning: Some information about previous transactions\nsuch as the recipient's address will be lost.",
ok: {
label: "RESCAN"
},
cancel: {
flat: true,
label: "CANCEL",
color: this.theme=="dark"?"white":"dark"
}
}).then(password => {
this.$gateway.send("wallet", "rescan_blockchain")
}).catch(() => {
})
} else {
this.$gateway.send("wallet", "rescan_spent")
}
},
selectKeyImageExportPath () {
this.$refs.keyImageExportSelect.click()
},
setKeyImageExportPath (file) {
this.modals.key_image.export_path = file.target.files[0].path
},
selectKeyImageImportPath () {
this.$refs.keyImageImportSelect.click()
},
setKeyImageImportPath (file) {
this.modals.key_image.import_path = file.target.files[0].path
},
doKeyImages () {
this.hideModal("key_image")
this.$q.dialog({
title: this.modals.key_image.type + " key images",
message: "Enter wallet password to continue.",
prompt: {
model: "",
type: "password"
},
ok: {
label: this.modals.key_image.type
},
cancel: {
flat: true,
label: "CANCEL",
color: this.theme=="dark"?"white":"dark"
}
}).then(password => {
if(this.modals.key_image.type == "Export")
this.$gateway.send("wallet", "export_key_images", {password: password, path: this.modals.key_image.export_path})
else if(this.modals.key_image.type == "Import")
this.$gateway.send("wallet", "import_key_images", {password: password, path: this.modals.key_image.import_path})
}).catch(() => {
})
},
doChangePassword () {
let old_password = this.modals.change_password.old_password
let new_password = this.modals.change_password.new_password
let new_password_confirm = this.modals.change_password.new_password_confirm
if(new_password == old_password) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "New password must be different"
})
} else if(new_password != new_password_confirm) {
this.$q.notify({
type: "negative",
timeout: 1000,
message: "New passwords do not match"
})
} else {
this.hideModal("change_password")
this.$gateway.send("wallet", "change_wallet_password", {old_password, new_password})
}
},
clearChangePassword () {
this.modals.change_password.old_password = ""
this.modals.change_password.new_password = ""
this.modals.change_password.new_password_confirm = ""
},
deleteWallet () {
this.$q.dialog({
title: "Delete wallet",
message: "Are you absolutely sure you want to delete your wallet?\nMake sure you have your private keys backed up.\nTHIS PROCESS IS NOT REVERSIBLE!",
ok: {
label: "DELETE",
color: "red"
},
cancel: {
flat: true,
label: "CANCEL",
color: this.theme=="dark"?"white":"dark"
}
}).then(() => {
this.$q.dialog({
title: "Delete wallet",
message: "Enter wallet password to continue.",
prompt: {
model: "",
type: "password"
},
ok: {
label: "DELETE",
color: "red"
},
cancel: {
flat: true,
label: "CANCEL",
color: this.theme=="dark"?"white":"dark"
}
}).then(password => {
this.$gateway.send("wallet", "delete_wallet", {password})
}).catch(() => {
})
}).catch(() => {
})
}
},
components: {
FormatLoki,
AddressHeader,
TxList
},
}
</script>
<style lang="scss">
.infoBoxBalance {
width: 290px;
}
@media screen and (max-width: 750px) {
.infoBoxBalance {
width: 200px;
}
}
</style>

View File

@ -1,6 +1,8 @@
import VueI18n from "vue-i18n"
import messages from "src/i18n"
let i18n
export default ({
app,
Vue
@ -13,4 +15,8 @@ export default ({
fallbackLocale: "en-us",
messages
})
i18n = app.i18n
}
export { i18n }