ui: simplify rounding around list items
This commit is contained in:
parent
3f0d8facac
commit
b6e55b0762
5 changed files with 62 additions and 120 deletions
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use egui::{RichText, Rounding, ScrollArea, Stroke, vec2};
|
use egui::{RichText, Rounding, ScrollArea, vec2};
|
||||||
use grin_servers::DiffBlock;
|
use grin_servers::DiffBlock;
|
||||||
|
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
|
@ -135,34 +135,24 @@ impl NetworkTab for NetworkMetrics {
|
||||||
blocks_size,
|
blocks_size,
|
||||||
|ui, row_range| {
|
|ui, row_range| {
|
||||||
for index in row_range {
|
for index in row_range {
|
||||||
|
// Add space before the first item.
|
||||||
|
if index == 0 {
|
||||||
|
ui.add_space(4.0);
|
||||||
|
}
|
||||||
let db = stats.diff_stats.last_blocks.get(index).unwrap();
|
let db = stats.diff_stats.last_blocks.get(index).unwrap();
|
||||||
let rounding = if blocks_size == 1 {
|
block_item_ui(ui, db, View::item_rounding(index, blocks_size))
|
||||||
[true, true]
|
|
||||||
} else if index == 0 {
|
|
||||||
[true, false]
|
|
||||||
} else if index == blocks_size - 1 {
|
|
||||||
[false, true]
|
|
||||||
} else {
|
|
||||||
[false, false]
|
|
||||||
};
|
|
||||||
block_item_ui(ui, db, rounding)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {}
|
fn on_modal_ui(&mut self, _: &mut egui::Ui, _: &Modal, _: &dyn PlatformCallbacks) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const BLOCK_ITEM_HEIGHT: f32 = 77.0;
|
const BLOCK_ITEM_HEIGHT: f32 = 77.0;
|
||||||
|
|
||||||
/// Draw block difficulty item.
|
/// Draw block difficulty item.
|
||||||
fn block_item_ui(ui: &mut egui::Ui, db: &DiffBlock, rounding: [bool; 2]) {
|
fn block_item_ui(ui: &mut egui::Ui, db: &DiffBlock, rounding: Rounding) {
|
||||||
// Add space before the first item.
|
|
||||||
if rounding[0] {
|
|
||||||
ui.add_space(4.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
rect.set_height(BLOCK_ITEM_HEIGHT);
|
rect.set_height(BLOCK_ITEM_HEIGHT);
|
||||||
ui.allocate_ui_at_rect(rect, |ui| {
|
ui.allocate_ui_at_rect(rect, |ui| {
|
||||||
|
@ -171,17 +161,7 @@ fn block_item_ui(ui: &mut egui::Ui, db: &DiffBlock, rounding: [bool; 2]) {
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
// Draw round background.
|
// Draw round background.
|
||||||
rect.min += vec2(8.0, 0.0);
|
rect.min += vec2(8.0, 0.0);
|
||||||
ui.painter().rect(
|
ui.painter().rect(rect, rounding, Colors::WHITE, View::ITEM_STROKE);
|
||||||
rect,
|
|
||||||
Rounding {
|
|
||||||
nw: if rounding[0] { 8.0 } else { 0.0 },
|
|
||||||
ne: if rounding[0] { 8.0 } else { 0.0 },
|
|
||||||
sw: if rounding[1] { 8.0 } else { 0.0 },
|
|
||||||
se: if rounding[1] { 8.0 } else { 0.0 },
|
|
||||||
},
|
|
||||||
Colors::WHITE,
|
|
||||||
Stroke { width: 1.0, color: Colors::ITEM_STROKE }
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,12 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use egui::{RichText, Rounding, ScrollArea, Stroke};
|
use egui::{RichText, Rounding, ScrollArea};
|
||||||
use grin_chain::SyncStatus;
|
use grin_chain::SyncStatus;
|
||||||
use grin_servers::WorkerStats;
|
use grin_servers::WorkerStats;
|
||||||
|
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_NOTCH_MINUS, FOLDER_NOTCH_PLUS, HARD_DRIVES, PLUGS, PLUGS_CONNECTED, POLYGON};
|
use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_SIMPLE_MINUS, FOLDER_SIMPLE_PLUS, HARD_DRIVES, PLUGS, PLUGS_CONNECTED, POLYGON};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, Network, View};
|
use crate::gui::views::{Modal, Network, View};
|
||||||
use crate::gui::views::network::setup::StratumSetup;
|
use crate::gui::views::network::setup::StratumSetup;
|
||||||
|
@ -174,17 +174,12 @@ impl NetworkTab for NetworkMining {
|
||||||
workers_size,
|
workers_size,
|
||||||
|ui, row_range| {
|
|ui, row_range| {
|
||||||
for index in row_range {
|
for index in row_range {
|
||||||
|
// Add space before the first item.
|
||||||
|
if index == 0 {
|
||||||
|
ui.add_space(4.0);
|
||||||
|
}
|
||||||
let worker = stratum_stats.worker_stats.get(index).unwrap();
|
let worker = stratum_stats.worker_stats.get(index).unwrap();
|
||||||
let rounding = if workers_size == 1 {
|
worker_item_ui(ui, worker, View::item_rounding(index, workers_size));
|
||||||
[true, true]
|
|
||||||
} else if index == 0 {
|
|
||||||
[true, false]
|
|
||||||
} else if index == workers_size - 1 {
|
|
||||||
[false, true]
|
|
||||||
} else {
|
|
||||||
[false, false]
|
|
||||||
};
|
|
||||||
worker_item_ui(ui, worker, rounding)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -217,28 +212,13 @@ impl NetworkTab for NetworkMining {
|
||||||
const WORKER_ITEM_HEIGHT: f32 = 76.0;
|
const WORKER_ITEM_HEIGHT: f32 = 76.0;
|
||||||
|
|
||||||
/// Draw worker statistics item.
|
/// Draw worker statistics item.
|
||||||
fn worker_item_ui(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) {
|
fn worker_item_ui(ui: &mut egui::Ui, ws: &WorkerStats, rounding: Rounding) {
|
||||||
// Add space before the first item.
|
|
||||||
if rounding[0] {
|
|
||||||
ui.add_space(4.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.horizontal_wrapped(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
ui.vertical_centered_justified(|ui| {
|
ui.vertical_centered_justified(|ui| {
|
||||||
// Draw round background.
|
// Draw round background.
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
rect.set_height(WORKER_ITEM_HEIGHT);
|
rect.set_height(WORKER_ITEM_HEIGHT);
|
||||||
ui.painter().rect(
|
ui.painter().rect(rect, rounding, Colors::WHITE, View::ITEM_STROKE);
|
||||||
rect,
|
|
||||||
Rounding {
|
|
||||||
nw: if rounding[0] { 8.0 } else { 0.0 },
|
|
||||||
ne: if rounding[0] { 8.0 } else { 0.0 },
|
|
||||||
sw: if rounding[1] { 8.0 } else { 0.0 },
|
|
||||||
se: if rounding[1] { 8.0 } else { 0.0 },
|
|
||||||
},
|
|
||||||
Colors::WHITE,
|
|
||||||
Stroke { width: 1.0, color: Colors::ITEM_STROKE }
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
@ -266,14 +246,14 @@ fn worker_item_ui(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Draw accepted shares.
|
// Draw accepted shares.
|
||||||
let accepted_text = format!("{} {}", FOLDER_NOTCH_PLUS, ws.num_accepted);
|
let accepted_text = format!("{} {}", FOLDER_SIMPLE_PLUS, ws.num_accepted);
|
||||||
ui.heading(RichText::new(accepted_text)
|
ui.heading(RichText::new(accepted_text)
|
||||||
.color(Colors::GREEN)
|
.color(Colors::GREEN)
|
||||||
.size(16.0));
|
.size(16.0));
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Draw rejected shares.
|
// Draw rejected shares.
|
||||||
let rejected_text = format!("{} {}", FOLDER_NOTCH_MINUS, ws.num_rejected);
|
let rejected_text = format!("{} {}", FOLDER_SIMPLE_MINUS, ws.num_rejected);
|
||||||
ui.heading(RichText::new(rejected_text)
|
ui.heading(RichText::new(rejected_text)
|
||||||
.color(Colors::RED)
|
.color(Colors::RED)
|
||||||
.size(16.0));
|
.size(16.0));
|
||||||
|
|
|
@ -165,45 +165,29 @@ impl NetworkTab for NetworkNode {
|
||||||
// Show peer stats when available.
|
// Show peer stats when available.
|
||||||
if stats.peer_count > 0 {
|
if stats.peer_count > 0 {
|
||||||
View::sub_title(ui, format!("{} {}", HANDSHAKE, t!("network_node.peers")));
|
View::sub_title(ui, format!("{} {}", HANDSHAKE, t!("network_node.peers")));
|
||||||
for (index, ps) in stats.peer_stats.iter().enumerate() {
|
let peers = &stats.peer_stats;
|
||||||
let rounding = if stats.peer_count == 1 {
|
for (index, ps) in peers.iter().enumerate() {
|
||||||
[true, true]
|
peer_item_ui(ui, ps, View::item_rounding(index, peers.len()));
|
||||||
} else if index == 0 {
|
// Add space after the last item.
|
||||||
[true, false]
|
if index == peers.len() - 1 {
|
||||||
} else if index == &stats.peer_stats.len() - 1 {
|
ui.add_space(5.0);
|
||||||
[false, true]
|
}
|
||||||
} else {
|
|
||||||
[false, false]
|
|
||||||
};
|
|
||||||
ui.vertical_centered(|ui| {
|
|
||||||
peer_item_ui(ui, ps, rounding);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {}
|
fn on_modal_ui(&mut self, _: &mut egui::Ui, _: &Modal, _: &dyn PlatformCallbacks) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw connected peer info item.
|
/// Draw connected peer info item.
|
||||||
fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) {
|
fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, rounding: Rounding) {
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
rect.set_height(77.0);
|
rect.set_height(77.0);
|
||||||
ui.allocate_ui_at_rect(rect, |ui| {
|
ui.allocate_ui_at_rect(rect, |ui| {
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
// Draw round background.
|
// Draw round background.
|
||||||
ui.painter().rect(
|
ui.painter().rect(rect, rounding, Colors::WHITE, View::ITEM_STROKE);
|
||||||
rect,
|
|
||||||
Rounding {
|
|
||||||
nw: if rounding[0] { 8.0 } else { 0.0 },
|
|
||||||
ne: if rounding[0] { 8.0 } else { 0.0 },
|
|
||||||
sw: if rounding[1] { 8.0 } else { 0.0 },
|
|
||||||
se: if rounding[1] { 8.0 } else { 0.0 },
|
|
||||||
},
|
|
||||||
Colors::WHITE,
|
|
||||||
Stroke { width: 1.0, color: Colors::ITEM_STROKE }
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
|
|
||||||
|
@ -233,9 +217,4 @@ fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) {
|
||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add space after the last item.
|
|
||||||
if rounding[1] {
|
|
||||||
ui.add_space(5.0);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -12,13 +12,13 @@
|
||||||
// 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 egui::{Id, RichText, Rounding, Stroke, TextStyle, Ui, Widget};
|
use egui::{Id, RichText, Rounding, TextStyle, Ui, Widget};
|
||||||
use egui_extras::{Size, StripBuilder};
|
use egui_extras::{Size, StripBuilder};
|
||||||
use grin_core::global::ChainTypes;
|
use grin_core::global::ChainTypes;
|
||||||
|
|
||||||
use crate::AppConfig;
|
use crate::AppConfig;
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::icons::{HANDSHAKE, PLUG, TRASH, GLOBE_SIMPLE, PLUS_CIRCLE, ARROW_FAT_LINES_UP, ARROW_FAT_LINES_DOWN, ARROW_FAT_LINE_UP, PROHIBIT_INSET, CLIPBOARD_TEXT};
|
use crate::gui::icons::{ARROW_FAT_LINE_UP, ARROW_FAT_LINES_DOWN, ARROW_FAT_LINES_UP, CLIPBOARD_TEXT, COMPUTER_TOWER, GLOBE_SIMPLE, HANDSHAKE, PLUG, PLUS_CIRCLE, PROHIBIT_INSET, TRASH};
|
||||||
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::network::settings::NetworkSettings;
|
use crate::gui::views::network::settings::NetworkSettings;
|
||||||
|
@ -301,7 +301,7 @@ impl P2PSetup {
|
||||||
|
|
||||||
/// Draw peer list content based on provided [`PeerType`].
|
/// Draw peer list content based on provided [`PeerType`].
|
||||||
fn peer_list_ui(&mut self, ui: &mut Ui, peer_type: &PeerType, cb: &dyn PlatformCallbacks) {
|
fn peer_list_ui(&mut self, ui: &mut Ui, peer_type: &PeerType, cb: &dyn PlatformCallbacks) {
|
||||||
let peer_list = match peer_type {
|
let peers = match peer_type {
|
||||||
PeerType::DefaultSeed => {
|
PeerType::DefaultSeed => {
|
||||||
if AppConfig::chain_type() == ChainTypes::Testnet {
|
if AppConfig::chain_type() == ChainTypes::Testnet {
|
||||||
self.default_test_seeds.clone()
|
self.default_test_seeds.clone()
|
||||||
|
@ -314,26 +314,17 @@ impl P2PSetup {
|
||||||
PeerType::Denied => NodeConfig::get_denied_peers(),
|
PeerType::Denied => NodeConfig::get_denied_peers(),
|
||||||
PeerType::Preferred => NodeConfig::get_preferred_peers()
|
PeerType::Preferred => NodeConfig::get_preferred_peers()
|
||||||
};
|
};
|
||||||
for (index, peer) in peer_list.iter().enumerate() {
|
for (index, peer) in peers.iter().enumerate() {
|
||||||
let rounding = if peer_list.len() == 1 {
|
|
||||||
[true, true]
|
|
||||||
} else if index == 0 {
|
|
||||||
[true, false]
|
|
||||||
} else if index == peer_list.len() - 1 {
|
|
||||||
[false, true]
|
|
||||||
} else {
|
|
||||||
[false, false]
|
|
||||||
};
|
|
||||||
ui.horizontal_wrapped(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
// Draw peer list item.
|
// Draw peer list item.
|
||||||
Self::peer_item_ui(ui, peer, peer_type, rounding);
|
Self::peer_item_ui(ui, peer, peer_type, View::item_rounding(index, peers.len()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if peer_type != &PeerType::DefaultSeed {
|
if peer_type != &PeerType::DefaultSeed {
|
||||||
// Draw description.
|
// Draw description.
|
||||||
if peer_type != &PeerType::CustomSeed {
|
if peer_type != &PeerType::CustomSeed {
|
||||||
if !peer_list.is_empty() {
|
if !peers.is_empty() {
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
}
|
}
|
||||||
let desc = match peer_type {
|
let desc = match peer_type {
|
||||||
|
@ -482,22 +473,12 @@ impl P2PSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw peer list item.
|
/// Draw peer list item.
|
||||||
fn peer_item_ui(ui: &mut Ui, peer_addr: &String, peer_type: &PeerType, rounding: [bool; 2]) {
|
fn peer_item_ui(ui: &mut Ui, peer_addr: &String, peer_type: &PeerType, rounding: Rounding) {
|
||||||
// Draw round background.
|
// Draw round background.
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
rect.min += egui::emath::vec2(6.0, 0.0);
|
rect.min += egui::emath::vec2(6.0, 0.0);
|
||||||
rect.set_height(42.0);
|
rect.set_height(42.0);
|
||||||
ui.painter().rect(
|
ui.painter().rect(rect, rounding, Colors::WHITE, View::ITEM_STROKE);
|
||||||
rect,
|
|
||||||
Rounding {
|
|
||||||
nw: if rounding[0] { 6.0 } else { 0.0 },
|
|
||||||
ne: if rounding[0] { 6.0 } else { 0.0 },
|
|
||||||
sw: if rounding[1] { 6.0 } else { 0.0 },
|
|
||||||
se: if rounding[1] { 6.0 } else { 0.0 },
|
|
||||||
},
|
|
||||||
Colors::WHITE,
|
|
||||||
Stroke { width: 1.0, color: Colors::ITEM_STROKE }
|
|
||||||
);
|
|
||||||
|
|
||||||
StripBuilder::new(ui)
|
StripBuilder::new(ui)
|
||||||
.size(Size::exact(42.0))
|
.size(Size::exact(42.0))
|
||||||
|
@ -512,10 +493,10 @@ impl P2PSetup {
|
||||||
strip.cell(|ui| {
|
strip.cell(|ui| {
|
||||||
ui.horizontal_centered(|ui| {
|
ui.horizontal_centered(|ui| {
|
||||||
// Draw peer address.
|
// Draw peer address.
|
||||||
let peer_text = format!("{} {}", GLOBE_SIMPLE, &peer_addr);
|
let peer_text = format!("{} {}", COMPUTER_TOWER, &peer_addr);
|
||||||
ui.label(RichText::new(peer_text)
|
ui.label(RichText::new(peer_text)
|
||||||
.color(Colors::TEXT_BUTTON)
|
.color(Colors::TEXT_BUTTON)
|
||||||
.size(17.0));
|
.size(16.0));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if peer_type != &PeerType::DefaultSeed {
|
if peer_type != &PeerType::DefaultSeed {
|
||||||
|
|
|
@ -28,6 +28,8 @@ pub struct View;
|
||||||
impl View {
|
impl View {
|
||||||
/// Default stroke around views.
|
/// Default stroke around views.
|
||||||
pub const DEFAULT_STROKE: Stroke = Stroke { width: 1.0, color: Colors::STROKE };
|
pub const DEFAULT_STROKE: Stroke = Stroke { width: 1.0, color: Colors::STROKE };
|
||||||
|
/// Stroke around list items.
|
||||||
|
pub const ITEM_STROKE: Stroke = Stroke { width: 1.0, color: Colors::ITEM_STROKE };
|
||||||
|
|
||||||
/// Callback on Enter key press event.
|
/// Callback on Enter key press event.
|
||||||
pub fn on_enter_key(ui: &mut egui::Ui, cb: impl FnOnce()) {
|
pub fn on_enter_key(ui: &mut egui::Ui, cb: impl FnOnce()) {
|
||||||
|
@ -194,6 +196,26 @@ impl View {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate list item rounding based on item index.
|
||||||
|
pub fn item_rounding(index: usize, len: usize) -> Rounding {
|
||||||
|
let rounding = if len == 1 {
|
||||||
|
[true, true]
|
||||||
|
} else if index == 0 {
|
||||||
|
[true, false]
|
||||||
|
} else if index == len - 1 {
|
||||||
|
[false, true]
|
||||||
|
} else {
|
||||||
|
[false, false]
|
||||||
|
};
|
||||||
|
|
||||||
|
Rounding {
|
||||||
|
nw: if rounding[0] { 8.0 } else { 0.0 },
|
||||||
|
ne: if rounding[0] { 8.0 } else { 0.0 },
|
||||||
|
sw: if rounding[1] { 8.0 } else { 0.0 },
|
||||||
|
se: if rounding[1] { 8.0 } else { 0.0 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw rounded box with some value and label in the middle,
|
/// Draw rounded box with some value and label in the middle,
|
||||||
/// where is r = (top_left, top_right, bottom_left, bottom_right).
|
/// where is r = (top_left, top_right, bottom_left, bottom_right).
|
||||||
/// | VALUE |
|
/// | VALUE |
|
||||||
|
|
Loading…
Reference in a new issue