diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index e4a82818..b1d4e54b 100644 --- a/fusion_plating/fusion_plating/__manifest__.py +++ b/fusion_plating/fusion_plating/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating', - 'version': '19.0.10.3.0', + 'version': '19.0.10.4.0', 'category': 'Manufacturing/Plating', 'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.', 'description': """ diff --git a/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js b/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js index 0eb2e43d..b5aefbb2 100644 --- a/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js +++ b/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js @@ -32,7 +32,11 @@ export class FpSimpleRecipeEditor extends Component { librarySearch: "", templateOptions: [], selectedTemplate: "", - dragOverIndex: null, + // Drop-position simulator (snaps to line above/below the + // hovered row based on cursor Y vs row midpoint). + dragOverIndex: null, // 0..N (insertion index) + dragPreviewLabel: "", // shown next to the indicator line + dragPreviewIcon: "fa-cog", }); this._recipeId = null; @@ -155,25 +159,51 @@ export class FpSimpleRecipeEditor extends Component { ev.dataTransfer.effectAllowed = "move"; ev.dataTransfer.setData("application/x-fp-step", String(stepId)); ev.dataTransfer.setData("text/plain", String(stepId)); + const step = this.state.steps.find((s) => s.id === stepId); + this.state.dragPreviewLabel = step ? step.name : ""; + this.state.dragPreviewIcon = (step && step.icon) || "fa-cog"; } onLibraryDragStart(templateId, ev) { ev.dataTransfer.effectAllowed = "copy"; ev.dataTransfer.setData("application/x-fp-library", String(templateId)); ev.dataTransfer.setData("text/plain", "library"); + const tpl = this.state.library.find((t) => t.id === templateId); + this.state.dragPreviewLabel = tpl ? tpl.name : ""; + this.state.dragPreviewIcon = (tpl && tpl.icon) || "fa-cog"; } - onDragOver(index, ev) { + /** + * Compute the insertion index from the cursor Y vs row midpoint: + * above midpoint → insert BEFORE this row (index = rowIndex) + * below midpoint → insert AFTER this row (index = rowIndex + 1) + */ + onRowDragOver(rowIndex, ev) { ev.preventDefault(); ev.dataTransfer.dropEffect = ev.dataTransfer.types.includes("application/x-fp-library") ? "copy" : "move"; - this.state.dragOverIndex = index; + const rect = ev.currentTarget.getBoundingClientRect(); + const before = (ev.clientY - rect.top) < (rect.height / 2); + this.state.dragOverIndex = before ? rowIndex : rowIndex + 1; } - async onDrop(targetIndex, ev) { + /** Trailing dropzone — always inserts at the end. */ + onTailDragOver(ev) { ev.preventDefault(); + ev.dataTransfer.dropEffect = + ev.dataTransfer.types.includes("application/x-fp-library") + ? "copy" + : "move"; + this.state.dragOverIndex = this.state.steps.length; + } + + async onDrop(ev) { + ev.preventDefault(); + const targetIndex = this.state.dragOverIndex !== null + ? this.state.dragOverIndex + : this.state.steps.length; const fromLibrary = ev.dataTransfer.getData("application/x-fp-library"); if (fromLibrary) { await this.insertFromLibrary(parseInt(fromLibrary, 10), targetIndex); @@ -184,11 +214,26 @@ export class FpSimpleRecipeEditor extends Component { await this.reorderStep(draggedId, targetIndex); } } - this.state.dragOverIndex = null; + this._clearDragState(); } - onDragLeave() { + onDragLeave(ev) { + // Only clear when leaving the panel entirely. Browser fires + // dragleave when crossing into a child element too — guard against + // that by checking relatedTarget. + if (!ev.currentTarget.contains(ev.relatedTarget)) { + this.state.dragOverIndex = null; + } + } + + onDragEnd() { + this._clearDragState(); + } + + _clearDragState() { this.state.dragOverIndex = null; + this.state.dragPreviewLabel = ""; + this.state.dragPreviewIcon = "fa-cog"; } // --------------------------------------------------------------- helpers diff --git a/fusion_plating/fusion_plating/static/src/scss/simple_recipe_editor.scss b/fusion_plating/fusion_plating/static/src/scss/simple_recipe_editor.scss index ca6fdef5..1b867acb 100644 --- a/fusion_plating/fusion_plating/static/src/scss/simple_recipe_editor.scss +++ b/fusion_plating/fusion_plating/static/src/scss/simple_recipe_editor.scss @@ -93,6 +93,50 @@ $fp-se-drop: var(--fp-drop-bg, #{$_fp_se_drop_hex}); } } +// ===================================================== Drop simulator +// +// Thin reservation line between rows that activates only when the +// cursor crosses a row's vertical midpoint. The active indicator +// expands to show a ghost-preview chip with the dragged step's icon +// + name so the operator knows EXACTLY where the drop lands. + +.o_fp_drop_indicator { + height: 0; + margin: 0; + border: 0; + transition: height .08s ease, margin .08s ease, background .08s; + background: transparent; + border-radius: 2px; + overflow: hidden; + + &.o_fp_drop_indicator_active { + height: 2.25rem; + margin: .25rem 0; + background: $fp-se-drop; + border: 2px dashed $fp-se-accent; + display: flex; + align-items: center; + padding: 0 .75rem; + } + + .o_fp_drop_label { + display: flex; + align-items: center; + gap: .5rem; + font-weight: 600; + color: $fp-se-accent; + font-size: .85rem; + + &::before { + content: "↓ insert here →"; + font-weight: 500; + color: $fp-se-muted; + font-size: .75rem; + margin-right: .5rem; + } + } +} + .o_fp_step_row { display: flex; align-items: center; diff --git a/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml b/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml index fa217bda..d3eb2937 100644 --- a/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml +++ b/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml @@ -33,17 +33,29 @@
-
+

Selected (drag to reorder)

+ + +
+ + + + +
+
+ t-on-dragover="(ev) => this.onRowDragOver(step_index, ev)"> . @@ -57,13 +69,26 @@ ×
+ + +
+ + + + +
+
- Drop here to add at end + t-on-dragover="(ev) => this.onTailDragOver(ev)"> + + Drag a library step here to start + + + Drop here to add at end +