trendline selection issue
This commit is contained in:
parent
eaf118ef8b
commit
18585e42cd
5 changed files with 85 additions and 14 deletions
|
|
@ -1104,6 +1104,23 @@ stored as 1d and therefore remain global because their original timeframe is
|
|||
unknowable. Integration coverage creates all four non-trendline drawing types,
|
||||
switches to 1d, and verifies the setting hides and restores them together.
|
||||
|
||||
### 2026-08-15 — checked in the list was not active on the chart
|
||||
|
||||
The Drawings checkboxes support bulk operations, but `selectedDrawing` was set
|
||||
to null whenever more than one checkbox was checked. Every checked row retained
|
||||
the same selected border while no line owned the handles, focus animation, or
|
||||
drag target. Duplicated lines had a related path: duplication assigned the new
|
||||
id programmatically, but the watcher called `setSelectedLine()` rather than
|
||||
`focusLine()`, so the expected glow never ran.
|
||||
|
||||
Bulk membership and the active chart drawing are now separate. Checking a row
|
||||
adds it to the bulk set and makes it the active drawing; unchecking the active
|
||||
row falls back to the most recently selected remaining id. Clicking a row makes
|
||||
it the sole selection. The active row has a distinct inset marker, and every
|
||||
selection path, including duplication, runs the same focus method. Browser
|
||||
coverage checks two rows simultaneously, requires the newest one to own the
|
||||
focus and hit polyline, and starts a real pointer drag on that line.
|
||||
|
||||
### 2026-08-15 — 1m zoom-out died at ~1am because the socket sent 1,000 bars
|
||||
|
||||
Compressing the 1m time scale stopped around 01:00, then looked empty.
|
||||
|
|
|
|||
|
|
@ -687,7 +687,6 @@ createApp({
|
|||
const id = chartApi.hitTest(param);
|
||||
selectedDrawing.value = id;
|
||||
selectedDrawings.value = id ? [id] : [];
|
||||
if (id) chartApi.focusLine(id);
|
||||
}
|
||||
|
||||
async function handleToolComplete(result) {
|
||||
|
|
@ -1115,15 +1114,17 @@ createApp({
|
|||
}
|
||||
|
||||
function toggleDrawingSelection(id) {
|
||||
selectedDrawings.value = selectedDrawings.value.includes(id)
|
||||
const removing = selectedDrawings.value.includes(id);
|
||||
selectedDrawings.value = removing
|
||||
? selectedDrawings.value.filter(value => value !== id)
|
||||
: [...selectedDrawings.value, id];
|
||||
selectedDrawing.value = selectedDrawings.value.length === 1 ? selectedDrawings.value[0] : null;
|
||||
if (selectedDrawing.value === id) {
|
||||
const item = drawings.value.find(drawing => drawing.id === id);
|
||||
if (item?.comment) chartApi?.focusComment(id);
|
||||
else chartApi?.focusLine(id);
|
||||
if (!removing) selectedDrawing.value = id;
|
||||
else if (selectedDrawing.value === id) selectedDrawing.value = selectedDrawings.value.at(-1) || null;
|
||||
}
|
||||
|
||||
function activateDrawing(id) {
|
||||
selectedDrawings.value = [id];
|
||||
selectedDrawing.value = id;
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
|
|
@ -1460,9 +1461,11 @@ createApp({
|
|||
watch(selectedDrawing, id => {
|
||||
if (chartApi) {
|
||||
chartApi.setSelectedDrawing(id);
|
||||
chartApi.setSelectedLine(
|
||||
levels.value.some(level => level.id === id) || fibs.value.some(fib => fib.id === id) ? id : null,
|
||||
);
|
||||
const lineLike = levels.value.some(level => level.id === id)
|
||||
|| fibs.value.some(fib => fib.id === id);
|
||||
if (lineLike && id) chartApi.focusLine(id);
|
||||
else chartApi.setSelectedLine(null);
|
||||
if (comments.value.some(comment => comment.id === id)) chartApi.focusComment(id);
|
||||
}
|
||||
revealSelectedDrawing(id);
|
||||
});
|
||||
|
|
@ -1539,6 +1542,6 @@ createApp({
|
|||
window.removeEventListener('keydown', handleKeydown);
|
||||
});
|
||||
|
||||
return { status, price, sessionOpen, quoteChange, animateCurrentPrice, autoScrollLivePrice, extraDetail, hideLowerTfDrawings, barAge, dataUpdatedAt, buildStamp, timeframe, timeframes, drawingColors, drawingColorRows, drawingColorName, colorRowLabels, symbolChoices, selectedSymbol, symbolColor, symbolScale, symbolScales, symbolPanelOpen, prefs, clusters, clustersByPrice, events, eventsMore, loadOlderEvents, diagnosticMode, captureBusy, captureDiagnostic, armedTool, drawName, drawColor, drawWidth, drawSide, snap, selectedDrawing, selectedDrawings, drawingList, startDrawingListResize, manualLines, hasDrawingSelection, allShownSelected, selectedAreHidden, alertPrice, alertNote, alertEarlyPoints, levelColor, levelWidth, addPriceAlert, armTool, selectTimeframe, allEnabled, toggleGroup, maValue, maAlertOn, toggleMaAlert, deleteSelected, deleteLine, toggleDrawingSelection, toggleSelectAll, toggleSelectedVisibility, renameLine, updateLineStyle, updateLevelNumber, setArmed, commentText, commentFloat, comments, drawings, filteredDrawings, drawingFilter, drawingKind, drawingTf, deleteDrawing, toggleComment, togglePinned, chooseSymbol, toggleSymbolPanel, startSymbolDrag, dropSymbol, optionExpirations, optionExpiryId, optionSide, optionMode, optionMin, optionMax, optionContracts, optionUnderlying, optionBusy, optionError, optionSearched, optionCopied, onOptionsToggle, searchOptions, copyOption, canUndo, undoTitle, undo };
|
||||
return { status, price, sessionOpen, quoteChange, animateCurrentPrice, autoScrollLivePrice, extraDetail, hideLowerTfDrawings, barAge, dataUpdatedAt, buildStamp, timeframe, timeframes, drawingColors, drawingColorRows, drawingColorName, colorRowLabels, symbolChoices, selectedSymbol, symbolColor, symbolScale, symbolScales, symbolPanelOpen, prefs, clusters, clustersByPrice, events, eventsMore, loadOlderEvents, diagnosticMode, captureBusy, captureDiagnostic, armedTool, drawName, drawColor, drawWidth, drawSide, snap, selectedDrawing, selectedDrawings, drawingList, startDrawingListResize, manualLines, hasDrawingSelection, allShownSelected, selectedAreHidden, alertPrice, alertNote, alertEarlyPoints, levelColor, levelWidth, addPriceAlert, armTool, selectTimeframe, allEnabled, toggleGroup, maValue, maAlertOn, toggleMaAlert, deleteSelected, deleteLine, toggleDrawingSelection, activateDrawing, toggleSelectAll, toggleSelectedVisibility, renameLine, updateLineStyle, updateLevelNumber, setArmed, commentText, commentFloat, comments, drawings, filteredDrawings, drawingFilter, drawingKind, drawingTf, deleteDrawing, toggleComment, togglePinned, chooseSymbol, toggleSymbolPanel, startSymbolDrag, dropSymbol, optionExpirations, optionExpiryId, optionSide, optionMode, optionMin, optionMax, optionContracts, optionUnderlying, optionBusy, optionError, optionSearched, optionCopied, onOptionsToggle, searchOptions, copyOption, canUndo, undoTitle, undo };
|
||||
},
|
||||
}).mount('#app');
|
||||
|
|
|
|||
|
|
@ -289,7 +289,8 @@
|
|||
|
||||
<div v-for="item in filteredDrawings" :key="item.id" class="trendline-row"
|
||||
:data-drawing-id="item.id"
|
||||
:class="{selected: selectedDrawings.includes(item.id), 'hidden-drawing': item.hidden}">
|
||||
:class="{selected: selectedDrawings.includes(item.id), active: selectedDrawing === item.id, 'hidden-drawing': item.hidden}"
|
||||
@click="activateDrawing(item.id)">
|
||||
<input class="line-select" type="checkbox"
|
||||
:checked="selectedDrawings.includes(item.id)" :aria-label="`Select drawing ${item.number}`"
|
||||
@click.stop @change="toggleDrawingSelection(item.id)">
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ aside { padding:16px; }h2 { margin:0 0 12px; color:var(--muted); font-size:11px;
|
|||
.drawing-list-resize::after { content:''; display:block; width:36px; height:3px; margin:3px auto 0; border-radius:1px; background:var(--muted); }
|
||||
.drawing-list-resize:hover::after { background:var(--accent); }
|
||||
.trendline-actions { display:grid; grid-template-columns:auto 1fr auto auto; align-items:center; gap:4px; margin-bottom:7px; }.trendline-actions button { padding:3px 5px; font-size:8px; white-space:nowrap; }.selection-count { color:var(--muted); font-size:8px; text-align:center; white-space:nowrap; }
|
||||
.trendline-row { display:grid; grid-template-columns:14px minmax(0,1fr); gap:4px; padding:2px 4px; border:1px solid transparent; border-bottom-color:var(--line); }.trendline-row.selected { border-color:var(--accent); }.trendline-row.hidden-drawing .drawing-content { opacity:.48; }.trendline-row.hidden-drawing .drawing-secondary>span::before { content:'HIDDEN · '; }.trendline-row>.line-select { align-self:center; width:12px; height:12px; margin:0; accent-color:var(--accent); }.trendline-row>.drawing-icon { align-self:center; }
|
||||
.trendline-row { display:grid; grid-template-columns:14px minmax(0,1fr); gap:4px; padding:2px 4px; border:1px solid transparent; border-bottom-color:var(--line); }.trendline-row.selected { border-color:var(--accent); }.trendline-row.active { background:color-mix(in srgb,var(--accent) 9%,transparent); box-shadow:inset 2px 0 var(--accent); }.trendline-row.hidden-drawing .drawing-content { opacity:.48; }.trendline-row.hidden-drawing .drawing-secondary>span::before { content:'HIDDEN · '; }.trendline-row>.line-select { align-self:center; width:12px; height:12px; margin:0; accent-color:var(--accent); }.trendline-row>.drawing-icon { align-self:center; }
|
||||
.drawing-content { min-width:0; display:grid; gap:1px; }.drawing-primary,.drawing-secondary { display:flex; align-items:center; min-width:0; }.drawing-primary { gap:3px; }.drawing-primary>input { flex:1; min-width:0; height:20px; padding:1px 3px; border:0; border-bottom:1px solid var(--line); background:transparent; color:var(--fg); font:inherit; font-size:10px; }.drawing-secondary { justify-content:space-between; gap:5px; min-height:19px; }.drawing-secondary>span { overflow:hidden; color:var(--muted); font-size:8px; letter-spacing:.25px; text-transform:uppercase; white-space:nowrap; text-overflow:ellipsis; }
|
||||
.drawing-state { position:relative; display:grid; place-items:center; flex:none; width:20px; height:20px; color:var(--accent); cursor:pointer; }.drawing-state.off { color:var(--muted); }.drawing-state input { position:absolute; opacity:0; pointer-events:none; }.drawing-delete,.collapse-toggle { display:grid; place-items:center; flex:none; width:20px; height:20px; padding:0; border:0; background:transparent; color:var(--muted); font-size:9px; cursor:pointer; }.drawing-delete:hover { color:var(--red); }
|
||||
.drawing-controls { display:flex; align-items:center; gap:3px; flex:none; }.drawing-controls select { width:31px; height:19px; padding:0 2px; border:1px solid var(--line); border-radius:3px; background:var(--panel); color:var(--fg); font:inherit; font-size:8px; }.drawing-controls select.mark-scale { width:38px; }.color-picker { position:relative; height:19px; }.color-picker>summary { width:19px; height:19px; border:1px solid var(--line); border-radius:3px; cursor:pointer; list-style:none; }.color-picker>summary::-webkit-details-marker { display:none; }.color-picker:not([open])>.color-popover { display:none; }.color-popover { position:absolute; right:0; bottom:24px; z-index:20; display:grid; grid-template-columns:repeat(4,18px); gap:3px; width:95px; padding:6px; border:1px solid var(--line); border-radius:5px; background:var(--panel); box-shadow:0 5px 18px color-mix(in srgb,var(--fg) 18%,transparent); }.color-popover>button,.custom-color { width:18px; height:18px; padding:0; border:1px solid color-mix(in srgb,var(--fg) 20%,transparent); border-radius:2px; cursor:pointer; }.color-popover>button.selected { outline:2px solid var(--fg); outline-offset:1px; }.color-popover>.palette-close { grid-column:4; background:var(--chart-bg); color:var(--muted); }.color-popover>.palette-close:hover { color:var(--fg); }.custom-color { position:relative; display:grid; place-items:center; background:var(--chart-bg); color:var(--muted); font-size:9px; }.custom-color input { position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:pointer; }
|
||||
|
|
|
|||
|
|
@ -66,6 +66,56 @@ test('the timeframe visibility setting governs every drawing type',
|
|||
});
|
||||
});
|
||||
|
||||
test('the newest checked trendline stays active and movable during bulk selection',
|
||||
{ timeout: 180000 }, async () => {
|
||||
await withChart(async page => {
|
||||
const ids = await page.evaluate(async () => {
|
||||
const bars = (await (await fetch('/api/bars?tf=1m&limit=30')).json()).bars;
|
||||
const create = async (first, second, suffix) => (await (await fetch('/api/lines', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
tf: '1m', side: 'support', anchor_t: first.t, anchor_p: first.l,
|
||||
end_t: second.t, end_p: second.l + 0.25, note: `active ${suffix} ${Date.now()}`,
|
||||
}),
|
||||
})).json()).id;
|
||||
return [
|
||||
await create(bars.at(-25), bars.at(-18), 'one'),
|
||||
await create(bars.at(-15), bars.at(-8), 'two'),
|
||||
];
|
||||
});
|
||||
const row = id => page.locator(`.trendline-row[data-drawing-id="${id}"]`);
|
||||
await page.waitForFunction(values => values.every(id =>
|
||||
document.querySelector(`.trendline-row[data-drawing-id="${id}"]`)), ids);
|
||||
await row(ids[0]).locator('.line-select').check();
|
||||
await row(ids[1]).locator('.line-select').check();
|
||||
await page.waitForFunction(id => window.__chart.selectedLineId === id
|
||||
&& !window.__chart.lineHitTarget.hasAttribute('hidden'), ids[1]);
|
||||
|
||||
assert.equal(await row(ids[0]).evaluate(node => node.classList.contains('selected')), true);
|
||||
assert.equal(await row(ids[1]).evaluate(node => node.classList.contains('selected')), true);
|
||||
assert.equal(await row(ids[0]).evaluate(node => node.classList.contains('active')), false);
|
||||
assert.equal(await row(ids[1]).evaluate(node => node.classList.contains('active')), true);
|
||||
assert.equal(await page.locator('.chart-line-focus').getAttribute('hidden'), null,
|
||||
'programmatic selection did not trigger the focus glow');
|
||||
|
||||
const target = await page.evaluate(() => {
|
||||
const chart = window.__chart;
|
||||
const hit = chart.lineHitTarget;
|
||||
const plot = chart.plotCanvas().getBoundingClientRect();
|
||||
return {
|
||||
x: plot.left + (Number(hit.getAttribute('x1')) + Number(hit.getAttribute('x2'))) / 2,
|
||||
y: plot.top + (Number(hit.getAttribute('y1')) + Number(hit.getAttribute('y2'))) / 2,
|
||||
};
|
||||
});
|
||||
await page.mouse.move(target.x, target.y);
|
||||
await page.mouse.down();
|
||||
assert.equal(await page.evaluate(() => window.__chart.draggingLine?.id), ids[1],
|
||||
'the active selected line did not own the drag target');
|
||||
await page.mouse.up();
|
||||
assertNoPageErrors(page, assert);
|
||||
});
|
||||
});
|
||||
|
||||
test('editing a drawing name with Backspace or Delete cannot delete the drawing',
|
||||
{
|
||||
timeout: 180000,
|
||||
|
|
|
|||
Loading…
Reference in a new issue