mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-11 16:34:47 -06:00
#3136: ask before a drag and drop replaces data
Dragging the selection's border onto cells that hold data replaced them
without asking, so dragging column B onto column A silently destroyed A.
Excel asks "There's already data here. Do you want to replace it?" first
(its "Alert before overwriting cells" option, on by default).
The drop now runs a new `range.move {range?, to, overwrite?}` command:
a cut and paste that returns a confirm question when the destination
holds data other than the moved cells; the grid shows it with OK and
Cancel, and OK runs it again with `overwrite: true`. A refused move
leaves the sheet and the clipboard as they were.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
178f3eb745
commit
5361e2c08a
@@ -1,7 +1,8 @@
|
||||
use super::cell::input_to_cell;
|
||||
use super::paste_special::paste_special;
|
||||
use super::{
|
||||
Cell, Clipboard, EngineError, Json, RangeRef, Result, Session, Value, cell_param_on, edit, json, ok, str_param, target_ranges, target_sheet,
|
||||
Cell, Clipboard, EngineError, Json, RangeRef, Result, Session, Value, bad, bool_param, cell_param_on, edit, json, ok, str_param, target_ranges,
|
||||
target_sheet,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------- clipboard
|
||||
@@ -163,6 +164,32 @@ pub(super) fn paste(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
paste_special(s, &json!({"what": "all", "at": p.get("at").cloned().unwrap_or(Json::Null)}))
|
||||
}
|
||||
|
||||
/// Drag and drop (dragging the selection's border): moves `range` so its top-left cell lands on
|
||||
/// `to`, as Cut and Paste would. Like Excel, it asks before replacing cells that hold data (other
|
||||
/// than the moved cells themselves); run it again with `overwrite: true` to replace them. A
|
||||
/// refused or failed move leaves the clipboard as it was.
|
||||
pub(super) fn range_move(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let sheet = target_sheet(s, p)?;
|
||||
let to = cell_param_on(s, p, "to", sheet)?.ok_or_else(|| bad("range.move", "missing `to`"))?;
|
||||
let kept = s.clipboard.take();
|
||||
let moved = cut(s, p).and_then(|_| {
|
||||
let src = s.clipboard.as_ref().map(|c| c.range).unwrap_or_default();
|
||||
let dest = to.offset(i64::from(src.height()) - 1, i64::from(src.width()) - 1).map(|end| RangeRef::new(to, end));
|
||||
if bool_param(p, "overwrite") != Some(true)
|
||||
&& let Some(dest) = dest
|
||||
&& let Some(sh) = s.doc()?.wb.sheet(sheet)
|
||||
&& sh.cells.iter_range(dest).any(|(c, cell)| !src.contains(c) && cell.has_content())
|
||||
{
|
||||
return Err(EngineError::Confirm("There's already data here. Do you want to replace it?".into()));
|
||||
}
|
||||
paste(s, &json!({"at": to.a1()}))
|
||||
});
|
||||
if moved.is_err() {
|
||||
s.clipboard = kept;
|
||||
}
|
||||
moved
|
||||
}
|
||||
|
||||
fn paste_text(s: &mut Session, p: &Json, text: &str) -> Result<Json> {
|
||||
let sheet = target_sheet(s, p)?;
|
||||
let at = cell_param_on(s, p, "at", sheet)?.unwrap_or(s.doc()?.selection.current().start);
|
||||
|
||||
@@ -353,6 +353,34 @@ fn cut_paste_that_overflows_the_grid_moves_nothing() {
|
||||
assert_eq!(get(&mut s, "XFD2")["value"], json!(2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drag_and_drop_asks_before_replacing_data() {
|
||||
// #3136: dragging column B's border onto column A replaced A without asking.
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
s.execute("range.setValues", json!({"range": "A1:B3", "values": [["Apple", "Kiwi"], ["Pear", "Plum"], ["Fig", "Lime"]]})).unwrap();
|
||||
s.execute("edit.copy", json!({"range": "D9"})).unwrap();
|
||||
let text = |s: &mut Session, a: &str| s.execute("cell.get", json!({"cell": a})).unwrap()["input"].clone();
|
||||
let r = s.execute("range.move", json!({"range": "B:B", "to": "A1"}));
|
||||
let Err(EngineError::Confirm(q)) = r else { panic!("{r:?}") };
|
||||
assert_eq!(q, "There's already data here. Do you want to replace it?");
|
||||
// Nothing moved, and the copy made before is still on the clipboard.
|
||||
assert_eq!(text(&mut s, "A1"), json!("Apple"));
|
||||
assert_eq!(text(&mut s, "B1"), json!("Kiwi"));
|
||||
assert!(s.clipboard.as_ref().is_some_and(|c| !c.cut && c.range.a1() == "D9"));
|
||||
// Confirmed, column B replaces column A.
|
||||
s.execute("range.move", json!({"range": "B:B", "to": "A1", "overwrite": true})).unwrap();
|
||||
assert_eq!(text(&mut s, "A1"), json!("Kiwi"));
|
||||
assert_eq!(text(&mut s, "A3"), json!("Lime"));
|
||||
assert_eq!(text(&mut s, "B1"), json!(""));
|
||||
// Onto empty cells, or overlapping only the moved cells themselves, nothing is asked.
|
||||
s.execute("range.move", json!({"range": "A1:A3", "to": "A2"})).unwrap();
|
||||
assert_eq!(text(&mut s, "A4"), json!("Lime"));
|
||||
s.execute("range.move", json!({"range": "A2:A4", "to": "C2"})).unwrap();
|
||||
assert_eq!(text(&mut s, "C2"), json!("Kiwi"));
|
||||
assert!(s.execute("range.move", json!({"range": "C2"})).is_err(), "`to` is required");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_case_insensitive() {
|
||||
assert_eq!(replace_ci("Apple apple APPLE", "apple", "pear"), "pear pear pear");
|
||||
|
||||
Reference in New Issue
Block a user