ui + config: wallet connections setup, initial wallet config, wallet list state, update translations
This commit is contained in:
parent
5cefb61bb3
commit
f461f27e4c
23 changed files with 564 additions and 127 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2249,6 +2249,7 @@ dependencies = [
|
||||||
"tokio 1.29.1",
|
"tokio 1.29.1",
|
||||||
"tokio-util 0.7.8",
|
"tokio-util 0.7.8",
|
||||||
"toml 0.7.6",
|
"toml 0.7.6",
|
||||||
|
"url",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
|
|
|
@ -51,6 +51,7 @@ toml = "0.7.4"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
pnet = "0.33.0"
|
pnet = "0.33.0"
|
||||||
zeroize = "1.6.0"
|
zeroize = "1.6.0"
|
||||||
|
url = "2.4.0"
|
||||||
|
|
||||||
# stratum server
|
# stratum server
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
|
|
|
@ -18,7 +18,11 @@ wallets:
|
||||||
not_valid_word: Entered word is not valid
|
not_valid_word: Entered word is not valid
|
||||||
create_phrase_desc: Safely write down and save your recovery phrase.
|
create_phrase_desc: Safely write down and save your recovery phrase.
|
||||||
restore_phrase_desc: Enter words from your saved recovery phrase.
|
restore_phrase_desc: Enter words from your saved recovery phrase.
|
||||||
setup_conn_desc: Choose wallet connection method.
|
setup_conn_desc: Choose how your wallet connects to the network.
|
||||||
|
conn_method: Connection method
|
||||||
|
ext_conn: 'External connections:'
|
||||||
|
add_node_url: Add node URL
|
||||||
|
invalid_url: Entered URL is invalid
|
||||||
network:
|
network:
|
||||||
self: Network
|
self: Network
|
||||||
node: Integrated node
|
node: Integrated node
|
||||||
|
@ -150,6 +154,7 @@ modal:
|
||||||
cancel: Cancel
|
cancel: Cancel
|
||||||
save: Save
|
save: Save
|
||||||
confirmation: Confirmation
|
confirmation: Confirmation
|
||||||
|
add: Add
|
||||||
modal_exit:
|
modal_exit:
|
||||||
description: Are you sure you want to quit the application?
|
description: Are you sure you want to quit the application?
|
||||||
exit: Exit
|
exit: Exit
|
|
@ -18,7 +18,11 @@ wallets:
|
||||||
not_valid_word: Введено недопустимое слово
|
not_valid_word: Введено недопустимое слово
|
||||||
create_phrase_desc: Безопасно запишите и сохраните вашу фразу восстановления.
|
create_phrase_desc: Безопасно запишите и сохраните вашу фразу восстановления.
|
||||||
restore_phrase_desc: Введите слова из вашей сохранённой фразы восстановления.
|
restore_phrase_desc: Введите слова из вашей сохранённой фразы восстановления.
|
||||||
setup_conn_desc: Выберите способ подключения кошелька
|
setup_conn_desc: Выберите способ подключения вашего кошелька к сети.
|
||||||
|
conn_method: Способ подключения
|
||||||
|
ext_conn: 'Внешние подключения:'
|
||||||
|
add_node_url: Добавить URL узла
|
||||||
|
invalid_url: Введенный URL-адрес недействителен
|
||||||
network:
|
network:
|
||||||
self: Сеть
|
self: Сеть
|
||||||
node: Встроенный узел
|
node: Встроенный узел
|
||||||
|
@ -150,6 +154,7 @@ modal:
|
||||||
cancel: Отмена
|
cancel: Отмена
|
||||||
save: Сохранить
|
save: Сохранить
|
||||||
confirmation: Подтверждение
|
confirmation: Подтверждение
|
||||||
|
add: Добавить
|
||||||
modal_exit:
|
modal_exit:
|
||||||
description: Вы уверены, что хотите выйти из приложения?
|
description: Вы уверены, что хотите выйти из приложения?
|
||||||
exit: Выход
|
exit: Выход
|
|
@ -20,7 +20,7 @@ use lazy_static::lazy_static;
|
||||||
|
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Wallets, Modal, ModalContainer, Network, View};
|
use crate::gui::views::{WalletsContent, Modal, ModalContainer, Network, View};
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -32,8 +32,8 @@ lazy_static! {
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
/// Side panel [`Network`] content.
|
/// Side panel [`Network`] content.
|
||||||
network: Network,
|
network: Network,
|
||||||
/// Central panel [`Wallets`] content.
|
/// Central panel [`WalletsContent`] content.
|
||||||
wallets: Wallets,
|
wallets: WalletsContent,
|
||||||
|
|
||||||
/// Check if app exit is allowed on close event of [`eframe::App`] implementation.
|
/// Check if app exit is allowed on close event of [`eframe::App`] implementation.
|
||||||
pub(crate) exit_allowed: bool,
|
pub(crate) exit_allowed: bool,
|
||||||
|
@ -52,7 +52,7 @@ impl Default for Root {
|
||||||
let exit_allowed = os == OperatingSystem::Android || os == OperatingSystem::IOS;
|
let exit_allowed = os == OperatingSystem::Android || os == OperatingSystem::IOS;
|
||||||
Self {
|
Self {
|
||||||
network: Network::default(),
|
network: Network::default(),
|
||||||
wallets: Wallets::default(),
|
wallets: WalletsContent::default(),
|
||||||
exit_allowed,
|
exit_allowed,
|
||||||
show_exit_progress: false,
|
show_exit_progress: false,
|
||||||
allowed_modal_ids: vec![
|
allowed_modal_ids: vec![
|
||||||
|
@ -117,7 +117,7 @@ impl Root {
|
||||||
(is_panel_open, panel_width)
|
(is_panel_open, panel_width)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if ui can show [`Network`] and [`Wallets`] at same time.
|
/// Check if ui can show [`Network`] and [`WalletsContent`] at same time.
|
||||||
pub fn is_dual_panel_mode(frame: &mut eframe::Frame) -> bool {
|
pub fn is_dual_panel_mode(frame: &mut eframe::Frame) -> bool {
|
||||||
let w = frame.info().window_info.size.x;
|
let w = frame.info().window_info.size.x;
|
||||||
let h = frame.info().window_info.size.y;
|
let h = frame.info().window_info.size.y;
|
||||||
|
|
|
@ -14,14 +14,15 @@
|
||||||
|
|
||||||
use egui::{Margin, RichText, TextStyle, vec2, Widget};
|
use egui::{Margin, RichText, TextStyle, vec2, Widget};
|
||||||
use egui_extras::{RetainedImage, Size, StripBuilder};
|
use egui_extras::{RetainedImage, Size, StripBuilder};
|
||||||
use crate::built_info;
|
|
||||||
|
|
||||||
|
use crate::built_info;
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::icons::{CHECK, EYE, EYE_SLASH, PLUS_CIRCLE, SHARE_FAT};
|
use crate::gui::icons::{CHECK, EYE, EYE_SLASH, PLUS_CIRCLE, SHARE_FAT};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, ModalPosition, View};
|
use crate::gui::views::{Modal, ModalPosition, View};
|
||||||
use crate::gui::views::wallets::creation::{ConnectionSetup, MnemonicSetup};
|
use crate::gui::views::wallets::creation::MnemonicSetup;
|
||||||
use crate::gui::views::wallets::creation::types::{PhraseMode, Step};
|
use crate::gui::views::wallets::creation::types::{PhraseMode, Step};
|
||||||
|
use crate::gui::views::wallets::setup::ConnectionSetup;
|
||||||
|
|
||||||
/// Wallet creation content.
|
/// Wallet creation content.
|
||||||
pub struct WalletCreation {
|
pub struct WalletCreation {
|
||||||
|
@ -51,8 +52,8 @@ impl Default for WalletCreation {
|
||||||
Self {
|
Self {
|
||||||
step: None,
|
step: None,
|
||||||
modal_just_opened: true,
|
modal_just_opened: true,
|
||||||
name_edit: "".to_string(),
|
name_edit: String::from(""),
|
||||||
pass_edit: "".to_string(),
|
pass_edit: String::from(""),
|
||||||
hide_pass: true,
|
hide_pass: true,
|
||||||
mnemonic_setup: MnemonicSetup::default(),
|
mnemonic_setup: MnemonicSetup::default(),
|
||||||
network_setup: ConnectionSetup::default(),
|
network_setup: ConnectionSetup::default(),
|
||||||
|
@ -89,17 +90,16 @@ impl WalletCreation {
|
||||||
let (step_text, step_available) = match step {
|
let (step_text, step_available) = match step {
|
||||||
Step::EnterMnemonic => {
|
Step::EnterMnemonic => {
|
||||||
let mode = &self.mnemonic_setup.mnemonic.mode;
|
let mode = &self.mnemonic_setup.mnemonic.mode;
|
||||||
let size_value = self.mnemonic_setup.mnemonic.size.value();
|
|
||||||
let text = if mode == &PhraseMode::Generate {
|
let text = if mode == &PhraseMode::Generate {
|
||||||
t!("wallets.create_phrase_desc", "number" => size_value)
|
t!("wallets.create_phrase_desc")
|
||||||
} else {
|
} else {
|
||||||
t!("wallets.restore_phrase_desc", "number" => size_value)
|
t!("wallets.restore_phrase_desc")
|
||||||
};
|
};
|
||||||
let available = !self
|
let available = !self
|
||||||
.mnemonic_setup
|
.mnemonic_setup
|
||||||
.mnemonic
|
.mnemonic
|
||||||
.words
|
.words
|
||||||
.contains(&"".to_string());
|
.contains(&String::from(""));
|
||||||
(text, available)
|
(text, available)
|
||||||
}
|
}
|
||||||
Step::ConfirmMnemonic => {
|
Step::ConfirmMnemonic => {
|
||||||
|
@ -108,7 +108,7 @@ impl WalletCreation {
|
||||||
.mnemonic_setup
|
.mnemonic_setup
|
||||||
.mnemonic
|
.mnemonic
|
||||||
.confirm_words
|
.confirm_words
|
||||||
.contains(&"".to_string());
|
.contains(&String::from(""));
|
||||||
(text, available)
|
(text, available)
|
||||||
},
|
},
|
||||||
Step::SetupConnection => (t!("wallets.setup_conn_desc"), true)
|
Step::SetupConnection => (t!("wallets.setup_conn_desc"), true)
|
||||||
|
@ -190,13 +190,9 @@ impl WalletCreation {
|
||||||
}
|
}
|
||||||
Some(step) => {
|
Some(step) => {
|
||||||
match step {
|
match step {
|
||||||
Step::EnterMnemonic => {
|
Step::EnterMnemonic => self.mnemonic_setup.ui(ui),
|
||||||
self.mnemonic_setup.ui(ui);
|
Step::ConfirmMnemonic => self.mnemonic_setup.confirm_ui(ui),
|
||||||
}
|
Step::SetupConnection => self.network_setup.ui(ui, cb)
|
||||||
Step::ConfirmMnemonic => {
|
|
||||||
self.mnemonic_setup.confirm_ui(ui);
|
|
||||||
}
|
|
||||||
Step::SetupConnection => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,8 +212,8 @@ impl WalletCreation {
|
||||||
Step::EnterMnemonic => {
|
Step::EnterMnemonic => {
|
||||||
// Clear values if it needs to go back on first step.
|
// Clear values if it needs to go back on first step.
|
||||||
self.step = None;
|
self.step = None;
|
||||||
self.name_edit = "".to_string();
|
self.name_edit = String::from("");
|
||||||
self.pass_edit = "".to_string();
|
self.pass_edit = String::from("");
|
||||||
self.mnemonic_setup.reset();
|
self.mnemonic_setup.reset();
|
||||||
}
|
}
|
||||||
Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic),
|
Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic),
|
||||||
|
@ -255,8 +251,8 @@ impl WalletCreation {
|
||||||
// Reset modal values.
|
// Reset modal values.
|
||||||
self.hide_pass = false;
|
self.hide_pass = false;
|
||||||
self.modal_just_opened = true;
|
self.modal_just_opened = true;
|
||||||
self.name_edit = "".to_string();
|
self.name_edit = String::from("");
|
||||||
self.pass_edit = "".to_string();
|
self.pass_edit = String::from("");
|
||||||
// Show modal.
|
// Show modal.
|
||||||
Modal::new(Self::NAME_PASS_MODAL)
|
Modal::new(Self::NAME_PASS_MODAL)
|
||||||
.position(ModalPosition::CenterTop)
|
.position(ModalPosition::CenterTop)
|
||||||
|
|
|
@ -38,7 +38,7 @@ impl Default for MnemonicSetup {
|
||||||
Self {
|
Self {
|
||||||
mnemonic: Mnemonic::default(),
|
mnemonic: Mnemonic::default(),
|
||||||
word_num_edit: 0,
|
word_num_edit: 0,
|
||||||
word_edit: "".to_string(),
|
word_edit: String::from(""),
|
||||||
valid_word_edit: true
|
valid_word_edit: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,12 +192,12 @@ impl MnemonicSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw word list item for current mode.
|
/// Draw word list item for current mode.
|
||||||
fn word_item_ui(&mut self, ui: &mut egui::Ui, word_number: usize, word: &String, edit: bool) {
|
fn word_item_ui(&mut self, ui: &mut egui::Ui, num: usize, word: &String, edit: bool) {
|
||||||
if edit {
|
if edit {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
View::button(ui, PENCIL.to_string(), Colors::BUTTON, || {
|
View::button(ui, PENCIL.to_string(), Colors::BUTTON, || {
|
||||||
// Setup modal values.
|
// Setup modal values.
|
||||||
self.word_num_edit = word_number;
|
self.word_num_edit = num;
|
||||||
self.word_edit = word.clone();
|
self.word_edit = word.clone();
|
||||||
self.valid_word_edit = true;
|
self.valid_word_edit = true;
|
||||||
// Show word edit modal.
|
// Show word edit modal.
|
||||||
|
@ -206,12 +206,12 @@ impl MnemonicSetup {
|
||||||
.title(t!("wallets.saved_phrase"))
|
.title(t!("wallets.saved_phrase"))
|
||||||
.show();
|
.show();
|
||||||
});
|
});
|
||||||
ui.label(RichText::new(format!("#{} {}", word_number, word))
|
ui.label(RichText::new(format!("#{} {}", num, word))
|
||||||
.size(17.0)
|
.size(17.0)
|
||||||
.color(Colors::BLACK));
|
.color(Colors::BLACK));
|
||||||
} else {
|
} else {
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
let text = format!("#{} {}", word_number, word);
|
let text = format!("#{} {}", num, word);
|
||||||
ui.label(RichText::new(text).size(17.0).color(Colors::BLACK));
|
ui.label(RichText::new(text).size(17.0).color(Colors::BLACK));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,14 +274,18 @@ impl MnemonicSetup {
|
||||||
self.valid_word_edit = false;
|
self.valid_word_edit = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
self.valid_word_edit = true;
|
||||||
|
|
||||||
// Select list where to save word.
|
// Select list where to save word.
|
||||||
let words = match self.mnemonic.mode {
|
let words = match self.mnemonic.mode {
|
||||||
PhraseMode::Generate => &mut self.mnemonic.confirm_words,
|
PhraseMode::Generate => &mut self.mnemonic.confirm_words,
|
||||||
PhraseMode::Import => &mut self.mnemonic.words
|
PhraseMode::Import => &mut self.mnemonic.words
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save word at list.
|
// Save word at list.
|
||||||
words.remove(word_index);
|
words.remove(word_index);
|
||||||
words.insert(word_index, self.word_edit.clone());
|
words.insert(word_index, self.word_edit.clone());
|
||||||
|
|
||||||
// Close modal or go to next word to edit.
|
// Close modal or go to next word to edit.
|
||||||
let close_modal = words.len() == self.word_num_edit
|
let close_modal = words.len() == self.word_num_edit
|
||||||
|| !words.get(self.word_num_edit).unwrap().is_empty();
|
|| !words.get(self.word_num_edit).unwrap().is_empty();
|
||||||
|
@ -290,7 +294,7 @@ impl MnemonicSetup {
|
||||||
modal.close();
|
modal.close();
|
||||||
} else {
|
} else {
|
||||||
self.word_num_edit += 1;
|
self.word_num_edit += 1;
|
||||||
self.word_edit = "".to_string();
|
self.word_edit = String::from("");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Call save on Enter key press.
|
// Call save on Enter key press.
|
||||||
|
|
|
@ -15,9 +15,6 @@
|
||||||
mod mnemonic;
|
mod mnemonic;
|
||||||
pub use mnemonic::MnemonicSetup;
|
pub use mnemonic::MnemonicSetup;
|
||||||
|
|
||||||
mod connection;
|
|
||||||
pub use connection::ConnectionSetup;
|
|
||||||
|
|
||||||
mod creation;
|
mod creation;
|
||||||
pub use creation::WalletCreation;
|
pub use creation::WalletCreation;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
use grin_keychain::mnemonic::{from_entropy, search};
|
use grin_keychain::mnemonic::{from_entropy, search};
|
||||||
use rand::{Rng, thread_rng};
|
use rand::{Rng, thread_rng};
|
||||||
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
/// Wallet creation step.
|
/// Wallet creation step.
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
|
@ -27,7 +28,8 @@ pub enum Step {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mnemonic phrase setup mode.
|
/// Mnemonic phrase setup mode.
|
||||||
#[derive(PartialEq, Clone)]
|
/// Will be completely cleaned from memory on drop.
|
||||||
|
#[derive(PartialEq, Clone, Zeroize, ZeroizeOnDrop)]
|
||||||
pub enum PhraseMode {
|
pub enum PhraseMode {
|
||||||
/// Generate new mnemonic phrase.
|
/// Generate new mnemonic phrase.
|
||||||
Generate,
|
Generate,
|
||||||
|
@ -36,7 +38,8 @@ pub enum PhraseMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mnemonic phrase size based on words count.
|
/// Mnemonic phrase size based on words count.
|
||||||
#[derive(PartialEq, Clone)]
|
/// Will be completely cleaned from memory on drop.
|
||||||
|
#[derive(PartialEq, Clone, Zeroize, ZeroizeOnDrop)]
|
||||||
pub enum PhraseSize { Words12, Words15, Words18, Words21, Words24 }
|
pub enum PhraseSize { Words12, Words15, Words18, Words21, Words24 }
|
||||||
|
|
||||||
impl PhraseSize {
|
impl PhraseSize {
|
||||||
|
@ -72,6 +75,8 @@ impl PhraseSize {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mnemonic phrase container.
|
/// Mnemonic phrase container.
|
||||||
|
/// Will be completely cleaned from memory on drop.
|
||||||
|
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct Mnemonic {
|
pub struct Mnemonic {
|
||||||
/// Phrase setup mode.
|
/// Phrase setup mode.
|
||||||
pub(crate) mode: PhraseMode,
|
pub(crate) mode: PhraseMode,
|
||||||
|
@ -130,7 +135,7 @@ impl Mnemonic {
|
||||||
}
|
}
|
||||||
from_entropy(&entropy).unwrap()
|
from_entropy(&entropy).unwrap()
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map(|s| s.to_string())
|
.map(|s| String::from(s))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
},
|
},
|
||||||
PhraseMode::Import => {
|
PhraseMode::Import => {
|
||||||
|
@ -143,7 +148,7 @@ impl Mnemonic {
|
||||||
fn empty_words(size: &PhraseSize) -> Vec<String> {
|
fn empty_words(size: &PhraseSize) -> Vec<String> {
|
||||||
let mut words = Vec::with_capacity(size.value());
|
let mut words = Vec::with_capacity(size.value());
|
||||||
for _ in 0..size.value() {
|
for _ in 0..size.value() {
|
||||||
words.push("".to_string())
|
words.push(String::from(""))
|
||||||
}
|
}
|
||||||
words
|
words
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,9 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
mod wallets;
|
|
||||||
mod creation;
|
mod creation;
|
||||||
mod wallet;
|
mod wallet;
|
||||||
|
mod setup;
|
||||||
|
|
||||||
|
mod wallets;
|
||||||
pub use wallets::*;
|
pub use wallets::*;
|
165
src/gui/views/wallets/setup/connection.rs
Normal file
165
src/gui/views/wallets/setup/connection.rs
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
// Copyright 2023 The Grim Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use egui::{Id, RichText, ScrollArea, TextStyle, Widget};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::AppConfig;
|
||||||
|
use crate::gui::Colors;
|
||||||
|
use crate::gui::icons::{GLOBE, GLOBE_SIMPLE};
|
||||||
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
|
use crate::gui::views::{Modal, View};
|
||||||
|
use crate::gui::views::wallets::setup::ConnectionMethod;
|
||||||
|
|
||||||
|
/// Wallet node connection method setup content.
|
||||||
|
pub struct ConnectionSetup {
|
||||||
|
/// Selected connection method.
|
||||||
|
method: ConnectionMethod,
|
||||||
|
|
||||||
|
/// External node connection URL value for [`Modal`].
|
||||||
|
ext_node_url_edit: String,
|
||||||
|
/// Flag to show URL format error.
|
||||||
|
ext_node_url_error: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ConnectionSetup {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
method: ConnectionMethod::Integrated,
|
||||||
|
ext_node_url_edit: "".to_string(),
|
||||||
|
ext_node_url_error: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectionSetup {
|
||||||
|
/// External node connection [`Modal`] identifier.
|
||||||
|
pub const ADD_CONNECTION_URL_MODAL: &'static str = "add_connection_url_modal";
|
||||||
|
|
||||||
|
//TODO: Setup for provided wallet
|
||||||
|
// pub fn new() -> Self {
|
||||||
|
// Self { method: ConnectionMethod::Integrated }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||||
|
ScrollArea::vertical()
|
||||||
|
.id_source("wallet_connection_setup")
|
||||||
|
.auto_shrink([false; 2])
|
||||||
|
.show(ui, |ui| {
|
||||||
|
View::sub_title(ui, format!("{} {}", GLOBE, t!("wallets.conn_method")));
|
||||||
|
View::horizontal_line(ui, Colors::STROKE);
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
// Show integrated node selection.
|
||||||
|
ui.add_space(6.0);
|
||||||
|
View::radio_value(ui,
|
||||||
|
&mut self.method,
|
||||||
|
ConnectionMethod::Integrated,
|
||||||
|
t!("network.node"));
|
||||||
|
|
||||||
|
ui.add_space(10.0);
|
||||||
|
ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::GRAY));
|
||||||
|
ui.add_space(6.0);
|
||||||
|
|
||||||
|
// Show button to add new external node connection.
|
||||||
|
let add_node_text = format!("{} {}", GLOBE_SIMPLE, t!("wallets.add_node_url"));
|
||||||
|
View::button(ui, add_node_text, Colors::GOLD, || {
|
||||||
|
// Setup values for Modal.
|
||||||
|
self.ext_node_url_edit = "".to_string();
|
||||||
|
self.ext_node_url_error = false;
|
||||||
|
// Show modal.
|
||||||
|
Modal::new(Self::ADD_CONNECTION_URL_MODAL)
|
||||||
|
.title(t!("wallets.ext_conn"))
|
||||||
|
.show();
|
||||||
|
cb.show_keyboard();
|
||||||
|
});
|
||||||
|
ui.add_space(12.0);
|
||||||
|
|
||||||
|
// Show external nodes URLs selection.
|
||||||
|
for conn in AppConfig::external_nodes_urls() {
|
||||||
|
View::radio_value(ui,
|
||||||
|
&mut self.method,
|
||||||
|
ConnectionMethod::External(conn.clone()),
|
||||||
|
conn);
|
||||||
|
ui.add_space(12.0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw external connections setup.
|
||||||
|
fn external_conn_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw modal content.
|
||||||
|
pub fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||||
|
ui.add_space(6.0);
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
// Draw external node URL text edit.
|
||||||
|
let text_edit_resp = egui::TextEdit::singleline(&mut self.ext_node_url_edit)
|
||||||
|
.id(Id::from(modal.id))
|
||||||
|
.font(TextStyle::Heading)
|
||||||
|
.desired_width(ui.available_width())
|
||||||
|
.cursor_at_end(true)
|
||||||
|
.ui(ui);
|
||||||
|
text_edit_resp.request_focus();
|
||||||
|
if text_edit_resp.clicked() {
|
||||||
|
cb.show_keyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error when specified URL is not valid.
|
||||||
|
if self.ext_node_url_error {
|
||||||
|
ui.add_space(12.0);
|
||||||
|
ui.label(RichText::new(t!("wallets.invalid_url"))
|
||||||
|
.size(17.0)
|
||||||
|
.color(Colors::RED));
|
||||||
|
}
|
||||||
|
ui.add_space(12.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show modal buttons.
|
||||||
|
ui.scope(|ui| {
|
||||||
|
// Setup spacing between buttons.
|
||||||
|
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||||
|
|
||||||
|
// Add button callback.
|
||||||
|
let on_add = || {
|
||||||
|
let error = Url::parse(self.ext_node_url_edit.as_str()).is_err();
|
||||||
|
self.ext_node_url_error = error;
|
||||||
|
if !error {
|
||||||
|
AppConfig::add_external_node_url(self.ext_node_url_edit.clone());
|
||||||
|
// Close modal.
|
||||||
|
cb.hide_keyboard();
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.columns(2, |columns| {
|
||||||
|
columns[0].vertical_centered_justified(|ui| {
|
||||||
|
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
|
||||||
|
// Close modal.
|
||||||
|
cb.hide_keyboard();
|
||||||
|
modal.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
columns[1].vertical_centered_justified(|ui| {
|
||||||
|
View::button(ui, t!("modal.add"), Colors::WHITE, on_add);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.add_space(6.0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,15 +12,8 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
mod connection;
|
||||||
|
pub use connection::ConnectionSetup;
|
||||||
|
|
||||||
#[derive(Default)]
|
mod types;
|
||||||
pub struct ConnectionSetup {
|
pub use types::*;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConnectionSetup {
|
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
22
src/gui/views/wallets/setup/types.rs
Normal file
22
src/gui/views/wallets/setup/types.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2023 The Grim Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
/// Wallet node connection method type.
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum ConnectionMethod {
|
||||||
|
/// Integrated node connection.
|
||||||
|
Integrated,
|
||||||
|
/// External node connection.
|
||||||
|
External(String)
|
||||||
|
}
|
|
@ -17,16 +17,17 @@ use egui::Margin;
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::View;
|
use crate::gui::views::View;
|
||||||
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
/// Selected wallet list item content.
|
/// Selected wallet list item content.
|
||||||
pub struct WalletContent {
|
pub struct WalletContent {
|
||||||
/// Current wallet instance.
|
/// Current wallet instance.
|
||||||
item: String
|
wallet: Wallet
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WalletContent {
|
impl WalletContent {
|
||||||
fn new(item: String) -> Self {
|
fn new(wallet: Wallet) -> Self {
|
||||||
Self { item }
|
Self { wallet }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,12 +21,14 @@ use crate::gui::icons::{ARROW_LEFT, GEAR, GLOBE, PLUS};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, ModalContainer, Root, TitlePanel, TitleType, View};
|
use crate::gui::views::{Modal, ModalContainer, Root, TitlePanel, TitleType, View};
|
||||||
use crate::gui::views::wallets::creation::{MnemonicSetup, WalletCreation};
|
use crate::gui::views::wallets::creation::{MnemonicSetup, WalletCreation};
|
||||||
|
use crate::gui::views::wallets::setup::ConnectionSetup;
|
||||||
use crate::gui::views::wallets::wallet::WalletContent;
|
use crate::gui::views::wallets::wallet::WalletContent;
|
||||||
|
use crate::wallet::{Wallet, WalletList};
|
||||||
|
|
||||||
/// Wallets content.
|
/// Wallets content.
|
||||||
pub struct Wallets {
|
pub struct WalletsContent {
|
||||||
/// List of wallets.
|
/// List of wallets.
|
||||||
list: Vec<String>,
|
list: Vec<Wallet>,
|
||||||
|
|
||||||
/// Selected list item content.
|
/// Selected list item content.
|
||||||
item_content: Option<WalletContent>,
|
item_content: Option<WalletContent>,
|
||||||
|
@ -37,28 +39,28 @@ pub struct Wallets {
|
||||||
modal_ids: Vec<&'static str>
|
modal_ids: Vec<&'static str>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Wallets {
|
impl Default for WalletsContent {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
//TODO load list.
|
|
||||||
Self {
|
Self {
|
||||||
list: vec![],
|
list: WalletList::list(),
|
||||||
item_content: None,
|
item_content: None,
|
||||||
creation_content: WalletCreation::default(),
|
creation_content: WalletCreation::default(),
|
||||||
modal_ids: vec![
|
modal_ids: vec![
|
||||||
WalletCreation::NAME_PASS_MODAL,
|
WalletCreation::NAME_PASS_MODAL,
|
||||||
MnemonicSetup::WORD_INPUT_MODAL
|
MnemonicSetup::WORD_INPUT_MODAL,
|
||||||
|
ConnectionSetup::ADD_CONNECTION_URL_MODAL
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModalContainer for Wallets {
|
impl ModalContainer for WalletsContent {
|
||||||
fn modal_ids(&self) -> &Vec<&'static str> {
|
fn modal_ids(&self) -> &Vec<&'static str> {
|
||||||
&self.modal_ids
|
&self.modal_ids
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wallets {
|
impl WalletsContent {
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
||||||
// Show modal content for current ui container.
|
// Show modal content for current ui container.
|
||||||
if self.can_draw_modal() {
|
if self.can_draw_modal() {
|
||||||
|
@ -70,6 +72,9 @@ impl Wallets {
|
||||||
MnemonicSetup::WORD_INPUT_MODAL => {
|
MnemonicSetup::WORD_INPUT_MODAL => {
|
||||||
self.creation_content.mnemonic_setup.modal_ui(ui, modal, cb);
|
self.creation_content.mnemonic_setup.modal_ui(ui, modal, cb);
|
||||||
}
|
}
|
||||||
|
ConnectionSetup::ADD_CONNECTION_URL_MODAL => {
|
||||||
|
self.creation_content.network_setup.modal_ui(ui, modal, cb);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -176,7 +181,7 @@ impl Wallets {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if ui can show [`Wallets`] list and [`WalletContent`] content at same time.
|
/// Check if ui can show [`WalletsContent`] list and [`WalletContent`] content at same time.
|
||||||
fn is_dual_panel_mode(ui: &mut egui::Ui, frame: &mut eframe::Frame) -> bool {
|
fn is_dual_panel_mode(ui: &mut egui::Ui, frame: &mut eframe::Frame) -> bool {
|
||||||
let dual_panel_root = Root::is_dual_panel_mode(frame);
|
let dual_panel_root = Root::is_dual_panel_mode(frame);
|
||||||
let max_width = ui.available_width();
|
let max_width = ui.available_width();
|
||||||
|
|
|
@ -93,13 +93,13 @@ pub fn start(mut options: eframe::NativeOptions, app_creator: eframe::AppCreator
|
||||||
options.default_theme = eframe::Theme::Light;
|
options.default_theme = eframe::Theme::Light;
|
||||||
options.renderer = eframe::Renderer::Wgpu;
|
options.renderer = eframe::Renderer::Wgpu;
|
||||||
options.initial_window_size = Some(egui::Vec2::new(1200.0, 720.0));
|
options.initial_window_size = Some(egui::Vec2::new(1200.0, 720.0));
|
||||||
|
// Setup translations.
|
||||||
setup_i18n();
|
setup_i18n();
|
||||||
|
// Start integrated node if needed.
|
||||||
if Settings::app_config_to_read().auto_start_node {
|
if Settings::app_config_to_read().auto_start_node {
|
||||||
Node::start();
|
Node::start();
|
||||||
}
|
}
|
||||||
|
// Launch graphical interface.
|
||||||
let _ = eframe::run_native("Grim", options, app_creator);
|
let _ = eframe::run_native("Grim", options, app_creator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,34 +45,11 @@ impl PeersConfig {
|
||||||
/// Save peers config to the file.
|
/// Save peers config to the file.
|
||||||
pub fn save(&self) {
|
pub fn save(&self) {
|
||||||
let chain_type = AppConfig::chain_type();
|
let chain_type = AppConfig::chain_type();
|
||||||
let config_path = Settings::get_config_path(Self::FILE_NAME, Some(&chain_type));
|
let chain_name = Some(chain_type.shortname());
|
||||||
|
let config_path = Settings::get_config_path(Self::FILE_NAME, chain_name);
|
||||||
Settings::write_to_file(self, config_path);
|
Settings::write_to_file(self, config_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save seed peer.
|
|
||||||
pub fn save_seed(&mut self, peer: String) {
|
|
||||||
self.seeds.insert(self.seeds.len(), peer);
|
|
||||||
self.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Save allowed peer.
|
|
||||||
pub fn save_allowed(&mut self, peer: String) {
|
|
||||||
self.allowed.insert(self.allowed.len(), peer);
|
|
||||||
self.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Save denied peer.
|
|
||||||
pub fn save_denied(&mut self, peer: String) {
|
|
||||||
self.denied.insert(self.denied.len(), peer);
|
|
||||||
self.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Save preferred peer.
|
|
||||||
pub fn save_preferred(&mut self, peer: String) {
|
|
||||||
self.preferred.insert(self.preferred.len(), peer);
|
|
||||||
self.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert string to [`PeerAddr`] if address is in correct format (`host:port`) and available.
|
/// Convert string to [`PeerAddr`] if address is in correct format (`host:port`) and available.
|
||||||
pub fn peer_to_addr(peer: String) -> Option<PeerAddr> {
|
pub fn peer_to_addr(peer: String) -> Option<PeerAddr> {
|
||||||
match SocketAddr::from_str(peer.as_str()) {
|
match SocketAddr::from_str(peer.as_str()) {
|
||||||
|
@ -169,7 +146,8 @@ impl NodeConfig {
|
||||||
|
|
||||||
// Initialize peers config.
|
// Initialize peers config.
|
||||||
let peers_config = {
|
let peers_config = {
|
||||||
let path = Settings::get_config_path(PeersConfig::FILE_NAME, Some(chain_type));
|
let chain_name = Some(chain_type.shortname());
|
||||||
|
let path = Settings::get_config_path(PeersConfig::FILE_NAME, chain_name);
|
||||||
let config = Settings::read_from_file::<PeersConfig>(path.clone());
|
let config = Settings::read_from_file::<PeersConfig>(path.clone());
|
||||||
if !path.exists() || config.is_err() {
|
if !path.exists() || config.is_err() {
|
||||||
Self::save_default_peers_config(chain_type)
|
Self::save_default_peers_config(chain_type)
|
||||||
|
@ -180,7 +158,8 @@ impl NodeConfig {
|
||||||
|
|
||||||
// Initialize node config.
|
// Initialize node config.
|
||||||
let node_config = {
|
let node_config = {
|
||||||
let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, Some(chain_type));
|
let chain_name = Some(chain_type.shortname());
|
||||||
|
let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, chain_name);
|
||||||
let config = Settings::read_from_file::<ConfigMembers>(path.clone());
|
let config = Settings::read_from_file::<ConfigMembers>(path.clone());
|
||||||
if !path.exists() || config.is_err() {
|
if !path.exists() || config.is_err() {
|
||||||
Self::save_default_node_server_config(chain_type)
|
Self::save_default_node_server_config(chain_type)
|
||||||
|
@ -194,9 +173,10 @@ impl NodeConfig {
|
||||||
|
|
||||||
/// Save default node config for specified [`ChainTypes`].
|
/// Save default node config for specified [`ChainTypes`].
|
||||||
fn save_default_node_server_config(chain_type: &ChainTypes) -> ConfigMembers {
|
fn save_default_node_server_config(chain_type: &ChainTypes) -> ConfigMembers {
|
||||||
let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, Some(chain_type));
|
let chain_name = Some(chain_type.shortname());
|
||||||
|
let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, chain_name.clone());
|
||||||
let mut default_config = GlobalConfig::for_chain(chain_type);
|
let mut default_config = GlobalConfig::for_chain(chain_type);
|
||||||
default_config.update_paths(&Settings::get_working_path(Some(chain_type)));
|
default_config.update_paths(&Settings::get_base_path(chain_name));
|
||||||
let config = default_config.members.unwrap();
|
let config = default_config.members.unwrap();
|
||||||
Settings::write_to_file(&config, path);
|
Settings::write_to_file(&config, path);
|
||||||
config
|
config
|
||||||
|
@ -204,7 +184,8 @@ impl NodeConfig {
|
||||||
|
|
||||||
/// Save default peers config for specified [`ChainTypes`].
|
/// Save default peers config for specified [`ChainTypes`].
|
||||||
fn save_default_peers_config(chain_type: &ChainTypes) -> PeersConfig {
|
fn save_default_peers_config(chain_type: &ChainTypes) -> PeersConfig {
|
||||||
let path = Settings::get_config_path(PeersConfig::FILE_NAME, Some(chain_type));
|
let chain_name = Some(chain_type.shortname());
|
||||||
|
let path = Settings::get_config_path(PeersConfig::FILE_NAME, chain_name);
|
||||||
let config = PeersConfig::default();
|
let config = PeersConfig::default();
|
||||||
Settings::write_to_file(&config, path);
|
Settings::write_to_file(&config, path);
|
||||||
config
|
config
|
||||||
|
@ -212,10 +193,8 @@ impl NodeConfig {
|
||||||
|
|
||||||
/// Save node config to the file.
|
/// Save node config to the file.
|
||||||
pub fn save(&self) {
|
pub fn save(&self) {
|
||||||
let config_path = Settings::get_config_path(
|
let chain_name = Some(self.node.server.chain_type.shortname());
|
||||||
SERVER_CONFIG_FILE_NAME,
|
let config_path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, chain_name);
|
||||||
Some(&self.node.server.chain_type)
|
|
||||||
);
|
|
||||||
Settings::write_to_file(&self.node, config_path);
|
Settings::write_to_file(&self.node, config_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +235,7 @@ impl NodeConfig {
|
||||||
|
|
||||||
/// Get path for secret file.
|
/// Get path for secret file.
|
||||||
fn get_secret_path(chain_type: &ChainTypes, secret_file_name: &str) -> PathBuf {
|
fn get_secret_path(chain_type: &ChainTypes, secret_file_name: &str) -> PathBuf {
|
||||||
let grin_path = Settings::get_working_path(Some(chain_type));
|
let grin_path = Settings::get_base_path(Some(chain_type.shortname()));
|
||||||
let mut api_secret_path = grin_path;
|
let mut api_secret_path = grin_path;
|
||||||
api_secret_path.push(secret_file_name);
|
api_secret_path.push(secret_file_name);
|
||||||
api_secret_path
|
api_secret_path
|
||||||
|
@ -446,7 +425,7 @@ impl NodeConfig {
|
||||||
|
|
||||||
/// Get API server IP and port.
|
/// Get API server IP and port.
|
||||||
pub fn get_api_ip_port() -> (String, String) {
|
pub fn get_api_ip_port() -> (String, String) {
|
||||||
let saved_addr = Self::get_api_address().as_str();
|
let saved_addr = Self::get_api_address();
|
||||||
let (addr, port) = saved_addr.split_once(":").unwrap();
|
let (addr, port) = saved_addr.split_once(":").unwrap();
|
||||||
(addr.into(), port.into())
|
(addr.into(), port.into())
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,11 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
mod stratum;
|
||||||
|
mod mine_block;
|
||||||
|
|
||||||
mod node;
|
mod node;
|
||||||
pub use node::Node;
|
pub use node::Node;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
pub use config::*;
|
||||||
mod stratum;
|
|
||||||
mod mine_block;
|
|
||||||
|
|
||||||
pub use config::{NodeConfig, PeersConfig};
|
|
|
@ -24,21 +24,28 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
use crate::node::NodeConfig;
|
use crate::node::NodeConfig;
|
||||||
|
use crate::wallet::WalletList;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Static settings state to be accessible globally.
|
/// Static settings state to be accessible globally.
|
||||||
static ref SETTINGS_STATE: Arc<Settings> = Arc::new(Settings::init());
|
static ref SETTINGS_STATE: Arc<Settings> = Arc::new(Settings::init());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Application configuration file name.
|
||||||
const APP_CONFIG_FILE_NAME: &'static str = "app.toml";
|
const APP_CONFIG_FILE_NAME: &'static str = "app.toml";
|
||||||
|
|
||||||
|
/// Default external node URL.
|
||||||
|
const DEFAULT_EXTERNAL_NODE_URL: &'static str = "https://grinnnode.live:3413";
|
||||||
|
|
||||||
/// Common application settings.
|
/// Common application settings.
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
/// Run node server on startup.
|
/// Run node server on startup.
|
||||||
pub auto_start_node: bool,
|
pub auto_start_node: bool,
|
||||||
/// Chain type for node and wallets.
|
/// Chain type for node and wallets.
|
||||||
chain_type: ChainTypes
|
chain_type: ChainTypes,
|
||||||
|
/// URLs of external nodes for wallets.
|
||||||
|
external_nodes_urls: Vec<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppConfig {
|
impl Default for AppConfig {
|
||||||
|
@ -46,12 +53,15 @@ impl Default for AppConfig {
|
||||||
Self {
|
Self {
|
||||||
auto_start_node: false,
|
auto_start_node: false,
|
||||||
chain_type: ChainTypes::default(),
|
chain_type: ChainTypes::default(),
|
||||||
|
external_nodes_urls: vec![
|
||||||
|
DEFAULT_EXTERNAL_NODE_URL.to_string()
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
/// Initialize application config from the disk.
|
/// Initialize application config from the file.
|
||||||
pub fn init() -> Self {
|
pub fn init() -> Self {
|
||||||
let path = Settings::get_config_path(APP_CONFIG_FILE_NAME, None);
|
let path = Settings::get_config_path(APP_CONFIG_FILE_NAME, None);
|
||||||
let parsed = Settings::read_from_file::<AppConfig>(path.clone());
|
let parsed = Settings::read_from_file::<AppConfig>(path.clone());
|
||||||
|
@ -64,7 +74,7 @@ impl AppConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save app config to disk.
|
/// Save app config to file.
|
||||||
pub fn save(&self) {
|
pub fn save(&self) {
|
||||||
Settings::write_to_file(self, Settings::get_config_path(APP_CONFIG_FILE_NAME, None));
|
Settings::write_to_file(self, Settings::get_config_path(APP_CONFIG_FILE_NAME, None));
|
||||||
}
|
}
|
||||||
|
@ -82,6 +92,9 @@ impl AppConfig {
|
||||||
let node_config = NodeConfig::for_chain_type(chain_type);
|
let node_config = NodeConfig::for_chain_type(chain_type);
|
||||||
w_node_config.node = node_config.node;
|
w_node_config.node = node_config.node;
|
||||||
w_node_config.peers = node_config.peers;
|
w_node_config.peers = node_config.peers;
|
||||||
|
|
||||||
|
// Reload wallets.
|
||||||
|
WalletList::reload(chain_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,14 +117,31 @@ impl AppConfig {
|
||||||
w_app_config.auto_start_node = !autostart;
|
w_app_config.auto_start_node = !autostart;
|
||||||
w_app_config.save();
|
w_app_config.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get external nodes URLs.
|
||||||
|
pub fn external_nodes_urls() -> Vec<String> {
|
||||||
|
let r_config = Settings::app_config_to_read();
|
||||||
|
r_config.external_nodes_urls.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add external node URL.
|
||||||
|
pub fn add_external_node_url(address: String) {
|
||||||
|
let mut w_config = Settings::app_config_to_update();
|
||||||
|
w_config.external_nodes_urls.insert(0, address);
|
||||||
|
w_config.save();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const WORKING_DIRECTORY_NAME: &'static str = ".grim";
|
/// Main application directory name.
|
||||||
|
const MAIN_DIR_NAME: &'static str = ".grim";
|
||||||
|
|
||||||
/// Provides access to app and node configs.
|
/// Provides access to application, integrated node and wallets configs.
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
|
/// Application config instance.
|
||||||
app_config: Arc<RwLock<AppConfig>>,
|
app_config: Arc<RwLock<AppConfig>>,
|
||||||
node_config: Arc<RwLock<NodeConfig>>
|
/// Integrated node config instance.
|
||||||
|
node_config: Arc<RwLock<NodeConfig>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
@ -120,7 +150,7 @@ impl Settings {
|
||||||
let app_config = AppConfig::init();
|
let app_config = AppConfig::init();
|
||||||
Self {
|
Self {
|
||||||
node_config: Arc::new(RwLock::new(NodeConfig::for_chain_type(&app_config.chain_type))),
|
node_config: Arc::new(RwLock::new(NodeConfig::for_chain_type(&app_config.chain_type))),
|
||||||
app_config: Arc::new(RwLock::new(app_config))
|
app_config: Arc::new(RwLock::new(app_config)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,16 +174,16 @@ impl Settings {
|
||||||
SETTINGS_STATE.app_config.write().unwrap()
|
SETTINGS_STATE.app_config.write().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get working directory path for the application.
|
/// Get base directory path for config.
|
||||||
pub fn get_working_path(chain_type: Option<&ChainTypes>) -> PathBuf {
|
pub fn get_base_path(sub_dir: Option<String>) -> PathBuf {
|
||||||
// Check if dir exists.
|
// Check if dir exists.
|
||||||
let mut path = match dirs::home_dir() {
|
let mut path = match dirs::home_dir() {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
None => PathBuf::new(),
|
None => PathBuf::new(),
|
||||||
};
|
};
|
||||||
path.push(WORKING_DIRECTORY_NAME);
|
path.push(MAIN_DIR_NAME);
|
||||||
if chain_type.is_some() {
|
if sub_dir.is_some() {
|
||||||
path.push(chain_type.unwrap().shortname());
|
path.push(sub_dir.unwrap());
|
||||||
}
|
}
|
||||||
// Create if the default path doesn't exist.
|
// Create if the default path doesn't exist.
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
|
@ -162,15 +192,14 @@ impl Settings {
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get config file path from provided name and [`ChainTypes`] if needed.
|
/// Get config file path from provided name and sub-directory if needed.
|
||||||
pub fn get_config_path(config_name: &str, chain_type: Option<&ChainTypes>) -> PathBuf {
|
pub fn get_config_path(config_name: &str, sub_dir: Option<String>) -> PathBuf {
|
||||||
let main_path = Self::get_working_path(chain_type);
|
let mut settings_path = Self::get_base_path(sub_dir);
|
||||||
let mut settings_path = main_path.clone();
|
|
||||||
settings_path.push(config_name);
|
settings_path.push(config_name);
|
||||||
settings_path
|
settings_path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read config from a file
|
/// Read config from the file.
|
||||||
pub fn read_from_file<T: DeserializeOwned>(config_path: PathBuf) -> Result<T, ConfigError> {
|
pub fn read_from_file<T: DeserializeOwned>(config_path: PathBuf) -> Result<T, ConfigError> {
|
||||||
let file_content = fs::read_to_string(config_path.clone())?;
|
let file_content = fs::read_to_string(config_path.clone())?;
|
||||||
let parsed = toml::from_str::<T>(file_content.as_str());
|
let parsed = toml::from_str::<T>(file_content.as_str());
|
||||||
|
@ -185,7 +214,7 @@ impl Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write config to a file
|
/// Write config to the file.
|
||||||
pub fn write_to_file<T: Serialize>(config: &T, path: PathBuf) {
|
pub fn write_to_file<T: Serialize>(config: &T, path: PathBuf) {
|
||||||
let conf_out = toml::to_string(config).unwrap();
|
let conf_out = toml::to_string(config).unwrap();
|
||||||
let mut file = File::create(path.to_str().unwrap()).unwrap();
|
let mut file = File::create(path.to_str().unwrap()).unwrap();
|
||||||
|
|
95
src/wallet/config.rs
Normal file
95
src/wallet/config.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright 2023 The Grim Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use crate::{AppConfig, Settings};
|
||||||
|
use crate::wallet::WalletList;
|
||||||
|
|
||||||
|
/// Wallet configuration.
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct WalletConfig {
|
||||||
|
/// Identifier for a wallet.
|
||||||
|
id: OsString,
|
||||||
|
/// Readable wallet name.
|
||||||
|
name: String,
|
||||||
|
/// External node connection URL.
|
||||||
|
external_node_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wallet configuration file name.
|
||||||
|
const CONFIG_FILE_NAME: &'static str = "grim-wallet.toml";
|
||||||
|
|
||||||
|
impl WalletConfig {
|
||||||
|
/// Create wallet config.
|
||||||
|
pub fn create(id: OsString, name: String) -> WalletConfig {
|
||||||
|
let config_path = Self::get_config_path(&id);
|
||||||
|
let config = WalletConfig {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
external_node_url: None,
|
||||||
|
};
|
||||||
|
Settings::write_to_file(&config, config_path);
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load config from provided wallet dir.
|
||||||
|
pub fn load(wallet_dir: PathBuf) -> Option<WalletConfig> {
|
||||||
|
let mut config_path: PathBuf = wallet_dir.clone();
|
||||||
|
config_path.push(CONFIG_FILE_NAME);
|
||||||
|
if let Ok(config) = Settings::read_from_file::<WalletConfig>(config_path) {
|
||||||
|
return Some(config)
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get config file path for provided wallet identifier.
|
||||||
|
fn get_config_path(id: &OsString) -> PathBuf {
|
||||||
|
let chain_type = AppConfig::chain_type();
|
||||||
|
let mut config_path = WalletList::get_wallets_base_dir(&chain_type);
|
||||||
|
config_path.push(id);
|
||||||
|
config_path.push(CONFIG_FILE_NAME);
|
||||||
|
config_path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save wallet config.
|
||||||
|
fn save(&self) {
|
||||||
|
let config_path = Self::get_config_path(&self.id);
|
||||||
|
Settings::write_to_file(self, config_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get readable wallet name.
|
||||||
|
pub fn get_name(&self) -> &String {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set readable wallet name.
|
||||||
|
pub fn set_name(&mut self, name: String) {
|
||||||
|
self.name = name;
|
||||||
|
self.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get external node connection URL.
|
||||||
|
pub fn get_external_node_url(&self) -> &Option<String> {
|
||||||
|
&self.external_node_url
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set external node connection URL.
|
||||||
|
pub fn set_external_node_url(&mut self, url: Option<String>) {
|
||||||
|
self.external_node_url = url;
|
||||||
|
self.save();
|
||||||
|
}
|
||||||
|
}
|
81
src/wallet/list.rs
Normal file
81
src/wallet/list.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright 2023 The Grim Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
use grin_core::global::ChainTypes;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use crate::{AppConfig, Settings};
|
||||||
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// Global wallets state.
|
||||||
|
static ref WALLETS_STATE: Arc<RwLock<WalletList >> = Arc::new(RwLock::new(WalletList::load()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List of created wallets.
|
||||||
|
pub struct WalletList {
|
||||||
|
list: Vec<Wallet>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base wallets directory name.
|
||||||
|
pub const BASE_DIR_NAME: &'static str = "wallets";
|
||||||
|
|
||||||
|
impl WalletList {
|
||||||
|
/// Load list of wallets.
|
||||||
|
fn load() -> Self {
|
||||||
|
Self { list: Self::load_wallets(&AppConfig::chain_type()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load wallets for provided [`ChainType`].
|
||||||
|
fn load_wallets(chain_type: &ChainTypes) -> Vec<Wallet> {
|
||||||
|
let mut wallets = Vec::new();
|
||||||
|
let wallets_dir = Self::get_wallets_base_dir(chain_type);
|
||||||
|
// Load wallets from directory.
|
||||||
|
for dir in wallets_dir.read_dir().unwrap() {
|
||||||
|
let wallet = Wallet::load(dir.unwrap().path());
|
||||||
|
if let Some(w) = wallet {
|
||||||
|
wallets.push(w);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
wallets
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get wallets base directory for provided [`ChainTypes`].
|
||||||
|
pub fn get_wallets_base_dir(chain_type: &ChainTypes) -> PathBuf {
|
||||||
|
let mut wallets_path = Settings::get_base_path(Some(chain_type.shortname()));
|
||||||
|
wallets_path.push(BASE_DIR_NAME);
|
||||||
|
// Create wallets directory if it doesn't exist.
|
||||||
|
if !wallets_path.exists() {
|
||||||
|
let _ = fs::create_dir_all(wallets_path.clone());
|
||||||
|
}
|
||||||
|
wallets_path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get list of wallets.
|
||||||
|
pub fn list() -> Vec<Wallet> {
|
||||||
|
let r_state = WALLETS_STATE.read().unwrap();
|
||||||
|
r_state.list.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reload list of wallets for provided [`ChainTypes`].
|
||||||
|
pub fn reload(chain_type: &ChainTypes) {
|
||||||
|
let mut w_state = WALLETS_STATE.write().unwrap();
|
||||||
|
w_state.list = Self::load_wallets(chain_type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,5 +13,10 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
mod wallet;
|
mod wallet;
|
||||||
|
pub use wallet::Wallet;
|
||||||
|
|
||||||
// pub use self::wallet::{init, init_from_seed};
|
mod config;
|
||||||
|
pub use config::*;
|
||||||
|
|
||||||
|
mod list;
|
||||||
|
pub use list::WalletList;
|
||||||
|
|
|
@ -12,3 +12,51 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use crate::node::NodeConfig;
|
||||||
|
|
||||||
|
use crate::wallet::WalletConfig;
|
||||||
|
|
||||||
|
/// Wallet loaded from config.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Wallet {
|
||||||
|
/// Identifier for a wallet, name of wallet directory.
|
||||||
|
id: OsString,
|
||||||
|
/// Base path for wallet data.
|
||||||
|
pub(crate) path: String,
|
||||||
|
/// Loaded file config.
|
||||||
|
pub(crate) config: WalletConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wallet {
|
||||||
|
/// Create new wallet from provided name.
|
||||||
|
pub fn create(name: String ) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load wallet from provided data path.
|
||||||
|
pub fn load(data_path: PathBuf) -> Option<Wallet> {
|
||||||
|
if !data_path.is_dir() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let wallet_config = WalletConfig::load(data_path.clone());
|
||||||
|
if let Some(config) = wallet_config {
|
||||||
|
// Set id as wallet directory name.
|
||||||
|
let id = data_path.file_name().unwrap().to_os_string();
|
||||||
|
let path = data_path.to_str().unwrap().to_string();
|
||||||
|
return Some(Self { id, path, config });
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get wallet node connection URL.
|
||||||
|
pub fn get_connection_url(&self) -> String {
|
||||||
|
match self.config.get_external_node_url() {
|
||||||
|
None => {
|
||||||
|
format!("http://{}", NodeConfig::get_api_address())
|
||||||
|
}
|
||||||
|
Some(url) => url.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue