mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
Merge pull request #22 from storytold/pr/never-crash-ui
Never crash: UI and app shell have no expect/unreachable
This commit is contained in:
@@ -29,6 +29,8 @@
|
||||
//! *separate child process* with a wall-clock timeout, so hangs, panics and aborts in any
|
||||
//! dependency are observed and reported instead of taking the harness down.
|
||||
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, ExitCode, Stdio};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! PrintCraft in the browser. Build: `cd apps/printcraft-web && trunk build --release`
|
||||
//! (trunk generates the JS loader; no handwritten JS — plan/execution-plan.md §1).
|
||||
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
use eframe::wasm_bindgen::JsCast;
|
||||
|
||||
@@ -21,7 +21,8 @@ fn main() {
|
||||
.set("InternalName", "printcraft");
|
||||
if let Err(e) = res.compile() {
|
||||
if std::env::var_os("PRINTCRAFT_REQUIRE_WINRES").is_some() {
|
||||
panic!("embedding Windows resources failed: {e}");
|
||||
println!("cargo::error=embedding Windows resources failed: {e}");
|
||||
return;
|
||||
}
|
||||
println!("cargo:warning=printcraft.exe built without icon/version resources: {e}");
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
//! loopback port and writes `{"port", "token", "pid"}` to `<file>` (owner-only permissions).
|
||||
//! Agents then drive it with `printcraft-cli ui --control <file> <method> …`.
|
||||
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
use printcraft_ui_egui::PrintCraftApp;
|
||||
|
||||
/// Freedesktop app id: the `.desktop` file name and the hicolor icon name.
|
||||
|
||||
@@ -103,8 +103,9 @@ impl PrintCraftApp {
|
||||
self.dialog = Some(Dialog::NumberPages);
|
||||
}
|
||||
link if printcraft_engine::links::for_command(link).is_some() => {
|
||||
let url = printcraft_engine::links::for_command(link).expect("checked").url;
|
||||
self.open_url(url);
|
||||
if let Some(l) = printcraft_engine::links::for_command(link) {
|
||||
self.open_url(l.url);
|
||||
}
|
||||
}
|
||||
"bookmark.add" => self.bookmark_action(crate::panels::BmAction::New),
|
||||
"edit.undo" => self.undo(),
|
||||
@@ -132,7 +133,7 @@ impl PrintCraftApp {
|
||||
}
|
||||
"comment.list" => self.right = Some(RightPanel::Comments),
|
||||
tool if crate::comments::CommentTool::from_command(tool).is_some() => {
|
||||
let tool = crate::comments::CommentTool::from_command(tool).expect("checked");
|
||||
let Some(tool) = crate::comments::CommentTool::from_command(tool) else { return false };
|
||||
self.comment_prefs.group_tool[tool.group()] = tool;
|
||||
self.quick_tool = crate::QuickTool::Comment(tool);
|
||||
// Acrobat opens the Comments panel with the commenting tools.
|
||||
@@ -140,8 +141,10 @@ impl PrintCraftApp {
|
||||
self.right = Some(RightPanel::Comments);
|
||||
}
|
||||
// A text selection made before picking a markup tool is marked right away.
|
||||
if let (Some(kind), Some(i)) = (tool.markup(), active) {
|
||||
let info = &self.session.get(self.views[i].id).expect("active").info;
|
||||
if let (Some(kind), Some(i)) = (tool.markup(), active)
|
||||
&& let Some(doc) = self.session.get(self.views[i].id)
|
||||
{
|
||||
let info = &doc.info;
|
||||
if let Some((page, quads)) = self.views[i].selection_quads(info) {
|
||||
self.views[i].clear_selection();
|
||||
let style = self.comment_prefs.style(tool);
|
||||
@@ -281,8 +284,10 @@ impl PrintCraftApp {
|
||||
self.left = crate::LeftPanel::Tool("redact");
|
||||
self.left_open = true;
|
||||
// A text selection made first is marked right away.
|
||||
if let Some(i) = active {
|
||||
let info = &self.session.get(self.views[i].id).expect("active").info;
|
||||
if let Some(i) = active
|
||||
&& let Some(doc) = self.session.get(self.views[i].id)
|
||||
{
|
||||
let info = &doc.info;
|
||||
if let Some((page, quads)) = self.views[i].selection_quads(info) {
|
||||
self.views[i].clear_selection();
|
||||
let author = self.comment_prefs.author.clone();
|
||||
@@ -409,7 +414,7 @@ impl PrintCraftApp {
|
||||
}
|
||||
}
|
||||
field if crate::prepare::FieldTool::from_command(field).is_some() => {
|
||||
let tool = crate::prepare::FieldTool::from_command(field).expect("checked");
|
||||
let Some(tool) = crate::prepare::FieldTool::from_command(field) else { return false };
|
||||
self.quick_tool = crate::QuickTool::Field(tool);
|
||||
self.left = crate::LeftPanel::Tool("form");
|
||||
self.left_open = true;
|
||||
@@ -419,7 +424,7 @@ impl PrintCraftApp {
|
||||
self.notify(format!("Click on the page to add a {}, or drag to set its size", tool.label().to_lowercase()));
|
||||
}
|
||||
fill if crate::fill_sign::FillTool::from_command(fill).is_some() => {
|
||||
let tool = crate::fill_sign::FillTool::from_command(fill).expect("checked");
|
||||
let Some(tool) = crate::fill_sign::FillTool::from_command(fill) else { return false };
|
||||
self.quick_tool = crate::QuickTool::Fill(tool);
|
||||
let initials = tool == crate::fill_sign::FillTool::Initials;
|
||||
if (tool == crate::fill_sign::FillTool::Signature && self.signature.is_none()) || (initials && self.initials.is_none()) {
|
||||
@@ -467,7 +472,7 @@ impl PrintCraftApp {
|
||||
let mut specs: Vec<&CommandSpec> = COMMANDS.iter().filter(|c| c.shortcut.is_some()).collect();
|
||||
specs.sort_by_key(|c| std::cmp::Reverse(c.shortcut.map(|s| s.modifier_count()).unwrap_or(0)));
|
||||
for spec in specs {
|
||||
let s = spec.shortcut.expect("filtered");
|
||||
let Some(s) = spec.shortcut else { continue };
|
||||
if typing && !spec.in_text {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -832,9 +832,10 @@ pub(crate) fn page_after_text(resp: &egui::Response, cx: &PageCx<'_>, view: &mut
|
||||
if tool.markup().is_none() || !cx.allowed || !(resp.drag_stopped() || resp.double_clicked()) {
|
||||
return;
|
||||
}
|
||||
if let Some((page, quads)) = view.selection_quads(cx.info).filter(|(p, _)| *p == cx.page) {
|
||||
if let Some(kind) = tool.markup()
|
||||
&& let Some((page, quads)) = view.selection_quads(cx.info).filter(|(p, _)| *p == cx.page)
|
||||
{
|
||||
view.clear_selection();
|
||||
let kind = tool.markup().expect("checked");
|
||||
view.pending_edit = Some(new_comment(cx, tool, Shape::TextMarkup { kind, quads }, String::new()));
|
||||
let _ = page;
|
||||
}
|
||||
@@ -994,7 +995,7 @@ pub(crate) fn composer(ctx: &egui::Context, view: &mut DocView, info: &DocInfo,
|
||||
let t = Tokens::get(ctx);
|
||||
let mut post = false;
|
||||
let mut cancel = false;
|
||||
let c = view.comments.composer.as_mut().expect("checked");
|
||||
let c = view.comments.composer.as_mut()?;
|
||||
let title = match c.kind {
|
||||
ComposerKind::Note => "Sticky note",
|
||||
ComposerKind::TextBox => "Text box",
|
||||
|
||||
@@ -280,15 +280,15 @@ fn card(
|
||||
});
|
||||
egui::Frame::NONE.inner_margin(egui::Margin { left: 34, right: 0, top: 4, bottom: 0 }).show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
if editing {
|
||||
let (_, _, text) = view.comments.editing.as_mut().expect("checked");
|
||||
if editing && let Some((_, _, text)) = view.comments.editing.as_mut() {
|
||||
let r = ui.add(egui::TextEdit::multiline(text).desired_rows(2).desired_width(f32::INFINITY).id_salt(("comment-edit", key)));
|
||||
if !r.has_focus() && !ui.memory(|m| m.focused().is_some()) {
|
||||
r.request_focus();
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
if ui.add(egui::Button::new(egui::RichText::new("Save").color(Color32::WHITE)).fill(t.accent).corner_radius(12)).clicked() {
|
||||
let (page, index, text) = view.comments.editing.take().expect("checked");
|
||||
if ui.add(egui::Button::new(egui::RichText::new("Save").color(Color32::WHITE)).fill(t.accent).corner_radius(12)).clicked()
|
||||
&& let Some((page, index, text)) = view.comments.editing.take()
|
||||
{
|
||||
edit = Some(Edit::SetAnnotationContents { page, index, text });
|
||||
}
|
||||
if ui.button("Cancel").clicked() || ui.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
|
||||
@@ -273,7 +273,7 @@ pub(crate) fn editor(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, ad
|
||||
let zoom = xf.rect.width() / xf.pw.max(1.0);
|
||||
let (mut commit, mut cancel) = (false, false);
|
||||
egui::Area::new(egui::Id::new(("added-text", view.id.0))).order(egui::Order::Foreground).fixed_pos(r.min).show(ctx, |ui| {
|
||||
let t = view.content.draft.as_mut().expect("checked");
|
||||
let Some(t) = view.content.draft.as_mut() else { return };
|
||||
let [cr, cg, cb] = t.style.color.map(|v| (v.clamp(0.0, 1.0) * 255.0) as u8);
|
||||
let resp = ui.add(
|
||||
egui::TextEdit::multiline(&mut t.text)
|
||||
|
||||
@@ -419,7 +419,8 @@ fn widget(id: NodeId, n: &accesskit::Node, depth: usize, focused: bool) -> Value
|
||||
"enabled": !n.is_disabled(),
|
||||
"clickable": n.supports_action(Action::Click),
|
||||
});
|
||||
let obj = w.as_object_mut().expect("object");
|
||||
let mut more = serde_json::Map::new();
|
||||
let obj = &mut more;
|
||||
if let Some(l) = n.label() {
|
||||
obj.insert("label".into(), json!(l));
|
||||
}
|
||||
@@ -441,6 +442,9 @@ fn widget(id: NodeId, n: &accesskit::Node, depth: usize, focused: bool) -> Value
|
||||
if focused {
|
||||
obj.insert("focused".into(), json!(true));
|
||||
}
|
||||
if let Value::Object(m) = &mut w {
|
||||
m.extend(more);
|
||||
}
|
||||
w
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ impl PrintCraftApp {
|
||||
fn open_created_bytes(&mut self, name: &str, created: Result<Arc<Vec<u8>>, String>) -> Result<(), String> {
|
||||
let bytes = created?;
|
||||
let id = self.session.open_new(name, bytes).map_err(|e| e.to_string())?;
|
||||
let info = &self.session.get(id).expect("just opened").info;
|
||||
let info = &self.session.get(id).ok_or("the new document could not be opened")?.info;
|
||||
self.views.push(crate::DocView::new(id, info));
|
||||
self.active = Some(self.views.len() - 1);
|
||||
Ok(())
|
||||
|
||||
@@ -32,7 +32,8 @@ impl PrintCraftApp {
|
||||
let label = edit.label();
|
||||
match self.session.apply(id, edit.clone()) {
|
||||
Ok(()) => {
|
||||
let info = &self.session.get(id).expect("exists").info;
|
||||
let Some(doc) = self.session.get(id) else { return true };
|
||||
let info = &doc.info;
|
||||
let view = &mut self.views[i];
|
||||
match comment_page(&edit) {
|
||||
// Comment edits change one page: keep every other raster.
|
||||
@@ -84,8 +85,9 @@ impl PrintCraftApp {
|
||||
let result = if undo { self.session.undo(id) } else { self.session.redo(id) };
|
||||
match result {
|
||||
Ok(label) => {
|
||||
let info = &self.session.get(id).expect("exists").info;
|
||||
self.views[i].document_changed(info);
|
||||
if let Some(doc) = self.session.get(id) {
|
||||
self.views[i].document_changed(&doc.info);
|
||||
}
|
||||
self.views[i].comments.selected = None;
|
||||
self.notify(format!("{} {label}", if undo { "Undid" } else { "Redid" }));
|
||||
}
|
||||
@@ -177,8 +179,9 @@ impl PrintCraftApp {
|
||||
match self.session.mark_saved(id, bytes, Some(dest.clone())) {
|
||||
Ok(()) => {
|
||||
self.forget_recovery(id);
|
||||
let info = &self.session.get(id).expect("exists").info;
|
||||
self.views[index].document_changed(info);
|
||||
if let Some(doc) = self.session.get(id) {
|
||||
self.views[index].document_changed(&doc.info);
|
||||
}
|
||||
self.notify(format!("Saved {}", short_name(&dest)));
|
||||
true
|
||||
}
|
||||
@@ -194,8 +197,9 @@ impl PrintCraftApp {
|
||||
match download(&name, &bytes) {
|
||||
Ok(()) => {
|
||||
let _ = self.session.mark_saved(id, bytes, None);
|
||||
let info = &self.session.get(id).expect("exists").info;
|
||||
self.views[index].document_changed(info);
|
||||
if let Some(doc) = self.session.get(id) {
|
||||
self.views[index].document_changed(&doc.info);
|
||||
}
|
||||
self.notify(format!("Downloaded {name}"));
|
||||
true
|
||||
}
|
||||
|
||||
@@ -155,7 +155,8 @@ fn run(
|
||||
}
|
||||
let mut ex = Exporter::from_source(src);
|
||||
match kind {
|
||||
ExportKind::AllImages => unreachable!("handled above"),
|
||||
// Handled above.
|
||||
ExportKind::AllImages => String::new(),
|
||||
ExportKind::Image => {
|
||||
for (k, p) in pages.iter().enumerate() {
|
||||
set(k, None);
|
||||
|
||||
@@ -350,8 +350,8 @@ impl PrintCraftApp {
|
||||
pub(crate) fn open_created(&mut self, name: &str, bytes: Arc<Vec<u8>>, message: &str) {
|
||||
match self.session.open_new(name, bytes) {
|
||||
Ok(id) => {
|
||||
let info = &self.session.get(id).expect("just opened").info;
|
||||
self.views.push(crate::DocView::new(id, info));
|
||||
let Some(doc) = self.session.get(id) else { return };
|
||||
self.views.push(crate::DocView::new(id, &doc.info));
|
||||
self.active = Some(self.views.len() - 1);
|
||||
self.notify(message);
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ pub(crate) fn page_input(
|
||||
None => Some(FillAction::CreateInitials),
|
||||
},
|
||||
mark => {
|
||||
let mark = mark.mark().expect("marks");
|
||||
let mark = mark.mark()?;
|
||||
let (w, h) = if mark == FillMark::Line { (36.0, 4.0) } else { (12.0, 12.0) };
|
||||
let rect = [at[0] - w / 2.0, at[1] - h / 2.0, at[0] + w / 2.0, at[1] + h / 2.0];
|
||||
Some(FillAction::Edit(Box::new(new(page, Shape::Mark { rect, mark }, String::new(), author))))
|
||||
@@ -280,7 +280,7 @@ pub(crate) fn type_box(ctx: &egui::Context, view: &mut DocView, info: &DocInfo,
|
||||
let mut commit = false;
|
||||
let mut cancel = false;
|
||||
egui::Area::new(egui::Id::new(("fill-text", view.id.0))).order(egui::Order::Foreground).fixed_pos(pos).show(ctx, |ui| {
|
||||
let t = view.fill_text.as_mut().expect("checked");
|
||||
let Some(t) = view.fill_text.as_mut() else { return };
|
||||
let width = ((t.text.len().max(8) as f32) * TEXT_SIZE as f32 * 0.6 * zoom).clamp(60.0, 600.0);
|
||||
let r = ui.add(
|
||||
egui::TextEdit::singleline(&mut t.text)
|
||||
|
||||
@@ -247,7 +247,7 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, f
|
||||
let font = (size.unwrap_or(((w.rect[3] - w.rect[1]) as f32 * 0.6).clamp(6.0, 12.0)) * zoom).clamp(6.0, 64.0);
|
||||
egui::Area::new(egui::Id::new(("form-editor", view.id.0))).order(egui::Order::Foreground).fixed_pos(rect.min).show(ctx, |ui| {
|
||||
ui.set_min_size(rect.size());
|
||||
let fx = view.forms.focus.as_mut().expect("checked");
|
||||
let Some(fx) = view.forms.focus.as_mut() else { return };
|
||||
let mut te = if multiline { egui::TextEdit::multiline(&mut fx.text) } else { egui::TextEdit::singleline(&mut fx.text) };
|
||||
te = te
|
||||
.desired_width(rect.width() - 6.0)
|
||||
@@ -310,7 +310,7 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, f
|
||||
ui.set_min_width(rect.width().max(160.0));
|
||||
ui.label(egui::RichText::new(&f.name).small().color(t.text_faint));
|
||||
egui::ScrollArea::vertical().max_height(260.0).show(ui, |ui| {
|
||||
let fx = view.forms.focus.as_mut().expect("checked");
|
||||
let Some(fx) = view.forms.focus.as_mut() else { return };
|
||||
for (export, display) in &f.options {
|
||||
let on = if multi { fx.picked.contains(export) } else { f.value.first() == Some(export) };
|
||||
if multi {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
//! Everything here is presentation: documents, rendering and the tool catalogue live in
|
||||
//! `printcraft-engine`.
|
||||
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
mod a11y_ui;
|
||||
mod actions_ui;
|
||||
pub mod canvas;
|
||||
@@ -600,7 +602,7 @@ impl PrintCraftApp {
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
self.password_prompt = None;
|
||||
let doc = self.session.get(id).expect("just opened");
|
||||
let doc = self.session.get(id).ok_or("the document could not be opened")?;
|
||||
let pages = doc.info.pages.len();
|
||||
// Acrobat opens straight to the Comments panel when a document has comments.
|
||||
if self.right.is_none() {
|
||||
|
||||
@@ -707,8 +707,9 @@ fn outline_item(ui: &mut egui::Ui, t: &Tokens, info: &DocInfo, item: &OutlineIte
|
||||
if resp.double_clicked() && cx.editable {
|
||||
*cx.action = Some(BmAction::StartRename(path.to_vec()));
|
||||
}
|
||||
if cx.editable {
|
||||
let i = *path.last().expect("non-empty path");
|
||||
if cx.editable
|
||||
&& let Some(&i) = path.last()
|
||||
{
|
||||
let current = cx.current;
|
||||
resp.context_menu(|ui| {
|
||||
let mut pick = |ui: &mut egui::Ui, label: &str, enabled: bool, a: BmAction| {
|
||||
|
||||
@@ -85,7 +85,8 @@ impl FieldTool {
|
||||
FieldTool::Radio => match join.filter(|f| f.kind == FormFieldKind::Radio) {
|
||||
Some(g) => {
|
||||
let taken: Vec<&str> = g.widgets.iter().filter_map(|w| w.on_state.as_deref()).collect();
|
||||
let export = (1..).map(|i| format!("Choice{i}")).find(|c| !taken.contains(&c.as_str())).expect("unbounded");
|
||||
let export =
|
||||
(1..=u64::MAX).map(|i| format!("Choice{i}")).find(|c| !taken.contains(&c.as_str())).unwrap_or_else(|| "Choice".into());
|
||||
NewField::Radio { group: Some(g.name.clone()), export }
|
||||
}
|
||||
None => NewField::Radio { group: None, export: "Choice1".into() },
|
||||
|
||||
@@ -188,12 +188,12 @@ impl PrintCraftApp {
|
||||
self.redact_prefs.mark(p, vec![rect_quad([c[0] as f64, c[1] as f64, c[2] as f64, c[3] as f64])], &author)
|
||||
})
|
||||
.collect();
|
||||
match edits.len() {
|
||||
0 => {}
|
||||
1 => {
|
||||
self.apply_edit(edits.into_iter().next().expect("one"));
|
||||
match <[Edit; 1]>::try_from(edits) {
|
||||
Ok([one]) => {
|
||||
self.apply_edit(one);
|
||||
}
|
||||
_ => {
|
||||
Err(edits) if edits.is_empty() => {}
|
||||
Err(edits) => {
|
||||
self.apply_edit(Edit::Batch { label: "Mark pages for redaction".into(), edits });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,10 +247,10 @@ impl PrintCraftApp {
|
||||
.ok_or("There is no folder to save the digital ID in.")?;
|
||||
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
|
||||
let stem: String = d.name.trim().chars().map(|c| if c.is_alphanumeric() || c == ' ' || c == '-' { c } else { '_' }).collect();
|
||||
let path = (1..)
|
||||
let path = (1..=u64::MAX)
|
||||
.map(|i| dir.join(if i == 1 { format!("{stem}.p12") } else { format!("{stem} {i}.p12") }))
|
||||
.find(|p| !p.exists())
|
||||
.expect("a free name");
|
||||
.ok_or("no free file name for the digital ID")?;
|
||||
std::fs::write(&path, &p12).map_err(|e| e.to_string())?;
|
||||
Ok(self.add_digital_id(&path.to_string_lossy(), &cert))
|
||||
}
|
||||
@@ -327,8 +327,8 @@ impl PrintCraftApp {
|
||||
let Some((_, id)) = self.active_ids() else { return };
|
||||
match self.session.open_revision(id, n) {
|
||||
Ok(new) => {
|
||||
let info = &self.session.get(new).expect("just opened").info;
|
||||
self.views.push(DocView::new(new, info));
|
||||
let Some(doc) = self.session.get(new) else { return };
|
||||
self.views.push(DocView::new(new, &doc.info));
|
||||
self.active = Some(self.views.len() - 1);
|
||||
}
|
||||
Err(e) => self.notify(format!("Couldn't open revision {n}: {e}")),
|
||||
@@ -343,8 +343,8 @@ impl PrintCraftApp {
|
||||
let name = format!("{} (signed version)", doc.name.trim_end_matches(".pdf"));
|
||||
match self.session.open(format!("{name}.pdf"), None, std::sync::Arc::new(bytes), doc.password.clone().as_deref()) {
|
||||
Ok(new) => {
|
||||
let info = &self.session.get(new).expect("just opened").info;
|
||||
self.views.push(DocView::new(new, info));
|
||||
let Some(doc) = self.session.get(new) else { return };
|
||||
self.views.push(DocView::new(new, &doc.info));
|
||||
self.active = Some(self.views.len() - 1);
|
||||
}
|
||||
Err(e) => self.notify(format!("Couldn't open the signed version: {e}")),
|
||||
@@ -381,7 +381,7 @@ fn choose(ui: &mut egui::Ui, app: &mut PrintCraftApp, t: &Tokens) -> bool {
|
||||
ui.label("Choose the digital ID that you want to use for signing:");
|
||||
ui.add_space(6.0);
|
||||
let ids = app.digital_ids.clone();
|
||||
let d = app.sign_draft.as_mut().expect("checked");
|
||||
let Some(d) = app.sign_draft.as_mut() else { return true };
|
||||
let mut close = false;
|
||||
egui::ScrollArea::vertical().max_height(220.0).show(ui, |ui| {
|
||||
for (i, e) in ids.iter().enumerate() {
|
||||
@@ -438,7 +438,7 @@ fn choose(ui: &mut egui::Ui, app: &mut PrintCraftApp, t: &Tokens) -> bool {
|
||||
|
||||
fn configure(ui: &mut egui::Ui, app: &mut PrintCraftApp, t: &Tokens) -> bool {
|
||||
title(ui, "Configure a Digital ID for Signing");
|
||||
let d = app.sign_draft.as_mut().expect("checked");
|
||||
let Some(d) = app.sign_draft.as_mut() else { return true };
|
||||
let mut close = false;
|
||||
let mut go = false;
|
||||
ui.radio_value(&mut d.new_id.create, false, "Use a Digital ID from a file");
|
||||
@@ -516,7 +516,7 @@ fn configure(ui: &mut egui::Ui, app: &mut PrintCraftApp, t: &Tokens) -> bool {
|
||||
if go {
|
||||
let create = app.sign_draft.as_ref().is_some_and(|d| d.new_id.create);
|
||||
let result = if create { app.create_digital_id() } else { app.import_digital_id() };
|
||||
let d = app.sign_draft.as_mut().expect("checked");
|
||||
let Some(d) = app.sign_draft.as_mut() else { return true };
|
||||
match result {
|
||||
Ok(i) => {
|
||||
d.selected = Some(i);
|
||||
@@ -562,7 +562,7 @@ fn preview(ui: &mut egui::Ui, t: &Tokens, name: &str, d: &SignDraft) {
|
||||
|
||||
fn sign_as(ui: &mut egui::Ui, app: &mut PrintCraftApp, t: &Tokens) -> bool {
|
||||
let ids = app.digital_ids.clone();
|
||||
let d = app.sign_draft.as_mut().expect("checked");
|
||||
let Some(d) = app.sign_draft.as_mut() else { return true };
|
||||
let Some(entry) = d.selected.and_then(|i| ids.get(i)).cloned() else {
|
||||
d.step = SignStep::Choose;
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user