feat: config, registry autodetect, remember domains
This commit is contained in:
Generated
+32
-5
@@ -611,7 +611,9 @@ dependencies = [
|
||||
"serde_json",
|
||||
"slint",
|
||||
"slint-build",
|
||||
"toml 1.1.4+spec-1.1.0",
|
||||
"winit",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4129,7 +4131,7 @@ dependencies = [
|
||||
"regex",
|
||||
"serde_json",
|
||||
"tar",
|
||||
"toml",
|
||||
"toml 0.9.12+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4662,6 +4664,21 @@ dependencies = [
|
||||
"winnow 0.7.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.4+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime 1.1.1+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
@@ -4695,18 +4712,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
version = "1.1.3+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
|
||||
dependencies = [
|
||||
"winnow 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
@@ -5757,6 +5774,16 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
|
||||
@@ -9,7 +9,9 @@ i-slint-backend-winit = "1.16.1"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
slint = { version = "1.16.1", features = ["renderer-skia", "serde"] }
|
||||
toml = "1.1.4"
|
||||
winit = "0.30"
|
||||
winreg = "0.56.0"
|
||||
|
||||
[build-dependencies]
|
||||
slint-build = "1.16.1"
|
||||
|
||||
+21
-6
@@ -101,13 +101,28 @@ HTML-макеты → PNG: **headless Edge** (`--headless --screenshot`), он
|
||||
|
||||
## Этап E — конфиг и настройки
|
||||
|
||||
- [ ] `config.toml` рядом с exe / `%APPDATA%\bropicker` (крейс `toml`), serde-структура BrowserConfig
|
||||
- [ ] Логика запуска с учётом галочек: remember-choice → записать выбор для домена;
|
||||
always-ask → показывать пикер несмотря на запомненное
|
||||
- [ ] Страница настроек: TabWidget + LineEdit/SpinBox/ComboBox/CheckBox std-widgets;
|
||||
вход по кнопке cog из футера (колбэк settings-clicked уже есть)
|
||||
- [ ] Автодетект установленных браузеров (реестр Windows) как fallback при отсутствии конфига
|
||||
### E1 — конфиг, автодетект, логика запуска — ГОТОВО
|
||||
|
||||
- [x] `src/config.rs`: `%APPDATA%\bropicker\config.toml` (крейсы `toml` + `winreg`);
|
||||
env `BP_CONFIG` для тестов; секции `[[browsers]]`, `[remembered]` (домен→имя),
|
||||
`[settings]` (remember_choice, always_ask)
|
||||
- [x] Автодетект из реестра `HKLM/HKCU\SOFTWARE\Clients\StartMenuInternet`:
|
||||
имя + exe из shell\open\command, дедуп по пути, проверка существования файла;
|
||||
результат автосохраняется. Проверено: найдены Firefox, Zen, Chrome, Edge
|
||||
- [x] Иконки по имени (firefox/chrome/opera/brave/yandex → `logos/*.png`,
|
||||
остальное → globe.svg). Нет логотипов: Edge, Zen
|
||||
- [x] Логика запуска: домен запомнен + always_ask=off → мгновенный запуск без окна
|
||||
(протестировано); иначе пикер. Клик/«Открыть» + remember_choice → запись
|
||||
домена в конфиг; переключение галочек персистится
|
||||
- [x] URL-бар привязан к свойству (был захардкожен), метка чекбокса — домен
|
||||
|
||||
### E2 — страница настроек (следующий шаг)
|
||||
|
||||
- [ ] Переключение вид/настройки по кнопке cog (settings-clicked)
|
||||
- [ ] Редактор списка браузеров: имя/путь/флаги (LineEdit), вкл/выкл
|
||||
- [ ] Список запомненных доменов с удалением
|
||||
- [ ] Чтение флагов из .lnk ярлыков (крейс `lnk`) — по желанию
|
||||
- [ ] Добавить логотипы Edge/Zen в logos/ (пересоздать набор из alrrr/browser-logos)
|
||||
|
||||
## Этап F — релиз
|
||||
|
||||
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct BrowserEntry {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
#[serde(default)]
|
||||
pub flags: String,
|
||||
#[serde(default)]
|
||||
pub icon: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Settings {
|
||||
#[serde(default = "default_true")]
|
||||
pub remember_choice: bool,
|
||||
#[serde(default = "default_true")]
|
||||
pub always_ask: bool,
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
remember_choice: true,
|
||||
always_ask: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
pub browsers: Vec<BrowserEntry>,
|
||||
#[serde(default)]
|
||||
pub remembered: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
pub settings: Settings,
|
||||
}
|
||||
|
||||
pub fn config_path() -> PathBuf {
|
||||
if let Ok(p) = std::env::var("BP_CONFIG") {
|
||||
return PathBuf::from(p);
|
||||
}
|
||||
std::env::var("APPDATA")
|
||||
.map(|d| PathBuf::from(d).join("bropicker").join("config.toml"))
|
||||
.unwrap_or_else(|_| PathBuf::from("config.toml"))
|
||||
}
|
||||
|
||||
pub fn load() -> Config {
|
||||
let path = config_path();
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(s) => toml::from_str(&s).unwrap_or_else(|e| {
|
||||
eprintln!("[bp] config parse error ({path:?}): {e}");
|
||||
Config::default()
|
||||
}),
|
||||
Err(_) => Config::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(cfg: &Config) {
|
||||
let path = config_path();
|
||||
if let Some(dir) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(dir);
|
||||
}
|
||||
match toml::to_string_pretty(cfg) {
|
||||
Ok(s) => {
|
||||
if let Err(e) = std::fs::write(&path, s) {
|
||||
eprintln!("[bp] config save failed ({path:?}): {e}");
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("[bp] config serialize failed: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_browsers(cfg: &mut Config) {
|
||||
if cfg.browsers.is_empty() {
|
||||
cfg.browsers = detect_browsers();
|
||||
eprintln!("[bp] autodetected {} browsers", cfg.browsers.len());
|
||||
if !cfg.browsers.is_empty() {
|
||||
save(cfg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn domain_of(url: &str) -> String {
|
||||
let rest = url.split("://").nth(1).unwrap_or(url);
|
||||
let host = rest.split(['/', '?', '#']).next().unwrap_or("");
|
||||
let host = host.split('@').next().unwrap_or(host);
|
||||
let host = host.split(':').next().unwrap_or(host);
|
||||
host.trim_start_matches("www.").to_lowercase()
|
||||
}
|
||||
|
||||
fn extract_exe(cmd: &str) -> Option<String> {
|
||||
let t = cmd.trim();
|
||||
if t.starts_with('"') {
|
||||
t.split('"').nth(1).map(String::from)
|
||||
} else {
|
||||
t.split_whitespace().next().map(String::from)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect_browsers() -> Vec<BrowserEntry> {
|
||||
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||
use winreg::RegKey;
|
||||
|
||||
let mut out: Vec<BrowserEntry> = Vec::new();
|
||||
|
||||
for hive in [HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER] {
|
||||
let Ok(clients) =
|
||||
RegKey::predef(hive).open_subkey("SOFTWARE\\Clients\\StartMenuInternet")
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
for key_name in clients.enum_keys().flatten() {
|
||||
let Ok(client) = clients.open_subkey(&key_name) else {
|
||||
continue;
|
||||
};
|
||||
let friendly: String = client
|
||||
.get_value("")
|
||||
.unwrap_or_else(|_| key_name.clone());
|
||||
let command: String = client
|
||||
.open_subkey("shell\\open\\command")
|
||||
.and_then(|k| k.get_value(""))
|
||||
.unwrap_or_default();
|
||||
let Some(path) = extract_exe(&command) else {
|
||||
continue;
|
||||
};
|
||||
if !Path::new(&path).exists() {
|
||||
continue;
|
||||
}
|
||||
if out
|
||||
.iter()
|
||||
.any(|b: &BrowserEntry| b.path.eq_ignore_ascii_case(&path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
out.push(BrowserEntry {
|
||||
name: friendly,
|
||||
path,
|
||||
flags: String::new(),
|
||||
icon: String::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub fn icon_for(name: &str) -> &'static str {
|
||||
let n = name.to_lowercase();
|
||||
if n.contains("firefox") {
|
||||
"logos/firefox_48x48.png"
|
||||
} else if n.contains("chrome") {
|
||||
"logos/chrome_48x48.png"
|
||||
} else if n.contains("opera") {
|
||||
"logos/opera_48x48.png"
|
||||
} else if n.contains("brave") {
|
||||
"logos/brave_48x48.png"
|
||||
} else if n.contains("яндекс") || n.contains("yandex") {
|
||||
"logos/yandex_48x48.png"
|
||||
} else {
|
||||
"icons/globe.svg"
|
||||
}
|
||||
}
|
||||
+119
-65
@@ -1,55 +1,53 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use i_slint_backend_winit::WinitWindowAccessor;
|
||||
use slint::Model;
|
||||
|
||||
use crate::winit::center_window;
|
||||
mod config;
|
||||
mod winit;
|
||||
|
||||
use config::{BrowserEntry, Config};
|
||||
|
||||
slint::include_modules!();
|
||||
|
||||
fn load_icon(rel: &str) -> slint::Image {
|
||||
slint::Image::load_from_path(std::path::Path::new(rel)).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn init() -> State {
|
||||
let browsers = vec![
|
||||
BrowserConfig {
|
||||
icon: load_icon("logos/firefox_48x48.png"),
|
||||
name: "Work browser".into(),
|
||||
path: "firefox.exe".into(),
|
||||
flags: "--profile work".into(),
|
||||
is_default: true,
|
||||
},
|
||||
BrowserConfig {
|
||||
icon: load_icon("logos/chrome_48x48.png"),
|
||||
name: "Ku6epXBOCTuK".into(),
|
||||
path: "chrome.exe".into(),
|
||||
flags: "--profile personal".into(),
|
||||
is_default: false,
|
||||
},
|
||||
BrowserConfig {
|
||||
icon: load_icon("logos/opera_48x48.png"),
|
||||
name: "Chrome".into(),
|
||||
path: "chrome.exe".into(),
|
||||
flags: "".into(),
|
||||
is_default: false,
|
||||
},
|
||||
BrowserConfig {
|
||||
icon: load_icon("icons/globe.svg"),
|
||||
name: "Zen Browser".into(),
|
||||
path: "zen.exe".into(),
|
||||
flags: "".into(),
|
||||
is_default: false,
|
||||
},
|
||||
];
|
||||
fn to_ui_config(entry: &BrowserEntry) -> BrowserConfig {
|
||||
let icon_rel = if entry.icon.is_empty() {
|
||||
config::icon_for(&entry.name)
|
||||
} else {
|
||||
&entry.icon
|
||||
};
|
||||
BrowserConfig {
|
||||
icon: load_icon(icon_rel),
|
||||
name: entry.name.clone().into(),
|
||||
path: entry.path.clone().into(),
|
||||
flags: entry.flags.clone().into(),
|
||||
is_default: false,
|
||||
}
|
||||
}
|
||||
|
||||
let default_index = browsers
|
||||
fn launch(entry: &BrowserEntry, url: &str) -> std::io::Result<()> {
|
||||
let mut cmd = std::process::Command::new(&entry.path);
|
||||
if !entry.flags.is_empty() {
|
||||
cmd.args(entry.flags.split_whitespace());
|
||||
}
|
||||
cmd.arg(url).spawn().map(|_| ())
|
||||
}
|
||||
|
||||
fn init(cfg: Config, url: Option<String>) -> State {
|
||||
let cfg = Rc::new(RefCell::new(cfg));
|
||||
|
||||
let ui_browsers: Vec<BrowserConfig> = cfg
|
||||
.borrow()
|
||||
.browsers
|
||||
.iter()
|
||||
.position(|b| b.is_default)
|
||||
.unwrap_or_default();
|
||||
|
||||
let browser_model = Rc::new(slint::VecModel::<BrowserConfig>::from(browsers));
|
||||
.map(to_ui_config)
|
||||
.collect();
|
||||
let browser_model = Rc::new(slint::VecModel::<BrowserConfig>::from(ui_browsers));
|
||||
|
||||
let main_window = MainWindow::new().unwrap();
|
||||
|
||||
@@ -57,17 +55,19 @@ fn init() -> State {
|
||||
main_window.global::<Theme>().set_is_dark(false);
|
||||
}
|
||||
|
||||
let url = std::env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "ku6epxboctuk.github.io".into());
|
||||
main_window.set_current_url(url.into());
|
||||
main_window.set_selected_index(default_index as i32);
|
||||
main_window.set_remember_choice(true);
|
||||
main_window.set_always_ask(true);
|
||||
main_window.set_current_url(
|
||||
url.clone().unwrap_or_else(|| "ku6epxboctuk.github.io".into()).into(),
|
||||
);
|
||||
if let Some(u) = &url {
|
||||
main_window.set_current_domain(config::domain_of(u).into());
|
||||
}
|
||||
main_window.set_remember_choice(cfg.borrow().settings.remember_choice);
|
||||
main_window.set_always_ask(cfg.borrow().settings.always_ask);
|
||||
|
||||
main_window.on_launch_browser({
|
||||
let main_window = main_window.clone_strong();
|
||||
let browser_model = browser_model.clone();
|
||||
let cfg = cfg.clone();
|
||||
move |browser: BrowserConfig| {
|
||||
let index = (0..browser_model.row_count())
|
||||
.find(|&i| {
|
||||
@@ -78,19 +78,30 @@ fn init() -> State {
|
||||
.unwrap_or(0);
|
||||
main_window.set_selected_index(index as i32);
|
||||
|
||||
let entry = browser_model.row_data(index).expect("row exists");
|
||||
let url = main_window.get_current_url().to_string();
|
||||
|
||||
println!(
|
||||
"Launching [{}]: {:?} {:?} {}",
|
||||
index, browser.path, browser.flags, url
|
||||
index, entry.path, entry.flags, url
|
||||
);
|
||||
|
||||
let mut cmd = std::process::Command::new(browser.path.to_string());
|
||||
if !browser.flags.is_empty() {
|
||||
cmd.args(browser.flags.split_whitespace());
|
||||
}
|
||||
let entry_ref = BrowserEntry {
|
||||
name: entry.name.to_string(),
|
||||
path: entry.path.to_string(),
|
||||
flags: entry.flags.to_string(),
|
||||
icon: String::new(),
|
||||
};
|
||||
|
||||
match cmd.arg(url).spawn() {
|
||||
match launch(&entry_ref, &url) {
|
||||
Ok(_) => {
|
||||
let domain = config::domain_of(&url);
|
||||
if main_window.get_remember_choice() && !domain.is_empty() {
|
||||
cfg.borrow_mut()
|
||||
.remembered
|
||||
.insert(domain, entry_ref.name.clone());
|
||||
config::save(&cfg.borrow());
|
||||
}
|
||||
let _ = main_window.window().hide();
|
||||
}
|
||||
Err(e) => eprintln!("launch failed: {e}"),
|
||||
@@ -101,25 +112,34 @@ fn init() -> State {
|
||||
main_window.on_open_clicked({
|
||||
let main_window = main_window.clone_strong();
|
||||
let browser_model = browser_model.clone();
|
||||
let cfg = cfg.clone();
|
||||
move || {
|
||||
let index = main_window.get_selected_index().max(0) as usize;
|
||||
let Some(browser) = browser_model.row_data(index) else {
|
||||
let Some(ui_entry) = browser_model.row_data(index) else {
|
||||
return;
|
||||
};
|
||||
let entry = BrowserEntry {
|
||||
name: ui_entry.name.to_string(),
|
||||
path: ui_entry.path.to_string(),
|
||||
flags: ui_entry.flags.to_string(),
|
||||
icon: String::new(),
|
||||
};
|
||||
let url = main_window.get_current_url().to_string();
|
||||
|
||||
println!(
|
||||
"Launching [{}]: {:?} {:?} {}",
|
||||
index, browser.path, browser.flags, url
|
||||
index, entry.path, entry.flags, url
|
||||
);
|
||||
|
||||
let mut cmd = std::process::Command::new(browser.path.to_string());
|
||||
if !browser.flags.is_empty() {
|
||||
cmd.args(browser.flags.split_whitespace());
|
||||
}
|
||||
|
||||
match cmd.arg(url).spawn() {
|
||||
match launch(&entry, &url) {
|
||||
Ok(_) => {
|
||||
let domain = config::domain_of(&url);
|
||||
if main_window.get_remember_choice() && !domain.is_empty() {
|
||||
cfg.borrow_mut()
|
||||
.remembered
|
||||
.insert(domain, entry.name.clone());
|
||||
config::save(&cfg.borrow());
|
||||
}
|
||||
let _ = main_window.window().hide();
|
||||
}
|
||||
Err(e) => eprintln!("launch failed: {e}"),
|
||||
@@ -127,6 +147,26 @@ fn init() -> State {
|
||||
}
|
||||
});
|
||||
|
||||
main_window.on_toggle_remember({
|
||||
let cfg = cfg.clone();
|
||||
move |checked| {
|
||||
cfg.borrow_mut().settings.remember_choice = checked;
|
||||
config::save(&cfg.borrow());
|
||||
}
|
||||
});
|
||||
|
||||
main_window.on_toggle_always_ask({
|
||||
let cfg = cfg.clone();
|
||||
move |checked| {
|
||||
cfg.borrow_mut().settings.always_ask = checked;
|
||||
config::save(&cfg.borrow());
|
||||
}
|
||||
});
|
||||
|
||||
main_window.on_settings_clicked(move || {
|
||||
println!("Settings clicked");
|
||||
});
|
||||
|
||||
main_window.on_request_drag({
|
||||
let main_window = main_window.clone_strong();
|
||||
move || {
|
||||
@@ -147,29 +187,43 @@ fn init() -> State {
|
||||
State {
|
||||
main_window,
|
||||
browser_model,
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
pub main_window: MainWindow,
|
||||
pub browser_model: Rc<slint::VecModel<BrowserConfig>>,
|
||||
pub config: Rc<RefCell<Config>>,
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
// TODO: how to enable skia? not panics
|
||||
// let _selector = BackendSelector::new()
|
||||
// .backend_name("renderer_skia".into())
|
||||
// .select()
|
||||
// .unwrap();
|
||||
let mut cfg = config::load();
|
||||
config::ensure_browsers(&mut cfg);
|
||||
|
||||
let state = init();
|
||||
let url = std::env::args().nth(1);
|
||||
|
||||
if let Some(url) = &url {
|
||||
let domain = config::domain_of(url);
|
||||
if let Some(name) = cfg.remembered.get(&domain).cloned() {
|
||||
if cfg.settings.always_ask {
|
||||
eprintln!("[bp] '{domain}' remembered -> {name}, but always_ask is on");
|
||||
} else if let Some(entry) = cfg.browsers.iter().find(|b| b.name == name) {
|
||||
eprintln!("[bp] '{domain}' remembered -> launching {name}");
|
||||
let _ = launch(entry, url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let state = init(cfg, url);
|
||||
let main_window = state.main_window.clone_strong();
|
||||
main_window.show().unwrap();
|
||||
|
||||
let weak = main_window.as_weak();
|
||||
let _ = slint::invoke_from_event_loop(move || {
|
||||
if let Some(mw) = weak.upgrade() {
|
||||
center_window(mw.window());
|
||||
winit::center_window(mw.window());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+6
-6
@@ -89,12 +89,12 @@ export component Header inherits Rectangle {
|
||||
VerticalLayout {
|
||||
padding-top: 2px;
|
||||
|
||||
Text {
|
||||
text: "ku6epxboctuk.github.io";
|
||||
color: Theme.text-control;
|
||||
font-size: 14px;
|
||||
font-family: "Consolas";
|
||||
}
|
||||
Text {
|
||||
text: root.url;
|
||||
color: Theme.text-control;
|
||||
font-size: 14px;
|
||||
font-family: "Consolas";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -58,6 +58,7 @@ export component MainWindow inherits Window {
|
||||
title: "bropicker";
|
||||
in property <[BrowserConfig]> browser-model: [];
|
||||
in property <string> current-url: "";
|
||||
in property <string> current-domain: "";
|
||||
in-out property <bool> remember-choice: false;
|
||||
in-out property <bool> always-ask: true;
|
||||
in property <int> selected-index: 0;
|
||||
@@ -141,7 +142,7 @@ export component MainWindow inherits Window {
|
||||
padding-bottom: 8px;
|
||||
|
||||
CheckRow {
|
||||
label: "Запомнить выбор для " + root.current-url;
|
||||
label: "Запомнить выбор для " + root.current-domain;
|
||||
checked <=> root.remember-choice;
|
||||
toggled => {
|
||||
root.toggle-remember(root.remember-choice);
|
||||
|
||||
Reference in New Issue
Block a user