Confirm layer deletion only from the Layers trash button (#2197)

* fix(ui): confirm deletion only from the Layers trash button

* Trash confirmation: Alt-click deletes without asking (Photoshop); drop docs page and screenshot

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: kotulsky <7805437+kotulsky@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kotulsky 2026-10-10 08:10:24 +02:00 committed by GitHub
parent 2c15f13f02
commit 26cc6ed6bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 382 additions and 1 deletions

View File

@ -0,0 +1,177 @@
//! Confirmation for a Layers-panel trash-button click; other deletion routes stay immediate.
use crate::{PhotocraftApp, state::DialogKind};
use serde_json::{Map, Value, json};
const MARK: &str = "__deleteLayer";
const PREF: &str = "layer.delete.confirmation";
pub fn owns(fields: &Map<String, Value>) -> bool {
fields.contains_key(MARK)
}
pub fn intercept(app: &mut PhotocraftApp, command: &str, params: &Value) -> Option<Result<Value, String>> {
if app.automation_input
|| command != "layer.delete"
|| params.get("__trash").and_then(Value::as_bool) != Some(true)
|| app.session.prefs().dialogs.get(PREF).and_then(|v| v.get("skip")).and_then(Value::as_bool) == Some(true)
{
return None;
}
let state = app.session.active()?;
let selected = state.selected_layers();
let ids = if let Some(id) = params.get("layer").and_then(Value::as_u64) { vec![photocraft_doc::LayerId(id)] } else { selected.clone() };
if ids.is_empty() {
return None;
}
let names = ids.iter().filter_map(|id| state.doc.layer(*id).map(|layer| layer.name.as_str())).take(4).collect::<Vec<_>>().join(", ");
let message = if ids.len() == 1 {
crate::i18n::fmt(tl!("Delete layer ‘{name}’?"), &[("name", &names)])
} else {
crate::i18n::fmt(tl!("Delete {count} layers? {names}"), &[("count", &ids.len().to_string()), ("names", &names)])
};
let fields = json!({MARK: true, "__command": command, "__label": "Delete Layer", "message": message,
"document": state.doc.id.0, "selected": selected.iter().map(|id| id.0).collect::<Vec<_>>(), "params": params, "skip": false});
if app.ui.dialogs.iter().any(|d| owns(&d.fields)) {
return Some(Ok(json!({"confirmationPending": true})));
}
let id = app.ui.open_dialog(DialogKind::Command, fields.as_object().cloned().unwrap_or_default());
Some(Ok(json!({"dialog": id, "confirmationPending": true})))
}
pub fn body(ui: &mut egui::Ui, fields: &mut Map<String, Value>) {
ui.label(fields.get("message").and_then(Value::as_str).unwrap_or(tl!("Delete layer?")));
ui.add_space(8.0);
let mut skip = fields.get("skip").and_then(Value::as_bool).unwrap_or(false);
ui.checkbox(&mut skip, tl!("Don't show again"));
fields.insert("skip".into(), json!(skip));
}
pub fn confirm(app: &mut PhotocraftApp, fields: &Map<String, Value>) -> Result<Value, String> {
let state = app.session.active().ok_or(tl!("The document is closed"))?;
if fields.get("document").and_then(Value::as_u64) != Some(state.doc.id.0)
|| fields.get("selected") != Some(&json!(state.selected_layers().iter().map(|id| id.0).collect::<Vec<_>>()))
{
return Err(tl!("The document or selected layers changed. Please request deletion again.").into());
}
let command = fields.get("__command").and_then(Value::as_str).ok_or(tl!("Missing delete command"))?;
if command != "layer.delete" {
return Err(tl!("Invalid delete command").into());
}
let result = app.run_command(command, fields.get("params").cloned().unwrap_or(Value::Null))?;
if fields.get("skip").and_then(Value::as_bool) == Some(true) {
app.session.edit_prefs(|prefs| {
prefs.dialogs.insert(PREF.into(), json!({"skip": true}));
});
if let Err(error) = crate::prefs_ui::save_preferences(app) {
app.ui.status = crate::i18n::fmt(tl!("Layer deleted, but confirmation preference could not be saved: {error}"), &[("error", &error)]);
app.ui.status_error = true;
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use egui_kittest::kittest::Queryable;
fn make_app() -> PhotocraftApp {
let mut s = photocraft_engine::Session::new();
s.execute("file.new", json!({"width":8,"height":8})).unwrap();
s.execute("layer.new.layer", json!({"name":"Ink"})).unwrap();
PhotocraftApp::new(s, crate::Services::default())
}
#[test]
fn cancel_keeps_layer_and_confirmation_preference() {
let mut app = make_app();
let id = app.session.active().unwrap().active_layer.unwrap();
let result = app.run("layer.delete", json!({"__trash":true})).unwrap();
let dialog = result["dialog"].as_u64().unwrap();
assert!(owns(&app.ui.dialogs[0].fields));
assert!(app.ui.dialogs[0].fields["message"].as_str().unwrap().contains("Ink"));
app.ui.dialog_mut(dialog).unwrap().fields.insert("skip".into(), json!(true));
app.ui.close_dialog(dialog);
assert!(app.session.active().unwrap().doc.layer(id).is_some());
assert!(!app.session.prefs().dialogs.contains_key(PREF));
}
#[test]
fn confirmation_deletes_once_and_remembers_across_restart() {
use std::{cell::RefCell, rc::Rc};
let mut app = make_app();
let saved = Rc::new(RefCell::new(String::new()));
let copy = saved.clone();
app.services.save_prefs = Some(Box::new(move |text| {
*copy.borrow_mut() = text.to_string();
Ok(())
}));
let target = app.session.active().unwrap().active_layer.unwrap();
let before = app.session.active().unwrap().history.past_len();
let dialog = app.run("layer.delete", json!({"__trash":true})).unwrap()["dialog"].as_u64().unwrap();
app.ui.dialog_mut(dialog).unwrap().fields.insert("skip".into(), json!(true));
crate::dialogs::confirm(&mut app, dialog).unwrap();
assert!(app.session.active().unwrap().doc.layer(target).is_none());
assert_eq!(app.session.active().unwrap().history.past_len(), before + 1);
assert!(app.ui.dialogs.is_empty());
app.session.undo();
assert!(app.session.active().unwrap().doc.layer(target).is_some());
let text = saved.borrow().clone();
assert!(text.contains(PREF));
let mut session = photocraft_engine::Session::new();
session.execute("file.new", json!({"width":8,"height":8})).unwrap();
session.execute("layer.new.layer", json!({"name":"Fresh"})).unwrap();
let mut fresh = PhotocraftApp::new(session, crate::Services { load_prefs: Some(Box::new(move || Some(text.clone()))), ..Default::default() });
fresh.run("layer.delete", json!({"__trash":true})).unwrap();
assert!(fresh.ui.dialogs.is_empty());
}
#[test]
fn changed_document_refuses_confirmation_and_clear_does_not_prompt() {
let mut app = make_app();
let dialog = app.run("layer.delete", json!({"__trash":true})).unwrap()["dialog"].as_u64().unwrap();
app.session.execute("file.new", json!({"width":8,"height":8})).unwrap();
assert!(crate::dialogs::confirm(&mut app, dialog).is_err());
assert_eq!(app.session.documents()[0].doc.layer_count(), 2);
app.run("edit.clear", json!({})).unwrap();
assert!(app.ui.dialogs.is_empty());
}
#[test]
fn only_trash_click_prompts_and_default_delete_binding_is_unchanged() {
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).with_max_steps(64).build_eframe(|cc| {
PhotocraftApp::setup_context(&cc.egui_ctx, Default::default());
make_app()
});
h.run_steps(4);
let id = h.state().session.active().unwrap().active_layer.unwrap();
h.get_by_label("Delete layer").click();
h.run_steps(4);
assert!(h.state().ui.dialogs.iter().any(|d| owns(&d.fields)));
assert!(h.state().session.active().unwrap().doc.layer(id).is_some());
let dialog = h.state().ui.dialogs[0].id;
h.state_mut().ui.close_dialog(dialog);
h.state_mut().run("layer.delete", json!({})).unwrap();
assert!(h.state().ui.dialogs.is_empty());
assert!(h.state().session.active().unwrap().doc.layer(id).is_none());
let clear = photocraft_engine::commands::find("edit.clear").unwrap();
let delete = photocraft_engine::commands::find("layer.delete").unwrap();
assert_eq!(clear.shortcut, Some("Delete"));
assert_eq!(delete.shortcut, None);
}
#[test]
fn confirmation_strings_are_translated_in_every_supported_catalog() {
for language in crate::i18n::LANGUAGES.iter().filter(|l| l.code != "en") {
let lang = crate::i18n::Lang::from_code(language.code).unwrap();
for key in [
"Delete layer ‘{name}’?",
"Delete {count} layers? {names}",
"Delete layer?",
"Don't show again",
"The document is closed",
"The document or selected layers changed. Please request deletion again.",
"Missing delete command",
"Invalid delete command",
"Layer deleted, but confirmation preference could not be saved: {error}",
] {
assert_ne!(crate::i18n::tr(lang, key), key, "{}: {key}", language.code);
}
}
}
}

View File

@ -190,6 +190,7 @@ pub fn show(app: &mut PhotocraftApp, ctx: &egui::Context) {
DialogKind::Command if crate::fill_ui::owns(&fields) => crate::fill_ui::body(app, ui, &mut fields),
DialogKind::Command if crate::stroke_ui::owns(&fields) => crate::stroke_ui::body(ui, &mut fields),
DialogKind::Command if crate::shape_dialog::owns(&fields) => crate::shape_dialog::body(app, ui, &mut fields),
DialogKind::Command if crate::delete_layer_prompt::owns(&fields) => crate::delete_layer_prompt::body(ui, &mut fields),
DialogKind::Command if crate::rasterize_prompt::owns(&fields) => crate::rasterize_prompt::body(ui, &fields),
DialogKind::Command if crate::variables_ui::owns(&fields) => crate::variables_ui::body(app, ui, &mut fields),
DialogKind::Command if crate::file_ui::owns(&fields) => crate::file_ui::body(app, ui, &mut fields),
@ -245,6 +246,8 @@ pub fn show(app: &mut PhotocraftApp, ctx: &egui::Context) {
} else {
let ok_label = if d.kind == DialogKind::NewDocument {
tl!("Create")
} else if crate::delete_layer_prompt::owns(&d.fields) {
tl!("Delete")
} else if d.fields.contains_key("__export") {
tl!("Export")
} else {
@ -407,6 +410,7 @@ pub fn confirm(app: &mut PhotocraftApp, id: u64) -> Result<Value, String> {
DialogKind::Command if crate::fill_ui::owns(&d.fields) => crate::fill_ui::confirm(app, &d.fields),
DialogKind::Command if crate::stroke_ui::owns(&d.fields) => crate::stroke_ui::confirm(app, &d.fields),
DialogKind::Command if crate::shape_dialog::owns(&d.fields) => crate::shape_dialog::confirm(app, &d.fields),
DialogKind::Command if crate::delete_layer_prompt::owns(&d.fields) => crate::delete_layer_prompt::confirm(app, &d.fields),
DialogKind::Command if crate::rasterize_prompt::owns(&d.fields) => crate::rasterize_prompt::confirm(app, &d.fields),
DialogKind::Command if crate::variables_ui::owns(&d.fields) => crate::variables_ui::confirm(app, &d.fields),
DialogKind::Command if crate::file_ui::owns(&d.fields) => crate::file_ui::confirm(app, &d.fields),

View File

@ -963,6 +963,17 @@
Delete current channel Odstranit aktuální kanál
Delete current state Odstranit aktuální stav
Delete effect layer Odstranit vrstvu efektu
# Layers-panel trash confirmation
Delete layer ‘{name}’? Smazat vrstvu „{name}“?
Delete {count} layers? {names} Smazat vrstvy ({count})? {names}
Delete layer? Smazat vrstvu?
The document is closed Dokument je zavřený
The document or selected layers changed. Please request deletion again. Dokument nebo vybrané vrstvy se změnily. Požádejte o smazání znovu.
Missing delete command Chybí příkaz ke smazání
Invalid delete command Neplatný příkaz ke smazání
Layer deleted, but confirmation preference could not be saved: {error} Vrstva byla smazána, ale nastavení potvrzení se nepodařilo uložit: {error}
Delete layer Odstranit vrstvu
Delete layer comp Odstranit kompozici vrstev
Delete note Odstranit poznámku

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -947,6 +947,17 @@
Delete current channel Aktuellen Kanal löschen
Delete current state Aktuellen Status löschen
Delete effect layer Effektebene löschen
# Layers-panel trash confirmation
Delete layer ‘{name}’? Ebene „{name}“ löschen?
Delete {count} layers? {names} {count} Ebenen löschen? {names}
Delete layer? Ebene löschen?
The document is closed Das Dokument ist geschlossen
The document or selected layers changed. Please request deletion again. Das Dokument oder die ausgewählten Ebenen haben sich geändert. Bitte die Löschung erneut anfordern.
Missing delete command Löschbefehl fehlt
Invalid delete command Ungültiger Löschbefehl
Layer deleted, but confirmation preference could not be saved: {error} Die Ebene wurde gelöscht, aber die Bestätigungseinstellung konnte nicht gespeichert werden: {error}
Delete layer Ebene löschen
Delete layer comp Ebenenkomposition löschen
Delete note Notiz löschen

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -2188,6 +2188,17 @@
Delete Pins Διαγραφή καρφιτσών
Delete Workspace Διαγραφή χώρου εργασίας
Delete Workspace… Διαγραφή χώρου εργασίας…
# Layers-panel trash confirmation
Delete layer ‘{name}’? Διαγραφή του επιπέδου «{name}»;
Delete {count} layers? {names} Διαγραφή {count} επιπέδων; {names}
Delete layer? Διαγραφή επιπέδου;
The document is closed Το έγγραφο είναι κλειστό
The document or selected layers changed. Please request deletion again. Το έγγραφο ή τα επιλεγμένα επίπεδα άλλαξαν. Ζητήστε ξανά τη διαγραφή.
Missing delete command Λείπει η εντολή διαγραφής
Invalid delete command Μη έγκυρη εντολή διαγραφής
Layer deleted, but confirmation preference could not be saved: {error} Το επίπεδο διαγράφηκε, αλλά δεν ήταν δυνατή η αποθήκευση της προτίμησης επιβεβαίωσης: {error}
Delete layer Διαγραφή επιπέδου
Density: Πυκνότητα:
Depth Βάθος

Can't render this file because it has a wrong number of fields in line 5.

View File

@ -948,6 +948,17 @@
Delete current channel Eliminar el canal actual
Delete current state Eliminar el estado actual
Delete effect layer Eliminar capa de efecto
# Layers-panel trash confirmation
Delete layer ‘{name}’? ¿Eliminar la capa «{name}»?
Delete {count} layers? {names} ¿Eliminar {count} capas? {names}
Delete layer? ¿Eliminar capa?
The document is closed El documento está cerrado
The document or selected layers changed. Please request deletion again. El documento o las capas seleccionadas han cambiado. Solicita la eliminación de nuevo.
Missing delete command Falta el comando de eliminación
Invalid delete command Comando de eliminación no válido
Layer deleted, but confirmation preference could not be saved: {error} La capa se eliminó, pero no se pudo guardar la preferencia de confirmación: {error}
Delete layer Eliminar capa
Delete layer comp Eliminar composición de capa
Delete note Eliminar nota

Can't render this file because it contains an unexpected character in line 4 and column 47.

View File

@ -938,6 +938,17 @@
Delete current channel Supprimer le canal actif
Delete current state Supprimer l'état actuel
Delete effect layer Supprimer le calque d'effet
# Layers-panel trash confirmation
Delete layer ‘{name}’? Supprimer le calque « {name} » ?
Delete {count} layers? {names} Supprimer {count} calques ? {names}
Delete layer? Supprimer le calque ?
The document is closed Le document est fermé
The document or selected layers changed. Please request deletion again. Le document ou les calques sélectionnés ont changé. Demandez à nouveau la suppression.
Missing delete command Commande de suppression manquante
Invalid delete command Commande de suppression non valide
Layer deleted, but confirmation preference could not be saved: {error} Le calque a été supprimé, mais la préférence de confirmation n’a pas pu être enregistrée : {error}
Delete layer Supprimer le calque
Delete layer comp Supprimer la composition de calques
Delete note Supprimer la note

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -932,6 +932,17 @@
Delete current channel Hapus channel saat ini
Delete current state Hapus kondisi saat ini
Delete effect layer Hapus layer efek
# Layers-panel trash confirmation
Delete layer ‘{name}’? Hapus layer ‘{name}’?
Delete {count} layers? {names} Hapus {count} layer? {names}
Delete layer? Hapus layer?
The document is closed Dokumen sudah ditutup
The document or selected layers changed. Please request deletion again. Dokumen atau layer yang dipilih telah berubah. Minta penghapusan lagi.
Missing delete command Perintah hapus tidak tersedia
Invalid delete command Perintah hapus tidak valid
Layer deleted, but confirmation preference could not be saved: {error} Layer dihapus, tetapi preferensi konfirmasi tidak dapat disimpan: {error}
Delete layer Hapus layer
Delete layer comp Hapus layer comp
Delete note Hapus catatan

Can't render this file because it contains an unexpected character in line 4 and column 47.

View File

@ -949,6 +949,17 @@
Delete current channel Elimina canale corrente
Delete current state Elimina stato corrente
Delete effect layer Elimina livello effetto
# Layers-panel trash confirmation
Delete layer ‘{name}’? Eliminare il livello «{name}»?
Delete {count} layers? {names} Eliminare {count} livelli? {names}
Delete layer? Eliminare il livello?
The document is closed Il documento è chiuso
The document or selected layers changed. Please request deletion again. Il documento o i livelli selezionati sono cambiati. Richiedi nuovamente l’eliminazione.
Missing delete command Comando di eliminazione mancante
Invalid delete command Comando di eliminazione non valido
Layer deleted, but confirmation preference could not be saved: {error} Il livello è stato eliminato, ma non è stato possibile salvare la preferenza di conferma: {error}
Delete layer Elimina livello
Delete layer comp Elimina composizione livelli
Delete note Elimina nota

Can't render this file because it has a wrong number of fields in line 14.

View File

@ -953,6 +953,17 @@
Delete current channel 現在のチャンネルを削除
Delete current state 現在の状態を削除
Delete effect layer 効果レイヤーを削除
# Layers-panel trash confirmation
Delete layer ‘{name}’? レイヤー「{name}」を削除しますか?
Delete {count} layers? {names} {count} 個のレイヤーを削除しますか? {names}
Delete layer? レイヤーを削除しますか?
The document is closed ドキュメントは閉じられています
The document or selected layers changed. Please request deletion again. ドキュメントまたは選択したレイヤーが変更されました。もう一度削除してください。
Missing delete command 削除コマンドがありません
Invalid delete command 無効な削除コマンドです
Layer deleted, but confirmation preference could not be saved: {error} レイヤーは削除されましたが、確認設定を保存できませんでした: {error}
Delete layer レイヤーを削除
Delete layer comp レイヤーカンプを削除
Delete note 注釈を削除

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -930,6 +930,17 @@
Delete current channel 현재 채널 삭제
Delete current state 현재 상태 삭제
Delete effect layer 효과 레이어 삭제
# Layers-panel trash confirmation
Delete layer ‘{name}’? 레이어 ‘{name}’을(를) 삭제하시겠습니까?
Delete {count} layers? {names} 레이어 {count}개를 삭제하시겠습니까? {names}
Delete layer? 레이어를 삭제하시겠습니까?
The document is closed 문서가 닫혔습니다
The document or selected layers changed. Please request deletion again. 문서 또는 선택한 레이어가 변경되었습니다. 삭제를 다시 요청하세요.
Missing delete command 삭제 명령이 없습니다
Invalid delete command 잘못된 삭제 명령입니다
Layer deleted, but confirmation preference could not be saved: {error} 레이어가 삭제되었지만 확인 설정을 저장할 수 없습니다: {error}
Delete layer 레이어 삭제
Delete layer comp 레이어 구성 요소 삭제
Delete note 메모 삭제

Can't render this file because it has a wrong number of fields in line 10.

View File

@ -941,6 +941,17 @@
Delete current channel Huidig kanaal verwijderen
Delete current state Huidige staat verwijderen
Delete effect layer Effectlaag verwijderen
# Layers-panel trash confirmation
Delete layer ‘{name}’? Laag ‘{name}’ verwijderen?
Delete {count} layers? {names} {count} lagen verwijderen? {names}
Delete layer? Laag verwijderen?
The document is closed Het document is gesloten
The document or selected layers changed. Please request deletion again. Het document of de geselecteerde lagen zijn gewijzigd. Vraag de verwijdering opnieuw aan.
Missing delete command Verwijderopdracht ontbreekt
Invalid delete command Ongeldige verwijderopdracht
Layer deleted, but confirmation preference could not be saved: {error} De laag is verwijderd, maar de bevestigingsvoorkeur kon niet worden opgeslagen: {error}
Delete layer Laag verwijderen
Delete layer comp Laagcompositie verwijderen
Delete note Notitie verwijderen

Can't render this file because it contains an unexpected character in line 8 and column 34.

View File

@ -952,6 +952,17 @@
Delete current channel Usuń bieżący kanał
Delete current state Usuń bieżący stan
Delete effect layer Usuń warstwę efektu
# Layers-panel trash confirmation
Delete layer ‘{name}’? Usunąć warstwę „{name}”?
Delete {count} layers? {names} Usunąć warstwy ({count})? {names}
Delete layer? Usunąć warstwę?
The document is closed Dokument jest zamknięty
The document or selected layers changed. Please request deletion again. Dokument lub zaznaczone warstwy uległy zmianie. Ponownie zażądaj usunięcia.
Missing delete command Brak polecenia usuwania
Invalid delete command Nieprawidłowe polecenie usuwania
Layer deleted, but confirmation preference could not be saved: {error} Warstwa została usunięta, ale nie udało się zapisać ustawienia potwierdzenia: {error}
Delete layer Usuń warstwę
Delete layer comp Usuń kompozycję warstw
Delete note Usuń notatkę

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -937,6 +937,17 @@
Delete current channel Excluir o canal atual
Delete current state Excluir o estado atual
Delete effect layer Excluir camada de efeito
# Layers-panel trash confirmation
Delete layer ‘{name}’? Excluir a camada “{name}”?
Delete {count} layers? {names} Excluir {count} camadas? {names}
Delete layer? Excluir camada?
The document is closed O documento está fechado
The document or selected layers changed. Please request deletion again. O documento ou as camadas selecionadas mudaram. Solicite a exclusão novamente.
Missing delete command Comando de exclusão ausente
Invalid delete command Comando de exclusão inválido
Layer deleted, but confirmation preference could not be saved: {error} A camada foi excluída, mas não foi possível salvar a preferência de confirmação: {error}
Delete layer Excluir camada
Delete layer comp Excluir composição de camadas
Delete note Excluir anotação

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -2644,3 +2644,13 @@ fontWeight Italic Курсив
Quick Selection found nothing there Быстрое выделение ничего здесь не нашло
Object Selection found nothing there — try a tighter rectangle Выделение объектов ничего здесь не нашло — попробуйте более узкий прямоугольник
No subject found — try a rectangle around the object Объект не найден — попробуйте прямоугольник вокруг объекта
# Layers-panel trash confirmation
Delete layer ‘{name}’? Удалить слой «{name}»?
Delete {count} layers? {names} Удалить слои ({count})? {names}
Delete layer? Удалить слой?
The document is closed Документ закрыт
The document or selected layers changed. Please request deletion again. Документ или выбранные слои изменились. Повторите запрос на удаление.
Missing delete command Отсутствует команда удаления
Invalid delete command Недопустимая команда удаления
Layer deleted, but confirmation preference could not be saved: {error} Слой удалён, но не удалось сохранить настройку подтверждения: {error}

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -2575,6 +2575,17 @@ fontWeight Italic Курсивний
Create new snapshot Створити новий знімок стану
Delete Cropped Pixels Видаляти обрізані пікселі
Delete current state Видалити поточний стан
# Layers-panel trash confirmation
Delete layer ‘{name}’? Видалити шар «{name}»?
Delete {count} layers? {names} Видалити шари ({count})? {names}
Delete layer? Видалити шар?
The document is closed Документ закрито
The document or selected layers changed. Please request deletion again. Документ або вибрані шари змінилися. Повторіть запит на видалення.
Missing delete command Відсутня команда видалення
Invalid delete command Недійсна команда видалення
Layer deleted, but confirmation preference could not be saved: {error} Шар видалено, але не вдалося зберегти налаштування підтвердження: {error}
Delete layer Видалити шар
Diamond Gradient Ромбічний градієнт
Enable airbrush-style build-up effects Увімкнути ефекти накопичення як в аерографі

Can't render this file because it has a wrong number of fields in line 12.

View File

@ -953,6 +953,17 @@
Delete current channel 删除当前通道
Delete current state 删除当前状态
Delete effect layer 删除效果图层
# Layers-panel trash confirmation
Delete layer ‘{name}’? 删除图层“{name}”?
Delete {count} layers? {names} 删除 {count} 个图层?{names}
Delete layer? 删除图层?
The document is closed 文档已关闭
The document or selected layers changed. Please request deletion again. 文档或所选图层已更改。请重新请求删除。
Missing delete command 缺少删除命令
Invalid delete command 无效的删除命令
Layer deleted, but confirmation preference could not be saved: {error} 图层已删除,但无法保存确认设置:{error}
Delete layer 删除图层
Delete layer comp 删除图层复合
Delete note 删除注释

Can't render this file because it has a wrong number of fields in line 14.

View File

@ -954,6 +954,17 @@
Delete current channel 刪除目前色版
Delete current state 刪除目前狀態
Delete effect layer 刪除效果圖層
# Layers-panel trash confirmation
Delete layer ‘{name}’? 刪除圖層「{name}」?
Delete {count} layers? {names} 刪除 {count} 個圖層?{names}
Delete layer? 刪除圖層?
The document is closed 文件已關閉
The document or selected layers changed. Please request deletion again. 文件或所選圖層已變更。請重新要求刪除。
Missing delete command 缺少刪除指令
Invalid delete command 無效的刪除指令
Layer deleted, but confirmation preference could not be saved: {error} 圖層已刪除,但無法儲存確認設定:{error}
Delete layer 刪除圖層
Delete layer comp 刪除圖層構圖
Delete note 刪除註解

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -50,6 +50,7 @@ pub mod crop_overlay;
pub mod crop_shield;
pub mod crop_straighten;
pub mod crop_ui;
pub mod delete_layer_prompt;
pub mod dialog_blend_ui;
pub mod dialogs;
pub mod direct_select;
@ -722,6 +723,9 @@ impl PhotocraftApp {
/// Run an engine command, reporting errors in the status bar.
pub fn run(&mut self, id: &str, params: Value) -> Result<Value, String> {
if let Some(result) = delete_layer_prompt::intercept(self, id, &params) {
return result;
}
// Automation input also gates every step a command runs on its behalf (`actions.play`).
let gate = if self.automation_input && self.session.authorize.is_none() { self.services.automation_authorize } else { None };
if gate.is_some() {

View File

@ -101,6 +101,7 @@ fn footer_trash_deletes_only_the_targeted_mask_linked_or_unlinked() {
let at = h.get_by_label(label).rect().center();
click_with(&mut h, at, Modifiers::NONE);
let l = layer(&h, masked);
assert!(h.state().ui.dialogs.is_empty(), "mask deletion remains immediate");
let original = before.layer(photocraft_doc::LayerId(masked)).unwrap();
assert_eq!(l.content, original.content);
assert_eq!(layer(&h, shape), *before.layer(photocraft_doc::LayerId(shape)).unwrap());
@ -130,6 +131,23 @@ fn footer_trash_still_deletes_the_layer_when_its_content_is_targeted() {
assert!(!h.state().ui.mask_target && !h.state().ui.vector_mask_target);
let at = h.get_by_label("Delete layer").rect().center();
click_with(&mut h, at, Modifiers::NONE);
assert!(h.state().session.active().unwrap().doc.layer(photocraft_doc::LayerId(masked)).is_some());
assert!(h.state().ui.dialogs.iter().any(|d| crate::delete_layer_prompt::owns(&d.fields)));
h.get_by_label("Delete").click();
h.run_steps(3);
assert!(h.state().session.active().unwrap().doc.layer(photocraft_doc::LayerId(masked)).is_none());
}
/// Like Photoshop, ⌥/Alt-clicking the trash deletes the layer without asking.
#[test]
fn alt_click_on_the_footer_trash_deletes_without_confirmation() {
let (s, masked, _) = session();
let mut h = harness(s, 0, 1.0, 290.0);
let row = crate::layer_row_ui::recorded(&h.ctx).into_iter().find(|r| r.layer == masked).unwrap().row;
click_with(&mut h, pos2(row.left() + 46.0, row.center().y), Modifiers::NONE);
let at = h.get_by_label("Delete layer").rect().center();
click_with(&mut h, at, Modifiers::ALT);
assert!(h.state().ui.dialogs.is_empty(), "no confirmation");
assert!(h.state().session.active().unwrap().doc.layer(photocraft_doc::LayerId(masked)).is_none());
}

View File

@ -1905,7 +1905,10 @@ fn layers(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
};
let trash = icons::button(ui, "trash", 26.0, false, tl!(label));
if trash.clicked() {
actions.push((delete.into(), json!({})));
// Like Photoshop, ⌥/Alt-click deletes without asking.
let confirm = delete == "layer.delete" && !ui.input(|i| i.modifiers.alt);
let params = if confirm { json!({"__trash": true}) } else { json!({}) };
actions.push((delete.into(), params));
}
actions.extend(footer_drop(ui, &trash, footer_drag, "layer.delete"));
// A click follows the selected thumbnail; name that action for screen readers even