Improve formations drawing UX with step-by-step flow

Changed UX flow from confusing to clear:
1. Click Line/Channel -> Shows instructions + points counter
2. Click points on chart -> Updates counter (Points: 1/2)
3. After enough points -> Shows name input + Save button
4. Enter name + Save -> Formation created

formations_hud.html:
- Add instructions panel with Cancel button
- Add points status display
- Separate name input controls (shown after points placed)

formations.js:
- Add showDrawingInstructions() with type-specific text
- Add updatePointsStatus() for live counter
- Add showNameInput() after drawing complete
- Add hideAllDrawingUI() for cleanup

formation_overlay.js:
- Add onPointsChangedCallback for UI updates
- Store _pointsNeeded per drawing session
- Stop accepting clicks after enough points
- Change cursor back to default when complete

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rob 2026-03-10 20:00:47 -03:00
parent fceb040ef0
commit ac4c085acd
3 changed files with 172 additions and 22 deletions

View File

@ -38,6 +38,12 @@ class FormationOverlay {
// Callback for saving formations // Callback for saving formations
this.onSaveCallback = null; this.onSaveCallback = null;
// Callback for when points change during drawing
this.onPointsChangedCallback = null;
// Track points needed for current drawing
this._pointsNeeded = 0;
// Colors // Colors
this.defaultColor = '#667eea'; this.defaultColor = '#667eea';
this.selectedColor = '#ff9500'; this.selectedColor = '#ff9500';
@ -57,6 +63,14 @@ class FormationOverlay {
this.onSaveCallback = callback; this.onSaveCallback = callback;
} }
/**
* Set callback for when points change during drawing.
* @param {Function} callback - Called with (currentPoints, pointsNeeded)
*/
setOnPointsChangedCallback(callback) {
this.onPointsChangedCallback = callback;
}
/** /**
* Create the SVG overlay layer. * Create the SVG overlay layer.
*/ */
@ -361,6 +375,7 @@ class FormationOverlay {
startDrawing(type) { startDrawing(type) {
this.drawingMode = type; this.drawingMode = type;
this.currentPoints = []; this.currentPoints = [];
this._pointsNeeded = this._getPointsNeeded(type);
this._clearTempElements(); this._clearTempElements();
// Change cursor // Change cursor
@ -373,7 +388,12 @@ class FormationOverlay {
this.svg.style.pointerEvents = 'all'; this.svg.style.pointerEvents = 'all';
} }
console.log('FormationOverlay: Started drawing', type); // Notify of initial state
if (this.onPointsChangedCallback) {
this.onPointsChangedCallback(0, this._pointsNeeded);
}
console.log('FormationOverlay: Started drawing', type, 'needs', this._pointsNeeded, 'points');
} }
/** /**
@ -381,16 +401,28 @@ class FormationOverlay {
* @param {Object} coords - {time, price} * @param {Object} coords - {time, price}
*/ */
_handleDrawingClick(coords) { _handleDrawingClick(coords) {
// Don't accept more points than needed
if (this.currentPoints.length >= this._pointsNeeded) {
console.log('FormationOverlay: Already have enough points');
return;
}
this.currentPoints.push(coords); this.currentPoints.push(coords);
// Draw anchor at click point // Draw anchor at click point
this._drawTempAnchor(coords); this._drawTempAnchor(coords);
// Check if drawing is complete based on formation type // Notify of points change
const pointsNeeded = this._getPointsNeeded(this.drawingMode); if (this.onPointsChangedCallback) {
this.onPointsChangedCallback(this.currentPoints.length, this._pointsNeeded);
}
if (this.currentPoints.length >= pointsNeeded) { // Check if drawing is complete
// Drawing complete, wait for name input if (this.currentPoints.length >= this._pointsNeeded) {
// Drawing complete, change cursor back
if (this.container) {
this.container.style.cursor = 'default';
}
console.log('FormationOverlay: Points collected', this.currentPoints); console.log('FormationOverlay: Points collected', this.currentPoints);
} }
} }
@ -546,6 +578,8 @@ class FormationOverlay {
_exitDrawingMode() { _exitDrawingMode() {
this.drawingMode = null; this.drawingMode = null;
this.currentPoints = []; this.currentPoints = [];
this._pointsNeeded = 0;
this.onPointsChangedCallback = null;
this._clearTempElements(); this._clearTempElements();
// Reset cursor // Reset cursor

View File

@ -4,8 +4,11 @@
class FormationsUIManager { class FormationsUIManager {
constructor() { constructor() {
this.targetEl = null; this.targetEl = null;
this.drawingControlsEl = null; this.instructionsEl = null;
this.nameControlsEl = null;
this.nameInputEl = null; this.nameInputEl = null;
this.instructionTextEl = null;
this.pointsStatusEl = null;
this.onDeleteFormation = null; this.onDeleteFormation = null;
this.onEditFormation = null; this.onEditFormation = null;
} }
@ -20,8 +23,11 @@ class FormationsUIManager {
console.warn(`Formations container "${targetId}" not found.`); console.warn(`Formations container "${targetId}" not found.`);
} }
this.drawingControlsEl = document.getElementById('formation_drawing_controls'); this.instructionsEl = document.getElementById('formation_drawing_instructions');
this.nameControlsEl = document.getElementById('formation_name_controls');
this.nameInputEl = document.getElementById('formation_name_input'); this.nameInputEl = document.getElementById('formation_name_input');
this.instructionTextEl = document.getElementById('formation_instruction_text');
this.pointsStatusEl = document.getElementById('formation_points_status');
} }
/** /**
@ -41,12 +47,62 @@ class FormationsUIManager {
} }
/** /**
* Show drawing controls. * Show drawing instructions for a formation type.
* @param {string} type - Formation type
* @param {number} pointsNeeded - Number of points needed
*/ */
showDrawingControls() { showDrawingInstructions(type, pointsNeeded) {
if (this.drawingControlsEl) { // Hide name controls if visible
this.drawingControlsEl.style.display = 'block'; if (this.nameControlsEl) {
this.nameControlsEl.style.display = 'none';
} }
// Show instructions
if (this.instructionsEl) {
this.instructionsEl.style.display = 'block';
}
// Set instruction text based on type
const instructions = {
'support_resistance': 'Click 2 points on the chart to draw a line',
'channel': 'Click 3 points: first line (2 pts) + parallel offset (1 pt)'
};
if (this.instructionTextEl) {
this.instructionTextEl.textContent = instructions[type] || 'Click on chart to place points';
}
// Update points status
this.updatePointsStatus(0, pointsNeeded);
}
/**
* Update the points status display.
* @param {number} current - Current points placed
* @param {number} needed - Points needed
*/
updatePointsStatus(current, needed) {
if (this.pointsStatusEl) {
this.pointsStatusEl.textContent = `Points: ${current} / ${needed}`;
// Change color when complete
this.pointsStatusEl.style.color = current >= needed ? '#28a745' : '#667eea';
}
}
/**
* Show name input after points are placed.
*/
showNameInput() {
// Hide instructions
if (this.instructionsEl) {
this.instructionsEl.style.display = 'none';
}
// Show name controls
if (this.nameControlsEl) {
this.nameControlsEl.style.display = 'block';
}
// Focus and clear input
if (this.nameInputEl) { if (this.nameInputEl) {
this.nameInputEl.value = ''; this.nameInputEl.value = '';
this.nameInputEl.focus(); this.nameInputEl.focus();
@ -54,12 +110,29 @@ class FormationsUIManager {
} }
/** /**
* Hide drawing controls. * Hide all drawing-related UI.
*/
hideAllDrawingUI() {
if (this.instructionsEl) {
this.instructionsEl.style.display = 'none';
}
if (this.nameControlsEl) {
this.nameControlsEl.style.display = 'none';
}
}
/**
* Legacy method - kept for compatibility
*/
showDrawingControls() {
// Now handled by showDrawingInstructions and showNameInput
}
/**
* Legacy method - kept for compatibility
*/ */
hideDrawingControls() { hideDrawingControls() {
if (this.drawingControlsEl) { this.hideAllDrawingUI();
this.drawingControlsEl.style.display = 'none';
}
} }
/** /**
@ -386,7 +459,7 @@ class Formations {
this.overlay.renderFormation(data.formation); this.overlay.renderFormation(data.formation);
} }
this.uiManager.hideDrawingControls(); this.uiManager.hideAllDrawingUI();
this.drawingMode = null; this.drawingMode = null;
} else { } else {
alert(`Failed to create formation: ${data.message}`); alert(`Failed to create formation: ${data.message}`);
@ -438,6 +511,19 @@ class Formations {
// ================ Drawing Methods ================ // ================ Drawing Methods ================
/**
* Get points needed for a formation type.
* @param {string} type - Formation type
* @returns {number}
*/
_getPointsNeeded(type) {
const pointsMap = {
'support_resistance': 2,
'channel': 3
};
return pointsMap[type] || 2;
}
/** /**
* Start drawing a new formation. * Start drawing a new formation.
* @param {string} type - Formation type ('support_resistance', 'channel') * @param {string} type - Formation type ('support_resistance', 'channel')
@ -446,15 +532,33 @@ class Formations {
console.log("Starting drawing mode:", type); console.log("Starting drawing mode:", type);
this.drawingMode = type; this.drawingMode = type;
// Show drawing controls const pointsNeeded = this._getPointsNeeded(type);
this.uiManager.showDrawingControls();
// Tell overlay to start drawing // Show drawing instructions (not name input yet)
this.uiManager.showDrawingInstructions(type, pointsNeeded);
// Tell overlay to start drawing, with callback for point updates
if (this.overlay) { if (this.overlay) {
this.overlay.setOnPointsChangedCallback(this._onPointsChanged.bind(this));
this.overlay.startDrawing(type); this.overlay.startDrawing(type);
} }
} }
/**
* Called when points are added/changed during drawing.
* @param {number} currentPoints - Current number of points
* @param {number} pointsNeeded - Points needed for completion
*/
_onPointsChanged(currentPoints, pointsNeeded) {
// Update the UI status
this.uiManager.updatePointsStatus(currentPoints, pointsNeeded);
// If we have enough points, show the name input
if (currentPoints >= pointsNeeded) {
this.uiManager.showNameInput();
}
}
/** /**
* Complete the current drawing. * Complete the current drawing.
*/ */
@ -475,7 +579,7 @@ class Formations {
*/ */
cancelDrawing() { cancelDrawing() {
this.drawingMode = null; this.drawingMode = null;
this.uiManager.hideDrawingControls(); this.uiManager.hideAllDrawingUI();
if (this.overlay) { if (this.overlay) {
this.overlay.cancelDrawing(); this.overlay.cancelDrawing();

View File

@ -9,8 +9,20 @@
</button> </button>
</div> </div>
<!-- Drawing controls (shown when drawing) --> <!-- Drawing instructions (shown when drawing starts) -->
<div id="formation_drawing_controls" style="display: none; margin-top: 10px; padding: 10px; background: #2a2a2a; border-radius: 5px;"> <div id="formation_drawing_instructions" style="display: none; margin-top: 10px; padding: 10px; background: #2a2a2a; border-radius: 5px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span id="formation_instruction_text" style="color: #8899aa; font-size: 12px;">Click on chart to place points...</span>
<button class="btn btn-sm" style="background: #dc3545; padding: 4px 8px;" onclick="UI.formations.cancelDrawing()">Cancel</button>
</div>
<div style="margin-top: 8px; color: #667eea; font-size: 11px;">
<span id="formation_points_status">Points: 0 / 2</span>
</div>
</div>
<!-- Name input (shown after points are placed) -->
<div id="formation_name_controls" style="display: none; margin-top: 10px; padding: 10px; background: #2a2a2a; border-radius: 5px;">
<div style="margin-bottom: 8px; color: #28a745; font-size: 12px;">Points placed. Enter a name:</div>
<div style="display: flex; gap: 10px; align-items: center;"> <div style="display: flex; gap: 10px; align-items: center;">
<input type="text" id="formation_name_input" placeholder="Formation name" <input type="text" id="formation_name_input" placeholder="Formation name"
style="flex: 1; padding: 5px; border-radius: 3px; border: 1px solid #444; background: #1e1e1e; color: #e0e0e0;"> style="flex: 1; padding: 5px; border-radius: 3px; border: 1px solid #444; background: #1e1e1e; color: #e0e0e0;">