Layers: Duplicate Layer numbers copies in story order and is translated

Duplicate Layer gave the copied stories' tables and anchored objects
their new ids in hash-map order, so the ids differed from run to run.
It now takes the stories in order. The Layers panel's "Duplicate Layer"
menu item has a row in every interface language.
This commit is contained in:
dotCooCoo committed 2026-10-25 15:26:23 -07:00
1 parent 8522253d74
commit d11a050a67
12 files changed
+62 -3

No files matched your search

+34 -3
View File
@@ -1409,17 +1409,21 @@ fn duplicate_layer(s: &mut Session, p: &Value) -> Result<Value> {
d.insert_item(r, copy, None)?;
}
// A story copy holds the same anchored objects as the original: they get ids of their own, and the stories of
// anchored text frames get copies in turn.
// anchored text frames get copies in turn. The story copies are taken in story order.
let mut pending: Vec<StoryId> = stories.values().copied().collect();
pending.sort_unstable();
pending.reverse();
while let Some(new) = pending.pop() {
let Some(mut st) = d.story(new).cloned() else { continue };
renumber_anchored(d, &mut st, new_id, &mut stories, &mut ids, &mut pending);
d.stories.insert(new, Arc::new(st));
}
// Each story copy gets its frames in the original order, text anchors of its own, which its cross-references
// and those of the other copies point at, and tables with ids of their own.
// and those of the other copies point at, and tables with ids of their own, allocated in story order.
let mut anchors = HashMap::new();
for (old, new) in &stories {
let mut order: Vec<(StoryId, StoryId)> = stories.iter().map(|(old, new)| (*old, *new)).collect();
order.sort_unstable_by_key(|(_, new)| *new);
for (old, new) in &order {
let Some(mut st) = d.story(*new).cloned() else { continue };
st.frames = src.story(*old).map(|o| o.frames.iter().filter_map(|f| ids.get(f).copied()).collect()).unwrap_or_default();
for (k, a) in (1..).zip(st.anchors.iter_mut()) {
@@ -2116,6 +2120,33 @@ mod layer_tests {
assert_eq!(d.story(copied).unwrap().tables[&new].nrows(), 2, "a command naming the copy's table changes the copy");
}
#[test]
fn duplicate_layer_numbers_copied_tables_and_anchored_objects_in_story_order() {
let mut s = crate::Session::new();
s.execute("file.new", &json!({})).unwrap();
let base = s.doc().unwrap().doc.layers[0].id.0;
for k in 0..8 {
let y = 72 + 80 * k;
let text = json!({"rect": [72, y, 400, y + 60], "content": "text", "text": "Rows"});
let f = s.execute("frame.create", &text).unwrap()["id"].as_u64().unwrap();
let sid = s.doc().unwrap().doc.item(designcraft_doc::ItemId(f)).unwrap().text_frame().unwrap().story;
s.execute("text.select", &json!({"story": sid.0, "anchor": 4, "focus": 4})).unwrap();
s.execute("table.insert", &json!({"rows": 1, "cols": 2})).unwrap();
let b = s.execute("frame.create", &json!({"rect": [450, y, 474, y + 40]})).unwrap()["id"].as_u64().unwrap();
s.execute("anchored.insert", &json!({"ids": [b], "story": sid.0, "pos": 0})).unwrap();
}
let copy = s.execute("layer.duplicate", &json!({"id": base})).unwrap()["id"].as_u64().unwrap();
let d = &s.doc().unwrap().doc;
d.check().unwrap();
let mut stories: Vec<_> = on_layer(d, copy).iter().filter_map(|i| i.text_frame().map(|t| t.story)).collect();
stories.sort_unstable();
assert_eq!(stories.len(), 8);
let tables: Vec<u64> = stories.iter().map(|sid| *d.story(*sid).unwrap().tables.keys().next().unwrap()).collect();
let objects: Vec<u64> = stories.iter().map(|sid| d.story(*sid).unwrap().objects[0].item.id.0).collect();
assert!(tables.is_sorted(), "the copied tables take ids in story order: {tables:?}");
assert!(objects.is_sorted(), "the copied anchored objects take ids in story order: {objects:?}");
}
/// 40 stories, each holding two anchored text frames threaded in the next: 2^40 paths, 40 stories.
#[test]
fn duplicate_layer_checks_each_shared_story_once() {
+18
View File
@@ -4562,6 +4562,10 @@ const TABLE: &[(&str, [&str; 5])] = &[
"“{name}”的图层选项…",
],
),
(
"Duplicate Layer \"{name}\"",
["Ebene „{name}“ duplizieren", "Dupliquer le calque « {name} »", "Duplicar capa «{name}»", "レイヤー「{name}」を複製", "复制图层“{name}”"],
),
("Swatch Name:", ["Farbfeldname:", "Nom de la nuance :", "Nombre de muestra:", "スウォッチ名:", "色板名称:"]),
(
"Name with Color Value",
@@ -4968,6 +4972,20 @@ mod tests {
}
}
#[test]
fn duplicate_layer_menu_item_is_localized_in_every_interface_language() {
let key = "Duplicate Layer \"{name}\"";
for (code, name) in LANGUAGES {
let label = tr(code, key);
if code.is_empty() {
assert_eq!(label, key, "{name}");
} else {
assert_ne!(label, key, "{name} shows the Layers panel's Duplicate Layer item in English");
assert!(label.contains("{name}"), "{name}");
}
}
}
#[test]
fn context_captions_come_from_their_context_or_the_plain_translation() {
for (lang, table) in CONTEXT_TABLES {
+1
View File
@@ -1813,6 +1813,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Color Group Options…", "خيارات مجموعة الألوان…"),
("Layer Options", "خيارات الطبقة"),
("Layer Options for \"{name}\"…", "خيارات الطبقة \"{name}\"…"),
("Duplicate Layer \"{name}\"", "تكرار الطبقة \"{name}\""),
("Swatch Name:", "اسم حامل اللون:"),
("Name with Color Value", "التسمية بقيمة اللون"),
("Color Type:", "نوع اللون:"),
+1
View File
@@ -210,6 +210,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Delete Column", "Odstranit sloupec"),
("Delete Condition", "Odstranit podmínku"),
("Delete Layer", "Odstranit vrstvu"),
("Duplicate Layer \"{name}\"", "Duplikovat vrstvu „{name}“"),
("Delete Page", "Odstranit stránku"),
("Delete Row", "Odstranit řádek"),
("Delete Swatch", "Odstranit vzorek"),
+1
View File
@@ -2040,6 +2040,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Color Group Options…", "Farbgruppenoptionen …"),
("Layer Options", "Ebenenoptionen"),
("Layer Options for \"{name}\"…", "Ebenenoptionen für „{name}“ …"),
("Duplicate Layer \"{name}\"", "Ebene „{name}“ duplizieren"),
("Swatch Name:", "Farbfeldname:"),
("Name with Color Value", "Name mit Farbwert"),
("Color Type:", "Farbtyp:"),
+1
View File
@@ -1865,6 +1865,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Color Group Options…", "Opzioni gruppo colori…"),
("Layer Options", "Opzioni livello"),
("Layer Options for \"{name}\"…", "Opzioni livello \"{name}\"…"),
("Duplicate Layer \"{name}\"", "Duplica livello \"{name}\""),
("Swatch Name:", "Nome campione:"),
("Name with Color Value", "Nome con valore colore"),
("Color Type:", "Tipo colore:"),
+1
View File
@@ -2123,6 +2123,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Color Group Options…", "カラーグループオプション…"),
("Layer Options", "レイヤーオプション"),
("Layer Options for \"{name}\"…", "「{name}」のレイヤーオプション…"),
("Duplicate Layer \"{name}\"", "レイヤー「{name}」を複製"),
("Swatch Name:", "スウォッチ名:"),
("Name with Color Value", "カラー値を名前にする"),
("Color Type:", "カラータイプ:"),
+1
View File
@@ -2155,6 +2155,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Last commit", "ბოლო კომიტი"),
("Layer Options", "შრის პარამეტრები"),
("Layer Options for \"{name}\"…", "შრის პარამეტრები: „{name}“…"),
("Duplicate Layer \"{name}\"", "შრის დუბლირება: „{name}“"),
("Leader:", "მიმთითებელი:"),
("Leading Model:", "სტრიქონთაშორისი ინტერვალის მოდელი:"),
("Left-aligned tab", "მარცხნივ სწორებული ტაბულაცია"),
+1
View File
@@ -211,6 +211,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Delete Column", "Usuń kolumnę"),
("Delete Condition", "Usuń warunek"),
("Delete Layer", "Usuń warstwę"),
("Duplicate Layer \"{name}\"", "Powiel warstwę „{name}”"),
("Delete Page", "Usuń stronę"),
("Delete Row", "Usuń wiersz"),
("Delete Swatch", "Usuń próbkę"),
+1
View File
@@ -1417,6 +1417,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Color Group Options…", "Opções do grupo de cores…"),
("Layer Options", "Opções de camada"),
("Layer Options for \"{name}\"…", "Opções da camada \"{name}\"…"),
("Duplicate Layer \"{name}\"", "Duplicar camada \"{name}\""),
("Swatch Name:", "Nome da amostra:"),
("Name with Color Value", "Nome com valor da cor"),
("Color Type:", "Tipo de cor:"),
+1
View File
@@ -210,6 +210,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Delete Column", "Удалить столбец"),
("Delete Condition", "Удалить условие"),
("Delete Layer", "Удалить слой"),
("Duplicate Layer \"{name}\"", "Дублировать слой «{name}»"),
("Delete Page", "Удалить страницу"),
("Delete Row", "Удалить строку"),
("Delete Swatch", "Удалить образец"),
+1
View File
@@ -1833,6 +1833,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Color Group Options…", "Параметри групи кольорів…"),
("Layer Options", "Параметри шару"),
("Layer Options for \"{name}\"…", "Параметри шару «{name}»…"),
("Duplicate Layer \"{name}\"", "Дублювати шар «{name}»"),
("Swatch Name:", "Назва зразка:"),
("Name with Color Value", "Назва за значенням кольору"),
("Color Type:", "Тип кольору:"),