Compare commits

..

19 Commits

Author SHA1 Message Date
f9eda9f18b fix: Remove text visibility bounds check 2026-03-21 23:59:37 +01:00
e3999e6506 fix: Ensure horizontal/vertical line text is visible on screen
- Add bounds checking to only render text when line is on screen
- Fix text positioning to be consistent with line position
2026-03-21 23:56:41 +01:00
c8d9cf9645 fix: Show Text tab immediately for all line types 2026-03-21 23:50:14 +01:00
9fefd1847a fix: Show Text tab for horizontal and vertical line settings 2026-03-21 23:47:27 +01:00
09c7657070 feat: Add text support for horizontal and vertical lines
- Add text-related default settings (text, textColor, fontSize, bold, italic, alignVert, alignHorz)
- Support text rendering on horizontal/vertical lines with alignment options
- Add label position storage and hit detection for draggable text labels
2026-03-21 23:43:19 +01:00
30ac99479f 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
2026-03-21 23:41:05 +01:00
43fa8efda7 feat: Implement draggable Trendline Settings Panel with Shift+Click and TF layout fixes 2026-03-21 23:28:23 +01:00
fd816edc54 UI: Move drawing toolbar to top and remove Cursor, Ray, and Fibonacci buttons 2026-03-21 23:13:05 +01:00
cb2cd53a8a Fix drawing tools: allow future/whitespace drawing and improve TF scaling precision 2026-03-21 22:31:24 +01:00
ace4d4f49e fix: disable auto-scrolling to real-time on new data load 2026-03-21 22:02:38 +01:00
cdfe8f1a39 fix: improve timeframe-independent dragging and prevent undefined dragStartPos error 2026-03-21 21:57:28 +01:00
7c92aa38be feat: enhance measurement duration to show d h m and refine label anchoring 2026-03-21 17:20:39 +01:00
8931d76a43 refactor: improve measurement label anchoring to track area edges during resize 2026-03-21 14:24:57 +01:00
a1564c25a7 feat: enable free movement of measurement tool label box 2026-03-21 14:11:39 +01:00
b2a4a6963d fix: implement touch support for mobile and fix timeframe-based data calculation bugs 2026-03-21 13:58:13 +01:00
338b1ee895 style: apply dynamic green/red colors to measurement tool and axis labels 2026-03-21 13:35:11 +01:00
3575d37764 style: refine measurement tool to match professional style and fix rendering bugs 2026-03-21 13:23:45 +01:00
cd2ca2e220 chore: correctly resolve merge conflicts and restore drawing tools implementation 2026-03-21 12:57:30 +01:00
eda151bff5 feat: implement professional drawing tools with selection and editing support 2026-03-21 11:36:17 +01:00
6 changed files with 1647 additions and 450 deletions

View File

@ -217,3 +217,51 @@ body {
} }
} }
/* Trendline Settings Panel */
#trendlineSettingsPanel input[type=range] {
-webkit-appearance: none;
background: transparent;
}
#trendlineSettingsPanel input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 12px;
width: 12px;
border-radius: 50%;
background: #ffffff;
cursor: pointer;
border: 2px solid #1a2333;
margin-top: -4px;
}
#trendlineSettingsPanel input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: #2d3a4f;
border-radius: 2px;
}
.color-swatch {
width: 20px;
height: 20px;
border-radius: 3px;
cursor: pointer;
border: 1px solid transparent;
transition: transform 0.1s, border-color 0.1s;
}
.color-swatch:hover {
transform: scale(1.1);
border-color: #ffffff;
}
.color-swatch.active {
border-color: #ffffff;
box-shadow: 0 0 0 1px #ffffff;
}
.tl-thickness-btn[data-active="true"],
.tl-style-btn[data-active="true"] {
background-color: #2d3a4f;
}

BIN
ignore/line_settings.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
ignore/text.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -105,68 +105,156 @@
<section class="relative w-full bg-[#0d1421] h-[60vh] md:h-[70vh]" data-purpose="chart-container" id="chartWrapper"> <section class="relative w-full bg-[#0d1421] h-[60vh] md:h-[70vh]" data-purpose="chart-container" id="chartWrapper">
<div id="chart" class="w-full h-full"></div> <div id="chart" class="w-full h-full"></div>
<!-- TradingView-style Left Toolbar --> <!-- Horizontal Drawing Toolbar (Top) -->
<div class="absolute left-0 top-0 bottom-0 flex flex-col justify-center bg-[#0d1421]/90 border-r border-[#1b1f2b] backdrop-blur-sm z-20 px-2 py-4 gap-3" style="display: none;" id="chartToolbar"> <div class="absolute top-2 left-1/2 -translate-x-1/2 flex flex-row gap-1 z-30 bg-[#1a2333]/80 backdrop-blur border border-[#2d3a4f] p-1 rounded-md shadow-xl" id="drawingToolbar">
<!-- Trend Line Tool --> <button class="w-8 h-8 text-gray-400 hover:text-white hover:bg-[#2d3a4f] rounded flex items-center justify-center transition-colors" onclick="window.activateDrawingTool('trend_line', event)" title="Trend Line">
<button class="w-8 h-8 bg-[#1e222d] border border-[#2d3a4f] text-gray-300 flex items-center justify-center rounded hover:bg-[#2d3a4f] hover:text-white transition-colors shadow-lg group" onclick="window.activateDrawingTool('trend_line')" title="Trend Line"> <span class="material-symbols-outlined text-sm">call_split</span>
<span class="material-symbols-outlined text-sm group-hover:text-[#2962ff]">call_split</span> </button>
</button> <button class="w-8 h-8 text-gray-400 hover:text-white hover:bg-[#2d3a4f] rounded flex items-center justify-center transition-colors" onclick="window.activateDrawingTool('horizontal_line', event)" title="Horizontal Line">
<span class="material-symbols-outlined text-sm">swap_horizontal_circle</span>
<!-- Horizontal Line Tool --> </button>
<button class="w-8 h-8 bg-[#1e222d] border border-[#2d3a4f] text-gray-300 flex items-center justify-center rounded hover:bg-[#2d3a4f] hover:text-white transition-colors shadow-lg group" onclick="window.activateDrawingTool('horizontal_line')" title="Horizontal Line"> <button class="w-8 h-8 text-gray-400 hover:text-white hover:bg-[#2d3a4f] rounded flex items-center justify-center transition-colors" onclick="window.activateDrawingTool('vertical_line', event)" title="Vertical Line">
<span class="material-symbols-outlined text-sm group-hover:text-[#2962ff]">swap_horizontal_circle</span> <span class="material-symbols-outlined text-sm">swap_vert</span>
</button> </button>
<button class="w-8 h-8 text-gray-400 hover:text-white hover:bg-[#2d3a4f] rounded flex items-center justify-center transition-colors" onclick="window.activateDrawingTool('rectangle', event)" title="Rectangle">
<!-- Vertical Line Tool --> <span class="material-symbols-outlined text-sm">crop_square</span>
<button class="w-8 h-8 bg-[#1e222d] border border-[#2d3a4f] text-gray-300 flex items-center justify-center rounded hover:bg-[#2d3a4f] hover:text-white transition-colors shadow-lg group" onclick="window.activateDrawingTool('vertical_line')" title="Vertical Line"> </button>
<span class="material-symbols-outlined text-sm group-hover:text-[#2962ff]">swap_vert</span> <button class="w-8 h-8 text-gray-400 hover:text-white hover:bg-[#2d3a4f] rounded flex items-center justify-center transition-colors" onclick="window.activateDrawingTool('text', event)" title="Text Label">
</button> <span class="material-symbols-outlined text-sm">text_fields</span>
</button>
<!-- Text Label Tool --> <button class="w-8 h-8 text-gray-400 hover:text-white hover:bg-[#2d3a4f] rounded flex items-center justify-center transition-colors" onclick="window.activateDrawingTool('arrow_up', event)" title="Arrow Up">
<button class="w-8 h-8 bg-[#1e222d] border border-[#2d3a4f] text-gray-300 flex items-center justify-center rounded hover:bg-[#2d3a4f] hover:text-white transition-colors shadow-lg group" onclick="window.activateDrawingTool('text')" title="Text Label"> <span class="material-symbols-outlined text-sm">arrow_upward</span>
<span class="material-symbols-outlined text-sm group-hover:text-[#2962ff]">text_fields</span> </button>
</button> <button class="w-8 h-8 text-gray-400 hover:text-white hover:bg-[#2d3a4f] rounded flex items-center justify-center transition-colors" onclick="window.activateDrawingTool('arrow_down', event)" title="Arrow Down">
<span class="material-symbols-outlined text-sm">arrow_downward</span>
<!-- Arrow Up Tool --> </button>
<button class="w-8 h-8 bg-[#1e222d] border border-[#2d3a4f] text-gray-300 flex items-center justify-center rounded hover:bg-[#2d3a4f] hover:text-white transition-colors shadow-lg group" onclick="window.activateDrawingTool('arrow_up')" title="Arrow Up"> <button class="w-8 h-8 text-gray-400 hover:text-white hover:bg-[#2d3a4f] rounded flex items-center justify-center transition-colors" onclick="window.activateDrawingTool('measure', event)" title="Measure">
<span class="material-symbols-outlined text-sm group-hover:text-[#2962ff]">arrow_upward</span> <span class="material-symbols-outlined text-sm">straighten</span>
</button> </button>
<div class="w-[1px] h-full bg-[#2d3a4f] mx-0.5"></div>
<!-- Arrow Down Tool --> <button class="w-8 h-8 text-red-400 hover:text-red-300 hover:bg-red-900/20 rounded flex items-center justify-center transition-colors" onclick="window.activateDrawingTool('clear', event)" title="Clear All">
<button class="w-8 h-8 bg-[#1e222d] border border-[#2d3a4f] text-gray-300 flex items-center justify-center rounded hover:bg-[#2d3a4f] hover:text-white transition-colors shadow-lg group" onclick="window.activateDrawingTool('arrow_down')" title="Arrow Down"> <span class="material-symbols-outlined text-sm">delete</span>
<span class="material-symbols-outlined text-sm group-hover:text-[#2962ff]">arrow_downward</span> </button>
</button>
<!-- Divider -->
<div class="w-8 h-px bg-[#2d3a4f] my-1"></div>
<!-- Clear All -->
<button class="w-8 h-8 bg-[#1e222d] border border-[#2d3a4f] text-gray-300 flex items-center justify-center rounded hover:bg-[#2d3a4f] hover:text-[#ef5350] transition-colors shadow-lg" onclick="window.activateDrawingTool('clear')" title="Clear All">
<span class="material-symbols-outlined text-sm">clear_all</span>
</button>
<!-- Divider -->
<div class="w-8 h-px bg-[#2d3a4f] my-1"></div>
<!-- Toggle Toolbar -->
<button class="w-8 h-8 bg-[#2d3a4f] border border-[#2d3a4f] text-[#2962ff] flex items-center justify-center rounded hover:bg-[#2962ff]/20 transition-colors shadow-lg" id="toggleChartToolbar" title="Toggle Toolbar">
<span class="material-symbols-outlined text-sm">more_vert</span>
</button>
</div> </div>
<!-- Settings & Price Scale Controls - Bottom Right --> <!-- Trendline Settings Panel -->
<div class="absolute bottom-4 right-4 flex gap-2 z-10 opacity-0 hover:opacity-100 transition-opacity" id="priceScaleControls"> <div id="trendlineSettingsPanel" class="hidden absolute top-14 left-1/2 -translate-x-1/2 bg-[#1a2333] border border-[#2d3a4f] rounded-lg shadow-2xl z-50 w-[280px] p-0 text-sm font-['Inter']">
<button class="w-8 h-8 bg-[#1e222d] border border-[#2d3a4f] text-gray-300 flex items-center justify-center rounded hover:bg-[#2d3a4f] transition-colors shadow-lg" id="btnSettings" title="Settings"> <!-- Drag Handle -->
<span class="material-symbols-outlined text-sm">settings</span> <div id="tlPanelHeader" class="h-8 flex items-center justify-between px-3 border-b border-[#2d3a4f] cursor-move bg-[#1f2937] rounded-t-lg">
</button> <span class="text-[10px] uppercase tracking-wider font-bold text-gray-400">Settings</span>
<button class="text-gray-500 hover:text-white" onclick="window.toggleTLSettings(false)">
<span class="material-symbols-outlined text-sm">close</span>
</button>
</div>
<!-- Settings Popup --> <div class="p-4">
<div class="hidden absolute bottom-10 right-0 bg-[#1a2333] border border-[#2d3a4f] rounded-lg py-2 z-50 w-64 shadow-xl text-sm" id="settingsPopup"> <!-- Tabs -->
<!-- Reset Scale --> <div class="flex border-b border-[#2d3a4f] mb-4">
<div class="px-4 py-2 hover:bg-[#252f3f] cursor-pointer flex items-center gap-3" onclick="window.dashboard.chart.timeScale().fitContent()"> <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>
<span class="material-symbols-outlined text-sm">refresh</span> Reset price scale <button class="flex-1 py-2 text-center text-gray-400 hover:text-white" id="tlTabText" onclick="window.switchTLTab('text')">Text</button>
</div> </div>
<!-- Style Tab Content -->
<div id="tlContentStyle">
<!-- Color Grid (Line) -->
<div class="grid grid-cols-8 gap-1 mb-4" id="tlColorGrid">
<!-- Colors injected by JS -->
</div>
<!-- Opacity -->
<div class="mb-4">
<div class="flex justify-between text-xs text-gray-400 mb-1">
<span>Opacity</span>
<span id="tlOpacityValue">100%</span>
</div>
<input type="range" min="0" max="100" value="100" class="w-full h-1 bg-[#2d3a4f] rounded-lg appearance-none cursor-pointer" id="tlOpacityInput">
</div>
<!-- Thickness -->
<div class="mb-4">
<label class="text-xs text-gray-400 mb-1 block">Thickness</label>
<div class="flex bg-[#0d1421] rounded border border-[#2d3a4f] p-1">
<button class="flex-1 h-6 flex items-center justify-center hover:bg-[#2d3a4f] rounded data-[active=true]:bg-[#2d3a4f] tl-thickness-btn" onclick="window.setTLThickness(1)" data-thickness="1"><div class="w-4 h-[1px] bg-white"></div></button>
<button class="flex-1 h-6 flex items-center justify-center hover:bg-[#2d3a4f] rounded data-[active=true]:bg-[#2d3a4f] tl-thickness-btn" onclick="window.setTLThickness(2)" data-thickness="2"><div class="w-4 h-[2px] bg-white"></div></button>
<button class="flex-1 h-6 flex items-center justify-center hover:bg-[#2d3a4f] rounded data-[active=true]:bg-[#2d3a4f] tl-thickness-btn" onclick="window.setTLThickness(3)" data-thickness="3"><div class="w-4 h-[3px] bg-white"></div></button>
<button class="flex-1 h-6 flex items-center justify-center hover:bg-[#2d3a4f] rounded data-[active=true]:bg-[#2d3a4f] tl-thickness-btn" onclick="window.setTLThickness(4)" data-thickness="4"><div class="w-4 h-[4px] bg-white"></div></button>
</div>
</div>
<!-- Line Style -->
<div class="mb-4">
<label class="text-xs text-gray-400 mb-1 block">Line style</label>
<div class="flex bg-[#0d1421] rounded border border-[#2d3a4f] p-1">
<button class="flex-1 h-6 flex items-center justify-center hover:bg-[#2d3a4f] rounded data-[active=true]:bg-[#2d3a4f] tl-style-btn" onclick="window.setTLStyle(0)" data-style="0"><div class="w-6 border-b border-white"></div></button>
<button class="flex-1 h-6 flex items-center justify-center hover:bg-[#2d3a4f] rounded data-[active=true]:bg-[#2d3a4f] tl-style-btn" onclick="window.setTLStyle(1)" data-style="1"><div class="w-6 border-b border-dashed border-white"></div></button>
<button class="flex-1 h-6 flex items-center justify-center hover:bg-[#2d3a4f] rounded data-[active=true]:bg-[#2d3a4f] tl-style-btn" onclick="window.setTLStyle(2)" data-style="2"><div class="w-6 border-b border-dotted border-white"></div></button>
</div>
</div>
</div>
<!-- Text Tab Content -->
<div id="tlContentText" class="hidden">
<div class="flex gap-2 mb-4 relative">
<!-- Text Color Button -->
<div class="w-8 h-8 rounded border border-[#2d3a4f] cursor-pointer" id="tlTextColorBtn" style="background-color: #2962ff"></div>
<!-- Text Color Picker Popup -->
<div id="tlTextColorPicker" class="hidden absolute top-10 left-0 bg-[#1a2333] border border-[#2d3a4f] rounded shadow-2xl p-2 z-[60] w-[210px]">
<div class="grid grid-cols-8 gap-1" id="tlTextColorGrid">
<!-- Colors injected by JS -->
</div>
</div>
<!-- Font Size -->
<select id="tlFontSize" class="bg-[#0d1421] border border-[#2d3a4f] rounded text-xs px-2 h-8 text-white focus:outline-none">
<option value="10">10</option>
<option value="12">12</option>
<option value="14" selected>14</option>
<option value="16">16</option>
<option value="20">20</option>
<option value="24">24</option>
</select>
<!-- Style Toggles -->
<button class="w-8 h-8 border border-[#2d3a4f] rounded flex items-center justify-center hover:bg-[#2d3a4f] data-[active=true]:bg-blue-600" id="tlBoldBtn" onclick="window.toggleTLBold()">B</button>
<button class="w-8 h-8 border border-[#2d3a4f] rounded flex items-center justify-center hover:bg-[#2d3a4f] italic data-[active=true]:bg-blue-600" id="tlItalicBtn" onclick="window.toggleTLItalic()">I</button>
</div>
<textarea id="tlTextInput" class="w-full h-24 bg-[#0d1421] border border-[#2d3a4f] rounded p-2 text-xs text-white focus:border-blue-500 focus:outline-none mb-4" placeholder="Add text"></textarea>
<div class="flex items-center justify-between mb-4">
<label class="text-xs text-gray-400">Alignment</label>
<div class="flex gap-2">
<select id="tlAlignVert" class="bg-[#0d1421] border border-[#2d3a4f] rounded text-xs px-2 h-6 text-white focus:outline-none">
<option value="top">Top</option>
<option value="middle">Middle</option>
<option value="bottom">Bottom</option>
</select>
<select id="tlAlignHorz" class="bg-[#0d1421] border border-[#2d3a4f] rounded text-xs px-2 h-6 text-white focus:outline-none">
<option value="left">Left</option>
<option value="center">Center</option>
<option value="right">Right</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Overlay Controls -->
<div class="absolute bottom-4 right-4 flex gap-2 z-10 opacity-0 hover:opacity-100 transition-opacity" id="priceScaleControls">
<button class="w-8 h-8 bg-[#1e222d] border border-[#2d3a4f] text-gray-300 flex items-center justify-center rounded hover:bg-[#2d3a4f] transition-colors shadow-lg" id="btnSettings" title="Settings">
<span class="material-symbols-outlined text-sm">settings</span>
</button>
<!-- Settings Popup -->
<div class="hidden absolute bottom-10 right-0 bg-[#1a2333] border border-[#2d3a4f] rounded-lg py-2 z-50 w-64 shadow-xl text-sm" id="settingsPopup">
<!-- Reset Scale -->
<div class="px-4 py-2 hover:bg-[#252f3f] cursor-pointer flex items-center gap-3" onclick="window.dashboard.chart.timeScale().fitContent()">
<span class="material-symbols-outlined text-sm">refresh</span> Reset price scale
</div>
<hr class="border-[#2d3a4f] my-1"> <hr class="border-[#2d3a4f] my-1">
<!-- Scale Toggles --> <!-- Scale Toggles -->
@ -270,18 +358,14 @@
<span class="text-[10px] font-medium mt-1 opacity-70 group-hover:opacity-100">Strategy</span> <span class="text-[10px] font-medium mt-1 opacity-70 group-hover:opacity-100">Strategy</span>
</div> </div>
<div class="flex flex-col items-center justify-center w-full py-4 text-[#c3c5d8] hover:text-[#dfe2f2] cursor-pointer transition-colors group" onclick="window.toggleFullscreen()"> <div class="flex flex-col items-center justify-center w-full py-4 text-[#c3c5d8] hover:text-[#dfe2f2] cursor-pointer transition-colors group" onclick="window.toggleFullscreen()">
<span class="material-symbols-outlined group-hover:text-blue-400">fullscreen</span> <span class="material-symbols-outlined group-hover:text-blue-400">fullscreen</span>
<span class="text-[10px] font-medium mt-1 opacity-70 group-hover:opacity-100">Full</span> <span class="text-[10px] font-medium mt-1 opacity-70 group-hover:opacity-100">Full</span>
</div> </div>
<div class="flex flex-col items-center justify-center w-full py-4 text-[#2962ff] bg-[#2962ff]/10 border-r-2 border-[#2962ff] cursor-pointer" onclick="document.getElementById('chartToolbar').style.display === 'none' || !document.getElementById('chartToolbar') ? document.getElementById('chartToolbar').style.display = 'flex' : document.getElementById('chartToolbar').style.display = 'none'"> <div class="mt-auto flex flex-col items-center justify-center w-full py-4 text-[#c3c5d8] hover:text-[#dfe2f2] cursor-pointer transition-colors group">
<span class="material-symbols-outlined">draw</span> <span class="material-symbols-outlined group-hover:text-blue-400">more_horiz</span>
<span class="text-[10px] font-medium mt-1">Draw</span> <span class="text-[10px] font-medium mt-1 opacity-70 group-hover:opacity-100">More</span>
</div> </div>
<div class="mt-auto flex flex-col items-center justify-center w-full py-4 text-[#c3c5d8] hover:text-[#dfe2f2] cursor-pointer transition-colors group"> </nav>
<span class="material-symbols-outlined group-hover:text-blue-400">more_horiz</span>
<span class="text-[10px] font-medium mt-1 opacity-70 group-hover:opacity-100">More</span>
</div>
</nav>
</div> </div>
<!-- Bottom Navigation (Mobile Vertical) --> <!-- Bottom Navigation (Mobile Vertical) -->
@ -303,18 +387,14 @@
<span class="text-[10px] font-medium mt-1">Strategy</span> <span class="text-[10px] font-medium mt-1">Strategy</span>
</div> </div>
<div class="flex flex-col items-center justify-center min-w-[70px] flex-shrink-0 text-[#c3c5d8] hover:text-[#dfe2f2] hover:scale-105 transition-all duration-200 cursor-pointer group" onclick="window.toggleFullscreen()"> <div class="flex flex-col items-center justify-center min-w-[70px] flex-shrink-0 text-[#c3c5d8] hover:text-[#dfe2f2] hover:scale-105 transition-all duration-200 cursor-pointer group" onclick="window.toggleFullscreen()">
<span class="material-symbols-outlined group-hover:text-blue-400">fullscreen</span> <span class="material-symbols-outlined group-hover:text-blue-400">fullscreen</span>
<span class="text-[10px] font-medium mt-1">Full</span> <span class="text-[10px] font-medium mt-1">Full</span>
</div> </div>
<div class="flex flex-col items-center justify-center min-w-[70px] flex-shrink-0 text-[#2962ff] bg-[#2962ff]/10 rounded-xl px-4 py-1.5 transition-transform duration-200 cursor-pointer border border-[#2962ff]/20" onclick="document.getElementById('chartToolbar').style.display === 'none' || !document.getElementById('chartToolbar') ? document.getElementById('chartToolbar').style.display = 'flex' : document.getElementById('chartToolbar').style.display = 'none'"> <div class="flex flex-col items-center justify-center min-w-[70px] flex-shrink-0 text-[#c3c5d8] hover:text-[#dfe2f2] hover:scale-105 transition-all duration-200 cursor-pointer group">
<span class="material-symbols-outlined">draw</span> <span class="material-symbols-outlined group-hover:text-blue-400">more_horiz</span>
<span class="text-[10px] font-bold mt-1">Draw</span> <span class="text-[10px] font-medium mt-1">More</span>
</div> </div>
<div class="flex flex-col items-center justify-center min-w-[70px] flex-shrink-0 text-[#c3c5d8] hover:text-[#dfe2f2] hover:scale-105 transition-all duration-200 cursor-pointer group"> </nav>
<span class="material-symbols-outlined group-hover:text-blue-400">more_horiz</span>
<span class="text-[10px] font-medium mt-1">More</span>
</div>
</nav>
<!-- Sidebar Overlay (Acts as Modal) --> <!-- Sidebar Overlay (Acts as Modal) -->
<div id="rightSidebar" class="collapsed fixed top-[64px] right-0 md:right-[72px] bottom-[64px] md:bottom-0 w-full max-w-[370px] bg-[#1a2333] border-l border-[#2d3a4f] shadow-2xl z-40 flex flex-col"> <div id="rightSidebar" class="collapsed fixed top-[64px] right-0 md:right-[72px] bottom-[64px] md:bottom-0 w-full max-w-[370px] bg-[#1a2333] border-l border-[#2d3a4f] shadow-2xl z-40 flex flex-col">

View File

@ -149,8 +149,10 @@ function throttle(func, limit) {
} }
} }
import { DrawingManager } from './drawing-tools.js';
export class TradingDashboard { export class TradingDashboard {
constructor() { constructor() {
this.chart = null; this.chart = null;
this.candleSeries = null; this.candleSeries = null;
// Load settings from local storage or defaults // Load settings from local storage or defaults
@ -169,14 +171,13 @@ constructor() {
this.avgPriceSeries = null; this.avgPriceSeries = null;
this.dailyMAData = new Map(); // timestamp -> { ma44, ma125, price } this.dailyMAData = new Map(); // timestamp -> { ma44, ma125, price }
this.currentMouseTime = null; this.currentMouseTime = null;
this.drawingItems = []; // Store drawing items for management this.drawingManager = null;
// Throttled versions of heavy functions // Throttled versions of heavy functions
this.throttledOnVisibleRangeChange = throttle(this.onVisibleRangeChange.bind(this), 150); this.throttledOnVisibleRangeChange = throttle(this.onVisibleRangeChange.bind(this), 150);
this.init(); this.init();
} }
async loadDailyMAData() { async loadDailyMAData() {
try { try {
// Use 1d interval for this calculation // Use 1d interval for this calculation
@ -393,7 +394,7 @@ constructor() {
if (candleDownInput) candleDownInput.value = savedDownColor; if (candleDownInput) candleDownInput.value = savedDownColor;
// Calculate initial minMove based on saved precision // Calculate initial minMove based on saved precision
const initialMinMove = savedPrecision === 0 ? 1 : Number((1 / Math.pow(10, savedPrecision)).toFixed(savedPrecision)); const initialMinMove = savedPrecision === 0 ? 1 : Number((1 / Math.pow(10, savedPrecision)).toFixed(precision));
this.candleSeries = this.chart.addSeries(LightweightCharts.CandlestickSeries, { this.candleSeries = this.chart.addSeries(LightweightCharts.CandlestickSeries, {
upColor: savedUpColor, upColor: savedUpColor,
@ -454,6 +455,14 @@ constructor() {
this.initPriceScaleControls(); this.initPriceScaleControls();
this.initNavigationControls(); this.initNavigationControls();
// Initialize Drawing Manager
this.drawingManager = new DrawingManager(this, chartContainer);
window.activateDrawingTool = (tool, event) => {
const e = event || window.event;
this.drawingManager.setTool(tool, e);
};
// Setup price format selector change handler // Setup price format selector change handler
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const priceSelect = document.getElementById("priceFormatSelect"); const priceSelect = document.getElementById("priceFormatSelect");
@ -502,8 +511,6 @@ constructor() {
initPriceScaleControls() { initPriceScaleControls() {
const btnSettings = document.getElementById('btnSettings'); const btnSettings = document.getElementById('btnSettings');
const settingsPopup = document.getElementById('settingsPopup'); const settingsPopup = document.getElementById('settingsPopup');
const toggleToolbarBtn = document.getElementById('toggleChartToolbar');
const chartToolbar = document.getElementById('chartToolbar');
// Settings Popup Toggle and Outside Click // Settings Popup Toggle and Outside Click
if (btnSettings && settingsPopup) { if (btnSettings && settingsPopup) {
@ -525,16 +532,6 @@ constructor() {
} }
} }
// Toggle Chart Toolbar
if (toggleToolbarBtn && chartToolbar) {
toggleToolbarBtn.addEventListener('click', () => {
chartToolbar.style.display = chartToolbar.style.display === 'flex' ? 'none' : 'flex';
});
}
// Drawing Tools
this.initDrawingTools();
// Initialize state from storage // Initialize state from storage
this.scaleState = { this.scaleState = {
autoScale: localStorage.getItem('winterfail_scale_auto') !== 'false', autoScale: localStorage.getItem('winterfail_scale_auto') !== 'false',
@ -592,341 +589,6 @@ constructor() {
}); });
} }
initDrawingTools() {
const btnDrawingTools = document.getElementById('btnDrawingTools');
const drawingToolsPopup = document.getElementById('drawingToolsPopup');
if (btnDrawingTools && drawingToolsPopup) {
btnDrawingTools.addEventListener('click', (e) => {
e.stopPropagation();
drawingToolsPopup.classList.toggle('hidden');
});
document.addEventListener('click', closeDrawingToolsPopup);
document.addEventListener('touchstart', closeDrawingToolsPopup, { passive: true });
function closeDrawingToolsPopup(e) {
const isInside = drawingToolsPopup.contains(e.target) || e.target === btnDrawingTools;
const isDrawingButton = e.target.closest('#btnDrawingTools');
if (!isInside && !isDrawingButton) {
drawingToolsPopup.classList.add('hidden');
}
}
}
let isDrawing = false;
let drawMode = null;
let startPoint = null;
let drawLine = null;
let drawArrow = null;
let drawText = null;
window.activateDrawingTool = (tool) => {
if (tool === 'clear') {
this.clearDrawingTools();
return;
}
drawMode = tool;
isDrawing = true;
const chart = this.chart;
const container = chart.container();
const onMouseDown = (e) => {
e.preventDefault();
const rect = container.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (drawMode === 'trend_line' || drawMode === 'horizontal_line' || drawMode === 'vertical_line') {
this.createLine(x, y);
} else if (drawMode === 'arrow_up' || drawMode === 'arrow_down') {
this.createArrow(drawMode === 'arrow_up' ? 'arrowUp' : 'arrowDown', x, y);
} else if (drawMode === 'text') {
this.createText(x, y);
}
};
const onTouchStart = (e) => {
e.preventDefault();
const touch = e.touches[0];
const rect = container.getBoundingClientRect();
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;
if (drawMode === 'trend_line' || drawMode === 'horizontal_line' || drawMode === 'vertical_line') {
this.createLine(x, y);
} else if (drawMode === 'arrow_up' || drawMode === 'arrow_down') {
this.createArrow(drawMode === 'arrow_up' ? 'arrowUp' : 'arrowDown', x, y);
} else if (drawMode === 'text') {
this.createText(x, y);
}
};
container.addEventListener('mousedown', onMouseDown);
container.addEventListener('touchstart', onTouchStart);
const closeDrawing = () => {
container.removeEventListener('mousedown', onMouseDown);
container.removeEventListener('touchstart', onTouchStart);
document.removeEventListener('click', closeDrawing);
document.removeEventListener('touchstart', closeDrawing);
};
document.addEventListener('click', closeDrawing);
document.addEventListener('touchstart', closeDrawing);
drawingToolsPopup.classList.add('hidden');
};
}
createLine(x, y) {
const chart = this.chart;
const candleSeries = this.candleSeries;
let lineData = {
time: [],
value: []
};
const line = chart.addLineSeries({
color: '#2962ff',
lineWidth: 2,
lineStyle: LightweightCharts.LineStyle.Solid,
crosshairMarkerVisible: true,
crosshairMarkerRadius: 4,
baseIndex: 0,
});
let points = [];
const container = chart.container();
let startTime = null;
let startPrice = null;
const onMouseDown = (e) => {
e.preventDefault();
const rect = container.getBoundingClientRect();
const clientX = e.clientX;
const clientY = e.clientY;
const timeCoord = chart.timeScale().coordinateToTime(x);
const price = chart.priceScale().coordinateToPrice(y);
if (startTime === null) {
startTime = timeCoord;
startPrice = price;
line.setData([{
time: startTime,
value: startPrice
}]);
} else {
line.setData([
{ time: startTime, value: startPrice },
{ time: timeCoord, value: price }
]);
startTime = null;
startPrice = null;
}
};
const onTouchStart = (e) => {
e.preventDefault();
const touch = e.touches[0];
const rect = container.getBoundingClientRect();
const clientX = touch.clientX;
const clientY = touch.clientY;
const timeCoord = chart.timeScale().coordinateToTime(x);
const price = chart.priceScale().coordinateToPrice(y);
if (startTime === null) {
startTime = timeCoord;
startPrice = price;
line.setData([{
time: startTime,
value: startPrice
}]);
} else {
line.setData([
{ time: startTime, value: startPrice },
{ time: timeCoord, value: price }
]);
startTime = null;
startPrice = null;
}
};
container.addEventListener('mousedown', onMouseDown);
container.addEventListener('touchstart', onTouchStart);
const cleanup = () => {
container.removeEventListener('mousedown', onMouseDown);
container.removeEventListener('touchstart', onTouchStart);
};
document.addEventListener('click', cleanup);
document.addEventListener('touchstart', cleanup);
}
createArrow(arrowType, x, y) {
const chart = this.chart;
const container = chart.container();
const markerPrimitive = new SeriesMarkersPrimitive();
this.candleSeries.attachPrimitive(markerPrimitive);
const rect = container.getBoundingClientRect();
const time = chart.timeScale().coordinateToTime(x);
const price = chart.priceScale().coordinateToPrice(y);
const marker = {
time: time,
position: arrowType === 'arrowUp' ? 'belowBar' : 'aboveBar',
color: arrowType === 'arrowUp' ? '#26a69a' : '#ef5350',
shape: arrowType === 'arrowUp' ? 'arrowUp' : 'arrowDown',
text: ''
};
markerPrimitive.setMarkers([marker]);
let isDragging = false;
let startX = 0;
let startY = 0;
let startTime = time;
let startPrice = price;
const onMouseDown = (e) => {
e.preventDefault();
isDragging = true;
startX = e.clientX;
startY = e.clientY;
};
const onMouseMove = (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const rect = container.getBoundingClientRect();
const timeCoord = chart.timeScale().coordinateToTime(x + dx);
const newPrice = chart.priceScale().coordinateToPrice(y + dy);
const newMarker = {
time: timeCoord,
position: arrowType === 'arrowUp' ? 'belowBar' : 'aboveBar',
color: arrowType === 'arrowUp' ? '#26a69a' : '#ef5350',
shape: arrowType === 'arrowUp' ? 'arrowUp' : 'arrowDown',
text: ''
};
markerPrimitive.setMarkers([newMarker]);
};
const onMouseUp = () => {
isDragging = false;
};
container.addEventListener('mousedown', onMouseDown);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
const cleanup = () => {
container.removeEventListener('mousedown', onMouseDown);
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
document.removeEventListener('click', cleanup);
document.removeEventListener('touchstart', cleanup);
};
document.addEventListener('click', cleanup);
document.addEventListener('touchstart', cleanup);
}
createText(x, y) {
const chart = this.chart;
const container = chart.container();
const text = prompt('Enter text label:', '');
if (!text) return;
const rect = container.getBoundingClientRect();
const time = chart.timeScale().coordinateToTime(x);
const price = chart.priceScale().coordinateToPrice(y);
const label = chart.addLabel({
text: text,
color: '#ffffff',
fontSize: 11,
fontFamily: 'Inter',
fontWeight: 'bold',
crosshairMarkerVisible: false,
time: time,
price: price
});
let isDragging = false;
let startX = 0;
let startY = 0;
let startTime = time;
let startPrice = price;
const onMouseDown = (e) => {
e.preventDefault();
isDragging = true;
startX = e.clientX;
startY = e.clientY;
};
const onMouseMove = (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newTime = chart.timeScale().coordinateToTime(x + dx);
const newPrice = chart.priceScale().coordinateToPrice(y + dy);
label.applyOptions({
time: newTime,
price: newPrice
});
};
const onMouseUp = () => {
isDragging = false;
};
container.addEventListener('mousedown', onMouseDown);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
const cleanup = () => {
container.removeEventListener('mousedown', onMouseDown);
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
document.removeEventListener('click', cleanup);
document.removeEventListener('touchstart', cleanup);
};
document.addEventListener('click', cleanup);
document.addEventListener('touchstart', cleanup);
}
clearDrawingTools() {
const allSeries = this.chart.series();
allSeries.forEach(series => {
if (series.type !== 'Candlestick' && series.type !== 'Line' && series.type !== 'Area') {
this.chart.removeSeries(series);
}
});
}
initNavigationControls() { initNavigationControls() {
const chartWrapper = document.getElementById('chartWrapper'); const chartWrapper = document.getElementById('chartWrapper');
const navLeft = document.getElementById('navLeft'); const navLeft = document.getElementById('navLeft');
@ -1126,9 +788,12 @@ async loadNewData() {
//console.log(`[NewData Load] Added ${chartData.length} new candles, total in dataset: ${this.allData.get(this.currentInterval).length}`); //console.log(`[NewData Load] Added ${chartData.length} new candles, total in dataset: ${this.allData.get(this.currentInterval).length}`);
// Auto-scrolling disabled per user request
/*
if (atEdge) { if (atEdge) {
this.chart.timeScale().scrollToRealTime(); this.chart.timeScale().scrollToRealTime();
} }
*/
this.updateStats(latest); this.updateStats(latest);

1404
js/ui/drawing-tools.js Normal file

File diff suppressed because it is too large Load Diff