diff --git a/crates/pdfs-gui/src/app/widgets/share_dialog.rs b/crates/pdfs-gui/src/app/widgets/share_dialog.rs index d0e3c4e..8da0459 100644 --- a/crates/pdfs-gui/src/app/widgets/share_dialog.rs +++ b/crates/pdfs-gui/src/app/widgets/share_dialog.rs @@ -127,6 +127,26 @@ impl Drop for Busy { } } +/// A section's action, such as Send Invitations. It goes under the section's +/// rows, at their right edge, rather than in a row of its own: a row in a boxed +/// list reads as a setting, and a button alone in one looks misplaced. +fn group_button(label: &str) -> gtk4::Button { + let button = gtk4::Button::builder() + .label(label) + .halign(gtk4::Align::End) + .margin_top(12) + .build(); + button.add_css_class("suggested-action"); + button +} + +/// A section's only row while the share is being read. +fn loading_row() -> adw::ActionRow { + let row = adw::ActionRow::builder().title(gettext("Loading…")).build(); + row.add_prefix(&spinner()); + row +} + /// How an open Share dialog addresses its node. /// /// A node the browser is showing has a mountpoint-relative path. A node reached @@ -287,26 +307,17 @@ pub(crate) fn open_share_dialog(ui: &Rc, entry: &DirEntry) { .selected(0) .valign(gtk4::Align::Center) .build(); - let role_wrap = adw::ActionRow::builder().title(gettext("Role")).build(); - role_wrap.add_suffix(&role_drop); + // The role goes with the addresses it applies to, at the end of their row. + role_drop.set_tooltip_text(Some(&gettext("Role"))); + email_row.add_suffix(&role_drop); let message_row = adw::EntryRow::builder() .title(gettext("Message (optional)")) .build(); - let invite_btn = gtk4::Button::builder() - .label(gettext("Send Invitations")) - .halign(gtk4::Align::End) - .margin_top(6) - .build(); - invite_btn.add_css_class("suggested-action"); + let invite_btn = group_button(&gettext("Send Invitations")); invite_btn.set_sensitive(false); - let invite_wrap = adw::PreferencesRow::builder() - .activatable(false) - .child(&invite_btn) - .build(); invite_group.add(&email_row); - invite_group.add(&role_wrap); invite_group.add(&message_row); - invite_group.add(&invite_wrap); + invite_group.add(&invite_btn); let people = adw::PreferencesGroup::builder() .title(gettext("People with access")) @@ -315,21 +326,18 @@ pub(crate) fn open_share_dialog(ui: &Rc, entry: &DirEntry) { .title(gettext("Public link")) .build(); - let content = gtk4::Box::new(gtk4::Orientation::Vertical, 18); - content.set_margin_top(12); - content.set_margin_bottom(12); - content.set_margin_start(12); - content.set_margin_end(12); - content.append(&invite_group); - content.append(&people); - content.append(&link_group); - let clamp = adw::Clamp::builder().child(&content).build(); - let scroll = gtk4::ScrolledWindow::builder() - .vexpand(true) - .hscrollbar_policy(gtk4::PolicyType::Never) - .child(&clamp) - .build(); - toolbar.set_content(Some(&scroll)); + // Until the share is read, each section says so rather than standing + // empty under its title. + let people_loading = loading_row(); + people.add(&people_loading); + let link_loading = loading_row(); + link_group.add(&link_loading); + + let content = adw::PreferencesPage::new(); + content.add(&invite_group); + content.add(&people); + content.add(&link_group); + toolbar.set_content(Some(&content)); let dialog = adw::Dialog::builder() .title(pgettext("verb", "Share")) @@ -343,8 +351,8 @@ pub(crate) fn open_share_dialog(ui: &Rc, entry: &DirEntry) { target, people, link_group, - people_rows: RefCell::new(Vec::new()), - link_rows: RefCell::new(Vec::new()), + people_rows: RefCell::new(vec![people_loading.upcast()]), + link_rows: RefCell::new(vec![link_loading.upcast()]), }); // Check the addresses as they are typed: a malformed one marks the row as @@ -430,17 +438,35 @@ pub(crate) fn share_dialog_reload(state: &Rc) { repaint_share_link(&state, link.as_ref()); } Ok(Ok(Response::Error { message, .. })) => { + share_dialog_unread(&state); toast_error(&state.ui, &gettext("Couldn't load sharing"), &message) } - _ => toast_error( - &state.ui, - &gettext("Couldn't load sharing"), - &gettext("The Proton Drive service didn't respond."), - ), + _ => { + share_dialog_unread(&state); + toast_error( + &state.ui, + &gettext("Couldn't load sharing"), + &gettext("The Proton Drive service didn't respond."), + ) + } } }); } +/// The share couldn't be read: what the sections showed (rows still loading, +/// or ones that may now be stale) gives way to one line that says so. +fn share_dialog_unread(state: &Rc) { + for row in state.people_rows.borrow_mut().drain(..) { + state.people.remove(&row); + } + for row in state.link_rows.borrow_mut().drain(..) { + state.link_group.remove(&row); + } + let row = dim_row(&gettext("Couldn't load sharing")); + state.people.add(&row); + state.people_rows.borrow_mut().push(row.upcast()); +} + /// Rebuild the "People with access" rows from a fresh share listing. pub(crate) fn repaint_share_people(state: &Rc, entries: &[ShareEntry]) { for row in state.people_rows.borrow_mut().drain(..) { @@ -455,15 +481,18 @@ pub(crate) fn repaint_share_people(state: &Rc, entries: &[ShareEntr return; } for entry in entries { + // A member is the plain case and needs no word; an invitation says + // it is still waiting on the other side. let subtitle = match entry.kind { - ShareEntryKind::Member => gettext("Member"), - ShareEntryKind::ProtonInvite => gettext("Invited (pending)"), - ShareEntryKind::ExternalInvite => gettext("Invited (external, pending)"), + ShareEntryKind::Member => String::new(), + ShareEntryKind::ProtonInvite => gettext("Invitation pending"), + ShareEntryKind::ExternalInvite => gettext("Invitation pending, no Proton account yet"), }; let row = adw::ActionRow::builder() .title(&entry.email) .subtitle(&subtitle) .build(); + row.add_prefix(&adw::Avatar::new(32, Some(&entry.email), true)); // External invites can't have their role changed; show it read-only. // Members and Proton invites get a role dropdown. @@ -653,16 +682,7 @@ pub(crate) fn repaint_share_link(state: &Rc, link: Option<&PublicLi .build(); let expiry_row = adw::ActionRow::builder().title(gettext("Expires")).build(); expiry_row.add_suffix(&expiry_drop); - let create = gtk4::Button::builder() - .label(gettext("Create Public Link")) - .halign(gtk4::Align::End) - .margin_top(6) - .build(); - create.add_css_class("suggested-action"); - let create_wrap = adw::PreferencesRow::builder() - .activatable(false) - .child(&create) - .build(); + let create = group_button(&gettext("Create Public Link")); // Enter in the password field creates the link. let btn = create.clone(); @@ -689,11 +709,11 @@ pub(crate) fn repaint_share_link(state: &Rc, link: Option<&PublicLi state.link_group.add(&role_row); state.link_group.add(&pw_row); state.link_group.add(&expiry_row); - state.link_group.add(&create_wrap); + state.link_group.add(&create); rows.push(role_row.upcast()); rows.push(pw_row.upcast()); rows.push(expiry_row.upcast()); - rows.push(create_wrap.upcast()); + rows.push(create.upcast()); } } *state.link_rows.borrow_mut() = rows; diff --git a/po/de.po b/po/de.po index bdccd1d..fa14a00 100644 --- a/po/de.po +++ b/po/de.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 2.1.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 13:23+0200\n" +"POT-Creation-Date: 2026-10-09 02:38+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: German\n" @@ -57,50 +57,50 @@ msgstr "%-d. %b %Y" msgid "%A, %B %-d, %Y, %H:%M" msgstr "%A, %-d. %B %Y, %H:%M" -#: crates/pdfs-gui/src/app/main.rs:760 +#: crates/pdfs-gui/src/app/main.rs:761 #: crates/pdfs-gui/src/app/pages/activity_text.rs:539 #: crates/pdfs-gui/src/app/pages/browser.rs:454 #: crates/pdfs-gui/src/app/pages/browser.rs:819 #: crates/pdfs-gui/src/app/pages/browser.rs:1217 -#: crates/pdfs-gui/src/app/pages/browser.rs:2090 -#: crates/pdfs-gui/src/app/pages/browser.rs:2167 +#: crates/pdfs-gui/src/app/pages/browser.rs:2083 +#: crates/pdfs-gui/src/app/pages/browser.rs:2160 #: crates/pdfs-gui/src/app/pages/locations.rs:1624 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:252 #: crates/pdfs-gui/src/app/pages/trash.rs:191 crates/pdfs-gui/src/prompt.rs:171 msgid "My files" msgstr "Meine Dateien" -#: crates/pdfs-gui/src/app/main.rs:763 crates/pdfs-gui/src/app/main.rs:1407 +#: crates/pdfs-gui/src/app/main.rs:764 crates/pdfs-gui/src/app/main.rs:1389 #: crates/pdfs-gui/src/app/pages/albums.rs:638 #: crates/pdfs-gui/src/app/pages/photos.rs:842 #: crates/pdfs-gui/src/app/pages/places.rs:441 msgid "Photos" msgstr "Fotos" -#: crates/pdfs-gui/src/app/main.rs:768 +#: crates/pdfs-gui/src/app/main.rs:769 #: crates/pdfs-gui/src/app/pages/shared.rs:168 #: crates/pdfs-gui/src/app/pages/shared.rs:482 msgid "Shared with me" msgstr "Mit mir geteilt" -#: crates/pdfs-gui/src/app/main.rs:773 +#: crates/pdfs-gui/src/app/main.rs:774 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120 msgid "Shared by me" msgstr "Von mir geteilt" -#: crates/pdfs-gui/src/app/main.rs:776 +#: crates/pdfs-gui/src/app/main.rs:777 #: crates/pdfs-gui/src/app/pages/devices.rs:140 #: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "Computer" -#: crates/pdfs-gui/src/app/main.rs:777 +#: crates/pdfs-gui/src/app/main.rs:778 #: crates/pdfs-gui/src/app/pages/trash.rs:101 msgid "Trash" msgstr "Papierkorb" #. Translators: sidebar entry for the page listing this computer's synced folders. -#: crates/pdfs-gui/src/app/main.rs:779 +#: crates/pdfs-gui/src/app/main.rs:780 msgid "Sync" msgstr "Synchronisierung" @@ -132,29 +132,11 @@ msgstr "Etwas anderes hat dies zuerst geändert" msgid "Your Proton Drive is full" msgstr "Das Proton Drive ist voll" -#: crates/pdfs-gui/src/app/main.rs:1160 crates/pdfs-gui/src/app/main.rs:1389 -#: crates/pdfs-gui/src/app/pages/status.rs:385 -msgid "Preferences" -msgstr "Einstellungen" - -#: crates/pdfs-gui/src/app/main.rs:1161 -#: crates/pdfs-gui/src/app/widgets/compat.rs:262 -msgid "Keyboard Shortcuts" -msgstr "Tastenkürzel" - -#: crates/pdfs-gui/src/app/main.rs:1163 -msgid "About Proton Drive for Linux" -msgstr "Info zu Proton Drive for Linux" - -#: crates/pdfs-gui/src/app/main.rs:1168 -msgid "Main menu" -msgstr "Hauptmenü" - -#: crates/pdfs-gui/src/app/main.rs:1224 +#: crates/pdfs-gui/src/app/main.rs:1206 msgid "Proton Drive for Linux" msgstr "Proton Drive for Linux" -#: crates/pdfs-gui/src/app/main.rs:1232 +#: crates/pdfs-gui/src/app/main.rs:1214 msgid "" "Files-on-demand Proton Drive for the Linux desktop.\n" "\n" @@ -166,23 +148,23 @@ msgstr "" "Inoffizieller Client – nicht mit Proton AG verbunden, von Proton AG " "unterstützt oder befürwortet." -#: crates/pdfs-gui/src/app/main.rs:1237 +#: crates/pdfs-gui/src/app/main.rs:1219 msgid "Source Code" msgstr "Quellcode" -#: crates/pdfs-gui/src/app/main.rs:1241 +#: crates/pdfs-gui/src/app/main.rs:1223 msgid "Proton Terms of Service" msgstr "Nutzungsbedingungen von Proton" -#: crates/pdfs-gui/src/app/main.rs:1245 +#: crates/pdfs-gui/src/app/main.rs:1227 msgid "Proton Privacy Policy" msgstr "Datenschutzerklärung von Proton" -#: crates/pdfs-gui/src/app/main.rs:1250 +#: crates/pdfs-gui/src/app/main.rs:1232 msgid "Disclaimer" msgstr "Haftungsausschluss" -#: crates/pdfs-gui/src/app/main.rs:1254 +#: crates/pdfs-gui/src/app/main.rs:1236 msgid "" "This is an unofficial, community-built client. It is not affiliated with, " "endorsed by, or supported by Proton AG.\n" @@ -207,7 +189,7 @@ msgstr "" "bereitgestellt. Daten, deren Verlust nicht hinnehmbar ist, sollten " "zusätzlich gesichert werden." -#: crates/pdfs-gui/src/app/main.rs:1262 +#: crates/pdfs-gui/src/app/main.rs:1244 msgid "" "Places are named with data from GeoNames, licensed under GeoNames, lizenziert unter CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -227,7 +209,7 @@ msgstr "" "href=\"https://www.naturalearthdata.com/\">Natural Earth gezeichnet, die " "gemeinfrei sind." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, licensed under CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -224,7 +206,7 @@ msgstr "" "\">Natural Earth land outlines and country borders, which are in the " "public domain." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, con licencia CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -225,7 +207,7 @@ msgstr "" "fronteras de países de Natural " "Earth, que son de dominio público." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, sous licence CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -226,7 +208,7 @@ msgstr "" "frontières des pays de Natural " "Earth, qui sont dans le domaine public." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, con licenza CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -224,7 +206,7 @@ msgstr "" "confini nazionali di Natural " "Earth, che sono di pubblico dominio." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, gelicentieerd onder CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -225,7 +207,7 @@ msgstr "" "href=\"https://www.naturalearthdata.com/\">Natural Earth, die in het " "publieke domein vallen." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the \n" "Language-Team: LANGUAGE \n" @@ -60,50 +60,50 @@ msgstr "" msgid "%A, %B %-d, %Y, %H:%M" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:760 +#: crates/pdfs-gui/src/app/main.rs:761 #: crates/pdfs-gui/src/app/pages/activity_text.rs:539 #: crates/pdfs-gui/src/app/pages/browser.rs:454 #: crates/pdfs-gui/src/app/pages/browser.rs:819 #: crates/pdfs-gui/src/app/pages/browser.rs:1217 -#: crates/pdfs-gui/src/app/pages/browser.rs:2090 -#: crates/pdfs-gui/src/app/pages/browser.rs:2167 +#: crates/pdfs-gui/src/app/pages/browser.rs:2083 +#: crates/pdfs-gui/src/app/pages/browser.rs:2160 #: crates/pdfs-gui/src/app/pages/locations.rs:1624 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:252 #: crates/pdfs-gui/src/app/pages/trash.rs:191 crates/pdfs-gui/src/prompt.rs:171 msgid "My files" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:763 crates/pdfs-gui/src/app/main.rs:1407 +#: crates/pdfs-gui/src/app/main.rs:764 crates/pdfs-gui/src/app/main.rs:1389 #: crates/pdfs-gui/src/app/pages/albums.rs:638 #: crates/pdfs-gui/src/app/pages/photos.rs:842 #: crates/pdfs-gui/src/app/pages/places.rs:441 msgid "Photos" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:768 +#: crates/pdfs-gui/src/app/main.rs:769 #: crates/pdfs-gui/src/app/pages/shared.rs:168 #: crates/pdfs-gui/src/app/pages/shared.rs:482 msgid "Shared with me" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:773 +#: crates/pdfs-gui/src/app/main.rs:774 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120 msgid "Shared by me" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:776 +#: crates/pdfs-gui/src/app/main.rs:777 #: crates/pdfs-gui/src/app/pages/devices.rs:140 #: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:777 +#: crates/pdfs-gui/src/app/main.rs:778 #: crates/pdfs-gui/src/app/pages/trash.rs:101 msgid "Trash" msgstr "" #. Translators: sidebar entry for the page listing this computer's synced folders. -#: crates/pdfs-gui/src/app/main.rs:779 +#: crates/pdfs-gui/src/app/main.rs:780 msgid "Sync" msgstr "" @@ -135,29 +135,11 @@ msgstr "" msgid "Your Proton Drive is full" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1160 crates/pdfs-gui/src/app/main.rs:1389 -#: crates/pdfs-gui/src/app/pages/status.rs:385 -msgid "Preferences" -msgstr "" - -#: crates/pdfs-gui/src/app/main.rs:1161 -#: crates/pdfs-gui/src/app/widgets/compat.rs:262 -msgid "Keyboard Shortcuts" -msgstr "" - -#: crates/pdfs-gui/src/app/main.rs:1163 -msgid "About Proton Drive for Linux" -msgstr "" - -#: crates/pdfs-gui/src/app/main.rs:1168 -msgid "Main menu" -msgstr "" - -#: crates/pdfs-gui/src/app/main.rs:1224 +#: crates/pdfs-gui/src/app/main.rs:1206 msgid "Proton Drive for Linux" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1232 +#: crates/pdfs-gui/src/app/main.rs:1214 msgid "" "Files-on-demand Proton Drive for the Linux desktop.\n" "\n" @@ -165,23 +147,23 @@ msgid "" "AG." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1237 +#: crates/pdfs-gui/src/app/main.rs:1219 msgid "Source Code" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1241 +#: crates/pdfs-gui/src/app/main.rs:1223 msgid "Proton Terms of Service" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1245 +#: crates/pdfs-gui/src/app/main.rs:1227 msgid "Proton Privacy Policy" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1250 +#: crates/pdfs-gui/src/app/main.rs:1232 msgid "Disclaimer" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1254 +#: crates/pdfs-gui/src/app/main.rs:1236 msgid "" "This is an unofficial, community-built client. It is not affiliated with, " "endorsed by, or supported by Proton AG.\n" @@ -194,21 +176,21 @@ msgid "" "of any data you cannot afford to lose." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1262 +#: crates/pdfs-gui/src/app/main.rs:1244 msgid "" "Places are named with data from GeoNames, licensed under CC BY 4.0." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " "public domain." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames udostępnionych na licencji CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -228,7 +210,7 @@ msgstr "" "www.naturalearthdata.com/\">Natural Earth, które należą do domeny " "publicznej." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, licenciados sob CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -224,7 +206,7 @@ msgstr "" "fronteiras de países do Natural Earth, que são de domínio público." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the