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 -->
|
<!-- Tabs -->
|
||||||
<div class="flex border-b border-[#2d3a4f] mb-4">
|
<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-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>
|
</div>
|
||||||
|
|
||||||
<!-- Style Tab Content -->
|
<!-- Style Tab Content -->
|
||||||
|
|||||||
@ -30,6 +30,18 @@ export class DrawingManager {
|
|||||||
italic: false,
|
italic: false,
|
||||||
alignVert: 'top',
|
alignVert: 'top',
|
||||||
alignHorz: 'left'
|
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) {
|
if (hit) {
|
||||||
this.selectedDrawing = hit.drawing;
|
this.selectedDrawing = hit.drawing;
|
||||||
// Only open panel for supported types
|
// 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.toggleSettingsPanel(true);
|
||||||
}
|
}
|
||||||
this.update();
|
this.update();
|
||||||
@ -337,11 +349,11 @@ export class DrawingManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defs = this.defaults.trend_line;
|
const color = this.defaults.trend_line.color;
|
||||||
const color = defs.color;
|
|
||||||
const p1 = { time: pos.time, price: pos.price };
|
const p1 = { time: pos.time, price: pos.price };
|
||||||
const p2 = { 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') {
|
if (this.activeTool === 'trend_line' || this.activeTool === 'ray' || this.activeTool === 'rectangle') {
|
||||||
this.currentDrawing = {
|
this.currentDrawing = {
|
||||||
type: this.activeTool, p1, p2,
|
type: this.activeTool, p1, p2,
|
||||||
@ -358,10 +370,26 @@ export class DrawingManager {
|
|||||||
alignHorz: defs.alignHorz
|
alignHorz: defs.alignHorz
|
||||||
};
|
};
|
||||||
} else if (this.activeTool === 'horizontal_line') {
|
} 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);
|
this.setTool(null);
|
||||||
} else if (this.activeTool === 'vertical_line') {
|
} 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);
|
this.setTool(null);
|
||||||
} else if (this.activeTool === 'fib_retracement') {
|
} else if (this.activeTool === 'fib_retracement') {
|
||||||
this.currentDrawing = { type: 'fib_retracement', p1, p2, color: '#fb8c00' };
|
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') {
|
} else if (d.price !== undefined && d.type === 'horizontal_line') {
|
||||||
const dy = this.series.priceToCoordinate(d.price);
|
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') {
|
} else if (d.time !== undefined && d.type === 'vertical_line') {
|
||||||
const dx = this.timeToX(d.time);
|
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) {
|
} else if (d.time !== undefined && d.price !== undefined) {
|
||||||
const dx = this.timeToX(d.time);
|
const dx = this.timeToX(d.time);
|
||||||
const dy = this.series.priceToCoordinate(d.price);
|
const dy = this.series.priceToCoordinate(d.price);
|
||||||
@ -752,18 +780,44 @@ export class DrawingManager {
|
|||||||
} else if (d.type === 'horizontal_line') {
|
} else if (d.type === 'horizontal_line') {
|
||||||
const y = series.priceToCoordinate(d.price);
|
const y = series.priceToCoordinate(d.price);
|
||||||
if (y !== null) {
|
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.beginPath();
|
||||||
ctx.moveTo(-1000, y);
|
ctx.moveTo(-1000, y);
|
||||||
ctx.lineTo(scope.mediaSize.width + 1000, y);
|
ctx.lineTo(scope.mediaSize.width + 1000, y);
|
||||||
ctx.stroke();
|
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') {
|
} else if (d.type === 'vertical_line') {
|
||||||
const x = this.timeToX(d.time);
|
const x = this.timeToX(d.time);
|
||||||
if (x !== null) {
|
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.beginPath();
|
||||||
ctx.moveTo(x, -1000);
|
ctx.moveTo(x, -1000);
|
||||||
ctx.lineTo(x, scope.mediaSize.height + 1000);
|
ctx.lineTo(x, scope.mediaSize.height + 1000);
|
||||||
ctx.stroke();
|
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') {
|
} else if (d.type === 'fib_retracement') {
|
||||||
const x1 = this.timeToX(d.p1.time);
|
const x1 = this.timeToX(d.p1.time);
|
||||||
@ -1017,8 +1071,17 @@ export class DrawingManager {
|
|||||||
|
|
||||||
window.switchTLTab = (tab) => {
|
window.switchTLTab = (tab) => {
|
||||||
this.activeTLTab = 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('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('tlContentStyle').className = tab === 'style' ? 'block' : 'hidden';
|
||||||
document.getElementById('tlContentText').className = tab === 'text' ? '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.setTLThickness = (width) => this.applySettings('width', width);
|
||||||
window.setTLStyle = (style) => this.applySettings('style', style);
|
window.setTLStyle = (style) => this.applySettings('style', style);
|
||||||
window.toggleTLBold = () => {
|
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);
|
this.applySettings('bold', !current);
|
||||||
};
|
};
|
||||||
window.toggleTLItalic = () => {
|
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);
|
this.applySettings('italic', !current);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1129,6 +1192,14 @@ export class DrawingManager {
|
|||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
panel.classList.remove('hidden');
|
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();
|
this.updateSettingsPanelUI();
|
||||||
} else {
|
} else {
|
||||||
panel.classList.add('hidden');
|
panel.classList.add('hidden');
|
||||||
@ -1136,7 +1207,7 @@ export class DrawingManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateSettingsPanelUI() {
|
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 => {
|
document.querySelectorAll('#tlColorGrid .color-swatch').forEach(el => {
|
||||||
if (el.style.backgroundColor === settings.color) el.classList.add('active');
|
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);
|
btn.setAttribute('data-active', parseInt(btn.dataset.style) === settings.style);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('#tlTextColorGrid .color-swatch').forEach(el => {
|
const isLineWithText = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type);
|
||||||
if (el.style.backgroundColor === settings.textColor) el.classList.add('active');
|
|
||||||
else el.classList.remove('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (document.getElementById('tlTextColorBtn')) {
|
const textColorBtn = document.getElementById('tlTextColorBtn');
|
||||||
document.getElementById('tlTextColorBtn').style.backgroundColor = settings.textColor || settings.color;
|
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) {
|
applySettings(key, value) {
|
||||||
if (this.selectedDrawing) {
|
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();
|
this.update();
|
||||||
} else {
|
} 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();
|
this.updateSettingsPanelUI();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user