feat: Implement settings panel for vertical and horizontal lines
- Add default settings (color, width, style, opacity) for horizontal_line and vertical_line - Enable settings panel to open on double-click for horizontal/vertical lines - Update rendering to support full styling (color, width, style, opacity) with visual selection handles - Show/hide Text tab based on drawing type (trend_line/ray/rectangle only) - Add proper drag handling to move entire horizontal/vertical lines
This commit is contained in:
@ -151,7 +151,7 @@
|
||||
<!-- Tabs -->
|
||||
<div class="flex border-b border-[#2d3a4f] mb-4">
|
||||
<button class="flex-1 py-2 text-center text-blue-500 border-b-2 border-blue-500 font-medium" id="tlTabStyle" onclick="window.switchTLTab('style')">Style</button>
|
||||
<button class="flex-1 py-2 text-center text-gray-400 hover:text-white" id="tlTabText" onclick="window.switchTLTab('text')">Text</button>
|
||||
<button class="flex-1 py-2 text-center text-gray-400 hover:text-white" id="tlTabText" onclick="window.switchTLTab('text')" style="display: none;">Text</button>
|
||||
</div>
|
||||
|
||||
<!-- Style Tab Content -->
|
||||
|
||||
@ -30,6 +30,18 @@ export class DrawingManager {
|
||||
italic: false,
|
||||
alignVert: 'top',
|
||||
alignHorz: 'left'
|
||||
},
|
||||
horizontal_line: {
|
||||
color: '#2962ff',
|
||||
width: 2,
|
||||
style: 0,
|
||||
opacity: 100
|
||||
},
|
||||
vertical_line: {
|
||||
color: '#2962ff',
|
||||
width: 2,
|
||||
style: 0,
|
||||
opacity: 100
|
||||
}
|
||||
};
|
||||
|
||||
@ -55,7 +67,7 @@ export class DrawingManager {
|
||||
if (hit) {
|
||||
this.selectedDrawing = hit.drawing;
|
||||
// Only open panel for supported types
|
||||
if (['trend_line', 'ray', 'rectangle'].includes(hit.drawing.type)) {
|
||||
if (['trend_line', 'ray', 'rectangle', 'horizontal_line', 'vertical_line'].includes(hit.drawing.type)) {
|
||||
this.toggleSettingsPanel(true);
|
||||
}
|
||||
this.update();
|
||||
@ -337,11 +349,11 @@ export class DrawingManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const defs = this.defaults.trend_line;
|
||||
const color = defs.color;
|
||||
const color = this.defaults.trend_line.color;
|
||||
const p1 = { time: pos.time, price: pos.price };
|
||||
const p2 = { time: pos.time, price: pos.price };
|
||||
|
||||
const defs = this.defaults.trend_line;
|
||||
if (this.activeTool === 'trend_line' || this.activeTool === 'ray' || this.activeTool === 'rectangle') {
|
||||
this.currentDrawing = {
|
||||
type: this.activeTool, p1, p2,
|
||||
@ -358,10 +370,26 @@ export class DrawingManager {
|
||||
alignHorz: defs.alignHorz
|
||||
};
|
||||
} else if (this.activeTool === 'horizontal_line') {
|
||||
this.drawings.push({ type: 'horizontal_line', price: pos.price, color });
|
||||
const defs = this.defaults.horizontal_line;
|
||||
this.drawings.push({
|
||||
type: 'horizontal_line',
|
||||
price: pos.price,
|
||||
color: defs.color,
|
||||
width: defs.width,
|
||||
style: defs.style,
|
||||
opacity: defs.opacity
|
||||
});
|
||||
this.setTool(null);
|
||||
} else if (this.activeTool === 'vertical_line') {
|
||||
this.drawings.push({ type: 'vertical_line', time: pos.time, color });
|
||||
const defs = this.defaults.vertical_line;
|
||||
this.drawings.push({
|
||||
type: 'vertical_line',
|
||||
time: pos.time,
|
||||
color: defs.color,
|
||||
width: defs.width,
|
||||
style: defs.style,
|
||||
opacity: defs.opacity
|
||||
});
|
||||
this.setTool(null);
|
||||
} else if (this.activeTool === 'fib_retracement') {
|
||||
this.currentDrawing = { type: 'fib_retracement', p1, p2, color: '#fb8c00' };
|
||||
@ -526,10 +554,10 @@ export class DrawingManager {
|
||||
}
|
||||
} else if (d.price !== undefined && d.type === 'horizontal_line') {
|
||||
const dy = this.series.priceToCoordinate(d.price);
|
||||
if (dy !== null && Math.abs(y - dy) < threshold) return { drawing: d, part: 'price' };
|
||||
if (dy !== null && Math.abs(y - dy) < threshold) return { drawing: d, part: 'all' };
|
||||
} else if (d.time !== undefined && d.type === 'vertical_line') {
|
||||
const dx = this.timeToX(d.time);
|
||||
if (dx !== null && Math.abs(x - dx) < threshold) return { drawing: d, part: 'time' };
|
||||
if (dx !== null && Math.abs(x - dx) < threshold) return { drawing: d, part: 'all' };
|
||||
} else if (d.time !== undefined && d.price !== undefined) {
|
||||
const dx = this.timeToX(d.time);
|
||||
const dy = this.series.priceToCoordinate(d.price);
|
||||
@ -752,18 +780,44 @@ export class DrawingManager {
|
||||
} else if (d.type === 'horizontal_line') {
|
||||
const y = series.priceToCoordinate(d.price);
|
||||
if (y !== null) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = d.opacity !== undefined ? this.hexToRgba(d.color, d.opacity) : d.color;
|
||||
ctx.lineWidth = d.width || 2;
|
||||
if (d.style === 1) ctx.setLineDash([10, 5]);
|
||||
else if (d.style === 2) ctx.setLineDash([2, 2]);
|
||||
else ctx.setLineDash([]);
|
||||
if (isSelected) { ctx.shadowBlur = 5; ctx.shadowColor = d.color; }
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-1000, y);
|
||||
ctx.lineTo(scope.mediaSize.width + 1000, y);
|
||||
ctx.stroke();
|
||||
if (isSelected) {
|
||||
ctx.setLineDash([]);
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.beginPath(); ctx.arc(0, y, 5, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
} else if (d.type === 'vertical_line') {
|
||||
const x = this.timeToX(d.time);
|
||||
if (x !== null) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = d.opacity !== undefined ? this.hexToRgba(d.color, d.opacity) : d.color;
|
||||
ctx.lineWidth = d.width || 2;
|
||||
if (d.style === 1) ctx.setLineDash([10, 5]);
|
||||
else if (d.style === 2) ctx.setLineDash([2, 2]);
|
||||
else ctx.setLineDash([]);
|
||||
if (isSelected) { ctx.shadowBlur = 5; ctx.shadowColor = d.color; }
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, -1000);
|
||||
ctx.lineTo(x, scope.mediaSize.height + 1000);
|
||||
ctx.stroke();
|
||||
if (isSelected) {
|
||||
ctx.setLineDash([]);
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.beginPath(); ctx.arc(x, 0, 5, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
} else if (d.type === 'fib_retracement') {
|
||||
const x1 = this.timeToX(d.p1.time);
|
||||
@ -1017,8 +1071,17 @@ export class DrawingManager {
|
||||
|
||||
window.switchTLTab = (tab) => {
|
||||
this.activeTLTab = tab;
|
||||
const isLineWithText = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type);
|
||||
|
||||
document.getElementById('tlTabStyle').className = tab === 'style' ? 'flex-1 py-2 text-center text-blue-500 border-b-2 border-blue-500 font-medium' : 'flex-1 py-2 text-center text-gray-400 hover:text-white';
|
||||
document.getElementById('tlTabText').className = tab === 'text' ? 'flex-1 py-2 text-center text-blue-500 border-b-2 border-blue-500 font-medium' : 'flex-1 py-2 text-center text-gray-400 hover:text-white';
|
||||
|
||||
const textTab = document.getElementById('tlTabText');
|
||||
if (isLineWithText) {
|
||||
textTab.className = tab === 'text' ? 'flex-1 py-2 text-center text-blue-500 border-b-2 border-blue-500 font-medium' : 'flex-1 py-2 text-center text-gray-400 hover:text-white';
|
||||
textTab.style.display = 'block';
|
||||
} else {
|
||||
textTab.style.display = 'none';
|
||||
}
|
||||
|
||||
document.getElementById('tlContentStyle').className = tab === 'style' ? 'block' : 'hidden';
|
||||
document.getElementById('tlContentText').className = tab === 'text' ? 'block' : 'hidden';
|
||||
@ -1032,11 +1095,11 @@ export class DrawingManager {
|
||||
window.setTLThickness = (width) => this.applySettings('width', width);
|
||||
window.setTLStyle = (style) => this.applySettings('style', style);
|
||||
window.toggleTLBold = () => {
|
||||
const current = this.selectedDrawing ? this.selectedDrawing.bold : this.defaults.trend_line.bold;
|
||||
const current = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type) ? this.selectedDrawing.bold : this.defaults.trend_line.bold;
|
||||
this.applySettings('bold', !current);
|
||||
};
|
||||
window.toggleTLItalic = () => {
|
||||
const current = this.selectedDrawing ? this.selectedDrawing.italic : this.defaults.trend_line.italic;
|
||||
const current = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type) ? this.selectedDrawing.italic : this.defaults.trend_line.italic;
|
||||
this.applySettings('italic', !current);
|
||||
};
|
||||
|
||||
@ -1129,6 +1192,14 @@ export class DrawingManager {
|
||||
|
||||
if (show) {
|
||||
panel.classList.remove('hidden');
|
||||
const isLineWithText = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type);
|
||||
if (!isLineWithText) {
|
||||
this.activeTLTab = 'style';
|
||||
document.getElementById('tlTabStyle').className = 'flex-1 py-2 text-center text-blue-500 border-b-2 border-blue-500 font-medium';
|
||||
document.getElementById('tlTabText').className = 'flex-1 py-2 text-center text-gray-400 hover:text-white';
|
||||
document.getElementById('tlContentStyle').className = 'block';
|
||||
document.getElementById('tlContentText').className = 'hidden';
|
||||
}
|
||||
this.updateSettingsPanelUI();
|
||||
} else {
|
||||
panel.classList.add('hidden');
|
||||
@ -1136,7 +1207,7 @@ export class DrawingManager {
|
||||
}
|
||||
|
||||
updateSettingsPanelUI() {
|
||||
const settings = this.selectedDrawing || this.defaults.trend_line;
|
||||
const settings = this.selectedDrawing || this.defaults[this.selectedDrawing ? this.selectedDrawing.type : 'trend_line'];
|
||||
|
||||
document.querySelectorAll('#tlColorGrid .color-swatch').forEach(el => {
|
||||
if (el.style.backgroundColor === settings.color) el.classList.add('active');
|
||||
@ -1154,28 +1225,60 @@ export class DrawingManager {
|
||||
btn.setAttribute('data-active', parseInt(btn.dataset.style) === settings.style);
|
||||
});
|
||||
|
||||
document.querySelectorAll('#tlTextColorGrid .color-swatch').forEach(el => {
|
||||
if (el.style.backgroundColor === settings.textColor) el.classList.add('active');
|
||||
else el.classList.remove('active');
|
||||
});
|
||||
|
||||
if (document.getElementById('tlTextColorBtn')) {
|
||||
document.getElementById('tlTextColorBtn').style.backgroundColor = settings.textColor || settings.color;
|
||||
const isLineWithText = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type);
|
||||
|
||||
const textColorBtn = document.getElementById('tlTextColorBtn');
|
||||
const textPicker = document.getElementById('tlTextColorPicker');
|
||||
const fontInput = document.getElementById('tlFontSize');
|
||||
const textInput = document.getElementById('tlTextInput');
|
||||
const alignVert = document.getElementById('tlAlignVert');
|
||||
const alignHorz = document.getElementById('tlAlignHorz');
|
||||
const boldBtn = document.getElementById('tlBoldBtn');
|
||||
const italicBtn = document.getElementById('tlItalicBtn');
|
||||
|
||||
if (isLineWithText) {
|
||||
if (textColorBtn && textPicker) {
|
||||
textColorBtn.style.backgroundColor = settings.textColor || settings.color;
|
||||
textPicker.classList.remove('hidden');
|
||||
}
|
||||
fontInput.value = settings.fontSize || 14;
|
||||
textInput.value = settings.text || '';
|
||||
boldBtn.setAttribute('data-active', !!settings.bold);
|
||||
italicBtn.setAttribute('data-active', !!settings.italic);
|
||||
alignVert.value = settings.alignVert || 'top';
|
||||
alignHorz.value = settings.alignHorz || 'left';
|
||||
} else {
|
||||
if (textPicker) textPicker.classList.add('hidden');
|
||||
}
|
||||
document.getElementById('tlFontSize').value = settings.fontSize || 14;
|
||||
document.getElementById('tlTextInput').value = settings.text || '';
|
||||
document.getElementById('tlBoldBtn').setAttribute('data-active', !!settings.bold);
|
||||
document.getElementById('tlItalicBtn').setAttribute('data-active', !!settings.italic);
|
||||
document.getElementById('tlAlignVert').value = settings.alignVert || 'top';
|
||||
document.getElementById('tlAlignHorz').value = settings.alignHorz || 'left';
|
||||
}
|
||||
|
||||
applySettings(key, value) {
|
||||
if (this.selectedDrawing) {
|
||||
this.selectedDrawing[key] = value;
|
||||
if (key === 'bold' || key === 'italic' || key === 'fontSize' || key === 'text' || key === 'textColor' || key === 'alignVert' || key === 'alignHorz') {
|
||||
const isLineWithText = ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type);
|
||||
if (!isLineWithText) return;
|
||||
this.selectedDrawing[key] = value;
|
||||
} else {
|
||||
this.selectedDrawing[key] = value;
|
||||
}
|
||||
this.update();
|
||||
} else {
|
||||
this.defaults.trend_line[key] = value;
|
||||
const drawingType = this.selectedDrawing ? this.selectedDrawing.type : 'trend_line';
|
||||
if (key === 'bold' || key === 'italic' || key === 'fontSize' || key === 'text' || key === 'textColor' || key === 'alignVert' || key === 'alignHorz') {
|
||||
if (drawingType === 'trend_line' || drawingType === 'ray' || drawingType === 'rectangle') {
|
||||
this.defaults.trend_line[key] = value;
|
||||
}
|
||||
} else {
|
||||
if (drawingType === 'trend_line' || drawingType === 'ray' || drawingType === 'rectangle') {
|
||||
this.defaults.trend_line[key] = value;
|
||||
} else if (drawingType === 'horizontal_line') {
|
||||
this.defaults.horizontal_line[key] = value;
|
||||
} else if (drawingType === 'vertical_line') {
|
||||
this.defaults.vertical_line[key] = value;
|
||||
} else {
|
||||
this.defaults.trend_line[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.updateSettingsPanelUI();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user