Add a Drawings layer toggle that hides lines and comments
This commit is contained in:
parent
1ee2a96fc0
commit
d9a272760b
7 changed files with 59 additions and 5 deletions
|
|
@ -23,7 +23,7 @@ def level_enabled(level, enabled: dict) -> bool:
|
|||
if kind == "ma":
|
||||
return level.period in enabled.get("ma", {}).get(level.tf.value, [])
|
||||
if kind == "manual":
|
||||
return enabled.get("manual", True)
|
||||
return enabled.get("drawings", True) and enabled.get("manual", True)
|
||||
if kind == "trendline":
|
||||
return enabled.get("auto", False)
|
||||
if kind == "horizontal":
|
||||
|
|
|
|||
|
|
@ -1153,3 +1153,11 @@ Written because they were skipped the first time and then asked for.
|
|||
- Daily MA bells in Layers, independent of visibility, cooldown not
|
||||
one-shot. Watches in `data/user_prefs.json`. See
|
||||
`docs/plan_dma_alerts.md`.
|
||||
|
||||
### 2026-08-18 — Drawings layer
|
||||
|
||||
Layers now has a **Drawings** checkbox. Off hides every user drawing on the
|
||||
chart — trendlines, price levels, and comments — and (unless "Hidden levels
|
||||
still count toward confluence" is on) keeps those lines out of clusters too.
|
||||
Manual lines stays as the finer control for lines only. Old stored prefs
|
||||
without the key keep drawings on.
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ async function apiFetch(url, options = {}) {
|
|||
|
||||
const defaultPrefs = {
|
||||
base_tf: '1m',
|
||||
enabled: { ma: { '1d': [10, 20, 50, 100, 200] }, manual: true, auto: false, horizontal: true, vwap: true },
|
||||
enabled: { ma: { '1d': [10, 20, 50, 100, 200] }, drawings: true, manual: true, auto: false, horizontal: true, vwap: true },
|
||||
hidden_levels_score: false,
|
||||
};
|
||||
|
||||
|
|
@ -667,7 +667,11 @@ createApp({
|
|||
}
|
||||
|
||||
function syncComments() {
|
||||
if (chartApi) chartApi.setComments(comments.value.filter(item => !item.hidden));
|
||||
if (!chartApi) return;
|
||||
const visible = prefs.value.enabled.drawings
|
||||
? comments.value.filter(item => !item.hidden)
|
||||
: [];
|
||||
chartApi.setComments(visible);
|
||||
}
|
||||
|
||||
async function createComment(result) {
|
||||
|
|
@ -1097,7 +1101,9 @@ createApp({
|
|||
|
||||
function enabled(level) {
|
||||
if (level.kind === 'ma') return (prefs.value.enabled.ma[level.tf] || []).includes(level.period);
|
||||
if (level.kind === 'manual') return prefs.value.enabled.manual;
|
||||
if (level.kind === 'manual') {
|
||||
return prefs.value.enabled.drawings && prefs.value.enabled.manual;
|
||||
}
|
||||
if (level.kind === 'horizontal') return prefs.value.enabled.horizontal;
|
||||
if (level.kind === 'vwap') return prefs.value.enabled.vwap;
|
||||
return prefs.value.enabled.auto;
|
||||
|
|
@ -1148,6 +1154,7 @@ createApp({
|
|||
watch(prefs, () => {
|
||||
localStorage.setItem('chart-layer-prefs', JSON.stringify(prefs.value));
|
||||
syncVisibleLevels();
|
||||
syncComments();
|
||||
sendPrefs();
|
||||
}, { deep: true });
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@
|
|||
<label><input type="checkbox" v-model="prefs.enabled.horizontal"><span class="swatch horizontal"></span>Prior day H/L/C</label>
|
||||
<label><input type="checkbox" v-model="prefs.enabled.vwap"><span class="swatch vwap"></span>Session VWAP</label>
|
||||
</div>
|
||||
<div class="layer-group">
|
||||
<label><input type="checkbox" v-model="prefs.enabled.drawings"><span class="swatch drawings"></span>Drawings</label>
|
||||
</div>
|
||||
<div class="layer-group layer-inline">
|
||||
<label><input type="checkbox" v-model="prefs.enabled.manual"><span class="swatch manual"></span>Manual lines</label>
|
||||
<label class="disabled" title="Automatic trendline detection is not built yet">(<input type="checkbox" disabled>auto)</label>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ aside { padding:16px; }h2 { margin:0 0 12px; color:var(--muted); font-size:11px;
|
|||
.color-popover { grid-template-columns:1fr; width:178px; gap:3px; }
|
||||
.color-family-row { display:grid; grid-template-columns:70px 1fr; align-items:center; gap:5px; min-width:0; }.color-family-row:nth-child(3),.color-family-row:nth-child(5) { margin-top:4px; padding-top:4px; border-top:1px solid var(--line); }.color-family-input { width:70px; height:18px; padding:1px 3px; border:1px solid transparent; border-radius:2px; background:transparent; color:var(--muted); font:inherit; font-size:8px; }.color-family-input:hover,.color-family-input:focus { border-color:var(--line); background:var(--chart-bg); color:var(--fg); outline:0; }.color-family-swatches { display:grid; grid-template-columns:repeat(4,18px); gap:3px; }.color-family-swatches>button,.color-popover-footer button { width:18px; height:18px; padding:0; border:1px solid color-mix(in srgb,var(--fg) 20%,transparent); border-radius:2px; cursor:pointer; }.color-family-swatches>button.selected { outline:2px solid var(--fg); outline-offset:1px; }.color-popover-footer { display:grid; grid-template-columns:18px 1fr 18px; align-items:center; gap:3px; margin-top:4px; padding-top:5px; border-top:1px solid var(--line); }.color-popover-footer .palette-close { grid-column:3; color:var(--muted); background:var(--chart-bg); }
|
||||
.level-editors { display:flex; gap:4px; overflow:visible; text-transform:none; }.level-editors label { display:flex; align-items:center; gap:2px; }.level-editors input { width:54px; height:19px; padding:1px 3px; border:1px solid var(--line); border-radius:3px; background:var(--panel); color:var(--fg); font:inherit; font-size:8px; }
|
||||
.layer-group { padding:9px 0; border-bottom:1px solid var(--line); display:grid; gap:7px; }.layer-group label,.score-hidden { display:flex; align-items:center; gap:7px; font-size:11px; cursor:pointer; }.layer-group input,.score-hidden input { accent-color:var(--accent); }.periods { display:grid; grid-template-columns:repeat(3, max-content); gap:7px 10px; padding-left:20px; }.periods .period { display:flex; align-items:center; gap:2px; }.periods label { color:var(--muted); gap:4px; }.periods input { width:12px; height:12px; margin:0; flex:none; }.ma-alert { display:grid; place-items:center; width:16px; height:16px; padding:0; border:0; background:transparent; color:var(--muted); font-size:9px; }.ma-alert.on { color:var(--accent); }.layer-inline { display:flex; align-items:center; gap:12px; }.layer-inline .disabled { gap:2px; }.swatch { width:13px; height:3px; display:inline-block; background:var(--muted); }.tf-1d { background:#d96073; }.tf-1h { background:#efb643; }.manual { background:#65b7cf; }.vwap { background:#b07ad6; }.horizontal { background:#9fb0c4; }
|
||||
.layer-group { padding:9px 0; border-bottom:1px solid var(--line); display:grid; gap:7px; }.layer-group label,.score-hidden { display:flex; align-items:center; gap:7px; font-size:11px; cursor:pointer; }.layer-group input,.score-hidden input { accent-color:var(--accent); }.periods { display:grid; grid-template-columns:repeat(3, max-content); gap:7px 10px; padding-left:20px; }.periods .period { display:flex; align-items:center; gap:2px; }.periods label { color:var(--muted); gap:4px; }.periods input { width:12px; height:12px; margin:0; flex:none; }.ma-alert { display:grid; place-items:center; width:16px; height:16px; padding:0; border:0; background:transparent; color:var(--muted); font-size:9px; }.ma-alert.on { color:var(--accent); }.layer-inline { display:flex; align-items:center; gap:12px; }.layer-inline .disabled { gap:2px; }.swatch { width:13px; height:3px; display:inline-block; background:var(--muted); }.tf-1d { background:#d96073; }.tf-1h { background:#efb643; }.manual { background:#65b7cf; }.drawings { background:#c4a36a; }.vwap { background:#b07ad6; }.horizontal { background:#9fb0c4; }
|
||||
.hint { margin:6px 0 2px; font-size:10px; color:var(--muted); line-height:1.35; }
|
||||
|
||||
/* Tool palette: the head arms the tool, the body configures what it creates. */
|
||||
|
|
|
|||
|
|
@ -132,6 +132,28 @@ test('a floating comment holds its position while the chart scrolls', { timeout:
|
|||
});
|
||||
});
|
||||
|
||||
test('unchecking Drawings hides comments on the chart', { timeout: 180000 }, async () => {
|
||||
await withChart(async page => {
|
||||
const box = await chartBox(page);
|
||||
await placeComment(page, box, 0.5, 0.4);
|
||||
assert.ok(await commentNode(page), 'the comment was not created');
|
||||
|
||||
await page.locator('details:has(summary:text-is("Layers"))').evaluate(node => { node.open = true; });
|
||||
const toggle = page.getByText('Drawings', { exact: true }).locator('input');
|
||||
assert.equal(await toggle.isChecked(), true);
|
||||
await toggle.click();
|
||||
await page.waitForFunction(text =>
|
||||
![...document.querySelectorAll('.chart-comment')].some(n => n.title === text), TEXT);
|
||||
assert.equal(await commentNode(page), null, 'the comment stayed visible with Drawings off');
|
||||
|
||||
await toggle.click();
|
||||
await page.waitForFunction(text =>
|
||||
[...document.querySelectorAll('.chart-comment')].some(n => n.title === text), TEXT);
|
||||
assert.ok(await commentNode(page), 'the comment did not return with Drawings on');
|
||||
assertNoPageErrors(page, assert);
|
||||
});
|
||||
});
|
||||
|
||||
test('a comment is never a level', { timeout: 180000 }, async () => {
|
||||
await withChart(async page => {
|
||||
const box = await chartBox(page);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,20 @@ def ma(id_: str, period: int, price: float) -> Level:
|
|||
)
|
||||
|
||||
|
||||
def test_drawings_off_hides_manual_lines_from_confluence():
|
||||
from app.api.ws import level_enabled
|
||||
|
||||
line = Level(
|
||||
"ml1", LevelKind.MANUAL, Timeframe.D1, Side.RESISTANCE, 12, 1,
|
||||
"line", 1000, 100.0, 0, None, 0, 1000, 1000, False, False,
|
||||
)
|
||||
assert level_enabled(line, {"manual": True, "drawings": True})
|
||||
assert not level_enabled(line, {"manual": True, "drawings": False})
|
||||
assert not level_enabled(line, {"manual": False, "drawings": True})
|
||||
# Prefs written before the drawings key still show manuals.
|
||||
assert level_enabled(line, {"manual": True})
|
||||
|
||||
|
||||
def test_websocket_layer_preferences_are_isolated_per_connection(tmp_path):
|
||||
app = FastAPI()
|
||||
app.include_router(ws_router)
|
||||
|
|
|
|||
Loading…
Reference in a new issue