mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-29 04:28:20 -06:00
Integration: M3: All tools expands to a full, searchable view (#2460)
This commit is contained in:
@@ -550,6 +550,23 @@ pub fn group(id: &str) -> Option<&'static ToolGroup> {
|
||||
TOOL_GROUPS.iter().find(|g| g.id == id)
|
||||
}
|
||||
|
||||
/// A heading of the expanded All tools view, as Acrobat files its tools under "Create & Edit",
|
||||
/// "Forms & Signatures" and so on. Every group belongs to exactly one category.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ToolCategory {
|
||||
pub label: &'static str,
|
||||
/// [`ToolGroup::id`]s, in display order.
|
||||
pub groups: &'static [&'static str],
|
||||
}
|
||||
|
||||
pub static TOOL_CATEGORIES: &[ToolCategory] = &[
|
||||
ToolCategory { label: "Create & Edit", groups: &["create", "edit", "export", "combine", "organize", "scan", "compress"] },
|
||||
ToolCategory { label: "Review & Compare", groups: &["comment", "stamp", "compare", "measure"] },
|
||||
ToolCategory { label: "Forms & Signatures", groups: &["fill_sign", "form", "certificate"] },
|
||||
ToolCategory { label: "Protect & Standardize", groups: &["protect", "redact", "accessibility", "standards", "print_production"] },
|
||||
ToolCategory { label: "Customize & Automate", groups: &["actions", "javascript", "search_index", "ai"] },
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -564,6 +581,18 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_group_is_in_exactly_one_category() {
|
||||
let mut seen = HashSet::new();
|
||||
for c in TOOL_CATEGORIES {
|
||||
for id in c.groups {
|
||||
assert!(group(id).is_some(), "{} lists unknown group {id}", c.label);
|
||||
assert!(seen.insert(*id), "{id} is in two categories");
|
||||
}
|
||||
}
|
||||
assert_eq!(seen.len(), TOOL_GROUPS.len(), "a group is in no category");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commands_are_namespaced() {
|
||||
for g in TOOL_GROUPS {
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
//! The expanded All tools view: every tool, filed under Acrobat-style categories, with a search
|
||||
//! field, filling the workspace instead of the sidebar. The sidebar's expand button opens it;
|
||||
//! its collapse button, Esc or choosing a tool closes it again.
|
||||
|
||||
use egui::{Align2, Color32, CornerRadius, Rect, Sense, Stroke, vec2};
|
||||
use pdfcraft_engine::catalog::{self, Availability, TOOL_CATEGORIES, ToolGroup};
|
||||
|
||||
use crate::theme::{self, Tokens};
|
||||
use crate::{PdfCraftApp, icons, widgets};
|
||||
|
||||
const TILE: egui::Vec2 = vec2(148.0, 118.0);
|
||||
/// Item names a tile lists when the search found the tool by them.
|
||||
const MATCHES_SHOWN: usize = 2;
|
||||
|
||||
/// Whether `query` (already trimmed and lower-cased) is in `label` or its translation.
|
||||
/// A right-to-left language's translations are in display order (their words run the other way)
|
||||
/// while the query is in typing order, so there several words match one by one, as in the
|
||||
/// command palette.
|
||||
fn label_hit(label: &str, translated: &str, query: &str, rtl: bool) -> bool {
|
||||
let translated = translated.to_lowercase();
|
||||
label.to_lowercase().contains(query)
|
||||
|| translated.contains(query)
|
||||
|| (rtl && query.contains(char::is_whitespace) && query.split_whitespace().all(|word| translated.contains(word)))
|
||||
}
|
||||
|
||||
/// Whether `group` matches `query` (already trimmed and lower-cased), in English or the UI
|
||||
/// language. `None` is no match; otherwise the items that matched, empty when the tool's own
|
||||
/// name did. An entry with a menu (Watermark: Add, Update, Remove) also matches by its commands'
|
||||
/// names, so "remove watermark" finds it.
|
||||
fn matches(query: &str, group: &ToolGroup) -> Option<Vec<&'static str>> {
|
||||
let rtl = crate::i18n::current().rtl();
|
||||
let hit = |label: &str| label_hit(label, tl!(label), query, rtl);
|
||||
if query.is_empty() || hit(group.label) {
|
||||
return Some(Vec::new());
|
||||
}
|
||||
let action_hit = |id: &&str| {
|
||||
pdfcraft_engine::commands::command(id).is_some_and(|spec| label_hit(spec.label, &crate::i18n::menu_label(spec.id, spec.label), query, rtl))
|
||||
};
|
||||
let items: Vec<_> =
|
||||
group.sections.iter().flat_map(|s| s.items.iter()).filter(|i| hit(i.label) || i.actions.iter().any(action_hit)).map(|i| i.label).collect();
|
||||
(!items.is_empty()).then_some(items)
|
||||
}
|
||||
|
||||
/// Draw the view over the workspace: the title with Collapse, the search field, and each
|
||||
/// category's tiles that match the search. Choosing a tile opens that tool's panel.
|
||||
pub fn show(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
// A dialog, prompt or the command palette over the view owns Escape.
|
||||
if !app.modal_open() && !app.palette_open && ui.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
app.all_tools_full = false;
|
||||
return;
|
||||
}
|
||||
let mut chosen = None;
|
||||
egui::Frame::NONE.fill(t.panel).show(ui, |ui| {
|
||||
ui.set_min_size(ui.available_size());
|
||||
egui::Frame::NONE.inner_margin(egui::Margin { left: 36, right: 28, top: 20, bottom: 12 }).show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(tl!("All tools")).font(theme::semibold(22.0)).color(t.text));
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if icons::button(ui, "minimize-2", 28.0, false, tl!("Collapse all tools")).clicked() {
|
||||
app.all_tools_full = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
let field = ui.add(
|
||||
widgets::line(&mut app.all_tools_query).hint_text(tl!("Search tools and commands…")).desired_width(ui.available_width().min(520.0)),
|
||||
);
|
||||
// Typing goes straight to the search, as in Acrobat's tools view.
|
||||
if !ui.memory(|m| m.focused().is_some()) {
|
||||
field.request_focus();
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
let query = app.all_tools_query.trim().to_lowercase();
|
||||
egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {
|
||||
egui::Frame::NONE.inner_margin(egui::Margin { left: 36, right: 28, top: 8, bottom: 28 }).show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
let mut any = false;
|
||||
for category in TOOL_CATEGORIES {
|
||||
let found: Vec<_> =
|
||||
category.groups.iter().filter_map(|id| catalog::group(id)).filter_map(|g| matches(&query, g).map(|m| (g, m))).collect();
|
||||
if found.is_empty() {
|
||||
continue;
|
||||
}
|
||||
any = true;
|
||||
ui.add_space(12.0);
|
||||
ui.label(egui::RichText::new(tl!(category.label)).font(theme::semibold(16.0)).color(t.text));
|
||||
ui.add_space(10.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(14.0, 14.0);
|
||||
for (g, items) in found {
|
||||
if tile(ui, &t, g, &items).clicked() {
|
||||
chosen = Some(g.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
if !any {
|
||||
ui.add_space(24.0);
|
||||
ui.label(egui::RichText::new(tl!("No tools match your search.")).color(t.text_muted));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
if let Some(id) = chosen {
|
||||
app.left_open = true;
|
||||
app.select_tool(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// One tool's tile: its icon and name, then the `items` the search found it by or, when there are
|
||||
/// none, its availability (a planned tool is greyed out with its milestone). A name or note cut
|
||||
/// short shows in full on hover.
|
||||
fn tile(ui: &mut egui::Ui, t: &Tokens, g: &ToolGroup, items: &[&'static str]) -> egui::Response {
|
||||
let (rect, resp) = ui.allocate_exact_size(TILE, Sense::click());
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, tl!(g.label)));
|
||||
let press = theme::Press::track(ui, &resp);
|
||||
crate::home::card_lift(ui, t, rect, press);
|
||||
ui.painter().rect(rect, CornerRadius::same(10), press.fill(t.card, t.hover, t.pressed), Stroke::new(1.0, t.divider), egui::StrokeKind::Inside);
|
||||
let body = rect.translate(press.offset());
|
||||
let ready = g.availability == Availability::Ready;
|
||||
let hue = Color32::from_rgb(g.hue[0], g.hue[1], g.hue[2]);
|
||||
icons::paint(ui, Rect::from_center_size(body.center_top() + vec2(0.0, 34.0), vec2(32.0, 32.0)), g.icon, 30.0, hue);
|
||||
let mut job =
|
||||
egui::text::LayoutJob::simple(tl!(g.label).to_owned(), theme::medium(13.0), if ready { t.text } else { t.text_muted }, body.width() - 16.0);
|
||||
job.halign = egui::Align::Center;
|
||||
job.wrap.max_rows = 2;
|
||||
let title = ui.fonts_mut(|f| f.layout_job(job));
|
||||
let mut elided = title.elided;
|
||||
let title_h = title.size().y;
|
||||
ui.painter().galley(body.center_top() + vec2(0.0, 60.0), title, t.text);
|
||||
// What found the tool, or else whether it works today.
|
||||
let note = if items.is_empty() {
|
||||
match g.availability {
|
||||
Availability::Ready => String::new(),
|
||||
Availability::Planned(m) => format!("{} · {m}", tl!("Planned")),
|
||||
Availability::Provider => g.badge.map(|b| tl!(b).to_owned()).unwrap_or_default(),
|
||||
}
|
||||
} else {
|
||||
items.iter().take(MATCHES_SHOWN).map(|l| tl!(l)).collect::<Vec<_>>().join(" · ")
|
||||
};
|
||||
if !note.is_empty() {
|
||||
let color = if items.is_empty() { t.text_faint } else { t.accent_text };
|
||||
let mut job = egui::text::LayoutJob::simple_singleline(note.clone(), theme::regular(11.0), color);
|
||||
job.halign = egui::Align::Center;
|
||||
job.wrap = egui::text::TextWrapping { max_width: body.width() - 16.0, max_rows: 1, break_anywhere: true, ..Default::default() };
|
||||
let galley = ui.fonts_mut(|f| f.layout_job(job));
|
||||
elided |= galley.elided;
|
||||
ui.painter().galley(body.center_top() + vec2(0.0, 64.0 + title_h), galley, color);
|
||||
}
|
||||
if let Some(b) = g.badge.filter(|_| g.availability == Availability::Ready) {
|
||||
let font = theme::semibold(9.5);
|
||||
let w = widgets::text_width(ui, tl!(b), &font);
|
||||
let r = Rect::from_min_size(body.right_top() + vec2(-w - 18.0, 8.0), vec2(w + 10.0, 16.0));
|
||||
ui.painter().rect_filled(r, CornerRadius::same(4), t.badge_new);
|
||||
ui.painter().text(r.center(), Align2::CENTER_CENTER, tl!(b), font, Color32::WHITE);
|
||||
}
|
||||
let resp = if elided { resp.on_hover_text(format!("{}\n{note}", tl!(g.label))) } else { resp };
|
||||
theme::hand(resp)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn group(id: &str) -> &'static ToolGroup {
|
||||
catalog::group(id).expect("catalogue group")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_matches_tool_names_and_their_items() {
|
||||
assert_eq!(matches("", group("organize")), Some(Vec::new()), "an empty query shows everything");
|
||||
assert_eq!(matches("organize", group("organize")), Some(Vec::new()), "the tool's own name");
|
||||
let rotate = matches("rotate", group("organize")).expect("Organize pages lists Rotate");
|
||||
assert!(!rotate.is_empty() && rotate.iter().all(|l| l.to_lowercase().contains("rotate")), "{rotate:?}");
|
||||
assert_eq!(matches("no such tool anywhere", group("organize")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_finds_an_entry_by_its_menus_commands() {
|
||||
let found = matches("remove watermark", group("edit")).expect("Watermark's menu has Remove watermark");
|
||||
assert!(found.contains(&"Watermark"), "{found:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_to_left_labels_match_a_query_typed_in_logical_order() {
|
||||
let shown = crate::bidi::display_rtl("تنظيم الصفحات");
|
||||
assert_ne!(shown, "تنظيم الصفحات", "the catalogue reverses the words for display");
|
||||
assert!(label_hit("Organize pages", &shown, "تنظيم الصفحات", true));
|
||||
assert!(label_hit("Organize pages", &shown, "الصفحات", true), "one word");
|
||||
assert!(!label_hit("Organize pages", &shown, "تنظيم الملفات", true), "every word must be there");
|
||||
assert!(!label_hit("Organize pages", &shown, "تنظيم الصفحات", false), "left-to-right labels stay exact");
|
||||
}
|
||||
}
|
||||
@@ -494,7 +494,7 @@ fn explained(issues: &[CalculationIssue]) -> String {
|
||||
|
||||
/// The Calculation order page; Back returns to Prepare a form.
|
||||
pub(crate) fn panel(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
let (back, close) = crate::panels::panel_header(ui, t, "Calculation order", Some(tl!("Back to Prepare a form")));
|
||||
let (back, close) = crate::panels::panel_header(ui, t, "Calculation order", Some(("chevron-left", tl!("Back to Prepare a form"))));
|
||||
if back {
|
||||
app.left = LeftPanel::Tool("form");
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{PdfCraftApp, icons, panels::human_size, widgets};
|
||||
const RECOMMENDED: [&str; 5] = ["organize", "comment", "form", "edit", "protect"];
|
||||
|
||||
/// A soft shadow that rises on hover and settles when the card is pressed.
|
||||
fn card_lift(ui: &egui::Ui, t: &Tokens, rect: Rect, press: theme::Press) {
|
||||
pub(crate) fn card_lift(ui: &egui::Ui, t: &Tokens, rect: Rect, press: theme::Press) {
|
||||
let lift = press.hover * (1.0 - press.down);
|
||||
if lift > 0.04 {
|
||||
let shadow = egui::Shadow { offset: [0, 3], blur: (14.0 * lift) as u8, spread: 0, color: t.page_shadow.gamma_multiply(0.85 * lift) };
|
||||
|
||||
@@ -2766,6 +2766,14 @@ certificate Version الإصدار
|
||||
Make properties default جعل الخصائص افتراضية
|
||||
Pages selected الصفحات المحددة
|
||||
Page properties خصائص الصفحة
|
||||
Create & Edit الإنشاء والتحرير
|
||||
Review & Compare المراجعة والمقارنة
|
||||
Forms & Signatures النماذج والتوقيعات
|
||||
Protect & Standardize الحماية والتوحيد القياسي
|
||||
Customize & Automate التخصيص والأتمتة
|
||||
No tools match your search. لا توجد أدوات تطابق بحثك.
|
||||
Expand all tools توسيع كل الأدوات
|
||||
Collapse all tools طي كل الأدوات
|
||||
Attach a file… إرفاق ملف…
|
||||
Delete attachment حذف المرفق
|
||||
Attaching files is not available on the web yet إرفاق الملفات غير متاح على الويب بعد
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2452,6 +2452,14 @@ animation effects Off Изключени
|
||||
Make properties default Задаване като свойства по подразбиране
|
||||
Pages selected Избрани страници
|
||||
Page properties Свойства на страницата
|
||||
Create & Edit Създаване и редактиране
|
||||
Review & Compare Преглед и сравнение
|
||||
Forms & Signatures Формуляри и подписи
|
||||
Protect & Standardize Защита и стандартизация
|
||||
Customize & Automate Персонализиране и автоматизация
|
||||
No tools match your search. Няма инструменти, отговарящи на търсенето.
|
||||
Expand all tools Разгъване на всички инструменти
|
||||
Collapse all tools Свиване на всички инструменти
|
||||
Attach a file… Прикачване на файл…
|
||||
Delete attachment Изтриване на прикачения файл
|
||||
Attaching files is not available on the web yet Прикачването на файлове още не е налично в уеб версията
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
@@ -2654,6 +2654,14 @@ certificate SHA-256 digest Resum SHA-256
|
||||
Optimizing… merging image fragments S'està optimitzant… fusionant fragments d'imatge
|
||||
Outline colour: Color del contorn:
|
||||
Page properties Propietats de la pàgina
|
||||
Create & Edit Crea i edita
|
||||
Review & Compare Revisa i compara
|
||||
Forms & Signatures Formularis i signatures
|
||||
Protect & Standardize Protegeix i estandarditza
|
||||
Customize & Automate Personalitza i automatitza
|
||||
No tools match your search. Cap eina coincideix amb la cerca.
|
||||
Expand all tools Desplega totes les eines
|
||||
Collapse all tools Replega totes les eines
|
||||
Pages counted Pàgines comptades
|
||||
Pages selected Pàgines seleccionades
|
||||
Print as image Imprimeix com a imatge
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 3.
|
@@ -2720,6 +2720,14 @@ certificate Version وەشان
|
||||
Make properties default کردنی تایبەتمەندییەکان بە بنەڕەتی
|
||||
Pages selected پەڕە هەڵبژێردراوەکان
|
||||
Page properties تایبەتمەندییەکانی پەڕە
|
||||
Create & Edit دروستکردن و دەستکاریکردن
|
||||
Review & Compare پێداچوونەوە و بەراوردکردن
|
||||
Forms & Signatures فۆڕمەکان و واژووەکان
|
||||
Protect & Standardize پاراستن و ستانداردکردن
|
||||
Customize & Automate تایبەتکردن و خۆکارکردن
|
||||
No tools match your search. هیچ ئامرازێک لەگەڵ گەڕانەکەت ناگونجێت.
|
||||
Expand all tools فراوانکردنی هەموو ئامرازەکان
|
||||
Collapse all tools کۆکردنەوەی هەموو ئامرازەکان
|
||||
|
||||
# Date picker: Today button (#4274) (AI-assisted, not separately native-reviewed)
|
||||
Today ئەمڕۆ
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2563,6 +2563,14 @@ animation effects Off Vypnuto
|
||||
Make properties default Nastavit vlastnosti jako výchozí
|
||||
Pages selected Vybrané stránky
|
||||
Page properties Vlastnosti stránky
|
||||
Create & Edit Vytváření a úpravy
|
||||
Review & Compare Revize a porovnání
|
||||
Forms & Signatures Formuláře a podpisy
|
||||
Protect & Standardize Ochrana a standardizace
|
||||
Customize & Automate Přizpůsobení a automatizace
|
||||
No tools match your search. Hledání neodpovídá žádný nástroj.
|
||||
Expand all tools Rozbalit všechny nástroje
|
||||
Collapse all tools Sbalit všechny nástroje
|
||||
Attach a file… Připojit soubor…
|
||||
Delete attachment Odstranit přílohu
|
||||
Attaching files is not available on the web yet Připojování souborů zatím na webu není k dispozici
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2490,6 +2490,14 @@ animation effects Off Aus
|
||||
Make properties default Eigenschaften als Standard festlegen
|
||||
Pages selected Ausgewählte Seiten
|
||||
Page properties Seiteneigenschaften
|
||||
Create & Edit Erstellen & Bearbeiten
|
||||
Review & Compare Prüfen & Vergleichen
|
||||
Forms & Signatures Formulare & Signaturen
|
||||
Protect & Standardize Schützen & Standardisieren
|
||||
Customize & Automate Anpassen & Automatisieren
|
||||
No tools match your search. Keine Werkzeuge entsprechen Ihrer Suche.
|
||||
Expand all tools Alle Werkzeuge ausklappen
|
||||
Collapse all tools Alle Werkzeuge einklappen
|
||||
Attach a file… Datei anhängen…
|
||||
Delete attachment Anhang löschen
|
||||
Attaching files is not available on the web yet Dateien anhängen ist im Web noch nicht verfügbar
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
@@ -2495,6 +2495,14 @@ animation effects Off Ανενεργά
|
||||
Make properties default Ορισμός ιδιοτήτων ως προεπιλογή
|
||||
Pages selected Επιλεγμένες σελίδες
|
||||
Page properties Ιδιότητες σελίδας
|
||||
Create & Edit Δημιουργία και επεξεργασία
|
||||
Review & Compare Αναθεώρηση και σύγκριση
|
||||
Forms & Signatures Φόρμες και υπογραφές
|
||||
Protect & Standardize Προστασία και τυποποίηση
|
||||
Customize & Automate Προσαρμογή και αυτοματοποίηση
|
||||
No tools match your search. Κανένα εργαλείο δεν ταιριάζει με την αναζήτησή σας.
|
||||
Expand all tools Ανάπτυξη όλων των εργαλείων
|
||||
Collapse all tools Σύμπτυξη όλων των εργαλείων
|
||||
Attach a file… Επισύναψη αρχείου…
|
||||
Delete attachment Διαγραφή συνημμένου
|
||||
Attaching files is not available on the web yet Η επισύναψη αρχείων δεν είναι ακόμη διαθέσιμη στον ιστό
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 482 and column 9.
|
@@ -2632,6 +2632,14 @@ animation effects Off Desactivados
|
||||
Make properties default Usar como propiedades predeterminadas
|
||||
Pages selected Páginas seleccionadas
|
||||
Page properties Propiedades de página
|
||||
Create & Edit Crear y editar
|
||||
Review & Compare Revisar y comparar
|
||||
Forms & Signatures Formularios y firmas
|
||||
Protect & Standardize Proteger y estandarizar
|
||||
Customize & Automate Personalizar y automatizar
|
||||
No tools match your search. Ninguna herramienta coincide con la búsqueda.
|
||||
Expand all tools Expandir todas las herramientas
|
||||
Collapse all tools Contraer todas las herramientas
|
||||
Font Fuente
|
||||
Bulleted list Lista con viñetas
|
||||
Numbered list Lista numerada
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 60.
|
@@ -2698,6 +2698,14 @@ certificate SHA-256 digest SHA-256-tiiviste
|
||||
Make properties default Aseta ominaisuudet oletuksiksi
|
||||
Pages selected Valitut sivut
|
||||
Page properties Sivun ominaisuudet
|
||||
Create & Edit Luo ja muokkaa
|
||||
Review & Compare Tarkasta ja vertaa
|
||||
Forms & Signatures Lomakkeet ja allekirjoitukset
|
||||
Protect & Standardize Suojaa ja standardoi
|
||||
Customize & Automate Mukauta ja automatisoi
|
||||
No tools match your search. Hakuasi vastaavia työkaluja ei löytynyt.
|
||||
Expand all tools Laajenna kaikki työkalut
|
||||
Collapse all tools Kutista kaikki työkalut
|
||||
The password must have at least 8 characters. Salasanassa on oltava vähintään 8 merkkiä.
|
||||
theme Appearance Teema
|
||||
# Labels added upstream 2026-10-16…18.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 47.
|
@@ -2489,6 +2489,14 @@ animation effects Off Désactivés
|
||||
Make properties default Définir comme propriétés par défaut
|
||||
Pages selected Pages sélectionnées
|
||||
Page properties Propriétés de la page
|
||||
Create & Edit Créer et modifier
|
||||
Review & Compare Réviser et comparer
|
||||
Forms & Signatures Formulaires et signatures
|
||||
Protect & Standardize Protéger et normaliser
|
||||
Customize & Automate Personnaliser et automatiser
|
||||
No tools match your search. Aucun outil ne correspond à votre recherche.
|
||||
Expand all tools Développer tous les outils
|
||||
Collapse all tools Réduire tous les outils
|
||||
Attach a file… Joindre un fichier…
|
||||
Delete attachment Supprimer la pièce jointe
|
||||
Attaching files is not available on the web yet Joindre des fichiers n'est pas encore disponible sur le web
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
@@ -2682,6 +2682,14 @@ certificate SHA-256 digest SHA-256 डाइजेस्ट
|
||||
Make properties default गुण डिफ़ॉल्ट बनाएँ
|
||||
Pages selected चयनित पृष्ठ
|
||||
Page properties पृष्ठ गुण
|
||||
Create & Edit बनाएँ और संपादित करें
|
||||
Review & Compare समीक्षा और तुलना करें
|
||||
Forms & Signatures फ़ॉर्म और हस्ताक्षर
|
||||
Protect & Standardize सुरक्षित और मानकीकृत करें
|
||||
Customize & Automate अनुकूलित और स्वचालित करें
|
||||
No tools match your search. आपकी खोज से कोई टूल मेल नहीं खाता।
|
||||
Expand all tools सभी टूल विस्तृत करें
|
||||
Collapse all tools सभी टूल समेटें
|
||||
The password must have at least 8 characters. पासवर्ड में कम से कम 8 वर्ण होने चाहिए।
|
||||
theme Appearance स्वरूप
|
||||
Zoom — type a percentage and press Enter ज़ूम — प्रतिशत टाइप करें और Enter दबाएँ
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 855 and column 12.
|
@@ -2488,6 +2488,14 @@ animation effects Off Ki
|
||||
Make properties default Tulajdonságok beállítása alapértelmezettként
|
||||
Pages selected Kijelölt oldalak
|
||||
Page properties Oldaltulajdonságok
|
||||
Create & Edit Létrehozás és szerkesztés
|
||||
Review & Compare Áttekintés és összehasonlítás
|
||||
Forms & Signatures Űrlapok és aláírások
|
||||
Protect & Standardize Védelem és szabványosítás
|
||||
Customize & Automate Testreszabás és automatizálás
|
||||
No tools match your search. Egy eszköz sem felel meg a keresésnek.
|
||||
Expand all tools Az összes eszköz kibontása
|
||||
Collapse all tools Az összes eszköz összecsukása
|
||||
Attach a file… Fájl csatolása…
|
||||
Delete attachment Melléklet törlése
|
||||
Attaching files is not available on the web yet Fájlok csatolása a weben még nem érhető el
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
@@ -2683,6 +2683,14 @@ key Space Space
|
||||
Make properties default Jadikan properti default
|
||||
Pages selected Halaman yang dipilih
|
||||
Page properties Properti halaman
|
||||
Create & Edit Buat & Edit
|
||||
Review & Compare Tinjau & Bandingkan
|
||||
Forms & Signatures Formulir & Tanda Tangan
|
||||
Protect & Standardize Lindungi & Standarkan
|
||||
Customize & Automate Sesuaikan & Otomatiskan
|
||||
No tools match your search. Tidak ada alat yang cocok dengan pencarian Anda.
|
||||
Expand all tools Perluas semua alat
|
||||
Collapse all tools Ciutkan semua alat
|
||||
The password must have at least 8 characters. Kata sandi harus memiliki minimal 8 karakter.
|
||||
theme Appearance Tampilan
|
||||
Zoom — type a percentage and press Enter Zoom — ketik persentase dan tekan Enter
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 483 and column 12.
|
@@ -2701,6 +2701,14 @@ certificate Key usage Utilizzo chiave
|
||||
certificate Self-signed Autofirmato
|
||||
certificate SHA-1 digest Impronta SHA-1
|
||||
certificate SHA-256 digest Impronta SHA-256
|
||||
Create & Edit Crea e modifica
|
||||
Review & Compare Revisione e confronto
|
||||
Forms & Signatures Moduli e firme
|
||||
Protect & Standardize Proteggi e standardizza
|
||||
Customize & Automate Personalizza e automatizza
|
||||
No tools match your search. Nessuno strumento corrisponde alla ricerca.
|
||||
Expand all tools Espandi tutti gli strumenti
|
||||
Collapse all tools Comprimi tutti gli strumenti
|
||||
Attach a file… Allega un file…
|
||||
Delete attachment Elimina allegato
|
||||
Attaching files is not available on the web yet Allegare file non è ancora disponibile sul web
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2489,6 +2489,14 @@ animation effects Off オフ
|
||||
Make properties default プロパティをデフォルトに設定
|
||||
Pages selected 選択したページ
|
||||
Page properties ページのプロパティ
|
||||
Create & Edit 作成と編集
|
||||
Review & Compare レビューと比較
|
||||
Forms & Signatures フォームと署名
|
||||
Protect & Standardize 保護と標準化
|
||||
Customize & Automate カスタマイズと自動化
|
||||
No tools match your search. 検索に一致するツールはありません。
|
||||
Expand all tools すべてのツールを展開
|
||||
Collapse all tools すべてのツールを折りたたむ
|
||||
Attach a file… ファイルを添付…
|
||||
Delete attachment 添付ファイルを削除
|
||||
Attaching files is not available on the web yet Web 版ではまだファイルを添付できません
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2487,6 +2487,14 @@ animation effects Off Өшірулі
|
||||
Make properties default Қасиеттерді әдепкі ету
|
||||
Pages selected Таңдалған беттер
|
||||
Page properties Бет қасиеттері
|
||||
Create & Edit Жасау және өңдеу
|
||||
Review & Compare Шолу және салыстыру
|
||||
Forms & Signatures Пішіндер және қолтаңбалар
|
||||
Protect & Standardize Қорғау және стандарттау
|
||||
Customize & Automate Баптау және автоматтандыру
|
||||
No tools match your search. Іздеуге сәйкес құралдар жоқ.
|
||||
Expand all tools Барлық құралдарды жаю
|
||||
Collapse all tools Барлық құралдарды жию
|
||||
Attach a file… Файл тіркеу…
|
||||
Delete attachment Тіркемені жою
|
||||
Attaching files is not available on the web yet Вебте файл тіркеу әзірге қолжетімсіз
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 860 and column 9.
|
@@ -2682,6 +2682,14 @@ certificate SHA-256 digest SHA-256 다이제스트
|
||||
Make properties default 속성을 기본값으로 설정
|
||||
Pages selected 선택한 페이지
|
||||
Page properties 페이지 속성
|
||||
Create & Edit 만들기 및 편집
|
||||
Review & Compare 검토 및 비교
|
||||
Forms & Signatures 양식 및 서명
|
||||
Protect & Standardize 보호 및 표준화
|
||||
Customize & Automate 사용자 지정 및 자동화
|
||||
No tools match your search. 검색과 일치하는 도구가 없습니다.
|
||||
Expand all tools 모든 도구 펼치기
|
||||
Collapse all tools 모든 도구 접기
|
||||
The password must have at least 8 characters. 암호는 8자 이상이어야 합니다.
|
||||
theme Appearance 모양
|
||||
Zoom — type a percentage and press Enter 줌 — 백분율을 입력하고 Enter를 누르세요
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 855 and column 12.
|
@@ -2436,6 +2436,14 @@ animation effects Off ປິດ
|
||||
Make properties default ກຳນົດຄຸນສົມບັດເປັນຄ່າເລີ່ມຕົ້ນ
|
||||
Pages selected ໜ້າທີ່ເລືອກ
|
||||
Page properties ຄຸນສົມບັດໜ້າ
|
||||
Create & Edit ສ້າງ & ແກ້ໄຂ
|
||||
Review & Compare ກວດກາຄືນ & ປຽບທຽບ
|
||||
Forms & Signatures ຟອມ & ລາຍເຊັນ
|
||||
Protect & Standardize ປ້ອງກັນ & ມາດຕະຖານ
|
||||
Customize & Automate ປັບແຕ່ງ & ອັດຕະໂນມັດ
|
||||
No tools match your search. ບໍ່ມີເຄື່ອງມືທີ່ກົງກັບການຄົ້ນຫາຂອງທ່ານ.
|
||||
Expand all tools ຂະຫຍາຍເຄື່ອງມືທັງໝົດ
|
||||
Collapse all tools ຫຍໍ້ເຄື່ອງມືທັງໝົດເຂົ້າ
|
||||
|
||||
# Date picker: Today button (#4274) (AI-assisted, not separately native-reviewed)
|
||||
Today ມື້ນີ້
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 32.
|
@@ -2739,6 +2739,14 @@ key Space Tarpas
|
||||
Make properties default Nustatyti ypatybes kaip numatytąsias
|
||||
Pages selected Pasirinkti puslapiai
|
||||
Page properties Puslapio ypatybės
|
||||
Create & Edit Kūrimas ir redagavimas
|
||||
Review & Compare Peržiūra ir palyginimas
|
||||
Forms & Signatures Formos ir parašai
|
||||
Protect & Standardize Apsauga ir standartizavimas
|
||||
Customize & Automate Pritaikymas ir automatizavimas
|
||||
No tools match your search. Paieškos neatitinka joks įrankis.
|
||||
Expand all tools Išskleisti visus įrankius
|
||||
Collapse all tools Suskleisti visus įrankius
|
||||
|
||||
# AI-assisted additions for concurrently merged UI features (2026-10-15).
|
||||
# These additions have not had separate native-speaker review.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 406 and column 9.
|
@@ -1794,6 +1794,7 @@ mod tests {
|
||||
section.items.iter().for_each(|item| add(item.label));
|
||||
}
|
||||
}
|
||||
pdfcraft_engine::catalog::TOOL_CATEGORIES.iter().for_each(|category| add(category.label));
|
||||
let engine: [&[&str]; 9] = [
|
||||
pdfcraft_engine::a11y::WORDINGS,
|
||||
pdfcraft_engine::a11y::REPORT_WORDS,
|
||||
|
||||
@@ -2614,6 +2614,14 @@
|
||||
Make properties default Bruk egenskapene som standard
|
||||
Pages selected Valgte sider
|
||||
Page properties Sideegenskaper
|
||||
Create & Edit Opprett og rediger
|
||||
Review & Compare Gjennomgå og sammenlign
|
||||
Forms & Signatures Skjemaer og signaturer
|
||||
Protect & Standardize Beskytt og standardiser
|
||||
Customize & Automate Tilpass og automatiser
|
||||
No tools match your search. Ingen verktøy samsvarer med søket.
|
||||
Expand all tools Utvid alle verktøy
|
||||
Collapse all tools Fold sammen alle verktøy
|
||||
Cut Image Klipp ut bilde
|
||||
Redact Image Sladd bilde
|
||||
Create Link… Opprett lenke…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
@@ -2465,6 +2465,14 @@ animation effects Off Wyłączone
|
||||
Make properties default Ustaw właściwości jako domyślne
|
||||
Pages selected Zaznaczone strony
|
||||
Page properties Właściwości strony
|
||||
Create & Edit Tworzenie i edycja
|
||||
Review & Compare Recenzja i porównanie
|
||||
Forms & Signatures Formularze i podpisy
|
||||
Protect & Standardize Ochrona i standaryzacja
|
||||
Customize & Automate Dostosowanie i automatyzacja
|
||||
No tools match your search. Żadne narzędzie nie pasuje do wyszukiwania.
|
||||
Expand all tools Rozwiń wszystkie narzędzia
|
||||
Collapse all tools Zwiń wszystkie narzędzia
|
||||
Attach a file… Dołącz plik…
|
||||
Delete attachment Usuń załącznik
|
||||
Attaching files is not available on the web yet Dołączanie plików nie jest jeszcze dostępne w wersji webowej
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2503,6 +2503,14 @@ animation effects Off Desativados
|
||||
Make properties default Tornar propriedades padrão
|
||||
Pages selected Páginas selecionadas
|
||||
Page properties Propriedades da página
|
||||
Create & Edit Criar e editar
|
||||
Review & Compare Revisar e comparar
|
||||
Forms & Signatures Formulários e assinaturas
|
||||
Protect & Standardize Proteger e padronizar
|
||||
Customize & Automate Personalizar e automatizar
|
||||
No tools match your search. Nenhuma ferramenta corresponde à pesquisa.
|
||||
Expand all tools Expandir todas as ferramentas
|
||||
Collapse all tools Recolher todas as ferramentas
|
||||
Attach a file… Anexar um arquivo…
|
||||
Delete attachment Excluir anexo
|
||||
Attaching files is not available on the web yet Anexar arquivos ainda não está disponível na web
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2654,6 +2654,14 @@ key Space Spațiu
|
||||
Optimizing… merging image fragments Se optimizează… unificare fragmente de imagine
|
||||
Outline colour: Culoare contur:
|
||||
Page properties Proprietăți pagină
|
||||
Create & Edit Creare și editare
|
||||
Review & Compare Revizuire și comparare
|
||||
Forms & Signatures Formulare și semnături
|
||||
Protect & Standardize Protejare și standardizare
|
||||
Customize & Automate Personalizare și automatizare
|
||||
No tools match your search. Niciun instrument nu corespunde căutării.
|
||||
Expand all tools Extinderea tuturor instrumentelor
|
||||
Collapse all tools Restrângerea tuturor instrumentelor
|
||||
Pages counted Pagini numărate
|
||||
Pages selected Pagini selectate
|
||||
Print as image Imprimare ca imagine
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3 and column 110.
|
@@ -2402,6 +2402,14 @@ animation effects Off Выкл
|
||||
Make properties default Сделать свойства стандартными
|
||||
Pages selected Выделенные страницы
|
||||
Page properties Свойства страницы
|
||||
Create & Edit Создание и редактирование
|
||||
Review & Compare Рецензирование и сравнение
|
||||
Forms & Signatures Формы и подписи
|
||||
Protect & Standardize Защита и стандартизация
|
||||
Customize & Automate Настройка и автоматизация
|
||||
No tools match your search. Нет инструментов, соответствующих запросу.
|
||||
Expand all tools Развернуть все инструменты
|
||||
Collapse all tools Свернуть все инструменты
|
||||
Attach a file… Прикрепить файл…
|
||||
Delete attachment Удалить вложение
|
||||
Attaching files is not available on the web yet Прикрепление файлов в веб-версии пока недоступно
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2494,6 +2494,14 @@ animation effects Off Izklopljeni
|
||||
Make properties default Nastavi lastnosti kot privzete
|
||||
Pages selected Izbrane strani
|
||||
Page properties Lastnosti strani
|
||||
Create & Edit Ustvarjanje in urejanje
|
||||
Review & Compare Pregled in primerjava
|
||||
Forms & Signatures Obrazci in podpisi
|
||||
Protect & Standardize Zaščita in standardizacija
|
||||
Customize & Automate Prilagajanje in avtomatizacija
|
||||
No tools match your search. Iskanju ne ustreza nobeno orodje.
|
||||
Expand all tools Razširi vsa orodja
|
||||
Collapse all tools Strni vsa orodja
|
||||
Attach a file… Priloži datoteko…
|
||||
Delete attachment Izbriši prilogo
|
||||
Attaching files is not available on the web yet Prilaganje datotek na spletu še ni na voljo
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2420,6 +2420,14 @@ animation effects Off Joaktiv
|
||||
Other options Other options
|
||||
Outline colour: Outline colour:
|
||||
Page properties Page properties
|
||||
Create & Edit Krijo dhe redakto
|
||||
Review & Compare Rishiko dhe krahaso
|
||||
Forms & Signatures Formularë dhe nënshkrime
|
||||
Protect & Standardize Mbro dhe standardizo
|
||||
Customize & Automate Personalizo dhe automatizo
|
||||
No tools match your search. Asnjë mjet nuk përputhet me kërkimin tuaj.
|
||||
Expand all tools Zgjero të gjitha mjetet
|
||||
Collapse all tools Palos të gjitha mjetet
|
||||
Pages counted Pages counted
|
||||
Pages selected Pages selected
|
||||
Paragraph spacing Paragraph spacing
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2398,6 +2398,14 @@ animation effects Off ఆఫ్
|
||||
Make properties default లక్షణాలను డిఫాల్ట్గా చేయి
|
||||
Pages selected ఎంచుకున్న పేజీలు
|
||||
Page properties పేజీ లక్షణాలు
|
||||
Create & Edit సృష్టించు & సవరించు
|
||||
Review & Compare సమీక్షించు & పోల్చు
|
||||
Forms & Signatures ఫారమ్లు & సంతకాలు
|
||||
Protect & Standardize రక్షించు & ప్రామాణీకరించు
|
||||
Customize & Automate అనుకూలీకరించు & ఆటోమేట్ చేయి
|
||||
No tools match your search. మీ శోధనకు సరిపోలే సాధనాలు లేవు.
|
||||
Expand all tools అన్ని సాధనాలను విస్తరించు
|
||||
Collapse all tools అన్ని సాధనాలను కుదించు
|
||||
Attach a file… ఫైల్ను జోడించు…
|
||||
Delete attachment జోడింపును తొలగించు
|
||||
Attaching files is not available on the web yet వెబ్లో ఫైల్లను జోడించడం ఇంకా అందుబాటులో లేదు
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 13 and column 16.
|
@@ -2686,6 +2686,14 @@ key Space Space
|
||||
Make properties default ตั้งคุณสมบัติเป็นค่าเริ่มต้น
|
||||
Pages selected หน้าที่เลือก
|
||||
Page properties คุณสมบัติหน้า
|
||||
Create & Edit สร้างและแก้ไข
|
||||
Review & Compare ตรวจทานและเปรียบเทียบ
|
||||
Forms & Signatures แบบฟอร์มและลายเซ็น
|
||||
Protect & Standardize ป้องกันและกำหนดมาตรฐาน
|
||||
Customize & Automate ปรับแต่งและทำงานอัตโนมัติ
|
||||
No tools match your search. ไม่มีเครื่องมือที่ตรงกับการค้นหาของคุณ
|
||||
Expand all tools ขยายเครื่องมือทั้งหมด
|
||||
Collapse all tools ยุบเครื่องมือทั้งหมด
|
||||
The password must have at least 8 characters. รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร
|
||||
theme Appearance ลักษณะที่ปรากฏ
|
||||
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 866 and column 12.
|
@@ -2485,6 +2485,14 @@ animation effects Off Kapalı
|
||||
Make properties default Özellikleri varsayılan yap
|
||||
Pages selected Seçilen sayfalar
|
||||
Page properties Sayfa özellikleri
|
||||
Create & Edit Oluştur ve Düzenle
|
||||
Review & Compare İncele ve Karşılaştır
|
||||
Forms & Signatures Formlar ve İmzalar
|
||||
Protect & Standardize Koru ve Standartlaştır
|
||||
Customize & Automate Özelleştir ve Otomatikleştir
|
||||
No tools match your search. Aramanızla eşleşen araç yok.
|
||||
Expand all tools Tüm araçları genişlet
|
||||
Collapse all tools Tüm araçları daralt
|
||||
Attach a file… Dosya ekle…
|
||||
Delete attachment Eki sil
|
||||
Attaching files is not available on the web yet Dosya eklemek web'de henüz kullanılamıyor
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 859 and column 9.
|
@@ -2511,6 +2511,14 @@ animation effects Off Вимкнено
|
||||
Make properties default Зробити властивості типовими
|
||||
Pages selected Вибрані сторінки
|
||||
Page properties Властивості сторінки
|
||||
Create & Edit Створення й редагування
|
||||
Review & Compare Рецензування й порівняння
|
||||
Forms & Signatures Форми й підписи
|
||||
Protect & Standardize Захист і стандартизація
|
||||
Customize & Automate Налаштування й автоматизація
|
||||
No tools match your search. Немає інструментів, що відповідають пошуку.
|
||||
Expand all tools Розгорнути всі інструменти
|
||||
Collapse all tools Згорнути всі інструменти
|
||||
Attach a file… Прикріпити файл…
|
||||
Delete attachment Видалити вкладення
|
||||
Attaching files is not available on the web yet Прикріплення файлів у веб-версії поки недоступне
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2676,6 +2676,14 @@ key Space Space
|
||||
Redact Image Xóa nội dung nhạy cảm của hình ảnh
|
||||
Create Link… Tạo liên kết…
|
||||
Recognize Text… Nhận dạng văn bản…
|
||||
Create & Edit Tạo và chỉnh sửa
|
||||
Review & Compare Xem xét và so sánh
|
||||
Forms & Signatures Biểu mẫu và chữ ký
|
||||
Protect & Standardize Bảo vệ và chuẩn hóa
|
||||
Customize & Automate Tùy chỉnh và tự động hóa
|
||||
No tools match your search. Không có công cụ nào khớp với tìm kiếm.
|
||||
Expand all tools Mở rộng tất cả công cụ
|
||||
Collapse all tools Thu gọn tất cả công cụ
|
||||
|
||||
# AI-assisted additions for concurrently merged UI features (2026-10-15).
|
||||
# These additions have not had separate native-speaker review.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 493 and column 12.
|
@@ -2453,6 +2453,14 @@ animation effects Off 关
|
||||
Make properties default 将属性设为默认
|
||||
Pages selected 选定的页面
|
||||
Page properties 页面属性
|
||||
Create & Edit 创建和编辑
|
||||
Review & Compare 审阅和比较
|
||||
Forms & Signatures 表单和签名
|
||||
Protect & Standardize 保护和标准化
|
||||
Customize & Automate 自定义和自动化
|
||||
No tools match your search. 没有与搜索匹配的工具。
|
||||
Expand all tools 展开所有工具
|
||||
Collapse all tools 折叠所有工具
|
||||
Attach a file… 附加文件…
|
||||
Delete attachment 删除附件
|
||||
Attaching files is not available on the web yet 网页版尚不支持附加文件
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2439,6 +2439,14 @@ animation effects Off 關
|
||||
Make properties default 將內容設為預設
|
||||
Pages selected 選取的頁面
|
||||
Page properties 頁面屬性
|
||||
Create & Edit 建立與編輯
|
||||
Review & Compare 審閱與比較
|
||||
Forms & Signatures 表單與簽名
|
||||
Protect & Standardize 保護與標準化
|
||||
Customize & Automate 自訂與自動化
|
||||
No tools match your search. 沒有符合搜尋的工具。
|
||||
Expand all tools 展開所有工具
|
||||
Collapse all tools 摺疊所有工具
|
||||
Attach a file… 附加檔案…
|
||||
Delete attachment 刪除附件
|
||||
Attaching files is not available on the web yet 網頁版尚不支援附加檔案
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -32,6 +32,7 @@ macro_rules! tl_ctx {
|
||||
|
||||
mod a11y_ui;
|
||||
mod actions_ui;
|
||||
mod all_tools_ui;
|
||||
mod appearance;
|
||||
mod calc_order_ui;
|
||||
pub mod canvas;
|
||||
@@ -708,6 +709,12 @@ pub struct PdfCraftApp {
|
||||
pub palette_open: bool,
|
||||
pub palette_query: String,
|
||||
pub all_tools_expanded: bool,
|
||||
/// The All tools view fills the workspace (its sidebar's expand button).
|
||||
pub all_tools_full: bool,
|
||||
pub all_tools_query: String,
|
||||
/// The tab that showed when the expanded All tools view opened ([`Self::shown_tab`]). Another
|
||||
/// tab coming to the front (Home, a document, Combine files) puts the view away.
|
||||
all_tools_tab: (Option<DocId>, bool),
|
||||
pub recent: Vec<RecentFile>,
|
||||
/// Preferences: reopen the files that were open when PdfCraft last closed (#442).
|
||||
pub reopen_last_session: bool,
|
||||
@@ -1133,6 +1140,9 @@ impl PdfCraftApp {
|
||||
palette_open: false,
|
||||
palette_query: String::new(),
|
||||
all_tools_expanded: false,
|
||||
all_tools_full: false,
|
||||
all_tools_query: String::new(),
|
||||
all_tools_tab: (None, false),
|
||||
recent: Vec::new(),
|
||||
reopen_last_session: false,
|
||||
restore_last_view: true,
|
||||
@@ -1601,6 +1611,10 @@ impl PdfCraftApp {
|
||||
{
|
||||
view.open_organize();
|
||||
}
|
||||
// The document shows, not the expanded All tools view over it (whichever workspace opens).
|
||||
if activate {
|
||||
self.all_tools_full = false;
|
||||
}
|
||||
if activate && let Some(mode) = self.mode_override {
|
||||
// Explicit mode options do not reset independent --tool / --left choices.
|
||||
self.mode = mode;
|
||||
@@ -2463,6 +2477,8 @@ impl PdfCraftApp {
|
||||
/// Enter a tool from the catalogue, Home, palette or control channel. Organize is a
|
||||
/// workspace, so opening its panel also opens the page grid (without toggling it off).
|
||||
pub(crate) fn select_tool(&mut self, id: &'static str) {
|
||||
// The tool's panel shows, not the expanded All tools view over it.
|
||||
self.all_tools_full = false;
|
||||
self.left = LeftPanel::Tool(id);
|
||||
self.reveal_tools();
|
||||
if let Some(view) = self.active.and_then(|i| self.views.get_mut(i)) {
|
||||
@@ -2497,6 +2513,10 @@ impl PdfCraftApp {
|
||||
/// Edit puts the editing tools down again and applies a paragraph still open in the editor.
|
||||
pub(crate) fn enter_mode(&mut self, mode: Mode) {
|
||||
self.mode = mode;
|
||||
// Another workspace puts the expanded All tools view away.
|
||||
if mode != Mode::AllTools {
|
||||
self.all_tools_full = false;
|
||||
}
|
||||
if mode == Mode::Edit {
|
||||
self.quick_tool = QuickTool::EditText;
|
||||
} else if matches!(self.quick_tool, QuickTool::EditText | QuickTool::AddText) {
|
||||
@@ -2509,6 +2529,21 @@ impl PdfCraftApp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fill the workspace with the expanded All tools view, entering All tools the way the mode
|
||||
/// bar does: leaving Read restores its right panel and leaving Edit puts the editing tool down.
|
||||
pub(crate) fn expand_all_tools(&mut self) {
|
||||
if self.mode != Mode::AllTools {
|
||||
self.switch_mode(Mode::AllTools, true);
|
||||
}
|
||||
self.all_tools_full = true;
|
||||
self.all_tools_tab = self.shown_tab();
|
||||
}
|
||||
|
||||
/// The tab at the front: the active document, or else whether Combine files shows over Home.
|
||||
fn shown_tab(&self) -> (Option<DocId>, bool) {
|
||||
(self.active.and_then(|i| self.views.get(i)).map(|v| v.id), self.combine_showing())
|
||||
}
|
||||
|
||||
/// Back to the Select tool from an editing tool. A paragraph still open in the editor is
|
||||
/// applied, as clicking away from it does; left open, its page would keep showing the
|
||||
/// paragraph's text removed with no editor over it.
|
||||
@@ -2893,6 +2928,10 @@ impl PdfCraftApp {
|
||||
let mode = Mode::parse(value).unwrap_or(Mode::AllTools);
|
||||
self.mode_override = Some(mode);
|
||||
self.mode = mode;
|
||||
// As switch_mode: another workspace puts the expanded All tools view away.
|
||||
if mode != Mode::AllTools {
|
||||
self.all_tools_full = false;
|
||||
}
|
||||
}
|
||||
("default-mode", _) => {
|
||||
self.default_mode = Mode::parse(value).ok_or("default-mode must be all, read, edit, convert or sign")?;
|
||||
@@ -2904,7 +2943,12 @@ impl PdfCraftApp {
|
||||
self.select_tool(g.id);
|
||||
}
|
||||
("left", _) => self.left_open = value != "closed",
|
||||
("home", _) => self.active = None,
|
||||
("home", _) => {
|
||||
self.active = None;
|
||||
// With no document open the expanded All tools view covers Home itself, so no
|
||||
// other tab comes forward to put it away.
|
||||
self.all_tools_full = false;
|
||||
}
|
||||
("dialog", _) => {
|
||||
self.dialog = match value {
|
||||
"properties" => Some(Dialog::Properties(PropsTab::Description)),
|
||||
@@ -2949,7 +2993,11 @@ impl PdfCraftApp {
|
||||
_ => Some(Dialog::About),
|
||||
}
|
||||
}
|
||||
("tools", _) => self.all_tools_expanded = value != "collapsed",
|
||||
("tools", _) if value == "full" => self.expand_all_tools(),
|
||||
("tools", _) => {
|
||||
self.all_tools_full = false;
|
||||
self.all_tools_expanded = value != "collapsed";
|
||||
}
|
||||
("palette", _) => {
|
||||
self.palette_open = true;
|
||||
self.palette_query = value.to_string();
|
||||
@@ -3662,6 +3710,10 @@ impl eframe::App for PdfCraftApp {
|
||||
.show(ui, |ui| panels::right_panel(self, ui));
|
||||
}
|
||||
}
|
||||
// Whichever way another tab came to the front, it shows instead of the expanded view.
|
||||
if self.all_tools_full && self.all_tools_tab != self.shown_tab() {
|
||||
self.all_tools_full = false;
|
||||
}
|
||||
let t = theme::Tokens::get(&ctx);
|
||||
egui::CentralPanel::default().frame(egui::Frame::NONE.fill(t.pasteboard)).show(ui, |ui| {
|
||||
if self.mode == Mode::Read {
|
||||
@@ -3673,6 +3725,8 @@ impl eframe::App for PdfCraftApp {
|
||||
None => canvas::document_area(self, index, ui),
|
||||
},
|
||||
}
|
||||
} else if self.mode == Mode::AllTools && self.all_tools_full {
|
||||
all_tools_ui::show(self, ui);
|
||||
} else {
|
||||
docking::show(self, ui);
|
||||
}
|
||||
|
||||
@@ -36,14 +36,14 @@ pub fn left_panel(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
|
||||
);
|
||||
}
|
||||
|
||||
/// A panel's title row: Back (with `back` as its tooltip) when given, the title and Close.
|
||||
/// Returns whether Back and Close were clicked.
|
||||
pub(crate) fn panel_header(ui: &mut egui::Ui, t: &Tokens, title: &str, back: Option<&str>) -> (bool, bool) {
|
||||
/// A panel's title row: a leading button (`lead`: its icon and tooltip) when given, the title
|
||||
/// and Close. Returns whether the leading button and Close were clicked.
|
||||
pub(crate) fn panel_header(ui: &mut egui::Ui, t: &Tokens, title: &str, lead: Option<(&str, &str)>) -> (bool, bool) {
|
||||
let mut go_back = false;
|
||||
let mut close = false;
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(tip) = back
|
||||
&& icons::button(ui, "chevron-left", 26.0, false, tip).clicked()
|
||||
if let Some((icon, tip)) = lead
|
||||
&& icons::button(ui, icon, 26.0, false, tip).clicked()
|
||||
{
|
||||
go_back = true;
|
||||
}
|
||||
@@ -62,8 +62,14 @@ pub(crate) fn panel_header(ui: &mut egui::Ui, t: &Tokens, title: &str, back: Opt
|
||||
(go_back, close)
|
||||
}
|
||||
|
||||
/// The All tools sidebar: Expand (the full view) and Close, then the tool list, the first few
|
||||
/// tools until View more shows them all.
|
||||
fn all_tools(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
let (_, close) = panel_header(ui, t, "All tools", None);
|
||||
// Labels distinct from the Combine grid's Expand and Collapse, which can show beside them.
|
||||
let (expand, close) = panel_header(ui, t, "All tools", Some(("maximize-2", tl!("Expand all tools"))));
|
||||
if expand {
|
||||
app.expand_all_tools();
|
||||
}
|
||||
if close {
|
||||
app.left_open = false;
|
||||
}
|
||||
@@ -154,7 +160,7 @@ fn tool_detail(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens, g: &'static
|
||||
edit_panel(app, ui, t, g);
|
||||
return;
|
||||
}
|
||||
let (back, close) = panel_header(ui, t, g.label, Some(tl!("Back to all tools")));
|
||||
let (back, close) = panel_header(ui, t, g.label, Some(("chevron-left", tl!("Back to all tools"))));
|
||||
if back {
|
||||
app.left = LeftPanel::AllTools;
|
||||
app.enter_mode(crate::Mode::AllTools);
|
||||
|
||||
@@ -125,6 +125,170 @@ fn every_catalog_tool_is_listed_after_view_more() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expanded_all_tools_lists_every_tool_by_category_and_searches() {
|
||||
let mut h = harness(|_| {});
|
||||
h.get_by_label("Expand all tools").click();
|
||||
h.run_steps(3);
|
||||
assert!(h.state().all_tools_full);
|
||||
for c in pdfcraft_engine::catalog::TOOL_CATEGORIES {
|
||||
h.get_by_label(c.label);
|
||||
}
|
||||
for g in pdfcraft_engine::catalog::TOOL_GROUPS {
|
||||
assert!(h.query_all_by_label(g.label).count() >= 1, "tool {} missing from the expanded view", g.label);
|
||||
}
|
||||
// An item name finds its tool; the other categories drop out.
|
||||
h.state_mut().all_tools_query = "rotate".into();
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.query_all_by_label("Organize pages").count(), 1, "only the expanded view's tile; the sidebar is gone");
|
||||
assert!(h.query_by_label("Forms & Signatures").is_none());
|
||||
h.state_mut().all_tools_query = "no such tool".into();
|
||||
h.run_steps(3);
|
||||
h.get_by_label("No tools match your search.");
|
||||
// Choosing a tool goes back to the workspace with that tool's panel.
|
||||
h.state_mut().all_tools_query = "organize".into();
|
||||
h.run_steps(3);
|
||||
h.get_by_label("Organize pages").click();
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().all_tools_full);
|
||||
assert_eq!(h.state().left, pdfcraft_ui_egui::LeftPanel::Tool("organize"));
|
||||
h.get_by_label("Back to all tools");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collapse_and_escape_close_the_expanded_all_tools() {
|
||||
let mut h = harness(|app| app.set_option("tools", "full").unwrap());
|
||||
h.get_by_label("Collapse all tools").click();
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().all_tools_full);
|
||||
h.get_by_label("View more");
|
||||
h.get_by_label("Expand all tools").click();
|
||||
h.run_steps(3);
|
||||
h.key_press(egui::Key::Escape);
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().all_tools_full);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_in_a_dialog_over_the_expanded_all_tools_leaves_the_view_open() {
|
||||
let mut h = harness(|app| app.set_option("tools", "full").unwrap());
|
||||
h.state_mut().dialog = Some(pdfcraft_ui_egui::Dialog::About);
|
||||
h.run_steps(3);
|
||||
h.key_press(egui::Key::Escape);
|
||||
h.run_steps(3);
|
||||
assert!(h.state().all_tools_full, "Escape belonged to the dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_in_the_command_palette_over_the_expanded_all_tools_leaves_the_view_open() {
|
||||
let mut h = harness(|app| app.set_option("tools", "full").unwrap());
|
||||
h.state_mut().palette_open = true;
|
||||
h.run_steps(3);
|
||||
h.key_press(egui::Key::Escape);
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().palette_open, "Escape closed the palette");
|
||||
assert!(h.state().all_tools_full, "and only the palette");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn another_workspace_from_the_mode_option_puts_the_expanded_all_tools_away() {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("tools", "full").unwrap();
|
||||
app.set_option("mode", "all").unwrap();
|
||||
assert!(app.all_tools_full, "All tools itself keeps the view");
|
||||
app.set_option("mode", "read").unwrap();
|
||||
app.set_option("mode", "all").unwrap();
|
||||
assert!(!app.all_tools_full, "coming back shows the sidebar, as the mode bar does");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opening_a_document_puts_the_expanded_all_tools_away() {
|
||||
use pdfcraft_ui_egui::Mode;
|
||||
// All tools as the default workspace: no workspace change, but the document must show.
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("tools", "full").unwrap();
|
||||
app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens");
|
||||
assert!(!app.all_tools_full, "the document shows, not the catalogue over it");
|
||||
// Read as the default workspace: coming back to All tools later shows the sidebar.
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("default-mode", "read").unwrap();
|
||||
app.set_option("tools", "full").unwrap();
|
||||
app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens");
|
||||
assert_eq!(app.mode, Mode::Read);
|
||||
assert!(!app.all_tools_full);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choosing_a_tool_puts_the_expanded_all_tools_away() {
|
||||
use pdfcraft_ui_egui::LeftPanel;
|
||||
let mut h = harness(|app| app.set_option("tools", "full").unwrap());
|
||||
h.state_mut().set_option("tool", "organize").unwrap();
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().all_tools_full, "the tool's panel shows, not the catalogue");
|
||||
assert_eq!(h.state().left, LeftPanel::Tool("organize"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn another_tab_coming_forward_puts_the_expanded_all_tools_away() {
|
||||
// Home, through the control option.
|
||||
let mut h = harness(|app| app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens"));
|
||||
h.state_mut().set_option("tools", "full").unwrap();
|
||||
h.run_steps(3);
|
||||
assert!(h.state().all_tools_full, "the document's own tab keeps it");
|
||||
h.state_mut().set_option("home", "").unwrap();
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().all_tools_full);
|
||||
h.get_by_label_contains("Welcome to PdfCraft");
|
||||
// Another document's tab.
|
||||
let mut h = harness(|app| {
|
||||
app.open_bytes("one.pdf", None, FIXTURE.to_vec()).expect("fixture opens");
|
||||
app.open_bytes("two.pdf", None, FIXTURE.to_vec()).expect("fixture opens");
|
||||
});
|
||||
h.state_mut().set_option("tools", "full").unwrap();
|
||||
h.run_steps(3);
|
||||
h.state_mut().active = Some(0);
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().all_tools_full, "one.pdf shows, not the catalogue over it");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn going_home_with_no_document_puts_the_expanded_all_tools_away() {
|
||||
// Home is already the tab in front, so only the option itself can put the view away.
|
||||
let mut h = harness(|app| app.set_option("tools", "full").unwrap());
|
||||
h.state_mut().set_option("home", "").unwrap();
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().all_tools_full);
|
||||
h.get_by_label_contains("Welcome to PdfCraft");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expanding_all_tools_from_read_mode_restores_the_right_panel() {
|
||||
use pdfcraft_ui_egui::{Mode, RightPanel};
|
||||
let mut h = harness(|app| app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens"));
|
||||
h.state_mut().right = Some(RightPanel::Bookmarks);
|
||||
h.state_mut().execute("view.read_mode");
|
||||
h.run_steps(3);
|
||||
assert_eq!((h.state().mode, h.state().right), (Mode::Read, None), "Read mode hides the panel");
|
||||
h.state_mut().set_option("tools", "full").unwrap();
|
||||
h.run_steps(3);
|
||||
assert!(h.state().all_tools_full);
|
||||
assert_eq!((h.state().mode, h.state().right), (Mode::AllTools, Some(RightPanel::Bookmarks)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expanding_all_tools_from_edit_puts_edit_text_down() {
|
||||
use pdfcraft_ui_egui::{LeftPanel, Mode, QuickTool};
|
||||
let mut h = harness(|app| app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens"));
|
||||
h.get_by_label("Edit").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().quick_tool, QuickTool::EditText);
|
||||
h.state_mut().set_option("tools", "full").unwrap();
|
||||
h.run_steps(3);
|
||||
assert!(h.state().all_tools_full);
|
||||
assert_eq!((h.state().mode, h.state().quick_tool), (Mode::AllTools, QuickTool::Select));
|
||||
assert_eq!(h.state().left, LeftPanel::AllTools, "collapsing goes back to the All tools list");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opening_a_pdf_shows_comments_and_bookmarks() {
|
||||
let mut h = harness(|app| app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens"));
|
||||
|
||||
Reference in New Issue
Block a user