modified backend/studio/modules/projects/montage.py +2 -1
| @@ -631,7 +631,8 @@ def _save(pid: str, raw: dict, keep_history: bool, shifts: list[dict] | None, ba |
| 631 | 631 | if older is not None: |
| 632 | 632 | mine = _keep(pid, json.dumps(cleaned, ensure_ascii=False, indent=1).encode("utf-8")) |
| 633 | 633 | cleaned = clean(pid, merge(older, cleaned, load(pid))[0], duration) |
| 634 | | - if keep_history: |
| 634 | + # rien n'a changé : pas d'étape d'annulation qui ne défait rien |
| 635 | + if keep_history and (shifts or cleaned != load(pid)): |
| 635 | 636 | snapshot(pid) |
| 636 | 637 | _write(pid, cleaned) |
| 637 | 638 | for shift in shifts or []: |
| @@ -631,7 +631,8 @@ def _save(pid: str, raw: dict, keep_history: bool, shifts: list[dict] | None, ba |
| 631 | if older is not None: | 631 | if older is not None: |
| 632 | mine = _keep(pid, json.dumps(cleaned, ensure_ascii=False, indent=1).encode("utf-8")) | 632 | mine = _keep(pid, json.dumps(cleaned, ensure_ascii=False, indent=1).encode("utf-8")) |
| 633 | cleaned = clean(pid, merge(older, cleaned, load(pid))[0], duration) | 633 | cleaned = clean(pid, merge(older, cleaned, load(pid))[0], duration) |
| 634 | - if keep_history: | 634 | + # rien n'a changé : pas d'étape d'annulation qui ne défait rien |
| | 635 | + if keep_history and (shifts or cleaned != load(pid)): |
| 635 | snapshot(pid) | 636 | snapshot(pid) |
| 636 | _write(pid, cleaned) | 637 | _write(pid, cleaned) |
| 637 | for shift in shifts or []: | 638 | for shift in shifts or []: |
modified web/src/project/steps/montage/useMontage.ts +3 -1
| @@ -61,7 +61,9 @@ export function useMontage(pid: string) { |
| 61 | 61 | edits++; |
| 62 | 62 | dirty.value = true; |
| 63 | 63 | clearTimeout(saveTimer); |
| 64 | | - saveTimer = setTimeout(flush, delay); |
| 64 | + // pendant un glisser, rien ne part : un arrêt de la souris enregistrerait un état intermédiaire, et l'annulation |
| 65 | + // y reviendrait au lieu de défaire le geste (le relâcher enregistre) |
| 66 | + saveTimer = setTimeout(() => { if (!holding.value) flush(); }, delay); |
| 65 | 67 | } |
| 66 | 68 | |
| 67 | 69 | // les insertions dans l'A-roll pas encore envoyées : le serveur y décale les beats du storyboard (et l'annulation |
| @@ -61,7 +61,9 @@ export function useMontage(pid: string) { |
| 61 | edits++; | 61 | edits++; |
| 62 | dirty.value = true; | 62 | dirty.value = true; |
| 63 | clearTimeout(saveTimer); | 63 | clearTimeout(saveTimer); |
| 64 | - saveTimer = setTimeout(flush, delay); | 64 | + // pendant un glisser, rien ne part : un arrêt de la souris enregistrerait un état intermédiaire, et l'annulation |
| | 65 | + // y reviendrait au lieu de défaire le geste (le relâcher enregistre) |
| | 66 | + saveTimer = setTimeout(() => { if (!holding.value) flush(); }, delay); |
| 65 | } | 67 | } |
| 66 | | 68 | |
| 67 | // les insertions dans l'A-roll pas encore envoyées : le serveur y décale les beats du storyboard (et l'annulation | 69 | // les insertions dans l'A-roll pas encore envoyées : le serveur y décale les beats du storyboard (et l'annulation |