community: one area for the people on both networks, and the hub grows search tabs, a folder column and a queue column
This commit is contained in:
1 parent
6dce4e2ac7
commit
8c25f93afe
14 files changed
+2368
-68
No files matched your search
@@ -857,6 +857,9 @@ pub async fn connect_and_login(
|
||||
"id": u.id,
|
||||
"username": u.username,
|
||||
"name": u.filename.rsplit('\\').next().unwrap_or(&u.filename),
|
||||
// Full shared path too — Transfers shows the
|
||||
// parent folder ahead of the name, Qt-style.
|
||||
"path": u.filename,
|
||||
"state": state,
|
||||
"sent": u.sent,
|
||||
"total": u.total,
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
//! The Community area's own small backend: the local contact metadata
|
||||
//! (petnames, groups, notes, the two local marks) and the file dialogs the
|
||||
//! You section needs. Everything Soulseek lives in commands.rs, everything
|
||||
//! Freenet in freenet.rs / persona.rs / profile.rs — this is the glue that
|
||||
//! is neither.
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::config::{self, ContactMeta};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn contacts_meta_list(app: tauri::AppHandle) -> Vec<ContactMeta> {
|
||||
config::load(&app).contacts
|
||||
}
|
||||
|
||||
/// Upsert one contact's local metadata. An entry with nothing set is
|
||||
/// dropped rather than stored empty, so the list only ever holds people the
|
||||
/// user actually said something about.
|
||||
#[tauri::command]
|
||||
pub fn contact_meta_set(app: tauri::AppHandle, meta: ContactMeta) -> Vec<ContactMeta> {
|
||||
let blank = |s: &Option<String>| s.as_deref().map_or(true, |v| v.trim().is_empty());
|
||||
let mut cfg = config::load(&app);
|
||||
cfg.contacts.retain(|c| c.username != meta.username);
|
||||
let empty = blank(&meta.petname)
|
||||
&& blank(&meta.group)
|
||||
&& blank(&meta.note)
|
||||
&& !meta.trusted
|
||||
&& !meta.favorite;
|
||||
if !empty {
|
||||
let trim = |s: Option<String>| s.map(|v| v.trim().to_string()).filter(|v| !v.is_empty());
|
||||
cfg.contacts.push(ContactMeta {
|
||||
username: meta.username,
|
||||
petname: trim(meta.petname),
|
||||
group: trim(meta.group),
|
||||
note: trim(meta.note),
|
||||
trusted: meta.trusted,
|
||||
favorite: meta.favorite,
|
||||
});
|
||||
}
|
||||
config::save(&app, &cfg);
|
||||
cfg.contacts
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum PickKind {
|
||||
/// A source image for the avatar pipeline.
|
||||
Image,
|
||||
/// An existing recovery artifact to import.
|
||||
RecoveryOpen,
|
||||
/// Where to write a new recovery artifact.
|
||||
RecoverySave,
|
||||
}
|
||||
|
||||
const RECOVERY_EXT: &str = "ssrecovery";
|
||||
|
||||
/// Native file dialogs for the You section. Paths only — the bytes behind
|
||||
/// them are read and written by Rust (persona.rs, profile.rs), never by the
|
||||
/// webview.
|
||||
#[tauri::command]
|
||||
pub async fn community_pick_file(
|
||||
app: tauri::AppHandle,
|
||||
kind: PickKind,
|
||||
) -> Result<Option<String>, String> {
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
let done = move |p: Option<tauri_plugin_dialog::FilePath>| {
|
||||
let _ = tx.send(p.map(|p| p.to_string()));
|
||||
};
|
||||
match kind {
|
||||
PickKind::Image => app
|
||||
.dialog()
|
||||
.file()
|
||||
.add_filter("Image", &["jpg", "jpeg", "png", "webp"])
|
||||
.pick_file(done),
|
||||
PickKind::RecoveryOpen => app
|
||||
.dialog()
|
||||
.file()
|
||||
.add_filter("SoulStream recovery file", &[RECOVERY_EXT])
|
||||
.pick_file(done),
|
||||
PickKind::RecoverySave => app
|
||||
.dialog()
|
||||
.file()
|
||||
.add_filter("SoulStream recovery file", &[RECOVERY_EXT])
|
||||
.set_file_name(&format!("soulstream-identity.{RECOVERY_EXT}"))
|
||||
.save_file(done),
|
||||
}
|
||||
Ok(rx.await.unwrap_or(None))
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
mod commands;
|
||||
mod community;
|
||||
mod config;
|
||||
mod cover_logo;
|
||||
mod diagnostics;
|
||||
mod freenet;
|
||||
mod persona;
|
||||
mod hero_crop;
|
||||
mod import;
|
||||
mod hero_detector;
|
||||
@@ -11,6 +14,7 @@ mod lyric_prep;
|
||||
mod lyrics;
|
||||
mod pipeline;
|
||||
mod playback;
|
||||
mod profile;
|
||||
mod playlist_formats;
|
||||
mod playlists;
|
||||
mod provider_playlist;
|
||||
@@ -57,6 +61,10 @@ pub fn run() {
|
||||
preview::init(app.handle());
|
||||
loudness::init(app.handle());
|
||||
diagnostics::init(app.handle());
|
||||
// The experimental Freenet layer: applies the saved opt-in (off
|
||||
// by default) and forwards adapter status to the webview. Fully
|
||||
// isolated — its absence or failure touches nothing else.
|
||||
freenet::init(app.handle());
|
||||
if let Err(e) = pipeline::init(app.handle()) {
|
||||
log::error!("library init failed: {e}");
|
||||
}
|
||||
@@ -215,6 +223,26 @@ pub fn run() {
|
||||
commands::preload_cached_stream,
|
||||
commands::stream_waveform,
|
||||
commands::stream_cache_ready,
|
||||
freenet::freenet_status,
|
||||
freenet::freenet_set_enabled,
|
||||
freenet::freenet_detect,
|
||||
freenet::freenet_set_load_remote_images,
|
||||
freenet::freenet_node_info,
|
||||
freenet::freenet_node_start,
|
||||
freenet::freenet_node_stop,
|
||||
community::contacts_meta_list,
|
||||
community::contact_meta_set,
|
||||
community::community_pick_file,
|
||||
persona::persona_status,
|
||||
persona::persona_create,
|
||||
persona::persona_export,
|
||||
persona::persona_import,
|
||||
persona::persona_rotate,
|
||||
persona::persona_remove,
|
||||
profile::profile_get,
|
||||
profile::profile_publish,
|
||||
profile::profile_set_avatar,
|
||||
profile::profile_clear_avatar,
|
||||
preview::preview_resolve,
|
||||
preview::preview_prewarm,
|
||||
preview::preview_load,
|
||||
|
||||
Reference in new issue
Block a user