compact drawing list
This commit is contained in:
parent
9fc6402f03
commit
8ca774f821
5 changed files with 111 additions and 38 deletions
|
|
@ -133,6 +133,12 @@ createApp({
|
||||||
const levelColor = ref('#e0a34a');
|
const levelColor = ref('#e0a34a');
|
||||||
const levelWidth = ref(2);
|
const levelWidth = ref(2);
|
||||||
const timeframes = ['1m', '5m', '15m', '30m', '1h', '1d'];
|
const timeframes = ['1m', '5m', '15m', '30m', '1h', '1d'];
|
||||||
|
const drawingColors = [
|
||||||
|
'#65b7cf', '#27825c', '#45c39b', '#a8c85d',
|
||||||
|
'#efb643', '#e0a34a', '#d97a3a', '#bd4545',
|
||||||
|
'#d96073', '#b07ad6', '#7d67c9', '#4f6fae',
|
||||||
|
'#9fb0c4', '#746c60', '#2c2924', '#c8992f',
|
||||||
|
];
|
||||||
const now = ref(Date.now());
|
const now = ref(Date.now());
|
||||||
let chartApi = null;
|
let chartApi = null;
|
||||||
let socket = null;
|
let socket = null;
|
||||||
|
|
@ -647,6 +653,6 @@ createApp({
|
||||||
window.removeEventListener('keydown', handleKeydown);
|
window.removeEventListener('keydown', handleKeydown);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { status, price, barAge, dataUpdatedAt, timeframe, timeframes, prefs, clusters, alerts, armedTool, drawName, drawColor, drawWidth, drawSide, snap, selectedLine, selectedLines, manualLines, hasLineSelection, allManualSelected, alertPrice, alertNote, levelColor, levelWidth, addPriceAlert, armTool, selectTimeframe, allEnabled, toggleGroup, deleteSelected, deleteLine, selectLine, toggleLineSelection, toggleSelectAll, deleteSelectedLines, renameLine, updateLineStyle, setArmed, commentText, commentFloat, comments, drawings, filteredDrawings, drawingFilter, drawingKind, deleteDrawing, deleteFilteredDrawings, toggleComment, togglePinned };
|
return { status, price, barAge, dataUpdatedAt, timeframe, timeframes, drawingColors, prefs, clusters, alerts, armedTool, drawName, drawColor, drawWidth, drawSide, snap, selectedLine, selectedLines, manualLines, hasLineSelection, allManualSelected, alertPrice, alertNote, levelColor, levelWidth, addPriceAlert, armTool, selectTimeframe, allEnabled, toggleGroup, deleteSelected, deleteLine, selectLine, toggleLineSelection, toggleSelectAll, deleteSelectedLines, renameLine, updateLineStyle, setArmed, commentText, commentFloat, comments, drawings, filteredDrawings, drawingFilter, drawingKind, deleteDrawing, deleteFilteredDrawings, toggleComment, togglePinned };
|
||||||
},
|
},
|
||||||
}).mount('#app');
|
}).mount('#app');
|
||||||
|
|
|
||||||
|
|
@ -133,35 +133,75 @@
|
||||||
@click.stop @change="toggleLineSelection(item.id)">
|
@click.stop @change="toggleLineSelection(item.id)">
|
||||||
<span v-else class="line-select drawing-icon"><i class="fa-solid fa-note-sticky"></i></span>
|
<span v-else class="line-select drawing-icon"><i class="fa-solid fa-note-sticky"></i></span>
|
||||||
|
|
||||||
<input v-if="item.kind !== 'comment'" :value="item.line.label" aria-label="Drawing name"
|
<div class="drawing-content">
|
||||||
@click.stop @change="renameLine(item.line, $event.target.value)">
|
<div class="drawing-primary">
|
||||||
<input v-else :value="item.comment.note" aria-label="Comment text"
|
<input v-if="item.kind !== 'comment'" :value="item.line.label" aria-label="Drawing name"
|
||||||
@click.stop @change="renameLine(item.comment, $event.target.value)">
|
@click.stop @change="renameLine(item.line, $event.target.value)">
|
||||||
|
<input v-else :value="item.comment.note" aria-label="Comment text"
|
||||||
|
@click.stop @change="renameLine(item.comment, $event.target.value)">
|
||||||
|
<label v-if="item.kind === 'comment'" class="drawing-state" @click.stop
|
||||||
|
:title="item.comment.pinned ? 'Pinned to its bar' : 'Floating on screen'">
|
||||||
|
<input type="checkbox" :checked="!item.comment.pinned" @change="togglePinned(item.comment)">
|
||||||
|
<i :class="item.comment.pinned ? 'fa-solid fa-thumbtack' : 'fa-solid fa-up-down-left-right'"></i>
|
||||||
|
</label>
|
||||||
|
<label v-else class="drawing-state" :class="{off: !item.line.armed}" @click.stop
|
||||||
|
:title="item.line.armed ? 'Armed — click to disarm' : 'Tripped — click to re-arm'">
|
||||||
|
<input type="checkbox" :checked="item.line.armed" @change="setArmed(item.line, $event.target.checked)">
|
||||||
|
<i :class="item.line.armed ? 'fa-solid fa-bell' : 'fa-solid fa-bell-slash'"></i>
|
||||||
|
</label>
|
||||||
|
<button class="drawing-delete" @click.stop="deleteDrawing(item)" aria-label="Delete drawing" title="Delete">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span v-if="item.kind === 'comment'">#{{ item.number }} · comment · {{ item.comment.pinned ? 'pinned' : 'floating' }}</span>
|
<div class="drawing-secondary">
|
||||||
<span v-else-if="item.kind === 'level'">#{{ item.number }} · level {{ item.line.anchor_p.toFixed(2) }}</span>
|
<span v-if="item.kind === 'comment'">#{{ item.number }} · NOTE · {{ item.comment.pinned ? 'PIN' : 'FLOAT' }}</span>
|
||||||
<span v-else>#{{ item.number }} · {{ item.line.tf }} · {{ item.line.side }}</span>
|
<span v-else-if="item.kind === 'level'">#{{ item.number }} · L {{ item.line.anchor_p.toFixed(2) }}</span>
|
||||||
|
<span v-else>#{{ item.number }} · {{ item.line.tf }} · {{ item.line.side === 'support' ? '↑' : '↓' }}</span>
|
||||||
|
|
||||||
<label v-if="item.kind === 'comment'" class="armed-toggle" @click.stop
|
<div class="drawing-controls" @click.stop v-if="item.kind !== 'comment'">
|
||||||
:title="item.comment.pinned ? 'Pinned to its bar — scrolls with the chart' : 'Floating — always on screen'">
|
<details class="color-picker">
|
||||||
<input type="checkbox" :checked="!item.comment.pinned" @change="togglePinned(item.comment)">
|
<summary aria-label="Choose drawing color" title="Choose color"
|
||||||
{{ item.comment.pinned ? 'pinned' : 'float' }}
|
:style="{ backgroundColor: item.line.color || '#65b7cf' }"></summary>
|
||||||
</label>
|
<div class="color-popover">
|
||||||
<label v-else class="armed-toggle" :class="{off: !item.line.armed}" @click.stop
|
<button v-for="color in drawingColors" :key="color" type="button"
|
||||||
:title="item.line.armed ? 'Armed — will alert once, then disarm' : 'Tripped — re-arm to alert again'">
|
:class="{selected: color === item.line.color}" :style="{backgroundColor: color}"
|
||||||
<input type="checkbox" :checked="item.line.armed" @change="setArmed(item.line, $event.target.checked)">
|
:aria-label="`Use color ${color}`"
|
||||||
{{ item.line.armed ? 'armed' : 'tripped' }}
|
@click.stop="updateLineStyle(item.line, {color}); $event.currentTarget.closest('details').open = false"></button>
|
||||||
</label>
|
<label class="custom-color" title="Custom color">
|
||||||
|
<i class="fa-solid fa-palette"></i>
|
||||||
<button @click.stop="deleteDrawing(item)" aria-label="Delete drawing">Delete</button>
|
<input type="color" :value="item.line.color || '#65b7cf'" aria-label="Custom drawing color"
|
||||||
|
@change="updateLineStyle(item.line, {color: $event.target.value})">
|
||||||
<div class="line-style-controls" @click.stop v-if="item.kind !== 'comment'">
|
</label>
|
||||||
<input type="color" :value="item.line.color || '#65b7cf'" aria-label="Drawing color" @change="updateLineStyle(item.line, { color: $event.target.value })">
|
</div>
|
||||||
<select :value="item.line.line_width || 2" aria-label="Drawing width" @change="updateLineStyle(item.line, { line_width: Number($event.target.value) })"><option v-for="width in [1,2,3,4]" :value="width">{{ width }}px</option></select>
|
</details>
|
||||||
</div>
|
<select :value="item.line.line_width || 2" aria-label="Drawing width"
|
||||||
<div class="line-style-controls" @click.stop v-else>
|
@change="updateLineStyle(item.line, {line_width: Number($event.target.value)})">
|
||||||
<input type="color" :value="item.comment.color || '#c8992f'" aria-label="Comment colour" @change="updateLineStyle(item.comment, { color: $event.target.value })">
|
<option v-for="width in [1,2,3,4]" :value="width">{{ width }}</option>
|
||||||
<button class="collapse-toggle" @click.stop="toggleComment(item.comment)">{{ item.comment.collapsed ? 'expand' : 'collapse' }}</button>
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="drawing-controls" @click.stop v-else>
|
||||||
|
<details class="color-picker">
|
||||||
|
<summary aria-label="Choose comment color" title="Choose color"
|
||||||
|
:style="{ backgroundColor: item.comment.color || '#c8992f' }"></summary>
|
||||||
|
<div class="color-popover">
|
||||||
|
<button v-for="color in drawingColors" :key="color" type="button"
|
||||||
|
:class="{selected: color === item.comment.color}" :style="{backgroundColor: color}"
|
||||||
|
:aria-label="`Use color ${color}`"
|
||||||
|
@click.stop="updateLineStyle(item.comment, {color}); $event.currentTarget.closest('details').open = false"></button>
|
||||||
|
<label class="custom-color" title="Custom color">
|
||||||
|
<i class="fa-solid fa-palette"></i>
|
||||||
|
<input type="color" :value="item.comment.color || '#c8992f'" aria-label="Custom comment color"
|
||||||
|
@change="updateLineStyle(item.comment, {color: $event.target.value})">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<button class="collapse-toggle" @click.stop="toggleComment(item.comment)"
|
||||||
|
:title="item.comment.collapsed ? 'Expand comment' : 'Collapse comment'">
|
||||||
|
<i :class="item.comment.collapsed ? 'fa-solid fa-expand' : 'fa-solid fa-compress'"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,11 @@ button { border:1px solid var(--line); background:transparent; color:var(--muted
|
||||||
.statusbar { min-height:34px; display:flex; align-items:center; gap:24px; padding:6px 13px; border-top:1px solid var(--line); color:var(--muted); font-size:10px; }.statusbar b { color:var(--fg); text-transform:uppercase; }.data-freshness { margin-left:auto; white-space:nowrap; }
|
.statusbar { min-height:34px; display:flex; align-items:center; gap:24px; padding:6px 13px; border-top:1px solid var(--line); color:var(--muted); font-size:10px; }.statusbar b { color:var(--fg); text-transform:uppercase; }.data-freshness { margin-left:auto; white-space:nowrap; }
|
||||||
aside { padding:16px; }h2 { margin:0 0 12px; color:var(--muted); font-size:11px; text-transform:uppercase; letter-spacing:1.3px; }h2:not(:first-child) { margin-top:30px; }.empty { border-left:2px solid var(--line); padding:10px 12px; color:var(--muted); font-size:11px; }
|
aside { padding:16px; }h2 { margin:0 0 12px; color:var(--muted); font-size:11px; text-transform:uppercase; letter-spacing:1.3px; }h2:not(:first-child) { margin-top:30px; }.empty { border-left:2px solid var(--line); padding:10px 12px; color:var(--muted); font-size:11px; }
|
||||||
.sidebar-section { margin-top:30px; }.sidebar-section:first-of-type { margin-top:0; }.sidebar-section summary { margin-bottom:12px; color:var(--muted); font-size:11px; text-transform:uppercase; letter-spacing:1.3px; cursor:pointer; user-select:none; }.sidebar-section:not([open]) summary { margin-bottom:0; }
|
.sidebar-section { margin-top:30px; }.sidebar-section:first-of-type { margin-top:0; }.sidebar-section summary { margin-bottom:12px; color:var(--muted); font-size:11px; text-transform:uppercase; letter-spacing:1.3px; cursor:pointer; user-select:none; }.sidebar-section:not([open]) summary { margin-bottom:0; }
|
||||||
.trendline-actions { display:flex; gap:5px; margin-bottom:7px; }.trendline-actions button { flex:1; padding:4px; font-size:9px; }.trendline-row { display:grid; grid-template-columns:auto minmax(0,1fr) auto; gap:5px 8px; padding:7px; border:1px solid transparent; }.trendline-row.selected { border-color:var(--accent); }.trendline-row>.line-select { align-self:center; accent-color:var(--accent); }.trendline-row>input:not(.line-select) { min-width:0; border:0; border-bottom:1px solid var(--line); background:transparent; color:var(--fg); font:inherit; font-size:11px; }.trendline-row span:not(.drawing-icon) { grid-column:2; color:var(--muted); font-size:9px; text-transform:uppercase; }.trendline-row>.drawing-icon { grid-column:1; align-self:center; }.trendline-row button { grid-column:3; grid-row:1; padding:3px 6px; font-size:9px; }.line-style-controls { grid-column:3; display:flex; align-items:center; gap:4px; }.line-style-controls input { width:24px; height:20px; padding:0; border:0; background:transparent; }.line-style-controls select { border:1px solid var(--line); background:var(--panel); color:var(--fg); font-size:9px; }
|
.trendline-actions { display:flex; gap:5px; margin-bottom:7px; }.trendline-actions button { flex:1; padding:4px; font-size:9px; }
|
||||||
|
.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>.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; }.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:91px; 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; }.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; }
|
||||||
.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:flex; flex-wrap:nowrap; gap:7px; padding-left:20px; }.periods label { color:var(--muted); gap:4px; }.periods input { width:12px; height:12px; margin:0; flex:none; }.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:flex; flex-wrap:nowrap; gap:7px; padding-left:20px; }.periods label { color:var(--muted); gap:4px; }.periods input { width:12px; height:12px; margin:0; flex:none; }.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; }
|
||||||
.hint { margin:6px 0 2px; font-size:10px; color:var(--muted); line-height:1.35; }
|
.hint { margin:6px 0 2px; font-size:10px; color:var(--muted); line-height:1.35; }
|
||||||
|
|
||||||
|
|
@ -48,8 +52,7 @@ aside { padding:16px; }h2 { margin:0 0 12px; color:var(--muted); font-size:11px;
|
||||||
.chart-price-tag { position:absolute; z-index:5; transform:translateY(-50%); padding:2px 6px; border-radius:4px;
|
.chart-price-tag { position:absolute; z-index:5; transform:translateY(-50%); padding:2px 6px; border-radius:4px;
|
||||||
background:#e0a34a; color:#1a1206; font-size:11px; font-variant-numeric:tabular-nums; pointer-events:none; }
|
background:#e0a34a; color:#1a1206; font-size:11px; font-variant-numeric:tabular-nums; pointer-events:none; }
|
||||||
.arm-hint { color:var(--accent); }
|
.arm-hint { color:var(--accent); }
|
||||||
.armed-toggle { display:flex; align-items:center; gap:4px; font-size:10px; color:var(--accent); cursor:pointer; }
|
.optional { color:var(--muted); }.disabled { opacity:.45; }.score-hidden { margin-top:11px; color:var(--muted); line-height:1.25; }
|
||||||
.armed-toggle.off { color:var(--muted); text-decoration:line-through; }.optional { color:var(--muted); }.disabled { opacity:.45; }.score-hidden { margin-top:11px; color:var(--muted); line-height:1.25; }
|
|
||||||
.cluster { margin:8px 0; padding:10px; border:1px solid var(--line); border-left:3px solid var(--green); background:var(--chart-bg); }.cluster.resistance { border-left-color:var(--red); }.cluster-top { display:flex; justify-content:space-between; text-transform:uppercase; font-size:10px; }.cluster-top strong { color:var(--accent); font-size:16px; }.zone { margin:4px 0; font-size:15px; }.members,.distance { color:var(--muted); font-size:9px; }.distance { margin-top:5px; }.alert-entry { white-space:pre-line; margin:8px 0; padding:9px; background:color-mix(in srgb,var(--accent) 8%,transparent); font-size:10px; }.alert-entry time { display:block; color:var(--accent); margin-bottom:4px; }
|
.cluster { margin:8px 0; padding:10px; border:1px solid var(--line); border-left:3px solid var(--green); background:var(--chart-bg); }.cluster.resistance { border-left-color:var(--red); }.cluster-top { display:flex; justify-content:space-between; text-transform:uppercase; font-size:10px; }.cluster-top strong { color:var(--accent); font-size:16px; }.zone { margin:4px 0; font-size:15px; }.members,.distance { color:var(--muted); font-size:9px; }.distance { margin-top:5px; }.alert-entry { white-space:pre-line; margin:8px 0; padding:9px; background:color-mix(in srgb,var(--accent) 8%,transparent); font-size:10px; }.alert-entry time { display:block; color:var(--accent); margin-bottom:4px; }
|
||||||
@media (max-width:850px) { #app { padding:10px; }.chart-shell { min-width:0; }main { grid-template-columns:1fr; }.drawing-tools { flex-wrap:wrap; }.drawing-tools .line-name { width:110px; }#chart { height:55vh; min-height:360px; }aside { min-height:180px; }header { height:40px; }.chart-head { align-items:flex-start; flex-direction:column; }.timeframes { justify-content:flex-start; }.timeframes button { padding:5px 8px; } }
|
@media (max-width:850px) { #app { padding:10px; }.chart-shell { min-width:0; }main { grid-template-columns:1fr; }.drawing-tools { flex-wrap:wrap; }.drawing-tools .line-name { width:110px; }#chart { height:55vh; min-height:360px; }aside { min-height:180px; }header { height:40px; }.chart-head { align-items:flex-start; flex-direction:column; }.timeframes { justify-content:flex-start; }.timeframes button { padding:5px 8px; } }
|
||||||
/* --- chart comments ---------------------------------------------------- */
|
/* --- chart comments ---------------------------------------------------- */
|
||||||
|
|
@ -72,8 +75,6 @@ aside { padding:16px; }h2 { margin:0 0 12px; color:var(--muted); font-size:11px;
|
||||||
.drawing-filters select, .drawing-filters input { font:inherit; font-size:10px; padding:4px 6px;
|
.drawing-filters select, .drawing-filters input { font:inherit; font-size:10px; padding:4px 6px;
|
||||||
border:1px solid var(--line); border-radius:5px; background:transparent; color:var(--fg); min-width:0; }
|
border:1px solid var(--line); border-radius:5px; background:transparent; color:var(--fg); min-width:0; }
|
||||||
.drawing-icon { display:flex; align-items:center; justify-content:center; color:var(--muted); font-size:10px; }
|
.drawing-icon { display:flex; align-items:center; justify-content:center; color:var(--muted); font-size:10px; }
|
||||||
.collapse-toggle { font:inherit; font-size:9px; padding:2px 6px; border:1px solid var(--line);
|
|
||||||
border-radius:4px; background:transparent; color:var(--muted); cursor:pointer; }
|
|
||||||
.tool-body textarea { width:100%; min-width:0; font:inherit; font-size:11px; padding:4px 6px; resize:vertical;
|
.tool-body textarea { width:100%; min-width:0; font:inherit; font-size:11px; padding:4px 6px; resize:vertical;
|
||||||
border:1px solid var(--line); border-radius:5px; background:transparent; color:var(--fg); }
|
border:1px solid var(--line); border-radius:5px; background:transparent; color:var(--fg); }
|
||||||
.side-auto { align-self:end; padding-bottom:6px; font-size:10px; color:var(--muted); }.side-auto b { color:var(--fg); font-weight:600; }
|
.side-auto { align-self:end; padding-bottom:6px; font-size:10px; color:var(--muted); }.side-auto b { color:var(--fg); font-weight:600; }
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,20 @@ test('editing a drawing name with Backspace or Delete cannot delete the drawing'
|
||||||
await page.fill('input[aria-label="Filter drawings by text"]', label);
|
await page.fill('input[aria-label="Filter drawings by text"]', label);
|
||||||
const input = page.locator('input[aria-label="Drawing name"]');
|
const input = page.locator('input[aria-label="Drawing name"]');
|
||||||
assert.equal(await input.count(), 1, 'the unique drawing filter did not isolate the test line');
|
assert.equal(await input.count(), 1, 'the unique drawing filter did not isolate the test line');
|
||||||
|
const row = page.locator('.trendline-row');
|
||||||
|
const rowBox = await row.boundingBox();
|
||||||
|
assert.ok(rowBox.height <= 48, `drawing row is still ${rowBox.height}px tall`);
|
||||||
|
assert.equal(await row.locator('.drawing-state .fa-bell').count(), 1,
|
||||||
|
'the armed state is not represented by a bell');
|
||||||
|
await row.locator('.color-picker>summary').click();
|
||||||
|
assert.equal(await row.locator('.color-popover>button').count(), 16,
|
||||||
|
'the preset palette does not contain 16 colors');
|
||||||
|
assert.equal(await row.locator('.custom-color input[type="color"]').count(), 1,
|
||||||
|
'the custom color choice is missing');
|
||||||
|
await row.locator('button[aria-label="Use color #bd4545"]').click();
|
||||||
|
await page.waitForFunction(([id, color]) =>
|
||||||
|
window.__chart.levels.find(level => level.id === id)?.color === color,
|
||||||
|
[created, '#bd4545']);
|
||||||
await input.focus();
|
await input.focus();
|
||||||
await input.press('End');
|
await input.press('End');
|
||||||
await input.press('Backspace');
|
await input.press('Backspace');
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,8 @@ test('the snapped extreme decides the side, overriding the dropdown', { timeout:
|
||||||
test('dragging an anchor uses the same bar-extreme snapping as placement',
|
test('dragging an anchor uses the same bar-extreme snapping as placement',
|
||||||
{ timeout: 180000 }, async () => {
|
{ timeout: 180000 }, async () => {
|
||||||
await withChart(async page => {
|
await withChart(async page => {
|
||||||
|
const beforeNumber = await page.evaluate(() => Math.max(0, ...window.__chart.levels
|
||||||
|
.filter(level => level.kind === 'manual').map(level => level.number)));
|
||||||
const box = await chartBox(page);
|
const box = await chartBox(page);
|
||||||
await armTool(page, 'Trendline');
|
await armTool(page, 'Trendline');
|
||||||
const first = at(box, 0.30, 0.55);
|
const first = at(box, 0.30, 0.55);
|
||||||
|
|
@ -124,8 +126,12 @@ test('dragging an anchor uses the same bar-extreme snapping as placement',
|
||||||
await page.mouse.click(first.x, first.y);
|
await page.mouse.click(first.x, first.y);
|
||||||
await page.waitForTimeout(300);
|
await page.waitForTimeout(300);
|
||||||
await page.mouse.click(second.x, second.y);
|
await page.mouse.click(second.x, second.y);
|
||||||
await page.waitForFunction(() =>
|
await page.waitForFunction(number => {
|
||||||
document.querySelectorAll('.chart-anchor:not([hidden])').length === 2);
|
const c = window.__chart;
|
||||||
|
const selected = c.levels.find(level => level.id === c.selectedLineId);
|
||||||
|
return selected?.number > number
|
||||||
|
&& document.querySelectorAll('.chart-anchor:not([hidden])').length === 2;
|
||||||
|
}, beforeNumber);
|
||||||
|
|
||||||
const target = await page.evaluate(() => {
|
const target = await page.evaluate(() => {
|
||||||
const c = window.__chart;
|
const c = window.__chart;
|
||||||
|
|
@ -188,7 +194,7 @@ test('future whitespace accepts endpoint and whole-line movement without enterin
|
||||||
return {
|
return {
|
||||||
id: line.id,
|
id: line.id,
|
||||||
x: plot.left + c.coordinateAtIndex(index),
|
x: plot.left + c.coordinateAtIndex(index),
|
||||||
y: plot.top + c.candles.priceToCoordinate(c.linePriceAtIndex(line, index)),
|
y: plot.top + plot.height * 0.45,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const endHandle = await page.locator(
|
const endHandle = await page.locator(
|
||||||
|
|
@ -280,9 +286,11 @@ test('the line context menu duplicates by ten bars and deletes through the norma
|
||||||
{ button: 'right' });
|
{ button: 'right' });
|
||||||
await page.locator('.chart-context-menu [data-action="duplicate"]').click();
|
await page.locator('.chart-context-menu [data-action="duplicate"]').click();
|
||||||
await page.waitForFunction(number => {
|
await page.waitForFunction(number => {
|
||||||
const lines = window.__chart.levels.filter(level =>
|
const c = window.__chart;
|
||||||
|
const lines = c.levels.filter(level =>
|
||||||
level.kind === 'manual' && level.slope !== 0);
|
level.kind === 'manual' && level.slope !== 0);
|
||||||
return lines.some(level => level.number > number);
|
const duplicate = lines.find(level => level.number > number);
|
||||||
|
return duplicate && c.selectedLineId === duplicate.id;
|
||||||
}, original.number);
|
}, original.number);
|
||||||
|
|
||||||
const duplicated = await page.evaluate(originalLine => {
|
const duplicated = await page.evaluate(originalLine => {
|
||||||
|
|
@ -297,6 +305,8 @@ test('the line context menu duplicates by ten bars and deletes through the norma
|
||||||
line,
|
line,
|
||||||
anchorShift: nearestIndex(line.anchor_t) - nearestIndex(original.anchor_t),
|
anchorShift: nearestIndex(line.anchor_t) - nearestIndex(original.anchor_t),
|
||||||
endShift: nearestIndex(line.last_t) - nearestIndex(original.last_t),
|
endShift: nearestIndex(line.last_t) - nearestIndex(original.last_t),
|
||||||
|
originalTitle: c.levelSeries.get(original.id).series.options().title,
|
||||||
|
duplicateTitle: c.levelSeries.get(line.id).series.options().title,
|
||||||
};
|
};
|
||||||
}, original);
|
}, original);
|
||||||
assert.equal(duplicated.anchorShift, 10);
|
assert.equal(duplicated.anchorShift, 10);
|
||||||
|
|
@ -304,6 +314,8 @@ test('the line context menu duplicates by ten bars and deletes through the norma
|
||||||
const expectedName = `${original.side === 'support' ? 'up' : 'down'}${original.tf}`;
|
const expectedName = `${original.side === 'support' ? 'up' : 'down'}${original.tf}`;
|
||||||
assert.equal(original.label, expectedName);
|
assert.equal(original.label, expectedName);
|
||||||
assert.equal(duplicated.line.label, `${expectedName} 2`);
|
assert.equal(duplicated.line.label, `${expectedName} 2`);
|
||||||
|
assert.equal(duplicated.originalTitle, `#${original.number}`);
|
||||||
|
assert.equal(duplicated.duplicateTitle, duplicated.line.label);
|
||||||
assert.equal(duplicated.line.color, original.color);
|
assert.equal(duplicated.line.color, original.color);
|
||||||
assert.equal(duplicated.line.line_width, original.line_width);
|
assert.equal(duplicated.line.line_width, original.line_width);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue