Make documents fall back to "Untitled Document" (+ number suffix) if given a blank name (#4074)

* Make documents fall back to "Untitled Document" (+ number suffix) if given a blank name

* Bug fix
This commit is contained in:
Keavon Chambers 2026-04-28 18:37:13 -07:00 committed by GitHub
parent b152f46380
commit d44b0cd308
6 changed files with 43 additions and 20 deletions

View File

@ -126,7 +126,7 @@ impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHa
DialogMessage::RequestNewDocumentDialog => {
self.on_dismiss = Some(DialogMessage::Close.into());
self.new_document_dialog = NewDocumentDialogMessageHandler {
name: portfolio.generate_new_document_name(),
name: portfolio.generate_new_document_name(None),
infinite: false,
dimensions: glam::UVec2::new(1920, 1080),
};

View File

@ -784,7 +784,15 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(EventMessage::SelectionChanged);
}
DocumentMessage::RenameDocument { new_name } => {
self.name = new_name.clone();
let new_name = new_name.trim().to_string();
// No-op when the resolved name is unchangedL committing the rename field without edits (or with
// only whitespace edits) shouldn't dissociate the document from its file on disk or mark it unsaved.
if new_name == self.name {
return;
}
self.name = new_name;
self.path = None;
self.set_save_state(false);

View File

@ -2475,7 +2475,7 @@ impl NodeGraphMessageHandler {
Separator::new(SeparatorStyle::Related).widget_instance(),
TextInput::new(context.document_name)
.tooltip_description("Name of the current document.")
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
.on_update(|text_input| PortfolioMessage::RenameDocument { new_name: text_input.value.clone() }.into())
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
])];

View File

@ -168,6 +168,9 @@ pub enum PortfolioMessage {
SelectDocument {
document_id: DocumentId,
},
RenameDocument {
new_name: String,
},
SubmitDocumentExport {
name: String,
file_type: FileType,

View File

@ -498,7 +498,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
PortfolioMessage::NewDocumentWithName { name } => {
let mut new_document = DocumentMessageHandler::default();
new_document.name = name;
new_document.name = self.resolve_document_name(name, None);
responses.add(DocumentMessage::PTZUpdate);
@ -799,28 +799,24 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
});
match (document_name, document_path, document_name_from_path) {
(Some(name), _, None) => {
document.name = name;
}
let candidate_name = match (document_name, document_path, document_name_from_path) {
(Some(name), _, None) => name,
(_, Some(path), Some(name)) => {
document.name = name;
document.path = Some(path);
name
}
(_, _, Some(name)) => {
document.name = name;
}
_ => {
document.name = DEFAULT_DOCUMENT_NAME.to_string();
}
}
(_, _, Some(name)) => name,
_ => String::new(),
};
document.name = self.resolve_document_name(candidate_name, None);
// Load the document into the portfolio so it opens in the editor
self.load_document(document, document_id, responses);
}
PortfolioMessage::OpenImage { name, image } => {
// `NewDocumentWithName`'s handler routes empty/None-equivalent names through `resolve_document_name` which assigns the next available "Untitled Document {N}".
responses.add(PortfolioMessage::NewDocumentWithName {
name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),
name: name.clone().unwrap_or_default(),
});
responses.add(DocumentMessage::PasteImage {
@ -847,7 +843,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
PortfolioMessage::OpenSvg { name, svg } => {
responses.add(PortfolioMessage::NewDocumentWithName {
name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),
name: name.clone().unwrap_or_default(),
});
// Parse the SVG to extract its declared canvas origin and dimensions from the viewBox attribute.
@ -1359,6 +1355,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
self.refresh_panel_content(target_active, responses);
}
}
PortfolioMessage::RenameDocument { new_name } => {
let resolved_name = self.resolve_document_name(new_name, self.active_document_id);
responses.add(DocumentMessage::RenameDocument { new_name: resolved_name });
}
PortfolioMessage::SelectDocument { document_id } => {
// Auto-save the document we are leaving
let mut node_graph_open = false;
@ -1744,10 +1744,22 @@ impl PortfolioMessageHandler {
}
}
pub fn generate_new_document_name(&self) -> String {
/// Resolves a proposed document name: if it's empty or only whitespace, falls back to the next
/// available "Untitled Document {N}" via [`Self::generate_new_document_name`]. Otherwise trims surrounding
/// whitespace and returns it. `exclude` is forwarded so a renaming document can skip its own current
/// name when computing the fallback (preventing self-collision).
pub fn resolve_document_name(&self, name: String, exclude: Option<DocumentId>) -> String {
let trimmed = name.trim();
if trimmed.is_empty() { self.generate_new_document_name(exclude) } else { trimmed.to_string() }
}
/// `exclude` lets a renaming caller skip its own current name so a document can rename back to its
/// existing slot rather than colliding with itself and getting bumped to the next number.
pub fn generate_new_document_name(&self, exclude: Option<DocumentId>) -> String {
let mut doc_title_numbers = self
.document_ids
.iter()
.filter(|id| exclude != Some(**id))
.filter_map(|id| self.document_details(*id))
.filter_map(|doc| {
doc.name

View File

@ -410,7 +410,7 @@ impl EditorWrapper {
/// Rename the currently active document.
#[wasm_bindgen(js_name = renameDocument)]
pub fn rename_document(&self, new_name: String) {
let message = DocumentMessage::RenameDocument { new_name };
let message = PortfolioMessage::RenameDocument { new_name };
self.dispatch(message);
}