diff --git a/shared/i18n/locales/ar.json b/shared/i18n/locales/ar.json index 995a660..2c4bb36 100644 --- a/shared/i18n/locales/ar.json +++ b/shared/i18n/locales/ar.json @@ -103,6 +103,7 @@ "Could not browse {p0}: {p1}": "لا يمكن التصفح {p0}: {p1}", "Could not cancel indexing: {p0}": "لا يمكن إلغاء التصنيف: {p0}", "Could not change download state: {p0}": "لا يمكن تغيير حالة التنزيل: {p0}", + "Could not switch window mode: {p0}": "تعذر تغيير وضع النافذة: {p0}", "Could not clear all transfers: {p0}": "لا يمكن إزالة جميع التحويلات: {p0}", "Could not clear every finished transfer: {p0}": "لا أستطيع أن أضيف كل نقل جاهز: {p0}", "Could not download {p0}: {p1}": "لا يمكن تنزيل {p0}: {p1}", @@ -264,6 +265,8 @@ "Napstr application window": "Napstr نافذة التطبيق", "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.": "Napstr يطلب من Tor إنشاء v3 onion الذي خرائط إلى هذا الخادم التراجع ويستمر فقط في جلسة التطبيق الحالية.", "Napstr audio player": "Napstr مشغل الصوت", + "Restore full window": "استعادة النافذة الكاملة", + "Switch to compact player": "التبديل إلى المشغل المصغر", "Napstr connected": "Napstr متصلة", "Napstr continues to handle Nostr discovery and private Tor downloads.": "Napstr لا تزال تتعامل مع Nostr اكتشاف و Tor تنزيلات خاصة.", "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.": "Napstr يخلق قدرة عشوائية 256 بت ويقوم بإرسالها مع العنوان الأصلي داخل NIP-17.", diff --git a/shared/i18n/locales/bn.json b/shared/i18n/locales/bn.json index ecb8da3..fa38350 100644 --- a/shared/i18n/locales/bn.json +++ b/shared/i18n/locales/bn.json @@ -103,6 +103,7 @@ "Could not browse {p0}: {p1}": "{p0}: {p1} অনুসন্ধান করতে পারবেন না", "Could not cancel indexing: {p0}": "ইনডেকশন বাতিল করতে পারবেন না: {p0}", "Could not change download state: {p0}": "ডাউনলোড অবস্থা পরিবর্তন করতে পারেন না: {p0}", + "Could not switch window mode: {p0}": "উইন্ডো মোড পরিবর্তন করা যায়নি: {p0}", "Could not clear all transfers: {p0}": "সব রিপোর্টগুলি পরিষ্কার করতে পারেননি: {p0}", "Could not clear every finished transfer: {p0}": "যেকোনো সম্পূর্ণ ট্রান্সফারটি পরিষ্কার করা যায় না: {p0}", "Could not download {p0}: {p1}": "ডাউনলোড করতে পারবেন না {p0}: {p1}", @@ -264,6 +265,8 @@ "Napstr application window": "Napstr অ্যাপ্লিকেশন উইন্ডো", "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.": "Napstr Torকে একটি v3 onion তৈরি করার জন্য অনুরোধ করে যে এটি লোপব্যাক সার্ভারকে মানচিত্র করে এবং বর্তমান অ্যাপস সেশনের জন্য শুধুমাত্র দীর্ঘ।", "Napstr audio player": "Napstr অডিও প্লেয়ার", + "Restore full window": "সম্পূর্ণ উইন্ডো পুনরুদ্ধার করুন", + "Switch to compact player": "কমপ্যাক্ট প্লেয়ারে যান", "Napstr connected": "Napstr সংযুক্ত", "Napstr continues to handle Nostr discovery and private Tor downloads.": "Napstr এখনও Nostr খোঁজ এবং ব্যক্তিগত Tor ডাউনলোডগুলি পরিচালনা করে।", "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.": "Napstr একটি random 256-bit ক্ষমতা তৈরি করে এবং এটি NIP-17 এর মধ্যে অনিয়নের ঠিকানা দিয়ে পাঠিয়ে দেয়।", diff --git a/shared/i18n/locales/en.json b/shared/i18n/locales/en.json index 6bbd728..50fac60 100644 --- a/shared/i18n/locales/en.json +++ b/shared/i18n/locales/en.json @@ -103,6 +103,7 @@ "Could not browse {p0}: {p1}": "Could not browse {p0}: {p1}", "Could not cancel indexing: {p0}": "Could not cancel indexing: {p0}", "Could not change download state: {p0}": "Could not change download state: {p0}", + "Could not switch window mode: {p0}": "Could not switch window mode: {p0}", "Could not clear all transfers: {p0}": "Could not clear all transfers: {p0}", "Could not clear every finished transfer: {p0}": "Could not clear every finished transfer: {p0}", "Could not download {p0}: {p1}": "Could not download {p0}: {p1}", @@ -264,6 +265,8 @@ "Napstr application window": "Napstr application window", "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.": "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.", "Napstr audio player": "Napstr audio player", + "Restore full window": "Restore full window", + "Switch to compact player": "Switch to compact player", "Napstr connected": "Napstr connected", "Napstr continues to handle Nostr discovery and private Tor downloads.": "Napstr continues to handle Nostr discovery and private Tor downloads.", "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.": "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.", diff --git a/shared/i18n/locales/es.json b/shared/i18n/locales/es.json index 47a971c..c29938b 100644 --- a/shared/i18n/locales/es.json +++ b/shared/i18n/locales/es.json @@ -103,6 +103,7 @@ "Could not browse {p0}: {p1}": "No puede navegar {p0}: {p1}", "Could not cancel indexing: {p0}": "No podría cancelar la indicación: {p0}", "Could not change download state: {p0}": "No puede cambiar el estado de descarga: {p0}", + "Could not switch window mode: {p0}": "No se pudo cambiar el modo de ventana: {p0}", "Could not clear all transfers: {p0}": "No podía limpiar todos los traslados: {p0}", "Could not clear every finished transfer: {p0}": "No podía limpiar cada transferencia terminada: {p0}", "Could not download {p0}: {p1}": "No puede descargar {p0}: {p1}", @@ -264,6 +265,8 @@ "Napstr application window": "Napstr ventana de aplicación", "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.": "Napstr pide a Tor crear un onion v3 que mapa a ese servidor de retroceso y solo dura para la sesión de aplicación actual.", "Napstr audio player": "Napstr reproductor de audio", + "Restore full window": "Restaurar la ventana completa", + "Switch to compact player": "Cambiar al reproductor compacto", "Napstr connected": "Napstr conectados", "Napstr continues to handle Nostr discovery and private Tor downloads.": "Napstr continúa manejando Nostr descubrimiento y descargas privadas Tor.", "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.": "Napstr crea una capacidad aleatoria de 256 bits y la envía con el enderezo de onion dentro del NIP-17.", diff --git a/shared/i18n/locales/fr.json b/shared/i18n/locales/fr.json index 29d3694..9026c4a 100644 --- a/shared/i18n/locales/fr.json +++ b/shared/i18n/locales/fr.json @@ -103,6 +103,7 @@ "Could not browse {p0}: {p1}": "Ne pouvait pas naviguer {p0}: {p1}", "Could not cancel indexing: {p0}": "Il ne peut pas annuler l'indexation: {p0}", "Could not change download state: {p0}": "Il ne peut pas changer l'état de téléchargement: {p0}", + "Could not switch window mode: {p0}": "Impossible de changer le mode de la fenêtre : {p0}", "Could not clear all transfers: {p0}": "Je ne pouvais pas nettoyer tous les transferts: {p0}", "Could not clear every finished transfer: {p0}": "Je ne pouvais pas clarifier chaque transfert terminé: {p0}", "Could not download {p0}: {p1}": "Je ne peux pas télécharger {p0}: {p1}", @@ -264,6 +265,8 @@ "Napstr application window": "Napstr fenêtre d'application", "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.": "Napstr demande à Tor de créer un onion v3 qui maps vers ce serveur de reprise et ne dure que pour la session actuelle de l'application.", "Napstr audio player": "Napstr lecteur audio", + "Restore full window": "Restaurer la fenêtre complète", + "Switch to compact player": "Passer au mini-lecteur", "Napstr connected": "Napstr connecté", "Napstr continues to handle Nostr discovery and private Tor downloads.": "Napstr continue à gérer Nostr découvertes et Tor téléchargements privés.", "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.": "Napstr crée une capacité de 256 bits par hasard et l'envoie avec l'adresse onion à l'intérieur du NIP-17.", diff --git a/shared/i18n/locales/hi.json b/shared/i18n/locales/hi.json index a9c41b4..be96bb7 100644 --- a/shared/i18n/locales/hi.json +++ b/shared/i18n/locales/hi.json @@ -103,6 +103,7 @@ "Could not browse {p0}: {p1}": "{p0}: {p1} का पता लगाने में असमर्थ", "Could not cancel indexing: {p0}": "इंडेक्स को रद्द नहीं किया जा सकता: {p0}", "Could not change download state: {p0}": "डाउनलोड की स्थिति में कोई बदलाव नहीं किया जा सकता: {p0}", + "Could not switch window mode: {p0}": "विंडो मोड नहीं बदला जा सका: {p0}", "Could not clear all transfers: {p0}": "सभी स्थानांतरणों को स्पष्ट नहीं किया जा सकता: {p0}", "Could not clear every finished transfer: {p0}": "हर समाप्त स्थानांतरण को स्पष्ट नहीं किया जा सकता: {p0}", "Could not download {p0}: {p1}": "डाउनलोड नहीं किया जा सकता {p0}: {p1}", @@ -264,6 +265,8 @@ "Napstr application window": "Napstr अनुप्रयोग विंडो", "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.": "Napstr पूछता है Tor एक v3 onion बनाने के लिए जो उस loopback सर्वर को मानचित्रित करता है और केवल वर्तमान ऐप सत्र के लिए रहता है।", "Napstr audio player": "Napstr ऑडियो प्लेयर", + "Restore full window": "पूरी विंडो पुनर्स्थापित करें", + "Switch to compact player": "कॉम्पैक्ट प्लेयर पर जाएँ", "Napstr connected": "Napstr कनेक्ट", "Napstr continues to handle Nostr discovery and private Tor downloads.": "Napstr अभी भी Nostr खोज और Tor निजी डाउनलोड का प्रबंधन करता है।", "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.": "Napstr एक यादृच्छिक 256-बिट क्षमता बनाता है और इसे NIP-17 के अंदर की ओनिन पते के साथ भेजता है।", diff --git a/shared/i18n/locales/id.json b/shared/i18n/locales/id.json index 7e3af02..cb8c5f8 100644 --- a/shared/i18n/locales/id.json +++ b/shared/i18n/locales/id.json @@ -103,6 +103,7 @@ "Could not browse {p0}: {p1}": "Tidak bisa melayari {p0}: {p1}", "Could not cancel indexing: {p0}": "Tidak bisa membatalkan indeks: {p0}", "Could not change download state: {p0}": "Tidak bisa mengubah status download: {p0}", + "Could not switch window mode: {p0}": "Tidak dapat mengubah mode jendela: {p0}", "Could not clear all transfers: {p0}": "Tidak dapat memadamkan semua transfer: {p0}", "Could not clear every finished transfer: {p0}": "Tidak bisa memisahkan setiap transfer selesai: {p0}", "Could not download {p0}: {p1}": "Tidak bisa mengunduh {p0}: {p1}", @@ -264,6 +265,8 @@ "Napstr application window": "Napstr jendela aplikasi", "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.": "Napstr meminta Tor untuk membuat onion v3 yang peta ke server loopback itu dan hanya berlangsung selama sesi aplikasi saat ini.", "Napstr audio player": "Napstr pemutar audio", + "Restore full window": "Pulihkan jendela penuh", + "Switch to compact player": "Beralih ke pemutar ringkas", "Napstr connected": "Napstr yang terhubung", "Napstr continues to handle Nostr discovery and private Tor downloads.": "Napstr terus menangani Nostr penemuan dan privasi Tor download.", "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.": "Napstr menciptakan kapasitas random 256 bit dan mengirimkannya dengan alamat onion di dalam NIP-17.", diff --git a/shared/i18n/locales/pt.json b/shared/i18n/locales/pt.json index 25b7f70..37f3134 100644 --- a/shared/i18n/locales/pt.json +++ b/shared/i18n/locales/pt.json @@ -103,6 +103,7 @@ "Could not browse {p0}: {p1}": "Não podia navegar {p0}: {p1}", "Could not cancel indexing: {p0}": "Não podia cancelar a indicação: {p0}", "Could not change download state: {p0}": "Não podia mudar o estado de download: {p0}", + "Could not switch window mode: {p0}": "Não foi possível mudar o modo da janela: {p0}", "Could not clear all transfers: {p0}": "Não podia limpar todos os transferências: {p0}", "Could not clear every finished transfer: {p0}": "Não podia limpar cada transferência concluída: {p0}", "Could not download {p0}: {p1}": "Não podia baixar {p0}: {p1}", @@ -264,6 +265,8 @@ "Napstr application window": "Napstr janela de aplicação", "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.": "Napstr pede a Tor para criar um onion v3 que mapeia para esse servidor de volta e dura apenas para a sessão atual da aplicação.", "Napstr audio player": "Napstr reproduzir áudio", + "Restore full window": "Restaurar a janela completa", + "Switch to compact player": "Mudar para o reprodutor compacto", "Napstr connected": "Napstr conectado", "Napstr continues to handle Nostr discovery and private Tor downloads.": "Napstr continua a lidar com Nostr descoberta e Tor downloads privados.", "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.": "O Napstr cria uma capacidade aleatória de 256 bits e o envia com o endereço onion dentro do NIP-17.", diff --git a/shared/i18n/locales/ur.json b/shared/i18n/locales/ur.json index 1bf135c..e7b4bea 100644 --- a/shared/i18n/locales/ur.json +++ b/shared/i18n/locales/ur.json @@ -103,6 +103,7 @@ "Could not browse {p0}: {p1}": "براؤز نہیں کر سکتے {p0}: {p1}", "Could not cancel indexing: {p0}": "انڈیکسنگ کو منسوخ نہیں کیا جا سکتا: {p0}", "Could not change download state: {p0}": "ڈاؤن لوڈ، اتارنا: {p0}", + "Could not switch window mode: {p0}": "ونڈو موڈ تبدیل نہیں کیا جا سکا: {p0}", "Could not clear all transfers: {p0}": "تمام ٹرانسفروں کو صاف نہیں کر سکتے ہیں: {p0}", "Could not clear every finished transfer: {p0}": "ہر مکمل ٹرانسمیشن کو صاف نہیں کیا جا سکتا: {p0}", "Could not download {p0}: {p1}": "ڈاؤن لوڈ نہیں کر سکتے ہیں {p0}: {p1}", @@ -264,6 +265,8 @@ "Napstr application window": "Napstr اپلی کیشن ونڈو", "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.": "Napstr Tor کو ایک v3 onion بنانے کے لئے پوچھتا ہے جو اس loopback سرور کو نقشہ دیتا ہے اور صرف موجودہ اپلی کیشن سیشن کے لئے جاری رہتا ہے.", "Napstr audio player": "Napstr آڈیو پلیئر", + "Restore full window": "مکمل ونڈو بحال کریں", + "Switch to compact player": "مختصر پلیئر پر جائیں", "Napstr connected": "Napstr منسلک", "Napstr continues to handle Nostr discovery and private Tor downloads.": "Napstr اب بھی Nostr تلاش اور ذاتی Tor ڈاؤن لوڈ کرنے کے لئے کام کرتا ہے.", "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.": "Napstr ایک تصادفی 256 بٹ کی صلاحیت پیدا کرتا ہے اور اسے NIP-17 کے اندر اندر اوون ایڈریس کے ساتھ بھیجتا ہے.", diff --git a/shared/i18n/locales/zh.json b/shared/i18n/locales/zh.json index ec1cb42..3de32be 100644 --- a/shared/i18n/locales/zh.json +++ b/shared/i18n/locales/zh.json @@ -103,6 +103,7 @@ "Could not browse {p0}: {p1}": "不能浏览 {p0}: {p1}", "Could not cancel indexing: {p0}": "不能取消索引: {p0}", "Could not change download state: {p0}": "不能改变下载状态: {p0}", + "Could not switch window mode: {p0}": "无法切换窗口模式: {p0}", "Could not clear all transfers: {p0}": "无法清除所有转账: {p0}", "Could not clear every finished transfer: {p0}": "未能清除每一次完成转会: {p0}", "Could not download {p0}: {p1}": "不能下载 {p0}: {p1}", @@ -264,6 +265,8 @@ "Napstr application window": "Napstr 应用窗口", "Napstr asks Tor to create a v3 onion that maps to that loopback server and lasts only for the current app session. Its private key is discarded.": "Napstr 要求 Tor 创建一个 v3 onion 地图到该 loopback 服务器,并且只持续当前应用程序会议。", "Napstr audio player": "Napstr 音频播放器", + "Restore full window": "恢复完整窗口", + "Switch to compact player": "切换到迷你播放器", "Napstr connected": "Napstr 连接", "Napstr continues to handle Nostr discovery and private Tor downloads.": "Napstr继续处理Nostr发现和私人Tor下载。", "Napstr creates a random 256-bit capability and sends it with the onion address inside NIP-17.": "Napstr 创建一个随机 256 位容量,并将其发送到 NIP-17 内部的线索地址。", diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 93ac488..f0a72ab 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -48,6 +48,14 @@ struct AppState { player: Arc, mobile: Arc, recovering_after_sleep: Arc, + full_window_geometry: Mutex>, +} + +#[derive(Clone, Copy)] +struct WindowGeometry { + size: tauri::PhysicalSize, + position: Option>, + maximized: bool, } struct ShutdownServices { @@ -2109,6 +2117,91 @@ fn toggle_maximise(window: tauri::Window) -> Result<(), String> { .map_err(|error| error.to_string()) } #[tauri::command] +fn set_compact_mode( + compact: bool, + window: tauri::Window, + state: State<'_, AppState>, +) -> Result<(), String> { + const FULL_MIN_WIDTH: f64 = 560.0; + const FULL_MIN_HEIGHT: f64 = 400.0; + const COMPACT_HEIGHT: f64 = 84.0; + + let mut saved = state + .full_window_geometry + .lock() + .map_err(|_| "window geometry lock poisoned".to_string())?; + + if compact { + if saved.is_some() { + return Ok(()); + } + let geometry = WindowGeometry { + size: window.outer_size().map_err(|error| error.to_string())?, + // Wayland compositors do not expose absolute window positions. + position: window.outer_position().ok(), + maximized: window.is_maximized().map_err(|error| error.to_string())?, + }; + let scale_factor = window.scale_factor().map_err(|error| error.to_string())?; + let logical_width = (f64::from(geometry.size.width) / scale_factor).max(FULL_MIN_WIDTH); + let maximum_width = window + .current_monitor() + .ok() + .flatten() + .map(|monitor| { + f64::from(monitor.work_area().size.width) / monitor.scale_factor() + }) + .unwrap_or(logical_width) + .max(logical_width); + let compact_size = tauri::LogicalSize::new(logical_width, COMPACT_HEIGHT); + if geometry.maximized { + window.unmaximize().map_err(|error| error.to_string())?; + } + window + .set_min_size(Some(tauri::LogicalSize::new( + FULL_MIN_WIDTH, + COMPACT_HEIGHT, + ))) + .map_err(|error| error.to_string())?; + window + .set_max_size(Some(tauri::LogicalSize::new( + maximum_width, + COMPACT_HEIGHT, + ))) + .map_err(|error| error.to_string())?; + window + .set_size(compact_size) + .map_err(|error| error.to_string())?; + *saved = Some(geometry); + return Ok(()); + } + + let Some(geometry) = *saved else { + return Ok(()); + }; + window + .set_max_size(None::>) + .map_err(|error| error.to_string())?; + window + .set_min_size(Some(tauri::LogicalSize::new( + FULL_MIN_WIDTH, + FULL_MIN_HEIGHT, + ))) + .map_err(|error| error.to_string())?; + window + .set_size(geometry.size) + .map_err(|error| error.to_string())?; + if let Some(position) = geometry.position { + window + .set_position(position) + .map_err(|error| error.to_string())?; + } + if geometry.maximized { + window.maximize().map_err(|error| error.to_string())?; + } + *saved = None; + Ok(()) +} +#[tauri::command] async fn close_window(window: tauri::Window, state: State<'_, AppState>) -> Result<(), String> { state.network.stop().await; state.network.preserve_interrupted_downloads()?; @@ -2188,6 +2281,7 @@ pub fn run() { player: Arc::new(player::NativePlayer::default()), mobile: mobile.clone(), recovering_after_sleep: Arc::new(AtomicBool::new(false)), + full_window_geometry: Mutex::new(None), }); if mobile.has_devices() { tauri::async_runtime::spawn(async move { @@ -2260,6 +2354,7 @@ pub fn run() { report_catalogue, minimise_window, toggle_maximise, + set_compact_mode, close_window ]) .build(tauri::generate_context!()) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index f248847..de924ed 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -197,6 +197,8 @@ let playerVolume = 0.85; let playerEnded = false; let lastPlayerError = ''; + let compactMode = false; + let compactModePending = false; let transferPaneHeight = 119; let stopTransferResize = () => {}; let transfers: Transfer[] = []; @@ -2061,6 +2063,20 @@ if (nativeReady) await invoke(command); }; + async function toggleCompactMode() { + if (!nativeReady || compactModePending) return; + compactModePending = true; + const compact = !compactMode; + try { + await invoke('set_compact_mode', { compact }); + compactMode = compact; + } catch (error) { + activityMessage = msg("Could not switch window mode: {p0}", { p0: String(error) }); + } finally { + compactModePending = false; + } + } + function beginWindowResize(event: PointerEvent, direction: WindowResizeDirection) { if (event.button !== 0) return; event.preventDefault(); @@ -2267,7 +2283,7 @@ {#if desktopRuntime}
-
+
@@ -2278,9 +2294,12 @@
-
{$t("Napstr - own your music again")}
-
diff --git a/src/styles.css b/src/styles.css index d0b4abd..256a467 100644 --- a/src/styles.css +++ b/src/styles.css @@ -40,6 +40,14 @@ button { color: #111; } grid-template-rows: 25px 70px 22px 55px minmax(120px, 1fr) var(--transfer-height, 119px) 23px; position: relative; } +.app-window.compact { height: 84px; max-height: 84px; grid-template-rows: 25px 55px; } +.app-window.compact .toolbar, +.app-window.compact .network-strip, +.app-window.compact .workspace, +.app-window.compact .transfer-dock, +.app-window.compact .statusbar { display: none; } +.app-window.compact .resize-n, +.app-window.compact .resize-s { display: none; } .window-resize-handle { position: fixed; z-index: 20; display: block; min-width: 0; min-height: 0; margin: 0; padding: 0; border: 0; outline: 0; background: transparent; } .resize-n { top: 0; left: 8px; right: 8px; height: 5px; cursor: n-resize; } @@ -64,15 +72,18 @@ button { color: #111; } font-weight: 700; letter-spacing: .2px; } -.title-left { display: flex; align-items: center; gap: 6px; } +.title-left { min-width: 0; display: flex; align-items: center; gap: 6px; } +.title-left > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .app-icon { width: 16px; height: 16px; display: block; overflow: hidden; background: var(--face); border: 1px solid; border-color: white #404040 #404040 white; } .app-icon img { display: block; width: auto; max-width: none; height: 14px; filter: drop-shadow(1px 1px 0 #555); } +.title-actions { display: flex; gap: 2px; } .window-controls { display: flex; gap: 2px; } -.window-controls button, .dock-title button, .tiny-button { +.compact-toggle, .window-controls button, .dock-title button, .tiny-button { min-width: 18px; height: 18px; padding: 0 3px; line-height: 13px; font-weight: bold; background: var(--face); border: 2px solid; border-color: var(--light) #333 #333 var(--light); } -.window-controls button:active, .dock-title button:active, .classic-button:active, .tool-button:active { border-color: #333 var(--light) var(--light) #333; padding-top: 1px; padding-left: 1px; } +.compact-toggle:active:not(:disabled), .window-controls button:active, .dock-title button:active, .classic-button:active, .tool-button:active { border-color: #333 var(--light) var(--light) #333; padding-top: 1px; padding-left: 1px; } +.compact-toggle:disabled { color: #777; } .toolbar { display: flex; align-items: stretch; gap: 2px; padding: 5px 6px 4px; border-top: 1px solid white; border-bottom: 1px solid #777; } .toolbar-brand { width: 190px; margin: 1px 2px 1px 0; padding: 3px 7px; display: flex; align-items: center; overflow: hidden; background: transparent; border: 1px solid transparent; box-shadow: none; } diff --git a/tests/browser/layout.spec.mjs b/tests/browser/layout.spec.mjs index dbe3d87..e54ef2b 100644 --- a/tests/browser/layout.spec.mjs +++ b/tests/browser/layout.spec.mjs @@ -1,6 +1,40 @@ import { test, expect } from '@playwright/test'; import { mockNative } from './helpers/native.mjs'; +test('Napstr compact player keeps only playback controls visible and restores the full window', async ({ page }) => { + await mockNative(page, { app: 'napstr', saved: 'en' }); + await page.goto('http://127.0.0.1:15173'); + await expect(page.locator('.search-button')).toBeEnabled(); + + await page.locator('.compact-toggle').click(); + await expect(page.locator('.app-window')).toHaveClass(/compact/); + await expect(page.locator('.player-bar')).toBeVisible(); + await expect(page.locator('.resize-e')).toBeVisible(); + await expect(page.locator('.resize-w')).toBeVisible(); + await expect(page.locator('.resize-se')).toBeVisible(); + await expect(page.locator('.resize-n')).toBeHidden(); + await expect(page.locator('.resize-s')).toBeHidden(); + await expect(page.locator('.toolbar')).toBeHidden(); + await expect(page.locator('.network-strip')).toBeHidden(); + await expect(page.locator('.workspace')).toBeHidden(); + await expect(page.locator('.transfer-dock')).toBeHidden(); + await expect(page.locator('.statusbar')).toBeHidden(); + await expect.poll(() => page.evaluate(() => window.calls.some(({ cmd, args }) => cmd === 'set_compact_mode' && args.compact === true))).toBe(true); + + for (const width of [1180, 850, 650, 560]) { + await page.setViewportSize({ width, height: 84 }); + const playerFits = await page.locator('.player-bar').evaluate((node) => + node.scrollWidth <= node.clientWidth + 2 && node.getBoundingClientRect().right <= innerWidth + 1 + ); + expect(playerFits, `compact player fits at ${width}px`).toBe(true); + } + + await page.locator('.compact-toggle').click(); + await expect(page.locator('.app-window')).not.toHaveClass(/compact/); + await expect(page.locator('.toolbar')).toBeVisible(); + await expect.poll(() => page.evaluate(() => window.calls.some(({ cmd, args }) => cmd === 'set_compact_mode' && args.compact === false))).toBe(true); +}); + for (const language of ['en', 'fr', 'ar']) { test(`Napstr layout: compact panels and transfers remain usable in ${language}`, async ({ page }) => { test.setTimeout(90_000);